使用 Terraform、Cloud Build 和 GitOps 以代码形式管理基础架构

本教程介绍如何使用常用的 GitOps 方法,通过 TerraformCloud Build 以代码形式管理基础架构。术语 GitOps 一词由 Weaveworks 首先提出,其主要概念是使用 Git 代码库来存储您所需的环境状态。Terraform 是一种 HashiCorp 工具,可让您使用代码以可预测的方式创建、更改和改进您的云基础设施。在本教程中,您将使用 Google Cloud 持续集成服务 Cloud Build 自动将 Terraform 清单应用到您的环境。

本教程适用于正在寻求一种出色的解决方案来以可预测的方式对基础架构做出更改的开发者和运维人员。本文假设您熟悉 Google Cloud、Linux 和 GitHub。

DevOps 现状报告确定了可提高软件交付方面表现的功能。本教程将帮助您使用以下功能:

架构

为了演示本教程如何运用 GitOps 方法来管理 Terraform 的执行,请参考以下架构图。请注意,它使用 GitHub 分支 devprod 来表示实际环境。这些环境分别由 Virtual Private Cloud (VPC) 网络 devprod 定义,组合成一个 Google Cloud 项目。

具有 dev 和 prod 环境的基础架构。

当您将 Terraform 代码推送到 devprod 分支时,该过程就会启动。在这种情况下,Cloud Build 会触发,然后会应用 Terraform 清单在相应环境中实现所需状态。另一方面,当您将 Terraform 代码推送到任何其他分支(例如,某个功能分支)时,Cloud Build 会运行以执行 terraform plan,但不会对任何环境应用任何内容。

理想情况下,开发者或运维人员必须向未受保护的分支提出基础架构提案,然后通过拉取请求提交这些提案。本教程稍后介绍的 Cloud Build GitHub 应用会自动触发构建作业,并将 terraform plan 报告与这些拉取请求进行关联。这样,您就可以与协作者讨论和查看潜在更改,并在将更改合并到基本分支之前添加后续提交。

如果不存在任何问题,您必须先将更改合并到 dev 分支。此合并会触发将基础架构部署到 dev 环境的操作,如此您便可以测试此环境。在测试所部署的内容并确信其没有问题之后,您必须将 dev 分支合并到 prod 分支,以触发在生产环境中安装基础架构的操作。

目标

  • 设置 GitHub 代码库。
  • 将 Terraform 配置为在 Cloud Storage 存储桶中存储状态。
  • 向您的 Cloud Build 服务账号授予权限。
  • 将 Cloud Build 连接到您的 GitHub 代码库。
  • 更改功能分支中的环境配置。
  • 将更改升级到开发环境。
  • 推广对生产环境的更改。

费用

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

您可使用价格计算器根据您的预计使用情况来估算费用。 Google Cloud 新用户可能有资格申请免费试用

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

前提条件

  1. 登录您的 Google Cloud 账号。如果您是 Google Cloud 新手,请创建一个账号来评估我们的产品在实际场景中的表现。新客户还可获享 $300 赠金,用于运行、测试和部署工作负载。
  2. 在 Google Cloud Console 中的项目选择器页面上,选择或创建一个 Google Cloud 项目

    转到“项目选择器”

  3. 确保您的 Google Cloud 项目已启用结算功能

  4. 在 Google Cloud Console 中的项目选择器页面上,选择或创建一个 Google Cloud 项目

    转到“项目选择器”

  5. 确保您的 Google Cloud 项目已启用结算功能

  6. 在 Google Cloud 控制台中,激活 Cloud Shell。

    激活 Cloud Shell

    Cloud Shell 会话随即会在 Google Cloud 控制台的底部启动,并显示命令行提示符。Cloud Shell 是一个已安装 Google Cloud CLI 且已为当前项目设置值的 Shell 环境。该会话可能需要几秒钟时间来完成初始化。

  7. 在 Cloud Shell 中,获取您刚刚选择的项目的 ID:
    gcloud config get-value project
    如果此命令未返回项目 ID,请将 Cloud Shell 配置为使用您的项目。将 PROJECT_ID 替换为项目 ID。
    gcloud config set project PROJECT_ID
  8. 启用所需的 API:
    gcloud services enable cloudbuild.googleapis.com compute.googleapis.com
    此步骤可能需要几分钟才能完成。
  9. 如果您从未在 Cloud Shell 中使用过 Git,请使用您的姓名和电子邮件地址配置 Cloud Shell:
    git config --global user.email "YOUR_EMAIL_ADDRESS"
    git config --global user.name "YOUR_NAME"
    
    Git 根据这些信息确定您就是在 Cloud Shell 中创建的提交的作者本人。

