使用 Cloud Build 实现 GitOps 形式的持续交付

本页面介绍如何仅使用托管产品和流行的 GitOps 方法在 Google Cloud 上创建持续集成和交付 (CI/CD) 流水线。

Google 工程师长期以来一直在我们的主要源代码库中存储配置和部署文件。这种方法在《站点可靠性工程》(Beyer 等人著,2016 年出版)一书第 8 章中有所介绍,并由 Kelsey Hightower 在他的 Google Cloud Next '17 主旨演讲中进行了演示。术语“GitOps”一词由 Weaveworks 提出。GitOps 的一个关键部分是“环境即代码”的概念:使用存储在 Git 代码库中的文件(例如 Kubernetes 清单)以声明的方式描述 Deployment。

在本教程中,您将创建一个 CI/CD 流水线。该流水线根据所提交的代码自动构建容器映像,将映像存储在 Container Registry 中,更新 Git 代码库中的 Kubernetes 清单,并使用该清单将应用部署到 Google Kubernetes Engine。

CI/CD 流水线的架构

本教程使用两个 Git 代码库:

  • app 代码库:包含应用本身的源代码
  • env 代码库:包含 Kubernetes 部署的清单

当您将更改推送到 app 代码库时,Cloud Build 流水线会运行测试、构建容器映像,并将其推送到 Container Registry。推送映像后,Cloud Build 会更新 Deployment 清单并将其推送到 env 代码库。这会触发另一个 Cloud Build 流水线,该流水线将清单应用于 GKE 集群,如果成功,则将清单存储在 env 代码库的另一个分支中。

我们将 app 代码库和 env 代码库分开,是因为它们具有不同的生命周期和用途。app 代码库的主要用户是真人,此代码库专用于特定应用。env 代码库的主要用户是自动化系统(例如 Cloud Build),并且此代码库可能由多个应用共享。env 代码库可以有多个分支,每个分支映射到特定环境(您在本教程中仅使用生产环境)并引用特定的容器映像,而 app 代码库则不会如此。

完成本教程后,您将拥有一个可助您轻松实现以下操作的系统:

  • 通过查看 Cloud Build 历史记录来区分失败和成功的部署
  • 通过查看 env 代码库的生产分支来访问当前使用的清单
  • 通过重新执行相应的 Cloud Build 构建,回滚到以前的任何版本

CI/CD 流水线的流程

关于本教程

本教程使用 Cloud Source Repositories 来托管 Git 代码库,但您可以使用其他第三方产品(如 GitHub、Bitbucket 或 GitLab)实现同样的结果。

此流水线未实现在部署之前先进行验证的机制。如果您使用 GitHub、Bitbucket 或 GitLab,则可以修改流水线以使用拉取请求实现此目的。

虽然我们向希望实现高级部署模式(蓝/绿、Canary 分析、多云端等)的团队推荐 Spinnaker,但对于较小的组织和项目,可能不需要这么多功能也能成功实施 CI/CD 策略。在本教程中,您将学习如何使用工具创建适用于 GKE 上托管的应用的 CI/CD 流水线。

为简单起见,本教程在 env 代码库中使用单一环境(即生产环境),但如果需要,您也可以将其扩展为部署到多个环境。

目标

  • 在 Cloud Source Repositories 中创建 Git 代码库
  • 您可以通过 Cloud Build 创建容器映像并将其存储在 Container Registry 中。
  • 创建 CI 流水线。
  • 创建 CD 流水线。
  • 测试 CI/CD 流水线。

费用

准备工作

  1. 选择或创建 Cloud 项目。

    转到“管理资源”

  2. 为您的项目启用结算功能。

    启用结算功能

  3. 打开 Cloud Shell 以执行本教程中列出的命令。Cloud Shell 是 Google Cloud 的交互式 Shell 环境,可让您通过网络浏览器管理项目和资源。

    转到 Cloud Shell

  4. 如果 gcloud config get-value project 命令未返回您选择的项目的 ID,请配置 Cloud Shell 以使用您的项目。

    gcloud config set project [PROJECT_ID]
    
  5. 在 Cloud Shell 中,启用所需的 API。

    gcloud services enable container.googleapis.com \
        cloudbuild.googleapis.com \
        sourcerepo.googleapis.com \
        containeranalysis.googleapis.com
    
  6. 在 Cloud Shell 中,创建一个 GKE 集群,您将用它来部署本教程的示例应用。

    gcloud container clusters create hello-cloudbuild \
        --num-nodes 1 --zone us-central1-b
    
  7. 如果您从未在 Cloud Shell 中使用过 Git,请使用您的姓名和电子邮件地址对其进行配置。Git 将使用它们来识别您将在 Cloud Shell 中创建的提交的作者。

    git config --global user.email "[YOUR_EMAIL_ADDRESS]"
    git config --global user.name "[YOUR_NAME]"
    
  8. 运行 gcloud 凭据帮助程序。这会将您的 Google Cloud 用户帐号连接到 Cloud Source Repositories。

    git config --global credential.helper gcloud.sh
    

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

