🌏

Flagger による k8s アプリケーションの Blue/Green デプロイ

2023/12/16に公開

はじめに

既に稼働しているアプリケーションのバージョンを上げる際のデプロイ方法にはいろいろありますが、特にダウンタイムの発生しないデプロイ方法としては以下のものが有名です。

  • Blue/Green デプロイ (以下 B/G デプロイ)
  • Canary デプロイ

k8s クラスタに deployment として展開されているアプリケーションのバージョンを変更することを考える場合、deployment リソース内のイメージタグを更新してクラスタに適用すれば RollingUpdateStrategy に基づいて既存の pod が徐々に置き換えられていきます。( kubernetes ドキュメント を参照)
この方法では常に最小限の pod が稼働していることが保証されておりダウンタイムが発生しません。なのでまあこれでも充分といえば充分ですが、上記デプロイ方法のように置き換え前後でアプリケーションの正常性確認をしたり、デプロイが失敗した際に自動でロールバックを実行したいような場合もあります。
k8s の Service を工夫することでも実現できますが、デプロイの自動化やリリース作業をより効率的に行うための Flagger という OSS ツールがあるので、今回は Flagger とサービスメッシュの linkerd を合わせた BG デプロイを試してみます。

Flagger

Flagger はサービスメッシュや Ingress Controller と組み合わせてアプリケーションの様々なデプロイ戦略を実現するツールです。CNCF graduate project である Flux の一部に位置づけられています。

https://flagger.app/

flagger ではダウンタイムの発生しない以下のようなデプロイ戦略を実現することができます。

  • Canary (progressive traffic shifting)
  • A/B Testing (HTTP headers and cookies traffic routing)
  • Blue/Green (traffic switching and mirroring)

また、デプロイ実行時や結果の slack への通知や自動ロールバック、rollout 中の負荷テストなどの機能もあります。

Linkerd

Linkerd は k8s 用の軽量サービスメッシュツールです。同様の役割を果たすツールとしては envoy が有名です。

https://linkerd.io/

Linkerd は監視対象の pod にマイクロプロキシと呼ばれるコンテナを注入し、pod を通過するトラフィックを監視、可視化、制御する役割を果たします。主な機能はドキュメントの Reference にまとまっており、サービスメッシュもこの機能の一つとなっています。

flagger と組み合わせた B/G デプロイにおいては HTTPRoute の機能を使っているようです。

準備

インストール

linkerd, flagger で BG デプロイを行うためにそれぞれのコンポーネントを k8s クラスタにインストールします。

Linkerd

linkerd のインストールは linkerd ドキュメントの Getting start に沿って進めます。
linkerd は CLI を通じて CRD や様々なリソースをインストールできるようになっているため、はじめに CLI をインストールします。

curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/install | sh
export PATH=$HOME/.linkerd2/bin:$PATH
linkerd check --pre

次に linkerd Custom Resource Definition (CRD) と linkerd 本体のインストールを行います。

linkerd install --crds | kubectl apply -f -
linkerd install | kubectl apply -f -
linkerd check

これにより linkerd namespace が作成され、linkerd dataplane が作成されます。

linkerd-viz のインストール

linkerd viz install | kubectl apply -f -

これにより linkerd-viz ダッシュボード等の拡張機能がインストールされます。今回の範囲でこの中に含まれる prometheus を使用します。

flagger

flagger のインストールは Linkerd Canary Deployments の Prerequisites に従って行います。マニフェストや helm などのインストール方法が用意されていますが、今回はマニフェストからインストールします。

kubectl apply -k github.com/fluxcd/flagger//kustomize/linkerd

linkerd SMI というコンポーネントも必要なのでインストール

curl -sL https://linkerd.github.io/linkerd-smi/install | sh
linkerd smi install | kubectl apply -f -

アプリケーションの準備

B/G デプロイの動作確認にはアプリケーションを古いバージョンから新しいバージョンにアップデートする作業が必要になるので、簡単なアプリケーションとしてルートにアクセスしたら現在のバージョンを返すだけの単純な python アプリを用意します。

main.py
from flask import Flask

app = Flask(__name__)
version = "1.0"

@app.route("/")
def test():
    return {"version": version}

if __name__ == "__main__":
    app.run(host="0.0.0.0")
Dockerfile
FROM python:3.11-slim

RUN pip install flask
COPY . /
ENTRYPOINT ["/python"]
CMD ["/main.py"]

