通过 Terraform、Jenkins 和 GitOps 以代码形式管理基础架构

本教程介绍了如何使用常用的 GitOps 方法,通过 TerraformJenkins 以代码形式管理基础架构。本教程面向正在寻求最佳做法来以管理软件应用的方式管理基础架构的开发者和运维人员。本文假定您熟悉 Terraform、Jenkins、GitHub、Google Kubernetes Engine (GKE) 和 Google Cloud。

架构

本教程中使用的架构采用 GitHub 分支 devprod 来表示实际开发和生产环境。这些环境由 Google Cloud 项目中的 Virtual Private Cloud (VPC) 网络 devprod 定义。

基础架构提案

如以下架构图所示,当开发者或运维人员向 GitHub 未受保护的分支(通常是功能分支)提出基础架构提案时,该过程就会启动。在适当的情况下,此功能分支可以通过发送至 dev 分支的拉取请求 (PR) 推广到开发环境。然后,Jenkins 会自动触发作业来执行验证流水线。此作业会运行 terraform plan 命令,将验证结果报告回 GitHub,并提供指向详细基础架构更改报告的链接。此步骤至关重要,因为您可以与协作者查看潜在更改,并在将更改合并到 dev 分支之前添加后续提交。

架构图,展示用于管理 Terraform 执行的 GitOps 做法。

Dev 部署

如果验证过程成功,并且您批准了建议的更改,则可以将拉取请求合并到 dev 分支。下图展示了此过程。

将拉取请求合并到“dev”分支。

合并完成后,Jenkins 会触发另一个作业来执行部署流水线。在这种情况下,该作业会在开发环境中应用 Terraform 清单,以实现您所需的状态。此步骤非常重要,因为您可以在将 Terraform 代码推广到生产环境之前对其进行测试。

Prod 部署

在您测试对生产环境的更改并准备好对其进行推广后,您必须将 dev 分支合并到 prod 分支,以触发在生产环境中安装基础架构的操作。下图展示了此过程。

将“dev”分支合并到“prod”分支,以触发在生产环境中安装基础架构的操作。

在您创建拉取请求时,系统会执行相同的验证过程。通过此过程,运营团队可以审核并批准对生产建议的更改。

目标

  • 设置 GitHub 代码库。
  • 创建 Terraform 远程状态存储。
  • 创建 GKE 集群并安装 Jenkins。
  • 更改功能分支中的环境配置。
  • 推广对开发环境的更改。
  • 推广对生产环境的更改。

费用

本教程使用 Google Cloud 的以下收费组件:

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

完成本教程后,您可以删除所创建的资源以避免继续计费。如需了解详情,请参阅清理

准备工作

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

    转到“项目选择器”

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

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

    激活 Cloud Shell

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

  5. 在 Cloud Shell 中,配置您的项目 ID 并设置您的 GitHub 用户名和电子邮件地址:
    PROJECT_ID=PROJECT_ID
    GITHUB_USER=YOUR_GITHUB_USER
    GITHUB_EMAIL=YOUR_EMAIL_ADDRESS
    gcloud config set project $PROJECT_ID
    

    如果您之前没有从 Cloud Shell 访问 GitHub,则可以使用您的用户名和电子邮件地址对其进行配置:

    git config --global user.email "$GITHUB_EMAIL"
    git config --global user.name "$GITHUB_USER"
    

    GitHub 根据这些信息确定您是在 Cloud Shell 中创建的提交的作者。

设置 GitHub 代码库

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

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

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

首先,您必须创建 solutions-terraform-jenkins-gitops 代码库分支。

  1. 在 GitHub 中,转到 solutions-terraform-jenkins-gitops 代码库。
  2. 点击创建分支 (Fork)。

    在 GitHub 中创建代码库分支。

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

  3. 在 Cloud Shell 中,克隆以下分支代码库:

    cd ~
    git clone https://github.com/$GITHUB_USER/solutions-terraform-jenkins-gitops.git
    cd ~/solutions-terraform-jenkins-gitops
    

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

    • example-pipelines/ 文件夹:包含子文件夹,其中包含本教程中使用的示例流水线。
    • example-create/:包含用于在您的环境中创建虚拟机的 Terraform 代码。
    • environments/:包含 devprod 环境文件夹,其中包含后端配置以及指向 example-create/ 文件夹中的文件的链接。
    • jenkins-gke/ 文件夹:包含在新 GKE 集群中部署 Jenkins 所需的脚本。
    • tf-gke/:包含用于部署到 GKE 以及安装 Jenkins 及其依赖资源的 Terraform 代码。

