Ruby on Rails開発 on OpenShift

こんにちは。Red Hatの森 (@mosuke5) です。
今日はRuby on RailsのOpenShift上での開発について少しみていきたいと思います。 自分がまさにそうなのですが、生のKubernetesを先に学んだあとにOpenShiftにはいると、結構違う部分もあってはじめ理解が難しい部分もあります。 皆さん馴染みあるRubyで(?) 簡単な開発環境を作ってみて確認していきます。

4月に入社した身として、触っていく中でよくわからなかった部分、気になったを中心に書いているので、 OpenShift初心者の方に少しでも参考になればと思います。

minishiftを起動しよう

OpenShiftの環境は、一番用意しやすいであろうminishift(v1.33.0+ba29431)を使いました。 minikubeのopenshift版と思っていただければ大丈夫です。ローカル端末の仮想マシンの上にOpenShiftが動く環境を立ち上げられるものです。

本記事ではminishiftのインストールについては割愛しますが、下記を参考にインストールしてみてください。

Overview - Getting Started | Minishift | OKD Latest

ocクライアントのバージョンは下記のとおりです。

$ oc version
oc v3.11.107
kubernetes v1.11.0+d4cacc0
features: Basic-Auth

Server https://192.168.64.7:8443
kubernetes v1.11.0+d4cacc

RailsをOpenShift上で動作させる

ドキュメントを探していると、OpenShift上で動かせるRailsのアプリのサンプルを見つけました。
しかし、この手順の通り試しても抽象度が高すぎて「動いたけどよくわからん!」みたいな状態になったので、 ポイントを解説しながらみていきたいと思います。それがこの記事を書こうと思ったモチベ。

github.com

oc new-appというコマンド

OpenShiftにはoc new-appというただのPodやDeploymentだけでなくアプリケーションを作るためのコマンドが用意されています。 あんまり実運用では利用されることは少ないかもしれませんが、今回のような開発環境のセットアップなどでは便利です。 しかし、このコマンドは良くも悪くもいろんな動きをするので理解が難しい一面もあります。

1つとして、OpenShiftなどで用意しているテンプレートからアプリケーションを実行することができます。
どんなテンプレートがあるかは下記のように確認できます。Rails+PostgreSQLのセットになったもの、Jenkinsなどいくつかあります。

$ oc get template -n openshift
NAME                       DESCRIPTION                                                                        PARAMETERS        OBJECTS
cakephp-mysql-persistent   An example CakePHP application with a MySQL database. For more information ab...   20 (4 blank)      9
dancer-mysql-persistent    An example Dancer application with a MySQL database. For more information abo...   17 (5 blank)      9
django-psql-persistent     An example Django application with a PostgreSQL database. For more informatio...   20 (5 blank)      9
...

テンプレートの実体は、kubernetesのマニフェストファイルでリソースの集合体です。OpenShiftにはTemplateというリソースがあります。 マニフェストファイルの中のパラメータなどを変数化することが可能で、データベースなどのシークレット情報をパラメータ化して配布することなど可能です。

access.redhat.com

実際にテンプレートの中身は下記のようにして見ることができます。
試しにpostgresqlのテンプレートをみてみますが、SecretやDeploymentConfig、Serviceのリソースが記載されているのがわかるかと思います。 また、テンプレートを使って起動する際にどんなパラメータを指定できるかなど確認できるので便利です。

$ oc get template -n openshift postgresql-persistent -o yaml | less

Railsアプリの起動

今回は上で紹介したRailsのサンプルのアプリケーションのレポジトリがあったので、こちらでRailsアプリの方の起動を行っていきます。 レポジトリをfork & cloneして、テンプレートで起動してみます。一旦この場は思考停止で実行してみます。

$ git clone git@github.com:mosuke5/rails-ex.git
$ oc new-project rails-dev
$ oc new-app openshift/templates/rails-postgresql.json -p SOURCE_REPOSITORY_URL=https://github.com/mosuke5/rails-ex 
--> Deploying template "rails-dev/rails-postgresql-example" for "openshift/templates/rails-postgresql.json" to project rails-dev

     Rails + PostgreSQL (Ephemeral)
     ---------
