Azure 容器应用中的蓝绿部署

蓝绿部署是一种软件发布策略,旨在最大限度地减少停机并降低与部署新版本应用程序相关的风险。 在蓝绿部署中,设置了两个相同的环境,分别称为“蓝色”和“绿色”。 一个环境(蓝色)运行当前应用程序版本,一个环境(绿色)运行新的应用程序版本。

测试绿色环境后,实时流量将定向到该环境,蓝色环境用于在下一个部署周期内部署新的应用程序版本。

可以通过组合容器应用修订流量权重以及修订标签,在 Azure 容器应用中启用蓝绿部署。

Azure 容器应用的屏幕截图:蓝/绿部署。

使用修订创建应用程序的蓝色和绿色版本的实例。

修订 说明
蓝色修订版 标记为蓝色的修订版是应用程序的当前运行稳定版本。 此修订是用户与之交互的修订,也是生产流量的目标。
绿色 修订版 标记为绿色的修订版是蓝色修订版的副本,但它使用较新版本的应用代码和可能的新环境变量集。 它最初不会接收任何生产流量,但可通过标记的完全限定域名 (FQDN) 进行访问。

测试并验证新修订后,可以将生产流量指向新修订。 如果遇到问题,可以轻松回滚到以前的版本。

操作 说明
测试和验证 绿色修订版经过全面测试和验证,以确保应用程序的新版本按预期运行。 此测试可能涉及各种任务,包括功能测试、性能测试和兼容性检查。
流量切换 绿色修订版通过所有必要的测试后,将执行流量切换,以便绿色修订版开始提供生产负载。 此开关以受控方式完成,确保平稳过渡。
回退 如果绿色修订版中出现问题,则可以还原流量切换,将流量路由回稳定的蓝色修订版。 如果新版本中出现问题,此回滚可确保对用户的影响最小。 绿色修订版仍可用于下一部署。
角色更改 成功部署到绿色修订版后,蓝色和绿色修订版的角色会发生变化。 在下一个发布周期中,绿色修订版表示稳定的生产环境,而新版本的应用程序代码是在蓝色修订版中部署和测试的。

本文介绍如何在容器应用中实现蓝绿部署。 若要运行以下示例,需要一个可在其中创建新应用的容器应用环境。

注意

有关实现容器应用的蓝绿部署的 GitHub 工作流的完整示例,请参阅 containerapps-blue-green 存储库

创建启用了多个活动修订的容器应用

容器应用必须将 configuration.activeRevisionsMode 属性设置为 multiple 才能启用流量拆分。 若要获取确定性的修订版名称,可以将 template.revisionSuffix 配置设置值设为唯一标识版本的字符串值。 例如,可以使用内部生成号,或者 git 提交短哈希。

对于以下命令,使用了一组提交哈希。

export APP_NAME=<APP_NAME>
export APP_ENVIRONMENT_NAME=<APP_ENVIRONMENT_NAME>
export RESOURCE_GROUP=<RESOURCE_GROUP>

# A commitId that is assumed to correspond to the app code currently in production
export BLUE_COMMIT_ID=fb699ef
# A commitId that is assumed to correspond to the new version of the code to be deployed
export GREEN_COMMIT_ID=c6f1515

# create a new app with a new revision
az containerapp create --name $APP_NAME \
  --environment $APP_ENVIRONMENT_NAME \
  --resource-group $RESOURCE_GROUP \
  --image mcr.microsoft.com/k8se/samples/test-app:$BLUE_COMMIT_ID \
  --revision-suffix $BLUE_COMMIT_ID \
  --env-vars REVISION_COMMIT_ID=$BLUE_COMMIT_ID \
  --ingress external \
  --target-port 80 \
  --revisions-mode multiple

# Fix 100% of traffic to the revision
az containerapp ingress traffic set \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --revision-weight $APP_NAME--$BLUE_COMMIT_ID=100

# give that revision a label 'blue'
az containerapp revision label add \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --label blue \
  --revision $APP_NAME--$BLUE_COMMIT_ID

将以下代码添加到名为 main.bicep 的文件中。

targetScope = 'resourceGroup'
param location string = resourceGroup().location

@minLength(1)
@maxLength(64)
@description('Name of containerapp')
param appName string

@minLength(1)
@maxLength(64)
@description('Container environment name')
param containerAppsEnvironmentName string

@minLength(1)
@maxLength(64)
@description('CommitId for blue revision')
param blueCommitId string

@maxLength(64)
@description('CommitId for green revision')
param greenCommitId string = ''

