チュートリアル: Kubernetes のデプロイにカナリア デプロイ戦略を使用する

Azure DevOps Services | Azure DevOps Server 2022

"カナリア" デプロイ戦略とは、安定した運用バージョンの隣に新しいバージョンのアプリケーションをデプロイすることを意味します。 その後、デプロイを昇格させるか拒否する前に、カナリア バージョンとベースラインを比較して確認できます。

このステップ バイ ステップ ガイドでは、Kubernetes マニフェスト タスクのカナリア戦略を使用する方法について説明します。 具体的には、Kubernetes のカナリア デプロイと、コードを評価するための関連ワークフローを設定する方法について説明します。 その後、そのコードを使ってベースラインとカナリア アプリのデプロイを比較し、カナリア デプロイを昇格させるか拒否するかを決定できます。

Azure Kubernetes Service を使っている場合、プライベート クラスターまたはローカル アカウントが無効になっているクラスターに接続する最適な方法は、Azure Resource Manager サービス接続の種類です。

前提条件

サンプル コード

GitHub で次のリポジトリをフォークします。

https://github.com/MicrosoftDocs/azure-pipelines-canary-k8s

このガイドで使うリポジトリ内のファイルの概要を次に示します。

  • ./app:
    • app.py - Python アプリケーション用の Prometheus インストルメンテーション ライブラリを使ってインストルメント化されたシンプルな Flask ベースの Web サーバー。 カスタム カウンターは、success_rate 変数の値に基づいて、指定された良い応答と悪い応答の数に設定されます。
    • Dockerfile - app.py に行われた変更ごとにイメージをビルドするために使われます。 変更されるたびに、ビルド パイプラインがトリガーされてイメージがビルドされ、コンテナー レジストリにプッシュされます。
  • ./manifests:
    • deployment.yml - 前に公開されたイメージに対応する sampleapp デプロイ ワークロードの仕様が格納されています。 このマニフェスト ファイルは、デプロイ オブジェクト安定バージョン用だけでなく、ワークロードのベースラインとカナリアのバリエーションを派生するためにも使います。
    • service.yml - sampleapp サービスを作成します。 このサービスは、前に説明したデプロイ (安定、ベースライン、カナリア) によってスピンアップされたポッドに要求をルーティングします。
  • ./misc
    • service-monitor.yml - ServiceMonitor オブジェクトを設定するために使われます。 このオブジェクトは、Prometheus メトリック スクレイピングを設定します。
    • fortio-deploy.yml - fortio デプロイを設定するために使われます。 このデプロイは、後でロード テスト ツールとして使われ、前にデプロイされた sampleapp サービスに要求のストリームを送信します。 sampleapp に送信される要求のストリームは、3 つのデプロイ (安定、ベースライン、カナリア) すべてでポッドにルーティングされます。

注意

このガイドでは、コードのインストルメンテーションと監視に Prometheus を使います。 Azure Application Insights などの同等のソリューションを代わりに使用できます。

prometheus-operator をインストールする

クラスターに Prometheus をインストールするには、開発用コンピューターから次のコマンドを使います。 kubectl と Helm がインストールされている必要があり、コンテキストをデプロイ対象のクラスターに設定する必要があります。 後でベースラインとカナリアのメトリックをダッシュボードで視覚化するために使う Grafana は、この Helm チャートの一部としてインストールされます。

最初に、Prometheus Community Kubernetes Helm Charts リポジトリを Helm インストールに追加します。 次に、kube-prometheus スタック、Kubernetes マニフェスト、Grafana ダッシュボード、および Prometheus ルールのコレクションをインストールします。

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update # update local cache
helm install --name sampleapp  prometheus-community/kube-prometheus-stack

サービス接続を作成する

  1. Azure DevOps メニューで [プロジェクトの設定]>[パイプライン]>[サービス接続] に移動します。
  2. コンテナー レジストリに関連付けられた Docker レジストリ サービス接続を作成します。 その名前を azure-pipelines-canary-k8s にします。
  3. デプロイ先の Kubernetes クラスターと名前空間に対する Kubernetes サービス接続を作成します。 その名前を azure-pipelines-canary-k8s にします。

注意

Azure Kubernetes Service を使っている場合、プライベート クラスターまたはローカル アカウントが無効になっているクラスターに接続する最適な方法は、Azure Resource Manager サービス接続の種類です。