(略)
--> Creating resources ...
    secret "rails-postgresql-example" created
    service "rails-postgresql-example" created
    route.route.openshift.io "rails-postgresql-example" created
    imagestream.image.openshift.io "rails-postgresql-example" created
    buildconfig.build.openshift.io "rails-postgresql-example" created
    deploymentconfig.apps.openshift.io "rails-postgresql-example" created
    service "postgresql" created
    deploymentconfig.apps.openshift.io "postgresql" created
--> Success
    Access your application via route 'rails-postgresql-example-rails-dev.192.168.64.7.nip.io'
    Build scheduled, use 'oc logs -f bc/rails-postgresql-example' to track its progress.
    Run 'oc status' to view your app.

実行後にpodの状態をみてみると、rails-postgresql-example-1-buildというbuildのpodが動いているのがわかります。 こちらは後ほど説明しますが、いまRailsのアプリケーションのイメージを作っている最中です。

$ oc get pod
NAME                               READY     STATUS    RESTARTS   AGE
postgresql-1-mwm94                 1/1       Running   0          55s
rails-postgresql-example-1-build   1/1       Running   0          58s

// 終わるとアプリケーション本体のPodが起動する
$ oc get pod
NAME                               READY     STATUS      RESTARTS   AGE
postgresql-1-mwm94                 1/1       Running     0          2m
rails-postgresql-example-1-build   0/1       Completed   0          3m
rails-postgresql-example-1-md8bx   1/1       Running     0          17s

起動が終わったあとに、routeを確認してアクセスしてみると。アプリケーションが動作しているのがわかります。 routeingressに相当するような概念で、外部からPodへのアクセスの経路を確保します。

rails-postgresql-example-rails-dev.192.168.64.7.nip.ioは仮のドメイン名なので、/etc/hostsに192.168.64.7 rails-postgresql-example-rails-dev.192.168.64.7.nip.ioと書いてあげればアクセスできますね。
もちろん、oc port-forward service/rails-postgresql-example 8080:8080のようにport-forwardを使ってアクセスしても問題ありません。

$ oc get route
NAME                       HOST/PORT                                                PATH      SERVICES                   PORT      TERMINATION   WILDCARD
rails-postgresql-example   rails-postgresql-example-rails-dev.192.168.64.7.nip.io             rails-postgresql-example   <all>                   None

/articlesなどアクセスすれば、記事の登録もできDB連携もできていることが確認できます。
さて、まあここまでやれば「なんか動いた!」という感じなわけですが、何が起きたのか少し追っていきたいと思います。

まずは作成されたリソースをみてみます。

NAME                                   READY     STATUS      RESTARTS   AGE
pod/postgresql-1-mwm94                 1/1       Running     0          9m
pod/rails-postgresql-example-1-build   0/1       Completed   0          9m
pod/rails-postgresql-example-1-md8bx   1/1       Running     0          7m

NAME                                               DESIRED   CURRENT   READY     AGE
replicationcontroller/postgresql-1                 1         1         1         9m
replicationcontroller/rails-postgresql-example-1   1         1         1         7m

NAME                               TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
service/postgresql                 ClusterIP   172.30.66.38   <none>        5432/TCP   9m
service/rails-postgresql-example   ClusterIP   172.30.72.44   <none>        8080/TCP   9m

NAME                                                          REVISION   DESIRED   CURRENT   TRIGGERED BY
deploymentconfig.apps.openshift.io/postgresql                 1          1         1         config,image(postgresql:10)
deploymentconfig.apps.openshift.io/rails-postgresql-example   1          1         1         config,image(rails-postgresql-example:latest)

NAME                                                      TYPE      FROM      LATEST
buildconfig.build.openshift.io/rails-postgresql-example   Source    Git       1

