[持續交付實踐] Jenkins Pipeline 高可用設計方法

前言

這篇寫好一段時間了,一直也沒發佈上來,今天稍微整理下了交下做業,部份內容偷懶引用了一些別人的內容。
使用Jenkins作持續集成/持續交付,當業務達到必定規模的時候,Jenkins自己就很容易成爲整條流水線的瓶頸,各個業務端都依靠Jenkins,部署Jenkins服務時如何保障服務的高可用變得尤其重要。
以微醫爲例,目前Jenkins的業務承載量:>1,000 Build Jobs,>5,000 Buils/Day,光依靠單master已經沒法承載高併發的性能壓力,瓶頸來自多方面,不只僅是Jenkins 應用自己佔用 memory 和 CPU 資源,也包括各個job編譯、測試、部署等的資源開銷,隨着job數量的增長,大量的workspace也會耗盡服務器的存儲空間,嚴重影響整個技術團隊的工做效率和部署節奏。java

1、Jenkins分佈式集羣架構

Jenkins 分佈式架構是由一個 Master 和多個 Slave Node組成的 分佈式架構。在 Jenkins Master 上管理你的項目,能夠把你的一些構建任務分擔到不一樣的 Slave Node 上運行,Master 的性能就提升了。
Master/Slave至關於Server和agent的概念。Master提供web接口讓用戶來管理job和slave,job能夠運行在master本機或者被分配到slave上運行構建。
一個master(jenkins服務所在機器)能夠關聯多個slave用來爲不一樣的job或相同的job的不一樣配置來服務。node

2、傳統的Jenkins Slave方式存在的問題

傳統的 Jenkins Slave 一主多從式會存在一些痛點。好比:git

  • 主 Master 發生單點故障時,整個流程都不可用了;
  • 每一個 Slave 的配置環境不同,來完成不一樣語言的編譯打包等操做,可是這些差別化的配置致使管理起來很是不方便,維護起來也是比較費勁;
  • 資源分配不均衡,有的 Slave 要運行的 job 出現排隊等待,而有的 Slave 處於空閒狀態;
  • 資源有浪費,每臺 Slave 多是實體機或者 VM,當 Slave 處於空閒狀態時,也不會徹底釋放掉資源。
     
    是否是很醜陋?

3、基於 Kubernetes 搭建容器化Jenkins集羣實踐

3.1 基於 Kubernetes 的Jenkins 集羣架構

因爲以上種種痛點,咱們渴望一種更高效更可靠的方式來完成這個 CI/CD 流程,而虛擬化容器技術能很好的解決這個痛點,下圖是基於 Kubernetes 搭建 Jenkins 集羣的簡單示意圖。github

 


Jenkins Master 和 Jenkins Slave 以 Docker Container 形式運行在 Kubernetes 集羣的 Node 上,Master 運行在其中一個節點,而且將其配置數據存儲到一個 Volume 上去,Slave 運行在各個節點上,而且它不是一直處於運行狀態,它會按照需求動態的建立並自動刪除。
這種方式的工做流程大體爲:當 Jenkins Master 接受到 Build 請求時,會根據配置的 Label 動態建立一個運行在 Docker Container 中的 Jenkins Slave 並註冊到 Master 上,當運行完 Job 後,這個 Slave 會被註銷而且 Docker Container 也會自動刪除,恢復到最初狀態。
這種方式帶來的好處有不少:web

 

  • 服務高可用,當 Jenkins Master 出現故障時,Kubernetes 會自動建立一個新的 Jenkins Master 容器,而且將 Volume 分配給新建立的容器,保證數據不丟失,從而達到集羣服務高可用。
  • 動態伸縮,合理使用資源,每次運行 Job 時,會自動建立一個 Jenkins Slave,Job 完成後,Slave 自動註銷並刪除容器,資源自動釋放,並且 Kubernetes 會根據每一個資源的使用狀況,動態分配 Slave 到空閒的節點上建立,下降出現因某節點資源利用率高,還排隊等待在該節點的狀況。
  • 擴展性好,當 Kubernetes 集羣的資源嚴重不足而致使 Job 排隊等待時,能夠很容易的添加一個 Kubernetes Node 到集羣中,從而實現擴展。

3.2 部署 Jenkins Master

在保證Jenkins Master高可用的前提下,能夠按傳統方式war包方式部署,可使用docker方式部署,也能夠在Kubernetes Node中部署,這部相對簡單,再也不展開詳述。docker

