GKE 上的 Jenkins 多分支流水线

1. 概览

Jenkins 是目前最受欢迎的持续集成解决方案之一。它用于自动执行软件开发流程中的关键非人为部分。通过将 Jenkins 部署到 Google Cloud 上的 Kubernetes 并利用 GKE 插件,我们能够根据需要快速自动扩缩构建执行器。结合使用 Cloud Storage,我们能够以最少的努力构建和测试应用。

实践内容

  • 将 Jenkins 部署到 Kubernetes 集群
  • 部署并配置 Jenkins GKE 插件,以便 Jenkins 能够创建和销毁作为执行器节点的 pod
  • 构建和测试 SpringBoot 示例应用
  • 构建容器并将其发布到 Google Container Registry
  • 将示例应用部署到预演环境和生产环境 GKE

所需条件

  • 设置了结算信息的 Google Cloud 项目。如果您还没有 Google 账号,则需要创建一个

2. 准备工作

此 Codelab 完全可以在 Google Cloud Platform 上运行,无需进行任何本地安装或配置。

Cloud Shell

在本 Codelab 中,我们将通过 Cloud Shell 使用命令行预配和管理不同的云资源和服务。

启用 API

我们需要在项目中启用以下 API:

  • Compute Engine API - 创建和运行虚拟机
  • Kubernetes Engine API - 构建和管理基于容器的应用
  • Cloud Build API:Google Cloud 的持续集成和持续交付平台
  • Service Management API - 允许服务生产者在 Google Cloud Platform 上发布服务
  • Cloud Resource Manager API - 创建、读取和更新 Google Cloud 资源容器的元数据

使用以下 gcloud 命令启用所需的 API:

gcloud services enable compute.googleapis.com \
container.googleapis.com \
cloudbuild.googleapis.com \
servicemanagement.googleapis.com \
cloudresourcemanager.googleapis.com \
--project ${GOOGLE_CLOUD_PROJECT}

创建 GCS 存储分区

我们需要一个 GCS 存储分区来上传测试工作。我们将在存储分区名称中使用项目 ID 以确保其唯一性:

gsutil mb gs://${GOOGLE_CLOUD_PROJECT}-jenkins-test-bucket/ 

3. 创建 Kubernetes 集群

创建集群

接下来,我们将创建一个 GKE 集群来托管 Jenkins 系统,包括将作为工作器节点调度的 Pod。--scopes 标志指示的额外范围将允许 Jenkins 访问 Cloud Source Repositories 和 Container Registry。在 Cloud 控制台中,运行以下命令:

gcloud container clusters create jenkins-cd \
--machine-type n1-standard-2 --num-nodes 1 \
--zone us-east1-d \
--scopes "https://www.googleapis.com/auth/source.read_write,cloud-platform" \
--cluster-version latest

我们还要部署 2 个集群,以托管示例应用的预演版 build 和正式版 build:

gcloud container clusters create staging \
--machine-type n1-standard-2 --num-nodes 1 \
--zone us-east1-d \
--cluster-version latest
gcloud container clusters create prod \
--machine-type n1-standard-2 --num-nodes 2 \
--zone us-east1-d \
--cluster-version latest

28b45298e1e82748.png 验证

集群创建完毕后,我们可以确认它们是否在使用 gcloud container clusters list 运行

输出的 STATUS 列中应包含 RUNNING

NAME        LOCATION    MASTER_VERSION  MASTER_IP     MACHINE_TYPE   NODE_VERSION  NUM_NODES  STATUS
jenkins-cd  us-east1-d  1.15.9-gke.9    34.74.77.124  n1-standard-2  1.15.9-gke.9  2          RUNNING
prod        us-east1-d  1.15.9-gke.9    35.229.98.12  n1-standard-2  1.15.9-gke.9  2          RUNNING
staging     us-east1-d  1.15.9-gke.9    34.73.92.228  n1-standard-2  1.15.9-gke.9  2          RUNNING

4. 使用 Helm 部署 Jenkins

安装 Helm

我们将使用 Helm(适用于 Kubernetes 的应用软件包管理器)在集群上安装 Jenkins。首先,下载包含要用于部署 Jenkins 的 Kubernetes 清单的项目:

