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

✨提示输入密码的时候,输入密码。

将 cert.pfx 传输到 jenkins 凭据中

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

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

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. 安装 https://plugins.jenkins.io/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)

添加包含docker服务容器

这种场景常用来在【agent pod】中将【代码打包容器】生成的程序通过【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 变量.
		                }
		            }
		        }
		    }
		}
    }
}

添加包含kubectl命令的容器

这种场景通常用来在【agent pod】中的【kubectl】容器执行 kubernetes 清单到kubernetes。

✨关于kubectl的容器,可以选用 kubesphere/kubectl 镜像.

创建kubectl命令所需的kubeconfig凭证

凭证类型:secret file

凭证文件:拥有创建 kubernetes 清单的权限

pipeline

pipeline{
    agent{
        kubernetes{
            //label "jenkins-agent" // pod 模板标签
            //cloud 'kubernetes'  // 插件配置名
            inheritFrom "kubectl" // 从标签为 kubectl 的 pod 模板继承
        }
    }

    stages {
		stage("git") {
			steps {
				git branch: 'master', url: 'https://github.com/abc-deployment.git' //从https://github.com/abc-deployment.git的master分支克隆代码
			}
		}
		stage('deploy') {
			steps {
				container('kubectl') {
                    withKubeConfig([credentialsId: 'k8s-cluster-admin-kubeconfig']){ // 这个指令基于 Kubernetes CLI 插件,k8s-cluster-admin-kubeconfig 是 secret file 类型凭据,加载 kubeconfig
                        sh 'kubectl apply -f kubernetes/'
                    }
				}
			}
		}
    }
}

出错

agent pod一直启动中

节点日志出现,jenkins jnlp Waiting for agent to connect,这个表示jenkins一直在等待jnlp链接。常见于 jnlp 没有设定 agent 启动命令或者 agent 无法链接到 jenkins master。

jenkins 工程中显示 kubectl 指令卡住

检查 withKubeConfig([credentialsId: ‘k8s-cluster-admin-kubeconfig’]) 凭证加载的 kubeconfig 是否可以正常执行清单。