jenkins☞基于k8s生成动态的Jenkins agent

阅读量: zyh 2021-01-16 20:07:13
Categories: > Tags:

流程

jenkins-master -> pipeline -> kubernetes 插件 -> kubernetes 集群 -> jenkins-agent【k8s pod】 -> jenkins-master

凭证信息

jenkins-master 通过凭证来动态的创建agent pod执行自动化工程。

jenkins master位于kubernetes外

通过 .kube/config 里的加密信息获取凭据所需的证书信息

certificate-authority-data=
client-certificate-data=
client-key-data=
echo "${certificate-authority-data}" | base64 -d > ca.crt
echo "${client-certificate-data}" | base64 -d > client.crt
echo "${client-key-data}" | base64 -d > client.key
openssl pkcs12 -export -out cert.pfx -inkey client.key -in client.crt -certfile ca.crt

将 pkcs12 传输到 jenkins 凭据中

![image-20210116152344875](jenkins☞基于k8s生成动态的Jenkins agent/image-20210116152344875.png)

💁你需要在凭据【密码】位置中输入你生成 pkcs12 证书时输入的密码.

jenkins master 位于kubernetes内

  1. 创建SA-RABC
apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins-admin
  namespace: jenkins

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: jenkins-admin
rules:
  - apiGroups: ["extensions", "apps"]
    resources: ["deployments"]
    verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
  - apiGroups: [""]
    resources: ["services"]
    verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create","delete","get","list","patch","update","watch"]
  - apiGroups: [""]
    resources: ["pods/exec"]
    verbs: ["create","delete","get","list","patch","update","watch"]
  - apiGroups: [""]
    resources: ["pods/log"]
    verbs: ["get","list","watch"]
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: jenkins-admin
roleRef:
  kind: ClusterRole
  name: jenkins-admin
  apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
  name: jenkins-admin
  namespace: jenkins
  1. jenkins-master调用sa
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jenkins
  namespace: jenkins
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jenkins
  template:
    metadata:
      labels:
        app: jenkins
    spec:
      terminationGracePeriodSeconds: 10
      serviceAccount: jenkins-admin
      ......

安装配置 kubernetes 插件

  1. 安装 kubernetes 插件

  2. 添加一个新的插件配置

jenkins master 位于集群外

![image-20210116152033813](jenkins☞基于k8s生成动态的Jenkins agent/image-20210116152033813.png)

jenkins master 位于集群内

只需填写如下信息

名称:kubernetes
kubernetes 地址:https://kubernetes.default.svc.cluster.local
Jenkins 地址: http://jenkins.jenkins.svc.cluster.local:8080
Jenkins 通道: jenkins-jnlp.jenkins.svc.cluster.local:50000

✨默认情况下,kubernetes集群会在default命名空间内,创建一个kubernetes的svc。

✨Jenkins地址根据实际的svc配置填写。

最后,确保插件配置【连接测试】显示成功连接

配置 agent pod 模板

概念

关于 agent 的 pod 模板,默认插件已经提供了一个,只不过你看不到。因此哪怕你什么都不做,你 pipeline stages 也会在默认的 agent pod 里执行。

我们一般选择给 agent pod 添加一个额外的容器,这个额外的容器里包含了我们执行 stages 所需要的环境,例如叫 jenkinstools。

即最终,整个pod包含多个容器,分别是沟通 jenkins-master 的 JNLP 容器和执行我们工作流步骤的 jenkinstools 容器。

需要注意的是:

多容器 pod 模板有诸多的硬性限制:

Multiple containers can be defined in a pod. One of them is automatically created with name jnlp, and runs the Jenkins JNLP agent service, with args ${computer.jnlpmac} ${computer.name}, and will be the container acting as Jenkins agent.

Other containers must run a long running process, so the container does not exit. If the default entrypoint or command just runs something and exit then it should be overridden with something like cat with ttyEnabled: true.

WARNING If you want to provide your own Docker image for the JNLP agent, you must name the container jnlp so it overrides the default one. Failing to do so will result in two agents trying to concurrently connect to the master.

  1. jnlp agent 容器会自动创建,哪怕你没有定义。
  2. 如果你自定义了 JNLP 服务所在的容器,那么负责连接 master 的 jnlp agent 容器名必须叫 jnlp。💥
  3. 工作容器(jenkinstools),也就是我们用来执行 stages 的容器,必须运行一个持久的程序,以便于容器不会自动退出。例如 cat 命令并附加一个伪终端。
  4. stages 阶段执行的时候,必须明确的指定工作容器(jenkinstools),否则会默认在 jnlp 容器里执行. (这个不是很确定,但是我测试是这样)