@maxLength(64)
@description('CommitId for the latest deployed revision')
param latestCommitId string = ''

@allowed([
  'blue'
  'green'
])
@description('Name of the label that gets 100% of the traffic')
param productionLabel string = 'blue'

var currentCommitId = !empty(latestCommitId) ? latestCommitId : blueCommitId

resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2022-03-01' existing = {
  name: containerAppsEnvironmentName
}

resource blueGreenDeploymentApp 'Microsoft.App/containerApps@2022-11-01-preview' = {
  name: appName
  location: location
  tags: {
    blueCommitId: blueCommitId
    greenCommitId: greenCommitId
    latestCommitId: currentCommitId
    productionLabel: productionLabel
  }
  properties: {
    environmentId: containerAppsEnvironment.id
    configuration: {
      maxInactiveRevisions: 10 // Remove old inactive revisions
      activeRevisionsMode: 'multiple' // Multiple active revisions mode is required when using traffic weights
      ingress: {
        external: true
        targetPort: 80
        traffic: !empty(blueCommitId) && !empty(greenCommitId) ? [
          {
            revisionName: '${appName}--${blueCommitId}'
            label: 'blue'
            weight: productionLabel == 'blue' ? 100 : 0
          }
          {
            revisionName: '${appName}--${greenCommitId}'
            label: 'green'
            weight: productionLabel == 'green' ? 100 : 0
          }
        ] : [
          {
            revisionName: '${appName}--${blueCommitId}'
            label: 'blue'
            weight: 100
          }
        ]
      }
    }
    template: {
      revisionSuffix: currentCommitId
      containers:[
        {
          image: 'mcr.microsoft.com/k8se/samples/test-app:${currentCommitId}'
          name: appName
          resources: {
            cpu: json('0.5')
            memory: '1.0Gi'
          }
          env: [
            {
              name: 'REVISION_COMMIT_ID'
              value: currentCommitId
            }
          ]
        }
      ]
    }
  }
}

output fqdn string = blueGreenDeploymentApp.properties.configuration.ingress.fqdn
output latestRevisionName string = blueGreenDeploymentApp.properties.latestRevisionName

使用以下命令通过 Bicep 模板部署应用:

export APP_NAME=<APP_NAME>
export APP_ENVIRONMENT_NAME=<APP_ENVIRONMENT_NAME>
export RESOURCE_GROUP=<RESOURCE_GROUP>

# A commitId that is assumed to belong to the app code currently in production
export BLUE_COMMIT_ID=fb699ef
# A commitId that is assumed to belong to the new version of the code to be deployed
export GREEN_COMMIT_ID=c6f1515

# create a new app with a blue revision
az deployment group create \
    --name createapp-$BLUE_COMMIT_ID \
    --resource-group $RESOURCE_GROUP \
    --template-file main.bicep \
    --parameters appName=$APP_NAME blueCommitId=$BLUE_COMMIT_ID containerAppsEnvironmentName=$APP_ENVIRONMENT_NAME \
    --query properties.outputs.fqdn

部署新的修订版并分配标签

蓝色标签当前是指采用到达应用 FQDN 的生产流量的修订版。 绿色标签是指即将推出到生产环境的新版应用。 新的提交哈希标识应用代码的新版本。 以下命令为该提交哈希部署新修订,并使用绿色标签标记。

#create a second revision for green commitId
az containerapp update --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --image mcr.microsoft.com/k8se/samples/test-app:$GREEN_COMMIT_ID \
  --revision-suffix $GREEN_COMMIT_ID  \
  --set-env-vars REVISION_COMMIT_ID=$GREEN_COMMIT_ID

#give that revision a 'green' label
az containerapp revision label add \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --label green \
  --revision $APP_NAME--$GREEN_COMMIT_ID
#deploy a new version of the app to green revision
az deployment group create \
    --name deploy-to-green-$GREEN_COMMIT_ID \
    --resource-group $RESOURCE_GROUP \
    --template-file main.bicep \
    --parameters appName=$APP_NAME blueCommitId=$BLUE_COMMIT_ID greenCommitId=$GREEN_COMMIT_ID latestCommitId=$GREEN_COMMIT_ID productionLabel=blue containerAppsEnvironmentName=$APP_ENVIRONMENT_NAME \
    --query properties.outputs.fqdn

以下示例演示如何配置流量部分。 蓝色 commitId 修订版占用 100% 的生产流量,而新部署的绿色 commitId 修订版不占用任何生产流量。