设置 GitHub 代码库

在本教程中,您将使用一个 Git 代码库来定义您的云基础架构。您还可以通过使不同的分支对应于不同的环境,来编排此基础架构:

  • dev 分支包含应用于开发环境的最新更改。
  • prod 分支包含应用于生产环境的最新更改。

借助此基础架构,您可以随时参考代码库以了解每个环境中的预期配置,并通过先将它们合并到 dev 环境中来提出新的更改建议。然后,您可以通过将 dev 分支合并到后续的 prod 分支来推广更改。

首先,请创建 solutions-terraform-cloudbuild-gitops 分支代码库。

  1. 在 GitHub 上,导航到 https://github.com/GoogleCloudPlatform/solutions-terraform-cloudbuild-gitops.git
  2. 在页面的右上角,点击创建分支

    创建分支代码库。

    现在,您已拥有包含源文件的 solutions-terraform-cloudbuild-gitops 代码库副本。

  3. 在 Cloud Shell 中,克隆此分支代码库,将 YOUR_GITHUB_USERNAME 替换为您的 GitHub 用户名:

    cd ~
    git clone https://github.com/YOUR_GITHUB_USERNAME/solutions-terraform-cloudbuild-gitops.git
    cd ~/solutions-terraform-cloudbuild-gitops
    

此代码库中代码的结构如下所示:

  • environments/ 文件夹包含代表环境(例如 devprod)的子文件夹,它们将处于不同成熟阶段(分别为开发和生产阶段)的工作负载进行逻辑分隔。尽管让这些环境尽可能保持相似是不错的做法,但每个子文件夹都有自己的 Terraform 配置,以确保它们可以根据需要采用独特的设置。

  • modules/ 文件夹包含内嵌 Terraform 模块。这些模块代表相关资源的逻辑分组,用于跨不同环境共享代码。

  • cloudbuild.yaml 文件是包含 Cloud Build 说明的构建配置文件,例如如何根据一系列步骤执行任务。此文件根据 Cloud Build 从中提取代码的分支指定条件执行,例如:

    • 对于 devprod 分支,执行以下步骤:

      1. terraform init
      2. terraform plan
      3. terraform apply
    • 对于任何其他分支,执行以下步骤:

      1. 对于所有 environments 子文件夹,执行 terraform init
      2. 对于所有 environments 子文件夹,执行 terraform plan

为了确保建议的更改适用于每种环境,请为所有 environments 子文件夹运行 terraform initterraform plan。例如,在合并拉取请求之前,您可以查看这些方案,以确保不会对未经授权的实体授予访问权限等等。

将 Terraform 配置为在 Cloud Storage 存储分区中存储状态

默认情况下,Terraform 会将状态存储在名为 terraform.tfstate 的本地文件中。采用此默认配置,使用 Terraform 对团队而言可能会很困难,尤其是当许多用户同时运行 Terraform 并且每个机器都对当前基础架构有自己的理解时。

为帮助您避免此类问题,本部分将配置指向 Cloud Storage 存储分区的远程状态。远程状态是一项后端功能,在本教程中,该功能在 backend.tf 文件中进行配置,例如:

# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

terraform {
  backend "gcs" {
    bucket = "PROJECT_ID-tfstate"
    prefix = "env/dev"
  }
}

在以下步骤中,您将创建一个 Cloud Storage 存储分区,并将一些文件更改为指向新存储分区和 Google Cloud 项目。

  1. 在 Cloud Shell 中,创建 Cloud Storage 存储分区:

    PROJECT_ID=$(gcloud config get-value project)
    gsutil mb gs://${PROJECT_ID}-tfstate
    
  2. 启用对象版本控制,以保留部署历史记录:

    gsutil versioning set on gs://${PROJECT_ID}-tfstate
    

    启用“对象版本控制”会增加存储费用,您可以将对象生命周期管理配置为删除旧的状态版本,以缓解存储费用增加问题。

  3. PROJECT_ID 占位符替换为 terraform.tfvarsbackend.tf 文件中的项目 ID:

    cd ~/solutions-terraform-cloudbuild-gitops
    sed -i s/PROJECT_ID/$PROJECT_ID/g environments/*/terraform.tfvars
    sed -i s/PROJECT_ID/$PROJECT_ID/g environments/*/backend.tf
    

    在 OS X/MacOS 上,您可能需要在 sed -i 后添加两个引号 (""),如下所示:

    cd ~/solutions-terraform-cloudbuild-gitops
    sed -i "" s/PROJECT_ID/$PROJECT_ID/g environments/*/terraform.tfvars
    sed -i "" s/PROJECT_ID/$PROJECT_ID/g environments/*/backend.tf
    
  4. 检查是否所有文件都已更新:

    git status
    

    输出类似于以下内容:

    On branch dev
    Your branch is up-to-date with 'origin/dev'.
    Changes not staged for commit:
     (use "git add <file>..." to update what will be committed)
     (use "git checkout -- <file>..." to discard changes in working directory)
           modified:   environments/dev/backend.tf
           modified:   environments/dev/terraform.tfvars
           modified:   environments/prod/backend.tf
           modified:   environments/prod/terraform.tfvars
    no changes added to commit (use "git add" and/or "git commit -a")
    
  5. 提交并推送您的更改:

    git add --all
    git commit -m "Update project IDs and buckets"
    git push origin dev
    

    根据您的 GitHub 配置,您需要进行身份验证才能推送上述更改。