git clone https://github.com/GoogleCloudPlatform/continuous-deployment-on-kubernetes.git ~/continuous-deployment-on-kubernetes

将当前工作目录更改为项目目录:

cd ~/continuous-deployment-on-kubernetes/

创建集群角色绑定,为您自己授予 cluster-admin 角色权限:

kubectl create clusterrolebinding cluster-admin-binding --clusterrole=cluster-admin --user=$(gcloud config get-value account)

获取 Jenkins 集群的凭据,以便连接到该集群:

gcloud container clusters get-credentials jenkins-cd --zone us-east1-d --project ${GOOGLE_CLOUD_PROJECT}

并将 Helm 二进制文件下载到 Cloud 控制台:

wget https://storage.googleapis.com/kubernetes-helm/helm-v2.14.1-linux-amd64.tar.gz

解压缩该文件,然后将其中包含的 Helm 文件复制到当前工作目录:

tar zxfv helm-v2.14.1-linux-amd64.tar.gz && \
cp linux-amd64/helm .

Tiller 是 Helm 的服务器端,在 Kubernetes 集群上运行。我们来创建一个名为 tiller 的服务账号:

kubectl create serviceaccount tiller \
--namespace kube-system

并将其绑定到 cluster-admin 集群角色,以便其进行更改:

kubectl create clusterrolebinding tiller-admin-binding \
--clusterrole=cluster-admin \
--serviceaccount=kube-system:tiller

现在,我们可以初始化 Helm 并更新代码库了:

./helm init --service-account=tiller && \
./helm repo update

28b45298e1e82748.png 验证

使用 ./helm version 确认 Helm 可以正常运行 - 这应该会返回客户端和服务器的版本号:

Client: &version.Version{SemVer:"v2.14.1", GitCommit:"5270352a09c7e8b6e8c9593002a73535276507c0", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.14.1", GitCommit:"5270352a09c7e8b6e8c9593002a73535276507c0", GitTreeState:"clean"}

安装 Jenkins

现在,集群上已安装 Helm,我们可以开始安装 Jenkins 了:

./helm install stable/jenkins -n cd \
-f jenkins/values.yaml \
--version 1.2.2 --wait

28b45298e1e82748.png 验证

我们来检查一下 Pod:

kubectl get pods

输出应显示状态为 RUNNING 的 Jenkins Pod:

NAME                          READY     STATUS    RESTARTS   AGE
cd-jenkins-7c786475dd-vbhg4   1/1       Running   0          1m

确认是否已正确创建 Jenkins 服务:

kubectl get svc

输出应类似如下所示:

NAME               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)     AGE
cd-jenkins         ClusterIP   10.35.241.170   <none>        8080/TCP    2m27s
cd-jenkins-agent   ClusterIP   10.35.250.57    <none>        50000/TCP   2m27s
kubernetes         ClusterIP   10.35.240.1     <none>        443/TCP     75m

Jenkins 安装项将使用 Kubernetes 插件来创建构建器代理。Jenkins 主节点会根据需要自动启动这些代理。操作完成后,它们将自动终止且其资源将重新添加到集群的资源池。

连接到 Jenkins

Jenkins 正在我们的集群上运行,但为了访问界面,我们需要通过 Cloud Shell 设置端口转发:

export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/component=jenkins-master" -l "app.kubernetes.io/instance=cd" -o jsonpath="{.items[0].metadata.name}") &&
kubectl port-forward $POD_NAME 8080:8080 >> /dev/null &

安装过程中会生成一个管理员密码。我们来检索一下:

printf $(kubectl get secret cd-jenkins -o jsonpath="{.data.jenkins-admin-password}" | base64 --decode);echo

在 Cloud Shell 顶部,点击“网页预览”图标 7ddf5a65fd556dd6.png,然后选择“在端口 8080 上预览”

1d614c831a621cff.png

我们应该会看到 Jenkins 的登录界面,在其中可以输入用户名 admin 和在上一步中返回的密码:

9cba23e856cbc84f.png

点击登录后,我们应该会进入 Jenkins 的主页。

9261f3e914829137.png

5. 安装并配置 GKE 插件

