背景
雖然雲原生時代有了 JenkinsX[1]、Drone[2]、Tekton[3] 這樣的後起之秀,但 Jenkins 這樣一個老牌的 CI/CD 工具仍是各大公司主流的使用方案。比如我司的私有云產品打包發佈就是用這老傢伙完成的。然而傳統的 Jenkins Slave 一主多從方式會存在一些痛點,比如:
-
每個 Slave 的配置環境不一樣,來完成不同語言的編譯打包等操作,但是這些差異化的配置導致管理起來非常不方便,維護起來也是比較費勁 -
資源分配不均衡,有的 Slave 要運行的 job 出現排隊等待,而有的 Slave 處於空閒狀態 -
資源有浪費,每臺 Slave 可能是物理機或者虛擬機,當 Slave 處於空閒狀態時,也不會完全釋放掉資源。
正因爲上面的 Jenkins slave 存在這些種種痛點,我們渴望一種更高效更可靠的方式來完成這個 CI/CD 流程,而 Docker 虛擬化容器技術能很好的解決這個痛點,又特別是在 Kubernetes 集羣環境下面能夠更好來解決上面的問題,下圖是基於 Kubernetes 搭建 Jenkins slave 集羣的簡單示意圖:
從圖上可以看到 Jenkins Master 時以 docker-compose 的方式運行在一個節點上。Jenkins Slave 以 Pod 形式運行在 Kubernetes 集羣的 Node 上,並且它不是一直處於運行狀態,它會按照需求動態的創建並自動刪除。這種方式的工作流程大致爲:當 Jenkins Master 接受到 Build 請求時,會根據配置的 Label 動態創建一個運行在 Pod 中的 Jenkins Slave 並註冊到 Master 上,當運行完 Job 後,這個 Slave 會被註銷並且這個 Pod 也會自動刪除,恢復到最初狀態。
那麼我們使用這種方式帶來了以下好處:
-
動態伸縮,合理使用資源,每次運行 Job 時,會自動創建一個 Jenkins Slave,Job 完成後,Slave 自動註銷並刪除容器,資源自動釋放,而且 Kubernetes 會根據每個資源的使用情況,動態分配 Slave 到空閒的節點上創建,降低出現因某節點資源利用率高,還排隊等待在該節點的情況。 -
擴展性好,當 Kubernetes 集羣的資源嚴重不足而導致 Job 排隊等待時,可以很容易的添加一個 Kubernetes Node 到集羣中,從而實現擴展。
上面的大半段複製粘貼自 基於 Jenkins 的 CI/CD (一)[4] 🤐
kubernetes 集羣
關於 kubernetes 集羣部署,使用 kubeadm 部署是最爲方便的了,可參考我很早之前寫過的文章《使用 kubeadm 快速部署體驗 K8s[5]》,在這裏只是簡單介紹一下:
-
使用 kubeadm 來創建一個單 master 節點的 kubernets 集羣
root@jenkins:~ # kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=192.168.20.11
-
集羣成功部署完成之後會有如下提示:
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
-
查看節點狀態和 pod 都已經正常
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 節點上的污點,允許其他的 pod 調度在 master 節點上,不然後面 Jenkins 所創建的 pod 將無法調度在該節點上。
kubectl taint nodes $(hostname) node-role.kubernetes.io/master:NoSchedule-
Jenkins master
至於 Jenkins master 的部署方式,個人建議使用 docker-compose 來部署。運行在 kubernetes 集羣集羣中也沒什麼毛病,可以參考 基於 Jenkins 的 CI/CD (一)[6] 這篇博客。但從個人運維踩的坑來講,還是將 Jenkins master 獨立於 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 來啓動,成功啓動後會有如下提示,日誌輸出的密鑰就是 admin
用戶的默認密碼,使用它來第一次登錄 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 的地方是在 系統管理 > 節點管理 > Configure Clouds
。點擊Add a new cloud
,來添加一個 kubernetes 集羣。
-
配置連接參數
參數 | 值 | 說明 |
---|---|---|
名稱 | kubernetes | 也是後面 pod 模板中的 cloud 的值 |
憑據 | kubeconfig 憑據 id | 使用 kubeconfig 文件來連接集羣 |
Kubernetes 地址 | 默認即可 | |
Use Jenkins Proxy | 默認即可 | |
Kubernetes 服務證書 key | 默認即可 | |
禁用 HTTPS 證書檢查 | 默認即可 | |
Kubernetes 命名空間 | 默認即可 | |
WebSocket | 默認即可 | |
Direct Connection | 默認即可 | |
Jenkins 地址 | http://jenkins.k8s.li:8080 | Jenkins pod 連接 Jenkins master 的 URL |
Jenkins 通道 | 50000 | Jenkins JNLP 的端口,默認爲 50000 |
Connection Timeout | 默認即可 | Jenkins 連接 kubernetes 超時時間 |
Read Timeout | 默認即可 | |
容器數量 | 默認即可 | Jenkins pod 創建的最大數量 |
Pod Labels | 默認即可 | Jenkins pod 的 lables |
連接 Kubernetes API 的最大連接數 | 默認即可 | |
Seconds to wait for pod to be running | 默認即可 | 等待 pod 正常 running 的時間 |
-
在 Jenkins 的憑據那裏添加上 kubeconfig 文件,憑據的類型選擇爲 Secret file
,然後將上面使用 kubeadm 部署生成的 kubeconfig 上傳到這裏。
-
點擊連接測試,如果提示 Connected to Kubernetes v1.19.8
就說明已經成功連接上了 kubernetes 集羣。
-
關於 pod 模板
其實就是配置 Jenkins Slave 運行的 Pod 模板,個人不太建議使用插件中的模板去配置,推薦將 pod 的模板放在 Jenkinsfile 中,因爲這些配置與我們的流水線緊密相關,把 pod 的配置存儲在 Jenkins 的插件裏實在是不太方便;不方便後續的遷移備份之類的工作;後續插件升級後這些配置也可能會丟失。因此建議將 pod 模板的配置直接定義在 Jenkinsfile 中,靈活性更高一些,不會受 Jenkins 插件升級的影響。總之用代碼去管理這些 pod 配置維護成本將會少很多。
Jenkinsfile
-
流水線 Jenkinsfile
,下面是一個簡單的任務,用於構建 webp-server-go [7] 項目的 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 模版如下,將模板的內容複製粘貼到上面的 Jenkinsfile 中。在容器中構建鏡像,我們使用 dind 的方案:將 pod 所在宿主機的 docker sock 文件掛載到 pod 的容器內,pod 容器內只要安裝好 docker-cli 工具就可以像宿主機那樣直接使用 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
""",
)
-
構建 debian:buster-docker
鏡像,使用它來在 pod 的容器內構建 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 文件並且構建好 pod 模板中的鏡像後,接下來我們開始使用它來創建流水線任務。
流水線
-
在 Jenkins 上新建一個任務,選擇任務的類型爲 流水線
-
將定義好的 Jenkinsfile 內容複製粘貼到流水線定義 Pipeline script
中並點擊保存。在新建好的 Job 頁面點擊立即構建
來運行流水線任務。
-
在 kubernetes 集羣的機器上使用 kubectl 命令查看 pod 是否正常 Running
root@jenkins:~ # kubectl get pod
NAME READY STATUS RESTARTS AGE
jenkins-webps-9-bs78x-5x204 2/2 Running 0 66s
-
Job 正常運行並且狀態爲綠色表明該 job 已經成功執行了。
-
在 kubernetes 集羣機器上查看 docker 鏡像是否構建成功
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
這是因爲 Jenkins pod 中的 jnlp 容器無法連接 Jenkins master。可以檢查一下 Jenkins master 上 系統管理 > 節點管理 > Configure Clouds
中 Jenkins 地址
和 Jenkins 通道
這兩個參數是否配置正確。
結束
到此爲止,我們就完成了讓 Jenkins 大叔與 kubernetes 船長手牽手 🧑🤝🧑 啦!上面使用了一個簡單的例子來展示瞭如何將 Jenkins 的 Job 任務運行在 kubernetes 集羣上,但在實際工作中遇到的情形可能比這要複雜一些,流水線需要配置的參數也要多一些。那麼我將會在下一篇博客中再講一下高級的用法:使用 Jenkins 完成 kubespray 離線安裝包打包。
參考
-
使用 Kubernetes 和 Jenkins 創建一個 CI/CD 流水線 [8] -
基於 Jenkins 的 CI/CD (一) [9] -
PingCAP 面試:Jenkins 和 Kubernetes [10] -
基於 Kubernetes 的 Jenkins 服務也可以去 Docker 了 [11] -
Jenkins Pipeline 使用及調試 [12] -
在 Kubernetes 上動態創建 Jenkins Slave [13] -
Jenkins X 不是 Jenkins ,而是一個技術棧 [14] -
Jenkins CI/CD (一) 基於角色的授權策略 [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 快速部署體驗 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 創建一個 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 服務也可以去 Docker 了: https://www.chenshaowen.com/blog/using-podman-to-build-images-under-kubernetes-and-jenkins.html
[12]Jenkins Pipeline 使用及調試: https://www.chenshaowen.com/blog/jenkins-pipeline-usging-and-debug.html
[13]在 Kubernetes 上動態創建 Jenkins Slave: https://www.chenshaowen.com/blog/creating-jenkins-slave-dynamically-on-kubernetes.html
[14]Jenkins X 不是 Jenkins ,而是一個技術棧: https://www.chenshaowen.com/blog/jenkins-x-is-not-jenkins-but-stack.html
[15]Jenkins CI/CD (一) 基於角色的授權策略: https://atbug.com/using-role-based-authorization-strategy-in-jenkins/
原文鏈接:https://blog.k8s.li/jenkins-with-kubernetes.html
你可能還喜歡
點擊下方圖片即可閱讀
雲原生是一種信仰 🤘
關注公衆號
後臺回覆◉k8s◉獲取史上最方便快捷的 Kubernetes 高可用部署工具,只需一條命令,連 ssh 都不需要!
點擊 "閱讀原文" 獲取更好的閱讀體驗!
發現朋友圈變“安靜”了嗎?
本文分享自微信公衆號 - 雲原生實驗室(cloud_native_yang)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。