learn-tech/专栏/Jenkins持续交付和持续部署/14.搞定不同环境下的Jenkins与Kubernetes集群连接配置.md
2024-10-16 06:37:41 +08:00

17 KiB
Raw Blame History

                        因收到Google相关通知网站将会择期关闭。相关通知内容
                        
                        
                        14.搞定不同环境下的Jenkins与Kubernetes集群连接配置
                        在前面的章节中简单的介绍了使用Jenkins与Docker集成的一些基本语法以及实现持续交付和持续部署的配置。在Jenkins中除了可以与docker集成外还可以与容器编排工具kubernetes集成。

Jenkins使用kubernetes插件主要用于完成两方面的工作一是用于在kubernetes集群内动态生成一个pod作为Jenkins 的slave节点提供流水线执行的工作环境二是用于将应用代码持续部署到kubernetes集群中。

基于上面提到的这两方面的用途在剩下的章节会详细介绍Jenkins与kubernetes的集成配置与使用方法。

同docker pipeline一样在Jenkins里集成kubernetes也是依赖于插件的所以在介绍如何配置与使用jenkins集成kubernetes之前需要先安装插件

Kubernetes plugin Kubernetes CLI Kubernetes Continous Deploy

上面列出的插件第一个用于在kubernetes集群中动态生成jenkins slave节点后两个插件用于通过不同的方式持续部署代码到kubernetes集群。无论哪种插件使用前都要先保证jenkins能够连接到kubernetes集群所以本章节就从不同环境下的jenkins连接kubernetes集群的配置说起。

配置Jenkins连接Kubernetes

使用docker pipeline做持续交付的时候默认直接使用的宿主机的docker进程。而Jenkins与kubernetes的集成主要是通过调用Kubernetes的API去kubernetes集群中进行工作的。大多数公司在安装kubernetes集群配置apiserver服务时使用了证书所以在配置jenkins连接kubernetes集群时需要根据kubernetes的配置文件生成一系列证书以及key并将证书上传到Jenkins用来对apiserver进行认证。

下面针对部署在kubernetes集群环境下的Jenkins和非kubernetes集群环境下的jenkins连接kubernetes集群的配置进行详细介绍。

部署在非kubernetes集群内Jenkins连接kubernetes配置

安装好插件以后进入Jenkins首页点击菜单”Manage Jenkins(系统管理)-> Configure System(系统设置)” 在跳转到的界面中,到最底部,点击” Add a new cloud新建一个云> kubernetes“。

如下所示:

其中:

名称这里用于填写要添加的这个云cloud的名称默认为”kubernetes”如果不想用这个可以自定义。在编写pipeline的时候会用到。

kubernetes 地址用于填写kubernetes集群的地址做了多master集群高可用的环境直接写vip地址加端口只有单个master的环境直接写master加端口地址即可。

Kubernetes 服务证书 key用于填写与kubernetes集群认证的证书内容。

Kubernetes 命名空间用于填写调用kubernetes时生成的pod工作的namespace。

Credentials凭据用于连接kubernetes的凭证。

Jenkins 地址Jenkins的连接地址。

了解了基本配置的参数说明下面主要说一下”Kubernetes 服务证书 key“和Credentials凭据配置。

kubernetes集群安装的时候生成了一系列证书以及key并且在配置kubernetes中kubectl客户端命令权限的时候根据这些证书以及key生成了一个kubeconfig文件用于kubectl与集群通信这个文件默认为/root/.kube/config文件对集群有最高操作权限如果给了cluster-admin权限

Jenkins需要根据这个文件生成的证书与集群通信所以我们在生产环境配置Jenkins连接kubernetes集群的时候需要注意一下kubeconfig文件绑定的用户的权限最好从新生成一个低权限的kubeconfig文件而不要用kubectl命令使用的文件。

我这里为了测试方便就先使用kubectl命令使用的kubeconfig文件至于如何生成低权限的kubeconfig文件这里不做介绍有兴趣的可以私聊我。

配置证书key

首先看一下config文件。