借助 Google Kubernetes Engine 插件,我们可以将在 Jenkins 中构建的部署发布到在 GKE 中运行的 Kubernetes 集群。您需要使用项目的 IAM 权限进行一些配置。我们将使用 Terraform 部署该配置。

首先,下载 GKE 插件项目:

git clone https://github.com/jenkinsci/google-kubernetes-engine-plugin.git ~/google-kubernetes-engine-plugin

自动 IAM 权限配置

将当前工作目录更改为我们之前克隆的 GKE 项目的 rbac 目录:

cd ~/google-kubernetes-engine-plugin/docs/rbac/

gcp-sa-setup.tf 是一个 Terraform 配置文件,用于创建具有受限权限的自定义 GCP IAM 角色以及要授予该角色的 GCP 服务账号。该文件需要为项目、区域和服务账号名称变量提供值。我们先声明以下环境变量,然后提供这些值:

export TF_VAR_project=${GOOGLE_CLOUD_PROJECT}
export TF_VAR_region=us-east1-d
export TF_VAR_sa_name=kaniko-role

初始化 Terraform、生成方案并应用该方案:

terraform init
terraform plan -out /tmp/tf.plan
terraform apply /tmp/tf.plan && rm /tmp/tf.plan

服务账号需要有存储空间管理员权限才能保存到我们的 Cloud Storage 存储分区:

gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \
--member serviceAccount:kaniko-role@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com \
--role 'roles/storage.admin'

它还需要流水线部署阶段的容器权限:

gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} --member \
serviceAccount:kaniko-role@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com --role 'roles/container.developer'

现在,我们可以使用 Helm 使用 gke robot 部署程序为 GKE 插件设置集群权限。将您的工作目录更改为 GKE 项目的 Helm 目录:

cd ~/google-kubernetes-engine-plugin/docs/helm/

并使用提供的 Helm 图表进行安装:

export TARGET_NAMESPACE=kube-system && \
envsubst < gke-robot-deployer/values.yaml | helm install ./gke-robot-deployer --name gke-robot-deployer -f -

6. 配置 Jenkins

服务账号密钥

为了让服务账号正常运行,我们需要生成一个私钥文件,并将其添加为 Kubernetes Secret。首先,使用以下 gcloud 命令生成文件:

gcloud iam service-accounts keys create /tmp/kaniko-secret.json --iam-account kaniko-role@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com

我们将使用该文件在 Kubernetes Secret 存储区中创建一个 Secret 密钥:

kubectl create secret generic jenkins-int-samples-kaniko-secret --from-file=/tmp/kaniko-secret.json 

通过 Cloud Shell 的三点状菜单访问“下载文件”项,将 json 文件下载到本地磁盘:

c40378e72013b843.png

输入文件路径 /tmp/kaniko-secret.json,然后点击“下载”。

返回 Jenkins 页面,在左侧窗格中,依次点击凭据系统

6c140f7e6bb82f8.png

3b874912cdc8019b.png

在页面标题为系统的部分下,点击左侧的“全局凭据”和“添加凭据”:

4350c0e68561119b.png

3d3526551cdae8b.png

在“Kind”(类型)下拉菜单中,选择 Google Service Account from private key(私钥中的 Google 服务账号)。输入“kaniko-role”作为名称,然后上传您在上一步中创建的 JSON 密钥,最后点击“确定”。

b0502213408e730e.png

环境变量

在创建多分支流水线之前,我们需要先定义一些 Jenkins 环境变量。它们分别是:

  • JENK_INT_IT_ZONE - Kubernetes 集群所在的区域。在本例中为 us-east1-d
  • JENK_INT_IT_PROJECT_ID - 是指托管此 Jenkins 实例的 GCP 项目 ID
  • JENK_INT_IT_STAGING - 我们的“暂存”集群名称,为演示目的,我们将其设为 staging
  • JENK_INT_IT_PROD - 我们的“prod”集群名称。出于演示目的,此值为 prod
  • JENK_INT_IT_BUCKET - 在上一步中创建的 Google Cloud Storage 存储分区
  • JENK_INT_IT_CRED_ID - 是指在上一步中使用 JSON 创建的凭据。该值应与我们为其指定的名称 kaniko-role 一致

