はじめに
kubernetes(k8s) クラスタを構築するためのインフラがあり、kubectl
コマンドで操作が出来る状態となっていることが前提です。
Rancher を使って構築する方法(勉強用ですが)について安いクラウド環境で RancherOS / Kubernetes を使って勉強用クラスタを作るも参照してみて下さい。
本記事について
色々な k8s の概念についての説明は本家ドキュメント、他記事や、本に書かれていることを参考にしてみて下さい。
この記事では概念の整理はせず、チュートリアル形式でアプリケーションを k8s で動作させることを目的として、なるべく 1 歩ずつ進めつつ概念理解が必要になったタイミングで説明していきます。
チュートリアルが終わった段階で k8s 上で特定のアプリケーションが公開できるようにするための関連知識を一通り理解できるようになることを目指します。
但し、チュートリアルではなるべく新しい概念や外部連携が少ない方法を選択するため、本番利用に適した設計とはなっていません。
アプリケーションはコンテナ化されていれば何でもよいですが、Rails アプリケーションを動作させることを目指してみます。
k8s 概要
k8s ではコンテナをどのように動作させるべきかを YAML 形式で定義します。
これをマニフェストと言います。
ここでの動作とは、コンテナの名前、コンテナイメージ、コンテナボリューム等の Docker コンテナでおなじみの設定に加えて、k8s システム内で何個起動するべきか、ラベル(負荷分散をするときのグループ名などとして使う)は何かなどを指します。
マニフェストという名前のとおり動作を宣言するものであり、起動してからどのようなコマンドを実行するといったシーケンスではありません。
コンテナを 1 つ起動する
k8s でコンテナを起動する最小単位は Pod です。
まずは Pod のマニフェストを記述して Rails アプリケーションを起動させることにします。
Pod は Workload リソースの 1 つです。
Workload リソースとは k8s におけるコンテナの動作を定義するリソースです。
Pod には 1 つ以上のコンテナが定義され、全てのコンテナは同一 IP アドレスを共有します。
Docker image をビルドする (RAILS_ENV=development)
まずは k8s コンテナとして起動する Rails アプリケーションの Docker image をビルドすることにします。
Rails アプリケーションは GitHub > rails-sample_app を使います。
RAILS_ENV=Production では S3 や SENDGRID 等の外部サービスを利用する設定を行う必要があるため、まずは RAILS_ENV=development で動作させて DB は sqlite を使うこととします。
FROM ruby:2.5.3
# install tools
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
postgresql-client \
apt-transport-https \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# install yarn
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN apt-get update \
&& apt-get -y install yarn \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# install node 10.x
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash -
RUN apt-get install -y nodejs \
&& apt-get clean
WORKDIR /usr/src/app
COPY Gemfile* ./
RUN bundle install --with development
COPY . .
EXPOSE 3000
CMD ["rails", "server", "-b", "0.0.0.0"]
使い方は Rails アプリケーションの TOP (Gemfile, Gemfile.lock が存在するディレクトリ) と同じディレクトリに Dockerfile を設置して docker build
を実行します。
$ docker build -t ruby-app .
# 作成が完了したことを確認する
$ docker image ls | grep ruby-app
ruby-app latest 93a74f4d19d9 7 seconds ago 1.19GB
Docker image が作成出来たら、k8s 上で起動する前に Docker run で正常に起動するか確認してみます。
尚、DB のマイグレーションを行うために entrypoint は上書きしています。
$ docker run --rm -p 3000:3000 --entrypoint /bin/bash ruby-app \
-c 'rails db:migrate && rails server -b 0.0.0.0'
# RailsアプリケーションのTOPにアクセスする
$ curl -I http://localhost:3000
# HTTP 200 OK が返ってくれば OK
Docker image をイメージリポジトリ (Docker Hub) に保存する
作成した Docker image を k8s から参照できる repository へ保存します。
今回は Docker hub へ保存することにします。
$ docker login # docker.ioにログインする
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: YOUR_ACCOUNT
Password: *****
WARNING! Your password will be stored unencrypted in /home/vagrant/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
# Docker hub のアカウント(YOUR_ACCOUNT)とリポジトリ名(REPOSITORY_NAME)とタグ名(TAG_NAME)は適宜設定してください。
$ docker image tag ruby-app YOUR_ACCOUNT/REPOSITORY_NAME:TAG_NAME
- Docker hub のアカウントが無ければ作成してから
docker login
すること - Repository を作成していなければ作成してから
docker push
すること
Rails アプリケーションを動作させる Pod リソースを作成する
Pod のマニフェストを作成していきます。
Pod に記述する内容は docker run で指定した内容とほぼ同じです。
apiVersion: v1
kind: Pod
metadata:
name: sample-pod-ruby
spec:
containers:
- name: rails-app
image: ryu310/rails-sample_app:20190418
command:
- /bin/bash
args:
- -c
- rails db:migrate && rails server -b 0.0.0.0
ファイルが作成出来たら k8s に Pod を作成します。
$ kubectl apply -f smaple-pod-ruby.yaml
適用されたらしばらくして Pod が Running
の状態となります。
起動している Pod の一覧を表示するために kubectl get pods コマンドが使えますが、ログファイルにおける tail -f のように、Pod の状態に変化があったタイミングで変更内容が随時表示される watch(-w) オプションをつけて確認するとよいでしょう。
$ kubectl get pods -w
NAME READY STATUS RESTARTS AGE
sample-pod-ruby 0/1 ContainerCreating 0 7s
sample-pod-ruby 1/1 Running 0 55s
Pod が持つ IP アドレス等の詳細情報を確認するためには kubectl describe
を実行します。
$ kubectl describe pod sample-pod-ruby
Name: sample-pod-ruby
Namespace: default
Priority: 0
PriorityClassName: <none>
Node: rancher-worker01/A.B.C.D
Start Time: Tue, 02 Apr 2019 02:18:47 +0900
Labels: <none>
Annotations: cni.projectcalico.org/podIP: 10.42.1.57/32
kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"name":"sample-pod-ruby","namespace":"default"},"spec":{"containers":[{"args":["-c...
Status: Running
IP: 10.42.1.57
Containers:
rails-app:
Container ID: docker://e84e4973a98c832f4d40be498c942c7d30390e568321e960c02a0639edf2c836
Image: YOUR_ACCOUNT/rails-sample_app:20190417
Image ID: docker-pullable://YOUR_ACCOUNT/rails-sample_app@sha256:ec734efc933de45469efd8094e854152a420d3c8c08d9aa77292b806baa46c7b
Port: <none>
Host Port: <none>
Command:
/bin/bash
Args:
-c
rails db:migrate && rails server -b 0.0.0.0
State: Running
Started: Tue, 02 Apr 2019 02:18:47 +0900
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-zvct6 (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
default-token-zvct6:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-zvct6
Optional: false
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s
node.kubernetes.io/unreachable:NoExecute for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 12m default-scheduler Successfully assigned default/sample-pod-ruby to rancher-worker01
Normal Pulled 12m kubelet, rancher-worker01 Container image "YOUR_ACCOUNT/rails-sample_app:20190417" already present on machine
Normal Created 12m kubelet, rancher-worker01 Created container
Normal Started 12m kubelet, rancher-worker01 Started container
また、Pod で動作する Container のログを表示するには kubectl logs
コマンドを実行する。
# ログを表示して終了する
$ kubectl logs sample-pod-ruby
# 継続してログを表示する(tail -f と同様の動作)
$ kubectl logs -f sample-pod-ruby
# 特定のコンテナのログを表示する
$ kubectl logs sample-pod-ruby -c rails-app
以上で Rails アプリケーションを Pod として起動する方法は終わりです。
尚、アプリケーションを停止する時は Pod を削除することになります。
$ kubectl delete pod sample-pod-ruby
Rails アプリケーションが起動したことを確認する
さて、起動した Rails アプリケーションに HTTP でアクセスしてみることにします。
しかし Pod が起動しただけでは Rails アプリケーションの 3000 番に対して k8s の外部からアクセスすることが出来ません。
そこで kubectl コマンドを実行しているホストの任意のポートを port forward し、Pod の 3000 番ポートへ接続して確認してみることにします。
$ kubectl port-forward pod/sample-pod-ruby 3000:3000
Forwarding from 127.0.0.1:3000 -> 3000
Forwarding from [::1]:3000 -> 3000
# プロンプトは返ってこない
上記を実行すると、Ctrl+C 等でコマンドを終了するまでポートフォワードが有効になります。
後はブラウザで http://localhost:3000/ にアクセスすれば下記のように Pod 上で動作する Rails アプリケーションを表示することが出来ます。
また、一時的に curl を実行するためだけの Pod を起動して動作確認することも出来ます。
curl の引数に渡す http://A.B.C.D:3000 は Pod の詳細情報の中に書かれた IP
の値を入力してください。
$ kubectl run --generator=run-pod/v1 -it --rm curl-test --image=centos:6 -- curl -I http://10.42.1.57:3000
If you don't see a command prompt, try pressing enter.
Error attaching, falling back to logs: unable to upgrade connection: unable to read error from server response
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Content-Type: text/html; charset=utf-8
ETag: W/"542b9c88b05fb8aee0dbdb502f0e69ba"
Cache-Control: max-age=0, private, must-revalidate
Set-Cookie: _rails_sample_app_session=L0hRTWVkd1Z4bjd3MThQK09BQmZqZ0ZZTG9ZWHVEbXZ5aHBNSEV2bHVEUVJIUlluU3Bxc2NSVTc4b0lMRWZ6QmNYODBtd1pKWmxEQ3dtWFJVMUg4SjBxbmdXV0ZVbkw4bndVTnUrc3RhUkZPRUxKUWRMUm51aTJteVIzazdReEtwSHB4YlhiSWlJR1BZaGowVXdOVllnPT0tLVQ0M2VYTE9vaWtYQVdhQ1ZIYmtMM2c9PQ%3D%3D--c330af50875366cad20ef6295375f8305e47edb1; path=/; HttpOnly
X-Request-Id: fe8218f3-0d5a-40a7-a2be-75e31db91b47
X-Runtime: 0.125827
pod "curl-test" deleted
上記のように HTTP 200 OK が応答されたら成功です。
DB(SQLite) ファイルを永続化する
先に紹介した方法では、Pod が停止(削除) されると DB ファイル(/tmp/development.sqlite3
) が削除される問題があります。
実際、アプリケーションを起動してから Sign up し(メールは届かない)、sqlite3 コマンド等を使って DB 上でユーザが作成されたことを確認した後に Pod の再作成をしてみて下さい。ユーザが初期化されることが確認できると思います。
# 事前に Sign up で適当なユーザの作成を試みてください。
# Pod 内 container の bash を起動
$ kubectl exec -it sample-pod-ruby bash
# Pod 内 container で rails console を実行
root@sample-pod-ruby:/usr/src/app# bin/rails c
Running via Spring preloader in process 1250
Loading development environment (Rails 5.2.3)
irb(main):001:0> User.count
(0.2ms) SELECT COUNT(*) FROM "users"
=> 1 # ユーザが存在することを確認
irb(main):001:0> exit
root@sample-pod-ruby:/usr/src/app# exit
# Pod を再起動する
$ kubectl delete pods sample-pod-ruby
$ kubectl apply -f sample-pod-ruby.yaml
$ kubectl exec -it sample-pod-ruby bash
root@sample-pod-ruby:/usr/src/app# bin/rails c
Running via Spring preloader in process 616
Loading development environment (Rails 5.2.3)
irb(main):001:0> User.count
(0.1ms) SELECT COUNT(*) FROM "users"
=> 0 # ユーザが存在しないことを確認
(前提) Dynamic Provisioning 環境を用意する
Pod が使用するファイルを永続化したい場合は PersistentVolumeClaim リソースを作成することになります。
PersistentVolumeClaim は Claim の名がつく通り永続化領域を要求する設定です。
あくまで要求であり、k8s 上ではこの要求に応じた PersistentVolume リソースが作成されます。
(自動で PersistentVolume リソースが作成される環境ではない場合は手動で作成する必要があります)
GKE を使っている場合、PersistentVolumeClaim リソースを作成すると自動で PersistentVolume として GCE の Persistent Disk が作成されます。
オンプレで k8s 環境を用意している場合は、快適な kubernetes オンプレミス環境を構築する(7. NFSサーバ&NFSクライアントセットアップ) 等を参考にして自動で PersistentVolume リソースが作成されるよう設定を行ってください。
本記事では PersistentVolumeClaim リソースを作成することにより、自動で PersistentVolume リソースが作成される Dynamic Provisioning 環境であることを前提とします。
PersistentVolumeClaim, PersistentVolume を作成する
まずは SQlite3 の DB ファイル保存先となるボリュームを作成します。
永続化されるボリュームは PersistentVolume と呼びます。
先に記載したとおり、PersistentVolumeClaim リソースを作成することにより自動で PersistentVolume リソースが作成されます。
本記事では NFS サーバを用意し、k8s クラスタ上に nfs-client-provisioner Pod をインストールした Dynamic Provisioning 環境を前提としています。
$ kubectl get storageclass
NAME PROVISIONER AGE
nfs-client (default) cluster.local/unrealistic-cardinal-nfs-client-provisioner 37m
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: sample-pvc-rails
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: nfs-client # Dynamic Provisioning 環境に応じて設定してください
# PersistentVolumeClaimを作成する
$ kubectl apply -f sample-pvc-rails.yaml
persistentvolumeclaim/sample-pvc-rails created
# 作成されたPersistentVolumeClaimを確認する
$ kubectl get persistentvolumeclaim
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
sample-pvc-rails Bound pvc-42326d92-6348-11e9-8ff7-92ffaff62273 1Gi RWO nfs-client 5s
# 作成されたPersistentVoluemを確認する
$ kubectl get persistentvolume
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-42326d92-6348-11e9-8ff7-92ffaff62273 1Gi RWO Delete Bound default/sample-pvc-rails nfs-client 10s
上記のように、PersistentVolumeClaim にはリソースの名前(metadata.name) をはじめ、利用ポリシー(spec.accessModes)、保存容量と保存先となるストレージ(spec.storageClassName)を指定します。
- spec.accessModes: ReadWriteOnce
- 同時に 1 つの Pod (正確には 1 つの Node)からのアクセスのみ許可する
- spec.resources.requests.storage: 1Gi
- 1GB の容量を要求する
- spec.storageClassName: nfs-client
- ストレージは nfs-client を使う
以上で k8s クラスタから利用できる NFS サーバ上のボリュームが 1GB だけ作成されました。
この PersistentVolumeClaim や PersistentVolume は Pod を削除しても消えません。
(ボリュームが不要になった場合は kubectl delete
コマンドにより PersistentVolumeClaim と PersistentVolume を削除します)
(本記事における nfs-client ストレージクラスでは、PersistentVolumeClaim リソースが削除されると対応する PersistentVolume も削除されます)
Rails アプリケーションを動作させる Pod リソースを作成する (PersistentVolumeClaim を利用する)
apiVersion: v1
kind: Pod
metadata:
name: sample-pod-ruby-pvc
spec:
containers:
- name: rails-app
image: ryu310/rails-sample_app:20190418
command:
- /bin/bash
args:
- -c
- rails db:migrate && rails server -b 0.0.0.0
volumeMounts:
- mountPath: "/tmp"
name: db
volumes:
- name: db
persistentVolumeClaim:
claimName: sample-pvc-rails
先に作成した Pod リソースで記述した YAML から追加された項目は spec.containers[].volumeMounts と spec.volumes です。
spec.containers[].volumeMounts では Container 内の /tmp (sqlite3ファイルが保存される先) は PersistentVolume からマウントするよう設定しています。
そこでマウントする先となる PersistentVolume が先に作成した PersistentVolumeClaim 名 sample-pvc-rails
で作成された pvc-42326d92-6348-11e9-8ff7-92ffaff62273
となります。
$ kubectl apply -f sample-pod-ruby-pvc.yaml
pod/sample-pod-ruby-pvc created
Rails アプリケーションが利用するデータが永続化されたことを確認する
# 事前に Sign up で適当なユーザの作成を試みてください。
# Pod 内 container の bash を起動
$ kubectl exec -it sample-pod-ruby-pvc bash
# Pod 内 container で rails console を実行
root@sample-pod-ruby-pvc:/usr/src/app# bin/rails c
Running via Spring preloader in process 1250
Loading development environment (Rails 5.2.3)
irb(main):001:0> User.count
(0.2ms) SELECT COUNT(*) FROM "users"
=> 1 # ユーザが存在することを確認
irb(main):001:0> exit
root@sample-pod-ruby-pvc:/usr/src/app# exit
# Pod を再起動する
$ kubectl delete pods sample-pod-ruby-pvc
$ kubectl apply -f sample-pod-ruby-pvc.yaml
$ kubectl exec -it sample-pod-ruby-pvc bash
root@sample-pod-ruby-pvc:/usr/src/app# bin/rails c
Running via Spring preloader in process 616
Loading development environment (Rails 5.2.3)
irb(main):001:0> User.count
(0.1ms) SELECT COUNT(*) FROM "users"
=> 1 # ユーザが存在する確認
もし NFS サーバを確認できるようであれば、ファイルが保存されていることを確認してみるのもよいでしょう。
$ ls -al /srv/nfsroot/
total 12
drwxr-xr-x 3 root root 4096 Apr 20 17:42 .
drwxr-xr-x 3 root root 4096 Apr 15 23:35 ..
drwxrwxrwx 2 root root 4096 Apr 20 18:03 default-sample-pvc-rails-pvc-42326d92-6348-11e9-8ff7-92ffaff62273
$ ls -al /srv/nfsroot/default-sample-pvc-rails-pvc-42326d92-6348-11e9-8ff7-92ffaff62273/
total 72
drwxrwxrwx 2 root root 4096 Apr 20 18:03 .
drwxr-xr-x 3 root root 4096 Apr 20 17:42 ..
-rw-r--r-- 1 root root 61440 Apr 20 18:03 development.sqlite3
default-sample-pvc-rails-pvc-42326d92-6348-11e9-8ff7-92ffaff62273
のように、Namespace default
と PersistentVolumeClaim 名 sample-pvc-rails
と PersistentVolume 名 pvc-42326d92-6348-11e9-8ff7-92ffaff62273
をハイフンで繋げたディレクトリが作成されているようでした。
PostgreSQL を動作させる Pod リソースを作成する
これまでは、構成を簡易にするために SQLite を使い、1 つの Pod だけで Rails アプリケーションを動作させていましたが、DB をアプリケーションと別 Pod として動作させるようにしてみます。
DB の Pod を作成する方法も Rails アプリケーションの Pod と同様です。
また、当然ですが PersistentVolume を使って DB のデータは永続化します。
DB は冗長構成にするのが望ましいですが、まずは 1 つの DB だけを起動するように設定していきます。
先に PersistentVolumeClaim リソースを作成します。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: sample-pvc-psql
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: nfs-client
$ kubectl apply -f sample-pvc-psql.yaml
persistentvolumeclaim/sample-pvc-psql created
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
sample-pvc-psql Bound pvc-0c9c2c3d-636c-11e9-8ff7-92ffaff62273 5Gi RWO nfs-client 16s
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-0c9c2c3d-636c-11e9-8ff7-92ffaff62273 5Gi RWO Delete Bound default/sample-pvc-psql nfs-client 7s
次に PostgreSQL を動作させる Pod リソースを作成します。
まずは永続ボリュームをもつ PostgreSQL を起動するだけの yaml を作成することにし、後に環境変数を与えて、アプリケーションで利用するためのユーザを作成することにします。
apiVersion: v1
kind: Pod
metadata:
name: sample-pod-psql
spec:
containers:
- name: psql
image: postgres:11
volumeMounts:
- mountPath: "/var/lib/postgresql/data"
name: pgdata
volumes:
- name: pgdata
persistentVolumeClaim:
claimName: sample-pvc-psql
YAML に記載した内容はこれまでに紹介した内容のため理解できると思います。
$ kubectl apply -f sample-pod-psql.yaml
pod/sample-pod-psql created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-pod-psql 1/1 Running 0 60s
# PostgreSQL のデータを psql コマンドを使って確認する
$ kubectl exec -it sample-pod-psql bash
root@sample-pod-psql:/# df -h /var/lib/postgresql/data/
Filesystem Size Used Avail Use% Mounted on
XXX.YY.ZZZ.AA:/srv/nfsroot/default-sample-pvc-psql-pvc-ad6f7f4d-636d-11e9-8ff7-92ffaff62273 78G 1.2G 77G 2% /var/lib/postgresql/data
root@sample-pod-psql:/# su - postgres
postgres@sample-pod-psql:~$ psql
psql (11.2 (Debian 11.2-1.pgdg90+1))
Type "help" for help.
postgres=# \du
List of roles
Role name | Attributes | Member of
-----------+------------------------------------------------------------+-----------
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
postgres=# \l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+----------+----------+------------+------------+-----------------------
postgres | postgres | UTF8 | en_US.utf8 | en_US.utf8 |
template0 | postgres | UTF8 | en_US.utf8 | en_US.utf8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | en_US.utf8 | en_US.utf8 | =c/postgres +
| | | | | postgres=CTc/postgres
(3 rows)
postgres=#
さて、psql コマンドで確認したとおり、PostgreSQL の内容は初期設定のままなので postgres ユーザと postgres データベースが存在する状態です。
ここに Rails アプリケーションで利用するユーザを設定していくことにしましょう。
新しいユーザは作成せずに postgres ユーザを利用することにし、パスワードだけ設定しておくことにします。
- ユーザ名:
postgres
(スーパーユーザ) - パスワード:
rails-sample-password
(適当に変更してください)
上記のとおり postgres ユーザにパスワードを設定することにしましょう。
また事前に Database を作成しておきます。
$ kubectl exec -it sample-pod-psql bash
root@sample-pod-psql:/# su - portgres
No passwd entry for user 'portgres'
root@sample-pod-psql:/# su - postgres
postgres@sample-pod-psql:~$ psql
psql (11.2 (Debian 11.2-1.pgdg90+1))
Type "help" for help.
postgres=# alter role postgres with password 'rails-sample-password';
ALTER ROLE
postgres=# create database rails_sample_app;
CREATE DATABASE
postgres=# \l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
------------------+----------+----------+------------+------------+-----------------------
postgres | postgres | UTF8 | en_US.utf8 | en_US.utf8 |
rails_sample_app | postgres | UTF8 | en_US.utf8 | en_US.utf8 |
template0 | postgres | UTF8 | en_US.utf8 | en_US.utf8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | en_US.utf8 | en_US.utf8 | =c/postgres +
| | | | | postgres=CTc/postgres
(4 rows)
Docker image をビルドする (RAILS_ENV=production)
FROM ruby:2.5.3
# install tools
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
postgresql-client \
apt-transport-https \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# install yarn
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN apt-get update \
&& apt-get -y install yarn \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# install node 10.x
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash -
RUN apt-get install -y nodejs \
&& apt-get clean
WORKDIR /usr/src/app
ENV RAILS_ENV=production
COPY Gemfile* ./
RUN bundle install --with production --without development,test,deployment
COPY . .
EXPOSE 3000
CMD ["rails", "server", "-b", "0.0.0.0"]
使い方は Rails アプリケーションの TOP (Gemfile, Gemfile.lock が存在するディレクトリ) と同じディレクトリに Dockerfile を設置して docker build
を実行します。
※ 値が config/environments/production.rb は config.force_ssl = false
でなければ修正してください
$ docker build -t ruby-app .
# 作成が完了したことを確認する
$ docker image ls | grep ruby-app
ruby-app latest 93a74f4d19d9 7 seconds ago 1.19GB
Docker image が作成出来たら、k8s 上で起動する前に Docker run で正常に起動するか確認してみます。
事前に PostgreSQL を起動しておき、link で接続できるようにしつつビルドしたイメージを起動します。
尚、本番環境で利用するための各環境変数を設定し、アセットのプリコンパイルと DB のマイグレーションを行うために entrypoint は上書きしています。
※ AWS S3 の Bucket を作成した上で、該当のバケットにアクセスできるよう S3_XXXX の値を設定してください
※ SendGrid を有効にした上で、該当のサービスを利用できるよう SENDGRID_XXXX の値を設定してください
$ docker run --name postgres -d \
-e POSTGRES_PASSWORD=rails-sample-password \
postgres:11
$ docker run --rm -p 3000:3000 \
-e DATABASE_URL=postgres://postgres:rails-sample-password@postgres/rails_sample_app \
-e S3_ACCESS_KEY=${INPUT_YOUR_VALUE} \
-e S3_BUCKET=${INPUT_YOUR_VALUE} \
-e S3_SECRET_KEY=${INPUT_YOUR_VALUE} \
-e SENDGRID_PASSWORD=${INPUT_YOUR_VALUE} \
-e SENDGRID_USERNAME=${INPUT_YOUR_VALUE} \
-e SECRET_KEY_BASE=${INPUT_YOUR_VALUE} \
-e RAILS_SERVE_STATIC_FILES=1 \
--link postgres \
--entrypoint /bin/bash \
ruby-app \
-c 'rails assets:precompile && rails db:migrate && rails server -b 0.0.0.0'
# RailsアプリケーションのTOPにアクセスする
$ curl -I http://localhost:3000
# HTTP 200 OK が返ってくれば OK
先と同様に Docker image をイメージリポジトリの Docker Hub にプッシュしてください。
Rails アプリケーションを動作させる Pod リソースを作成する
PostgreSQL と接続する Rails アプリケーションを動作させる Pod を作成します。
RAILS_ENV=production で動作させるために必要な環境変数はマニフェスト内の spec.containers[].env で指定できます。
(環境変数として設定する値は ConfigMap リソースとして管理する方が望ましく、アクセスキーやパスワードは Secret リソースとして管理する方が直指定するより望ましいです。今は簡易のため環境変数で直指定しますが後ほど修正します)
マニフェストに記述する内容は Docker run で実行した内容とほぼ同じです。
apiVersion: v1
kind: Pod
metadata:
name: sample-pod-ruby-psql
spec:
containers:
- name: rails-app
image: ryu310/rails-sample_app:20190420
command:
- /bin/bash
args:
- -c
- rails assets:precompile && rails db:migrate && rails server -b 0.0.0.0
env:
- name: DATABASE_URL
value: postgres://postgres:rails-sample-password@10.42.1.144/rails_sample_app
- name: S3_ACCESS_KEY
value: ${INPUT_YOUR_VALUE}
- name: S3_BUCKET
value: ${INPUT_YOUR_VALUE}
- name: S3_SECRET_KEY
value: ${INPUT_YOUR_VALUE}
- name: SENDGRID_PASSWORD
value: ${INPUT_YOUR_VALUE}
- name: SENDGRID_USERNAME
value: ${INPUT_YOUR_VALUE}
- name: SECRET_KEY_BASE
value: ${INPUT_YOUR_VALUE}
- name: RAILS_SERVE_STATIC_FILES
value: 1
DATABASE_URL に書かれている IP アドレスは sample-pod-psql Pod の IP アドレスです。
Pod は Pod network に所属するため、IP アドレスを指定すれば Pod 同士で通信ができます。
下記コマンドで確認できます。
$ kubectl get pod sample-pod-psql -o jsonpath="{.status.podIP}"
10.42.1.144
Rails アプリケーションが起動したことを確認する
さて、先と同様に Port forward を行い、起動した Rails アプリケーションに HTTP でアクセスしてみることにします。
画面が表示され、 RAILS_ENV=development で表示するように設定されたデバッグ情報が表示されていなければ成功です。
Rails アプリケーションから DB の Pod を発見する
先の Rails アプリケーションの Pod には PostgreSQL が動作する Pod の IP アドレスが直指定されていました。
これでは sample-pod-psql の IP アドレスが変わった場合に接続できなくなる問題があります。
クラスタ内のサービスディスカバリを行う Service リソースを使って IP アドレスが変わった場合にも接続できるように修正します。
apiVersion: v1
kind: Service
metadata:
name: sample-service-psql
spec:
selector:
app: sample-psql
ports:
- protocol: TCP
port: 5432
記述内容のポイントは spec.selector に書かれた app: sample-psql
です。
この Service は Pod のラベルに app: sample-psql
が含まれる Pod を発見し Service の EndPoint として認識し、このサービスに対する接続を EndPoint に受け渡します。
# Service を作成する
$ kubectl apply -f sample-service-psql.yaml
service/sample-service-psql created
# Service を確認する
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 24d
sample-service-psql ClusterIP 10.43.7.196 <none> 5432/TCP 3s
# Service の詳細を確認する
$ kubectl describe service sample-service-psql
Name: sample-service-psql
Namespace: default
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"sample-service-psql","namespace":"default"},"spec":{"ports":[{"po...
Selector: app=sample-psql
Type: ClusterIP
IP: 10.43.233.109
Port: <unset> 5432/TCP
TargetPort: 5432/TCP
Endpoints: <none>
Session Affinity: None
Events: <none>
まだ Endpoint が作成されていません。
これは selector の条件に合致する Pod が存在しないためです。
先に作成した PostgreSQL を動作させる Pod にラベルとして app: sample-psql
を指定し、Rails アプリケーションを動作させる Pod の DB 接続先を service に変えてみます。
apiVersion: v1
kind: Pod
metadata:
name: sample-pod-psql
labels:
app: sample-psql
spec:
containers:
- name: psql
image: postgres:11
volumeMounts:
- mountPath: "/var/lib/postgresql/data"
name: pgdata
volumes:
- name: pgdata
persistentVolumeClaim:
claimName: sample-pvc-psql
apiVersion: v1
kind: Pod
metadata:
name: sample-pod-ruby-psql
spec:
containers:
- name: rails-app
image: ryu310/rails-sample_app:20190420
command:
- /bin/bash
args:
- -c
- rails assets:precompile && rails db:migrate && rails server -b 0.0.0.0
env:
- name: DATABASE_URL
value: postgres://postgres:rails-sample-password@sample-service-psql/rails_sample_app
- name: S3_ACCESS_KEY
value: ${INPUT_YOUR_VALUE}
- name: S3_BUCKET
value: ${INPUT_YOUR_VALUE}
- name: S3_SECRET_KEY
value: ${INPUT_YOUR_VALUE}
- name: SENDGRID_PASSWORD
value: ${INPUT_YOUR_VALUE}
- name: SENDGRID_USERNAME
value: ${INPUT_YOUR_VALUE}
- name: SECRET_KEY_BASE
value: ${INPUT_YOUR_VALUE}
- name: RAILS_SERVE_STATIC_FILES
value: 1
以上で sample-pod-psql の IP アドレスは直指定せずに済むようになりました。
sample-service-psql は k8s クラスタ内 DNS サーバにより名前解決が出来るため、IP アドレスが変わった場合にも対応することが出来ます。
# Endpoint の IP アドレスを確認する
$ kubectl describe service sample-service-psql
Name: sample-service-psql
Namespace: default
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"sample-service-psql","namespace":"default"},"spec":{"ports":[{"po...
Selector: app=sample-psql
Type: ClusterIP
IP: 10.43.100.113
Port: <unset> 5432/TCP
TargetPort: 5432/TCP
Endpoints: 10.42.1.147:5432
Session Affinity: None
Events: <none>
# sample-pod-psql Pod を再作成する(IPアドレスを変える)
$ kubectl delete pod sample-pod-psql
pod "sample-pod-psql" deleted
$
$ kubectl apply -f sample-pod-psql.yaml
pod/sample-pod-psql created
# Endpoint の IP アドレスを確認する
$ kubectl describe service sample-service-psql
Name: sample-service-psql
Namespace: default
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"sample-service-psql","namespace":"default"},"spec":{"ports":[{"po...
Selector: app=sample-psql
Type: ClusterIP
IP: 10.43.100.113
Port: <unset> 5432/TCP
TargetPort: 5432/TCP
Endpoints: 10.42.1.152:5432
Session Affinity: None
Events: <none>
環境変数を Secret として保存する
環境変数が複数の Pod で使用する場合に値を指定する箇所は 1 つにまとまっていた方が、変更する時に楽です。そのような用途で ConfigMap が使われます。
また、ユーザ名やパスワード等の秘匿したい情報は Secret を使うことが推奨されています。
今回記述する中で RAILS_SERVE_STATIC_FILES の設定以外は Secret に登録してよいでしょう。
RAILS_SERVE_STATIC_FILES は Rails アプリケーションの動作に関する設定なので ConfigMap に纏めずに env で指定したままにしておくことにします。
Secret ファイルには KEY
と VALUE
のセットを記述しますが、VALUE は BASE64 でエンコードする必要があります。
kubectl コマンドから Secret ファイルを作成する場合にはファイルや引数を与えれば BASE64 でエンコードできる --from-file
や --from-literal
オプションがあります。
# Secretファイルを作成する
$ kubectl create secret generic --save-config sample-secret \
--from-literal=DATABASE_URL=postgres://postgres:rails-sample-password@sample-service-psql/rails_sample_app \
--from-literal=S3_ACCESS_KEY=${INPUT_YOUR_VALUE} \
--from-literal=S3_BUCKET=${INPUT_YOUR_VALUE} \
--from-literal=S3_SECRET_KEY=${INPUT_YOUR_VALUE} \
--from-literal=SENDGRID_PASSWORD=${INPUT_YOUR_VALUE} \
--from-literal=SENDGRID_USERNAME=${INPUT_YOUR_VALUE} \
--from-literal=SECRET_KEY_BASE=${INPUT_YOUR_VALUE}
secret/sample-secret created
# 作成した Secret を確認する
$ kubectl describe secret sample-secret
Name: sample-secret
Namespace: default
Labels: <none>
Annotations:
Type: Opaque
Data
====
SENDGRID_PASSWORD: 12 bytes
SENDGRID_USERNAME: 23 bytes
DATABASE_URL: 78 bytes
S3_ACCESS_KEY: 20 bytes
S3_BUCKET: 10 bytes
S3_SECRET_KEY: 40 bytes
SECRET_KEY_BASE: 128 bytes
尚、BASE64 でエンコードされた文字列はデコード(復元)できますので、外部に流出しないよう気を付けて下さい。
apiVersion: v1
kind: Pod
metadata:
name: sample-pod-ruby-psql
spec:
containers:
- name: rails-app
image: ryu310/rails-sample_app:20190420
command:
- /bin/bash
args:
- -c
- rails assets:precompile && rails db:migrate && rails server -b 0.0.0.0
env:
- name: RAILS_SERVE_STATIC_FILES
value: '1'
envFrom:
- secretRef:
name: sample-secret
このように Secret を全て参照する場合に spec.containers[].envFrom.secretRef でリソース名を指定することが出来ます。
尚、Secret の中の特定の値だけを利用する場合は spec.containers[].env[].valueFrom.secretKeyRef で key を指定することが出来ます。
修正した Pod を再作成すれば正常に起動することからも Secret が読み込まれていることが分かると思います。
Container で env コマンドを実行して Secret で指定した値が環境変数として設定されていることを確認するのもよいでしょう。
Rails アプリケーションを冗長化する
これまでのアプリケーションは 1 つの Container で動作しており耐障害性はありません。
k8s では複数の Pod をまとめて管理するリソースとして ReplicaSet があり、ReplicaSet にリリースの概念を追加した Deployment があります。
アプリケーション起動時の初期化処理を Job で実行する
rails db:migration は並列で実行することが出来ません。
Pod を複数用意した場合にこれらが実行されると Pod が Running にならずに起動失敗してしまいます。
(ReplicaSet や Deployment を使う場合は Pod が再起動されるので大きな問題にはなりません)
Pod 起動時に指定している rails db:migrate
を行うリソースを作成していくことにします。
Rails アプリケーションを動作させる Pod を起動する前に Job によりマイグレーション処理を行うことになります。
apiVersion: batch/v1
kind: Job
metadata:
name: sample-job-migrate
spec:
completions: 1
parallelism: 1
template:
spec:
containers:
- name: rails-app
image: ryu310/rails-sample_app:20190420
command:
- /bin/bash
args:
- -c
- rails db:migrate
envFrom:
- secretRef:
name: sample-secret
restartPolicy: Never
Job のマニフェストには spec.template
配下に Pod のマニフェストの spec を書きます。
Pod に書かれていた spec.containers[].args
から rails db:migrate を実行する処理を切り出しています。
spec.completions は成功回数を指定します。マイグレーション処理は1回成功すればよいので1を指定しています。(デフォルトは1なので敢えて指定する必要はありません)
spec.parallelism は並列処理数を指定します。マイグレーション処理は並列処理が出来ないため1を指定しています。(デフォルトは1なので敢えて指定する必要はありません)
spec.template.restartPolicy は Job の処理が失敗した時に、Pod を再利用(再起動) するかどうか指定します。Never では再起動せずに新規 Pod を立ち上げます。
# Job リソースを作成する
$ kubectl apply -f sample-job-migrate.yaml
job.batch/sample-job-migrate created
# Job リソースを確認する(処理が完了していない場合)
$ kubectl get jobs
NAME COMPLETIONS DURATION AGE
sample-job-migrate 0/1 5s 5s
# Job リソースを確認する(処理が完了している場合)
$ kubectl get jobs
NAME COMPLETIONS DURATION AGE
sample-job-migrate 1/1 6s 10s
これでマイグレーション処理は Job で実施する方針となったので Rails アプリケーションを実行する Pod の args からはマイグレーション処理が不要になりました。
apiVersion: v1
kind: Pod
metadata:
name: sample-pod-ruby-psql
spec:
containers:
- name: rails-app
image: ryu310/rails-sample_app:20190420
command:
- /bin/bash
args:
- -c
- rails assets:precompile && rails server -b 0.0.0.0
env:
- name: RAILS_SERVE_STATIC_FILES
value: '1'
envFrom:
- secretRef:
name: sample-secret
ReplicaSet により Rails アプリケーションを起動する Pod を冗長化する
Pod を複数起動するためのリソースとして ReplicaSet があります。
ReplicaSet はマニフェストとして Pod のマニフェストを spec.template として指定し、その Pod が期待した数だけ起動している状態を維持するリソースです。
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: sample-replicaset-ruby
spec:
replicas: 2
selector:
matchLabels:
app: rails-app
template:
metadata:
labels:
app: rails-app
spec:
containers:
- name: rails-app
image: ryu310/rails-sample_app:20190420
command:
- /bin/bash
args:
- -c
- rails assets:precompile && rails server -b 0.0.0.0
env:
- name: RAILS_SERVE_STATIC_FILES
value: '1'
envFrom:
- secretRef:
name: sample-secret
ReplicaSet マニフェストの spec.template 配下には起動する Pod の spec を書きます。
起動する Pod の数は spec.replicas
で指定します。クラスタ内でこの数だけ Pod が起動している状態を維持しようとされます。
Pod の数は spec.selector
の条件に一致するものが対象になります。
spec.selector.matchLabels
に app: rails-app
と書いた場合は、Pod のラベルに app=rails-app
が書かれた Pod であれば条件に一致することになります。
ReplicaSet のマニフェストで記載した Pod が対象となるよう、 spec.template.metadata.labels
に app: rails-app
を指定しています。
ReplicaSet を作成するには、これまでと同様 kubectl apply
コマンドを使います。
# ReplicaSet を作成する
$ kubectl apply -f sample-replicaset-ruby.yaml
replicaset.apps/sample-replicaset-ruby created
# ReplicaSet リソースを確認する
$ kubectl get replicaset
NAME DESIRED CURRENT READY AGE
sample-replicaset-ruby 2 2 2 24s
# Pod リソースを確認する
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-job-migrate-sfdfn 0/1 Completed 0 25m
sample-pod-psql 1/1 Running 0 12h
sample-replicaset-ruby-mjzv6 1/1 Running 0 5s
sample-replicaset-ruby-rzhns 1/1 Running 0 5s
kubectl get replicaset
の結果から、ReplicaSet が起動する必要のある Pod 数が DESIRED として表示され、現状で起動している Pod 数が CURRENT として表示されていることが分かります。
kubectl get pods
の結果を見ると ReplicaSet の名前の末尾にランダムな文字列が追加された Pod が 2 つ起動していることが分かります。これらが ReplicaSet により起動された Pod です。
ここで Pod を削除してみると、自動で新しい Pod が作成されることが分かります。
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-job-migrate-sfdfn 0/1 Completed 0 55m
sample-pod-psql 1/1 Running 0 12h
sample-replicaset-ruby-78g9b 1/1 Running 0 2m15s
sample-replicaset-ruby-mjlsf 1/1 Running 0 2m15s
$ kubectl delete pod sample-replicaset-ruby-mjlsf
pod "sample-replicaset-ruby-mjlsf" deleted
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-job-migrate-sfdfn 0/1 Completed 0 58m
sample-pod-psql 1/1 Running 0 12h
sample-replicaset-ruby-78g9b 1/1 Running 0 5m47s
sample-replicaset-ruby-7kwqw 1/1 Running 0 18s
Deployment により冗長化された Rails アプリケーションを起動する Pod をリリース管理する
ReplicaSet により Pod の冗長化が出来ました。
しかし ReplicaSet は Pod の起動数が期待どおりとなるよう維持してくれますが、Rails アプリケーションを更新してリリースする時など、Rails アプリケーションのコンテナイメージが新しくしたい場合に、全ての Pod に新しいコンテナイメージが適用されるように Pod の再起動をしたりしません。
試しに、ReplicaSet で管理する Rails アプリケーション Pod のコンテナイメージを更新してみます。
コンテナイメージは ryu310/rails-sample_app:20190420
から ryu310/rails-sample_app:20190421
に変更します。
# ReplicaSet が管理する Rails アプリケーション Pod のコンテナイメージを更新する
$ kubectl set image replicaset sample-replicaset-ruby rails-app=ryu310/rails-sample_app:20190421
replicaset.extensions/sample-replicaset-ruby image updated
# ReplicaSet リソースや、Pod の AGE に変化はないこと、ReplicaSet のコンテナイメージが変わっていることを確認する
$ kubectl get replicaset
NAME DESIRED CURRENT READY AGE
sample-replicaset-ruby 2 2 2 36m
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-job-migrate-sfdfn 0/1 Completed 0 89m
sample-pod-psql 1/1 Running 0 13h
sample-replicaset-ruby-78g9b 1/1 Running 0 36m
sample-replicaset-ruby-7kwqw 1/1 Running 0 31m
$ kubectl describe replicaset sample-replicaset-ruby
Name: sample-replicaset-ruby
Namespace: default
Selector: app=rails-app
Labels: app=rails-app
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"apps/v1","kind":"ReplicaSet","metadata":{"annotations":{},"name":"sample-replicaset-ruby","namespace":"default"},"spec":{"r...
Replicas: 2 current / 2 desired
Pods Status: 2 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
Labels: app=rails-app
Containers:
rails-app:
Image: ryu310/rails-sample_app:20190421 ★コンテナイメージが更新されている
: <snip>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 38m replicaset-controller Created pod: sample-replicaset-ruby-78g9b
Normal SuccessfulCreate 38m replicaset-controller Created pod: sample-replicaset-ruby-mjlsf
Normal SuccessfulCreate 32m replicaset-controller Created pod: sample-replicaset-ruby-7kwqw
# Pod の image が ReplicaSet で指定したものと違うことを確認する
$ kubectl describe pod sample-replicaset-ruby-78g9b
Name: sample-replicaset-ruby-78g9b
Namespace: default
Priority: 0
PriorityClassName: <none>
Node: rancher-worker01/XXX.YY.ZZZ.AA
Start Time: Sun, 21 Apr 2019 16:18:11 +0900
Labels: app=rails-app
Annotations: cni.projectcalico.org/podIP: 10.42.1.173/32
Status: Running
IP: 10.42.1.173
Controlled By: ReplicaSet/sample-replicaset-ruby
Containers:
rails-app:
Container ID: docker://5d7003e42764b0696086152038151022293e867bcc5d789cfc4d9a26ae29330b
Image: ryu310/rails-sample_app:20190420 ★コンテナイメージが ReplicaSet で指定したものと違う
Image ID: docker-pullable://ryu310/rails-sample_app@sha256:0f0b6495c084cadf675658c1d3c7b07947cb12e26fa2d90098a4882e420b13be
: <snip>
# Pod を削除して強制再起動する
$ kubectl delete pod sample-replicaset-ruby-78g9b
pod "sample-replicaset-ruby-78g9b" deleted
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-job-migrate-sfdfn 0/1 Completed 0 92m
sample-pod-psql 1/1 Running 0 13h
sample-replicaset-ruby-7kwqw 1/1 Running 0 34m
sample-replicaset-ruby-txtsg 1/1 Running 0 11s
unrealistic-cardinal-nfs-client-provisioner-69859fb545-lnn4w 1/1 Running 0 23h
# Pod のコンテナイメージが更新されたことを確認する
$ kubectl describe pod sample-replicaset-ruby-txtsg
Name: sample-replicaset-ruby-txtsg
Namespace: default
Priority: 0
PriorityClassName: <none>
Node: rancher-worker01/XXX.YY.ZZZ.AA
Start Time: Sun, 21 Apr 2019 16:57:43 +0900
Labels: app=rails-app
Annotations: cni.projectcalico.org/podIP: 10.42.1.175/32
Status: Running
IP: 10.42.1.175
Controlled By: ReplicaSet/sample-replicaset-ruby
Containers:
rails-app:
Container ID: docker://e5131278a3263e380ba6c163b2f6295d547a91c7bb14bc788eabfa3a5419cb51
Image: ryu310/rails-sample_app:20190421
Image ID: docker-pullable://ryu310/rails-sample_app@sha256:99e2b13498a9c3d798ff46b183e5272e2d7d3dfef87c7233211ecdc7c3e800d9
: <snip>
これは ReplicaSet では Pod のリリースを管理するための仕組みがないためです。
コンテナイメージを最新化したい場合や、新しいイメージが正常に動作する時だけ Pod を更新したい場合、古いバージョンにロールバックしたい場合など、Pod のバージョン管理を行いたい場合は Deployment リソースを使います。
apiVersion: apps/v1
kind: Deployment
metadata:
name: sample-deployment-ruby
spec:
replicas: 2
selector:
matchLabels:
app: rails-app
template:
metadata:
labels:
app: rails-app
spec:
containers:
- name: rails-app
image: ryu310/rails-sample_app:20190420
command:
- /bin/bash
args:
- -c
- rails assets:precompile && rails server -b 0.0.0.0
env:
- name: RAILS_SERVE_STATIC_FILES
value: '1'
envFrom:
- secretRef:
name: sample-secret
Deployment マニフェストに記載する内容は ReplicaSet と同様です。
kind, metadata 以外は同じ内容を記述してみました。
Deployment を作成してコンテナイメージを更新してみることにします。
# Deployment リソースを作成する
$ kubectl apply -f sample-deployment-ruby.yaml
deployment.apps/sample-deployment-ruby created
# Deployment のイメージを更新する
$ kubectl set image deployment sample-deployment-ruby rails-app=ryu310/rails-sample_app:20190421
deployment.extensions/sample-deployment-ruby image updated
# Deployment の AGE には変化が無いが、Pod の AGE に変化があることを確認する
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
sample-deployment-ruby 2/2 2 2 20m
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
sample-deployment-ruby-6dd8b54c8-bf26t 1/1 Running 0 28s
sample-deployment-ruby-6dd8b54c8-btcqn 1/1 Running 0 27s
sample-job-migrate-sfdfn 0/1 Completed 0 140m
sample-pod-psql 1/1 Running 0 13h
unrealistic-cardinal-nfs-client-provisioner-69859fb545-lnn4w 1/1 Running 0 24h
# Pod のコンテナイメージが更新されていることを確認する
$ kubectl get pod sample-deployment-ruby-6dd8b54c8-bf26t -o 'jsonpath={.spec.containers[0].image}'
ryu310/rails-sample_app:20190421
Deployment を使った場合、set image を使ってコンテナイメージを更新すると、既に起動している Rails アプリケーション用 Pod のコンテナイメージも更新されていることが分かります。
Deployment ではマニフェストに更新があった場合に、更新された内容を反映させる方法を spec.storategy
で指定できます。デフォルトでは spec.storagety.type: RollingUpdate
となります。
$ kubectl get deployment sample-deployment-ruby -o yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "2"
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"name":"sample-deployment-ruby","namespace":"default"},"spec":{"replicas":2,"selector":{"matchLabels":{"app":"rails-app"}},"template":{"metadata":{"labels":{"app":"rails-app"}},"spec":{"containers":[{"args":["-c","rails assets:precompile \u0026\u0026 rails server -b 0.0.0.0"],"command":["/bin/bash"],"env":[{"name":"RAILS_SERVE_STATIC_FILES","value":"1"}],"envFrom":[{"secretRef":{"name":"sample-secret"}}],"image":"ryu310/rails-sample_app:20190420","name":"rails-app"}]}}}}
creationTimestamp: "2019-04-21T08:24:30Z"
generation: 2 # 世代番号
labels:
app: rails-app
name: sample-deployment-ruby
namespace: default
resourceVersion: "3117677"
selfLink: /apis/extensions/v1beta1/namespaces/default/deployments/sample-deployment-ruby
uid: e2635f32-640e-11e9-8ff7-92ffaff62273
spec:
progressDeadlineSeconds: 600
replicas: 2
revisionHistoryLimit: 10
selector:
matchLabels:
app: rails-app
strategy:
rollingUpdate:
maxSurge: 25% # アップデート中に許容する超過Pod数(%の場合はreplicas数からの比率)
maxUnavailable: 25% # アップデート中に許容する不足Pod数(%の場合はreplicas数からの比率)
type: RollingUpdate # RollingUpdate方式でアップデートする
template:
metadata:
creationTimestamp: null
labels:
app: rails-app
spec:
containers:
- args:
- -c
- rails assets:precompile && rails server -b 0.0.0.0
command:
- /bin/bash
env:
- name: RAILS_SERVE_STATIC_FILES
value: "1"
envFrom:
- secretRef:
name: sample-secret
image: ryu310/rails-sample_app:20190421
imagePullPolicy: IfNotPresent
name: rails-app
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
status:
availableReplicas: 2
conditions:
- lastTransitionTime: "2019-04-21T08:24:32Z"
lastUpdateTime: "2019-04-21T08:24:32Z"
message: Deployment has minimum availability.
reason: MinimumReplicasAvailable
status: "True"
type: Available
- lastTransitionTime: "2019-04-21T08:24:30Z"
lastUpdateTime: "2019-04-21T08:45:04Z"
message: ReplicaSet "sample-deployment-ruby-6dd8b54c8" has successfully progressed.
reason: NewReplicaSetAvailable
status: "True"
type: Progressing
observedGeneration: 2
readyReplicas: 2
replicas: 2
updatedReplicas: 2
Deployment リソースは ReplicaSet を世代管理しています。
metadata.generation が現状の世代を表す番号です。
(Deploymentを新規作成した時点の generation が 1 であり、コンテナイメージを更新したため generation が 2 になっています)
Deployment はコンテナイメージの更新等、spec.template
配下の情報が更新された場合に ReplicaSet が更新されたと判断して更新処理を行います。
RollingUpdate 方式の場合、新しい ReplicaSet を用意してその中に maxSurge で許容される数だけ Pod を作成して正常に Running 状態となったら順序古い ReplicaSet の Pod を停止して最終的に新しい世代の ReplicaSet とその内容に応じた Pod となるように更新されます。
そのため、仮に kubectl set image
コマンドで存在しないコンテナイメージが指定された場合は新しい ReplicaSet が用意できずに更新処理が行われません。つまり最低限起動する Pod である場合だけ更新処理を行うよう管理してくれます。
存在しないコンテナイメージを指定した場合は RollingUpdate は失敗して Pod 再起動が発生しないことを確認してみましょう。
# 存在しないコンテナイメージを設定して Deployment を更新する
$ kubectl set image deployment sample-deployment-ruby rails-app=ryu310/rails-sample_app:fault-tag
deployment.extensions/sample-deployment-ruby image updated
# 更新処理のステータスを確認する(処理が完了したり中断されるまでプロンプトが返らない)
$ kubectl rollout status deployment sample-deployment-ruby
Waiting for deployment "sample-deployment-ruby" rollout to finish: 1 out of 2 new replicas have been updated...
# 既に起動していた Pod の AGE が更新されていないことを確認する
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-deployment-ruby-59c766c7c4-ts6cr 0/1 ImagePullBackOff 0 12m
sample-deployment-ruby-6dd8b54c8-bf26t 1/1 Running 0 39m
sample-deployment-ruby-6dd8b54c8-btcqn 1/1 Running 0 39m
sample-job-migrate-sfdfn 0/1 Completed 0 178m
sample-pod-psql 1/1 Running 0 14h
参考までに、Deployment の Reivision は kubectl rollout history
で確認できます。
Deployment の更新が失敗した場合は kubectl rollout undo
で元に戻せます。
$ kubectl rollout history deployment sample-deployment-ruby
deployment.extensions/sample-deployment-ruby
REVISION CHANGE-CAUSE
1 <none> # コマンドを実行する際に --record true を指定するとコマンドが表示されます。
2 <none>
3 <none>
$ kubectl rollout undo deployment sample-deployment-ruby
deployment.extensions/sample-deployment-ruby rolled back
$ kubectl rollout history deployment sample-deployment-ruby
deployment.extensions/sample-deployment-ruby
REVISION CHANGE-CAUSE
1 <none>
3 <none>
4 <none> # REVISION 2 に元戻しされたが REVISION は 4 に採番され直されている
ReplicaSet を使って PostgreSQL を動作させる Pod を冗長化する
PostgreSQL を動作させる Pod を冗長化させていきます。
Rails アプリケーションの Pod を冗長化させた時と同様にまずは ReplicaSet を使って冗長化させます。
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: sample-replicaset-psql
spec:
replicas: 2
selector:
matchLabels:
app: sample-psql
template:
metadata:
labels:
app: sample-psql # Service の selector で指定した値と一致させる
spec:
containers:
- name: psql
image: postgres:11
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: pgdata
volumes:
- name: pgdata
persistentVolumeClaim:
claimName: sample-pvc-psql
# ReplicaSet を作成する
$ kubectl apply -f sample-replicaset-psql.yaml
replicaset.apps/sample-replicaset-psql created
# Pod が複数作成されたことを確認する
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-deployment-ruby-6dd8b54c8-bf26t 1/1 Running 0 79m
sample-deployment-ruby-6dd8b54c8-btcqn 1/1 Running 0 79m
sample-job-migrate-sfdfn 0/1 Completed 0 3h39m
sample-replicaset-psql-2qz7q 1/1 Running 0 18s
sample-replicaset-psql-6dlrx 1/1 Running 0 18s
# Service の Endpoints に 2 つの Pod が認識されていることを確認する
$ kubectl describe service sample-service-psql
Name: sample-service-psql
Namespace: default
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"sample-service-psql","namespace":"default"},"spec":{"ports":[{"po...
Selector: app=sample-psql
Type: ClusterIP
IP: 10.43.100.113
Port: <unset> 5432/TCP
TargetPort: 5432/TCP
Endpoints: 10.42.1.190:5432,10.42.1.191:5432
Session Affinity: None
Events: <none>
StatefulSet を使って冗長化した PostgreSQL Pod を管理する
ReplicaSet を使った PostgreSQL Pod は同じ PersistentVolumeClaim を利用します。
つまり PostgreSQL インスタンスが 2 つでデータは共用しているといういびつな状態です。
冗長化させたい場合は PostgreSQL のレプリケーションを作るのがよいでしょう。
k8s で永続化したデータを取り扱うようなステートフルリソースを管理するために StatefulSet リソースが使えます。
StatefulSet は複数の Pod を管理し、Pod が使う PersistentVolumeClaim も template として管理します。
PostgreSQL のレプリケーションを作る際、bitnami/posgresql を使うのが簡単な用でしたので、利用することにします。
README.md に書かれているとおり、 POSTGRESQL_REPLICATION_MODE=master
等の環境変数を設定することでレプリケーションの設定が出来ます。
README.md に書かれている参考の設定を使って master と slave それぞれの StatefulSet リソースを作成していきます。
まずは事前に Secret を事前に作成しておきます。
(DB のユーザ名は postgres
, パスワードは rails-sample-password
です※再掲)
# Secret を作成する
$ kubectl create secret generic sample-secret-psql \
--from-literal=POSTGRESQL_REPLICATION_USER=repl_user \
--from-literal=POSTGRESQL_REPLICATION_PASSWORD=repl_password \
--from-literal=POSTGRESQL_USERNAME=postgres \
--from-literal=POSTGRESQL_PASSWORD=rails-sample-password
# Secret が作成されたことを確認する(sample-secret-psqlが追加されている)
$ kubectl get secret
NAME TYPE DATA AGE
default-token-zvct6 kubernetes.io/service-account-token 3 24d
sample-secret Opaque 7 19h
sample-secret-psql Opaque 4 34m
PosqgreSQL の master 用の StatefulSet を作成します。
- 起動時に database
rails_sample_app
を作成する - PosqgreSQL レプリケーションの master として起動する
- postgres ユーザアカウントと、レプリケーション用ユーザのアカウントは Secret から参照する
※ 環境変数は https://github.com/bitnami/bitnami-docker-postgresql#step-1-create-the-replication-master を参考にします。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: sample-statefulset-psql-master
spec:
serviceName: sample-statefulset-psql-master
replicas: 1
selector:
matchLabels:
app: sample-psql
role: master
template:
metadata:
labels:
app: sample-psql
role: master
spec:
containers:
- name: psql
image: bitnami/postgresql:11
volumeMounts:
- name: pgdata
mountPath: /bitnami
env:
- name: POSTGRESQL_REPLICATION_MODE
value: master
- name: POSTGRESQL_DATABASE
value: rails_sample_app
envFrom:
- secretRef:
name: sample-secret-psql
volumeClaimTemplates:
- metadata:
name: pgdata
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: nfs-client
次に PostgreSQL の slave 用の StatefulSet を作成します。
- master の IP アドレスは先に作成した Service
sample-service-psql
を使う - PosqgreSQL レプリケーションの slave として起動する
- レプリケーション用ユーザのアカウントは Secret から参照する
※ 環境変数は https://github.com/bitnami/bitnami-docker-postgresql#step-2-create-the-replication-slave を参照する
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: sample-statefulset-psql-slave
spec:
serviceName: sample-statefulset-psql-slave
replicas: 1
selector:
matchLabels:
app: sample-psql
role: slave
template:
metadata:
labels:
app: sample-psql
role: slave
spec:
containers:
- name: psql
image: bitnami/postgresql:11
volumeMounts:
- name: pgdata
mountPath: /bitnami
env:
- name: POSTGRESQL_REPLICATION_MODE
value: slave
- name: POSTGRESQL_MASTER_HOST
value: sample-service-psql
- name: POSTGRESQL_REPLICATION_USER
valueFrom:
secretKeyRef:
name: sample-secret-psql
key: POSTGRESQL_REPLICATION_USER
- name: POSTGRESQL_REPLICATION_PASSWORD
valueFrom:
secretKeyRef:
name: sample-secret-psql
key: POSTGRESQL_REPLICATION_PASSWORD
volumeClaimTemplates:
- metadata:
name: pgdata
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: nfs-client
ここで、PostgreSQL slave から master を参照する設定として POSTGRESQL_MASTER_HOST に sample-service-psql を指定していますが、これは Service sample-service-psql
を使うことを意図しています。
しかし Service sample-service-psql
はラベルに app: sample-psql
を持つ Pod を Selector として指定しているため、今のままでは PostgreSQL の master/slave 両方を指すことになってしまいます。
そこで PostgreSQL master を指すよう selector を修正することにします。
apiVersion: v1
kind: Service
metadata:
name: sample-service-psql
spec:
selector:
app: sample-psql
role: master # role: master を追加した
ports:
- protocol: TCP
port: 5432
※ Service の selector を更新する場合は kubectl apply
コマンドで更新後のファイルを指定することで更新できます。
以上で StatefulSet を作成する準備が整ったので、最後に StatefulSet を作成します。
# PostgreSQL の master となる Pod を StatefulSet で作成する
$ kubectl apply -f sample-statefulset-psql-master.yaml
statefulset.apps/sample-statefulset-psql-master created
# PostgreSQL の slave となる Pod を StatefulSet で作成する
$ kubectl apply -f sample-statefulset-psql-slave.yaml
statefulset.apps/sample-statefulset-psql-slave created
# 作成した StatefulSet を確認する
$ kubectl get statefulset
NAME READY AGE
sample-statefulset-psql-master 1/1 27m
sample-statefulset-psql-slave 1/1 25m
# 作成した Pod を確認する
# ※ StatefulSet が作成する Pod は ${StatefulSet名}-[0-9] のように、0から順に採番されます
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
sample-deployment-ruby-6dd8b54c8-kn2d4 1/1 Running 0 41m
sample-deployment-ruby-6dd8b54c8-l82pj 1/1 Running 0 41m
sample-job-migrate-2rlbl 0/1 Completed 0 24m
sample-statefulset-psql-master-0 1/1 Running 0 27m
sample-statefulset-psql-slave-0 1/1 Running 0 25m
# 作成した PersistentVolumeClaim を確認する
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pgdata-sample-statefulset-psql-master-0 Bound pvc-a4397a0f-6446-11e9-8ff7-92ffaff62273 5Gi RWO nfs-client 27m
pgdata-sample-statefulset-psql-slave-0 Bound pvc-f0c756ec-6446-11e9-8ff7-92ffaff62273 5Gi RWO nfs-client 25m
sample-pvc-psql Bound pvc-5098bd06-638b-11e9-8ff7-92ffaff62273 5Gi RWO nfs-client 22h
# 作成した PersistnetVolume を確認する
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-5098bd06-638b-11e9-8ff7-92ffaff62273 5Gi RWO Delete Bound default/sample-pvc-psql nfs-client 22h
pvc-a4397a0f-6446-11e9-8ff7-92ffaff62273 5Gi RWO Delete Bound default/pgdata-sample-statefulset-psql-master-0 nfs-client 27m
pvc-f0c756ec-6446-11e9-8ff7-92ffaff62273 5Gi RWO Delete Bound default/pgdata-sample-statefulset-psql-slave-0 nfs-client 25m
※ StatefulSet を作成する前に Pod sample-pod-psql
用に作成した PersistentVolumeClaim sample-pvc-psql
は使わなくなりました。
最後にやることが残っています。
StatefulSet を作成して PersistentVolumeClaim を管理することにしたため、PostgreSQL 起動時にはマイグレーションが行われておりません。
そこで Job を再度使用してマイグレーションを行います。
Job は既に成功した Pod を 1 つ持ち目的を果たしているため、一度削除してから再度作成します。
これでマイグレーション処理が行われることになります。
# マイグレーション用 Job を削除する
$ kubectl delete job sample-job-migrate
job.batch "sample-job-migrate" deleted
# マイグレーション用 Job を作成する
$ kubectl apply -f sample-job-migrate.yaml
job.batch/sample-job-migrate created
# Job が作成した Pod が Completed となったことを確認する
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
sample-deployment-ruby-6dd8b54c8-kn2d4 1/1 Running 0 41m
sample-deployment-ruby-6dd8b54c8-l82pj 1/1 Running 0 41m
sample-job-migrate-2rlbl 0/1 Completed 0 24m
sample-statefulset-psql-master-0 1/1 Running 0 27m
sample-statefulset-psql-slave-0 1/1 Running 0 25m
Rails アプリケーションを外部に公開する
これまで Rails アプリケーションも PostgreSQL もクラスタ内部からのアクセスしかできませんでした。本記事の最後として Rails アプリケーションを外部に公開することにします。
GCP や AWS 等のサービスを使っている場合は LoadBalancer リソースを使うのが望ましいと思います。
LoadBalancer リソースを作成すると、クラスの外部に LoadBalancer インスタンスが作成されます。(GCP では負荷分散インスタンスが作成されます)
本記事では Rancher を使ったオンプレミス環境を想定しているため LoadBalancer リソースは使用せず、Nginx Ingress Controller による Ingress リソースを使うことにします。
Nginx Ingress Controller による Ingress リソースはクラスタ内に Pod を作成し、その Pod が L7 Load Balancer となります。
Ingress リソースは backend として type: NodePort の Service を指定する必要があります。
そのため、事前に type: NodePort の Service を作成しておく必要があります。
type: NodePort の Service リソースを作成すると、クラスタ内全ノードの外部インタフェースに 0.0.0.0:XXXX で LISTEN されます。従って NodePort を作成すると外部に公開されることになります。
但し、あくまでそのポートを使って通信が出来るだけのため、証明書を使った HTTPS 通信やパスベースルーティングなどは行われません。
apiVersion: v1
kind: Service
metadata:
name: sample-service-ruby
spec:
type: NodePort
selector:
app: rails-app
ports:
- name: rails-app
protocol: "TCP"
port: 80
targetPort: 3000
type: NodePort の Service リソースで記述する内容は Rails アプリケーションから PostgresSQL に接続するために作成した Service と同様です。
# Service を作成する
$ kubectl apply -f sample-service-nodeport-ruby.yaml
service/sample-service-ruby created
# Service が作成した NodePort のポート番号を確認する
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 25d
sample-service-psql ClusterIP 10.43.100.113 <none> 5432/TCP 23h
sample-service-ruby NodePort 10.43.154.15 <none> 80:30411/TCP 5s
$ kubectl describe service sample-service-ruby
Name: sample-service-ruby
Namespace: default
Labels: <none>
Annotations: field.cattle.io/publicEndpoints:
[{"addresses":["XXX.YY.ZZZ.AA"],"port":32194,"protocol":"TCP","serviceName":"default:sample-service-ruby","allNodes":true}]
kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"sample-service-ruby","namespace":"default"},"spec":{"ports":[{"na...
Selector: app=rails-app
Type: NodePort
IP: 10.43.154.15
Port: rails-app 3000/TCP
TargetPort: 3000/TCP
NodePort: rails-app 32194/TCP
Endpoints: 10.42.1.197:3000,10.42.1.198:3000
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
NodePort 32194/TCP とあるように、クラスタ内全ノードの外部インタフェースの TCP 32194 番が公開されていることが分かります。
クラスタノードの 1 つを選び、外部IPアドレスとポート番号(例では32194)を使ってブラウザでアクセスしてみて下さい。
http://<クラスタノードのIPアドレス>:32194
Rails アプリケーションが表示されれば成功です。
次に Ingress リソースを作成します。
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: sample-ingress-ruby
spec:
rules:
- http:
paths:
- backend:
serviceName: sample-service-ruby
servicePort: 3000
# Ingress リソースを作成する
$ kubectl apply -f sample-ingress-ruby.yaml
ingress.extensions/sample-ingress-ruby created
# Ingress リソースが作成されたことを確認する
$ kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
sample-ingress-ruby * XXX.YY.ZZZ.AA 80 4m21s # XXX.YY.ZZZ.AA はアドレスが表示されるまで時間がかかる場合があります
$ kubectl describe ingress sample-ingress-ruby
Name: sample-ingress-ruby
Namespace: default
Address:
Default backend: default-http-backend:80 (<none>)
Rules:
Host Path Backends
---- ---- --------
*
sample-service-ruby:3000 (10.42.1.197:3000,10.42.1.198:3000)
Annotations:
kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{},"name":"sample-ingress-ruby","namespace":"default"},"spec":{"rules":[{"http":{"paths":[{"backend":{"serviceName":"sample-service-ruby","servicePort":3000}}]}}]}}
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal CREATE 15s nginx-ingress-controller Ingress default/sample-ingress-ruby
Ingress はどのホスト名でどのパスに来ても Rails アプリケーション用 Service sample-service-ruby
に Proxy します。
クラスタの IP アドレスを使ってブラウザで Rails アプリケーションが表示できることを確認してみて下さい。
http://<クラスタノードのIPアドレス>
終わりに
Rails アプリケーションを k8s で公開することを目的とし、クラスタ外部からブラウザでアプリケーションにアクセスできるようになるところまで紹介しました。
勉強のため冗長構成を作成する最低限の方法を記述しておりますが、PostgreSQL を冗長化させるためには Helm Chart を使ってインストールする方が望ましいでしょう。
HTTPS 通信が出来るようにしたり、証明書を発行する等より実践的な方法については他記事を参照するようにしてください。