向您的 Cloud Build 服务账号授予权限

要允许 Cloud Build 服务账号运行 Terraform 脚本以管理 Google Cloud 资源,您需要向其授予项目的相应访问权限。为简单起见,本教程中授予了 Project Editor 访问权限。但是,如果 Project Editor 角色具有较宽泛的权限,那么在生产环境中,您必须遵循公司的 IT 安全最佳做法,此类最佳做法通常提供的是最低访问权限

  1. 在 Cloud Shell 中,从电子邮件中检索项目的 Cloud Build 服务账号:

    CLOUDBUILD_SA="$(gcloud projects describe $PROJECT_ID \
        --format 'value(projectNumber)')@cloudbuild.gserviceaccount.com"
    
  2. 向 Cloud Build 服务账号授予所需的访问权限:

    gcloud projects add-iam-policy-binding $PROJECT_ID \
        --member serviceAccount:$CLOUDBUILD_SA --role roles/editor
    

将 Cloud Build 直接连接到 GitHub 代码库

本部分介绍如何安装 Cloud Build GitHub 应用。在此安装过程中,您可以将 GitHub 代码库与 Google Cloud 项目关联,这样每当您创建新分支或将代码推送到 GitHub 时,Cloud Build 都能自动应用 Terraform 清单。

以下步骤提供了仅针对 solutions-terraform-cloudbuild-gitops 代码库安装该应用的说明,但您可以选择针对更多或所有代码库安装该应用。

  1. 转到 Cloud Build 应用的 GitHub Marketplace 页面:

    打开 Cloud Build 应用页面

    • 如果您是第一次在 GitHub 中配置应用,请点击页面底部的使用 Google Cloud Build 进行设置。然后点击向此应用授予对 GitHub 账号的访问权限
    • 如果这不是在 GitHub 中首次配置应用:请点击配置访问权限。您的个人账号的应用页面随即打开。
  2. 点击 Cloud Build 行中的配置

  3. 选择仅选择代码库,然后选择 solutions-terraform-cloudbuild-gitops 以连接到该代码库。

  4. 点击保存安装 - 按钮标签会因工作流而异。系统会将您重定向到 Google Cloud 以继续安装。

  5. 使用您的 Google Cloud 账号登录。如果需要,请授权 Cloud Build 与 GitHub 集成。

  6. Cloud Build 页面上,选择您的项目。系统会显示一个向导。

  7. 选择代码库部分中,选择您的 GitHub 账号和 solutions-terraform-cloudbuild-gitops 代码库。

  8. 如果您同意这些条款及条件,请选中复选框,然后点击连接

  9. 创建触发器部分,点击创建触发器

    1. 添加触发器名称,例如 push-to-branch。请记下此触发器名称,稍后您将用到它。
    2. 事件部分中,选择推送到分支
    3. 来源部分的分支字段中,选择 .*
    4. 点击创建

Cloud Build GitHub 应用现已配置完毕,您的 GitHub 代码库已关联到您的 Google Cloud 项目。从现在起,对 GitHub 代码库的更改会触发 Cloud Build 的执行操作,该操作会使用 GitHub 检查将结果发回 GitHub。

更改新功能分支中的环境配置

