NGINX Full Version

NGINX 延伸阅读:如何使用 GitHub Action 实现微服务灰度发布自动化

本文是“Microservices June 微服务之月 2023”系列教程的延伸阅读之一,旨在帮助您将概念付诸实践。该系列教程包括:

该系列教程包括:

自动化部署对于大多数项目的成功而言至关重要。但是,仅仅部署代码还不够,您还需确保减少(或消除)停机时间,并能够在发生故障时迅速回滚。结合使用灰度部署和蓝绿部署是确保新代码成功部署的一种常见方法。该策略包括两个步骤:

I如果您尚不熟悉在不同版本的应用或网站之间分配流量(流量分割)的各种用例,请参阅我们的博文《如何通过高级流量管理提高 Kubernetes 的弹性》,以获得对蓝绿部署、灰度发布、A/B 测试、速率限制、断路等的概念性理解。虽然本文主要针对 Kubernetes,但这些概念广泛适用于微服务应用。

 

教程概述

在本教程中,我们将展示如何使用 GitHub Actions 自动执行灰度蓝绿部署的第一步。在本教程的四个挑战中,您将使用 Microsoft Azure Container Apps 来部署新版本的应用,然后使用 Azure 流量管理器将流量从旧环境转移到新环境:

注:虽然本教程使用 Azure Container Apps,但相关概念和技术可应用于任何基于云的主机。

 

准备工作和设置

准备工作

如果您想在自己的环境中学习本教程,则需要:

设置

创建并配置必要的基础资源。创建分支(Fork)并克隆本教程所用的代码库,登录 Azure CLI,并为 Azure Container App 安装扩展插件。

  1. 在您的主目录下,创建 microservices-march 目录。(您也可以使用其他目录名称,相应修改指令即可)。

    注:本教程中省略了 Linux 命令行提示符,以便您将命令复制和粘贴到终端。

    mkdir ~/microservices-march
    cd ~/microservices-march
  2. 使用 GitHub CLI 或 GUI Fork 为 Microservices March 平台代码库创建分支(Fork),并克隆到您的个人 GitHub 帐户。

    • 如果使用 GitHub GUI:

      1. 在窗口的右上角点击 Fork,并在 Owner(所有者)菜单中选择您的个人 GitHub 帐户。

      2. 克隆代码库到本地,用您的帐户名称替换 <your_GitHub_account>

        git clone https://github.com/<your_GitHub_account>/platform.git
        cd platform
    • 如果使用 GitHub CLI,则运行:

      gh repo fork microservices-march/platform -–clone
  3. 登录到 Azure CLI。按照提示,使用浏览器登录:

    az login
    [
      {
        "cloudName": "AzureCloud",
        "homeTenantId": "cfd11e0f-1435-450s-bdr1-ffab73b4er8e",
        "id": "60efapl2-38ad-41w8-g49a-0ecc6723l97c",
        "isDefault": true,
        "managedByTenants": [],
        "name": "Azure subscription 1",
        "state": "Enabled",
        "tenantId": "cfda3e0f-14g5-4e05-bfb1-ffab73b4fsde",
        "user": {
          "name": "user@example.com",
          "type": "user"
        }
      }
    ]
  4. 安装 containerapp 扩展插件:

    az extension add --name containerapp -upgrade
    The installed extension 'containerapp' is in preview.

 

挑战 1:创建和部署 NGINX 容器应用

