如何利用 Helm 在 Kubernetes 上快速部署 Jenkins | IDCF

IDCF

Jenkins 做为最著名的 CI/CD 工具,在全世界范围内被广泛使用,而随着以 Kubernetes 为首的云平台的不断发展与壮大,在 Kubernetes 上运行 Jenkins 的需求越来越多。

本文将介绍如何使用 Helm3 在 Kubernets 上快速部署一个生产环境可用的 Jenkins 实例。

通过本文的学习,你将能够:

  • 了解并掌握 Helm 的一些基本操作知识。
  • 利用 Helm Chart 快速部署一个符合自己需求的 Jenkins 实例。

安装前的准备

Kubernetes 集群

既然是部署在 Kubernetes 平台上,首先你当然需要有一个可用的 Kubernetes 集群,本文支持几乎所有 Kubernetes 平台,并分别在 GKE、EKS 以及 Minikube 上做了部署测试。同时你还需要正确配置了本地的 kubeconfig 文件,确保 kubectl 命令可以与你的集群正常通信。

HELM

本文将使用 Helm 部署 Jenkins 到 Kubernetes 平台上。Helm 是一款 Kubernetes 平台上类似于包的管理工具,它将在 Kubernetes 平台上部署应用程序时所需的所有 Kubernetes 资源的定义文件(如:Deployment、Service、PVC 等等)打包到一起,称之为 Chart,而 Helm 正是通过部署这些 Chart 将相应的应用程序部署到 Kubernetes 平台上的。

安装 HELM

在能够使用 Helm 之前,首先要确保 Helm 客户端被正确安装到本地环境中。本文所使用的 Helm 版本为 Helm3 ,它与 Helm2 版本最大的区别在于:Helm3 不在像 Helm2 那样,在可以使用 Helm 命令之前,需要在目标 Kubernetes 平台上提前部署好用于接收和处理由 Helm 命令发送的 API 请求的 Tiller(部署在 kube-system namespace 下的 Pod),Helm3 则可以直接与目标 Kubernetes API 进行通信,而不再需要任何中间翻译层。

因此,在使用 Helm3 版本时,只需确保 Helm3 被正确安装到本地即可。安装 Helm 非常简单,对于 MacOS 用户,可直接通过 brew 命令安装:

$ brew install helm

而 Windows 用户则可以使用 choco 命令安装:

$ choco install kubernetes-helm

或者你也可直接到 Helm 所在的 Github Release 页面下载 Helm3 版本下对应平台的可执行二进制文件,并保存到系统环境变量 PATH 指定的目录下。关于更多安装详情,请查看官方 安装文档。

当安装完成后,即可通过 Helm 的 version 命令查看当前安装的 Helm 版本信息:

$ helm version

version.BuildInfo{Version:"v3.1.2", GitCommit:"d878d4d45863e42fd5cff6743294a11d28a9abce", GitTreeState:"clean", GoVersion:"go1.13.8"}

如果你也得到以上类似信息,那么说明 Helm3 已经被正确安装,接下来我们就可以直接使用 helm命令来部署应程序到 Kubernetes 平台上了。

添加 Helm Chart 仓库

我们已经知道,Helm 是通过部署某个应用程序所对应的 Chart 来实现对该应用程序的部署的。当前已经存在了许许多多被定义好的 Chart 来供我们使用,这些 Chart 被保存在了不同的 Chart 仓库中,你可以通过在 Helm Hub 中搜索并查看是否存在某个应用程序对应的 Chart,也可以通过查看 HELM 官方在 Github 上的 Chart 仓库 来查看官方为我们提供的所有可用的 Chart 包。

注意:官方 Chart 被分为了两类:stable 和 incubator。顾名思义,stable 为稳定版,处于 stable 下的所有 Chart 可以被应用到生产环境中;而 incubator 中的 Chart 仍然处在积极的开发状态中,不推荐在生产环境中使用。而我们本文中所要使用的则是位于 stable 下的 Jenkins Chart 。

当我们要部署某个保存在 Chart 仓库中的 Chart 时,首先需要将该仓库通过 Helm 的 repo add 命令添加到本地仓库列表中,这样 Helm 才知道从何处下载该 Chart:

$ helm repo add stable https://kubernetes-charts.storage.googleapis.com/

"stable" has been added to your repositories

上面这条指令将官方 Stable Chart 库的地址添加到了本地,并为其命名为 stable,这样我们就可以使用 stable/CHART-NAME 格式引用并部署被存储到该仓库下的Chart 了,如我们接下来要使用的 stable/jenkins Chart。

当某个仓库被添加后,就可以在 repo list 命令列出的所有仓库列表中查看到刚刚添加的仓库信息:

$ helm repo list

NAME    URL
stable  https://kubernetes-charts.storage.googleapis.com/

也可以使用 repo update 命令更新当前已添加的 Chart 库:

