在 Kubernetes 上动态创建 Jenkins Slave

2023年 1月 4日 44.2k 0

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启动代理】。保存之后,点击查看代理,可以看到启动节点命令:

1
java -jar agent.jar -jnlpUrl http://dev.chenshaowen.com:8080/computer/jnlp%E6%A8%A1%E5%BC%8F/slave-agent.jnlp -secret 8c7f2a83ea2f29ab9e5427d137c324b8102dfab18f8750229e36e34839a9e9c8 -workDir "/data"

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。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins
  namespace: default

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: jenkins-rolebinding
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: admin
subjects:
  - kind: ServiceAccount
    name: jenkins
    namespace: default

创建 ServiceAccount

1
kubectl apply -f jenkins-rbac.yml

获取访问 token

1
kubectl describe  secret

忽略 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 语法,进行构建
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
podTemplate(containers: [
    containerTemplate(name: 'maven', image: 'maven:3.3.9-jdk-8-alpine', ttyEnabled: true, command: 'cat'),
    containerTemplate(name: 'golang', image: 'golang:1.8.0', ttyEnabled: true, command: 'cat')
  ]) {

    node(POD_LABEL) {
        stage('Maven project') {
            container('maven') {
                stage('Maven test') {
                    sh 'mvn -version'
                }
            }
        }
        stage('Golang project') {
            container('golang') {
                stage('Go test') {
                    sh 'go version'
                }
            }
        }

    }
}

执行日志:

  • 使用声明式语法,直接提供 Yaml 内容。也可以提供文件路径,指向 Yaml 文件。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
pipeline {
  agent {
    kubernetes {
      yaml """
apiVersion: v1
kind: Pod
metadata:
  labels:
    mylabel: myvalue
spec:
  containers:
  - name: maven
    image: maven:3.3.9-jdk-8-alpine
    command:
    - cat
    tty: true
  - name: go
    image: golang:1.8.0
    command:
    - cat
    tty: true
"""
    }
  }
  stages {
    stage('Run maven') {
      steps {
        container('maven') {
          sh 'mvn -version'
        }
        container('go') {
          sh 'go version'
        }
      }
    }
  }
}

执行日志:

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 创建模板任务

  • 创建一个流水线任务
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
pipeline {
  agent {
    node {
      label 'python'
    }
  }
  stages {
    stage('Run Test') {
      steps {
        container('python') {
          sh 'python --version'
        }
      }
    }
  }
}

执行日志:

  • 创建一个自由风格任务

构建中,选择【执行 shell】,填入如下内容:

1
2
uname -a
python  --version

执行日志:可以看到 python --version 执行失败了。这是因为默认容器为 jnlp,而 jenkins/jnlp-slave:3.35-5-alpine 镜像中没有打包 python。这也意味着,这里仅能执行 jnlp 提供的命令。

5. 参考

  • https://github.com/jenkinsci/kubernetes-plugin/blob/master/README_zh.md

相关文章

KubeSphere 部署向量数据库 Milvus 实战指南
探索 Kubernetes 持久化存储之 Longhorn 初窥门径
征服 Docker 镜像访问限制!KubeSphere v3.4.1 成功部署全攻略
那些年在 Terraform 上吃到的糖和踩过的坑
无需 Kubernetes 测试 Kubernetes 网络实现
Kubernetes v1.31 中的移除和主要变更

发布评论