概要
Kubernetes で GitOps を実現するツールである Argo CDと、Kubernetes で blue/grenn デプロイや canary リリースを実現するためのデプロイメントコントローラーであるArgo Rolloutsを使用して、canaryリリースの動作検証をしてみます。
Argo Rollouts と Nginx Ingress Controller の連携の必要性
Kubernetes は標準でトラフィックミラーリングやヘッダーによるルーティングやパーセンテージ指定による負荷分散などの細かいトラフィック管理を実現できるような機能はありません。
Kubernetesでアプリケーションの異なるバージョンへのトラフィックの割合を制御する唯一の方法は、svc のセレクターに基づいて pod のグループにトラフィックをルーティングするエンドポイントを提供することです。つまりバージョンのレプリカ数を操作することで実現できる単純な負荷分散です。
この負荷分散方式だと Pod 単位 での割合でしか分散することができません。Argo Rolloutsも Kubernetesの標準機能を使用しているので、実現できる Canaryデプロイメントもこの程度です。
しかしArgo Rolloutsは、サービスメッシュリソースを操作してロールアウトの目的に一致させることにより、きめ細かいトラフィック管理を実現することが可能です。Argo Rollouts は現在次のサービスメッシュをサポートしています。
- Istio
- Nginx Ingress Controller
- AWS ALB Ingress Controller
- Service Mesh Interface(SMI)
私の環境ではNginx Ingress Controllerにて各サービスへのトラフィック管理をしているため、Nginx Ingress Controller と Argo Rollouts を連携させることで、きめ細かいトラフィック管理ができる canaryデプロイメントを実現します。
Nginx Ingress Controller はそれ単体でトラフィックミラーリングやヘッダーによるルーティングやパーセンテージ指定による負荷分散を使用したcanary の仕組みが備わっており、アプリケーションの異なるバージョン間のトラフィック管理のために、いくつかの特別なアノテーションを備えた2つ目のIngressオブジェクト(Canary Ingress)を導入することにより、トラフィックを分割する機能を提供します。
Argo Rollouts Controller は、動的に Canary Ingress を生成することで、Nginx Ingress Controller と連携します。
構成図

基本の構成はこのようになります。このうちtomozo-canaryIngressは、Argo Rollouts Controller に自動生成されることになります。
各マニフェスト
Ingress
Nignx Ingress Controller用 の Ingress です。argo rolloutsを使用することで追加で必要な設定は特にありません。
くどいようですが、canary用のIngressは自動生成されるため、ここで定義する必要はありません。
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: tomozo
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/use-regex: "true"
spec:
  rules:
  - host: tomozo.tokyo
    http:
      paths:
      - backend:
          serviceName: tomozo-stable
          servicePort: 80
        path: /v1/tomozo/.*
Service
通常(安定版)用の Service として tomozo-stable。Canary用の Service として tomozo-canaryを定義します。名前以外は全て同じ設定値で問題ありません。
---
kind: Service
apiVersion: v1
metadata:
  name: tomozo-stable
spec:
  selector:
    app: tomozo
  ports:
    - port: 80
---
kind: Service
apiVersion: v1
metadata:
  name: tomozo-canary
spec:
  selector:
    app: tomozo
  ports:
    - port: 80
rollout
strategy:より上は deployment と書き方変わりません。大事なのは trafficRouting で nginx を指定しているところです。
stableIngressでしていた Ingress をベースに、 Canary-Ingress を自動生成します。
またannotationPrefix,additionalIngressAnnotationsで指定した内容が、自動生成される Canary-Ingressに設定されます。
私の場合は、 Ingress で正規表現を使用しているのでuse-regex: 'true'を追加しました。
また Nginx の canary 用として、以下の4つの annotation があります。
| Name | Description | 
|---|---|
| canary-weigh | パーセンテージを指定し、特定の割合でルーティング先を変えることができます | 
| canary-by-cookie | 特定のCookieの名前を設定しておくことで、そのCookieの値がリクエストに含まれている際に、アクセスを新バージョン側に振ることができる | 
| canary-by-header | 特定のHeaderの名前を設定しておくことで、そのHeaderの値がリクエストに含まれている際に、アクセスを新バージョン側に振ることができる | 
| canary-by-header-value | canary-by-header で指定したHeaderの値が、ここで指定した値の場合に、アクセスを新バージョン側に振ることができます | 
carry-weighについては、strategy.canary.steps.setWeightで指定した内容が反映されるため、ここであえて指定する必要はありません。
今回は canary-by-headerとcanary-by-header-valueを設定してみました。
---
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: tomozo
  labels:
    app: tomozo
spec:
  selector:
    matchLabels:
      app: tomozo
  template:
    metadata:
      labels:
        app: tomozo
    spec:
      serviceAccountName: tomozo
      containers:
        - name: tomozo
          image: tomozo-repo/tomozo:0.0.1
# -----この行から上は元のDeploymentと一緒------
# -----この行から下はRolloutの拡張部分------
  strategy:
    canary:
      stableService: tomozo-stable
      canaryService: tomozo-canary
      trafficRouting:
        nginx:
          stableIngress: tomozo
          annotationPrefix: nginx.ingress.kubernetes.io
          additionalIngressAnnotations:
             use-regex: 'true'
             canary-by-header: 'X-Canary'
             canary-by-header-value: 'true'
      steps:
      - setWeight: 10
      - pause: {}
Canary デプロイメントの流れ
      steps:
      - setWeight: 10
      - pause: {}
rollout のマニフェストで設定したstrategy.canary.stepsの場合、どのような流れになるかを簡単な図で追っていきます。
通常時

通常時はこのようになっており、 アクセスは Service: tomozo-stable を通って ver.1の pod にルーティングされます。
Service: tomozo-canary もver.1 の pod にルーティングされていますが、Nginxからはアクセスを振っていません。
アプリケーションのリリース時

コンテナのタグを ver.2 にしたマニフェストを反映させると、ver.2用の ReplicaSet が作成され、 Service:tomozo-canaryからルーティングされます。また wetWeightの値が Nginx に反映され、 10%のアクセスが ver.2 のアプリケーションにルーティングされます。
アプリケーションのリリース完了

strategy.canary.stepsで設定している pause を解除すると、ver.2のアプリケーションが Service:tomozo-stableからもルーティングされるようになり、 ver.1のアプリケーションは削除されます。これでリリース完了となります。
なお、pauseの解除は コマンドでもできますし、 ArgoCD のGUI上からも操作可能です。
終わりに
Argo Rollouts を Nginx Ingress Controller と連携させることにより、お手軽かつきめ細かいCannaryデプロイメントを実現することができました。