到目前为止,您已经配置了环境的大部分设置。现在,您可以在开发环境中更改代码了。

  1. 在 GitHub 中,导航到分支代码库的主页。

    https://github.com/YOUR_GITHUB_USERNAME/solutions-terraform-cloudbuild-gitops
    
  2. 确保您位于 dev 分支中。

  3. 如需打开文件进行修改,请转至 modules/firewall/main.tf 文件,然后点击铅笔图标。

  4. 在第 30 行,更正 target_tags 字段中的 "http-server2" 拼写错误。

    该值必须为 "http-server"

  5. 在页面底部添加提交消息,例如“Fixing http firewall target”(修复 http 防火墙目标),然后选择 Ceate a new branch for this commit and start a pull request

  6. 点击建议修改

  7. 在随后出现的页面上,点击 Create pull request,以打开包含更改的新拉取请求。

    打开拉取请求后,系统会自动启动 Cloud Build 作业。

  8. 点击 Show all checks,然后等待检查变为绿色。

    显示拉取请求中的所有检查。

  9. 点击 Details 查看更多信息,包括 View more details on Google Cloud Build 链接中 terraform plan 的输出。

暂时不要合并拉取请求。

请注意,Cloud Build 作业运行了 cloudbuild.yaml 文件中定义的流水线。如前所述,根据所提取的分支,此流水线的行为会有所不同。该构建会检查 $BRANCH_NAME 变量是否与任何环境文件夹匹配。如果匹配,Cloud Build 会针对该环境执行 terraform plan。否则,Cloud Build 会针对所有环境执行 terraform plan,以确保建议的更改适用于所有环境。如果这些方案中的任一方案未能执行,构建会失败。

