部署到 Compute Engine


本指南介绍了如何使用 Cloud Build 和 Terraform 在 Compute Engine 代管式实例组 (MIG) 上执行零停机蓝绿部署。

Cloud Build 可让您自动执行各种开发者流程,包括将应用构建和部署到各种 Google Cloud 运行时,例如 Compute Engine、Google Kubernetes EngineGKE EnterpriseCloud Run 函数

借助 Compute Engine MIG,您可以在多个相同的虚拟机 (VM) 上运行应用。您可以利用自动化 MIG 服务让您的工作负载具有可伸缩性和高可用性,这些服务包括自动伸缩、自动修复、区域(多地区)部署和自动更新。使用蓝绿持续部署模型,您将学习如何逐步将用户流量从一个 MIG(蓝色)转移到另一个 MIG(绿色),这两个 MIG 都在生产环境中运行。

设计概览

下图展示了本文档中所述代码示例使用的蓝/绿部署模型:

蓝/绿模型

从宏观层面来看,此模型包含以下组件:

  • 两个 Compute Engine 虚拟机池:蓝色和绿色。
  • 三个外部 HTTP(S) 负载平衡器:
    • 一种蓝/绿负载均衡器,可将最终用户的流量路由到蓝色或绿色虚拟机实例池。
    • 一个蓝色负载均衡器,用于将流量从质量检查工程师和开发者路由到蓝色虚拟机实例池。
    • 一个绿色负载均衡器,用于将来自 QA 工程师和开发者的流量路由到绿色实例池。
  • 两组用户:
    • 有权访问蓝/绿负载均衡器的最终用户,该负载均衡器会将他们定向到蓝色或绿色实例池。
    • 需要同时访问这两组池的 QA 工程师和开发者,以便进行开发和测试。他们可以访问蓝色和绿色负载平衡器,这些负载平衡器会将他们分别路由到蓝色实例池和绿色实例池。

蓝色和绿色虚拟机池以 Compute Engine MIG 的形式实现,外部 IP 地址通过外部 HTTP(s) 负载平衡器路由到 MIG 中的虚拟机。本文档中描述的代码示例使用 Terraform 来配置此基础架构。

下图说明了部署中发生的开发者操作:

开发者运营流程

在上图中,红色箭头表示首次设置部署基础架构时发生的引导流程,蓝色箭头表示每次部署期间发生的 GitOps 流程。

如需设置此基础架构,您需要运行一个设置脚本,该脚本会启动引导加载程序进程并设置 GitOps 流程的组件。

设置脚本会执行一个 Cloud Build 流水线,该流水线会执行以下操作:

  • Cloud Source Repositories 中创建一个名为 copy-of-gcp-mig-simple 的代码库,并将 GitHub 示例代码库中的源代码复制到 Cloud Source Repositories 中的该代码库。
  • 创建两个名为 applydestroyCloud Build 触发器

apply 触发器附加到 Cloud Source Repositories 中名为 main.tfvars 的 Terraform 文件。此文件包含表示蓝色和绿色负载平衡器的 Terraform 变量。

如需设置部署,请更新 main.tfvars 文件中的变量。apply 触发器会运行一个 Cloud Build 流水线,该流水线会执行 tf_apply 并执行以下操作:

  • 创建两个 Compute Engine MIG(一个用于绿色版本,一个用于蓝色版本)、四个 Compute Engine 虚拟机实例(两个用于绿色 MIG,两个用于蓝色 MIG)、三个负载平衡器(蓝色、绿色和拆分器)以及三个公共 IP 地址。
  • 输出可用于查看蓝色和绿色实例中已部署应用的 IP 地址。

销毁触发器会手动触发,以删除应用触发器创建的所有资源。

目标

  • 使用 Cloud Build 和 Terraform 设置具有 Compute Engine 虚拟机实例组后端的外部 HTTP(S) 负载平衡器。

  • 在虚拟机实例上执行蓝绿部署。

费用

在本文档中,您将使用 Google Cloud的以下收费组件:

您可使用价格计算器根据您的预计使用情况来估算费用。

新 Google Cloud 用户可能有资格申请免费试用