$ helm repo update

Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈ Happy Helming!⎈

我们还可以通过 search repo 命令在当前配置的 Chart 仓库中查询某个 Chart :

$ helm search repo jenkins

NAME            CHART VERSION   APP VERSION DESCRIPTION
stable/jenkins  1.10.1          lts         Open source continuous integration server. It s...

该命令会在添加到当前系统中的所有 Chart 仓库中搜索名为 Jenkins 的 Chart,而从获取到的结果中我们可以看到,在我们刚刚添加的 stable 库中找到了 Jenkins Chart stable/jenkins,这正是我们接下来部署 Jenkins 时所需要用到的 Chart 。

创建 Jenkins namespace

为了能够更好地组织我们部署在 Kubernetes 中的应用程序,通常会将某个应用程序安装到自己独立的 namespace 下,Jenkins 也不例外,因此在部署 Jenkins 之前,首先创建一个专用的 namespace jenkins

$ kubectl create ns jenkins

如果你熟悉 Helm2,会了解到当使用 Helm2 部署应用程序到某个特定的 namespace 下时,若该 namespace 在当前 Kubernetes 不存在,Helm2 会自动为我们创建出该 namespace。而这一行为在 Helm3 中则发生了改变 ,当使用 Helm3 部署某个应用程序时,若指定的 namespace 在当前 Kubernetes 中不存在,则会报出 namespace 无法找到的错误信息,并终止部署流程。

持久化存储卷

默认情况下,我们所使用的 Jenkins Chart 会在部署过程中使用当前 Kubernetes 集群中默认的 StorageClass 创建一个大小为 8G 的 PVC 做为 JENKINS_HOME 的挂载卷,因此在部署前请确保你所在的 Kubernetes 集群存在这样一个默认的 StorageClass,并能够创建出符合条件的 PVC,否则你的部署流程会由于无法创建出对应的 PVC 而被卡住。可通过如下命令查看你的 Kubernetes 集群中是否存在默认的 StorageClass:

$ kubectl get sc

NAME                 PROVISIONER            AGE
standard (default)   kubernetes.io/gce-pd   1h

其中 StorageClass standard 后面的 (default) 说明了该 StorageClass 为当前系统默认的 StorageClass。

如果你使用的 Kubernetes 集群中没有默认的 StorageClass,或是没有足够的空间来创建出 8G 大小的 PVC,也不用担心,可以暂时在下面的部署命令中通过追加 --set persistence.enabled=false 参数来禁止该 Chart 创建任何 PVC,我们将在后面介绍 Jenkins Home 持久化存储时讲解如何使用其它方式创建出 PVC 来。

开始第一次部署

配置好 Helm 及创建好 namespace 之后,就可以使用 helm install 命令来部署 Jenkins Chart 了:

$ helm install jenkins stable/jenkins -n jenkins

或者如果你无法创建出 PVC,请使用下面命令代替:

# 或使用该命令来禁止在部署过程中创建任何 PVC
$ helm install jenkins stable/jenkins -n jenkins --set persistence.enabled=false

上面的 Helm 命令使用 stable/jenkins Chart 创建一个名为 jenkins 的部署,其中-n 参数指定来我们要部署的 namespace。

当命令执行成功后,你将会看到类似如下的输出信息:

NAME: jenkins
LAST DEPLOYED: Thu Apr  2 11:07:36 2020
NAMESPACE: jenkins
STATUS: deployed
REVISION: 1
NOTES:
1. Get your 'admin' user password by running:
  printf $(kubectl get secret --namespace jenkins jenkins -o jsonpath="{.data.jenkins-admin-password}" | base64 --decode);echo
2. Get the Jenkins URL to visit by running these commands in the same shell:
  export POD_NAME=$(kubectl get pods --namespace jenkins -l "app.kubernetes.io/component=jenkins-master" -l "app.kubernetes.io/instance=jenkins" -o jsonpath="{.items[0].metadata.name}")
  echo http://127.0.0.1:8080
  kubectl --namespace jenkins port-forward $POD_NAME 8080:8080

3. Login with the password from step 1 and the username: admin


For more information on running Jenkins on Kubernetes, visit:
https://cloud.google.com/solutions/jenkins-on-container-engine

通过上面的输出,我们可以得知以下几点信息:

  • 此次的 Helm 部署名为 jenkins,部署的 namespace 同样为 jenkins。
  • Jenkins 管理员登陆账号为 admin,且密码可以通过执行命令 printf $(kubectl get secret --namespace jenkins jenkins -o jsonpath="{.data.jenkins-admin-password}" | base64 --decode);echo获取到。
  • 当前部署的 Jenkins 实例并没有任何外部可直接访问的 IP 地址或 URL,而是需要通过 port forward 的方式将部署的 Jenkins Pod 的端口映射到本地的方式来访问它。