この中の version = "1.0" をそれぞれ 1.0, 2.0 としたイメージを作成し、イメージタグをそれぞれ v1, v2 とします。
アプリケーションのルートにアクセスすると現在のバージョンが取得できます。

$ curl 10.106.142.221:5000
{"version":"1.0"}

$ curl 10.106.142.221:5000
{"version":"2.0"}

k8s クラスタにアプリケーションをデプロイするために deployment を作成します。
イメージ名は linkerd-test としてどこかのレジストリに push しておきます (今回はローカルに立てた harbor)。

myapp-v1.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: linkerd-test
  namespace: linkerd-mysvc
spec:
  replicas: 2
  selector:
    matchLabels:
      app: linkerd-test
  template:
    metadata:
      labels:
        app: linkerd-test
    spec:
      containers:
      - image: harbor.centre.com/k8s/linkerd-test:v1
        name: flask
        ports:
        - containerPort: 5000
          name: http

Flagger デプロイで使う用語・概念

flagger ではやや見慣れない概念や用語があるので、デプロイを試す前に確認しておきます。

canary オブジェクト

flagger ではカスタムリソースである canary オブジェクトによってデプロイ時の挙動を制御します。設定可能な項目は ドキュメントの usage に細かく書いてあります。

Deployment の種類

canary オブジェクトをクラスタ上に作成すると、デプロイ対象として設定した deployments の pod 数が 0 に設定され、代わりに同じプロパティが設定された [deployment_name]-primary という deployment が作成されます。

デプロイを行う前後の定常状態では primary deployment の pod が外部からのトラフィックに対応し、デプロイ中の正常性試験などは元の deployment の pod が対応します。
元の deployment のイメージ等の更新作業は管理者などのユーザが行い、primary deployment のバージョンアップやロールバックは canary オブジェクトが変更に基づいて自動で更新する構成になっています。

また、canary オブジェクト作成時に外部からのトラフィックをルーティングするための 3 つの svc オブジェクトも同時に作成されます。
例えば linkerd-test という deployment に対して canary オブジェクトを作成した場合、以下の svc が自動で作成されます。

$ kubectl get svc
NAME                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
linkerd-test           ClusterIP   10.97.84.59      <none>        5000/TCP   136m
linkerd-test-canary    ClusterIP   10.109.254.145   <none>        5000/TCP   136m
linkerd-test-primary   ClusterIP   10.110.50.78     <none>        5000/TCP   136m

デプロイを行う前後の定常状態ではアプリケーションのトラフィックは linkerd-test, linkerd-test-primary サービスを通じて primary deployment へルーティングされます。

デプロイ中では元の deployment の replica 数が調整され、置き換え先バージョンへの正常性確認は linkerd-test-canary サービスを通じて行われます。linkerd-test サービスには HTTPRoute オブジェクトが紐付けられ、事前定義したプロパティに応じてトラフィックを linkerd-test-canary と linkerd-test-primary に分割します (canary デプロイの場合)。このような構成によりトラフィックを置き換え先バージョンへルーティングしてテストなどを行います。

Flagger におけるデプロイ phase

flagger を使ったデプロイでは内部的にいくつかの phase を経由して新しいバージョンへの置き換えが行われます。ドキュメントの用語を使うと以下の phase に分けられます。

  • 定常状態
    • デプロイが行われる前、またはデプロイが完了した状態。
  • Rollout
    • アプリケーションのバージョンアップを行う前の段階。
    • analysis に基づいてメトリクスの評価などが行われます。事前に定義した基準を満たすと rollout は成功とみなされ Promotion に移行します。
    • この最中に基準を下回った場合などはデプロイに失敗したとみなされ、リソースをデプロイ前の状態に戻す rollback に移行します。
  • Promotion
    • Promotion では primary deployment を新しいバージョンに変更します。これにより外部からのトラフィックは新しいバージョンのアプリケーションにルーティングされます。
  • Rollback
    • rollback では Rollout phase で増えた deployments の replica 数を 0 に戻す等の処理を行い、リソースを deploy が始まる前の状態に戻します。これによりデプロイ中の primary への影響を最小限に抑えることができます。

これら phase の移行をざっくりと図示すると以下のようになります。

image

また、各 phase の前後やトラフィック移行前などに webhook を実行することができます。これを利用すると Rollout 前にアプリケーションの各 API の応答をチェックしたり、rollout の最中に負荷テストを行うことができます。

B/G デプロイの実行

ここでは実際に B/G デプロイによってアプリケーションのバージョンを v1 → v2 に更新する際の挙動を確認します。

