教程:使用 Canary 部署策略进行 Kubernetes 部署

Azure DevOps Services | Azure DevOps Server 2022

本分步指南介绍如何将 Kubernetes 清单任务canary 策略结合使用。 Canary 部署策略将应用程序的新版本部署在稳定的生产版本旁边。

使用关联的工作流来部署代码,并比较基线和 Canary 应用部署。 根据评估,决定是升级还是拒绝 Canary 部署。

本教程使用 Docker 注册表和 Azure Resource Manager 服务连接连接到 Azure 资源。 对于 Azure Kubernetes 服务 (AKS) 专用群集或禁用了本地帐户的群集,Azure 资源管理器服务连接是更好的连接方式。

先决条件

GitHub 存储库文件

GitHub 存储库包含以下文件:

文件 说明
./app/app.py 简单的基于 Flask 的 Web 服务器。 该文件根据 success_rate 变量的值为良好响应和不良响应的数量设置了一个自定义计数器。
./app/Dockerfile 用于在每次更改 app.py 时生成映像。 每次更改都会触发生成管道来构建映像并将其推送到容器注册表。
./manifests/deployment.yml 包含与已发布镜像对应的 sampleapp 部署工作负载的规范。 可以将此清单文件用于部署对象的稳定版本,并用于导出工作负载的基线和 Canary 变体。
./manifests/service.yml 创建 sampleapp 服务。 此服务将请求路由到由稳定部署、基准部署和 Canary 部署启动的 Pod。
./misc/fortio.yml 设置 fortio 部署。 此部署是一个负载测试工具,用于向已部署 sampleapp 的服务发送请求流。 请求流在三种部署下路由到 Pod:稳定、基准和 Canary。

创建服务连接

  1. 在 Azure DevOps 项目中,转到项目设置>管道>服务连接
  2. 创建与 Azure 容器注册表实例关联的名为 azure-pipelines-canary-acrDocker 注册表服务连接
  3. 为资源组创建名为 azure-pipelines-canary-k8sAzure Resource Manager 与工作负荷标识的服务连接

添加生成阶段

  1. 在 Azure DevOps 项目中,转到管道>创建管道新建管道

  2. 为代码位置选择 GitHub,然后选择分叉的 azure-pipelines-canary-k8s 存储库。

  3. 在“配置”选项卡上,选择“初学者管道”。

  4. 审阅选项卡上,用以下代码替换管道 YAML。

    trigger:
    - main
    
    pool:
      vmImage: ubuntu-latest
    
    variables:
      imageName: azure-pipelines-canary-k8s # name of ACR image
      dockerRegistryServiceConnection: azure-pipelines-canary-acr # name of ACR service connection
      imageRepository: 'azure-pipelines-canary-k8s' # name of image repository
      containerRegistry: example.azurecr.io # name of Azure container registry
      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)
    

    如果创建的 Docker 注册表服务连接与名为 example.azurecr.io 的容器注册表相关联,则映像将设置为 example.azurecr.io/azure-pipelines-canary-k8s:$(Build.BuildId)

  5. 选择保存并运行,并确保作业成功运行。

编辑清单文件

在存储库分支中,编辑 manifests/deployment.yml,将 <foobar> 替换为容器注册表的 URL,例如 example.azurecr.io/azure-pipelines-canary-k8s

设置连续部署

现在,设置持续部署,部署 Canary 阶段,并通过手动审批来升级或拒绝 Canary。

创建环境

可以使用 YAML 或经典进行部署。

  1. 在 Azure DevOps 项目中,转到管道>环境,然后选择创建环境新建环境
  2. 在第一个新建环境屏幕上,在名称下输入 akscanary,在资源下选择 Kubernetes,然后选择下一步
  3. 按如下所示填写 Kubernetes 资源屏幕:
    • 提供程序:选择 Azure Kubernetes 服务
    • Azure 订阅:选择自己的 Azure 订阅。
    • 群集:选择你的 AKS 群集。
    • 命名空间:选择新建,并输入 canarydemo
  4. 选择“验证和创建”。