提示:我们也可以通过使用 Helm 的 status 查看当前某个部署的信息的方式,来获取以上信息:helm status jenkins -n jenkins

上面的输出仅仅代表了 Helm 命令返回成功,但这并不意味着 Jenkins 已经被成功部署到了 Kubernetes 平台上,该 Chart 所定义的 Kubernetes 资源可能仍然处在创建过程中,设置可能会出现创建失败的情况。如果你没有在部署的命令行中通过追加 --set persistence.enabled=false参数来禁止创建 PVC,则我们应当首先确认 PVC 是否被成功的创建出来:

$ kubectl -n jenkins get pvc

NAME      STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
jenkins   Bound    pvc-265e71bb-2b82-46d1-8b3c-d495407b1e6d   8Gi        RWO            standard       87s

当确认 PVC 被成功创建后,接下来让我们查看一下 Pod 的状态信息,如果你在 Helm 命令执行成功后马上查看 Pod 的状态信息,你可能会看到类似下面的信息:

$ kubectl -n jenkins get pods

NAME                       READY   STATUS     RESTARTS   AGE
jenkins-58d9d655df-cjw5h   0/1     Init:0/1   0          55s

Pod 状态为 Init:0/1,说明该 Pod 正在处于初始化阶段。Jenkins Chart 生成的 Deployment 资源为 Pod 定义了一个名为 copy-default-config 的 initContainers,用于执行初始化脚本 apply\_config.sh,该脚本的主要作用是将定义在 configmap 中的配置信息应用到我们最终部署的 Jenkins 实例中。同时,该脚本还会自动为我们安装定义的插件、创建自定义初始化 job 等等工作。

我们可以在查看 Pod 日志信息时通过 -c copy-default-config 参数来指定查看该 initContainers 的日志信息:

$ kubectl -n jenkins logs -f jenkins-58d9d655df-cjw5h -c copy-default-config

Creating initial locks...
Analyzing war /usr/share/jenkins/jenkins.war...
Registering preinstalled plugins...
Using version-specific update center: https://updates.jenkins.io/2.204...
Downloading plugins...
...
...
...
Cleaning up locks

Jenkins Chart 为我们创建的不仅仅是 Pod,同时还包括了所有其它的必要资源,如:service、persistentVolumeClaim、serviceAccount 等等,可通过 Helm 的 get manifest 命令将所部署的所有资源以 YAML 的格式打印出来:

$ helm get manifest jenkins -n jenkins

---
# Source: jenkins/templates/service-account.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins
  namespace: jenkins
  labels:
    "app.kubernetes.io/name": 'jenkins'
    "helm.sh/chart": "jenkins-1.10.1"
    "app.kubernetes.io/managed-by": "Helm"
    "app.kubernetes.io/instance": "jenkins"
    "app.kubernetes.io/component": "jenkins-master"
---
# Source: jenkins/templates/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: jenkins
  namespace: jenkins
... more output

访问 Jenkins 服务

等待 Pod 状态转换为 Running,且 Pod 内运行的 Jenkins 服务完全启动后,就可以访问我们的 Jenkins 实例了。

你可以通过观察 Pod 的日志输出,如果输出的最后包含了类似 INFO hudson.WebAppMain$3#run: Jenkins is fully up and running 的信息,则说明 Pod 内的 Jenkins 服务已经启动成功,并可以接收请求了。

但在访问之前,我们还需完成两件事:首先需要获取登陆用户 admin 的密码,该密码为随机生成,可通过 Helm 输出中给定的 print 命令获取:

# 获取 Admin 用户的登陆密码
printf $(kubectl get secret --namespace jenkins jenkins -o jsonpath="{.data.jenkins-admin-password}" | base64 --decode);echo

另一件需要完成的事是端口转发。如果你此时查看 jenkins namespace 的 SVC:

$ kubectl -n jenkins get svc

NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)     AGE
jenkins         ClusterIP   10.101.220.161   <none>        8080/TCP    2m
jenkins-agent   ClusterIP   10.97.93.57      <none>        50000/TCP   2m

Chart 为我们创建的 Jenkins 的 SVC 类型为 ClusterIP,而该类型的 SVC 只能在 Kubernetes 内部进行访问。因此,我们需要通过端口转发的方式,将该 Pod 端口直接映射到本地指定的端口上,在通过访问本地地址+该端口号的方式访问该 Pod:

# 获取 Pod 名
export POD_NAME=$(kubectl get pods --namespace jenkins -l "app.kubernetes.io/component=jenkins-master" -l "app.kubernetes.io/instance=jenkins" -o jsonpath="{.items[0].metadata.name}")

# 开启端口转发
kubectl --namespace jenkins port-forward $POD_NAME 8080:8080

上面命令表示将本地的 8080 端口映射到 Pod 的 8080 端口,执行成功后,你将会看到类似如下的输出结果:

Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080