创建 Terraform 远程状态存储

默认情况下,Terraform 状态存储在本地。不过,我们建议您将状态存储在您可以从任何系统进行访问的远程中央存储中。此方法可帮助您避免在不同系统上创建多个副本,这可能会导致基础架构配置和状态不匹配。

在本部分中,您将配置一个存储 Terraform 的远程状态的 Cloud Storage 存储分区。

  1. 在 Cloud Shell 中,创建一个 Cloud Storage 存储分区。

    gsutil mb gs://${PROJECT_ID}-tfstate
    
  2. 启用对象版本控制以保留状态的历史记录:

    gsutil versioning set on gs://${PROJECT_ID}-tfstate
    
  3. PROJECT_ID 占位符替换为 terraform.tfvarsbackend.tf 文件中的项目 ID:

    sed -i.bak "s/PROJECT_ID/${PROJECT_ID}/g" ./example-pipelines/environments/*/terraform.tfvars
    sed -i.bak "s/PROJECT_ID/${PROJECT_ID}/g" ./example-pipelines/environments/*/backend.tf
    
    sed -i.bak "s/PROJECT_ID/${PROJECT_ID}/g" ./jenkins-gke/tf-gke/terraform.tfvars
    sed -i.bak "s/PROJECT_ID/${PROJECT_ID}/g" ./jenkins-gke/tf-gke/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:   example-pipelines/environments/dev/backend.tf
            modified:   example-pipelines/environments/dev/terraform.tfvars
            modified:   example-pipelines/environments/prod/backend.tf
            modified:   example-pipelines/environments/prod/terraform.tfvars
            modified:   jenkins-gke/tf-gke/backend.tf
            modified:   jenkins-gke/tf-gke/terraform.tfvars
    
  5. 提交并推送您的更改:

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

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

创建 GKE 集群并安装 Jenkins

在本部分中,您将使用 Terraform 和 Helm 设置环境,以便以代码形式管理基础架构。首先,使用 Terraform 和 Cloud Foundations Toolkit 来配置 Virtual Private Cloud、GKE 集群和 Workload Identity。然后,使用 helm 基于此环境安装 Jenkins。

在开始运行 Terraform 命令之前,您必须创建 GitHub 个人访问令牌。如需允许 Jenkins 访问您的分支代码库,您必须拥有此令牌。

创建 GitHub 个人访问令牌

  1. 登录 GitHub
  2. 点击您的个人资料照片,然后点击设置
  3. 点击开发者设置,然后点击个人访问令牌 (Personal access tokens)。
  4. 点击生成新令牌,并在备注字段中提供令牌说明,然后选择 repo 范围。
  5. 点击生成令牌并将新创建的令牌复制到剪贴板。

    生成令牌并将其复制到剪贴板。

  6. 在 Cloud Shell 中,将令牌保存在 GITHUB_TOKEN 变量中。此变量内容稍后会作为 Secret 存储在 GKE 集群中。

    GITHUB_TOKEN="NEWLY_CREATED_TOKEN"
    

创建 GKE 集群并安装 Jenkins

现在,您可以创建 GKE 集群。此集群包括 Workload Identityjenkins-wi-jenkins@PROJECT_ID.iam.gserviceaccount.com),可让您在 Cloud Console服务帐号页面中为 Jenkins 授予所需的权限。由于 Workload Identity 具有增强的安全属性和可管理性,我们建议使用 Workload Identity 从 GKE 中访问 Google Cloud 服务。

如需以代码形式管理 Google Cloud 基础架构,Jenkins 必须进行身份验证才能使用 Google Cloud API。在以下步骤中,Terraform 将 Jenkins 使用的 Kubernetes 服务帐号 (KSA) 配置为充当 Google 服务帐号 (GSA)。借助此配置,Jenkins 可以在访问 Google Cloud API 时自动以 GSA 的身份进行身份验证。