在首个挑战中,您需要创建一个 NGINX Azure 容器应用作为应用初始版本,用作灰度蓝绿部署基线。Azure Container Apps 是一种 Microsoft Azure 服务,支持您在生产就绪的容器环境中轻松执行封装在容器中的应用代码。

  1. 为容器应用创建一个 Azure 资源组

    az group create --name my-container-app-rg --location westus
    {
      "id": "/subscriptions/0efafl2-38ad-41w8-g49a-0ecc6723l97c/resourceGroups/my-container-app-rg",
      "location: "westus",
      "managedBy": null,
      "name": "my-container-app-rg",
      "properties": {
        "provisioningState": "Succeeded"
      },
      "tags": null,
      "type": "Microsoft.Resources/resourceGroups"
    }
  2. 将容器部署到 Azure Container Apps(这一步可能需要一些时间):

    az containerapp up \
        --resource-group my-container-app-rg \
        --name my-container-app \
        --source ./ingress \
        --ingress external \
        --target-port 80 \
        --location westus
    ... 
    - image:
        registry: cac085021b77acr.azurecr.io
        repository: my-container-app
        tag: "20230315212413756768"
        digest: sha256:90a9fc67c409e244195ec0ea190ce3b84195ae725392e8451...
      runtime-dependency:
        registry: registry.hub.docker.com
        repository: library/nginx
        tag: "1.23"
        digest: sha256:aa0afebbb3cfa473099a62c4b32e9b3fb73ed23f2a75a65ce...
      git: {} 
    Run ID: cf1 was successful after 27s
    Creating Containerapp my-container-app in resource group my-container-app-rg
    Adding registry password as a secret with name "ca2ffbce7810acrazurecrio-cac085021b77acr" 
    Container app created. Access your app at https://my-container-app.delightfulmoss-eb6d59d5.westus.azurecontainerapps.io/
    ...
  3. 在第二步的输出结果中,找到您在 Azure 容器注册表 (ACR) 中创建的容器应用的名称和 URL。在示例输出结果中,它们以橙色高亮显示。在整个教程中,您需要用输出结果中的值(不同于第二步中的示例输出结果)来替换命令中的指定变量:

    • 容器应用的名称——在 image.registry 键中,是指 .azurecr.io 前面的字符串。在第二步的示例输出结果中,它是 cac085021b77acr

      在随后的命令中,用这个字符串替换 <ACR_name>

    • 容器应用的 URL——以 Container app created 开头的一行中的 URL。在第二步的示例输出结果中,它是 https://my-container-app.delightfulmoss-eb6d59d5.westus.azurecontainerapps.io/

      在随后的命令中,用这个 URL 替换 <ACR_URL>

  4. 按照蓝绿部署的要求,为该容器应用启用版本修订:

    az containerapp revision set-mode \
        --name my-container-app \
        --resource-group my-container-app-rg \
        --mode multiple
    "Multiple"
  5. (可选)通过查询容器中的 /health 端点来测试部署是否正在运行:

    curl <ACR_URL>/health
    OK

 

挑战 2:设置权限以支持 Azure 容器应用部署自动化

在这个挑战中,您需要获取 JSON 令牌,以自动执行 Azure 容器应用部署。

首先,您需要获取用于 Azure 容器注册表 (ACR) 的 ID 以及您的 Azure 托管身份的主 ID。然后,将用于 ACR 的内置 Azure 角色分配给托管身份,并将容器应用配置为使用该托管身份。最后,获取托管身份的 JSON 凭证,GitHub Action 将使用该凭证向 Azure 进行身份验证。

虽然这一系列步骤可能看起来很繁琐,但您只需在新建应用时执行一次,便可将该流程完全脚本化。在本教程中,您可以通过手动执行这些步骤加深印象。

