Jenkins in k8s #
2023.09.21
在很长一段时间我都是以 docker/docker-compose 的方式使用 Jenkins,在学习 k8s 的过程中我想到能否将 Jenkins 也以 pod 的形式部署呢?于是就开始了尝试,不得不说还是有些折腾,可能是我 k8s 还没入门、Jenkins 也只是会使用的原因吧 😂
本文基于:jenkins:2.424
、k8s 1.28.1
架构 #
Jenkins Master 和 Jenkins Slave 以 Pod 形式运行在 Kubernetes 集群的 Node 上,Master是常驻服务,所有的配置数据都存储在一个 Volume 中,Slave 不是一直处于运行状态,它会按照需求动态的创建并自动删除。
当 Jenkins Master 接受到 Build 请求时,会根据配置的 Label 动态创建一个运行在 Pod 中的 Jenkins Slave 并注册到 Master 上,当运行完 Job 后,这个 Slave 会被注销并且这个 Pod 也会自动删除,恢复到最初状态。
开始 #
Jenkins 相关资源
- Namespace
- ServiceAccount
- ClusterRoleBinding
- RersistentVolume
- PersistentVolumeClaim
- Service
- Deployment
- Ingress(可选)
PV/PVC #
PV/PVC 对象用于持久化 Jenkins 相关的工作目录、插件等数据
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jenkins-pvc
namespace: alomerry
labels:
service: jenkins
spec:
storageClassName: local-storage
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
apiVersion: v1
kind: PersistentVolume
metadata:
name: jenkins-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /root/apps/jenkins
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- master
- node
- node2
Deployment #
Deployment 用于部署 Jenkins Pod,在 securityContext
配置 runAsUser
、runAsGroup
和 fsGroup
为 uid:1000
以 jenkins
用户运行,并暴露端口 8080 和 50000 端口
apiVersion: apps/v1
kind: Deployment
metadata:
name: jenkins-deployment
namespace: alomerry
spec:
replicas: 1
selector:
matchLabels:
service: jenkins
template:
metadata:
labels:
service: jenkins
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
containers:
- name: jenkins
image: registry.cn-hangzhou.aliyuncs.com/alomerry/jenkins:v2.424
imagePullPolicy: Always
env:
- name: TZ
value: Asia/Shanghai
ports:
- name: web
containerPort: 8080
- name: wesocket
containerPort: 50000
volumeMounts:
- name: jenkins-pv
mountPath: /var/jenkins_home
volumes:
- name: jenkins-pv
persistentVolumeClaim:
claimName: jenkins-pvc
Service/Ingress #
Service 用于将 Jenkins Pod 的端口以服务的形式统一暴露,如需要集群外访问,可以配置 Ingress 将 Service 暴露出去
ServiceAccount/ClusterRoleBinding #
如果需要在 Jenkins 访问和部署 Pod 需要配置对应的 ServiceAccount/ClusterRoleBinding 授权访问集群信息
Case #
以下是我部署的 Jenkins 对应的资源文件:
@code
图示 #
执行 kubectl apply -f jenkins.yml
后即可进入初始化页面:
执行 kubectl logs -n ${namespace} ${pod name}
Running from: /usr/share/jenkins/jenkins.war
...
2023-09-24 15:17:21.975+0000 [id=36] INFO jenkins.install.SetupWizard#init:
*************************************************************
*************************************************************
*************************************************************
Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:
${init code}
This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
*************************************************************
*************************************************************
*************************************************************
...
2023-09-24 15:20:58.800+0000 [id=23] INFO hudson.lifecycle.Lifecycle#onReady: Jenkins is fully up and running
设置好用户名密码后就进入 Jenkins 主页面了
将内置的节点执行数量设置成 0,设置除非指定节点名时才能使用以保证主节点的稳定
配置 k8s 环境
- k8s 地址:IP + 端口号或者是集群内地址
https://kubernetes.default.svc
- 将 kubeconfg 配置成 secret file 后设置到凭据中
获取 server certificate key
cat /etc/kubernetes/admin.conf | grep certificate-authority-data
echo ${certificate-authority-data} | base64 -d
jenkins 地址可以写 jenkins-service 的地址
优势 #
相对于部署在虚拟机环境下的 Jenkins 一主多从架构,将 Jenkins 部署到 k8s 会带来以下好处:
- 服务高可用: 当 Jenkins Master 出现故障时,Kubernetes 会自动创建一个新的 Jenkins Master 容器,并且将 Volume 分配给新创建的容器,保证数据不丢失,从而达到集群服务高可用。
- 动态伸缩: 合理使用资源,每次运行 Job 时,会自动创建一个 Jenkins Slave,Job 完成后,Slave 自动注销并删除容器,资源自动释放,而且 Kubernetes 会根据每个资源的使用情况,动态分配 Slave 到空闲的节点上创建,降低出现因某节点资源利用率高,还排队等待在该节点的情况。
- 扩展性:当 Kubernetes 集群的资源严重不足而导致 Job 排队等待时,可以很容易的添加一个 Kubernetes Node 到集群中,从而实现扩展。
DinD #
自 1.24 版本之后,Kubernetes 社区将正式放弃对 docker CRI 的支持,所以 k8s 集群中的 Jenkins Pod 已经不包含 docker 了,无法直接在 Pod 中使用 docker 相关的指令了。
我使用了 DinD 的方式来解决,DinD 即 Docker in Docker,看以下资源文件:
apiVersion: v1
kind: Pod
spec:
containers:
- name: dind
image: docker:24.0.6-dind
tty: true
securityContext:
privileged: true
command:
- dockerd
- --host=tcp://0.0.0.0:8000
- --host=unix:///var/run/docker.sock
- --tls=false
volumeMounts:
- mountPath: /var/run
name: docker-sock
readinessProbe:
exec:
command: [docker, info]
initialDelaySeconds: 10
failureThreshold: 6
- name: docker-builder
image: docker:24.0.6
tty: true
securityContext:
privileged: true
volumeMounts:
- mountPath: /var/run
name: docker-sock
volumes:
- name: docker-sock
emptyDir: { }
通过 docker:24.0.6-dind
启动 dockerd 服务并挂在到 docker:24.0.6
容器中,实现 docker-builder
container 可以执行 docker 命令