Jenkins 大叔與 kubernetes 船長手牽手 ????????

背景
雖然云原生時(shí)代有了 JenkinsX[1]、Drone[2]、Tekton[3] 這樣的后起之秀,但 Jenkins 這樣一個(gè)老牌的 CI/CD 工具仍是各大公司主流的使用方案。比如我司的私有云產(chǎn)品打包發(fā)布就是用這老家伙完成的。然而傳統(tǒng)的 Jenkins Slave 一主多從方式會(huì)存在一些痛點(diǎn),比如:
-
每個(gè) Slave 的配置環(huán)境不一樣,來完成不同語言的編譯打包等操作,但是這些差異化的配置導(dǎo)致管理起來非常不方便,維護(hù)起來也是比較費(fèi)勁 -
資源分配不均衡,有的 Slave 要運(yùn)行的 job 出現(xiàn)排隊(duì)等待,而有的 Slave 處于空閑狀態(tài) -
資源有浪費(fèi),每臺(tái) Slave 可能是物理機(jī)或者虛擬機(jī),當(dāng) Slave 處于空閑狀態(tài)時(shí),也不會(huì)完全釋放掉資源。
正因?yàn)樯厦娴?Jenkins slave 存在這些種種痛點(diǎn),我們渴望一種更高效更可靠的方式來完成這個(gè) CI/CD 流程,而 Docker 虛擬化容器技術(shù)能很好的解決這個(gè)痛點(diǎn),又特別是在 Kubernetes 集群環(huán)境下面能夠更好來解決上面的問題,下圖是基于 Kubernetes 搭建 Jenkins slave 集群的簡(jiǎn)單示意圖:
從圖上可以看到 Jenkins Master 時(shí)以 docker-compose 的方式運(yùn)行在一個(gè)節(jié)點(diǎn)上。Jenkins Slave 以 Pod 形式運(yùn)行在 Kubernetes 集群的 Node 上,并且它不是一直處于運(yùn)行狀態(tài),它會(huì)按照需求動(dòng)態(tài)的創(chuàng)建并自動(dòng)刪除。這種方式的工作流程大致為:當(dāng) Jenkins Master 接受到 Build 請(qǐng)求時(shí),會(huì)根據(jù)配置的 Label 動(dòng)態(tài)創(chuàng)建一個(gè)運(yùn)行在 Pod 中的 Jenkins Slave 并注冊(cè)到 Master 上,當(dāng)運(yùn)行完 Job 后,這個(gè) Slave 會(huì)被注銷并且這個(gè) Pod 也會(huì)自動(dòng)刪除,恢復(fù)到最初狀態(tài)。
那么我們使用這種方式帶來了以下好處:
-
動(dòng)態(tài)伸縮,合理使用資源,每次運(yùn)行 Job 時(shí),會(huì)自動(dòng)創(chuàng)建一個(gè) Jenkins Slave,Job 完成后,Slave 自動(dòng)注銷并刪除容器,資源自動(dòng)釋放,而且 Kubernetes 會(huì)根據(jù)每個(gè)資源的使用情況,動(dòng)態(tài)分配 Slave 到空閑的節(jié)點(diǎn)上創(chuàng)建,降低出現(xiàn)因某節(jié)點(diǎn)資源利用率高,還排隊(duì)等待在該節(jié)點(diǎn)的情況。 -
擴(kuò)展性好,當(dāng) Kubernetes 集群的資源嚴(yán)重不足而導(dǎo)致 Job 排隊(duì)等待時(shí),可以很容易的添加一個(gè) Kubernetes Node 到集群中,從而實(shí)現(xiàn)擴(kuò)展。
上面的大半段復(fù)制粘貼自 基于 Jenkins 的 CI/CD (一)[4] ??
kubernetes 集群
關(guān)于 kubernetes 集群部署,使用 kubeadm 部署是最為方便的了,可參考我很早之前寫過的文章《使用 kubeadm 快速部署體驗(yàn) K8s[5]》,在這里只是簡(jiǎn)單介紹一下:
-
使用 kubeadm 來創(chuàng)建一個(gè)單 master 節(jié)點(diǎn)的 kubernets 集群
root@jenkins:~ # kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=192.168.20.11
-
集群成功部署完成之后會(huì)有如下提示:
Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
-
查看節(jié)點(diǎn)狀態(tài)和 pod 都已經(jīng)正常
root@jenkins:~ # kubectl get pod -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-f9fd979d6-9t6qp 1/1 Running 0 89s
kube-system coredns-f9fd979d6-hntm8 1/1 Running 0 89s
kube-system etcd-jenkins 1/1 Running 0 106s
kube-system kube-apiserver-jenkins 1/1 Running 0 106s
kube-system kube-controller-manager-jenkins 1/1 Running 0 106s
kube-system kube-proxy-8pzkz 1/1 Running 0 89s
kube-system kube-scheduler-jenkins 1/1 Running 0 106s
root@jenkins:~ # kubectl get node
NAME STATUS ROLES AGE VERSION
jenkins Ready master 119s v1.19.8
-
去除 master 節(jié)點(diǎn)上的污點(diǎn),允許其他的 pod 調(diào)度在 master 節(jié)點(diǎn)上,不然后面 Jenkins 所創(chuàng)建的 pod 將無法調(diào)度在該節(jié)點(diǎn)上。
kubectl taint nodes $(hostname) node-role.kubernetes.io/master:NoSchedule-
Jenkins master
至于 Jenkins master 的部署方式,個(gè)人建議使用 docker-compose 來部署。運(yùn)行在 kubernetes 集群集群中也沒什么毛病,可以參考 基于 Jenkins 的 CI/CD (一)[6] 這篇博客。但從個(gè)人運(yùn)維踩的坑來講,還是將 Jenkins master 獨(dú)立于 kubernetes 集群部署比較方便 ??。
-
docker-compose.yaml
version: '3.6'
services:
jenkins:
image: jenkins/jenkins:2.263.4-lts-slim
container_name: jenkins
restart: always
volumes:
- ./jenkins_home:/var/jenkins_home
network_mode: host
user: root
environment:
- JAVA_OPTS=-Duser.timezone=Asia/Shanghai
-
使用 docker-compose up 來啟動(dòng),成功啟動(dòng)后會(huì)有如下提示,日志輸出的密鑰就是 admin用戶的默認(rèn)密碼,使用它來第一次登錄 Jenkins。
jenkins | 2021-03-06 02:22:31.741+0000 [id=41] INFO jenkins.install.SetupWizard#init:
jenkins |
jenkins | *************************************************************
jenkins | *************************************************************
jenkins | *************************************************************
jenkins |
jenkins | Jenkins initial setup is required. An admin user has been created and a password generated.
jenkins | Please use the following password to proceed to installation:
jenkins |
jenkins | 4c2361968cd94323acdde17f7603d8e1
jenkins |
jenkins | This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
jenkins |
jenkins | *************************************************************
jenkins | *************************************************************
jenkins | *************************************************************
-
登錄上去之后,建議選擇 選擇插件來安裝,盡可能少地安裝插件,按需安裝即可。
-
在 Jenkins 的插件管理那里安裝上 kubernetes 插件
-
接下來開始配置 Jenkins 大叔如何與 kubernetes 船長手牽手 ???????? :-)。配置 kubernets 的地方是在 系統(tǒng)管理 > 節(jié)點(diǎn)管理 > Configure Clouds。點(diǎn)擊Add a new cloud,來添加一個(gè) kubernetes 集群。
-
配置連接參數(shù)
| 參數(shù) | 值 | 說明 |
|---|---|---|
| 名稱 | kubernetes | 也是后面 pod 模板中的 cloud 的值 |
| 憑據(jù) | kubeconfig 憑據(jù) id | 使用 kubeconfig 文件來連接集群 |
| Kubernetes 地址 | 默認(rèn)即可 | |
| Use Jenkins Proxy | 默認(rèn)即可 | |
| Kubernetes 服務(wù)證書 key | 默認(rèn)即可 | |
| 禁用 HTTPS 證書檢查 | 默認(rèn)即可 | |
| Kubernetes 命名空間 | 默認(rèn)即可 | |
| WebSocket | 默認(rèn)即可 | |
| Direct Connection | 默認(rèn)即可 | |
| Jenkins 地址 | http://jenkins.k8s.li:8080 | Jenkins pod 連接 Jenkins master 的 URL |
| Jenkins 通道 | 50000 | Jenkins JNLP 的端口,默認(rèn)為 50000 |
| Connection Timeout | 默認(rèn)即可 | Jenkins 連接 kubernetes 超時(shí)時(shí)間 |
| Read Timeout | 默認(rèn)即可 | |
| 容器數(shù)量 | 默認(rèn)即可 | Jenkins pod 創(chuàng)建的最大數(shù)量 |
| Pod Labels | 默認(rèn)即可 | Jenkins pod 的 lables |
| 連接 Kubernetes API 的最大連接數(shù) | 默認(rèn)即可 | |
| Seconds to wait for pod to be running | 默認(rèn)即可 | 等待 pod 正常 running 的時(shí)間 |
-
在 Jenkins 的憑據(jù)那里添加上 kubeconfig 文件,憑據(jù)的類型選擇為 Secret file,然后將上面使用 kubeadm 部署生成的 kubeconfig 上傳到這里。
-
點(diǎn)擊連接測(cè)試,如果提示 Connected to Kubernetes v1.19.8就說明已經(jīng)成功連接上了 kubernetes 集群。
-
關(guān)于 pod 模板
其實(shí)就是配置 Jenkins Slave 運(yùn)行的 Pod 模板,個(gè)人不太建議使用插件中的模板去配置,推薦將 pod 的模板放在 Jenkinsfile 中,因?yàn)檫@些配置與我們的流水線緊密相關(guān),把 pod 的配置存儲(chǔ)在 Jenkins 的插件里實(shí)在是不太方便;不方便后續(xù)的遷移備份之類的工作;后續(xù)插件升級(jí)后這些配置也可能會(huì)丟失。因此建議將 pod 模板的配置直接定義在 Jenkinsfile 中,靈活性更高一些,不會(huì)受 Jenkins 插件升級(jí)的影響??傊么a去管理這些 pod 配置維護(hù)成本將會(huì)少很多。
Jenkinsfile
-
流水線 Jenkinsfile,下面是一個(gè)簡(jiǎn)單的任務(wù),用于構(gòu)建 webp-server-go[7] 項(xiàng)目的 docker 鏡像。
// Kubernetes pod template to run.
def JOB_NAME = "${env.JOB_NAME}"
def BUILD_NUMBER = "${env.BUILD_NUMBER}"
def POD_NAME = "jenkins-${JOB_NAME}-${BUILD_NUMBER}"
podTemplate(
# 這里定義 pod 模版
)
{ node(POD_NAME) {
container(JOB_NAME) {
stage("Build image") {
sh """#!/bin/bash
git clone https://github.com/webp-sh/webp_server_go /build
cd /build
docker build -t webps:0.3.2-rc.1 .
"""
}
}
}
}
-
pod 模版如下,將模板的內(nèi)容復(fù)制粘貼到上面的 Jenkinsfile 中。在容器中構(gòu)建鏡像,我們使用 dind 的方案:將 pod 所在宿主機(jī)的 docker sock 文件掛載到 pod 的容器內(nèi),pod 容器內(nèi)只要安裝好 docker-cli 工具就可以像宿主機(jī)那樣直接使用 docker 了。
podTemplate(
cloud: "kubernetes",
namespace: "default",
name: POD_NAME,
label: POD_NAME,
yaml: """
apiVersion: v1
kind: Pod
spec:
containers:
- name: ${JOB_NAME}
image: "debian:buster-docker"
imagePullPolicy: IfNotPresent
tty: true
volumeMounts:
- name: dockersock
mountPath: /var/run/docker.sock
- name: jnlp
args: ["\$(JENKINS_SECRET)", "\$(JENKINS_NAME)"]
image: "jenkins/inbound-agent:4.3-4-alpine"
imagePullPolicy: IfNotPresent
volumes:
- name: dockersock
hostPath:
path: /var/run/docker.sock
""",
)
-
構(gòu)建 debian:buster-docker鏡像,使用它來在 pod 的容器內(nèi)構(gòu)建 docker 鏡像,使用的Dockerfile如下:
FROM debian:buster
RUN apt update \
&& apt install -y --no-install-recommends \
vim \
curl \
git \
make \
ca-certificates \
gnupg \
&& rm -rf /var/lib/apt/lists/*
RUN curl -fsSL "https://download.docker.com/linux/debian/gpg" | apt-key add -qq - >/dev/null \
&& echo "deb [arch=amd64] https://download.docker.com/linux/debian buster stable" > /etc/apt/sources.list.d/docker.list \
&& apt update -qq \
&& apt-get install -y -qq --no-install-recommends docker-ce-cli \
&& rm -rf /var/lib/apt/lists/*
定義好 jenkinsfile 文件并且構(gòu)建好 pod 模板中的鏡像后,接下來我們開始使用它來創(chuàng)建流水線任務(wù)。
流水線
-
在 Jenkins 上新建一個(gè)任務(wù),選擇任務(wù)的類型為 流水線
-
將定義好的 Jenkinsfile 內(nèi)容復(fù)制粘貼到流水線定義 Pipeline script中并點(diǎn)擊保存。在新建好的 Job 頁面點(diǎn)擊立即構(gòu)建來運(yùn)行流水線任務(wù)。
-
在 kubernetes 集群的機(jī)器上使用 kubectl 命令查看 pod 是否正常 Running
root@jenkins:~ # kubectl get pod
NAME READY STATUS RESTARTS AGE
jenkins-webps-9-bs78x-5x204 2/2 Running 0 66s
-
Job 正常運(yùn)行并且狀態(tài)為綠色表明該 job 已經(jīng)成功執(zhí)行了。
-
在 kubernetes 集群機(jī)器上查看 docker 鏡像是否構(gòu)建成功
root@jenkins:~ # docker images | grep webps
webps 0.3.2-rc.1 f68f496c0444 20 minutes ago 13.7MB
踩坑
-
pod 無法正常 Running
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline
[Pipeline] podTemplate
[Pipeline] {
[Pipeline] node
Created Pod: kubernetes default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r][Scheduled] Successfully assigned default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r to jenkins
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r][Pulling] Pulling image "debian:buster"
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r][Pulled] Successfully pulled image "debian:buster" in 2.210576896s
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r][Created] Created container debian
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r][Started] Started container debian
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r][Pulling] Pulling image "jenkins/inbound-agent:4.3-4-alpine"
Still waiting to schedule task
‘debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r’ is offline
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r][Pulled] Successfully pulled image "jenkins/inbound-agent:4.3-4-alpine" in 3.168311973s
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r][Created] Created container jnlp
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-9wm0r][Started] Started container jnlp
Created Pod: kubernetes default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-qdw4m
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-qdw4m][Scheduled] Successfully assigned default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-qdw4m to jenkins
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-qdw4m][Pulled] Container image "debian:buster" already present on machine
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-qdw4m][Created] Created container debian
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-qdw4m][Started] Started container debian
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-qdw4m][Pulled] Container image "jenkins/inbound-agent:4.3-4-alpine" already present on machine
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-qdw4m][Created] Created container jnlp
[Normal][default/debian-35a11b49-087b-4a8c-abac-bd97d7eb5a1f-fkmzq-qdw4m][Started] Started container jnlp
這是因?yàn)?Jenkins pod 中的 jnlp 容器無法連接 Jenkins master??梢詸z查一下 Jenkins master 上 系統(tǒng)管理 > 節(jié)點(diǎn)管理 > Configure Clouds 中 Jenkins 地址 和 Jenkins 通道 這兩個(gè)參數(shù)是否配置正確。
結(jié)束
到此為止,我們就完成了讓 Jenkins 大叔與 kubernetes 船長手牽手 ???????? 啦!上面使用了一個(gè)簡(jiǎn)單的例子來展示了如何將 Jenkins 的 Job 任務(wù)運(yùn)行在 kubernetes 集群上,但在實(shí)際工作中遇到的情形可能比這要復(fù)雜一些,流水線需要配置的參數(shù)也要多一些。那么我將會(huì)在下一篇博客中再講一下高級(jí)的用法:使用 Jenkins 完成 kubespray 離線安裝包打包。
參考
-
使用 Kubernetes 和 Jenkins 創(chuàng)建一個(gè) CI/CD 流水線[8] -
基于 Jenkins 的 CI/CD (一)[9] -
PingCAP 面試:Jenkins 和 Kubernetes[10] -
基于 Kubernetes 的 Jenkins 服務(wù)也可以去 Docker 了[11] -
Jenkins Pipeline 使用及調(diào)試[12] -
在 Kubernetes 上動(dòng)態(tài)創(chuàng)建 Jenkins Slave[13] -
Jenkins X 不是 Jenkins ,而是一個(gè)技術(shù)棧[14] -
Jenkins CI/CD (一) 基于角色的授權(quán)策略[15]
參考資料
JenkinsX: https://jenkins-x.io/
[2]Drone: https://www.drone.io/
[3]Tekton: https://tekton.dev
[4]基于 Jenkins 的 CI/CD (一): https://www.qikqiak.com/k8s-book/docs/36.Jenkins%20Slave.html
[5]使用 kubeadm 快速部署體驗(yàn) K8s: https://blog.k8s.li/kubeadm-deploy-k8s-v1.17.4.html
[6]基于 Jenkins 的 CI/CD (一): https://www.qikqiak.com/k8s-book/docs/36.Jenkins%20Slave.html
[7]webp-server-go: https://github.com/webp-sh/webp_server_go
[8]使用 Kubernetes 和 Jenkins 創(chuàng)建一個(gè) CI/CD 流水線: https://jenkins-zh.cn/wechat/articles/2020/03/2020-03-10-create-a-ci-cd-pipeline-with-kubernetes-and-jenkins/
[9]基于 Jenkins 的 CI/CD (一): https://www.qikqiak.com/k8s-book/docs/36.Jenkins%20Slave.html
[10]PingCAP 面試:Jenkins 和 Kubernetes: https://a-wing.top/kubernetes/2021/01/27/jenkins_and_kubernetes.html
[11]基于 Kubernetes 的 Jenkins 服務(wù)也可以去 Docker 了: https://www.chenshaowen.com/blog/using-podman-to-build-images-under-kubernetes-and-jenkins.html
[12]Jenkins Pipeline 使用及調(diào)試: https://www.chenshaowen.com/blog/jenkins-pipeline-usging-and-debug.html
[13]在 Kubernetes 上動(dòng)態(tài)創(chuàng)建 Jenkins Slave: https://www.chenshaowen.com/blog/creating-jenkins-slave-dynamically-on-kubernetes.html
[14]Jenkins X 不是 Jenkins ,而是一個(gè)技術(shù)棧: https://www.chenshaowen.com/blog/jenkins-x-is-not-jenkins-but-stack.html
[15]Jenkins CI/CD (一) 基于角色的授權(quán)策略: https://atbug.com/using-role-based-authorization-strategy-in-jenkins/
原文鏈接:https://blog.k8s.li/jenkins-with-kubernetes.html


你可能還喜歡
點(diǎn)擊下方圖片即可閱讀

云原生是一種信仰 ??
關(guān)注公眾號(hào)
后臺(tái)回復(fù)?k8s?獲取史上最方便快捷的 Kubernetes 高可用部署工具,只需一條命令,連 ssh 都不需要!

點(diǎn)擊 "閱讀原文" 獲取更好的閱讀體驗(yàn)!
發(fā)現(xiàn)朋友圈變“安靜”了嗎?