注:这个为部署创建凭证的流程仅限于 Azure。

  1. 查询您托管身份的主 ID。它显示在输出结果的 PrincipalID 栏(为方便阅读,输出结果分成了两行)。您将用这个值替换第三步中的 <managed_identity_principal_ID>

    az containerapp identity assign \
        --name my-container-app \
        --resource-group my-container-app-rg \
        --system-assigned \
        --output table
    PrincipalId                          ...                           
    ------------------------------------ ...  
        39f8434b-12d6-4735-81d8-ba0apo14579f ...
     
        ... TenantId
        ... ------------------------------------
            ... cfda3e0f-14g5-4e05-bfb1-ffab73b4fsde
  2. 在 ACR 中查找容器应用的资源 ID,将 <ACR_name> 替换为您在 挑战 1 第三步中记录的名称。您将用这个值替换下一步中的 <ACR_resource_ID>

    az acr show --name <ACR_name> --query id --output tsv
    /subscriptions/60efafl2-38ad-41w8-g49a-0ecc6723l97c/resourceGroups/my-container-app-rg/providers/Microsoft.ContainerRegistry/registries/cac085021b77acr
  3. 将用于 ACR 的内置 Azure 角色分配给容器应用的托管身份,将 <managed_identity_principal_ID> 替换为您在第一步中获得的托管身份,并将 <ACR_resource_ID> 替换为您在第二步中获得的资源 ID:

    az role assignment create \
        --assignee <managed_identity_principal_ID> \
        --role AcrPull \
        --scope <ACR_resource_ID>
    {
      "condition": null,
      "conditionVersion": null,
      "createdBy": null,
      "createdOn": "2023-03-15T20:28:40.831224+00:00",
      "delegatedManagedIdentityResourceId": null,
      "description": null,
      "id": "/subscriptions/0efafl2-38ad-41w8-g49a-0ecc6723l97c/resourceGroups/my-container-app-rg/providers/Microsoft.ContainerRegistry/registries/cac085021b77acr/providers/Microsoft.Authorization/roleAssignments/f0773943-8769-44c6-a820-ed16007ff249",
      "name": "f0773943-8769-44c6-a820-ed16007ff249",
      "principalId": "39f8ee4b-6fd6-47b5-89d8-ba0a4314579f",
      "principalType": "ServicePrincipal",
      "resourceGroup": "my-container-app-rg",
      "roleDefinitionId": "/subscriptions/60e32142-384b-43r8-9329-0ecc67dca94c/providers/Microsoft.Authorization/roleDefinitions/7fd21dda-4fd3-4610-a1ca-43po272d538d",
      "scope": "/subscriptions/ 0efafl2-38ad-41w8-g49a-0ecc6723l97c/resourceGroups/my-container-app-rg/providers/Microsoft.ContainerRegistry/registries/cac085021b77acr",
      "type": "Microsoft.Authorization/roleAssignments",
      "updatedBy": "d4e122d6-5e64-4bg1-9cld-2aceeb0oi24d",
      "updatedOn": "2023-03-15T20:28:41.127243+00:00"
    }
  4. 将容器应用配置为从 ACR 中提取镜像时使用的托管身份,并将 <ACR_name> 替换为您在挑战 1 的第三步中记录的容器应用名称(在上面第二步中也用过):

    az containerapp registry set \
        --name my-container-app \
        --resource-group my-container-app-rg \
        --server <ACR_name>.azurecr.io \
        --identity system
    [
      {
        "identity": "system",
        "passwordSecretRef": "",
        "server": "cac085021b77acr.azurecr.io",
        "username": ""
      }
    ]
  5. 查询您的 Azure 订阅 ID

    az account show --query id --output tsv
    0efafl2-38ad-41w8-g49a-0ecc6723l97c
  6. 创建一个 JSON 令牌,其中包含 GitHub Action 要使用的凭证,然后将 <subscription_ID> 替换为您的 Azure 订阅 ID。保存输出结果,在“将密钥添加到您的 GitHub 代码库”中用作 AZURE_CREDENTIALS 密钥的值。您可以安然地忽略有关 --sdk-auth 被弃用的警告;这是一个已知问题

    az ad sp create-for-rbac \
        --name my-container-app-rbac \
        --role contributor \
        --scopes /subscriptions/<subscription_ID>/resourceGroups/my-container-app-rg \
        --sdk-auth \
        --output json
    Option '--sdk-auth' has been deprecated and will be removed in a future release.
    ...
    {
      "clientId": "0732444d-23e6-47fb-8c2c-74bddfc7d2er",
      "clientSecret": "qg88Q~KJaND4JTWRPOLWgCY1ZmZwN5MK3N.wwcOe",
      "subscriptionId": "0efafl2-38ad-41w8-g49a-0ecc6723l97c",
      "tenantId": "cfda3e0f-14g5-4e05-bfb1-ffab73b4fsde",
      "activeDirectoryEndpointUrl": "https://login.microsoftonline.com",
      "resourceManagerEndpointUrl": "https://management.azure.com/",
      "activeDirectoryGraphResourceId": "https://graph.windows.net/",
      "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
      "galleryEndpointUrl": "https://gallery.azure.com/",
      "managementEndpointUrl": "https://management.core.windows.net/"
    }

 

挑战 3:创建灰度蓝绿部署 GitHub Action

在这个挑战中,您需要将密钥添加到您的 GitHub 代码库(用于管理您的 GitHub Action 工作流中的敏感数据),创建 Action 工作流文件执行 Action 工作流

有关密钥管理的详细介绍,请参阅我们的博文——Microservices June 23 的第二篇教程《如何安全地管理容器中的密钥》。

将密钥添加到您的 GitHub 代码库中

为了部署新版本的应用,您需要在设置中 fork 过的 GitHub 代码库中创建一系列密钥。这些密钥是在挑战 2 中创建的托管身份的 JSON 凭证,以及将新版本的 NGINX 镜像部署到 Azure 所需的一些敏感的部署参数。在下一部分中,您将在 GitHub Action 中使用这些密钥实现灰度蓝绿部署自动化。