在 Cloud Source Repositories 中创建 Git 代码库

在本部分中,您将创建本教程中使用的两个 Git 代码库(app 和 env),并使用一些示例代码初始化 app 代码库。

  1. 在 Cloud Shell 中,创建两个 Git 代码库。

    gcloud source repos create hello-cloudbuild-app
    gcloud source repos create hello-cloudbuild-env
    
  2. 从 GitHub 克隆示例代码。

    cd ~
    git clone https://github.com/GoogleCloudPlatform/gke-gitops-tutorial-cloudbuild \
        hello-cloudbuild-app
    
  3. 将 Cloud Source Repositories 配置为远程代码库。

    cd ~/hello-cloudbuild-app
    PROJECT_ID=$(gcloud config get-value project)
    git remote add google \
        "https://source.developers.google.com/p/${PROJECT_ID}/r/hello-cloudbuild-app"
    

您克隆的代码包含一个“Hello World”应用。

from flask import Flask
app = Flask('hello-cloudbuild')

@app.route('/')
def hello():
  return "Hello World!\n"

if __name__ == '__main__':
  app.run(host = '0.0.0.0', port = 8080)

使用 Cloud Build 创建容器映像

您克隆的代码包含以下 Dockerfile。

FROM python:3.7-slim
RUN pip install flask
WORKDIR /app
COPY app.py /app/app.py
ENTRYPOINT ["python"]
CMD ["/app/app.py"]

使用此 Dockerfile,您可以通过 Cloud Build 创建容器映像并将其存储在 Container Registry 中。

  1. 在 Cloud Shell 中,使用以下命令基于最近提交的代码创建 Cloud Build 构建作业。

    cd ~/hello-cloudbuild-app
    COMMIT_ID="$(git rev-parse --short=7 HEAD)"
    gcloud builds submit --tag="gcr.io/${PROJECT_ID}/hello-cloudbuild:${COMMIT_ID}" .
    

    您执行此命令时,Cloud Build 会将容器映像创建过程中生成的日志流式传输到终端。

  2. 构建完成后,验证您的新容器映像在 Container Registry 中可用。

    转到 Container Registry

    Container Registry 中的 hello-cloudbuild 映像

创建持续集成流水线

在本部分中,您将 Cloud Build 配置为自动运行小型单元测试、构建容器映像,然后将其推送到 Container Registry。将新提交推送到 Cloud Source Repositories 会自动触发此流水线。包含在代码中的 cloudbuild.yaml 文件是流水线的配置。

steps:
# This step runs the unit tests on the app
- name: 'python:3.7-slim'
  id: Test
  entrypoint: /bin/sh
  args:
  - -c
  - 'pip install flask && python test_app.py -v'

# This step builds the container image.
- name: 'gcr.io/cloud-builders/docker'
  id: Build
  args:
  - 'build'
  - '-t'
  - 'gcr.io/$PROJECT_ID/hello-cloudbuild:$SHORT_SHA'
  - '.'