3.3 Jenkins 配置 Kubernetes Plugin

管理員帳戶登陸 Jenkins Master 頁面,點擊 「系統管理」 —> 「管理插件」 —> 「可選插件」 —> 「Kubernetes plugin」 勾選安裝便可。 shell

 


安裝完畢後,點擊 「系統管理」 —> 「系統設置」 —> 「新增一個雲」 —> 選擇 「Kubernetes」,而後填寫 Kubernetes 和 Jenkins 配置信息。 apache

 

 

3.4 使用Jenkins Pipeline測試驗證

接下來,咱們能夠配置 Job 測試一下是否會根據配置的 Label 動態建立一個運行在 Docker Container 中的 Jenkins Slave 並註冊到 Master 上,而且在運行完 Job 後,Slave 會被註銷而且自動刪除 Docker Container。
建立一個 Pipeline 類型 Job 並命名爲"pipeline_kubernetes_demo1,而後在 Pipeline 腳本處填寫一個簡單的測試腳本以下:npm

pipeline {
agent {
kubernetes {
//cloud 'kubernetes'
label 'k8s-jenkins-jnlp'
containerTemplate {
name 'jnlp'
image 'harbor.guahao-inc.com/base/jenkins/jnlp-slave:latest'
}
}
}
stages {
stage('Run shell') {
steps {
script {
git 'https://github.com/nbbull/demoProject.git'
sh 'sleep 5'
}
}
}
}

}

執行構建,此時去構建隊列裏面,能夠看到有一個構建任務,第一次構建的時候會稍慢,由於k8s的node須要去下載jnlp-slave的鏡像。
稍等一會就會看到k8s-jenkins-jnlp-8gqtp-j9948的容器正在建立,而後開始運行,Job 執行完畢後,jenkins-slave 會自動註銷並刪除容器,咱們經過 kubectl 命令行,能夠看到整個自動建立和刪除過程,整個過程自動完成。json

[root@kubernetes-master1 ~]# kubectl get pods|grep jenkins 
k8s-jenkins-jnlp-8gqtp-j9948 0/1 ContainerCreating 0 4s
[root@kubernetes-master1 ~]# kubectl get pods|grep jenkins
k8s-jenkins-jnlp-8gqtp-j9948 1/1 Running 0 18s

具體的構建日誌參考以下:

 

 

3.5 自定義 jenkins-slave 鏡像

經過 kubernetest plugin 默認提供的鏡像 jenkinsci/jnlp-slave 能夠完成一些基本的操做,它是基於 openjdk:8-jdk 鏡像來擴展的,可是對於咱們來講這個鏡像功能過於簡單,好比咱們想執行 Maven 編譯或者其餘命令時,就有問題了,那麼能夠經過製做本身的鏡像來預安裝一些軟件,既能實現 jenkins-slave 功能,又能夠完成本身個性化需求,dockfile以下:

FROM harbor.guahao-inc.com/base/jenkins/jnlp-slave:latest
USER root
//下載安裝必要組件
RUN apt-get update && apt-get install -y sudo && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y vim && apt-get install -y sshpass
//下載和配置maven
COPY apache-maven-3.2.6-GH /usr/greenline/install/apache-maven-3.2.6-GH
RUN ln -s /usr/greenline/install/apache-maven-3.2.6-GH /usr/greenline/maven3
//下載和配置jdk
COPY jdk1.8.0_91 /usr/greenline/install/jdk1.8.0_91
RUN ln -s /usr/greenline/install/jdk1.8.0_91 /usr/greenline/jdk_1.8
ENV JAVA_HOME=/usr/greenline/jdk_1.8
ENV CLASSPATH=.:/usr/greenline/jdk_1.8/lib/dt.jar:/usr/greenline/jdk_1.8/lib/tools.jar:/usr/greenline/jdk_1.8/lib/rt.jar
ENV PATH=/usr/greenline/jdk_1.8/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/usr/greenline/maven3/bin

USER jenkins

ENTRYPOINT ["jenkins-slave"]

除了在Pipeline中定義slave的image,咱們也可使用非 Pipeline 類型指定運行該自定義 slave,那麼咱們就須要修改 「系統管理」 —> 「系統設置」 —> 「雲」 —> 「Kubernetes」 —> 「Add Pod Template」 修改配置 「Kubernetes Pod Template」 信息以下:

 

