ブルーグリーン切替とカナリア分析
このラボは本物のVM上で動きます
この箱はPodではなく、KubeVirtが起動した仮想マシンです。Linuxカーネルが別に動き、systemdが実際にサービスを管理し、dockerは模倣ではなく本物のDockerエンジンです。docker runで起動したコンテナは実際にプロセスになり、docker execもdocker logsもそのまま動作します。
以前はこのラボがPodの中で動いていました。カーネル権限をすべて手放した箱だったので、コンテナを起動するステップが塞がれており、イメージのアーカイブを自分で展開してみる回り道で学んでいました。もう回り道は必要ありません。
知っておくことが2つあります。
- 最初に起動するまで1分ほどかかります。VMが起動し、Dockerをインストールするためです。Podのラボ(通常40秒)より遅くなります。
- ブラウザープレビューがありません。VMへの接続は採点用のポート1つだけが開いています。Webサーバーを起動したら、VMの中で
curlを使って確認してください。
目標
コンテナ2つでブルーグリーン環境を作り、トラフィックの切り替え・ヘルスチェック・自動ロールバック・カナリアの割合の振り分け・自動中止の判定をシェルで実装します。
なぜ重要なのか
デプロイ戦略の違いは、結局のところ、ロールバックの速度とトラフィック制御の精度の交換です。ブルーグリーンは2つの環境を同時に起動するので、リソースがPodの2倍かかりますが、問題が見えたらトラフィックをまるごとすぐに元へ戻せます。カナリアは1%単位で精密にさらせますが、戻すときにはステップが必要です。そのため、ほとんどのサービスにはカナリアを、決済や認証のように1件のエラーでもコストが大きいサービスにはブルーグリーンを使います。このラボでは、アクティブな対象がファイルの1行で表現されます。その1行を誰がいつ変え、変えたあとに何を確認し、確認が失敗したらどう元に戻すのかが、デプロイ自動化のすべてです。
ステップ
- レスポンスの本文に
version=blueが入るコンテナを、名前ci3-blueで起動し、127.0.0.1:8091に公開してください。curl http://127.0.0.1:8091/でその文字列が見える必要があります。 - 同じ方法で
ci3-greenを127.0.0.1:8092で起動してください。ただし本文はversion=greenにします。このときci3-blueはrunningのままでなければなりません。 /root/ci3/switch.sh <blue|green>を作ってください。アクティブな対象の名前を/root/ci3/activeに書きますが、ACTIVE_FILE環境変数が設定されていればそのパスに書きます。blue/green以外の値なら何も書かず、0以外のコードで終了してください。このステップを終えたとき、/root/ci3/activeの内容はgreenでなければなりません。/root/ci3/health.sh <URL>を作ってください。対象が正常に応答したら終了コード0、誰もいないか失敗したら0以外のコードを出します。採点は、8091、8092、そして誰もいない8099で確認します。/root/ci3/deploy.sh <대상>(プレースホルダーは対象です)を作ってください。現在のアクティブな値を記憶しておき、対象に切り替えたあと、その対象のポートでヘルスチェックをします。失敗したらアクティブな値を以前の値に戻し、0以外のコードで終了してください。成功したらアクティブな値は対象のまま残り、終了コードは0です。ポートはPORT_BLUE(既定は8091)、PORT_GREEN(既定は8092)の環境変数で上書きできる必要があり、ACTIVE_FILEにも対応する必要があります。/root/ci3/canary.sh <퍼센트>(プレースホルダーはパーセントです)を作ってください。リクエストを100件送り、どこへ行ったかを数えてblue=<수> green=<수>(プレースホルダーは件数です)を出力します。2つの数の合計は常に100で、引数が0ならblue=100 green=0、100ならblue=0 green=100でなければなりません。/root/ci3/analyze.sh <지표파일> <임계퍼센트>(プレースホルダーはメトリクスファイルとしきい値のパーセントです)を作ってください。メトリクスファイルには、total=1000とerrors=10が1行ずつ入っています。エラー率はerrors * 100 / totalで、しきい値以下なら終了コード0、超えたら実際のエラー率を出力して0以外のコードを出してください。total=0ならサンプルがないので、通過させず、失敗として扱ってください。/root/ci3/deploy-report.jsonを作ってください。フィールドはstrategy(blue-greenまたはcanary)、active(現在の/root/ci3/activeの内容と同じでなければなりません)、previous(activeと違っていなければなりません)、rollback_used、error_rate_pct、abort_threshold_pctです。rollback_usedには、今回のデプロイで実際にロールバックが起きたかどうかを、JSONの真偽値(trueまたはfalse)で書いてください。文字列"true"は使えません。error_rate_pctはabort_threshold_pct以下でなければなりません。
参考
- 本文の作り方の例:
/root/ci3/blue/index.htmlにversion=blueを入れ、docker run -d --name ci3-blue -p 127.0.0.1:8091:80 -v /root/ci3/blue:/usr/share/nginx/html:ro nginx:1.27-alpineを実行します。 - 1024未満のポートはバインドできません。必ず8091/8092のような高いポートを127.0.0.1に公開してください。
rollback_usedは、trueでもfalseでも事実のとおりに書けばかまいません。ただし必ずJSONの真偽値でなければならず、"false"のように引用符で囲むと型の検査で引っかかります。- カナリアの振り分けは、乱数の代わりに連番(例: 100で割った余り)を使うと、結果を再現できます。
- よくあるミスは、greenを起動するときにblueを停止してしまうこと、switch.shが
ACTIVE_FILEを無視して常に固定のパスに書いてしまうこと、activeとpreviousを同じ値で記録してしまうことです。
blue環境を起動する
レスポンスの本文にversion=blueが入るコンテナを、名前ci3-blueで起動し、127.0.0.1:8091に公開してください。curl http://127.0.0.1:8091/でその文字列が見える必要があります。
コンテナ名は正確にci3-blue、公開ポートは127.0.0.1:8091です。1024未満のポートはバインドできません。レスポンスの本文にversion=blueが入る必要があるので、index.htmlを作ってマウントする方法が最も簡単です。
green環境を並べて起動する
同じ方法でci3-greenを127.0.0.1:8092で起動してください。ただし本文はversion=greenにします。このときci3-blueはrunningのままでなければなりません。
ci3-greenを8092で起動し、本文はversion=greenです。このときblueを停止してはいけません。2つの環境が同時に生きていてこそ、無停止の切り替えが成り立ちます。
アクティブな対象の切り替えスクリプト
/root/ci3/switch.sh <blue|green>を作ってください。アクティブな対象の名前を/root/ci3/activeに書きますが、ACTIVE_FILE環境変数が設定されていればそのパスに書きます。blue/green以外の値なら何も書かず、0以外のコードで終了してください。このステップを終えたとき、/root/ci3/activeの内容はgreenでなければなりません。
switch.sh <blue|green>で、既定の書き込み先のファイルは/root/ci3/activeです。ACTIVE_FILE環境変数があれば、そのパスを使う必要があります(採点は一時的なパスで検証します)。知らない値なら何も書かずに失敗してください。1文字のタイプミスが、トラフィックを存在しない場所へ送ります。
ヘルスチェック
/root/ci3/health.sh <URL>を作ってください。対象が正常に応答したら終了コード0、誰もいないか失敗したら0以外のコードを出します。採点は、8091、8092、そして誰もいない8099で確認します。
health.sh <URL>は、生きていれば0、そうでなければ0以外のコードです。curl -fsS -m 3のように、失敗時にエラーになり、タイムアウトがある形で呼び出してください。常に通過するヘルスチェックは、ないのと同じです。
失敗したら自動でロールバックする
/root/ci3/deploy.sh <대상>(プレースホルダーは対象です)を作ってください。現在のアクティブな値を記憶しておき、対象に切り替えたあと、その対象のポートでヘルスチェックをします。失敗したらアクティブな値を以前の値に戻し、0以外のコードで終了してください。成功したらアクティブな値は対象のまま残り、終了コードは0です。ポートはPORT_BLUE(既定は8091)、PORT_GREEN(既定は8092)の環境変数で上書きできる必要があり、ACTIVE_FILEにも対応する必要があります。
deploy.sh <대상>(プレースホルダーは対象です)は、現在のアクティブな値を先に記憶しておかないと元に戻せません。ポートはPORT_BLUE(既定は8091)、PORT_GREEN(既定は8092)で上書きできる必要があり、ACTIVE_FILEにも対応する必要があります。ヘルスチェックが失敗したら、以前の値に復元し、0以外の終了コードを返します。
割合の振り分け
/root/ci3/canary.sh <퍼센트>(プレースホルダーはパーセントです)を作ってください。リクエストを100件送り、どこへ行ったかを数えてblue=<수> green=<수>(プレースホルダーは件数です)を出力します。2つの数の合計は常に100で、引数が0ならblue=100 green=0、100ならblue=0 green=100でなければなりません。
canary.sh <퍼센트>(プレースホルダーはパーセントです)は、リクエストを100件送り、blue=<수> green=<수>(プレースホルダーは件数です)を出力します。合計は常に100でなければならないので、リクエストの失敗をそのまま見逃してはいけません。乱数より連番に基づく振り分けのほうが、再現性が高いです。
自動中止の判定
/root/ci3/analyze.sh <지표파일> <임계퍼센트>(プレースホルダーはメトリクスファイルとしきい値のパーセントです)を作ってください。メトリクスファイルには、total=1000とerrors=10が1行ずつ入っています。エラー率はerrors * 100 / totalで、しきい値以下なら終了コード0、超えたら実際のエラー率を出力して0以外のコードを出してください。total=0ならサンプルがないので、通過させず、失敗として扱ってください。
analyze.sh <지표파일> <임계퍼센트>(プレースホルダーはメトリクスファイルとしきい値のパーセントです)で、メトリクスファイルはtotal=1000とerrors=10の2行です。しきい値と同じなら通過です。totalが0ならサンプルがなく判断できないので、通過させてはいけません。中止するときは、実際のエラー率を出力してください。
デプロイレポート
/root/ci3/deploy-report.jsonを作ってください。フィールドはstrategy(blue-greenまたはcanary)、active(現在の/root/ci3/activeの内容と同じでなければなりません)、previous(activeと違っていなければなりません)、rollback_used、error_rate_pct、abort_threshold_pctです。rollback_usedには、今回のデプロイで実際にロールバックが起きたかどうかを、JSONの真偽値(trueまたはfalse)で書いてください。文字列"true"は使えません。error_rate_pctはabort_threshold_pct以下でなければなりません。
/root/ci3/deploy-report.jsonのactiveは/root/ci3/activeの内容と同じで、previousは違っている必要があります。rollback_usedには、今回のデプロイで実際にロールバックが起きたかどうかを、真か偽かで書きます。引用符のないJSONの真偽値でなければなりません。error_rate_pctはabort_threshold_pct以下でなければなりません。