[root@k8s_master1 ~]# cat /root/.kube/config apiVersion: v1 clusters:

  • cluster: certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUR2akNDQXFhZ0F3SUJBZ0lVT0g4cDd6QXZaR3p4cGxUVy9xe...... WRnVRNm9IcjZ0Yk0wa1NJVkhvN2JNQjRWOGZoUWk4WjlLS243ZTFsQWdaVWhyWGMzTzRqVS9xVHRWRQpSRXc9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K server: https://192.168.176.156:6443 name: kubernetes contexts:
  • context: cluster: kubernetes user: admin name: kubernetes current-context: kubernetes kind: Config preferences: {} users:
  • name: admin user: client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQzVENDQXNXZ0F3SUJBZ0lVQmI1ZTJFaUk1WndSY1JVeFVyZ...... Qk9DTzRBcEVzWXNOa084UVF2RTlwVEhpNlE0LzhLV0NtU0wyNgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBeDdjNkpRTFdQaC90REtjUDQrcDV5aCtWdWRCUmFacFJ2THM2MU1POGFZWFRCT09pCkJyZzFQb0laRzZEbXNBNnUyUStOVnlGOWg5RTQ3VVpFNjI5ND...... K3dGdUF2S29vRm9lRFZCS3I3NjdiTFA3ZTBibkVqS1ExRmNHVFFFQVU9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==

获取文件中certificate-authority-data的内容并转化成base64 encoded文件

echo LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUR2akNDQXFhZ0F3SUJBZ0lVT0g4cDd6QXZaR3p4cGxUVy9xejRWRmxLQllRd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1pURUxNQWtHQTFVRUJoTUNRMDR4RURBT0JnTlZCQWdUQjBKbG...... WRnVRNm9IcjZ0Yk0wa1NJVkhvN2JNQjRWOGZoUWk4WjlLS243ZTFsQWdaVWhyWGMzTzRqVS9xVHRWRQpSRXc9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K |base64 -d > ca.crt

将ca.crt的内容粘贴到kubernetes server certificate key(Kubernetes 服务证书 key)框里,如下图所示:

配置凭据

获取/root/.kube/config文件中client-certificate-data和client-key-data的内容并转化成base64 encoded文件

echo LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQzVENDQXNXZ0F3SUJBZ0lVQmI1ZTJFaUk1WndSY1JVeFVyZ...... Qk9DTzRBcEVzWXNOa084UVF2RTlwVEhpNlE0LzhLV0NtU0wyNgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== |base64 -d >client.crt

//生成key echo LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBeDdjNkpRTFdQaC90REtjUDQrc...... K3dGdUF2S29vRm9lRFZCS3I3NjdiTFA3ZTBibkVqS1ExRmNHVFFFQVU9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== | base64 -d >client.key

生成Client P12认证文件cert.pfx并下载至本地

openssl pkcs12 -export -out cert.pfx -inkey client.key -in client.crt -certfile ca.crt Enter Export Password:            //密码自定义jenkins导入证书的时候需要用到 Verifying - Enter Export Password:

在jenkins凭据菜单添加凭据

如上图所指示凭据类型为”Certiticate“password为生成证书时的设置的密码。

添加完成后如下图所示,点击连接测试

连接成功如下所示:

到此部署在非kubernetes集群内的Jenkins连接kubernetes配置就完成了。

部署在kubernetes集群内jenkins连接kubernetes配置

在前面介绍jenkins安装配置章节没有介绍如何在kubernetes集群中部署所以本小节的开头就先介绍一下如何在kubernetes集群中部署jenkins。

kubernetes系统中使用资源对象来描述某个系统的期望状态以及对象的基本信息。对于部署某个应用或者服务使用最多的资源对象就是deployment所以这里使用deployment来描述jenkins的配置并部署。

在kubernetes集群部署jenkins

首先需要创建一个服务账户ServiceAccount用来绑定对某一个命名空间的一系列kubernetes资源对象的操作权限。

如下jenkins-rbac.yaml文件


apiVersion: v1 kind: ServiceAccount metadata: name: jenkins namespace: kube-system