如需添加这些信息,请前往 Manage Jenkins(管理 Jenkins):

d54f279190a07878.png

然后,点击配置系统

ce79d218b2799640.png

您会看到一个名为全局属性的部分,勾选环境变量对应的复选框后,系统会显示一个添加按钮,您可以点击该按钮以将上述变量添加为键值对:

81aa222a2b17b2cc.png

点击页面底部的保存按钮以应用更改。

7. 设置流水线

在 Jenkins 中,点击“New Item”(新建项目):

8d1270ce4d7b6a8a.png

输入“jenkins-integration-sample”作为名称,选择“多分支流水线”作为项目类型,然后点击“确定”:

eb071ecfbb4d775b.png

系统会将您重定向至流水线配置页面。在分支源代码下,输入 https://github.com/GoogleCloudPlatform/jenkins-integration-samples.git 作为项目代码库。在构建配置下,将“gke/Jenkinsfile”输入为脚本路径

5135bd6b0374508c.png

点击保存以应用这些设置。保存后,Jenkins 将启动对代码库的扫描,并为每个分支进行后续构建。在构建过程中,您会在 Kubernetes 工作负载页面上看到系统创建、运行和销毁 pod。

构建完成后,您会在 Kubernetes 工作负载页面上看到两个名为 jenkins-integration-samples-gke 的项目,分别对应生产集群和测试集群。状态将显示为“OK”:

bdec6b1753d1ba07.png

使用以下 gcloud 命令,我们可以看到自己已将容器映像上传到与我们的流水线对应的 Google Container Registry:

gcloud container images list

如需在浏览器中查看工作负载,请获取生产集群的凭据:

gcloud container clusters get-credentials prod --zone us-east1-d --project ${GOOGLE_CLOUD_PROJECT}

然后运行以下命令,将端口 8081 从 shell 转发到工作负载的端口 8080:

export POD_NAME=$(kubectl get pods -o jsonpath="{.items[0].metadata.name}") &&
kubectl port-forward $POD_NAME 8081:8080 >> /dev/null &

在 Cloud Shell 顶部,点击“网页预览”图标,然后选择“在端口 8081 上预览”

1b19b5b56f1bae7.png

e80e995e71763bb2.png

8. 清理

我们已经探讨了如何在 Kubernetes 上部署 Jenkins 和示例多分支流水线。现在,我们需要清理项目中创建的所有资源。

删除项目

您也可以选择删除整个项目。在 GCP Console 中,转到 Cloud Resource Manager 页面:

在项目列表中,选择我们一直使用的项目,然后点击删除。此时,系统会提示您输入项目 ID。输入项目 ID,然后点击关停

或者,您也可以使用 gcloud 直接从 Cloud Shell 中删除整个项目:

gcloud projects delete $GOOGLE_CLOUD_PROJECT

如果您想逐个删除不同的可计费组件,请继续阅读下一部分。

Kubernetes 集群

使用 gcloud 删除整个 Kubernetes 集群:

gcloud container clusters delete jenkins-cd --zone=us-east1-d

存储分区

使用 gsutil 移除所有上传的文件并删除我们的存储分区:

gsutil rm -r gs://${GOOGLE_CLOUD_PROJECT}-jenkins-test-bucket

Google Container Registry 映像

我们将使用映像摘要删除 Google Container Registry 映像。首先,使用以下命令检索摘要:

gcloud container images list-tags gcr.io/${GOOGLE_CLOUD_PROJECT}/jenkins-integration-samples-gke --format="value(digest)"

然后,对于返回的每个摘要:

gcloud container images delete gcr.io/${GOOGLE_CLOUD_PROJECT}/jenkins-integration-samples-gke@sha256:<DIGEST>

9. 恭喜!

太棒了!您已完成。您已了解如何在 GKE 上部署 Jenkins 并将作业调度到 Kubernetes 集群。

所学内容

  • 我们部署了一个 Kubernetes 集群,并使用 Helm 安装了 Jenkins
  • 我们安装并配置了 GKE 插件,以便 Jenkins 将构建工件部署到 Kubernetes 集群
  • 我们配置了 Jenkins,以设置一个多分支流水线,用于将工作调度到 GKE 集群