3.6 Jenkins啓動參數調整

默認狀況下,Jenkins保守地生成代理。好比,若是隊列中有2個構建,它將不會當即生成2個執行程序。它會產生一個執行器並等待一段時間讓第一個執行器被釋放,而後再決定產生第二個執行器。Jenkins確保它產生的每一個執行者都獲得最大限度的利用。若是要覆蓋此行爲並當即爲隊列中的每一個構建生成執行程序,能夠在Jenkins啓動時參加一下參數:

-Dhudson.slaves.NodeProvisioner.initialDelay=0
-Dhudson.slaves.NodeProvisioner.MARGIN=50
-Dhudson.slaves.NodeProvisioner.MARGIN0=0.85

 

4、使用GlusterFS共享存儲

Jenkins的平常工做包括大量的編譯構建,構建過程當中會涉及到大量依賴包的下載(好比jar包,npm包等),採用原生容器的方式因爲沒有持久化本地倉庫,每次構建都須要對這些依賴包從新下載,嚴重影響效率。
這裏須要解決公共依賴包持久化存儲的問題,一種作法是配置宿主機目錄掛載的方式,把文件掛載到宿主機,這樣雖然方便可是不夠安全,並且Kubernetes集羣通常有多個Node節點,若是容器在掛了被從新拉起的時候被調度到其餘的Node節點,那映射在原先主機上的數據仍是在原先主機上,新的容器仍是沒有原來的數據。
因此推薦的方法通常都是把數據存儲在遠程服務器上如:NFS,GlusterFS,ceph等,目前主流的仍是使用GlusterFS。事實上,Kubernetes的選擇不少,目前Kubernetes支持的存儲有下面這些:

GCEPersistentDisk
AWSElasticBlockStore
AzureFile
AzureDisk
FC (Fibre Channel)
FlexVolume
Flocker
NFS
iSCSI
RBD (Ceph Block Device)
CephFS
Cinder (OpenStack block storage)
Glusterfs
VsphereVolume
Quobyte Volumes
HostPath (就是剛纔說的映射到主機的方式,多個Node節點會有問題)
VMware Photon
Portworx Volumes
ScaleIO Volumes
StorageOS

Kubernetes有這麼多選擇,GlusterFS只是其中之一,但爲何能夠脫穎而出呢?GlusterFS,是一個開源的分佈式文件系統,具備強大的橫向擴展能力,經過擴展可以支持數PB存儲容量和處理數千客戶端。GlusterFS藉助TCP/IP或InfiniBand RDMA網絡將物理分佈的存儲資源彙集在一塊兒,使用單一全局命名空間來管理數據。GlusterFS的Volume有多種模式,複製模式能夠保證數據的高可靠性,條帶模式能夠提升數據的存取速度,分佈模式能夠提供橫向擴容支持,幾種模式能夠組合使用實現優點互補。

4.1 GlusterFS集羣的部署:

安裝環境: 192.168.XX.A , 192.168.XX.B
GlusterFS集羣的部署比較簡單,在各臺機器分別安裝

# yum install centos-release-gluster
# yum install glusterfs-server
# /etc/init.d/glusterd start

在一臺上面創建信任關係

# gluster peer probe 192.168.XX.B #後面跟另一臺的IP
# gluster peer status

建立名稱爲「jenkins_public」的分佈式卷:

# gluster volume create jenkins_public 192.168.XX.A:/data/exp1 1192.168.XX.B:/data/exp2 force
# gluster volume info 查看邏輯卷信息
# gluster volume start jenkins_public #啓動邏輯卷

4.2 如何在Kubernetes中使用GlusterFS

Kubernetes用PV(PersistentVolume)、PVC(PersistentVolumeClaim)來使用GlusterFS的存儲。PV與GlusterFS的Volume相連,至關於提供存儲設備;PVC消耗PV提供的存儲,由應用部署人員建立,應用直接使用PVC進而使用PV的存儲。
官方文檔對配置過程進行了介紹:https://github.com/kubernetes/examples/blob/master/staging/volumes/glusterfs/README.md

4.2.1 在Kubernetes中建立GlusterFS的端點定義(endpoints)

data1-volume-pv-cluster.json:

{
"kind": "Endpoints",
"apiVersion": "v1",
"metadata": {
"name": "data1-volume-pv-cluster"
},
"subsets": [
{
"addresses": [{ "ip": "192.168.XX.A" }],
"ports": [{ "port": 20 }]
},
{
"addresses": [{ "ip": "192.168.XX.B" }],
"ports": [{ "port": 20 }]
}
]
}