NAME                                                  TYPE      FROM          STATUS     STARTED         DURATION
build.build.openshift.io/rails-postgresql-example-1   Source    Git@67d882b   Complete   9 minutes ago   2m39s

NAME                                                      DOCKER REPO                                          TAGS      UPDATED
imagestream.image.openshift.io/rails-postgresql-example   172.30.1.1:5000/rails-dev/rails-postgresql-example   latest    7 minutes ago

NAME                                                HOST/PORT                                                PATH      SERVICES                   PORT      TERMINATION   WILDCARD
route.route.openshift.io/rails-postgresql-example   rails-postgresql-example-rails-dev.192.168.64.7.nip.io             rails-postgresql-example   <all>                   None

なにやらいろいろと作成されていますが、取り上げてみていくのが、deploymentconfigとbuildconfigとimagestreamです。

DeploymentConfig

まずはDeploymentConfigをみていきます。DeploymentConfig(dcと略せる)はOpenShift特有のリソースで、 KubernetesのDeploymentに該当するような機能で、trigger機能などがいくつかの機能が拡充されています。

気になるポイントの1つ目としては、strategyのなかのpreです。 Railsアプリケーションではデータベースを使う場合に、bundle exec rails db:migrateでテーブルのスキーマを作っていきますが、今回それを実行することなくアプリケーションの動作までいけていました。どこで実行されていたのか?というとここです。migrate-database.shが用意されていて、Podが新しくできるときにシェルを実行していました。

次に気になるポイントは、triggersの部分です。この設定では、イメージの変更をトリガーに更新できる設定が入っています。 後述するBuildConfigが新しいイメージを作成すると、それをトリガーにDeploymentConfigがPodを入れ替えてくれるわけです。

$ oc get dc
NAME         REVISION   DESIRED   CURRENT   TRIGGERED BY
postgresql   1          1         1         config,image(postgresql:10)
rails-app    1          1         1         config,image(rails-app:latest)

$ oc get dc rrails-postgresql-example -o yaml | less
...
  strategy:
    activeDeadlineSeconds: 21600
    recreateParams:
      pre:
        execNewPod:
          command:
          - ./migrate-database.sh
          containerName: rails-postgresql-example
        failurePolicy: Abort
      timeoutSeconds: 600
    resources: {}
    type: Recreate
(略)
triggers:
  - type: ConfigChange
  - imageChangeParams:
      automatic: true
      containerNames:
      - rails-app
      from:
        kind: ImageStreamTag
        name: rails-app:latest
        namespace: rails-dev
      lastTriggeredImage: 172.30.1.1:5000/rails-dev/rails-app@sha256:487b72449aa58cd0ef56252a72d26e2aac98e2c63699042becd5eb2ae3d88053
    type: ImageChange

BuildConfig

続いてBuildConfig(bcと略せる)をみてみます。
今回稼働させたRailsアプリケーションのコンテナイメージを作成するためのビルドの設定がこちらです。 生のKubernetesではKubernetesとは別のプロセスとしてコンテナイメージを作成することが一般的かと思います。 OpenShiftでは、S2I (Source to Image)という機能があり、開発者がコンテナイメージを作らなくても、書いたコードをOpenShiftで動かすためのワークロードが用意されています。 そのビルドの設定がBuildConfigです。oc new-appの時に指定した SOURCE_REPOSITORY_URLの中身をみてアプリケーションの種別(RubyなのかJavaなのか)を判別して、イメージをビルドしてくれました。 似ているものとしては、buildpack, herokuなどが近いでしょうか。

$ oc get bc
NAME        TYPE      FROM              LATEST
rails-app   Source    Git@blog-sample   1

$ oc get bc rails-app -o yaml | less
source:
    git:
      ref: blog-sample
      uri: https://github.com/mosuke5/rails-ex.git
    type: Git
  strategy:
    sourceStrategy:
      from:
        kind: ImageStreamTag
        name: ruby:2.5
        namespace: openshift
    type: Source
S2Iについてもっと知りたい方向けリンク

ImageStream