# This step pushes the image to Container Registry
# The PROJECT_ID and SHORT_SHA variables are automatically
# replaced by Cloud Build.
- name: 'gcr.io/cloud-builders/docker'
  id: Push
  args:
  - 'push'
  - 'gcr.io/$PROJECT_ID/hello-cloudbuild:$SHORT_SHA'
  1. 打开 Cloud Build 触发器页面。

    转到“触发器”

  2. 点击创建触发器

  3. 填写以下选项:

    • 名称字段中,输入 hello-cloudbuild
    • 事件下,选择推送到分支
    • 来源下,选择 hello-cloudbuild-app 作为您的代码库,选择 ^master$ 作为您的分支
    • 构建配置下,选择 Cloud Build 配置文件
    • Cloud Build 配置文件位置字段中的 / 后面,输入 cloudbuild.yaml
  4. 点击创建以保存您的构建触发器。

    提示:如果您需要为多个项目创建构建触发器,则可以使用 Build Triggers API

  5. 在 Cloud Shell 中,将应用代码推送到 Cloud Source Repositories 以在 Cloud Build 中触发 CI 流水线。

    cd ~/hello-cloudbuild-app
    git push google master
    
  6. 打开 Cloud Build 控制台。

    转至 Cloud Build

    此时将显示您最近运行和已完成的构建。您可以点击构建作业以跟踪其执行情况并检查其日志。

创建持续交付流水线

Cloud Build 也用于持续交付流水线。每次将提交推送到 hello-cloudbuild-env 代码库的“候选”(candidate) 分支时,该流水线都会运行。该流水线会将新版本的清单应用于 Kubernetes 集群,如果成功,则将清单复制到“生产”(production) 分支。此过程具有以下属性:

  • “候选”分支是部署尝试的历史记录。
  • “生产”分支是成功部署的历史记录。
  • 您可以在 Cloud Build 中查看成功和失败的部署。
  • 您可以通过在 Cloud Build 中重新执行相应的构建,回滚到任何先前的部署。回滚还会更新“生产”分支,以真实地反映部署的历史记录。

您将修改持续集成流水线以更新 hello-cloudbuild-env 代码库的“候选”(candidate) 分支,从而触发持续交付流水线。

为 Cloud Build 授予 GKE 的访问权限

为了在 Kubernetes 集群中部署应用,Cloud Build 需要 Kubernetes Engine Developer Identity and Access Management 角色

Shell

在 Cloud Shell 中,执行以下命令:

PROJECT_NUMBER="$(gcloud projects describe ${PROJECT_ID} --format='get(projectNumber)')"
gcloud projects add-iam-policy-binding ${PROJECT_NUMBER} \
    --member=serviceAccount:${PROJECT_NUMBER}@cloudbuild.gserviceaccount.com \
    --role=roles/container.developer

控制台

  1. 在 Google Cloud Console 中,打开 Cloud Build 的“设置”页面:

    打开 Cloud Build 的“设置”页面

    屏幕上会出现服务帐号权限页面:

    “服务帐号权限”页面的屏幕截图

  2. Kubernetes Engine Developer 角色的状态设置为启用

初始化 hello-cloudbuild-env 代码库

您需要使用两个分支(生产和候选)以及描述部署过程的 Cloud Build 配置文件来初始化 hello-cloudbuild-env 代码库。

  1. 在 Cloud Shell 中,克隆 hello-cloudbuild-env 代码库并创建“生产”分支。

    cd ~
    gcloud source repos clone hello-cloudbuild-env
    cd ~/hello-cloudbuild-env
    git checkout -b production
    
  2. 复制 hello-cloudbuild-app 代码库中提供的 cloudbuild-delivery.yaml 文件,并提交更改。

    cd ~/hello-cloudbuild-env
    cp ~/hello-cloudbuild-app/cloudbuild-delivery.yaml ~/hello-cloudbuild-env/cloudbuild.yaml
    git add .
    git commit -m "Create cloudbuild.yaml for deployment"
    

    cloudbuild-delivery.yaml 文件描述了要在 Cloud Build 中运行的部署过程。它有两个步骤:

    1. Cloud Build 在 GKE 集群上应用清单。

    2. 如果成功,Cloud Build 将清单复制到“生产”分支。

    steps:
    # This step deploys the new version of our container image
    # in the hello-cloudbuild Kubernetes Engine cluster.
    - name: 'gcr.io/cloud-builders/kubectl'
      id: Deploy
      args:
      - 'apply'
      - '-f'
      - 'kubernetes.yaml'
      env:
      - 'CLOUDSDK_COMPUTE_ZONE=us-central1-b'
      - 'CLOUDSDK_CONTAINER_CLUSTER=hello-cloudbuild'
    
    # This step copies the applied manifest to the production branch
    # The COMMIT_SHA variable is automatically
    # replaced by Cloud Build.
    - name: 'gcr.io/cloud-builders/git'
      id: Copy to production branch
      entrypoint: /bin/sh
      args:
      - '-c'
      - |
        set -x && \
        # Configure Git to create commits with Cloud Build's service account
        git config user.email $(gcloud auth list --filter=status:ACTIVE --format='value(account)') && \
        # Switch to the production branch and copy the kubernetes.yaml file from the candidate branch
        git fetch origin production && git checkout production && \
        git checkout $COMMIT_SHA kubernetes.yaml && \
        # Commit the kubernetes.yaml file with a descriptive commit message
        git commit -m "Manifest from commit $COMMIT_SHA
        $(git log --format=%B -n 1 $COMMIT_SHA)" && \
        # Push the changes back to Cloud Source Repository
        git push origin production

  3. 创建“候选”分支并推送两个分支,使其在 Cloud Source Repositories 中可用。

    git checkout -b candidate
    git push origin production
    git push origin candidate
    
  4. 将“源代码库写入者”IAM 角色授予 hello-cloudbuild-env 代码库的 Cloud Build 服务帐号。

    PROJECT_NUMBER="$(gcloud projects describe ${PROJECT_ID} \
        --format='get(projectNumber)')"
    cat >/tmp/hello-cloudbuild-env-policy.yaml <<EOF
    bindings:
    - members:
      - serviceAccount:${PROJECT_NUMBER}@cloudbuild.gserviceaccount.com
      role: roles/source.writer
    EOF
    gcloud source repos set-iam-policy \
        hello-cloudbuild-env /tmp/hello-cloudbuild-env-policy.yaml
    