継続的インテグレーションのセットアップ

  1. [パイプライン]>[パイプラインの作成] に移動して、リポジトリを選びます。

  2. [構成] タブで、[スタート パイプライン] を選びます。

  3. [レビュー] タブで、パイプライン YAML を次のコードに置き換えます。

    trigger:
    - main
    
    pool:
      vmImage: ubuntu-latest
    
    variables:
      imageName: azure-pipelines-canary-k8s
    
    steps:
    - task: Docker@2
      displayName: Build and push image
      inputs:
        containerRegistry: azure-pipelines-canary-k8s #replace with name of your Docker registry service connection
        repository: $(imageName)
        command: buildAndPush
        Dockerfile: app/Dockerfile
        tags: |
          $(Build.BuildId)
    

    作成した Docker レジストリ サービス接続が example.azurecr.io に関連付けられている場合、上記の構成に基づいて、イメージは example.azurecr.io/azure-pipelines-canary-k8s:$(Build.BuildId) に対するものになります。

マニフェスト ファイルを編集する

manifests/deployment.yml で、<example> を自分のコンテナー レジストリの URL に置き換えます。 たとえば、置き換えた後のイメージ フィールドは contosodemo.azurecr.io/azure-pipelines-canary-k8s のようになります。

Azure App Service での GIT による継続的なデプロイ

次のセクションでは、カナリア ステージのデプロイ方法や、手動介入によってカナリアを昇格させるか拒否する方法など、継続的デプロイを設定する手順について説明します。

Deploy canary ステージ