デプロイシナリオの定義

B/G デプロイを実行する際は、どのような条件でデプロイを成功・失敗と判断するかの条件を事前に定義することが重要になります。
実際のデプロイではアプリケーションの仕様によってデプロイ前に API エンドポイントの正常性を確認したり Rollout の段階で負荷テストを行ったりしますが、今回は単純なケースとしてデプロイ成功・失敗条件を以下のように決めます。

  • 1 回の判定条件について
    • 30 秒毎のアプリケーションの Response 成功率が 99 % 以上で成功、それ以下で失敗。
  • 5 回連続で判定に失敗したらデプロイに失敗したとみなし、前バージョンにロールバックする。
  • 5 回判定に判定に成功したらデプロイに成功したとみなし、トラフィックを新しいバージョンに移行する。
  • 判定は 30 秒毎に行う。

デプロイのシナリオを決めたら、この動作を実現するための canary リソースを以下のように定義します。

canary.yml
spec:
  analysis:
    interval: 30s
    iterations: 5
    threshold: 5
    metrics:
      - name: success-rate
        templateRef:
          name: success-rate
          namespace: test
        thresholdRange:
          min: 99
        interval: 30s
        templateVariables:
          direction: inbound

Rollout 中の判定条件は linkerd-viz インストール時に同時にインストールされた prometheus が取得する metrics に基づいて評価します。これは flagger CRD の MetricTemplate オブジェクトによって行われます。

---
apiVersion: flagger.app/v1beta1
kind: MetricTemplate
metadata:
  name: success-rate
  namespace: test
spec:
  provider:
    type: prometheus
    address: http://prometheus.linkerd-viz:9090
  query: |
    sum(
      rate(
        response_total{
          namespace="{{ namespace }}",
          deployment=~"{{ target }}",
          classification!="failure",
          direction="{{ variables.direction }}"
        }[{{ interval }}]
      )
    )
    /
    sum(
      rate(
        response_total{
          namespace="{{ namespace }}",
          deployment=~"{{ target }}",
          direction="{{ variables.direction }}"
        }[{{ interval }}]
      )
    )
    * 100

query フィールドの中身が判定条件である 30 秒毎のアプリケーションの Response 成功率 を計算するクエリ式に対応しています (数値自体は canary オブジェクト側で代入)。
この他、別の prometheus を使用する、固有のカスタムメトリクスを評価対象する、他の provider を指定するなどの手順が https://docs.flagger.app/usage/metrics にまとまっています。

実行

B/G デプロイを実行する前に、まずアプリケーションの deployment バージョン 1.0 である linkerd-test deployment をクラスタにデプロイします。
次に上記で作成した canary オブジェクトをデプロイします。これにより元の deployment の replica が 0 に設定され、[deployment]-primary という deployment から作成された pod が代わりに起動します。

$ kubectl get deployments.apps linkerd-test
NAME           READY   UP-TO-DATE   AVAILABLE   AGE
linkerd-test   0/0     0            0           23h

$ kubectl get deployments.apps linkerd-test-primary
NAME                   READY   UP-TO-DATE   AVAILABLE   AGE
linkerd-test-primary   1/1     1            1           23h

これで準備が整ったので、アプリケーションを新しいバージョン v2 に更新します。

deployment.yml
-      - image: harbor.centre.com/k8s/linkerd-test:v1
+      - image: harbor.centre.com/k8s/linkerd-test:v2

canary オブジェクトが新しいバージョンへの更新を検知すると、version 2.0 の deployment の replica 数が増え、デプロイの Rollout が開始されます。Rollout 中は canary オブジェクトによって定期的にメトリクス評価が行われ、新しいバージョンの deployment が判定条件を満たすかどうかチェックされます。この様子は kubectl event -wkubectl describe canary [name] 等で確認できます。

Events:
  Type     Reason  Age                 From     Message
  ----     ------  ----                ----     -------
  Warning  Synced  10m                 flagger  linkerd-test-primary.linkerd-mysvc not ready: waiting for rollout to finish: observed deployment generation less than desired generation
  Warning  Synced  9m9s                flagger  HTTPRoute .linkerd-mysvc update error: resource name may not be empty while reconciling
  Normal   Synced  8m9s (x3 over 10m)  flagger  all the metrics providers are available!
  Normal   Synced  8m9s                flagger  Initialization done! ctest.linkerd-mysvc
  Normal   Synced  5m9s                flagger  New revision detected! Scaling up linkerd-test.linkerd-mysvc
  Normal   Synced  4m9s                flagger  Starting canary analysis for linkerd-test.linkerd-mysvc
  Normal   Synced  4m9s                flagger  Advance ctest.linkerd-mysvc canary iteration 1/5
  Normal   Synced  3m9s                flagger  Advance ctest.linkerd-mysvc canary iteration 2/5
  Normal   Synced  2m9s                flagger  Advance ctest.linkerd-mysvc canary iteration 3/5
  Normal   Synced  69s                 flagger  Advance ctest.linkerd-mysvc canary iteration 4/5
  Normal   Synced  9s                  flagger  Advance ctest.linkerd-mysvc canary iteration 5/5