kind: Role apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: jenkins namespace: kube-system rules:

  • 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/v1beta1 kind: RoleBinding metadata: name: jenkins namespace: kube-system roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: jenkins subjects:

  • kind: ServiceAccount name: jenkins

然后创建deployment资源对象文件来描述jenkins的一些基本配置信息比如开放的端口启动参数使用的镜像等。

如下jenkins-deployment.yaml文件

apiVersion: extensions/v1beta1 kind: Deployment metadata: name: jenkins labels: app-name: jenkins namespace: kube-system spec: replicas: 1 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 template: metadata: labels: app-name: jenkins spec: serviceAccount: "jenkins" containers: - name: jenkins image: docker.io/jenkins:latest imagePullPolicy: IfNotPresent ports: - containerPort: 8080 name: web protocol: TCP - containerPort: 50000 name: agent protocol: TCP volumeMounts: - name: jenkins-home mountPath: /var/jenkins_home env: - name: JAVA_OPTS value: "-Duser.timezone=Asia/Shanghai -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85" volumes: - name: jenkins-home persistentVolumeClaim: claimName: jenkins-public-pvc

在资源对象的定义中使用了pv和pvc做持久化volume所以还需要创建这两种资源对象定义的文件。

$ cat jenkins-pv.yaml

kind: PersistentVolume apiVersion: v1 metadata: labels: name: jenkins-public-pv name: jenkins-public-pv namespace: kube-system spec: capacity: storage: 100Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain nfs: path: /data/nfs/jenkins-data server: 192.168.177.43

$ cat jenkins-pvc.yaml kind: PersistentVolumeClaim apiVersion: v1 metadata: name: jenkins-public-pvc namespace: kube-system spec: accessModes: - ReadWriteMany resources: requests: storage: 100Gi selector: matchLabels: name: jenkins-public-pv

使用nfs作为共享存储创建pvc使用pv在实际工作中一般使用storageclass替代pv作为动态存储。当然如果为了简单你也可以用挂载nfs目录的方式或者指定节点通过hostpath将工作目录挂在到宿主机方法比较多根据个人实际情况选择合适的存储即可。

部署service资源对象相当于给运行jenkins服务的pod加了一个代理。

如下jenkins-service.yaml

kind: Service apiVersion: v1 metadata: labels: app-name: jenkins name: jenkins namespace: kube-system spec: ports:

  • port: 8080 targetPort: 8080 name: web
  • port: 50000 targetPort: 50000 name: agent selector: app-name: jenkins

部署ingress服务直接跳过service接管service代理的pod。

如下jenkins-ingress.yaml

apiVersion: extensions/v1beta1 kind: Ingress metadata: name: jenkins namespace: kube-system spec: rules:

  • host: k8s-jenkins.com http: paths:
    • path: / backend: serviceName: jenkins servicePort: 8080

创建好ingress后需要添加部署ingress controllernode节点的ip和ingress中定义的host到本地hosts文件做解析。如果没有部署ingress服务可以直接编辑上一步骤中创建service时的类型为nodePort这样就可以通过访问nodeIP:nodePort的方式访问jenkins了。

部署完成后如下所示:

$ kubectl get pods,ingress,svc,deploy -n kube-system |grep jenkins

pod/jenkins-78f658b5d6-25qgl 1/1 Running 0 163d ingress.extensions/jenkins k8s-jenkins.com 80 10m

service/jenkins ClusterIP 10.254.179.172 8080/TCP,50000/TCP 198d

deployment.extensions/jenkins 1/1 1 1 198d

部署好后的其他配置与在前面章节安装配置jenkins的方法一样。有关在kubernetes集群中安装jenkins服务的方法就介绍到这里。

配置jenkins连接kubernetes

部署在kubernetes集群内的jenkins配置连接kubernetes相对较简单同样在系统配置页进行配置不用生成上面的一系列证书kubernetes的地址直接写kubernetes的FQDN即可。

如下所示:-

说明

Name 处默认为 kubernetes也可以修改为其他名称这里不做重复介绍。