{ 
  "traffic": [
    {
      "revisionName": "<APP_NAME>--fb699ef",
      "weight": 100,
      "label": "blue"
    },
    {
      "revisionName": "<APP_NAME>--c6f1515",
      "weight": 0,
      "label": "green"
    }
  ]
}

可以使用特定于标签的 FQDN 测试新部署的修订版:

#get the containerapp environment default domain
export APP_DOMAIN=$(az containerapp env show -g $RESOURCE_GROUP -n $APP_ENVIRONMENT_NAME --query properties.defaultDomain -o tsv | tr -d '\r\n')

#Test the production FQDN
curl -s https://$APP_NAME.$APP_DOMAIN/api/env | jq | grep COMMIT

#Test the blue lable FQDN
curl -s https://$APP_NAME---blue.$APP_DOMAIN/api/env | jq | grep COMMIT

#Test the green lable FQDN
curl -s https://$APP_NAME---green.$APP_DOMAIN/api/env | jq | grep COMMIT

将生产流量发送到绿色修订版

确认绿色修订版中的应用代码按预期工作后,100% 的生产流量将发送到该修订版。 绿色修订版现在成为生产修订版。

# set 100% of traffic to green revision
az containerapp ingress traffic set \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --label-weight blue=0 green=100

# make green the prod revision
az deployment group create \
    --name make-green-prod-$GREEN_COMMIT_ID \
    --resource-group $RESOURCE_GROUP \
    --template-file main.bicep \
    --parameters appName=$APP_NAME blueCommitId=$BLUE_COMMIT_ID greenCommitId=$GREEN_COMMIT_ID latestCommitId=$GREEN_COMMIT_ID productionLabel=green containerAppsEnvironmentName=$APP_ENVIRONMENT_NAME \
    --query properties.outputs.fqdn

以下示例演示如何在此步骤后配置 traffic 部分。 包含新应用程序代码的绿色修订版会占用所有用户流量,而使用旧应用程序版本的蓝色修订版不接受用户请求。

{ 
  "traffic": [
    {
      "revisionName": "<APP_NAME>--fb699ef",
      "weight": 0,
      "label": "blue"
    },
    {
      "revisionName": "<APP_NAME>--c6f1515",
      "weight": 100,
      "label": "green"
    }
  ]
}

出现问题时回滚部署

如果在生产环境中运行后,发现新修订版存在 bug,则可以回滚到以前的良好状态。 回滚后,100% 的流量将发送到蓝色修订版中的旧版本,并将该修订版再次指定为生产修订版。

# set 100% of traffic to green revision
az containerapp ingress traffic set \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --label-weight blue=100 green=0
# rollback traffic to blue revision
az deployment group create \
    --name rollback-to-blue-$GREEN_COMMIT_ID \
    --resource-group $RESOURCE_GROUP \
    --template-file main.bicep \
    --parameters appName=$APP_NAME blueCommitId=$BLUE_COMMIT_ID greenCommitId=$GREEN_COMMIT_ID latestCommitId=$GREEN_COMMIT_ID productionLabel=blue containerAppsEnvironmentName=$APP_ENVIRONMENT_NAME \
    --query properties.outputs.fqdn

修复 bug 后,新版本的应用程序将再次部署为绿色修订版。 绿色版本最终成为生产修订版。

下一个部署周期

现在,绿色标签标记当前正在运行稳定生产代码的修订版。

在下一个部署周期中,蓝色通过推出到生产的新应用程序版本来标识修订版。

以下命令演示如何准备下一个部署周期。

# set the new commitId
export BLUE_COMMIT_ID=ad1436b

# create a third revision for blue commitId
az containerapp update --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --image mcr.microsoft.com/k8se/samples/test-app:$BLUE_COMMIT_ID \
  --revision-suffix $BLUE_COMMIT_ID  \
  --set-env-vars REVISION_COMMIT_ID=$BLUE_COMMIT_ID

# give that revision a 'blue' label
az containerapp revision label add \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --label blue \
  --revision $APP_NAME--$BLUE_COMMIT_ID
# set the new commitId
export BLUE_COMMIT_ID=ad1436b

# deploy new version of the app to blue revision
az deployment group create \
    --name deploy-to-blue-$BLUE_COMMIT_ID \
    --resource-group $RESOURCE_GROUP \
    --template-file main.bicep \
    --parameters appName=$APP_NAME blueCommitId=$BLUE_COMMIT_ID greenCommitId=$GREEN_COMMIT_ID latestCommitId=$BLUE_COMMIT_ID productionLabel=green containerAppsEnvironmentName=$APP_ENVIRONMENT_NAME \
    --query properties.outputs.fqdn

后续步骤