Advance ctest.linkerd-mysvc canary iteration 2/5 等のメッセージにより、 30 秒毎にメトリクの評価が行われていることが確認できます。今回設定した Response 成功率が 99 % 以上という条件はアプリケーションが正常な状態であればまず合格するため、Rollout の評価は正常に進んでいきます。

今回はデプロイ成功条件を 5 回に設定しているため、iteration が 5 回成功するとデプロイ成功と判断され、 Promotion phase に移行します。Promotion では primary deployment のイメージが v2 へと更新され、それが完了すると元の Deployment の replica 数が再度 0 に戻り、元の定常状態へと戻ります。

この辺りの元の deployment と primary deployment の pod 入れ替え動作は案外やってみないとわかづらいですが、ざっくり図示すると以下のような流れで変化していきます。


デプロイ中の deployment の動作

Canary デプロイの実行

flagger ではトラフィックを徐々に移行できる canary デプロイも実現できるため、こちらも試してみます。

準備

デプロイ方法を B/G から canary へと変更する方法は単純で、canary オブジェクトで iterations としていた部分を削除し maxWeightstopWeight を追加すれば良いです。

canary.yml
    interval: 60s
-    iterations: 5
+    maxWeight: 100
+    stepWeight: 25

この変更を行うだけでデプロイ方法が B/G → canary へと変更されます。

canary デプロイでは Rollout phase にトラフィックを徐々に新しいバージョンへと移行するため、移行の割合と移行完了の閾値をそれぞれ stepWeight, maxWeight として設定する必要があります。
上記の例では 60 秒毎に 25 % ずつトラフィックが新しいバージョンのアプリケーションへと移行し、100 % に達した時点で Rollout が完了します。すなわち、イメージタグを更新してからトラフィックの移行は以下のようになります、

経過時間 古いバージョンへのトラフィック 新しいバージョンへのトラフィック
0 sec 100 % 0 %
60 sec 75 % 25 %
120 sec 50 % 50 %
180 sec 25 % 75 %
240 sec 0 % 100 %

実行

B/G デプロイのときの同様に元の deployment のイメージタグを更新することで変更が検知され canary デプロイが始まります。
デプロイの進捗状況も B/G デプロイと同様に canary オブジェクトの events で確認できます。

Events:
  Type     Reason  Age                  From     Message
  ----     ------  ----                 ----     -------
  Warning  Synced  11m                  flagger  linkerd-test-primary.linkerd-mysvc not ready: waiting for rollout to finish: observed deployment generation less than desired generation
  Warning  Synced  10m                  flagger  HTTPRoute .linkerd-mysvc update error: resource name may not be empty while reconciling
  Normal   Synced  9m14s (x3 over 11m)  flagger  all the metrics providers are available!
  Normal   Synced  9m14s                flagger  Initialization done! ctest.linkerd-mysvc
  Normal   Synced  8m14s                flagger  New revision detected! Scaling up linkerd-test.linkerd-mysvc
  Normal   Synced  7m14s                flagger  Starting canary analysis for linkerd-test.linkerd-mysvc
  Normal   Synced  7m14s                flagger  Advance ctest.linkerd-mysvc canary weight 25
  Normal   Synced  6m14s                flagger  Advance ctest.linkerd-mysvc canary weight 50
  Normal   Synced  5m14s                flagger  Advance ctest.linkerd-mysvc canary weight 75
  Normal   Synced  4m14s                flagger  Advance ctest.linkerd-mysvc canary weight 100
  Normal   Synced  3m14s                flagger  Copying linkerd-test.linkerd-mysvc template spec to linkerd-test-primary.linkerd-mysvc
  Normal   Synced  74s (x2 over 2m14s)  flagger  (combined from similar events): Promotion completed! Scaling down linkerd-test.linkerd-mysvc

