VTRyo Blog

一歩ずつ前に進むブログ

KubernetesのJobで実行したMigrationのログをCircleCI内で見れると便利

現在、本番も開発もKubernetesとCircleCIでCI/CDしている。

※参考

MigrationについてもKubernetes JobがCircleCIのJob内からDBに対して実行するようにしてあるのだが、これまでMigrationのログはCircleCIのJobには出力していなくて不便だった。

どこまでMigrationされたかを確認するのに、わざわざクラスタへ接続してPodのログを見るみたいな…。

今回はそれをやめて、CircleCI内で見れるようにしたら便利だよって話。

概要

説明しないこと

  • Kubernetesについて
  • CircleCIについて
  • Migrationとはなにか

わかること

  • Migration Jobをどう実行させてるか
  • どうやってログをCircleCI内で出力させるか

f:id:vtryo:20200515200603p:plain

Migration Jobをどう実行させてるか

Deployフローの簡易図。

  • Git pushをトリガーに、更新分のDocker imageを作成する
  • Migration Jobが実行される ←この部分の話
  • kubectlでDeploy

f:id:vtryo:20200515192959p:plain
CircleCI workflowの例

このMigrationを実行するためのスクリプトやJobの作成はこちらが大変参考になる。

blog.manabusakai.com

記事に書いてある通り、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

結果がこちら

f:id:vtryo:20200515140958p:plain
成功したときの状態

というわけで

わざわざログを見に行く手間が省けて便利。

こんな感じで、日々CI/CDを改善して行く〜。

参考

kubernetes.io

blog.manabusakai.com