続いてImageStream (isと略せる)ですが、こちらも生のKubernetesにはないリソースです。ImageStreamは概ねDocker Registryと考えていただいて大丈夫ですが、 コンテナイメージを抽象化した概念になります。 特徴としては、コンテナイメージをプッシュした履歴が残る点です。今回、2回ビルドを行い、どちらもlatestタグでプッシュしましたが、イメージのハッシュ値で別々に管理されているのがわかります。そのため、今回のようにイメージのタグを特に考えずlatestのままでしたが、適切にPodの入れ替えが行われたということです。

$ oc get is rails-postgresql-example -o yaml | less
...
status:
  dockerImageRepository: 172.30.1.1:5000/rails-dev/rails-postgresql-example
  tags:
  - items:
    - created: 2019-07-09T08:40:53Z
      dockerImageReference: 172.30.1.1:5000/rails-dev/rails-postgresql-example@sha256:1d42310b4182199b471839108b278c646b99e3e731af783a1d0f5e4fca22e1d7
      generation: 1
      image: sha256:1d42310b4182199b471839108b278c646b99e3e731af783a1d0f5e4fca22e1d7
    - created: 2019-07-09T07:42:45Z
      dockerImageReference: 172.30.1.1:5000/rails-dev/rails-postgresql-example@sha256:ca0141e090f7fd82ff665b6f87a7e099b920fa6a04d8dcf5263ddfccfad9f9ec
      generation: 1
      image: sha256:ca0141e090f7fd82ff665b6f87a7e099b920fa6a04d8dcf5263ddfccfad9f9ec
    tag: latest

アプリケーションの変更

適当にアプリケーションを変更してgithubへpushします。
せっかくなので、データベースのスキーマも変更するような修正を入れたいと思います。 scaffoldを使ってユーザモデルとコントローラを作ります。

$ bundle exec rails generate scaffold user name:string age:integer
$ bundle exec rake routes
bundle exec rake routes
              Prefix Verb   URI Pattern                                       Controller#Action
               users GET    /users(.:format)                                  users#index
                     POST   /users(.:format)                                  users#create
            new_user GET    /users/new(.:format)                              users#new
           edit_user GET    /users/:id/edit(.:format)                         users#edit
                user GET    /users/:id(.:format)                              users#show
                     PATCH  /users/:id(.:format)                              users#update
                     PUT    /users/:id(.:format)                              users#update
                     DELETE /users/:id(.:format)                              users#destroy
$ git add .
$ git commit -m 'update'
$ git push origin master

それではこの変更をOpenShift上のRailsアプリケーションに反映させたいと思います。
oc new-appで作られたBuildConfigなどはもちろんあとから実行が可能です。oc start-build で実行してみます。 rails-postgresql-example-2-buildという2回目の実行のbuildコンテナが動いているのがわかります。

$ oc start-build rails-postgresql-example
$ oc get pod -w
NAME                               READY     STATUS      RESTARTS   AGE
postgresql-1-mwm94                 1/1       Running     0          49m
rails-postgresql-example-1-build   0/1       Completed   0          49m
rails-postgresql-example-1-md8bx   1/1       Running     0          46m
rails-postgresql-example-2-build   1/1       Running   0         <invalid>

buildが終わるとImageStreamが更新され、それをトリガーにDeploymentConfigが新しいイメージでPodを入れ替えてくれました。 実際に、 http://rails-postgresql-example-rails-dev.192.168.64.7.nip.io/usersにアクセスするとユーザ情報を操作できる画面がでてきました。 データベースのスキーマも無事に変更してくれています。

まとめ

OpenShiftを触り始めて、チュートリアル的に行うnew-appコマンドを使ったアプリケーションの構築でしたが、 裏では様々な動きが行われていて、奥深さを感じるものでした。 また、OpenShiftを使った開発のフローや戦略がみえるものでもあるなあと感じます。

今日見てきたところはほんのごく一部の基礎的なところではありますが、これからもOpenShiftを使ったアプリケーション開発について情報発信できたらと思います。

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。