为持续交付流水线创建触发器

在本部分中,您将 Cloud Build 配置为在向 hello-cloudbuild-env 代码库的“候选”分支推送代码时触发。

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

    转到“触发器”

  2. 点击创建触发器

  3. 填写以下选项:

    • 名称字段中,输入 hello-cloudbuild-deploy
    • 事件下,选择推送到分支
    • 来源下,选择 hello-cloudbuild-env 作为您的代码库,选择 ^candidate$ 作为您的分支
    • 配置下,选择 Cloud Build 配置文件(yaml 或 json)
    • Cloud Build 配置文件位置字段中的 / 后面,输入 cloudbuild.yaml
  4. 点击创建

修改持续集成流水线以触发持续交付流水线

在本部分中,您将向持续集成流水线添加一些步骤,这些步骤将生成新版本的 Kubernetes 清单并将其推送到 hello-cloudbuild-env 代码库以触发持续交付流水线。

  1. cloudbuild.yaml 文件替换为 cloudbuild-trigger-cd.yaml 文件中的扩展示例。

    cd ~/hello-cloudbuild-app
    cp cloudbuild-trigger-cd.yaml cloudbuild.yaml
    

    cloudbuild-trigger-cd.yamlcloudbuild.yaml 文件的扩展版本。它添加了以下步骤:生成新的 Kubernetes 清单并触发持续交付流水线。

    # This step clones the hello-cloudbuild-env repository
    - name: 'gcr.io/cloud-builders/gcloud'
      id: Clone env repository
      entrypoint: /bin/sh
      args:
      - '-c'
      - |
        gcloud source repos clone hello-cloudbuild-env && \
        cd hello-cloudbuild-env && \
        git checkout candidate && \
        git config user.email $(gcloud auth list --filter=status:ACTIVE --format='value(account)')
    
    # This step generates the new manifest
    - name: 'gcr.io/cloud-builders/gcloud'
      id: Generate manifest
      entrypoint: /bin/sh
      args:
      - '-c'
      - |
         sed "s/GOOGLE_CLOUD_PROJECT/${PROJECT_ID}/g" kubernetes.yaml.tpl | \
         sed "s/COMMIT_SHA/${SHORT_SHA}/g" > hello-cloudbuild-env/kubernetes.yaml
    
    # This step pushes the manifest back to hello-cloudbuild-env
    - name: 'gcr.io/cloud-builders/gcloud'
      id: Push manifest
      entrypoint: /bin/sh
      args:
      - '-c'
      - |
        set -x && \
        cd hello-cloudbuild-env && \
        git add kubernetes.yaml && \
        git commit -m "Deploying image gcr.io/${PROJECT_ID}/hello-cloudbuild:${SHORT_SHA}
        Built from commit ${COMMIT_SHA} of repository hello-cloudbuild-app
        Author: $(git log --format='%an <%ae>' -n 1 HEAD)" && \
        git push origin candidate
    

  2. 提交修改并将其推送到 Cloud Source Repositories。

    cd ~/hello-cloudbuild-app
    git add cloudbuild.yaml
    git commit -m "Trigger CD pipeline"
    git push google master
    

    这会触发 Cloud Build 中的持续集成流水线。

  3. 检查持续集成构建作业。

    转至 Cloud Build

    您会看到最近运行的构建,以及已为 hello-cloudbuild-app 代码库完成的构建。您可以点击构建作业以跟踪其执行情况并检查其日志。此流水线的最后一步将新清单推送到 hello-cloudbuild-env 代码库,以触发持续交付流水线。

  4. 检查持续交付构建作业。

    转至 Cloud Build

    您会看到最近运行的构建,以及已为 hello-cloudbuild-env 代码库完成的构建。您可以点击构建作业以跟踪其执行情况并检查其日志。

