はじめに
AWSもKubernetesに対応したEKSを発表しました。今やDockerとKubernetesは切っても切れない関係になっていくと思ってます。
今回は既にKubernetesの使用を前提としているGoogle Kubernetes Engine(GKE)でのRailsアプリケーションのデプロイを書いておきます。ただし、GKEへのデプロイって結構記事はあるんですが、migrateが絡んだ記事や実用になるような記事ってなかなか書いてないなと思い、ここに書いておきます。
初めてKubernetesやGKEに触れる方の参考になればと思います。
前提条件
- アプリケーションはRailsで、バックエンドもフロントエンドもRailsとする。
- データベースはPostgreSQLを使用するが、Google Cloud SQLではなく、Dockerコンテナとして立ち上げるものとする。
- gcloudやkubectlのインストールや設定は説明しませんので、その辺はググって下さい。
Railsアプリケーション
まずはDockerコンテナで動かすRailsアプリケーションを作成する必要があります。
簡単ですが、以下にDockerコンテナで動作するRailsアプリケーションを作成していきます。
ポイントは解説していきます。(正直Railsアプリケーションにフォーカスしているので読み飛ばしても可)
Dockerファイル
これは至って簡単です。単純にコンテナにすればいいだけなので、以下にサンプルを作っておきます。(あくまでサンプルです。本当はalpine-sdk
は後で消しておいた方がサイズが小さくなると思います)
FROM ruby:2.4.1-alpine
RUN apk update && apk upgrade && apk add --update --no-cache alpine-sdk tzdata postgresql-dev nodejs
RUN mkdir /app
WORKDIR /app
ARG BUNDLE_OPTIONS
ADD Gemfile /app/Gemfile
ADD Gemfile.lock /app/Gemfile.lock
RUN bundle install --path vendor/bundle -j4 ${BUNDLE_OPTIONS}
ADD . /app
RUN bundle exec rake assets:precompile
EXPOSE 3000
database.ymlの接続設定
次にデータベースへの接続情報を持つdatabase.yml
です。
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
production:
<<: *default
database: rails_production
host: <%= ENV['DATABASE_HOST'] %>
username: <%= ENV['DATABASE_USERNAME'] %>
password: <%= ENV['DATABASE_PASSWORD'] %>
port: <%= ENV['DATABASE_PORT'] %>
データベースの接続を環境変数から読み込むように設定します。このように設定することで環境設定にさえ設定すればどこへでも接続することが可能となるわけです。
DockerイメージのPUSH
実際作るDockerイメージのbuildからDockerレジストリへのPUSHを以下に記載します。
$ docker build -t rails:latest build --build-arg BUNDLE_OPTIONS='--without development test' .
$ docker tag rails:latest us.gcr.io/<プロジェクト>-xxxxxxx/<イメージ名>:<コンテナタグ>
$ gcloud docker -- push us.gcr.io/<プロジェクト>-xxxxxxx/<イメージ名>:<コンテナタグ>
環境変数を渡すには--build-arg
にて渡すことが可能です。これでbundle install
に余計なgemをインストールしないようにしています。
Google Kubernetes Engine(GKE)
ここからの記事の本題です。
以下を実行していくことで簡単にRailsアプリケーションをデプロイまで持っていけるはずです。
クラスタの作成
AWSでいうとECSみたいなイメージで考えてくれて結構です。これを下記のコマンドでサクッと作ります。
$ gcloud container clusters create --machine-type=g1-small --disk-size=30 --num-nodes=3 test-cluster
ディスクサイズは適当です。30G程度あれば事足りるはずです。
ここで重要なのは--machine-type=g1-small
です。ここにマシンスペックごとの料金が書いています。GKEは「f1-micro」「ノード数1」「USリージョン」であれば無料で使えるのですが、前提条件に書いている通り、データベースもコンテナで動かすにはメモリもCPUも足りません。ですので、g1-small
は最低でも必要です。
データベースの永続ストレージの追加
クラスタ作成にディスクは作成しましたが、データベースには共通して必要なディスクがいるのでそれを作成します。
$ gcloud compute disks create --size 5GB postgresql-disk
サイズはかなり適当です。データが多くて必要になりそうなら100GB程度でも用意しておいて下さい。
環境変数の登録
Railsにはデータベースの接続情報やsecret_key_base
といった情報を必要とします。しかし簡単に人には知られたくない情報でもあります。そこでKubernetesには機密情報を扱う機能を保持していますので、それの機能を使用して登録します。
ドキュメントに書いている通り、マニュアルで登録するには必要文字列はBase64でエンコードして登録する必要があります。
$ echo -n "database_user" | base64
#=> ZGF0YWJhc2VfdXNlcg==
$ echo -n "database_password" | base64
#=> ZGF0YWJhc2VfcGFzc3dvcmQ=
$ echo -n "secret_key_base" | base64
#=> c2VjcmV0X2tleV9iYXNl
上記のようにBase64にエンコードした文字列をSecretファイルに定義します。
注意:secret_key_baseはちゃんと長い文字列を使用しましょう。
apiVersion: v1
kind: Secret
metadata:
name: rails
type: Opaque
data:
database_user: ZGF0YWJhc2VfdXNlcg==
database_password: ZGF0YWJhc2VfcGFzc3dvcmQ=
secret_key_base: c2VjcmV0X2tleV9iYXNl
name
は何のsecret情報なのかわかりやすい名前をつけましょう。
上のYMLファイルができれば後はそれを登録するだけです。
# ymlの保存先はお好きなとこで
$ kubectl create -f ~/.kube/secret.yml
$ kubectl get secret
# NAME TYPE DATA AGE
# rails Opaque 3 1d
登録できれば上記のようになるはずです。
暗号化だけであればyaml_vaultを使うのもありです。
PostgreSQL(データベース)
PostgreSQLコンテナの起動
コンテナの起動となるdeploymentの作成をします。
作成は下記のYMLファイルを使用します。
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: postgresql
labels:
app: postgresql
spec:
replicas: 1
selector:
matchLabels:
app: postgresql
template:
metadata:
labels:
app: postgresql
spec:
containers:
- image: postgres:9.6-alpine
name: postgresql
env:
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: rails
key: database_user
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: rails
key: database_password
ports:
- containerPort: 5432
name: postgresql
volumeMounts:
- name: postgresql-persistent-storage
mountPath: /var/lib/postgresql
volumes:
- name: postgresql-persistent-storage
gcePersistentDisk:
pdName: postgresql-disk
fsType: ext4
name
はわかりやすい名前を好きにつけて下さい。ここで大切なのはenv
とvolumes
です。
まずはenv
ですが、これは先程登録したsecret情報を参照するという記述になっております。上記を記載することによりPostgreSQLのコンテナ起動に以下のユーザが作成されます。
次にvolumes
ですが、これがデータベースのストレージを定義しています。最初にデータベースのストレージを作成しましたが、ここで使用する設定が出てきます。この設定をすることによりデータの永続化が可能となります。
これを以下のコマンドで登録してします。
$ kubectl create -f kubernetes/postgresql.yml
$ kubectl get deployment
# NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
# postgresql 1 1 1 1 1d
このように起動するはずです。
PostgreSQLサービスの起動
上記のdeploymentを設定しただけ、接続は一切できません。そこでserviceの設定を追加します。
apiVersion: v1
kind: Service
metadata:
name: postgresql
labels:
app: postgresql
spec:
type: ClusterIP
ports:
- port: 5432
selector:
app: postgresql
ここで大事なのでtype
です。上記の設定ではClusterIP
としていますので、クラスタ内からの接続に限定しています。
これを起動するには同様に以下のコマンドです。
$ kubectl create -f kubernetes/postgresql-service.yml
$ kubectl get service
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# kubernetes ClusterIP 10.3.240.1 <none> 443/TCP 1d
# postgresql ClusterIP 10.3.253.218 <none> 5432/TCP 1d
これでデータベースへ接続できる状態となりました。
Railsアプリケーション
データベースが準備できたので、アプリケーションのコンテナを起動していきます。
基本的にはデータベースと同様にdeploymentを設定し、serviceを設定します。
アプリケーションコンテナの起動
まずはアプリケーションのdeploymentとなるYMLファイルを以下に記載します。
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: rails
labels:
app: rails
spec:
replicas: 1
selector:
matchLabels:
app: rails
template:
metadata:
labels:
app: rails
spec:
containers:
- image: $RAILS_IMAGE
name: rails
env:
- name: RAILS_ENV
value: "production"
- name: DATABASE_HOST
value: postgresql
- name: DATABASE_USERNAME
valueFrom:
secretKeyRef:
name: rails
key: database_user
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: rails
key: database_password
- name: SECRET_KEY_BASE
valueFrom:
secretKeyRef:
name: rails
key: secret_key_base
- name: DATABASE_PORT
value: "5432"
ports:
- containerPort: 3000
name: rails
command: ["bundle", "exec", "rails", "s", "-p", "3000", "-b", "0.0.0.0"]
ここも重要な設定項目を以下に説明します。
まずはなんといってもenv
項目です。ここではまずデータベースの設定と同様にsecretに設定したデータベースの接続情報を参照するように記載しています。更にデータベースの接続先ホストをDATABASE_HOST
の環境設定に記載しています。値はpostgresql
と記載していますが、これは先に設定したデータベースのコンテナ(service)でのname
を指定して下さい。kubernetesはpods間のネットワークが自動的に構築されます。docker-composeにも同じようなものがありますが、それと似たようなものと考えて結構です。(これがあるがゆえにminikubeに移行した方がいいと言われる1つかなと思います)
次に重要なのがimage
です。ここはGoogle Container Registryに登録されたイメージを記載します。本来ならばここにはimage: us.gcr.io/<プロジェクト>-xxxxxxx/<コンテナ名>:<コンテナタグ>
というような設定をしますが、ここには環境変数のように わざと 設定しています。これについては以下の起動コマンドを見れば理由がわかると思います。
$ export RAILS_IMAGE=us.gcr.io/rails-test-182304/rails:latest
$ cat kubernetes/rails.yml | envsubst | kubectl create -f -
そうです。image
の設定を外から渡せるようにしているのです。これの最大の利点は最後に説明するアプリケーションコンテナの更新時に最大の効果を発揮します。
簡単に説明しておくとenvsubst
は環境変数に登録した部分を変換してくれます。これによりcat
により出力されたYMLファイルの環境変数部分を変換した上で、kubectl create
に渡すようになるわけです。
アプリケーションサービスの起動
このままではデータベース同様にアプリケーションにアクセスができませんので、serviceを登録します。
apiVersion: v1
kind: Service
metadata:
name: rails
labels:
app: rails
spec:
type: LoadBalancer
ports:
- port: 3000
selector:
app: rails
大事なのはtype: LoadBalancer
の設定です。これによりEXTERNAL-IPを取得します。
注意:静的IPではないので必要な場合はload-balancer-ipを指定すること
アプリケーションのデプロイ&マイグレーション
データベース、アプリケーションが起動しました。
が、大事なことを忘れています。そうです、データベースにまだテーブルがありません。それもそのはずで、ここまで一切マイグレーションを実行していないからです。
データベースの作成&マイグレーション
マイグレーションを実施するのですが、実施にはJobを使用します。
apiVersion: batch/v1
kind: Job
metadata:
name: deploy-tasks
spec:
template:
metadata:
name: deploy-tasks
labels:
name: deploy-tasks
spec:
nodeSelector:
cloud.google.com/gke-nodepool: default-pool
restartPolicy: Never
containers:
- name: deploy-tasks-runner
image: $RAILS_IMAGE
command: ["/app/script/deploy-tasks.sh"]
env:
- name: RAILS_ENV
value: "production"
- name: DATABASE_HOST
value: postgresql
- name: DATABASE_USERNAME
valueFrom:
secretKeyRef:
name: rails
key: database_user
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: rails
key: database_password
- name: SECRET_KEY_BASE
valueFrom:
secretKeyRef:
name: rails
key: secret_key_base
- name: DATABASE_PORT
value: "5432"
大事なのはアプリケーションコンテナの起動でも説明しましたが、image
とenv
です。
これに関しては説明したので省略します。
あとはcommand
部分です。本記事ではshellファイルを用意してそれを実行するようにしています。shellファイルは以下のようなものです。
#!/bin/sh
set -e
bundle exec rails db:create
bundle exec rails db:migrate
# 他にしたい処理があれば記載する
これを実行することで、データベースの作成からマイグレートを実行します。
ただし、今回はJobが正常に実行できたかどうかを判定する必要があります。そのために以下のようなshellファイルを作成し、実行すればそれを検出することが可能です。
#!/bin/bash
# 前のJobが残っていたらまずは消す
kubectl delete job deploy-tasks 2&> /dev/null || true
# マイグレート用のJobを作成し、実行します
cat kubernetes/deploy-tasks-job.yml | envsubst | kubectl create -f -
# Jobが正常に実行されるまで待ちます
while [ true ]; do
phase=`kubectl get pods -a --selector="name=deploy-tasks" -o 'jsonpath={.items[0].status.phase}' || 'false'`
if [[ "$phase" != 'Pending' ]]; then
break
fi
done
# Jobの終了状態を取得します
while [ true ]; do
succeeded=`kubectl get jobs deploy-tasks -o 'jsonpath={.status.succeeded}'`
failed=`kubectl get jobs deploy-tasks -o 'jsonpath={.status.failed}'`
if [[ "$succeeded" == "1" ]]; then
break
elif [[ "$failed" -gt "0" ]]; then
kubectl describe job deploy-tasks
kubectl delete job deploy-tasks
echo 'マイグレートに失敗!'
exit 1
fi
done
kubectl delete job deploy-tasks || true
上記のshellを以下のように実行すれば、マイグレートが実行できます。
$ export RAILS_IMAGE=us.gcr.io/rails-test-182304/rails:latest
$ ./script/deploy.sh
アプリケーションのデプロイ
アプリケーションのコンテナを立てたことは立てのですが、プログラムを更新したい場合はコンテナを更新する必要があります。ここで更新する方法は2つあります。
kubectl set image deployment/rails rails:1.0.0
-
kubectl apply -f kubernetes/rails.yaml
まず1ですが、既にデプロイされている設定に新たなイメージを記載する方法です。逆に2は作成したYMLファイルを更新してからコンテナを更新させる方法です。
1に関しては初回設定をずっと使用し続けます。逆に途中で変更するには2を使用する必要があります。なので設定も変更することを考慮して2を使用するとします。
しかし、2を使用するにはコンテナのアップデートをわざわざYMLファイルに記載しないといけませんが、envsubst
を使用することで解消できるわけです。
$ export RAILS_IMAGE=us.gcr.io/rails-test-182304/rails:latest
$ cat kubernetes/rails.yml | envsubst | kubectl apply -f -
これでコンテナのデプロイも可能となるわけです。
これで以下のコマンドで出てきたhttp://<EXTERNAL-IP>:3000
でサイトにアクセスできるはずです。
$ kubectl get service
トラブルシューティング
こうは書いても結構つまることが多々あります。こう書いている自分も色々と詰まりましたので、自身のログ参照方法を書いておきます。
Cloud Computeにsshログインする
コンテナを実行しているクラウドサーバにsshにログインします。ログイン先は以下のコマンドで確認可能です。
$ kubectl get pods -o wide
# NAME READY STATUS RESTARTS AGE IP NODE
# postgresql-1102438392-7cc4p 1/1 Running 0 1d 10.0.2.6 gke-rails-cluster-default-pool-df1514fd-30s7
# rails-4074712412-k9np7 1/1 Running 0 1d 10.0.1.4 gke-rails-cluster-default-pool-df1514fd-qpth
$ gcloud compute ssh gke-rails-cluster-default-pool-df1514fd-qpth
これでsshにログインできると思います。何か調査するお供にどうぞ。
ログや状態を確認する
deploymentを設定してもkubectl get pods
を叩くと"Ready"や"0/1"となるコンテナが起動しない。もしくは起動と失敗を繰り返す場合にはログを確認すると早いです。
kubectl describe pod <pod name>
取得したpodの名前を上記のコマンドで使用すると状態を確認することができます。
標準出力を確認する
標準出力だけを参照する場合は
$ kubectl logs <podのNAME>
で標準出力の参照が可能です。
起動コンテナの中に入る
ログを見てもよくわからない場合はコンテナに入ってしまいましょう。最終手段ですが…
kubectl exec -it <pod name> /bin/sh
これでコンテナの中に入れるようになるはずです。ただし、コンテナが起動していることが最低条件です。
最後に
Railsアプリケーションを例に本記事を書きましたが、Railsだけではなく他のアプリケーションでも応用可能だと思います。
よいGKE(Kubernetes)ライフをお送り下さい。
参考URL
https://cloud.google.com/kubernetes-engine/docs/tutorials?hl=ja
https://engineering.adwerx.com/rails-on-kubernetes-8cd4940eacbe