添加 Canary 阶段

  1. 转到管道,选择创建的管道,然后选择编辑

  2. 将整个管道 YAML 替换为以下代码。

    此代码更改了你之前运行的 Docker@2 步骤以使用阶段,并添加了另外两个步骤来复制清单和 misc 目录作为连续阶段使用的工件。

    该代码还将一些值移动到变量中,以便以后在管道中更容易使用。 在 containerRegistry 变量中,将 <example> 替换为容器注册表的名称。

    trigger:
    - main
    
    pool:
      vmImage: ubuntu-latest
    
    variables:
      imageName: azure-pipelines-canary-k8s
      dockerRegistryServiceConnection: azure-pipelines-canary-acr
      imageRepository: 'azure-pipelines-canary-k8s'
      containerRegistry: <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
    
  3. 在 YAML 文件末尾添加另一个阶段以部署 Canary 版本。 将值 my-resource-groupmy-aks-cluster 替换为资源组和 Azure Kubernetes 服务群集名称。

    trigger:
    - main
    
    pool:
      vmImage: ubuntu-latest
    
    variables:
      imageName: azure-pipelines-canary-k8s
      dockerRegistryServiceConnection: azure-pipelines-canary-acr
      imageRepository: 'azure-pipelines-canary-k8s'
      containerRegistry: yourcontainerregistry.azurecr.io #update with container registry
      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
    
    - stage: DeployCanary
      displayName: Deploy canary
      dependsOn: Build
      condition: succeeded()
    
      jobs:
      - deployment: Deploycanary
        displayName: Deploy canary
        pool:
          vmImage: ubuntu-latest
        environment: 'akscanary'
        strategy:
          runOnce:
            deploy:
              steps:
              - task: KubernetesManifest@1
                displayName: Create Docker Registry Secret
                inputs:
                  action: 'createSecret'
                  connectionType: 'azureResourceManager'
                  azureSubscriptionConnection: 'azure-pipelines-canary-sc'
                  azureResourceGroup: 'my-resource-group'
                  kubernetesCluster: 'my-aks-cluster'
                  secretType: 'dockerRegistry'
                  secretName: 'my-acr-secret'
                  dockerRegistryEndpoint: 'azure-pipelines-canary-acr'
    
              - task: KubernetesManifest@1
                displayName: Deploy to Kubernetes cluster
                inputs:
                  action: 'deploy'
                  connectionType: 'azureResourceManager'
                  azureSubscriptionConnection: 'azure-pipelines-canary-sc'
                  azureResourceGroup: 'my-resource-group'
                  kubernetesCluster: 'my-aks-cluster'
                  strategy: 'canary'
                  percentage: '25'
                  manifests: |
                    $(Pipeline.Workspace)/manifests/deployment.yml
                    $(Pipeline.Workspace)/manifests/service.yml
                  containers: '$(containerRegistry)/$(imageRepository):$(tag)'
                  imagePullSecrets: 'my-acr-secret'
    
              - task: KubernetesManifest@1
                displayName: Deploy Forbio to Kubernetes cluster
                inputs:
                  action: 'deploy'
                  connectionType: 'azureResourceManager'
                  azureSubscriptionConnection: 'azure-pipelines-canary-sc'
                  azureResourceGroup: 'my-resource-group'
                  kubernetesCluster: 'my-aks-cluster'
                  manifests: '$(Pipeline.Workspace)/misc/*'
    
  4. 选择验证并保存,然后将管道直接保存到主分支。

添加用于提升或拒绝 Canary 部署的手动批准

可以使用 YAML 或经典进行手动干预。

  1. 创建名为 akspromote 的新 Kubernetes 环境。
  2. 从环境列表中打开新的 akspromote 环境,然后在审批和检查选项卡上选择审批
  3. 审批屏幕上,在审批者下添加自己的用户帐户。
  4. 展开高级,并确保选中允许审批者审批自己的运行
  5. 选择创建