设置基本Pod模板

下图是我的 pod 模板配置(部分截图)

![image-20210126182630608](jenkins☞基于k8s生成动态的Jenkins agent/image-20210126182630608.png)

✨jenkinstools 即实际执行自动化工作的容器,通常可能是代码打包容器。

例如:若为go程序,则这里设置为go环境容器,若为java程序,则这里设置为maven环境容器等。

优化模板里的容器

假设我们的程序是java,则对应的代码打包容器镜像可能是 maven:3.6-openjdk-11-slim。而 maven 在构建 jar 包的时候,通常会下载依赖包到 /root/.m2 (通过root执行)。我们为了避免每次所需的依赖文件,则可以通过构建一个多读写卷 nfs pvc 挂载到 /root/.m2。

✨这里的挂载路径会挂载到所有的容器中。

![image-20220327104422310](jenkins☞基于k8s生成动态的Jenkins agent/image-20220327104422310.png)

在Agent Pod中添加提供Docker服务的容器

这种场景常用来将【代码打包容器】生成的程序通过docker打包成镜像并推送到镜像注册服务。

✨如果要在Pipeline中基于声明使用docker命令,则依赖docker和docker pipeline插件。

✨通过和代码打包容器保持相同的工作目录 /home/jenkins/agent,从而可以让docker容器查找到代码里的Dockerfile文件。

新加一个Pod模板:dockerbuild,只需要写名称,其它不用写。

✨如果存在【代码打包容器】的Pod模板,则在父级的 Pod 模板名称栏里添加。从而将【代码打包容器】加到 dockerbuild Pod 模板中。

![image-20220328180135103](jenkins☞基于k8s生成动态的Jenkins agent/image-20220328180135103.png)

将下列Pod配置复制到Raw YAML for the Pod,并将Yaml merge strategy设置为Merge

apiVersion: "v1"
kind: "Pod"
spec:
  hostAliases:
  - ip: "10.0.0.10"
    hostnames:
    - "gitlab.xxx.com"
  containers:
  - args:
    - "--host=unix:///var/run/docker.sock"
    - "--host=tcp://0.0.0.0:8000"
    command:
    - "dockerd"
    image: "docker:stable-dind"
    imagePullPolicy: "IfNotPresent"
    name: "docker"
    resources:
      limits: {}
      requests: {}
    securityContext:
      privileged: true
    tty: false
    volumeMounts:
    - mountPath: "/home/jenkins/agent"
      name: "workspace-volume"
      readOnly: false
    workingDir: "/home/jenkins/agent"
  hostNetwork: false
  nodeSelector:
    kubernetes.io/os: "linux"
  restartPolicy: "Never"
  volumes:
  - emptyDir:
      medium: ""
    name: "workspace-volume"

pipeline的调用示例

pipeline{
	environment {
		appName = "server" // 程序名,亦是镜像仓库名
		appVersion = "v0.1.0" // 程序版本,亦是镜像标签
		appGit = "http://gitgg.xxx.com/crm/xxx.git"
		registryCredential = "harbor-jenkins-rw" // 镜像注册表登录凭据名,需提前在jenkins凭据功能里添加
		registryUrl = "https://harbor.xxx.com/" // 镜像注册表地址
		registryProject = "crm" // 项目名/命名空间
		dockerImage = "" // 空的环境变量名
	}
    agent{
        kubernetes{
            inheritFrom "dockerbuild" // Agent Pod模板名
    }

    stages {
		stage("code: pull") {
			steps {
				git credentialsId: 'gitgg-apps-ro', branch: 'master', url: appGit
			}
		}
        stage('docker: build') {
			steps {
				container('docker') {
					script {
						dockerImage = docker.build( registryRepository + "/" + appName + ":" +appVersion, "docker")  // build方法接收两个参数,第一个是镜像tag,第二个是dockerfile在代码里的相对目录
					}
				}
			}
		}
		stage('docker: push') {
		    steps {
		        container('docker') {
		            script {
		                docker.withRegistry( registryUrl, registryCredential ) { // 登录注册表
		                    dockerImage.push() // dockerImage 即上一阶段的 dockerImage 变量.
		                }
		            }
		        }
		    }
		}
    }
}