此时说明已经成功开启了端口映射,在浏览器输入 http://127.0.0.1:8080 即可访问我们的 Jenkins 服务了。

至此,一个最基本的 Jenkins 实例就已经被成功地部署到了我们的 Kubernetes 集群中,并且可以开始为我们提供了。但就目前所部署的 Jenkins 实例来说,它还不适用于生产环境,至少我们还需要为其设定一个可以直接对外访问的 URL 地址,这通常是通过为创建的 service 资源的 Type 属性设定为 LoadBalancer 或是 NodePort,或是为其创建一个 Ingress 资源来实现的,接下来就让我们学习如果通过自定义的方式来部署我们所需的资源。

自定义部署

通过使用 Helm chart 的方式部署应用程序时, 带给我们的不仅仅是简单快速的部署方式,更多的是灵活的自定义性。

当使用 Chart 部署应用程序时,Helm 首先会解析定义在该 Chart 中的模版文件,使其生成对应的用于创建 Kubernetes 资源的 YAML 文件,并最终利用这些文件来创建出所有资源。Chart 模版文件使用 Go 语言实现,它支持 Go 语言模版引擎所提供的几乎全部功能,包括例如变量、表达式、循环、函数等等。

Chart 中定义的每个模版文件中几乎都定义了一系列变量,并且在 Chart 的 values.yaml 文件中为所有这些定义的变量设定了默认值,如 Jenkins Chart 的 values.yaml 文件。我们可以通过在部署 Chart 的过程中为这些变量赋予不同于默认值的其它合法值的方式,来创建含有特定设定的 Kubernetes 资源,以实现自定义部署的目的。

Helm 支持两种变量赋值的方式:在命令行中通过 --set 参数直接为某个变量进行赋值,如 --set foo=bar,将字符串 bar 赋值给变量 foo,当需要为多个变量设定值时,可以多次指定 set 参数来为每个变量赋值;另一种方式是像 Chart 中的 values 文件那样,创建自己的 values.yaml文件,并将所有想要重新赋值的变量及其新值写入到该文件中,并在部署时通过 -f 参数指定该文件,如: -f values.yaml

我们通常更倾向于采用第二种方式:将所有要自定义的变量和值保存到 values 文件中。这种方式不仅可以简化我们的命令行,因为无论有多少要重写的变量,最终在命令行都只需指定一个 -f 参数即可;同时,我们还可以将 values 文件保存到如 Git 这样的 SCM 工具中,在方便多人合作的同时,还可以对该文件的历史记录进行跟踪管理。

不同的 Helm Chart 可设定的自定义变量都不尽相同,在安装前需仔细阅读相关 Chart 文档,或直接查看其 Chart 的 values.yaml 文件。

接下来就让我们看一下如何通过为这些变量设定适当的值来部署一个生产环境可用的 Jenkins 服务。

创建工作目录

由于我们将要采用的是以文件的方式保存要设定的变量值,因此在开始之前首先使用如下命令创建出我们所需的工作目录及文件:

$ mkdir jenkins
$ cd jenkins
$ touch values.yaml

在接下来的讲解中,所有设定的变量都将被保存到 values.yaml 文件中。

创建可对外访问的服务

对于一个的 Jenkins 服务来说,我们更希望可以直接通过某个 URL 地址对其进行访问,而不是像前面那样通过端口转发的方式进行访问。

为能够直接访问 Jenkins 服务,我们可以将 Chart 创建的 service 资源的 type 属性设定为 NodePortLoadBalancer,或是通过创建 Ingress 资源使其 service 可对外访问。

而在实际的生产环境中,我们则更倾向于后两种方式:LoadBalancer 和 Ingress,接下来让我们分别看一下如何实现这两种设定。

LoadBalancer

如果你所使用的云厂商尝试支持 LoadBalancer,那么创建一个 LoadBalancer 类型的 service 是最简单,也是最值得推荐的方式。可以通过将变量 serviceType 的值直接设定为 LoadBalancer 即可:

master:
  serviceType: LoadBalancer

将上面的内容保存到本地的 values.yaml 文件中。注意,当为某个变量重新赋值时,要保证该变量对应的 key 值所处的结构不能发生改变。如这里的 serviceType key 在原 Chart 的 values.yaml 文件中属于 master 下的一个子属性,在我们自己所编写的 values.yaml 文件中要遵守同样的规则。

Ingress

若你不希望使用 LoadBalancer,或者你所在的 Kubernetes 平台不支持 LoadBalancer 功能,你也可以通过创建 Ingress 资源的方式让外界访问到你到 service:

注意,如果你使用的是 minikube,在继续下面到讲解之前请确保你已经开启了 ingress 插件,可通过命令 minikube addons list 查看 Ingress 插件是否被启用,如若没有启用,则执行命令 minikube addons enable ingress 来启用。
master:
  ingress:
    enabled: true
  serviceType: NodePort