- id: 'tf plan'
  name: 'hashicorp/terraform:1.0.0'
  entrypoint: 'sh'
  args:
  - '-c'
  - |
      if [ -d "environments/$BRANCH_NAME/" ]; then
        cd environments/$BRANCH_NAME
        terraform plan
      else
        for dir in environments/*/
        do
          cd ${dir}
          env=${dir%*/}
          env=${env#*/}
          echo ""
          echo "*************** TERRAFOM PLAN ******************"
          echo "******* At environment: ${env} ********"
          echo "*************************************************"
          terraform plan || exit 1
          cd ../../
        done
      fi 

同样,terraform apply 命令会针对各环境分支运行,但它在任何其他情况下都会被完全忽略。在此部分中,您是向新分支提交的代码更改,因此未对您的 Google Cloud 项目应用任何基础架构部署。

- id: 'tf apply'
  name: 'hashicorp/terraform:1.0.0'
  entrypoint: 'sh'
  args:
  - '-c'
  - |
      if [ -d "environments/$BRANCH_NAME/" ]; then
        cd environments/$BRANCH_NAME
        terraform apply -auto-approve
      else
        echo "***************************** SKIPPING APPLYING *******************************"
        echo "Branch '$BRANCH_NAME' does not represent an official environment."
        echo "*******************************************************************************"
      fi

强制 Cloud Build 执行成功后才合并分支

如需确保仅在各个 Cloud Build 执行成功时才应用合并,请继续执行以下步骤:

  1. 在 GitHub 中,导航到分支代码库的主页。

    https://github.com/YOUR_GITHUB_USERNAME/solutions-terraform-cloudbuild-gitops
    
  2. 在代码库名称下,点击设置

  3. 在左侧菜单中,点击 Branches

  4. Branch protection rules 下,点击 Add rule

  5. 分支名称模式中,输入 dev

  6. 保护匹配的分支部分中,选择合并前需要先通过状态检查

  7. 搜索您之前创建的 Cloud Build 触发器名称。

  8. 点击创建

  9. 重复第 3 步到第 7 步,将分支名称模式 (Branch name pattern) 设置为 prod

此配置非常重要,可用于保护 devprod 分支。这意味着,必须首先将提交内容推送到另一个分支,然后才能将它们合并到受保护的分支。在本教程中,该保护机制要求 Cloud Build 执行成功后,才能合并提交内容。

推广对开发环境的更改

您有一个待合并的拉取请求。现在可以对您的 dev 环境应用所需的状态。

  1. 在 GitHub 中,导航到分支代码库的主页。

    https://github.com/YOUR_GITHUB_USERNAME/solutions-terraform-cloudbuild-gitops
    
  2. 在代码库名称下,点击 Pull requests

  3. 点击您刚创建的拉取请求。

  4. 点击 Merge pull request,然后点击 Confirm merge

    确认合并。

  5. 检查是否已触发新的 Cloud Build:

    前往 Cloud Build 页面

  6. 打开该构建并检查日志。

    构建完成后,您会看到如下内容:

    Step #3 - "tf apply": external_ip = EXTERNAL_IP_VALUE
    Step #3 - "tf apply": firewall_rule = dev-allow-http
    Step #3 - "tf apply": instance_name = dev-apache2-instance
    Step #3 - "tf apply": network = dev
    Step #3 - "tf apply": subnet = dev-subnet-01
    
  7. 复制 EXTERNAL_IP_VALUE,并在网络浏览器中打开该地址。

    http://EXTERNAL_IP_VALUE
    

    此配置可能需要几秒钟时间来启动虚拟机并传播防火墙规则。最终,您将在网络浏览器中看到 Environment: dev

  8. 导航到 Cloud Storage 存储桶中的 Terraform 状态文件。

    https://storage.cloud.google.com/PROJECT_ID-tfstate/env/dev/default.tfstate
    

推广对生产环境的更改

现在,您已经对开发环境进行了全面测试,可以将您的基础架构代码推广到生产环境。

  1. 在 GitHub 中,导航到分支代码库的主页。

    https://github.com/YOUR_GITHUB_USERNAME/solutions-terraform-cloudbuild-gitops
    
  2. 在代码库名称下,点击 Pull requests

  3. 点击 New pull request

  4. 对于 base repository,请选择刚创建的分支代码库。

  5. 对于 base,请从您自己的基本代码库中选择 prod。对于比较,请选择 dev

    对比更改。

  6. 点击创建拉取请求

  7. 对于 title,请输入一个标题(如 Promoting networking changes),然后点击 Create pull request

  8. 查看建议的更改,包括 Cloud Build 中的 terraform plan 详细信息,然后点击 Merge pull request

  9. 点击 Confirm merge

  10. 在 Google Cloud 控制台中,打开构建记录页面,查看应用到生产环境的更改:

    转到 Cloud Build 页面

  11. 等待构建完成,然后检查日志。

    日志末尾会显示如下内容:

    Step #3 - "tf apply": external_ip = EXTERNAL_IP_VALUE
    Step #3 - "tf apply": firewall_rule = prod-allow-http
    Step #3 - "tf apply": instance_name = prod-apache2-instance
    Step #3 - "tf apply": network = prod
    Step #3 - "tf apply": subnet = prod-subnet-01
    
  12. 复制 EXTERNAL_IP_VALUE,并在网络浏览器中打开该地址。

    http://EXTERNAL_IP_VALUE
    

    此配置可能需要几秒钟时间来启动虚拟机并传播防火墙规则。最终,您将在网络浏览器中看到 Environment: prod

  13. 导航到 Cloud Storage 存储桶中的 Terraform 状态文件。

    https://storage.cloud.google.com/PROJECT_ID-tfstate/env/prod/default.tfstate
    

您已在 Cloud Build 上成功配置了无服务器基础架构即代码流水线。以后,您可能想要尝试以下操作:

  • 针对不同的使用情形添加部署。
  • 创建其他环境以满足您的需求。
  • 每个环境使用一个项目,而非每个环境使用一个 VPC。

清理

完成本教程后,请清理您在 Google Cloud 上创建的资源,以免这些资源将来产生费用。

删除项目

  1. 在 Google Cloud 控制台中,进入管理资源页面。

    转到“管理资源”

  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击关闭以删除项目。

删除 GitHub 代码库

为避免在 GitHub 代码库中阻止新的拉取请求,您可以删除分支保护规则:

  1. 在 GitHub 中,导航到分支代码库的主页。
  2. 在代码库名称下,点击设置
  3. 在左侧菜单中,点击 Branches
  4. 分支保护规则部分,点击 devprod 行的删除按钮。

或者,您可以从 GitHub 中完全卸载 Cloud Build 应用:

  1. 转到您的 GitHub 应用设置。

    转到“GitHub 应用”页面

  2. 已安装的 GitHub 应用 (Installed GitHub Apps) 标签页中,点击 Cloud Build 行中的配置。然后,在危险可用区 (Danger zone) 部分中,点击卸载 Google Cloud Builder (Uninstall Google Cloud Builder) 行中的卸载按钮。

    在页面顶部,您会看到一条消息:“您已设置完毕。作业已加入队列以卸载 Google Cloud Build。”

  3. 已获授权的 GitHub 应用标签页中,点击 Google Cloud Build 行中的撤消按钮,然后选中弹出窗口中的我了解,撤消访问权限

如果您不想保留 GitHub 代码库,请执行以下操作:

  1. 在 GitHub 中,转到分支代码库的主页面。
  2. 在代码库名称下,点击设置
  3. 向下滚动到危险可用区
  4. 点击删除此代码库,然后按照确认步骤操作。

后续步骤