创建 GitHub Action 工作流文件

有了托管身份和密钥,您可为 GitHub Action 创建一个工作流文件,以自动执行灰度蓝绿部署。

注:工作流文件以 YAML 格式定义,其中空格是有意义的。请务必保留以下步骤中显示的缩进。

  1. 为 Action 工作流创建一个文件。

    • 如果使用 GitHub GUI:

      1. 导航到您的 GitHub 代码库。
      2. 选择 Actions > New workflow(新工作流)> Skip this and set up a workflow yourself(跳过这一步,自己设置一个工作流)
    • 如果使用 GitHub CLI,则创建 .github/workflows 目录并新建一个名为 main.yml 的文件:

      mkdir .github/workflows
      touch .github/workflows/main.yml
  2. 使用您常用的文本编辑器,将工作流的文本添加到 main.yml 中。最简单的方法是直接复制完整工作流文件中显示的文本。或者,您也可以通过添加此步骤中注释的代码段来手动创建文件。

    注:工作流文件以 YAML 格式定义,其中空格是有意义的。复制代码段时,请务必保留缩进(保险起见,请对您的文件与完整工作流文件进行比较)。

    • 定义工作流的名称:

      name: Deploy to Azure
    • 将工作流配置为在向主分支发出 push 或 pull 请求时运行:

      on:
        push:
          branches:
            - main
        pull_request:
          branches:
            - main
    • jobs 部分,定义 build-deploy 作业,该作业会检测代码、登录 Azure 并将应用部署到 Azure 容器应用中:

      jobs:
        build-deploy:
          runs-on: ubuntu-22.04
          steps:
            - name: Check out the codebase
              uses: actions/checkout@v3
      
            - name: Log in to Azure
              uses: azure/login@v1
              with:
                creds: ${{ secrets.AZURE_CREDENTIALS }}
      
            - name: Build and deploy Container App
              run: |
                # 手动添加 containerapp 扩展
                az extension add --name containerapp --upgrade
                # 使用 Azure CLI 来部署更新
                az containerapp up -n ${{ secrets.CONTAINER_APP_NAME }}\
                  -g ${{ secrets.RESOURCE_GROUP }} \
                  --source ${{ github.workspace }}/ingress \
                  --registry-server ${{ secrets.ACR_NAME }}.azurecr.io
    • 定义 test-deployment 作业,该作业可获取新部署修订版本的预发布 URL,并使用 GitHub Action 对 API 端点 /health 进行 ping 操作,以确保新修订版本可正常响应。 如果健康检查成功,容器应用上的 Azure 流量管理器会进行更新,将所有流量都定向到新部署的容器。

      注:请确保 test-deployment 键值的缩进量与您在上一节中定义的 build-deploy 键值相同:

        test-deployment:
          needs: build-deploy
          runs-on: ubuntu-22.04
          steps:
            - name: Log in to Azure
              uses: azure/login@v1
              with:
                creds: ${{ secrets.AZURE_CREDENTIALS }}
      
            - name: Get new container name
              run: |
                # 手动添加 containerapp 扩展
                az extension add --name containerapp --upgrade
      
                # 获取最新部署修订版本的名称
                REVISION_NAME=`az containerapp revision list -n ${{ secrets.CONTAINER_APP_NAME }} -g ${{ secrets.RESOURCE_GROUP }} --query "[].name" -o tsv | tail -1`
                # 获取最新部署修订版本的 fqdn
                REVISION_FQDN=`az containerapp revision show -n ${{ secrets.CONTAINER_APP_NAME }} -g ${{ secrets.RESOURCE_GROUP }} --revision "$REVISION_NAME" --query properties.fqdn -o tsv`
                # 将值存储在 env vars 中
                echo "REVISION_NAME=$REVISION_NAME" >> $GITHUB_ENV
                echo "REVISION_FQDN=$REVISION_FQDN" >> $GITHUB_ENV
      
            - name: Test deployment
              id: test-deployment
              uses: jtalk/url-health-check-action@v3 # Marketplace action 触达端点
              with:
                url: "https://${{ env.REVISION_FQDN }}/health" # 预发布端点
      
            - name: Deploy succeeded
              run: |
                echo "Deployment succeeded! Enabling new revision"
                az containerapp ingress traffic set -n ${{ secrets.CONTAINER_APP_NAME }} -g ${{ secrets.RESOURCE_GROUP }} --revision-weight "${{ env.REVISION_NAME }}=100"