YAML またはクラシックを使ってデプロイできます。

  1. [パイプライン]>[環境]>[環境の作成] に移動します。

  2. 環境の新規作成

    • 名前: akscanary
    • リソース: Kubernetes を選びます。
  3. [次へ] を選んで、次のように Kubernetes リソースを構成します。

    • プロバイダー: Azure Kubernetes Service
    • Azure サブスクリプション: Kubernetes クラスターを保持するサブスクリプションを選びます。
    • クラスター: 自分のクラスターを選びます。
    • 名前空間: canarydemo という名前で新しい名前空間を作成します。
  4. [検証と作成] を選びます。

  5. [パイプライン] に移動します。 作成したパイプラインを選んで、[編集] を選びます。

  6. 前に作成したステップを、ステージを使うように変更します。 連続するステージで使うための成果物として manifests と misc ディレクトリをコピーするため、さらに 2 つのステップを追加します。 後のパイプラインで簡単に使用できるように、いくつかの値を変数に移動することもできます。 完成した YAML は次のようになります。

    trigger:
    - main
    
    pool:
      vmImage: ubuntu-latest
    
    variables:
      imageName: azure-pipelines-canary-k8s
      dockerRegistryServiceConnection: dockerRegistryServiceConnectionName #replace with name of your Docker registry service connection
      imageRepository: 'azure-pipelines-canary-k8s'
      containerRegistry: example.azurecr.io #replace with the name of your container registry, Should be in the format example.azurecr.io
      tag: '$(Build.BuildId)'
    
    stages:
    - stage: Build
      displayName: Build stage
      jobs:  
      - job: Build
        displayName: Build
        pool:
          vmImage: ubuntu-latest
        steps:
        - task: Docker@2
          displayName: Build and push image
          inputs:
            containerRegistry: $(dockerRegistryServiceConnection)
            repository: $(imageName)
            command: buildAndPush
            Dockerfile: app/Dockerfile
            tags: |
              $(tag)
    
        - publish: manifests
          artifact: manifests
    
        - publish: misc
          artifact: misc
    
  7. カナリア バージョンをデプロイするためのステージを、YAML ファイルの最後に追加します。

    - stage: DeployCanary
      displayName: Deploy canary
      dependsOn: Build
      condition: succeeded()
    
      jobs:
      - deployment: Deploycanary
        displayName: Deploy canary
        pool:
          vmImage: ubuntu-latest
        environment: 'akscanary.canarydemo'
        strategy:
          runOnce:
            deploy:
              steps:
              - task: KubernetesManifest@0
                displayName: Create imagePullSecret
                inputs:
                  action: createSecret
                  secretName: azure-pipelines-canary-k8s
                  dockerRegistryEndpoint: azure-pipelines-canary-k8s
    
              - task: KubernetesManifest@0
                displayName: Deploy to Kubernetes cluster
                inputs:
                  action: 'deploy'
                  strategy: 'canary'
                  percentage: '25'
                  manifests: |
                    $(Pipeline.Workspace)/manifests/deployment.yml
                    $(Pipeline.Workspace)/manifests/service.yml
                  containers: '$(containerRegistry)/$(imageRepository):$(tag)'
                  imagePullSecrets: azure-pipelines-canary-k8s
    
              - task: KubernetesManifest@0
                displayName: Deploy Forbio and ServiceMonitor
                inputs:
                  action: 'deploy'
                  manifests: |
                    $(Pipeline.Workspace)/misc/*
    
  8. メイン ブランチに直接コミットして、パイプラインを保存します。 このコミットで、パイプラインは既に正常に実行しているはずです。

カナリアを昇格させるか拒否するための手動介入

YAML またはクラシックを使って手動で介入できます。

  1. [パイプライン]>[環境]>[新しい環境] に移動します。

  2. 新しい環境を構成します。

    • 名前: akspromote
    • リソース: Kubernetes を選びます。
  3. [次へ] を選んで、次のように Kubernetes リソースを構成します。

    • プロバイダー: Azure Kubernetes Service
    • Azure サブスクリプション: Kubernetes クラスターを保持するサブスクリプションを選びます。
    • クラスター: 自分のクラスターを選びます。
    • 名前空間: 前に作成した名前空間 canarydemo を選びます。
  4. [検証と作成] を選びます。

  5. 環境の一覧から新しい akspromote 環境を選びます。

  6. [承認とチェック]>[承認] を選びます。 次に、省略記号アイコン (3 つのドット) を選びます。

  7. 次のように承認を構成します。

    • 承認者: 自分のユーザー アカウントを追加します。
    • 詳細設定: [承認者が自分の実行を承認できるようにします] ボックスがオンになっていることを確認します。
  8. [作成] を選択します

  9. [パイプライン] に移動し、作成したパイプラインを選びます。 次に、 [編集] を選択します。

  10. 変更を昇格させるための別のステージ PromoteRejectCanary を、YAML ファイルの最後に追加します。

    - stage: PromoteRejectCanary
      displayName: Promote or Reject canary
      dependsOn: DeployCanary
      condition: succeeded()
    
      jobs:
      - deployment: PromoteCanary
        displayName: Promote Canary
        pool: 
          vmImage: ubuntu-latest
        environment: 'akspromote.canarydemo'
        strategy:
          runOnce:
            deploy:
              steps:            
              - task: KubernetesManifest@0
                displayName: promote canary
                inputs:
                  action: 'promote'
                  strategy: 'canary'
                  manifests: '$(Pipeline.Workspace)/manifests/*'
                  containers: '$(containerRegistry)/$(imageRepository):$(tag)'
                  imagePullSecrets: '$(imagePullSecret)'
    
  11. 変更をロールバックするための別のステージ RejectCanary を、YAML ファイルの最後に追加します。

    - stage: RejectCanary
      displayName: Reject canary
      dependsOn: PromoteRejectCanary
      condition: failed()
    
      jobs:
      - deployment: RejectCanary
        displayName: Reject Canary
        pool: 
          vmImage: ubuntu-latest
        environment: 'akscanary.canarydemo'
        strategy:
          runOnce:
            deploy:
              steps:            
              - task: KubernetesManifest@0
                displayName: reject canary
                inputs:
                  action: 'reject'
                  strategy: 'canary'
                  manifests: '$(Pipeline.Workspace)/manifests/*'
    
  12. [保存] を選んで YAML パイプラインを保存してから、メイン ブランチに直接コミットします。

安定バージョンをデプロイする

YAML またはクラシックを使って安定バージョンをデプロイできます。

パイプラインの最初の実行では、ワークロードの安定バージョンと、そのベースラインまたはカナリア バージョンは、クラスターに存在しません。 安定バージョンをデプロイするには:

  1. app/app.py で、success_rate = 5success_rate = 10 に変更します。 この変更によってパイプラインがトリガーされ、イメージがビルドされて、コンテナー レジストリにプッシュされます。 また、DeployCanary ステージもトリガーされます。
  2. akspromote 環境で承認を構成したため、リリースはそのステージを実行する前に待機します。
  3. 実行の概要で、[レビュー]>[承認] を選びます。 これにより、ワークロードの安定バージョン (manifests/deployment.yml 内の sampleapp のデプロイ) が名前空間にデプロイされます。

カナリア ワークフローを開始する

ワークロード sampleapp の安定バージョンがクラスターに存在するようになりました。 次に、シミュレーション アプリケーションに対して次の変更を行います。

app/app.py で、success_rate = 10success_rate = 20 に変更します。

この変更によりビルド パイプラインがトリガーされ、イメージがビルドされてコンテナー レジストリにプッシュされます。 このプロセスによってリリース パイプラインがトリガーされて、Deploy canary ステージが開始されます。

要求をシミュレートする

開発用コンピューターで次のコマンドを実行し、それを実行したままにして、sampleapp サービスで一定の要求ストリームを送信します。 sampleapp によって、要求は、安定デプロイ sampleapp によってスピンアップされたポッドと、デプロイ sampleapp-baselinesampleapp-canary によってスピンアップされたポッドにルーティングされます。 sampleapp に対して指定したセレクターを、これらのすべてのポッドに適用できます。

FORTIO_POD=$(kubectl get pod | grep fortio | awk '{ print $1 }')
kubectl exec -it $FORTIO_POD -c fortio /usr/bin/fortio -- load -allow-initial-errors -t 0 http://sampleapp:8080/

Grafana ダッシュボードを設定する

  1. Grafana にアクセスできるように、ローカル開発用コンピューターで次のポート転送コマンドを実行します。

    kubectl port-forward svc/sampleapp-grafana 3000:80
    
  2. ブラウザーで、次の URL を開きます。

    http://localhost:3000/login
    
  3. 資格情報の入力を求められたら、prometheus-operator Helm チャートのインストール中に adminPassword の値がオーバーライドされていない限り、次の値を使用できます。

    • ユーザー名: admin
    • パスワード: prom-operator
  4. 左側のメニューから、[+]>[ダッシュボード]>[グラフ] を選びます。

  5. 新しく追加されたパネルの任意の場所を選び、「e」と入力してパネルを編集します。

  6. [メトリック] タブで、次のクエリを入力します。

    rate(requests_total{pod=~"sampleapp-.*", custom_status="good"}[1m])
    
  7. [全般] タブで、このパネルの名前を「すべての sampleapp ポッド」に変更します。

  8. ページの上部にある概要バーで、期間の範囲を [過去 5 分] または [過去 15 分] に変更します。

  9. このパネルを保存するには、概要バーの保存アイコンを選びます。

  10. 前のパネルに、すべてのバリエーションの成功率メトリックが表示されます。 これには、安定 (sampleapp デプロイから)、ベースライン (sampleapp-baseline デプロイから)、カナリア (sampleapp-canary デプロイから) が含まれます。 次の構成で別のパネルを追加することで、ベースラインとカナリアのメトリックのみを表示できます。

    • [全般] タブで、[タイトル] を「sampleapp のベースラインとカナリア」にします。
    • [メトリック] タブで、次のクエリを使います。
    rate(requests_total{pod=~"sampleapp-baseline-.*|sampleapp-canary-.*", custom_status="good"}[1m])
    

    注意

    ベースラインとカナリアのメトリックのパネルには、特定の条件下での比較に使用できるメトリックのみが表示されます。 これらの条件は、Deploy canary ステージが正常に完了し、Promote/reject canary ステージが手動介入を待機している場合です。

    ヒント

    Deploy canaryPromote/reject canary のステージ完了イベントを視覚的に示すため、Grafana ダッシュボードの注釈を設定します。 これは、それぞれ、ベースラインとカナリアの比較を開始するタイミングと、カナリアの昇格または拒否が完了したタイミングを把握するのに役立ちます。

ベースラインとカナリアを比較する

  1. この時点で、Deploy canary ステージは正常に完了しています (success_rate10 から 20 に変化したので)。 Promote/reject canary ステージは、手動介入を待機しています。 Grafana ダッシュボードで、ベースラインとカナリアのバリエーションの (custom_status=good によって決定される) 成功率を比較できます。 次のようになっているはずです。

    Screenshot that shows a comparison of baseline and canary metrics.

  2. カナリアの成功率が高いという観察に基づいて、カナリアを昇格させます。 手動介入タスクで [再開] を選びます。