使用 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 帐号。

    如果您还没有 Google 帐号,请注册一个新帐号

  2. 在 Cloud Console 的项目选择器页面上,选择或创建 Cloud 项目。

    转到项目选择器页面

  3. 确保您的 Google Cloud 项目已启用结算功能。 了解如何确认您的项目已启用结算功能

  4. 在 Cloud Console 中,激活 Cloud Shell。

    激活 Cloud Shell

    Cloud Shell 会话随即会在 Cloud Console 的底部启动,并显示命令行提示符。Cloud Shell 是一个已安装 Cloud SDK 的 Shell 环境,其中包括 gcloud 命令行工具以及已为当前项目设置的值。该会话可能需要几秒钟时间来完成初始化。

  5. 在 Cloud Shell 中,获取您刚刚选择的项目的 ID:
    gcloud config get-value project
    如果此命令未返回项目 ID,请将 Cloud Shell 配置为使用您的项目。将 PROJECT_ID 替换为您的项目 ID。
    gcloud config set project PROJECT_ID
  6. 启用所需的 API:
    gcloud services enable cloudbuild.googleapis.com compute.googleapis.com
    此步骤可能需要几分钟才能完成。
  7. 如果您从未在 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
    
  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 应用页面

  2. 如果您是第一次在 GitHub 中配置应用,请点击 Setup with Google Cloud Build。如果不是第一次,请点击 Edit your plan,选择您的结算信息,然后在 Edit your plan 页面中,点击 grant this app access

  3. Install Google Cloud Build 页面中,选择 Only select repositories,然后输入 your-user/solutions-terraform-cloudbuild-gitops 以连接到分支代码库。

  4. 点击安装

  5. 登录 Google Cloud。

    授权页面随即显示。系统会要求您授权 Cloud Build GitHub 应用连接到 Google Cloud。

    登录 Google Cloud

  6. 点击授权 Google Cloud Build by GoogleCloudBuild (Authorize Google Cloud Build by GoogleCloudBuild)。

    系统会将您重定向至 Cloud Console。

  7. 选择您要处理的 Google Cloud 项目。

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

  9. 选择代码库步骤中,选择 your-user/solutions-terraform-cloudbuild-gitops 以连接到您的 Google Cloud 项目,然后点击连接

  10. 点击完成,然后点击连接

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

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

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

  1. 在 GitHub 中,导航到分支代码库的主页。
  2. 确保您位于 dev 分支中。

    确保您位于 dev 分支中。

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

  4. 在第 15 行,更正 target_tags 字段中的 "http-server**2**" 拼写错误。

    该值必须为 "http-server"

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

  6. 点击 Propose file change

  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:0.11.14'
  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:0.11.14'
  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 oficial environment."
        echo "*******************************************************************************"
      fi

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

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

  1. 在 GitHub 中,导航到分支代码库的主页。
  2. 在代码库名称下,点击 Settings
  3. 在左侧菜单中,点击 Branches
  4. Branch protection rules 下,点击 Add rule
  5. Branch name pattern 中,选择 dev
  6. Rule settings 中,选择 Require status checks to pass before merging,然后在 Status checks found in the last week for this repository 中,点击 Build
  7. 点击 Create
  8. 重复第 5 步到第 8 步,将 Branch name pattern 设置为 prod

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

推广对开发环境的更改

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

  1. 在 GitHub 中,导航到分支代码库的主页。
  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,并在网络浏览器中打开该地址。

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

推广对生产环境的更改

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

  1. 在 GitHub 中,导航到分支代码库的主页。
  2. 点击 New pull request
  3. 对于 base repository,请选择刚创建的分支代码库。
  4. 对于 base,请选择 prod,对于 compare,请选择 dev
  5. 点击 Create pull request
  6. 对于 title,请输入一个标题(如 Promoting networking changes),然后点击 Create pull request
  7. 查看建议的更改,包括 Cloud Build 中的 terraform plan 详细信息,然后点击 Merge pull request
  8. 点击 Confirm merge
  9. 在 Cloud Console 中,打开构建记录页面,查看应用到生产环境的更改:

    转到 Cloud Build 页面

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

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

    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
    
  11. 复制 external-ip-value,并在网络浏览器中打开该地址。

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

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

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

清理

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

删除项目

  1. 在 Cloud Console 中,转到管理资源页面。

    转到“管理资源”页面

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

后续步骤