1. Jenkins 的工作模式
Jenkins 是一个单 Master,多 Slave 架构。Master 负责分配任务、管理服务。 Slave 负责执行具体任务。即使部署了多个 Master,这些 Master 之间依然相互独立,无法协同调度。在高可用的 Jenkins 方案中,需要借助外部的任务分发框架,协调多 Master 之间的调度,比如,gearman。在每个 Master 节点上,安装 gearman 插件,连接到 gearman server。在 gearman server 的统一分发下,各个 Master 才能构成高可用的 Jenkins 应用。那么,Master 和 Slave 之间是如何通信的呢?主要有两种方式:ssh,jnlp。
- ssh 模式
将 Master 的 SSH 公钥配置到所有的 Slave 上。当有任务调度时,由 Master 使用 ssh 客户端进行远程通信,启动 Agent 执行任务。在 【系统管理】->【节点管理】->【新建节点】页面中,启动方式选择 【Launch agent agents via SSH】,填入主机相关信息,保存即可。
- jnlp 模式
在 Slave 上需要常驻一个守护进程 jnlp ,使用 HTTP 协议与 Master 进行通信。每个 jnlp 都需要配置一个单独的 secret。在 【系统管理】->【节点管理】->【新建节点】页面中,启动方式选择 【通过Jave Web启动代理】。保存之后,点击查看代理,可以看到启动节点命令:
|
|
2. 安装 Kuernetes 和 Jenkins
安装不是本篇主要内容,这里主要提供相关文档或脚本,以保证内容连贯。
2.1 Kubernetes
提供两种安装方式:
- KubeSpray
实际上 KubeSpray 也是使用 Kubeadm 进行安装,但 KubeSpray 提供了各种相关组件的集成。
- Kubeadm
参考文档:使用 Kubeadm 安装 Kubernetes 集群
2.2 Jenkins
Jenkins 非常友好地提供了一个完整 war 包,有三种方式部署 Jenkins。
- 直接在 Java 环境,运行 war 包
- 使用 Docker Compose 运行
- 在 Kubernetes 中部署 Jenkins,使用 yaml 文件或者 helm 包
这里,我使用的是 Docker Compose 的方式运行 Jenkins。其他方式类似,注意开放相关访问端口即可。
3. 在 Kubernetes 动态创建 Slave
3.1 创建 ServiceAccount
为了使 Jenkins 有权限访问 Kubernetes 集群,这里我们需要创建一个 ServiceAccount。在 Kubernetes 集群主机上,创建文件 jenkins-rbac.yml。为了省略创建命名空间的步骤,这里 namespace 使用 default。
|
|
创建 ServiceAccount
|
|
获取访问 token
|
|
忽略 default-token-xxx,获取 jenkins-token-xxx 中的 token 值:
Name: jenkins-token-6vn2v
Namespace: default
Labels: <none>
Annotations: kubernetes.io/service-account.name: jenkins
kubernetes.io/service-account.uid: e1c181ae-ac1a-4ca8-b696-c06087b8c87c
Type: kubernetes.io/service-account-token
Data
====
token:[这里的值,需要配置在 Jenkins 中]
ca.crt: 1025 bytes
namespace: 7 bytes
3.2 安装插件 Kubernetes Plugin
在【系统管理】->【插件管理】->【可选插件】中,搜索 Kubernetes ,找到插件 Kubernetes plugin,安装并重启 Jenkins 即可。下图中已经安装插件,如果待安装,请选择【可选插件】Tab。
3.3 插件配置
在【系统管理】->【系统配置】中,滚动到页面底部,找到 cloud,新增加一个云。Kubernetes 地址指的是 Apiserver 地址,Jenkins 地址指的是 Jenkins 页面的访问地址,Jenkins 通道指的是 jnlp 与 Jenkins 通信的地址,默认是 Master 的 50000 端口地址。在【凭据】中添加 Secret text 类型的凭据,内容填写上面获取的 token 值。凭证选择【jenkins-token】后,点击【连接测试】,提示 Connection test successful 则表示配置成功。最终配置参数如下图:
3.4 创建任务
创建流水线类型的任务,将以下脚本粘贴在流水线的编辑框内,保存执行。
- 使用 podTemplate 语法,进行构建
|
|
执行日志:
- 使用声明式语法,直接提供 Yaml 内容。也可以提供文件路径,指向 Yaml 文件。
|
|
执行日志:
4. 使用模板创建 Slave
Kubernetes Plugin 提供的 podTemplate 十分的灵活,能够为 Jenkins 提供各种各样的工作节点。但是,如果每个流水线都需要这样配置模板,反而会变得十分繁琐。在 Kubernetes Plugin 中提供了内置的 podTemplate,当流水线配置的 agent label 与之匹配时,就可以直接用于 Pod 创建。
4.1 配置 PodTemplate
在【系统管理】->【系统配置】中,滚动到页面底部,找到 Pod Template,增加一个模板,填入相关信息。这里需要特别注意的是标签列表值。在流水线中,可以通过标签列表选中当前 Pod Template。如上图,如果仅增加 Python 容器,在流水线执行时,会一直等待调度。
Started by user admin
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline
[Pipeline] node
Still waiting to schedule task
‘test-nqppr’ is offline
查看 Jenkins 的系统全局日志,可以发现:
Failed to send back a reply to the request [email protected]: hudson.remoting.ChannelClosedException: Channel "[email protected]:JNLP4-connect connection from 172.17.0.1/172.17.0.1:56788": channel is already closed
意思是 Pod 中没有 jnlp 与之通信。因此,除了运行时容器,还需要添加一个名为 jnlp 的容器用于与 Jenkins 通信。如下图:最终的效果是,一个 Pod 模板,包含一个或多个指定的运行时容器,加一个 jnlp 容器。如果没有填写 jnlp 容器,会自动创建 jnlp 容器,但有时会报错无法连接(可能是 Jenkins 的 Bug),所以建议显示指定一个 jnlp 容器。同时,jnlp 是默认容器,也就是没有被 container 包裹的运行环境都是 jnlp。为了方便,另外一种方案,就是定制 jnlp 。【非必须】如果需要 Pod 中的容器能够共享宿主机上的 Docker,也就是 docker in docker,可以在【卷】中添加一个 【Host Path Volume】,将所主机上的 /var/run/docker.sock
挂载到容器的 /var/run/docker.sock
上,如下图。
4.2 创建模板任务
- 创建一个流水线任务
|
|
执行日志:
- 创建一个自由风格任务
构建中,选择【执行 shell】,填入如下内容:
|
|
执行日志:可以看到 python --version
执行失败了。这是因为默认容器为 jnlp,而 jenkins/jnlp-slave:3.35-5-alpine 镜像中没有打包 python。这也意味着,这里仅能执行 jnlp 提供的命令。
5. 参考
- https://github.com/jenkinsci/kubernetes-plugin/blob/master/README_zh.md