ingress.enabled 默认值为 false,表示不会为我们创建任何 Ingress 资源,因此这里需要显示设定该值为 true 以确保让 Jenkins Chart 自动为我们创建出 Ingress 资源。

同时我们需要将 serviceType 属性指定为 NodePort 以便 Ingress 可以访问到我们到 service。如果你所使用到云平台厂商支持 LoadBalancer,则将 serviceType 设置为 LoadBalancer 仍然是你最好到选择。

除了 enabled 外,用于创建 Ingress 的 Chart 模版 jenkins-master-ingress.yaml 还支持许多其它参数来为生成的 Ingress 设定其它属性,如为 ingress 资源创建 Labels,Annotations,设定 hostname 以及 tls 等等,可通过查看 values 文件中的关于 ingress 设定的部分所有支持的变量,并根据自己的实际情况为这些变量设定相应的值。

重新部署你的 Jenkins 服务

到目前为止,我们创建了一个名为 values.yaml 的 YAML 文件,并包含了可以让你的 Jenkins 服务直接对外访问的基本配置,接下来,就让我们利用该文件来重新部署我们的 Jenkins 实例。

但在重新部署之前,首先让我们查看下当前 Kubernetes 中 Helm 的部署情况:

$ helm ls -n jenkins

NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
jenkins jenkins         1               2020-04-02 11:07:36.98936 +0800 CST     deployed        jenkins-1.10.1  lts

上面使用了 Helm 的 ls 命令列出了当前在 jenkins namespace 下的所有部署信息,可以看到已经存在了有一个名为 jenkins 的部署存在于我们的 Kubernetes 中,这正是我们之前使用默认值部署的 Jenkins 实例。

注意:Helm3 与 Helm2 另一个不同之处就是,在 Helm3 中,所有的部署都被绑定到了某个 namespace 下,因此,上面的命令需要通过 -n 参数指定我们要查看的 namespace。

而对于一个已经存在了的部署,要想对其进行重新部署,一般有两种选择:

  • 删除当前部署,之后在重新对其进行另一个新的部署;
  • 在当前部署的基础之上对其进行升级部署;

让我们分别看一下如何通过这两种方式重新部署我们的 Jenkins 服务。

1)重新创建

这种通过删除/创建的方式来对某个 Chart 进行重新部署,是非常不推荐的一种方式,在这里列出该方式的实现只是为了给大家做参考使用。

首先是使用 delete 命令删除某个特定的部署:

$ helm delete jenkins -n jenkins

release "jenkins" uninstalled

该命令将删除 jenkins namespace 下的名为 jenkins 的部署,在命令执行完成后,你将会看到上面的输出结果,提示我们的 jenkins 部署已经被成功删除。

你可以使用下面命令确认所有资源是否已经被删除:

$ kubectl -n jenkins get all
No resources found in jenkinss namespace.

之后在重新使用 install 命令进行新的部署:

$ helm install jenkins stable/jenkins -n jenkins -f values.yaml

在上面的命令中,通过 -f 参数将我们的 values.yaml 文件传递给了 Helm 命令,这样,Helm 就会从该文件中读取我们设定的变量值。

2)升级部署

升级部署是指对当前某个已经存在的部署直接做升级操作,这也是比较推荐的一种方式。通过该种方式对某个 Chart 做升级操作后,不仅可以保留升级的历史记录,同时还可以将我们升级的 Chart 做回滚操作,使其回退到之前的某个版本。

Helm 使用 upgrade 命令对某个部署进行升级:

$ helm upgrade jenkins stable/jenkins -n jenkins -f values-ing.yaml

该命令使用了我们创建的 values-ing.yaml 文件对当前的 jenkins 部署做了升级操作,升级完成后你将看到类似如下的输出结果:

Release "jenkins" has been upgraded. Happy Helming!
NAME: jenkins
LAST DEPLOYED: Thu Apr  2 13:27:58 2020
NAMESPACE: jenkins
STATUS: deployed
REVISION: 2
NOTES:
1. Get your 'admin' user password by running:
  printf $(kubectl get secret --namespace jenkins jenkins -o jsonpath="{.data.jenkins-admin-password}" | base64 --decode);echo
2. Get the Jenkins URL to visit by running these commands in the same shell:
  export NODE_PORT=$(kubectl get --namespace jenkins -o jsonpath="{.spec.ports[0].nodePort}" services jenkins)
  export NODE_IP=$(kubectl get nodes --namespace jenkins -o jsonpath="{.items[0].status.addresses[0].address}")
  echo http://$NODE_IP:$NODE_PORT/login

3. Login with the password from step 1 and the username: admin


For more information on running Jenkins on Kubernetes, visit:
https://cloud.google.com/solutions/jenkins-on-container-engine