测试完整的流水线

完整的 CI/CD 流水线现已完成配置。在本部分中,您将从头到尾对其进行测试。

  1. 转到 GKE 服务页面。

    转到“Google Kubernetes Engine 服务”

    该列表包含名为 hello-cloudbuild 的单项服务,它是由最近完成的持续交付构建作业创建的。

  2. 点击 hello-cloudbuild 服务的端点。屏幕上会显示“Hello World!”。如果没有端点,或者如果您看到负载平衡器错误,您可能需要等待几分钟时间,让负载平衡器完成初始化。如果需要,可点击刷新以更新页面。

  3. 在 Cloud Shell 中,在应用和单元测试中将“Hello World”替换为“Hello Cloud Build”。

    cd ~/hello-cloudbuild-app
    sed -i 's/Hello World/Hello Cloud Build/g' app.py
    sed -i 's/Hello World/Hello Cloud Build/g' test_app.py
    
  4. 提交更改,并将其推送到 Cloud Source Repositories。

    git add app.py test_app.py
    git commit -m "Hello Cloud Build"
    git push google master
    

    这会触发完整的 CI/CD 流水线。

  5. 几分钟后,在浏览器中重新加载应用。屏幕上会出现“Hello Cloud Build!”。

测试回滚

在本部分中,您将回滚到显示“Hello World!”的应用版本。

  1. hello-cloudbuild-env 代码库打开 Cloud Build 控制台。

    转至 Cloud Build

  2. 点击所列出的第二新的构建作业。

  3. 点击重建

  4. 构建完成后,在浏览器中重新加载应用。屏幕上会再次显示“Hello, World!”。

清理

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

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

    转到“管理资源”

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

删除资源

如果您希望保留在本教程中使用的 Google Cloud 项目,请删除单个资源:

  1. 删除本地 Git 代码库。

    cd ~
    rm -rf ~/hello-cloudbuild-app
    rm -rf ~/hello-cloudbuild-env
    
  2. 删除 Cloud Source Repositories 中的 Git 代码库。

    gcloud source repos delete hello-cloudbuild-app --quiet
    gcloud source repos delete hello-cloudbuild-env --quiet
    
  3. 删除 Cloud Build 触发器。

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

      转到“触发器”

    2. 对于每个触发器,请点击更多 ,然后点击删除

  4. 删除 Container Registry 中的映像。

    gcloud beta container images list-tags \
        gcr.io/${PROJECT_ID}/hello-cloudbuild \
        --format="value(tags)" | \
        xargs -I {} gcloud beta container images delete \
        --force-delete-tags --quiet \
        gcr.io/${PROJECT_ID}/hello-cloudbuild:{}
    
  5. 撤消 Cloud Build 连接到 GKE 的权限。

    PROJECT_NUMBER="$(gcloud projects describe ${PROJECT_ID} \
        --format='get(projectNumber)')"
    gcloud projects remove-iam-policy-binding ${PROJECT_NUMBER} \
        --member=serviceAccount:${PROJECT_NUMBER}@cloudbuild.gserviceaccount.com \
        --role=roles/container.developer
    
  6. 删除 GKE 集群。

    gcloud container clusters delete hello-cloudbuild \
        --zone us-central1-b
    

后续步骤