完整工作流文件

以下为 Action 工作流文件的全文。

name: Deploy to Azure
on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
jobs:
  build-deploy:
    runs-on: ubuntu-22.04
    steps:
      - name: Check out the codebase
        uses: actions/checkout@v3

      - name: Log in to Azure
        uses: azure/login@v1
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}
     
      - name: Build and deploy Container 
        run: |
          # 手动添加 containerapp 扩展
          az extension add --name containerapp -upgrade
       
          # 使用 Azure CLI 来部署更新
          az containerapp up -n ${{ secrets.CONTAINER_APP_NAME }} \
            -g ${{ secrets.RESOURCE_GROUP }} \
            --source ${{ github.workspace }}/ingress \
            --registry-server ${{ secrets.ACR_NAME }}.azurecr.io
  test-deployment:
    needs: build-deploy
    runs-on: ubuntu-22.04
    steps:
      - name: Log in to Azure
        uses: azure/login@v1
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      - name: Get new container name
        run: |
          # 为 Azure CLI 安装 containerapp 扩展
          az extension add --name containerapp --upgrade
          # 获取最新部署修订版本的名称
          REVISION_NAME=`az containerapp revision list -n ${{ secrets.CONTAINER_APP_NAME }} -g ${{ secrets.RESOURCE_GROUP }} --query "[].name" -o tsv | tail -1`
          # 获取最新部署修订版本的 fqdn
          REVISION_FQDN=`az containerapp revision show -n ${{ secrets.CONTAINER_APP_NAME }} -g ${{ secrets.RESOURCE_GROUP }} --revision "$REVISION_NAME" --query properties.fqdn -o tsv`
          # 将值存储在 env vars 中
          echo "REVISION_NAME=$REVISION_NAME" >> $GITHUB_ENV
          echo "REVISION_FQDN=$REVISION_FQDN" >> $GITHUB_ENV

      - name: Test deployment
        id: test-deployment
        uses: jtalk/url-health-check-action@v3 # Marketplace action 触达端点
        with:
          url: "https://${{ env.REVISION_FQDN }}/health" # 预发布端点

      - name: Deploy succeeded
        run: |
          echo "Deployment succeeded! Enabling new revision"
          az containerapp ingress traffic set -n ${{ secrets.CONTAINER_APP_NAME }} -g ${{ secrets.RESOURCE_GROUP }} --revision-weight "${{ env.REVISION_NAME }}=100"

执行 Action 工作流

 

挑战 4:测试 GitHub Action 工作流

在这个挑战中,您将测试工作流。首先,您需要模拟一次对 Ingress 负载均衡器的成功更新,并确认应用已更新。然后,模拟一次不成功的更新(导致内部服务器错误),并确认已发布的应用没有变化。

进行成功的更新

进行一次成功的更新,并观察工作流是否成功。

进行一次不成功的更新

现在进行一次不成功的更新,并观察工作流是否失败。其步骤与进行一次成功的更新的步骤基本相同,只是 return 指令的值不同。

 

资源清理

如果您希望删除在操作本教程期间部署的 Azure 资源,以避免任何潜在的费用,输入下面一行代码:

az group delete -n my-container-app-rg -y

您也可以按需删除所创建的 fork。

 

后续步骤

恭喜!您已了解如何使用 GitHub Action 来执行微服务应用的灰度蓝绿部署。请查阅 GitHub 文档中的以下文章,继续探索并增进有关 DevOps 的知识:

如果您准备尝试灰度部署的第二步(在生产环境中测试),请参阅我们的博文——Microservices June 2022 教程《NGINX 实操教程:通过灰度部署改善正常运行时间和弹性》。该教程介绍了如何使用 NGINX Service Mesh 逐步过渡到新的应用版本。即使您的部署尚未复杂到需要使用服务网格的程度,或者您并未使用 Kubernetes,这些原则同样适用于只使用 Ingress Controller(Ingress 控制器)或负载均衡器的较简单部署。

如欲进一步了解有关微服务的更多内容,请观看 Microservices June 2023 第三单元:利用 Docker、Kubernetes 和 Gitlab实现微服务自动化部署和 CI/CD 的课程录像,详细了解部署自动化的相关概念。