Kubernetes URL 处可以填写 https://kubernetes.default 为Kubernetes Service 对应的 DNS 记录通过该DNS记录可以解析成该 Service 的 Cluster IP。注意这里也可以填写 https://kubernetes.default.svc.cluster.local 完整 DNS 记录,因为它要符合 service_name.namespace_name.svc.cluster_domain的命名方式。也可以直接填写外部 Kubernetes 的地址 https://ClusterIP:Ports (不推荐)。

Jenkins URL 处应该填写Jenkins的service地址和端口。比如我的http://jenkins.kube-system.svc.cluster.local:8080 也可以写成http://jenkins.kube-system:8080 表示jenkins的service名称和所在的namespace名称同kubernetes url设置类似也是使用Jenkins Service 对应的 DNS 记录以及端口。如果暴露服务的方式为nodeport也可以用http://NodeIP:NodePort本示例没配置的方式根据自己实际情况修改即可

Credentials :同属于一个集群环境,对于集群认证的过程就不需要了,认证凭证也就不用填写了。

Jenkins虽然能连接kubernetes了但是通过kubernetes还不能生成动态的slave代理因为slave agentjnlp-agent在启动的时候会通过50000默认端口与jenkins master进行通信。Jenkins这个端口默认是关闭的所以还需要开启这个端口。

Manage Jenkins(系统管理)->Configure global Security全局安全配置-> Agents(代理)

说明:

这里指定的端口是jnlp-agent连接jenkins-master使用的端口。

如果Jenkins-master只是在Docker容器(没有使用容器编排系统)中启动的一定要记得将这个端口暴露到外部不然jenkins-master会不知道slave是否已经启动会反复去创建pod直到到超过重试次数。

该端口的默认值是50000如果要修改为其他端口需要修改在创建jenkins”云”时对应的Jenkins Tunnel(通道)参数的配置如果使用50000端口这里可以不用填写如果换成别的端口这里需要单独设定最好是jenkins_url:port的形式。

如果不开启代理端口Jenkins通过kubernetes动态生成slave节点的时候jenkins后台会报如下错误并且pod会不断的生成和删除。

到这里jenkins与kubernetes的集成就成功了。

默认情况下agent代理连Jenkins的接超时时间为100秒。在特殊情况下如果想设置一个不同的值可以将system属性设置org.csanchez.jenkins.plugins.kubernetes.PodTemplate.connectionTimeout为一个不同的值。但是100s的话其实够用了如果超过100s还没生成代理pod就需要根据jenkins日志去排查问题了。如果jenkins连接kubernetes配置成功了大多数情况下的错误一般就是配置podtemplate是什么后面章节会介绍到出现了问题。

示例

上面配置好了如何连接kubernetes下面用一个示例测试配置是否成功。

将下面示例放到pipeline脚本中。

def label = "mypod-${UUID.randomUUID().toString()}" podTemplate(label: label, cloud: 'kubernetes') { node(label) { stage('Run shell') { sh 'hostname' sh 'echo hello world.' } } }

说明:

PodTemplate中的label参数用于给Pod指定一个唯一的名称。

PodTemplate中cloud参数的值必须是在系统配置添加的云的名称。

node中直接使用”label”便可以引用上面定义的label变量表示在该pod名称中执行命令如果不指定的话默认还是在Jenkins服务所在主机上执行流水线脚本。

执行结果如下:

根据上面的执行结果可以看到Jenkins通过kubernetes调用的资源对象pod的yaml定义使用的镜像默认为jenkins/jnlp-slave:3.35-5-alpine镜像名称以及一些pod的环境变量等信息。

除了可以在pipeline类型的job中使用kubernetes插件外也可以在其他类型的job中使用但是此时需要在jenkins的系统配置中配置PodTemplate此处先不介绍在后面的实践章节会进行说明。

无论哪种类型的job在job执行的过程中都可以通过kubectl命令在kubernetes集群中查看pod运行的一些详细信息而关于该pod的资源定义又是怎么配置的是否可以自定义呢下节会通过学习kubernetes的基本语法来解答这个问题。