为简单起见,在本教程中您授予了 Project Editor 访问权限。但是,由于此角色具有一组宽泛权限,因此在生产环境中,您必须遵循公司的 IT 安全最佳做法,此类最佳做法通常提供的是最低访问权限

  1. 在 Cloud Shell 中,安装 Terraform:

    wget https://releases.hashicorp.com/terraform/0.12.24/terraform_0.12.24_linux_amd64.zip
    unzip terraform_0.12.24_linux_amd64.zip
    sudo mv terraform /usr/local/bin/
    rm terraform_0.12.24_linux_amd64.zip
    
  2. 创建 GKE 集群并安装 Jenkins:

    cd jenkins-gke/tf-gke/
    terraform init
    terraform plan --var "github_username=$GITHUB_USER" --var "github_token=$GITHUB_TOKEN"
    terraform apply --auto-approve --var "github_username=$GITHUB_USER" --var "github_token=$GITHUB_TOKEN"
    

    此过程可能需要几分钟才能完成。输出内容如下所示:

    Apply complete! Resources: 28 added, 0 changed, 0 destroyed.
    
    Outputs:
    
    ca_certificate = LS0tLS1CRU..
    client_token = <sensitive>
    cluster_name = jenkins
    gcp_service_account_email = jenkins-wi-jenkins@PROJECT_ID.iam.gserviceaccount.com
    jenkins_k8s_config_secrets = jenkins-k8s-config
    jenkins_project_id = PROJECT_ID
    k8s_service_account_name = jenkins-wi-jenkins
    kubernetes_endpoint = <sensitive>
    service_account = tf-gke-jenkins-k253@PROJECT_ID.iam.gserviceaccount.com
    zone = us-east4-a
    

    在 Jenkins 部署到新创建的 GKE 集群后,Jenkins 主目录会根据 Helm 图表文档存储在永久性卷中。此部署还附带有预先配置了 example-pipelines/environments/Jenkinsfile多分支流水线,该流水线会对拉取请求触发并合并到 dev 分支和 prod 分支。

  3. 返回主文件夹:

    cd ../..
    
  4. 检索您刚刚创建的集群凭据:

    gcloud container clusters get-credentials jenkins --zone=us-east4-a --project=${PROJECT_ID}
    
  5. 检索 Jenkins 网址和凭据:

    JENKINS_IP=$(kubectl get service jenkins -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    JENKINS_PASSWORD=$(kubectl get secret jenkins -o jsonpath="{.data.jenkins-admin-password}" | base64 --decode);echo
    printf "Jenkins url: http://$JENKINS_IP\nJenkins user: admin\nJenkins password: $JENKINS_PASSWORD\n"
    
  6. 使用上一步中的输出信息登录 Jenkins。

  7. 配置 Jenkins 位置,以便 GitHub 可以创建直接指向您的构建的链接。点击 管理 Jenkins (Manage Jenkins) > 配置系统 (Configure System),然后在 Jenkins 网址 (Jenkins URL) 字段中,设置您的 Jenkins 网址。

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

环境的大部分设置现已配置完成。接下来,可以进行一些代码更改。

  1. 在 Cloud Shell 中,创建一个新的功能分支,通过该功能分支,您可以在不影响团队中其他人员的情况下正常工作:

    git checkout -b change-vm-name
    
  2. 更改虚拟机名称:

    cd example-pipelines/example-create
    sed -i.bak "s/\${var.environment}-001/\${var.environment}-new/g" main.tf
    

    您要更改 example-create 文件夹中的 main.tf 文件。此文件由 devprod 环境文件夹关联,这意味着您的更改会传播到这两个环境。

  3. 将代码更改推送到 GitHub 功能分支:

    git commit -am "change vm name"
    git push --set-upstream origin change-vm-name
    
  4. 在 GitHub 中,转到分支代码库的主页面。

  5. 点击代码库的拉取请求 (Pull requests) 标签页,然后点击新建拉取请求 (New pull request)。

  6. 对于基础代码库 (base repository),请选择分支代码库。

    为基础代码库创建拉取请求。

  7. 对于基础,请选择 dev,对于 比较,请选择 change-vm-name

    选择基础分支和比较分支。

  8. 点击创建拉取请求 (Create pull request)。

  9. 打开拉取请求后,系统会自动启动 Jenkins 作业(Jenkins 可能需要一分钟左右才能确认新的拉取请求)。点击显示所有检查 (Show all checks),然后等待检查变为绿色。

    等待检查变为绿色。

  10. 点击详细信息以查看更多信息,包括 terraform plan 命令的输出。

    有关结果的更多详细信息,包括“terraform plan”的输出。

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

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

下面是 JENKINSFILE 中的 terraform plan 代码:

stage('TF plan') {
  when { anyOf {branch "prod";branch "dev";changeRequest() } }
  steps {
    container('terraform') {
      sh '''
      if [[ $CHANGE_TARGET ]]; then
        TARGET_ENV=$CHANGE_TARGET
      else
        TARGET_ENV=$BRANCH_NAME
      fi

      if [ -d "example-pipelines/environments/${TARGET_ENV}/" ]; then
        cd example-pipelines/environments/${TARGET_ENV}
        terraform plan
      else
        for dir in example-pipelines/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 项目应用任何基础架构部署。

下面是 JENKINSFILE 中的 terraform apply 代码:

stage('TF Apply') {
  when { anyOf {branch "prod";branch "dev" } }
  steps {
    container('terraform') {
      sh '''
      TARGET_ENV=$BRANCH_NAME

      if [ -d "example-pipelines/environments/${TARGET_ENV}/" ]; then
        cd example-pipelines/environments/${TARGET_ENV}
        terraform apply -input=false -auto-approve
      else
        echo "*************** SKIPPING APPLY ******************"
        echo "Branch '$TARGET_ENV' does not represent an official environment."
        echo "*************************************************"
      fi'''
    }
  }
}

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

您可以确保仅在 Jenkins 作业执行成功时才应用合并。

  1. 在 GitHub 中,转到分支代码库的主页面。
  2. 对于您的代码库名称,点击设置
  3. 点击分支
  4. 对于分支保护规则 (Branch protection rules),点击添加规则
  5. 分支名称模式 (Branch name pattern) 中,输入 dev
  6. 保护匹配的分支 (Protect matching branches) 中,选择合并前需要先通过状态检查 (Require status checks to pass before merging),然后选择 continuous-integration/jenkins/pr-merge

    (可选)考虑启用合并前需要审核拉取请求 (Require pull request reviews before merging) 和包含管理员 (Include administrators) 选项,以避免将未审核和未经授权的拉取请求合并到生产环境中。

  7. 点击创建

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

此配置对于保护 devprod 分支非常重要,这意味着您必须先将提交推送到另一个分支,然后才能将它们合并到受保护的分支。在本教程中,该保护机制要求 Jenkins 作业执行成功后,才能允许合并。您可以查看您的配置是否已在新创建的拉取请求中应用。查找绿色对勾标记。

验证您的配置是否已应用。

推广对开发环境的更改

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

  1. 在 GitHub 中,转到分支代码库的主页面。
  2. 对于您的代码库名称,点击拉取请求 (Pull requests)。
  3. 点击您创建的拉取请求。
  4. 点击合并拉取请求 (Merge pull request),然后点击确认合并

    合并并确认拉取请求。

  5. 在 Jenkins 中,点击打开 Blue Ocean (Open Blue Ocean)。然后,在 terraform-jenkins-create-demo 多分支项目的分支标签页中,检查“状态”图标以查看新的 dev 作业是否已触发。该作业可能需要 1 分钟左右才能启动。

    检查新的 **dev** 作业是否已触发。

  6. 在 Cloud Console 中,转到虚拟机实例页面,然后检查您是否有使用新名称的虚拟机。

    转到虚拟机实例

    检查您是否有使用新名称的虚拟机。

推广对生产环境的更改

现在您已测试开发环境,接下来可以将您的基础架构代码推广到生产环境。

  1. 在 GitHub 中,转到分支代码库的主页面。
  2. 点击新建拉取请求 (New pull request)。
  3. 对于基础代码库 (base repository),请选择创建分支的代码库。

    选择创建分支的代码库。

  4. 对于基础,请选择 prod,对于比较,请选择 dev

    针对基础和比较的分支代码库。

  5. 点击创建拉取请求 (Create pull request)。

  6. 输入标题(如 Promoting vm name change),然后点击创建拉取请求 (Create pull request)。

  7. 等待检查工具变为绿色(可能需要一两分钟时间),然后点击 continuous-integration/jenkins/pr-merge 旁边的详细信息链接。

    等待检查工具变为绿色。

  8. 在 Jenkins 中,选择 TF Plan 并在日志中查看建议的更改。

    在日志中查看建议的更改。

  9. 如果建议的更改看起来正确无误,请在 GitHub 中点击合并拉取请求 (Merge pull request),然后点击确认合并

  10. 在 Cloud Console 中,打开虚拟机实例页面,然后检查您的生产虚拟机是否已部署。

    虚拟机实例

    检查您的生产虚拟机是否已部署。

您已在 Jenkins 中配置基础架构即代码流水线。以后,您可能想要尝试以下操作:

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

清理

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

删除项目

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

    转到“管理资源”

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

后续步骤