その他Production環境で利用したい保守作業
その他Production環境で利用したい保守作業として、2つのスクリプトファイルを例として追加しました。
- マイグレーション:script/k8s-migrate
- コンソール(one-off container):script/k8s-console
Console
サービスを運用していく上では必ず、サーバーでの調査作業やアドホックな作業が必要になると思います。Kubernetesにも作業できる仕組みはありますが、どうしても手順が複雑になるため、script/k8s-consoleというファイルを作成し、コマンド一発で利用できるようにしています。
利用するコンテナイメージはプロジェクトによってさまざまな要件があるものの、大きく分けて2つあると思います。
- Productionで動いているPodと同じコンテナイメージで調査
- ブランチ開発中のコンテナイメージで調査
この2つを同時に満たすため、基本的にlatestタグのイメージを利用します。ブランチの指定があった場合は、そのブランチの最新のコミット番号から利用できるようにしています。
実行するシェルスクリプトは以下の通りです。
#!/usr/bin/env bash
set -eu
set -o pipefail
KUBE=kubectl
NAME="$(awk '{ print tolower($0) }' <<< "$USER")"
NAME="${NAME}-$(date '+%Y%m%d%H%M%s')"
TAG="latest"
if [ $# -eq 1 ]; then
COMMIT=$(git rev-parse $1)
TAG=${COMMIT::7}
fi
TEMPFILE="$(mktemp)"
COLUMNS=`tput cols`
LINES=`tput lines`
TERM=xterm
create_pod() {
cat kubernetes/run.yaml | sed "s,USER,$NAME,g" | sed "s/latest/$TAG/g" > "$TEMPFILE"
$KUBE create -f "$TEMPFILE"
}
destroy_pod() {
$KUBE delete po $NAME -n myapp
}
wait_for_up() {
while true ; do
sleep 2
echo "Waiting for the container to be up and running..."
$KUBE get po $NAME -n myapp | grep Running > /dev/null 2>&1 && break
done
}
enter_to_pod() {
$KUBE exec -it $NAME env COLUMNS=$COLUMNS LINES=$LINES TERM=$TERM -n myapp /bin/bash
}
at_exit() {
rm "$TEMPFILE"
destroy_pod
}
main() {
create_pod
trap at_exit EXIT
trap "trap - EXIT; at_exit; exit -1" SIGHUP SIGINT SIGTERM
wait_for_up
enter_to_pod
}
main
コンソールを立ち上げるためのマニフェストファイルです。
apiVersion: v1
kind: Pod
metadata:
name: USER
namespace: myapp
spec:
containers:
- command:
- bash
image: quay.io/koudaiii/myapp:latest
imagePullPolicy: Always
name: USER
stdin: true
stdinOnce: true
terminationMessagePath: /dev/termination-log
tty: true
env:
- name: "RAILS_SERVE_STATIC_FILES"
value: "enabled"
- name: "RAILS_LOG_TO_STDOUT"
value: "enabled"
envFrom:
- secretRef:
name: dotenv
restartPolicy: Never
コンソールで中に入って確認します。
$ ./script/k8s-console pod "koudaiii-2017091114111505106709" created Waiting for the container to be up and running... Waiting for the container to be up and running... bash-4.3# rails c Loading production environment (Rails 5.1.3) irb(main):001:0>
Productionの環境変数が入った状態でコンテナが起動し、bashが利用できます。Podの一覧を見るとPCのアカウント名が入った専用のPodが作られています。
$ kubectl get po -n myapp NAME READY STATUS RESTARTS AGE koudaiii-2017091114111505106709 1/1 Running 0 9s myapp-2136627869-62hdf 2/2 Running 0 4h myapp-2136627869-k2l7b 2/2 Running 0 4h myapp-2136627869-nb49l 2/2 Running 0 4h myapp-2136627869-r6961 2/2 Running 0 4h postgres-2312165663-5vzcs 1/1 Running 0 4h
作業が終わったらexitで閉じて、Podも削除します。
bash-4.3# rails c Loading production environment (Rails 5.1.3) irb(main):001:0> exit bash-4.3# exit exit pod "koudaiii-2017091114111505106709" deleted
Migrate
サービスを運用していくと必ずスキーマの変更が出てくるタイミングがあります。KubernetesではJobという形でone-shotで実行できます。しかし、rails db:migrateのように任意で複数回立たれるものについては、その度にJobのマニフェストファイルを作成するのが手間です。そのためこちらも、script/k8s-migrateというファイルを作成し、コマンド一発で利用できるようにしています。
ここでも、利用するコンテナイメージはプロジェクトによってさまざまな要件があるものの、大きく分けて2つあると思います。
- 現在Productionで動いているコンテナのイメージでMigrateを実行したい
- ブランチ作業中でマージ直前に作成したコンテナのイメージで実行したい
この2つを同時に満たすため、自分の作業場所から最新のコミット番号を取得し利用できるようにしています。
実行するシェルスクリプトは以下の通りです。
#!/usr/bin/env bash
set -eu
set -o pipefail
LOWUSER=$(tr '[A-Z]' '[a-z]' <<<$USER)
NAME=$LOWUSER-$(date '+%Y%m%d%H%M%s')
COMMIT=$(git rev-parse HEAD)
TAG=${COMMIT::7}
cat kubernetes/migration.yaml | sed "s/\[REPLACE_WITH_DATETIME\]/$NAME/g" | sed "s/\[REPLACE_WITH_TAG\]/$TAG/g"> /tmp/$NAME.yaml
kubectl create -f /tmp/$NAME.yaml
こちらはMigrateを実行するためのマニフェストファイルです。
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate-[REPLACE_WITH_DATETIME]
namespace: myapp
labels:
name: db-migrate-[REPLACE_WITH_DATETIME]
role: batch
spec:
template:
metadata:
name: db-migrate-[REPLACE_WITH_DATETIME]
labels:
name: db-migrate-[REPLACE_WITH_DATETIME]
role: batch
spec:
restartPolicy: Never
containers:
- name: db-migrate-[REPLACE_WITH_DATETIME]
image: quay.io/koudaiii/myapp:[REPLACE_WITH_TAG]
command: ["bundle", "exec", "rails", "db:migrate"]
env:
- name: "RAILS_SERVE_STATIC_FILES"
value: "enabled"
- name: "RAILS_LOG_TO_STDOUT"
value: "enabled"
envFrom:
- secretRef:
name: dotenv
$ ./script/k8s-migrate
job "db-migrate-koudaiii-2017091116251505114716" created
$ kubectl get po -a -n myapp
NAME READY STATUS RESTARTS AGE
db-migrate-koudaiii-2017091116251505114716-707pt 0/1 Completed 0 21m
myapp-2136627869-62hdf 2/2 Running 0 6h
myapp-2136627869-k2l7b 2/2 Running 0 6h
myapp-2136627869-nb49l 2/2 Running 0 6h
myapp-2136627869-r6961 2/2 Running 0 6h
postgres-2312165663-5vzcs 1/1 Running 0 6h
$ kubectl logs db-migrate-koudaiii-2017091116251505114716-707pt -n myapp
D, [2017-09-11T16:29:01.229152 #1] DEBUG -- : (31.6ms) CREATE TABLE "schema_migrations" ("version" character varying NOT NULL PRIMARY KEY)
D, [2017-09-11T16:29:01.237281 #1] DEBUG -- : (5.6ms) CREATE TABLE "ar_internal_metadata" ("key" character varying NOT NULL PRIMARY KEY, "value" character varying, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL)
D, [2017-09-11T16:29:01.238534 #1] DEBUG -- : (0.5ms) SELECT pg_try_advisory_lock(4671394259554759170)
D, [2017-09-11T16:29:01.259086 #1] DEBUG -- : (0.6ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
D, [2017-09-11T16:29:01.265267 #1] DEBUG -- : ActiveRecord::InternalMetadata Load (0.3ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2 [["key", "environment"], ["LIMIT", 1]]
D, [2017-09-11T16:29:01.272660 #1] DEBUG -- : (0.4ms) BEGIN
D, [2017-09-11T16:29:01.274580 #1] DEBUG -- : SQL (0.5ms) INSERT INTO "ar_internal_metadata" ("key", "value", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "key" [["key", "environment"], ["value", "production"], ["created_at", "2017-09-11 07:29:01.273098"], ["updated_at", "2017-09-11 07:29:01.273098"]]
D, [2017-09-11T16:29:01.275963 #1] DEBUG -- : (0.9ms) COMMIT
D, [2017-09-11T16:29:01.276508 #1] DEBUG -- : (0.3ms) SELECT pg_advisory_unlock(4671394259554759170)
まとめ
今回はサンプルとしてRuby on Railsを利用しましたが、どの言語でも今回紹介したファイル群の考え方が活用できるでしょう。
マイクロサービス化する際には、こうしたルールを作ることが重要です。バラバラになりやすい運用作業について、可能な限り統制を保つことができます。
また、今回のような事例が社内で増えれば、それをそのまま参考にして組織として横展開がしやすくなります。
- オペレーションの統一化
- アプリケーション固有の方言をなくす
この他にも、ちょっとした仕組みで効率化できる部分はたくさんあると思います。
ここからさらに発展して、分散管理になりがちなスクリプトファイルもできる限りツールなどで中央管理的に扱えるようになれば、さらに統制が効きやすくなってエンジニアの生産性向上に役立ちます。私たちは日々こういったツールの開発をしています。
