現在、本番も開発もKubernetesとCircleCIでCI/CDしている。
※参考
MigrationについてもKubernetes JobがCircleCIのJob内からDBに対して実行するようにしてあるのだが、これまでMigrationのログはCircleCIのJobには出力していなくて不便だった。
どこまでMigrationされたかを確認するのに、わざわざクラスタへ接続してPodのログを見るみたいな…。
今回はそれをやめて、CircleCI内で見れるようにしたら便利だよって話。
概要
説明しないこと
- Kubernetesについて
- CircleCIについて
- Migrationとはなにか
わかること
- Migration Jobをどう実行させてるか
- どうやってログをCircleCI内で出力させるか
Migration Jobをどう実行させてるか
Deployフローの簡易図。
- Git pushをトリガーに、更新分のDocker imageを作成する
- Migration Jobが実行される ←この部分の話
- kubectlでDeploy
このMigrationを実行するためのスクリプトやJobの作成はこちらが大変参考になる。
記事に書いてある通り、Deploy前に確実にMigrationを終わらせる必要があるため、Migration JobのPodのステータスをチェックする必要がある。 それをこのスクリプトで実現しておく。
- applyされるJobのyaml
apiVersion: batch/v1 kind: Job metadata: name: xxx-db-migration #Jobの名前にアンダースコアが入るとエラーになるのでハイフンで spec: backoffLimit: 3 parallelism: 1 completions: 1 template: spec: containers: - name: xxx-db-migration image: xxxxxxxxx command: ["/bin/bash", "-c"] args: - | rake db:migrate env: - name: yyyyy value: "xxxxxx" restartPolicy: Never
- CircleCIが実際に実行するスクリプト例(先の参考にさせていただいたスクリプトからは改良してある)
- 各環境に対してこのスクリプトで対応できるように、Migration先の分岐を実施
- Jobのステータスチェックを実施
#!/bin/bash set -e echo "環境変数に設定されたデータベース名によってMigration先を分岐します。" if [ "$MIGRATION_TARGET" != '' ]; then echo -e "\033[0;32mMigration Database is $MIGRATION_TARGET.\033[0;39m" else echo -e "\033[0;31m[ERROR] CircleCIに設定された環境変数 \$MIGRATION_TARGET (Migration対象のデータベース名)の値が見つかりませんでした。\033[0;39m" echo -e "contextページにて、適切な変数を設定してください。https://circleci.com/gh/organizations/xxxxxx/settings#contexts" exit 1 fi case $MIGRATION_TARGET in "aaaaaaa" ) MIGRATION_FILE=~/project/staging/migration_job.yaml ;; "bbbbbb" ) MIGRATION_FILE=~/project/production/migration_job.yaml ;; esac # execute job ENV_TARGET=$(echo ${MIGRATION_TARGET} | sed s#_#\-#g) #Jobの名前にアンダースコアが入るとエラーになるので置換 job_name="${ENV_TARGET}-db-migration" # Stop if job remain. if kubectl get job ${job_name}; then echo -e "\033[0;31m[ERROR] Migration Jobが残っています。次の事象がないか確認してください。\n・別のWorkflowで、${MIGRATION_TARGET}へのMigrationが実行されている\n・別のWorkflowで、${MIGRATION_TARGET}へのMigration Jobが失敗している\n\033[0;39m" echo -e "[Solution] Migration Jobをdeleteしてください。\n$ kubectl get pod\n$ kubectl delete job ${job_name}" exit 1 else echo -e "\033[0;32m別のWorkflowで$MIGRATION_TARGETへのJobはありませんでした。\033[0;39m" echo -e "\033[0;32mJobの実行を開始します。\033[0;39m" fi # Apply the job. kubectl apply -f $MIGRATION_FILE # Wait for the job to run. while true; do phase=$(kubectl get pod --selector="job-name=${job_name}" -o 'jsonpath={.items[0].status.phase}') if [ "${phase}" != 'Pending' ]; then break fi sleep 2 done # Check the status of the job. while true; do is_active=$(kubectl get job ${job_name} -o 'jsonpath={.status.active}') if [ "${is_active}" != '' ]; then # migration pod log. pod=$(kubectl get pods --selector="job-name=${job_name}" -o 'jsonpath={.items..metadata.name}') kubectl logs -f ${pod} sleep 2 continue fi succeeded=$(kubectl get job ${job_name} -o 'jsonpath={.status.succeeded}') if [ "${succeeded}" -eq 1 ]; then break else exit 1 fi done # Delete the job. echo -e "\033[0;32m正常にJobが完了しました。Jobを削除します。\033[0;39m" kubectl delete -f $MIGRATION_FILE
どうやってログをCircleCI内で出力させるか
これまで、Migrationが異常終了してもCircleCIのJobがエラーになったことしかわからず不便だったので、参考にさせていただいたスクリプトに以下のものを追加した。
どうやってというか、echoで色付出力してるだけである。
- 異常終了時
- CI実行者は誰かわからない(誰でもDeployすることが可能なのがよいところ)ので、何かが起きたら、何が起きたかわかるように
# Migrationのターゲットとなる先が存在しなかった場合 if [ "$MIGRATION_TARGET" != '' ]; then echo -e "\033[0;32mMigration Database is $MIGRATION_TARGET.\033[0;39m" else echo -e "\033[0;31m[ERROR] CircleCIに設定された環境変数 \$MIGRATION_TARGET (Migration対象のデータベース名)の値が見つかりませんでした。\033[0;39m" echo -e "contextページにて、適切な変数を設定してください。https://circleci.com/gh/organizations/xxxxxx/settings#contexts" exit 1 fi # Migration Jobがすでに存在していた場合は停止する / 存在していなければJob実行 # Stop if job remain. if kubectl get job ${job_name}; then echo -e "\033[0;31m[ERROR] Migration Jobが残っています。次の事象がないか確認してください。\n・別のWorkflowで、${MIGRATION_TARGET}へのMigrationが実行されている\n・別のWorkflowで、${MIGRATION_TARGET}へのMigration Jobが失敗している\n\033[0;39m" echo -e "[Solution] Migration Jobをdeleteしてください。\n$ kubectl get pod\n$ kubectl delete job ${job_name}" exit 1 else echo -e "\033[0;32m別のWorkflowで$MIGRATION_TARGETへのJobはありませんでした。\033[0;39m" echo -e "\033[0;32mJobの実行を開始します。\033[0;39m" fi
- 正常終了時
# 終わったことを出力 echo -e "\033[0;32m正常にJobが完了しました。Jobを削除します。\033[0;39m"
- 実行ログ
- これが地味によい。クラスタに接続しなくても実行ログが見れる
- loopの中でkubectl logs -f をして出力させる
# Check the status of the job. while true; do is_active=$(kubectl get job ${job_name} -o 'jsonpath={.status.active}') if [ "${is_active}" != '' ]; then # 実行されているJobのPod名を取得してlogs -fで出力させる # migration pod log. pod=$(kubectl get pods --selector="job-name=${job_name}" -o 'jsonpath={.items..metadata.name}') kubectl logs -f ${pod} sleep 2 continue fi
結果がこちら
というわけで
わざわざログを見に行く手間が省けて便利。
こんな感じで、日々CI/CDを改善して行く〜。