Azure 容器应用中的蓝绿部署

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

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

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

Screenshot of Azure Container Apps: Blue/Green deployment.

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

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

后续步骤