与我们创建部署时返回的结果基本相似,除了第一句提示为 升级。

查看我们的 service 和 ingress 情况:

# 获取 service 信息
kubectl -n jenkins get svc
NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
jenkins         NodePort    10.101.220.161   <none>        8080:30450/TCP   140m
jenkins-agent   ClusterIP   10.97.93.57      <none>        50000/TCP        140m

# 获取 ingress 信息
$ kubectl -n jenkins get ing
NAME      CLASS    HOSTS   ADDRESS          PORTS   AGE
jenkins   <none>   *       192.168.99.101   80      35s

此时的 jenkins service 类型已经被更新为了 NodePort 类型,同时 Ingress 资源也被创建出来了,并且我们可以通过 Ingress 地址 192.168.99.101 来访问我们的 Jenkins 服务了。

使用 Helm 的 history 命令查看 Jenkins Chart 的所有升级历史记录:

$ helm history jenkins -n jenkins
REVISION        UPDATED                         STATUS          CHART           APP VERSION     DESCRIPTION
1               Thu Apr  2 11:07:36 2020        superseded      jenkins-1.10.1  lts             Install complete
2               Thu Apr  2 13:27:58 2020        deployed        jenkins-1.10.1  lts             Upgrade complete

什么的输出中,第二条记录即为我们本次升级的记录信息。

提示:无论是创建或者升级某个部署时,你都可以在命令行中指定 --dry-run 参数,该参数的作用是仅仅打印出 Chart 生成的 Kubernetes 资源定义信息,而不会做任何真正的部署,我们通常在部署之前通过该命令验证 Chart 生成的资源定义信息是否是我们所需要的。

Jenkins Home 持久化存储

Jenkins 的所有配置信息及 Job 信息都是以文件的方式存储在 JENKINS_HOME目录中的,在真正的生产环境中,我们通常需要将该目录挂载到 PVC 中做持久存储,这样做不仅可以保证数据在 pod 意外重启或删除后不会丢失;同时,这也是实现 Jenkins 可扩容性(同时支持多个 Jenkins Pod 运行)的必要前提。

默认情况下,Jenkins chart 会利用当前 Kubernetes 上的默认 StorageClass 创建一个大小为 8G 的 PVC,并自动将 JENKINS\_HOME 挂载到该 PVC 上。

指定其它 StorageClass

如果你当前所在的 Kubernetes 平台没有设定默认的 StorageClass,或是你希望使用其它非默认的 StorageClass 来为你创建 PVC,可以通过设定 persistence.storageClass key 来实现:

persistence:
  storageClass: MySC
注意:persistence 并没有位于 master 下。

上面的配置指定了在部署过程中,将会使用 MySC StorageClass 来创建 PVC。

使用现有 PVC

你也可以提前创建好自己的 PVC,并通过 existingClaim key 指定你的 PVC:

persistence:
  existingClaim: JENKINS_HOME_PVC

一旦为 existingClaim 设定了值,Chart 就不会在利用 StorageClass 创建任何 PVC,如果你所在的 Kubernetes 平台上没有任何可用的 StorageClass ,则可以采用该方式进行部署。

Chart 模版还支持一些其它用于设定 PVC 属性的变量,如设定 PVC 大小的 size 变量,设定 PVC 访问模式的 accessMode 变量等等,如下是我们当前所要使用的 PVC 配置,创建一个大小为 5G 的 PVC:

persistence:
  # 修改自动创建的 PVC 大小
  size: "5Gi"

关于更多支持的变量,请查看 values 文件中关于 persistence 获取完整可支持的变量。

有一点需要注意,一旦我们创建好一个 PVC 后,就不能在对其大小作出任何改变(虽然有些 PVC 可以对其进行扩容操作,这取决要 PVC 底层使用的存储系统是否对支持该功能),因此如果我们对 PVC 对大小作出了任何改变,应当先删除该 Helm 部署,之后在对其重新创建一个新对部署。删除某个 PVC,也意味着该 PVC 中存储的数据也都将丢失,因此在创建 PVC 之前尽量对其规划好,尽量避免后期对其进行更改。

禁用 PVC

如果基于某种原因,你不希望部署的 Jenkins 实例使用任何 PVC,你可以设定 persistence.enabled 的值为 false:

persistence:
  enabled: false

这样 Chart 就不会创建任何 PVC,也不会为我们的 Jenkins 挂载任何 PVC 资源。

预安装 Jenkins 插件

Jenkins 的成功可以说离不开它丰富而强大的插件,在每次初始化好一个 Jenkins 实例后,我们通常首要的任务就是安装各种我们所需的插件。

Jenkins Helm Chart 支持我们将需要安装的所有插件以列表的形式赋值给 installPlugins 变量,这样在 Jenkins Pod 初始化时会在 initContainers 中自动为我们安装这些插件:

master:
  installPlugins:
    - ldap
    - matrix-auth
    - authorize-project
    - ansicolor
    - jacoco
    - job-dsl
    - slack
    - checkstyle
    - kubernetes
    - openshift-client
    - workflow-job
    - workflow-aggregator
    - credentials-binding
    - git
    - github-branch-source
    - github-pullrequest
    - ghprb
注意:这里使用的是插件 ID,而不是插件的名字,插件 ID 可以在插件详情页面的 ID 字段中获取到。

除了像上面那样只写插件 ID 外,还可以在插件 ID 后追加要安装的插件的版本号,中间使用冒号分隔,如:kubernetes:1.24.1,来安装指定版本的插件。

当部署完成后,就可以在 Manage Jenkins -> Plugins Manager 页面的 Installed tab 下查看到这些已经被安装的插件:

image.png

Approval scripts

Jenkins 中的 Pipeline 实际上都是在 Jenkins 提供的 Groovy Sandbox 中运行的,而为了安全起见,Sandbox 会限制我们执行的 Groovy 脚本,只有少数一些被信任当 Groovy 方法才可以被执行。

但有些时候我们需要在 Pipeline 中执行一些特殊的 Groovy 方法,而在首次执行包含了没有被信任的 Groovy 方法的 Pipeline 时,Pipeline 会因报错而终止,并提示我们 Jenkins 的 Admin 用户需要将这些方法添加到 Jenkins 的信任列表以后才可以在 Pipelien 中调用他们。

我们可以将所有希望添加到信任列表中的 Groovy 方法以 list 的形式赋值给 scriptApproval 变量,这样当 Jenkins 部署好后,会将这些方法自动添加到信任列表中:

master:
  scriptApproval:
    - "method groovy.json.JsonSlurperClassic parseText java.lang.String"
    - "new groovy.json.JsonSlurperClassic"

使用该 scriptApproval 部署的 Jenkins 服务可以让我们直接在 Pipeline 中实例化 JsonSlurperClassic 类,以及调用该类的 parseText 方法。

通过 “Manage Jenkins” -> “In-process Script Approval” 页面可以查看所有 Approved scripts:

image.png

注意:该功能需要设定 enableXmlConfig 值为 true。

自动创建 Job

每当我们在 Jenkins 中创建好一个 Job 后,都会在 JENKINS_HOME/jobs/ 目录下生成出一个与该 Job 同名的目录,且该 Job 的配置信息将以 XML 的格式保存在了该目录下的 config.xml 文件中。

我们可以通过将这些 Job 的配置信息传递给 Jenkins Helm Chart 的 jobs 变量的方式,让 Chart 自动为我们在新部署的 Jenkins 中将这些 Job 创建出来:

master:
  jobs:
    HelloMyPipeline: |-
      <?xml version='1.1' encoding='UTF-8'?>
      <flow-definition plugin="workflow-job@2.38">
        <description></description>
        <keepDependencies>false</keepDependencies>
        <properties/>
        <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition" plugin="workflow-cps@2.80">
          <script>pipeline {
         agent any
         stages {
            stage(&apos;Hello&apos;) {
               steps {
                  echo &apos;Hello World&apos;
               }
            }
         }
      }
      </script>
          <sandbox>true</sandbox>
        </definition>
        <triggers/>
        <disabled>false</disabled>
      </flow-definition>

上面的配置会在部署好的 Jenkins 中创建一个名为 HelloMyPipeline 的 Pipeline, 其 Pipeline 脚本内容如下图所示:

image.png

注意:该功能需要设定 enableXmlConfig 值为 true。

其它常见配置

更改默认登陆名和密码

若没有为部署的 Jenkins 服务指定任何其它如 LDAP 等登陆方式时,Jenkins 将采用默认的用户名密码的登陆方式进行认证,并自动创建出一个登录名为 admin,密码为随机生成的管理员账号,我们可以通过 Helm Chart 生成的 secret 中获取经过 base64 转译后的该用户名和密码(secret 名与我们部署的 Helm 名同名,这里即为 jenkins),如:

$ kubectl -n jenkins get secret jenkins -o yaml
apiVersion: v1
data:
  jenkins-admin-password: clBEWVBtZDg2eg==
  jenkins-admin-user: YWRtaW4=
kind: Secret
metadata:
... more data

其中 jenkins-admin-userjenkins-admin-password 分别保存了经过 base64 转译后的值,我们可以通过 base64 -d 命令获取其转译前真正的值:echo "YWRtaW4=" | base64 -d -

我们也可以通过为 adminUseradminPassword 设定值的方式来指定我们想要的登陆账号和密码,如 :

master:
  adminUser: sysadmin
  adminPassword: sysadmin

上面的设定将登录名和密码同时修改成了 sysadmin

Pod resources