完成本文档中描述的任务后,您可以通过删除所创建的资源来避免继续计费。如需了解详情,请参阅清理

准备工作

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. Install the Google Cloud CLI.

  3. 如果您使用的是外部身份提供方 (IdP),则必须先使用联合身份登录 gcloud CLI

  4. 如需初始化 gcloud CLI,请运行以下命令:

    gcloud init
  5. Create or select a Google Cloud project.

    • Create a Google Cloud project:

      gcloud projects create PROJECT_ID

      Replace PROJECT_ID with a name for the Google Cloud project you are creating.

    • Select the Google Cloud project that you created:

      gcloud config set project PROJECT_ID

      Replace PROJECT_ID with your Google Cloud project name.

  6. Verify that billing is enabled for your Google Cloud project.

  7. Install the Google Cloud CLI.

  8. 如果您使用的是外部身份提供方 (IdP),则必须先使用联合身份登录 gcloud CLI

  9. 如需初始化 gcloud CLI,请运行以下命令:

    gcloud init
  10. Create or select a Google Cloud project.

    • Create a Google Cloud project:

      gcloud projects create PROJECT_ID

      Replace PROJECT_ID with a name for the Google Cloud project you are creating.

    • Select the Google Cloud project that you created:

      gcloud config set project PROJECT_ID

      Replace PROJECT_ID with your Google Cloud project name.

  11. Verify that billing is enabled for your Google Cloud project.

  12. 测试

    1. 从 Google 代码示例代码库运行设置脚本:

      bash <(curl https://raw.githubusercontent.com/GoogleCloudPlatform/cloud-build-samples/main/mig-blue-green/setup.sh)
      
    2. 当设置脚本要求用户同意时,输入 yes

      脚本会在几秒钟内完成运行。

    3. 在 Google Cloud 控制台中,打开 Cloud Build 构建记录页面:

      打开“构建历史记录”页面

    4. 点击最新 build。

      您会看到构建详情页面,其中显示了一个包含三个构建步骤的 Cloud Build 流水线:第一个构建步骤在 Cloud Source Repositories 中创建一个代码库,第二个步骤将 GitHub 中示例代码库的内容克隆到 Cloud Source Repositories,第三个步骤添加两个构建触发器。

    5. 打开 Cloud Source Repositories:

      打开 Cloud Source Repositories

    6. 在代码库列表中,点击 copy-of-gcp-mig-simple

      在页面底部的历史记录标签页中,您会看到一个提交,其说明为 A copy of https://github.com/GoogleCloudPlatform/cloud-build-samples.git,由 Cloud Build 创建,用于创建名为 copy-of-gcp-mig-simple 的代码库。

    7. 打开 Cloud Build 的触发器页面:

      打开“触发器”页面

    8. 您会看到两个名为 applydestroy 的 build 触发器。apply 触发器已附加到 main 分支中的 infra/main.tfvars 文件。每当文件更新时,系统都会执行此触发器。destroy 触发器是手动触发器。

    9. 如需开始部署流程,请更新 infra/main.tfvars 文件:

      1. 在终端窗口中,创建并进入名为 deploy-compute-engine 的文件夹:

        mkdir ~/deploy-compute-engine
        cd ~/deploy-compute-engine
        
      2. 克隆 copy-of-gcp-mig-simple 代码库:

        gcloud source repos clone copy-of-mig-blue-green
        
      3. 进入克隆的目录:

        cd ./copy-of-mig-blue-green
        
      4. 更新 infra/main.tfvars 以将蓝色替换为绿色:

        sed -i'' -e 's/blue/green/g' infra/main.tfvars
        
      5. 添加更新后的文件:

        git add .
        
      6. 提交此文件:

        git commit -m "Promote green"
        
      7. 推送文件:

        git push
        

        infra/main.tfvars 进行更改会触发 apply 触发器的执行,从而启动部署。

    10. 打开 Cloud Source Repositories:

      打开 Cloud Source Repositories

    11. 在代码库列表中,点击 copy-of-gcp-mig-simple

      您会在页面底部的历史记录标签页中看到说明为 Promote green 的提交。

    12. 如需查看 apply 触发器的执行情况,请在 Google Cloud 控制台中打开构建历史记录页面:

      打开“构建历史记录”页面

    13. 点击第一个 build,打开build 详情页面。

      您将看到包含两个构建步骤的 apply 触发流水线。第一个 build 步骤会执行 Terraform apply,以创建部署所需的 Compute Engine 和负载均衡资源。第二个 build 步骤会输出您可以在其中查看应用运行情况的 IP 地址。

    14. 在浏览器中打开与绿色 MIG 对应的 IP 地址。您将看到类似于以下屏幕截图的界面,其中显示了部署情况:

      部署

    15. 前往 Compute Engine 的实例组页面,查看蓝色实例组和绿色实例组:

      打开“实例组”页面

    16. 打开虚拟机实例页面,查看以下四个虚拟机实例:

      打开“虚拟机实例”页面

    17. 打开外部 IP 地址页面,查看三个负载平衡器:

      打开“外部 IP 地址”页面

    了解代码

    此代码示例的源代码包括:

    • 与设置脚本相关的源代码。
    • 与 Cloud Build 流水线相关的源代码。
    • 与 Terraform 模板相关的源代码。

    设置脚本

    setup.sh 是运行引导过程并为蓝绿部署创建组件的设置脚本。该脚本执行以下操作:

    • 启用 Cloud Build、Resource Manager、Compute Engine 和 Cloud Source Repositories API。
    • 向您项目中的 Cloud Build 服务账号授予 roles/editor IAM 角色。Cloud Build 需要此角色才能创建和设置部署所需的 GitOps 组件。
    • 向您项目中的 Cloud Build 服务账号授予 roles/source.admin IAM 角色。Cloud Build 服务账号必须具有此角色,才能在您的项目中创建 Cloud Source Repositories,并将示例 GitHub 代码库的内容克隆到您的 Cloud Source Repositories。
    • 内联生成名为 bootstrap.cloudbuild.yaml 的 Cloud Build 流水线,该流水线:

      • 在 Cloud Source Repositories 中创建新代码库。
      • 将示例 GitHub 代码库中的源代码复制到 Cloud Source Repositories 中的新代码库。
      • 创建 apply 和 destroy 构建触发器。
    set -e
    
    BLUE='\033[1;34m'
    RED='\033[1;31m'
    GREEN='\033[1;32m'
    NC='\033[0m'
    
    echo -e "\n${GREEN}######################################################"
    echo -e "#                                                    #"
    echo -e "#  Zero-Downtime Blue/Green VM Deployments Using     #"
    echo -e "#  Managed Instance Groups, Cloud Build & Terraform  #"
    echo -e "#                                                    #"
    echo -e "######################################################${NC}\n"
    
    echo -e "\nSTARTED ${GREEN}setup.sh:${NC}"
    
    echo -e "\nIt's ${RED}safe to re-run${NC} this script to ${RED}recreate${NC} all resources.\n"
    echo "> Checking GCP CLI tool is installed"
    gcloud --version > /dev/null 2>&1
    
    readonly EXPLICIT_PROJECT_ID="$1"
    readonly EXPLICIT_CONSENT="$2"
    
    if [ -z "$EXPLICIT_PROJECT_ID" ]; then
        echo "> No explicit project id provided, trying to infer"
        PROJECT_ID="$(gcloud config get-value project)"
    else
        PROJECT_ID="$EXPLICIT_PROJECT_ID"
    fi
    
    if [ -z "$PROJECT_ID" ]; then
        echo "ERROR: GCP project id was not provided as parameter and could not be inferred"
        exit 1
    else
        readonly PROJECT_NUM="$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')"
        if [ -z "$PROJECT_NUM" ]; then
            echo "ERROR: GCP project number could not be determined"
            exit 1
        fi
        echo -e "\nYou are about to:"
        echo -e "  * modify project ${RED}${PROJECT_ID}/${PROJECT_NUM}${NC}"
        echo -e "  * ${RED}enable${NC} various GCP APIs"
        echo -e "  * make Cloud Build ${RED}editor${NC} of your project"
        echo -e "  * ${RED}execute${NC} Cloud Builds and Terraform plans to create"
        echo -e "  * ${RED}4 VMs${NC}, ${RED}3 load balancers${NC}, ${RED}3 public IP addresses${NC}"
        echo -e "  * incur ${RED}charges${NC} in your billing account as a result\n"
    fi
    
    if [ "$EXPLICIT_CONSENT" == "yes" ]; then
      echo "Proceeding under explicit consent"
      readonly CONSENT="$EXPLICIT_CONSENT"
    else
        echo -e "Enter ${BLUE}'yes'${NC} if you want to proceed:"
        read CONSENT
    fi
    
    if [ "$CONSENT" != "yes" ]; then
        echo -e "\nERROR: Aborted by user"
        exit 1
    else
        echo -e "\n......................................................"
        echo -e "\n> Received user consent"
    fi
    
    #
    # Executes action with one randomly delayed retry.
    #
    function do_with_retry {
        COMMAND="$@"
        echo "Trying $COMMAND"
        (eval $COMMAND && echo "Success on first try") || ( \
            echo "Waiting few seconds to retry" &&
            sleep 10 && \
            echo "Retrying $COMMAND" && \
            eval $COMMAND \
        )
    }
    
    echo "> Enabling required APIs"
    # Some of these can be enabled later with Terraform, but I personally
    # prefer to do all API enablement in one place with gcloud.
    gcloud services enable \
        --project=$PROJECT_ID \
        cloudbuild.googleapis.com \
        cloudresourcemanager.googleapis.com \
        compute.googleapis.com \
        sourcerepo.googleapis.com \
        --no-user-output-enabled \
        --quiet
    
    echo "> Adding Cloud Build to roles/editor"
    gcloud projects add-iam-policy-binding \
        "$PROJECT_ID" \
        --member="serviceAccount:$PROJECT_NUM@cloudbuild.gserviceaccount.com" \
        --role='roles/editor' \
        --condition=None \
        --no-user-output-enabled \
        --quiet
    
    echo "> Adding Cloud Build to roles/source.admin"
    gcloud projects add-iam-policy-binding \
        "$PROJECT_ID" \
        --member="serviceAccount:$PROJECT_NUM@cloudbuild.gserviceaccount.com" \
        --condition=None \
        --role='roles/source.admin' \
        --no-user-output-enabled \
        --quiet
    
    echo "> Configuring bootstrap job"
    rm -rf "./bootstrap.cloudbuild.yaml"
    cat <<'EOT_BOOT' > "./bootstrap.cloudbuild.yaml"
    tags:
    - "mig-blue-green-bootstrapping"
    steps:
    - id: create_new_cloud_source_repo
      name: "gcr.io/cloud-builders/gcloud"
      script: |
        #!/bin/bash
        set -e
    
        echo "(Re)Creating source code repository"
    
        gcloud source repos delete \
            "copy-of-mig-blue-green" \
            --quiet || true
    
        gcloud source repos create \
            "copy-of-mig-blue-green" \
            --quiet
    
    - id: copy_demo_source_into_new_cloud_source_repo
      name: "gcr.io/cloud-builders/gcloud"
      env:
        - "PROJECT_ID=$PROJECT_ID"
        - "PROJECT_NUMBER=$PROJECT_NUMBER"
      script: |
        #!/bin/bash
        set -e
    
        readonly GIT_REPO="https://github.com/GoogleCloudPlatform/cloud-build-samples.git"
    
        echo "Cloning demo source repo"
        mkdir /workspace/from/
        cd /workspace/from/
        git clone $GIT_REPO ./original
        cd ./original
    
        echo "Cloning new empty repo"
        mkdir /workspace/to/
        cd /workspace/to/
        gcloud source repos clone \
            "copy-of-mig-blue-green"
        cd ./copy-of-mig-blue-green
    
        echo "Making a copy"
        cp -r /workspace/from/original/mig-blue-green/* ./
    
        echo "Setting git identity"
        git config user.email \
            "$PROJECT_NUMBER@cloudbuild.gserviceaccount.com"
        git config user.name \
            "Cloud Build"
    
        echo "Commit & push"
        git add .
        git commit \
            -m "A copy of $GIT_REPO"
        git push
    
    - id: add_pipeline_triggers
      name: "gcr.io/cloud-builders/gcloud"
      env:
        - "PROJECT_ID=$PROJECT_ID"
      script: |
        #!/bin/bash
        set -e
    
        echo "(Re)Creating destroy trigger"
        gcloud builds triggers delete "destroy" --quiet || true
        gcloud builds triggers create manual \
            --name="destroy" \
            --repo="https://source.developers.google.com/p/$PROJECT_ID/r/copy-of-mig-blue-green" \
            --branch="master" \
            --build-config="pipelines/destroy.cloudbuild.yaml" \
            --repo-type=CLOUD_SOURCE_REPOSITORIES \
            --quiet
    
        echo "(Re)Creating apply trigger"
        gcloud builds triggers delete "apply" --quiet || true
        gcloud builds triggers create cloud-source-repositories \
            --name="apply" \
            --repo="copy-of-mig-blue-green" \
            --branch-pattern="master" \
            --build-config="pipelines/apply.cloudbuild.yaml" \
            --included-files="infra/main.tfvars" \
            --quiet
    
    EOT_BOOT
    
    echo "> Waiting API enablement propagation"
    do_with_retry "(gcloud builds list --project "$PROJECT_ID" --quiet && gcloud compute instances list --project "$PROJECT_ID" --quiet && gcloud source repos list --project "$PROJECT_ID" --quiet) > /dev/null 2>&1" > /dev/null 2>&1
    
    echo "> Executing bootstrap job"
    gcloud beta builds submit \
        --project "$PROJECT_ID" \
        --config ./bootstrap.cloudbuild.yaml \
        --no-source \
        --no-user-output-enabled \
        --quiet
    rm ./bootstrap.cloudbuild.yaml
    
    echo -e "\n${GREEN}All done. Now you can:${NC}"
    echo -e "  * manually run 'apply' and 'destroy' triggers to manage deployment lifecycle"
    echo -e "  * commit change to 'infra/main.tfvars' and see 'apply' pipeline trigger automatically"
    
    echo -e "\n${GREEN}Few key links:${NC}"
    echo -e "  * Dashboard: https://console.cloud.google.com/home/dashboard?project=$PROJECT_ID"
    echo -e "  * Repo: https://source.cloud.google.com/$PROJECT_ID/copy-of-mig-blue-green"
    echo -e "  * Cloud Build Triggers: https://console.cloud.google.com/cloud-build/triggers;region=global?project=$PROJECT_ID"
    echo -e "  * Cloud Build History: https://console.cloud.google.com/cloud-build/builds?project=$PROJECT_ID"
    
    echo -e "\n............................."
    
    echo -e "\n${GREEN}COMPLETED!${NC}"

    Cloud Build 流水线

    apply.cloudbuild.yamldestroy.cloudbuild.yaml 是设置脚本用于为 GitOps 流程设置资源的 Cloud Build 配置文件。apply.cloudbuild.yaml 包含两个构建步骤:

    • tf_apply build 构建步骤,用于调用安装 Terraform 的函数 tf_install_in_cloud_build_steptf_apply 用于创建 GitOps 流程中使用的资源。函数 tf_install_in_cloud_build_steptf_applybash_utils.sh 中定义,构建步骤使用 source 命令来调用它们。
    • describe_deployment build 步骤,用于调用可输出负载平衡器 IP 地址的函数 describe_deployment

    destroy.cloudbuild.yaml 调用 tf_destroy,后者会删除 tf_apply 创建的所有资源。

    函数 tf_install_in_cloud_build_steptf_applydescribe_deploymenttf_destroy 在文件 bash_utils.sh 中定义。构建配置文件使用 source 命令来调用函数。

    steps:
      - id: run-terraform-apply
        name: "gcr.io/cloud-builders/gcloud"
        env:
          - "PROJECT_ID=$PROJECT_ID"
        script: |
          #!/bin/bash
          set -e
          source /workspace/lib/bash_utils.sh
          tf_install_in_cloud_build_step
          tf_apply
    
      - id: describe-deployment
        name: "gcr.io/cloud-builders/gcloud"
        env:
          - "PROJECT_ID=$PROJECT_ID"
        script: |
          #!/bin/bash
          set -e
          source /workspace/lib/bash_utils.sh
          describe_deployment
    
    tags:
      - "mig-blue-green-apply"
    steps:
      - id: run-terraform-destroy
        name: "gcr.io/cloud-builders/gcloud"
        env:
          - "PROJECT_ID=$PROJECT_ID"
        script: |
          #!/bin/bash
          set -e
          source /workspace/lib/bash_utils.sh
          tf_install_in_cloud_build_step
          tf_destroy
    
    tags:
      - "mig-blue-green-destroy"

    以下代码显示了在 bash_utils.sh 中定义的函数 tf_install_in_cloud_build_step。build config 文件会调用此函数来动态安装 Terraform。它会创建一个 Cloud Storage 存储桶来记录 Terraform 状态。

    function tf_install_in_cloud_build_step {
        echo "Installing deps"
        apt update
        apt install \
            unzip \
            wget \
            -y
    
        echo "Manually installing Terraform"
        wget https://releases.hashicorp.com/terraform/1.3.4/terraform_1.3.4_linux_386.zip
        unzip -q terraform_1.3.4_linux_386.zip
        mv ./terraform /usr/bin/
        rm -rf terraform_1.3.4_linux_386.zip
    
        echo "Verifying installation"
        terraform -v
    
        echo "Creating Terraform state storage bucket $BUCKET_NAME"
        gcloud storage buckets create \
            "gs://$BUCKET_NAME" || echo "Already exists..."
    
        echo "Configure Terraform provider and state bucket"
    cat <<EOT_PROVIDER_TF > "/workspace/infra/provider.tf"
    terraform {
      required_version = ">= 0.13"
      backend "gcs" {
        bucket = "$BUCKET_NAME"
      }
      required_providers {
        google = {
          source  = "hashicorp/google"
          version = ">= 3.77, < 5.0"
        }
      }
    }
    EOT_PROVIDER_TF
    
        echo "$(cat /workspace/infra/provider.tf)"
    }

    以下代码段显示了在 bash_utils.sh 中定义的函数 tf_apply。它首先调用 terraform init 加载所有模块和自定义库,然后运行 terraform apply 以从 main.tfvars 文件加载变量。

    function tf_apply {
        echo "Running Terraform init"
        terraform \
            -chdir="$TF_CHDIR" \
            init
    
        echo "Running Terraform apply"
        terraform \
            -chdir="$TF_CHDIR" \
            apply \
            -auto-approve \
            -var project="$PROJECT_ID" \
            -var-file="main.tfvars"
    }

    以下代码段显示了在 bash_utils.sh 中定义的函数 describe_deployment。它使用 gcloud compute addresses describe 通过名称提取负载平衡器的 IP 地址,然后将其打印出来。

    function describe_deployment {
        NS="ns1-"
        echo -e "Deployment configuration:\n$(cat infra/main.tfvars)"
        echo -e \
          "Here is how to connect to:" \
          "\n\t* active color MIG: http://$(gcloud compute addresses describe ${NS}splitter-address-name --region=us-west1 --format='value(address)')/" \
          "\n\t* blue color MIG: http://$(gcloud compute addresses describe ${NS}blue-address-name --region=us-west1 --format='value(address)')/" \
          "\n\t* green color MIG: http://$(gcloud compute addresses describe ${NS}green-address-name --region=us-west1 --format='value(address)')/"
        echo "Good luck!"
    }

    以下代码段显示了在 bash_utils.sh 中定义的函数 tf_destroy。它会调用 terraform init 来加载所有模块和自定义库,然后运行 terraform destroy 来卸载 Terraform 变量。

    function tf_destroy {
        echo "Running Terraform init"
        terraform \
            -chdir="$TF_CHDIR" \
            init
    
        echo "Running Terraform destroy"
        terraform \
            -chdir="$TF_CHDIR" \
            destroy \
            -auto-approve \
            -var project="$PROJECT_ID" \
            -var-file="main.tfvars"
    }

    Terraform 模板

    您可以在 copy-of-gcp-mig-simple/infra/ 文件夹中找到所有 Terraform 配置文件和变量。

    • main.tf:这是 Terraform 配置文件
    • main.tfvars:此文件定义了 Terraform 变量。
    • mig/splitter/:这些文件夹包含定义负载平衡器的模块。mig/ 文件夹包含 Terraform 配置文件,用于定义蓝色和绿色负载平衡器的 MIG。蓝色 MIG 和绿色 MIG 是相同的,因此只需定义一次,然后针对蓝色对象和绿色对象进行实例化。拆分器负载均衡器的 Terraform 配置文件位于 splitter/ 文件夹中。

    以下代码段显示了 infra/main.tfvars 的内容。它包含三个变量:两个用于确定要部署到蓝色池和绿色池的应用版本,一个用于确定有效颜色(蓝色或绿色)。对此文件的更改会触发部署。

    MIG_VER_BLUE     = "v1"
    MIG_VER_GREEN    = "v1"
    MIG_ACTIVE_COLOR = "blue"

    以下是 infra/main.tf 中的一个代码段。在此代码段中:

    • 为 Google Cloud 项目定义了变量。
    • Google 已设置为 Terraform 提供商。
    • 为命名空间定义了变量。Terraform 创建的所有对象都以该变量为前缀,以便在同一项目中部署多个版本的应用,并且对象名称不会相互冲突。
    • 变量 MIG_VER_BLUEMIG_VER_BLUEMIG_ACTIVE_COLORinfra/main.tfvars 文件中变量的绑定。
    variable "project" {
      type        = string
      description = "GCP project we are working in."
    }
    
    provider "google" {
      project = var.project
      region  = "us-west1"
      zone    = "us-west1-a"
    }
    
    variable "ns" {
      type        = string
      default     = "ns1-"
      description = "The namespace used for all resources in this plan."
    }
    
    variable "MIG_VER_BLUE" {
      type        = string
      description = "Version tag for 'blue' deployment."
    }
    
    variable "MIG_VER_GREEN" {
      type        = string
      description = "Version tag for 'green' deployment."
    }
    
    variable "MIG_ACTIVE_COLOR" {
      type        = string
      description = "Active color (blue | green)."
    }

    以下代码段来自 infra/main.tf,展示了拆分器模块的实例化。此模块会接收有效颜色,以便拆分器负载平衡器知道要将应用部署到哪个 MIG。

    module "splitter-lb" {
      source               = "./splitter"
      project              = var.project
      ns                   = "${var.ns}splitter-"
      active_color         = var.MIG_ACTIVE_COLOR
      instance_group_blue  = module.blue.google_compute_instance_group_manager_default.instance_group
      instance_group_green = module.green.google_compute_instance_group_manager_default.instance_group
    }

    infra/main.tf 中的以下代码段为蓝色和绿色 MIG 定义了两个相同的模块。它会接收在拆分器模块中定义的颜色、网络和子网。

    module "blue" {
      source                               = "./mig"
      project                              = var.project
      app_version                          = var.MIG_VER_BLUE
      ns                                   = var.ns
      color                                = "blue"
      google_compute_network               = module.splitter-lb.google_compute_network
      google_compute_subnetwork            = module.splitter-lb.google_compute_subnetwork_default
      google_compute_subnetwork_proxy_only = module.splitter-lb.google_compute_subnetwork_proxy_only
    }
    
    module "green" {
      source                               = "./mig"
      project                              = var.project
      app_version                          = var.MIG_VER_GREEN
      ns                                   = var.ns
      color                                = "green"
      google_compute_network               = module.splitter-lb.google_compute_network
      google_compute_subnetwork            = module.splitter-lb.google_compute_subnetwork_default
      google_compute_subnetwork_proxy_only = module.splitter-lb.google_compute_subnetwork_proxy_only
    }

    文件 splitter/main.tf 定义了为拆分器 MIG 创建的对象。以下是 splitter/main.tf 中的一个代码段,其中包含在绿色 MIG 和蓝色 MIG 之间切换的逻辑。它由服务 google_compute_region_backend_service 提供支持,该服务可以将流量路由到两个后端区域:var.instance_group_bluevar.instance_group_greencapacity_scaler 定义要路由的流量百分比。

    以下代码会将 100% 的流量路由到指定的颜色,但您可以更新此代码以进行 Canary 部署,从而将流量路由到部分用户。

    resource "google_compute_region_backend_service" "default" {
      name                  = local.l7-xlb-backend-service
      region                = "us-west1"
      load_balancing_scheme = "EXTERNAL_MANAGED"
      health_checks         = [google_compute_region_health_check.default.id]
      protocol              = "HTTP"
      session_affinity      = "NONE"
      timeout_sec           = 30
      backend {
        group           = var.instance_group_blue
        balancing_mode  = "UTILIZATION"
        capacity_scaler = var.active_color == "blue" ? 1 : 0
      }
      backend {
        group           = var.instance_group_green
        balancing_mode  = "UTILIZATION"
        capacity_scaler = var.active_color == "green" ? 1 : 0
      }
    }

    文件 mig/main.tf 定义了与蓝色和绿色 MIG 相关联的对象。此文件中的以下代码段定义了用于创建虚拟机池的 Compute Engine 实例模板。请注意,此实例模板的 Terraform 生命周期属性设置为 create_before_destroy。 这是因为,在更新池的版本时,如果模板仍被旧版池使用,您就无法使用该模板创建新版池。但是,如果在创建新模板之前销毁了旧版本的池,则会有一段时间池处于关闭状态。为避免这种情况,我们将 Terraform 生命周期设置为 create_before_destroy,以便先创建较新版本的虚拟机池,然后再销毁较旧版本。

    resource "google_compute_instance_template" "default" {
      name = local.l7-xlb-backend-template
      disk {
        auto_delete  = true
        boot         = true
        device_name  = "persistent-disk-0"
        mode         = "READ_WRITE"
        source_image = "projects/debian-cloud/global/images/family/debian-10"
        type         = "PERSISTENT"
      }
      labels = {
        managed-by-cnrm = "true"
      }
      machine_type = "n1-standard-1"
      metadata = {
        startup-script = <<EOF
        #! /bin/bash
        sudo apt-get update
        sudo apt-get install apache2 -y
        sudo a2ensite default-ssl
        sudo a2enmod ssl
        vm_hostname="$(curl -H "Metadata-Flavor:Google" \
        http://169.254.169.254/computeMetadata/v1/instance/name)"
        sudo echo "<html><body style='font-family: Arial; margin: 64px; background-color: light${var.color};'><h3>Hello, World!<br><br>version: ${var.app_version}<br>ns: ${var.ns}<br>hostname: $vm_hostname</h3></body></html>" | \
        tee /var/www/html/index.html
        sudo systemctl restart apache2
        EOF
      }
      network_interface {
        access_config {
          network_tier = "PREMIUM"
        }
        network    = var.google_compute_network.id
        subnetwork = var.google_compute_subnetwork.id
      }
      region = "us-west1"
      scheduling {
        automatic_restart   = true
        on_host_maintenance = "MIGRATE"
        provisioning_model  = "STANDARD"
      }
      tags = ["load-balanced-backend"]
    
      # NOTE: the name of this resource must be unique for every update;
      #       this is wy we have a app_version in the name; this way
      #       new resource has a different name vs old one and both can
      #       exists at the same time
      lifecycle {
        create_before_destroy = true
      }
    }

    清理

    为避免因本教程中使用的资源导致您的 Google Cloud 账号产生费用,请删除包含这些资源的项目,或者保留项目但删除各个资源。

    删除各个资源

    1. 删除由应用触发器创建的 Compute Engine 资源:

      1. 打开 Cloud Build 的触发器页面:

        打开“触发器”页面

      2. 触发器表中,找到与 destroy 触发器对应的行,然后点击运行。当触发器完成执行时,由 apply 触发器创建的资源会被删除。

    2. 如需删除在引导过程中创建的资源,请在终端窗口中运行以下命令:

      bash <(curl https://raw.githubusercontent.com/GoogleCloudPlatform/cloud-build-samples/main/mig-blue-green/teardown.sh)
      

    删除项目

      Delete a Google Cloud project:

      gcloud projects delete PROJECT_ID

    后续步骤