向管道添加升级和拒绝阶段

  1. 转到管道,选择创建的管道,然后选择编辑

  2. 在 YAML 文件的末尾添加以下 PromoteRejectCanary 阶段,以提升更改。

    - stage: PromoteRejectCanary
      displayName: Promote or Reject canary
      dependsOn: DeployCanary
      condition: succeeded()
    
      jobs:
      - deployment: PromoteCanary
        displayName: Promote Canary
        pool: 
          vmImage: ubuntu-latest
        environment: 'akspromote'
        strategy:
          runOnce:
            deploy:
              steps:      
              - task: KubernetesManifest@1
                displayName: Create Docker Registry Secret for akspromote
                inputs:
                  action: 'createSecret'
                  connectionType: 'azureResourceManager'
                  azureSubscriptionConnection: 'azure-pipelines-canary-sc'
                  azureResourceGroup: 'my-resource-group'
                  kubernetesCluster: 'my-aks-cluster'
                  secretType: 'dockerRegistry'
                  secretName: 'my-acr-secret'
                  dockerRegistryEndpoint: 'azure-pipelines-canary-acr'
    
              - task: KubernetesManifest@1
                displayName: promote canary
                inputs:
                  action: 'promote'
                  connectionType: 'azureResourceManager'
                  azureSubscriptionConnection: 'azure-pipelines-canary-sc'
                  azureResourceGroup: 'my-resource-group'
                  kubernetesCluster: 'my-aks-cluster'
                  strategy: 'canary'
                  manifests: '$(Pipeline.Workspace)/manifests/*'
                  containers: '$(containerRegistry)/$(imageRepository):$(tag)'
                  imagePullSecrets: 'my-acr-secret'
        ```
    
    
  3. 在回滚更改的文件末尾添加以下 RejectCanary 阶段。

    - stage: RejectCanary
      displayName: Reject canary
      dependsOn: PromoteRejectCanary
      condition: failed()
    
      jobs:
      - deployment: RejectCanary
        displayName: Reject Canary
        pool: 
          vmImage: ubuntu-latest
        environment: 'akscanary'
        strategy:
          runOnce:
            deploy:
              steps:        
              - task: KubernetesManifest@1
                displayName: Create Docker Registry Secret for reject canary
                inputs:
                  action: 'createSecret'
                  connectionType: 'azureResourceManager'
                  azureSubscriptionConnection: 'azure-pipelines-canary-sc'
                  azureResourceGroup: 'kubernetes-testing'
                  kubernetesCluster: 'my-aks-cluster'
                  secretType: 'dockerRegistry'
                  secretName: 'my-acr-secret'
                  dockerRegistryEndpoint: 'azure-pipelines-canary-acr'    
              - task: KubernetesManifest@1
                displayName: Reject canary deployment
                inputs:
                  action: 'reject'
                  connectionType: 'azureResourceManager'
                  azureSubscriptionConnection: 'azure-pipelines-canary-sc'
                  azureResourceGroup: 'my-resource-group'
                  kubernetesCluster: 'my-aks-cluster'
                  namespace: 'default'
                  strategy: 'canary'
                  manifests: '$(Pipeline.Workspace)/manifests/*'
        ```
    
  4. 选择验证并保存,然后将管道直接保存到主分支。

部署稳定版本

对于管道的首次运行,群集中不存在工作负载的稳定版本及其基线或 Canary 版本。 按如下所示部署 sampleapp 工作负载的稳定版本。

可以使用 YAML 或经典部署稳定版本。

  1. 在 app/app.py 中,将 success_rate = 50 更改为 success_rate = 100。 此更改会触发管道,生成映像,并将其推送到容器注册表,还会触发 DeployCanary 阶段。
  2. 由于你在 akspromote 环境中配置了审批,因此发布会在运行该阶段之前等待。 在生成运行摘要页面上,选择查看,然后选择审批

一旦获得批准,管道就会将 manifests/deployment.yml 中的 sampleapp 工作负载的稳定版本部署到命名空间。

启动 Canary 工作流并拒绝审批

群集中现在存在稳定版本的 sampleapp 工作负载。 接下来,对模拟应用程序进行以下更改。

  1. 在 app/app.py 中,将 success_rate = 50 更改为 success_rate = 100。 此更改会触发管道,生成映像,并将其推送到容器注册表,还会触发 DeployCanary 阶段。
  2. 由于你在 akspromote 环境中配置了审批,因此发布会在运行该阶段之前等待。
  3. 在生成运行摘要页面上,选择“审核”,然后在后续对话框中选择“拒绝”。 这会拒绝部署。

拒绝后,管道将阻止代码部署。

清理

如果不打算继续使用此应用程序,请删除 Azure 门户中的资源组和 Azure DevOps 中的项目。