備:該subsets字段應填充GlusterFS集羣中節點的地址。能夠在port字段中提供任何有效值(從1到65535)。

##建立端點:
[root@k8s-master-01 ~]# kubectl create -f data1-volume-pv-cluster.json
##驗證是否已成功建立端點
[root@k8s-master-01 ~]# kubectl get ep |grep data1-volume-pv-cluster
glusterfs-cluster 192.168.XX.A:20,192.168.XX.B:20

4.2.2 配置 service

咱們還須要爲這些端點(endpoint)建立服務(service),以便它們可以持久存在。咱們將在沒有選擇器的狀況下添加此服務,以告知Kubernetes咱們想要手動添加其端點
data1-volume-sv-cluster.json:

{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "data1-volume-pv-cluster",
"namespace": "default",
}
},
"spec": {
"ports": [
{
"protocol": "TCP",
"port": 20,
"targetPort": 20
}
]
}
}

建立服務

[root@k8s-master-01 ]# kubectl create -f data1-volume-sv-cluster.json
##查看service
[root@k8s-master-01 ]# kubectl get service | grep data1-volume-sv-cluster

4.2.3.配置PersistentVolume(簡稱pv)

建立jenkins-public-pv.yaml文件,指定storage容量和讀寫屬性
jenkins-public-pv.yaml:

kind: PersistentVolume
apiVersion: v1
metadata:
labels:
name: jenkins-public-pv
namespace: default
name: jenkins-public-pv
spec:
capacity:
storage: 600Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
glusterfs:
endpoints: data1-volume-pv-cluster
path: jenkins_public
readOnly: false

而後執行:

[root@k8s-master-01 ~]# kubectl create -f jenkins-public-pv.yaml
## 查看pv
[root@k8s-master-01 ~]# kubectl get pv|grep jenkins-public-pv

4.2.4 配置PersistentVolumeClaim(簡稱pvc)

建立jenkins-public-pvc.yaml文件,指定請求資源大小

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: jenkins-public-pvc
namespace: default
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 600Gi
selector:
matchLabels:
name: jenkins-public-pv

而後執行:

[root@k8s-master-01 ~]# kubectl create -f jenkins-public-pvc.yaml
## 查看pvc
[root@k8s-master-01 ~]# kubectl get pvc|grep jenkins-public-pvc

4.2.5 部署Jenkins Slave掛載pvc

修改pipeline,把pvc掛載到容器內的/home/maven_repository,下面是完整的Pipeline:

pipeline {
agent {
kubernetes {
cloud 'kubernetes'
label 'k8s-jenkins-jnlp'
defaultContainer 'jnlp'
yaml """
apiVersion: v1
kind: Pod
metadata:
labels:
node-label: "k8s-jenkins-jnlp-${UUID.randomUUID().toString()}"
spec:
containers:
- name: jnlp
image: harbor.guahao-inc.com/base/jenkins/jnlp-slave:1123_20
volumeMounts:
- mountPath: /home/maven_repository
name: docker-socker-file
volumes:
- name: docker-socker-file
persistentVolumeClaim:
claimName: jenkins-public-pvc
"""
}
}
stages {
stage('Run shell') {
steps {
script {
git 'https://github.com/nbbull/demoProject.git'
sh 'sleep 50'
}
}
}
}

}

能夠把agent的邏輯統一抽象到共享庫,就能夠實現全部的job都經過K8S進行構建,至此部署完成。

5、Jenkins性能調優

除告終合K8S搭建Jenkins集羣保證高可用之外,最後再補充總結一些jenkins自身的性能調優技巧,這裏再也不展開,具體可在使用中去體會和補充。
• 使用Pipeline方式比配置方式運行更快。
• 讓Pipeline作中間組織的工做,而不是取代其餘工具。
• 能用腳本實現的,就不要用插件。
• 根據資源狀況限制併發執行的job數量。
• 使用共享庫抽象公共的代碼並持續優化。
• 不要寫太複雜的Pipeline腳本(>1000行),包括共享庫代碼在內。
• 不要對外網環境有強依賴(萬惡的牆)。
• 使用「@NonCPS」註解高耗代碼方法。• 使用SSD硬盤等高性能硬件• Durability/Speed Options

相關文章
相關標籤/搜索