Jenkins Helm Chart 为创建的 Pod 设定了如下默认的资源限制:

master:
  resources:
    requests:
      cpu: "50m"
      memory: "256Mi"
    limits:
      cpu: "2000m"
      memory: "4096Mi"

因此我们可以通过修改这些值来修改 Pod 对资源限制的需求。

Pod Probes

为了保证 Jenkins 服务的健壮性,Helm Chart 通过以下变量为 Jenkins Pod 分别设定了 Readiness 和 Liveness:

master:
  healthProbes: true
  healthProbesLivenessTimeout: 5
  healthProbesReadinessTimeout: 5
  healthProbeLivenessPeriodSeconds: 10
  healthProbeReadinessPeriodSeconds: 10
  healthProbeLivenessFailureThreshold: 5
  healthProbeReadinessFailureThreshold: 3
  healthProbeLivenessInitialDelay: 90
  healthProbeReadinessInitialDelay: 60

你可以通过设定 healthProbes 的值设定为 false 来关闭 livenessProbereadinessProbe,当然并不推荐在生产环境中这样做。

完整的 values 文件

至此,我们已经对大部分我们需要的变量进行了重新赋值,来满足我们部署的要求, 现在是时候看一下完整的 Chart 中的 values.yaml 文件了:

persistence:
  # 修改自动创建的 PVC 大小
  size: "5Gi"
master:
  serviceType: NodePort
  # 启用 Ingresss 资源
  ingress:
    enabled: true
  # 设定要安装的插件
  installPlugins:
    - ldap
    - matrix-auth
    - authorize-project
    - ansicolor
    - jacoco
    - job-dsl
    - slack
    - checkstyle
    - kubernetes
    - openshift-client
    - workflow-job
    - workflow-aggregator
    - credentials-binding
    - git
    - github-branch-source
    - github-pullrequest
    - ghprb
  # 修改 Pod 资源限制
  resources:
    requests:
      cpu: "50m"
      memory: "256Mi"
    limits:
      cpu: "2000m"
      memory: "4096Mi"
  # 添加要加入到信任列表中的 Groovy 方法
  scriptApproval:
    - "method groovy.json.JsonSlurperClassic parseText java.lang.String"
    - "new groovy.json.JsonSlurperClassic"

如果你足够细心,会发现这里并没有 Job 的定义信息。这里我们选择将 Job 的配置信息放到一个独立的文件中,将 Job 从主配置文件中分离出来,不仅简化了主配置信息,而且我们可以编写多个 Job 配置文件,在为不同环境部署 Jenkins 服务时来部署针对该环境下的特定的 Job。将 Job 配置单独存放在 jobs.yaml 中:

master:
  # 需要自动创建的初始化 Job
  jobs:
    HelloMyPipeline: |-
      <?xml version='1.1' encoding='UTF-8'?>
      <flow-definition plugin="workflow-job@2.38">
        <description></description>
        <keepDependencies>false</keepDependencies>
        <properties/>
        <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition" plugin="workflow-cps@2.80">
          <script>pipeline {
         agent any

         stages {
            stage(&apos;Hello&apos;) {
               steps {
                  echo &apos;Hello World&apos;
               }
            }
         }
      }
      </script>
          <sandbox>true</sandbox>
        </definition>
        <triggers/>
        <disabled>false</disabled>
      </flow-definition>

再次使用 upgrade 对我们的 Jenkins 进行升级部署:

$ helm upgrade jenkins stable/jenkins -n jenkins -f values.yaml -f jobs.yaml

在上面的命令中,我们通过指定的第二个 -f 参数将 jobs.yaml 中定义的内容传递给 Helm,以便帮助我们创建出我们所需的 Job。由于本次我们对生成对 Pod 作出了改动,因此在升级对过程中 Pod 会被重新创建:

kubectl -n jenkins get pods
NAME                      READY   STATUS     RESTARTS   AGE
jenkins-ddd7fb47f-blv7m   0/1     Init:0/1   0          35s

等待升级完成,新创建的 Pod 启动成功后,我们的 Jenkins 实例就被部署成功了,再次通过 Ingress 地址或是 LoadBalancer 地址即可访问刚刚升级后的 Jenkins 服务了。

至此,我们成功地部署了一个经过定制化的 Jenkins 实例到我们的 Kubernetes 平台。虽然部署好后的 Jenkins 会自动为我们安装所需插件,创建初始化 job 等一些工作,但是在可以正式使用之前,仍然需要对我们的 Jenkins 服务进行许多手动配置,在下一篇文章中,我们将介绍如何使用 Jenkins 的 Configuration as Code 插件实现将所有的配置保存到我们的 values.yaml 文件中,最终达到 Jenkins 零配置的目的。

来源:DevSecOps SIG

阅读 148
22 声望
5 粉丝
0 条评论
你知道吗?

22 声望
5 粉丝
宣传栏