canary デプロイでは Advance ctest.linkerd-mysvc canary weight のメッセージによりトラフィックが段階的に移行していることが確認できます。
トラフィックの移行は httproutes オブジェクトの backend service への Weight が変化することに対応しています。
例えば古いバージョンへのルーティングが 75 %, 新しいバージョンへのルーティングが 25 % の段階で kubectl describe httproutes.gateway.networking.k8s.io で httproutes を確認すると、Backend Refs の Weight がそれぞれこの値に設定されていることが確認できます。

Spec:
  Parent Refs:
    Group:      core
    Kind:       Service
    Name:       linkerd-test
    Namespace:  linkerd-mysvc
    Port:       5000
  Rules:
    Backend Refs:
      Group:
      Kind:    Service
      Name:    linkerd-test-primary
      Port:    5000
      Weight:  75
      Group:
      Kind:    Service
      Name:    linkerd-test-canary
      Port:    5000
      Weight:  25
    Matches:
      Path:
        Type:   PathPrefix
        Value:  /

linkerd-test svc にアクセスした際に上記の割合でルーティングすることで canary デプロイを実現する構成になっています。もちろん canary デプロイの Rollout が進むにつれて httproutes の weight の割合も変化していきます。

トラフィックのルーティングについて

ところで canary デプロイではトラフィックを新旧バージョンにそれぞれルーティングできますが、デプロイ中も指定した割合で本当にトラフィックがルーティングされているのか少し気になります。これを実際に確かめるために、デプロイ中に linkerd-test svc に継続的にアクセスし、レスポンス結果を csv に書き込むごくシンプルなスクリプトを使ってざっくりと計測しました。

#!/usr/bin/env python3
import requests
from datetime import datetime
import time
import csv

def run():
    url = "http://linkerd-test.linkerd.mysvc:5000"
    output = "output.csv"

    with open (output, "w") as f:
        print("Start")
        writer = csv.writer(f)
        while True:
            try:
                res = requests.get(url=url)
                now = datetime.now().strftime("%Y/%m/%d %H:%M:%S.%f")
                value = res.json()["version"]
                print(now, value)
                writer.writerow([now, value])
                time.sleep(0.1)
            except KeyboardInterrupt:
                break
            except:
                pass

if __name__ == "__main__":
    run()

今回のアプリケーションでは http request に対してアプリケーションのバージョンを response として返すように実装しているため、バージョンを見ることで新旧どちらの svc にルーティングされたか判別できます。デプロイ開始 ~ 完了まで上記のスクリプトを実行することでトラフィックルーティングの変化が見れるようになっています。

実際に計測を行い、ルーティング先を 10 秒ごとにまとめてグラフ化したものが以下になります。

これにより、デプロイ開始前では linkerd-test svc へのアクセスはすべて旧バージョンのサービス(version 1.0 pod) にルーティングされますが、デプロイ開始から時間が経過するにつれてアクセスが徐々に新バージョンのサービス (version 2.0 の pod) にルーティングされている様子が確認できます。
ただし、canary デプロイ自体は 60 秒毎に 25 % ずつトラフィックを移行するように設定したのでグラフの version 1, 2 の割合も 3:1 → 2:2 → 1:3 のように変化することが想定されますが、図を見れば明らかなように上記の計測ではそのように変化していません。今回の計測手段や集計方法はかなりざっくりとしたものだったので、このあたりはもう少し厳密に計測することでより近い値になるかと思われます。
いずれにしても、canary デプロイの最中は新旧サービスにトラフィックが分割される動作が実際に確認できました。

その他

slack 通知

flagger の Alertprovider カスタムリソースを使用すると、デプロイの開始時や成功時に slack にメッセージを投稿できます。

https://docs.flagger.app/usage/alerting

apiVersion: flagger.app/v1beta1
kind: AlertProvider
metadata:
  name: on-call
  namespace: linkerd-mysvc
spec:
  type: slack
  channel: on-call-alerts
  username: bot-test
  secretRef:
    name: on-call-url

---
apiVersion: v1
kind: Secret
metadata:
  name: on-call-url
  namespace: linkerd-mysvc
data:
  address: <encoded-url>

使うには canary マニフェストの analysis.alerts に定義します。

spec:
 analysis:
    alerts:
      - name: "on-call Slack"
        severity: info
        providerRef:
          name: on-call
          namespace: linkerd-mysvc

デプロイを実行すると、デプロイのタイプやターゲット、実行結果を slack に通知してくれます。

