Jenkins 大叔與 kubernetes 船長手牽手 🧑‍🤝‍🧑


背景

雖然雲原生時代有了 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 CloudsJenkins 地址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]

參考資料

[1]

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



你可能還喜歡

點擊下方圖片即可閱讀

eBPF 的發展歷史和核心設計

雲原生是一種信仰 🤘


關注公衆號

後臺回覆◉k8s◉獲取史上最方便快捷的 Kubernetes 高可用部署工具,只需一條命令,連 ssh 都不需要!



點擊 "閱讀原文" 獲取更好的閱讀體驗!


發現朋友圈變“安靜”了嗎?

本文分享自微信公衆號 - 雲原生實驗室(cloud_native_yang)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章