提到基於Kubernete的CI/CD,可以使用的工具有很多,比如Jenkins、Gitlab CI已經新興的drone之類的,
這裏會使用大家最爲熟悉的Jenkins來做CI/CD的工具。
安裝
既然要基於Kubernetes來做CI/CD,這裏需要將 Jenkins 安裝到 Kubernetes 集羣當中,
新建一個 Deployment:(jenkins_deployment.yaml)
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-jenkins #deployment名稱
namespace: jenkins #命名空間
spec:
replicas: 1
selector:
matchLabels:
app: jenkins
template:
metadata:
labels:
app: jenkins
spec:
terminationGracePeriodSeconds: 10 #優雅停止pod
serviceAccount: jenkins #後面還需要創建服務賬戶
containers:
- name: jenkins
image: registry:8304/jenkins:lts #鏡像版本
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080 #外部訪問端口
name: web
protocol: TCP
- containerPort: 50000 #jenkins save發現端口
name: agent
protocol: TCP
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 500m
memory: 512Mi
livenessProbe:
httpGet:
path: /login
port: 8080
initialDelaySeconds: 60 #容器初始化完成後,等待60秒進行探針檢查
timeoutSeconds: 5
failureThreshold: 12 #當Pod成功啓動且檢查失敗時,Kubernetes將在放棄之前嘗試failureThreshold次。放棄生存檢查意味着重新啓動Pod。而放棄就緒檢查,Pod將被標記爲未就緒。默認爲3.最小值爲1
readinessProbe:
httpGet:
path: /login
port: 8080
initialDelaySeconds: 60
timeoutSeconds: 5
failureThreshold: 12
volumeMounts: #需要將jenkins_home目錄掛載出來
- name: jenkinshome
subPath: jenkins
mountPath: /var/jenkins_home
env:
- name: LIMITS_MEMORY
valueFrom:
resourceFieldRef:
resource: limits.memory
divisor: 1Mi
- name: JAVA_OPTS
value: -Xmx$(LIMITS_MEMORY)m -XshowSettings:vm -Dhudson.slaves.NodeProvisioner.initialDelay=0 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85 -Duser.timezone=Asia/Shanghai
securityContext:
fsGroup: 1000
volumes:
- name: jenkinshome
persistentVolumeClaim:
claimName: pvc-jenkins #這裏將上面創建的pv關聯到pvc上
所有的對象資源都放置在一個名爲 jenkins 的 namespace 下面,所以需要添加創建一個 namespace:
$ kubectl create namespace jenkins
這裏使用一個稱爲jenkins / jenkins:lts的副本,這是jenkins官方的Docker所有權,
然後也有一些環境變量,當然也可以根據自己的需求來定製一個實例,
這樣可以將一些插件打包在自定義的其中實質,
可以參考文檔:https : //github.com/jenkinsci/docker,這裏使用替換的官方替代就行,
另外一個還需要注意的是將容器的/data/jenkins/jenkins_volume目錄掛載到了一個稱爲opspvc的PVC對象上面,
所以同樣還得提前創建了一個對應的PVC對象,當然也可以使用前面的StorageClass對象來自動創建:
(jenkins_pv.yaml)
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-jenkins
namespace: jenkins
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Delete
nfs:
server: k8s-node-1
path: /data/jenkins/jenkins_volume
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc-jenkins
namespace: jenkins
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
創建需要用到的PVC對象:
$ kubectl create -f jenkins_pv.yaml
另外這裏還需要使用到一個擁有相關權限的服務帳戶:jenkins,
這裏只是給jenkins授予了一些必要的權限,當然如果你對serviceAccount的權限不是很熟悉的話,
給這個sa綁定一個cluster-admin的授權角色權限也是可以的,當然這樣具有一定的安全風險:(jenkins_rbac.yaml)
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins
namespace: jenkins
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: jenkins
rules:
- apiGroups: ["extensions", "apps"]
resources: ["deployments"]
verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
- apiGroups: [""]
resources: ["services"]
verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["create","delete","get","list","patch","update","watch"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create","delete","get","list","patch","update","watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get","list","watch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: jenkins
namespace: jenkins
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: jenkins
subjects:
- kind: ServiceAccount
name: jenkins
namespace: jenkins
創建rbac相關的資源對象:
$ kubectl create -f rbac.yaml
serviceaccount "jenkins" created
role.rbac.authorization.k8s.io "jenkins" created
rolebinding.rbac.authorization.k8s.io "jenkins" created
最後爲了方便測試,這裏通過NodePort的形式來暴露Jenkins的web服務,固定爲30002端口,另外還需要暴露一個代理的端口,這個端口主要是使用Jenkins的主控和slave之間通信使用的。
一切準備的資源準備好過後,直接創建Jenkins服務:
$ kubectl create -f jenkins_deployment.yaml
deployment.extensions "jenkins" created
service "jenkins" created
創建完成後,要去拉取適當可能需要等待一會兒,然後查看下Pod的狀態:
$ kubectl get pods -n jenkins
NAME READY STATUS RESTARTS AGE
jenkins-7f5494cd44-pqpzs 0/1 Running 0 2m
可以看到該Pod處於運行狀態,但READY值確實爲0,然後用describe命令去查看下該Pod的詳細信息:
$ kubectl describe pod jenkins-7f5494cd44-pqpzs -n jenkins
...
Normal Created 3m kubelet, k8s-node-1 Created container
Normal Started 3m kubelet, k8s-node-1 Started container
Warning Unhealthy 1m (x10 over 2m) kubelet, k8s-node-1 Liveness probe failed: Get http://10.244.1.165:8080/login: dial tcp 10.244.1.165:8080: getsockopt: connection refused
Warning Unhealthy 1m (x10 over 2m) kubelet, k8s-node-1 Readiness probe failed: Get http://10.244.1.165:8080/login: dial tcp 10.244.1.165:8080: getsockopt: connection refused
可以看到上方的警告信息,健康檢查沒有通過,具體原因是什麼引起的呢??可以通過查看日誌進一步瞭解:
$ kubectl logs -f jenkins-7f5494cd44-pqpzs -n jenkins
touch: cannot touch '/data/jenkins/jenkins_volume/copy_reference_file.log': Permission denied
Can not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?
很明顯可以看到上面的錯誤信息,意味着就是沒有權限在jenkins的目錄下創建文件,這是因爲交替的替換使用的是jenkins這個用戶,而通過PVC掛載到nfs服務器的共享數據目錄下面卻是root用戶的,所以沒有權限訪問該目錄,
要解決該問題,也很簡單,我只需要在nfs共享數據目錄下面把的目錄權限重新分配下即可:
$ chown -R 1000 /data/jenkins/jenkins_volume
然後再重新創建:
$ kubectl delete -f jenkins_deployment.yaml
deployment.extensions "jenkins" deleted
service "jenkins" deleted
$ kubectl create -f jenkins_deployment.yaml
deployment.extensions "jenkins" created
service "jenkins" created
現在再去查看新生成的Pod已經沒有錯誤信息了:
$ kubectl get pods -n jenkins
NAME READY STATUS RESTARTS AGE
jenkins-7f5494cd44-smn2r 1/1 Running 0 25s
等到服務啓動成功後,就可以根據任意優先級的IP:30002端口就可以訪問jenkins服務了,
可以根據提示信息進行安裝配置即可:初始化的密碼可以在jenkins的容器的日誌中進行查看,也可以直接在nfs的共享數據目錄中查看:
$ cat /data/jenkins/jenkins_volume/jenkins/secrets/initAdminPassword
然後選擇安裝推薦的插件即可。
安裝完成後添加管理員帳號即可進入到jenkins主界面:
優點
Jenkins安裝完成了,瞭解下在Kubernetes環境下面使用Jenkins有什麼好處。
持續整合與發佈是日常工作中必不可少的一個步驟,目前主要公司都採用Jenkins進行來構建符合需求的CI / CD流程,而傳統的Jenkins Slave一主多從方式會存在一些痛點,例如:
- 主節點發生單點故障時,整個流程都不可用了
- 每個Slave的配置環境不一樣,來完成不同語言的編譯打包等操作,但是這些差異化的配置導致管理起來非常不方便,維護起來也是比較費勁
- 資源分配不均衡,有的從屬要運行的作業出現排隊等待,而有的從屬處於空閒狀態
- 資源有浪費,每臺Slave可能是物理機或虛擬機,當Slave處於空閒狀態時,也不會完全釋放掉資源。
正因爲上面的這些這些種種痛點,渴望一種更高效更可靠的方式來完成這個CI / CD流程,而Docker虛擬化容器技術能很好的解決這個痛點,又特別是在Kubernetes可行的環境下面能夠更好來解決上面的問題,下圖是基於Kubernetes構造的Jenkins實現的簡單示意圖:
從圖上可以看到Jenkins Master和Jenkins Slave以Pod形式運行在Kubernetes的節點上,Master運行在其中一個實例,並且將其配置數據存儲到一個Volume上去,Slave運行在多個上面,並且它不是一直處於運行狀態,它會按照需求動態的創建並自動刪除。
這種方式的工作流程大致爲:當Jenkins Master接受到Build請求時,會根據配置的標籤動態創建一個運行在Pod中的Jenkins Slave並註冊到Master上,當運行完Job後,這個Slave會被替換並且這個Pod也會自動刪除,恢復到最初狀態。
好處:
- 服務高可用,當Jenkins Master出現故障時,Kubernetes會自動創建一個新的Jenkins Master容器,並將卷分配給新創建的容器,保證數據不丟失,從而達到可用服務高可用。
- 動態擴展,合理使用資源,每次運行時,會自動創建一個Jenkins Slave,Job完成後,Slave自動替換並刪除容器,資源自動釋放,並且Kubernetes會根據每個資源的使用情況,動態分配Slave到本身的上游上創造,降低出現因某某資源的累積高,還排隊等待在該例程的情況下。
- 擴展性好,當Kubernetes擴展的資源嚴重不足而導致Job排隊等待時,可以很容易的添加一個Kubernetes Node到可以中,從而實現擴展。
配置
配置Jenkins,讓他能夠動態的生成Slave的Pod。
- 第1步。需要安裝kubernetes插件,單擊管理Jenkins- >管理插件->可用-> Kubernetes插件即可安裝即可。
- 第2步。安裝完畢後,單擊管理Jenkins->配置系統->(拖到最下方)添加新雲->選擇Kubernetes,然後填充Kubernetes和Jenkins配置信息。
注意namespace,這裏填jenkins,然後單擊Test Connection,
如果出現Connection test success的提示信息證明Jenkins已經可以和Kubernetes系統正常通信了,
下方的Jenkins URL地址:http://jenkins.jenkins .svc.cluster.local:8080,
此處的格式爲:服務名.namespace.svc.cluster.local:8080,根據某些創建的jenkins的服務名填寫,
我這裏是之前創建的稱爲jenkins,如果是用上面創造的就應該是jenkins - 第3步。配置Pod模板,實際上就是配置Jenkins Slave運行的Pod模板,命名空間同樣是用jenkins,
標籤這裏也非常重要,對於後面執行Job的時候需要用到該值,然後這裏使用的是cnych/jenkinsjnlp這個更大,
這個附加是在官方的jnlp之上基礎上定製的,加入了kubectl等一些實用的工具。
另外需要注意這裏需要在下面掛載兩個主機目錄,一個是/var/run/docker.sock,該文件是用於Pod中的容器能夠共享託管機的Docker,這就是大家說的docker in docker的方式,Docker二進制文件已經打包到上面的高度中了,另外一個目錄下/root/.kube目錄,將這個目錄掛載到容器的/root/.kube目錄下面這是爲了讓能夠在Pod的容器中能夠使用kubectl工具來訪問的Kubernetes。在Slave Pod部署Kubernetes應用。
另外還有幾個參數需要注意,如下所示的時間,以分鐘爲單位在空閒時保留從屬,這個參數表示的意思是當處於位置狀態的時候保留Slave Pod多連續,這個參數最好保存最小就行了,如果您設置過大的話,工作任務執行完成後,對應的Slave Pod就不會立即被銷燬刪除。
另外一些同學在配置了後運行Slave Pod的時候出現了權限問題,因爲Jenkins Slave Pod中沒有配置權限,所以需要配置上ServiceAccount,在Slave Pod配置的地方單擊下面的高級,添加上對應的ServiceAccount即可:
還有一些同學在配置完成後發現啓動Jenkins Slave Pod的時候,出現Slave Pod連接不上,然後嘗試100次連接之後銷燬Pod,然後會再創建一個Slave Pod繼續嘗試連接,無限循環,一片下面的信息:
如果出現這種情況的話就需要將Slave Pod中的運行命令和參數兩個值給清空掉
到這裏的Kubernetes插件插件就算配置完成了。
測試
Kubernetes插件的配置工作完成了,添加一個Job任務,看是否能夠在Slave Pod中執行,任務執行完成後看Pod是否會被銷燬。
在Jenkins首頁點擊創造新工作,創建一個測試的任務,輸入任務名稱,然後選擇Freestyle project類型的任務:
注意在下面的Label Expression這裏要填入jenkins-slave,就是前面配置的Slave Pod中的Label,這兩個地方必須保持一致
然後往下拉,在Build區域選擇Execute shell
然後輸入測試命令
echo "測試 Kubernetes 動態生成 jenkins slave"
echo "==============docker in docker==========="
docker info
echo "=============kubectl============="
kubectl get pods --all-namespaces
echo "=============kubectl============="
echo 172.17.180.105 git-host >> /etc/hosts
ping git-host -c4
chmod +x /usr/local/maven/bin/mvn
/usr/local/maven/bin/mvn -version
現在直接在頁面上點擊生成的立即構建觸發並立即,然後觀察Kubernetes移植中Pod的變化
可以看到在點擊立即生成的時候可以看到一個新的Pod:jenkins-slave被創造了,這就是的Jenkins Slave。任務執行完成後可以看到任務信息
到這裏證明的任務已經完成,然後這個時候再去繼承查看的Pod列表,發現jenkins這個命名空間下面已經沒有之前的Slave這個Pod了
jenkins-slave 模式部署完成!