ただ現時点では機能はそれほど多くなく、投稿されるメッセージのカスタマイズなどはできなさそうです。Github を眺めるとメッセージのカスタマイズの機能リクエストなどの issue が見られるので今後機能が充実していくかも知れません。

自動ロールバック

flagger では新しいバージョンへの移行中に決められた基準を満たさいない場合にデプロイ前のバージョンに自動でロールバックする機能があるのでそちらも試してみます。

ロールバックの動作を試すには移行途中で正常性チェックに失敗する必要があるので、以下のように起動してから 30 秒後にアクセスできなくなるようにアプリケーションを修正します。

entrypoint.sh
#!/bin/bash
/main.py &
sleep 30
pkill python
sleep 120
Dockerfile
FROM python:3.11-slim-bookworm

RUN apt-get update && apt-get install -y procps
RUN pip install flask
COPY main.py /main.py
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x main.py entrypoint.sh

ENTRYPOINT [ "/entrypoint.sh" ]

これを rollback バージョンとして、v1 → rollback へのバージョンアップの際にロールバックが発生するようにします。デプロイに使用するオブジェクトや手順については B/G デプロイの実行 と同様です。

deployment 内のイメージタグを更新して Rollout phase に進んだ後、kubectl describe canaries.flagger.app により canary オブジェクトの event を見ると、アプリケーションの停止により 3 回目の iteration に失敗してロールバックが行われたことが確認できます。

Events:
  Type     Reason  Age                    From     Message
  ----     ------  ----                   ----     -------
  Warning  Synced  3m12s                  flagger  linkerd-test-primary.linkerd-mysvc not ready: waiting for rollout to finish: observed deployment generation less than desired generation
  Warning  Synced  2m52s                  flagger  HTTPRoute .linkerd-mysvc update error: resource name may not be empty while reconciling
  Normal   Synced  2m32s (x3 over 3m12s)  flagger  all the metrics providers are available!
  Normal   Synced  2m32s                  flagger  Initialization done! ctest.linkerd-mysvc
  Normal   Synced  112s                   flagger  New revision detected! Scaling up linkerd-test.linkerd-mysvc
  Normal   Synced  92s                    flagger  Starting canary analysis for linkerd-test.linkerd-mysvc
  Normal   Synced  92s                    flagger  Advance ctest.linkerd-mysvc canary iteration 1/5
  Normal   Synced  72s                    flagger  Advance ctest.linkerd-mysvc canary iteration 2/5
  Warning  Synced  52s                    flagger  Halt ctest.linkerd-mysvc advancement success-rate 10.78 < 99
  Warning  Synced  32s                    flagger  Rolling back ctest.linkerd-mysvc failed checks threshold reached 1
  Warning  Synced  32s                    flagger  Canary failed! Scaling down ctest.linkerd-mysvc

slack 上では iteration に失敗した際のメッセージが赤色で表示されます。

ロールバックが完了した状態では primary deployment のバージョンはデプロイ前の v1 ですが、もとの deployment では変更後の rollback となっています (ただし pod 数は 0)。

$ kubectl get deployments.apps linkerd-test-primary -o yaml | grep image:
      - image: harbor.centre.com/k8s/linkerd-test:v1

$ kubectl get deployments.apps linkerd-test -o yaml | grep image:
      - image: harbor.centre.com/k8s/linkerd-test:rollback

この状態からデプロイを再実行するには flagger Q&A に書いてあるようにいくつかの方法があります。例にあるようにもとの deployment を編集して annotations.timestamp のようなフィールドを追加のが一番手っ取り早いです。

apiVersion: apps/v1
kind: Deployment
spec:
  template:
    metadata:
      annotations:
        timestamp: "2020-03-10T14:24:48+0000

これがもとの deployment オブジェクトに対する変更とみなされデプロイが再実行されます。

イメージタグは既に変更済みなので変更してデプロイをトリガーすることはできず、オブジェクトの他のプロパティを更新する必要があるのがやや不便かと思いました。このあたりの動作を CI/CD に組み込んで自動化するには少し工夫する必要がありそうです。

まとめ

Flagger を用いた k8s 上アプリケーションの Blue/Green デプロイを試しました。
やや独特な用語や仕組みがあるので使い始めは慣れが必要ですが、様々なデプロイ戦略を実施できるため使いこなせるとアプリケーションのリリース作業がより効率的に実行できるかと思われます。シンプルなデプロイの自動化などは Argocd などの CI/CD ツールで実現できますが、デプロイ中の正常性確認や自動ロールバックを実現したい場合は Flagger と組み合わせると有効そうです。

Discussion