事前準備
設定 Helm Repository
helm repo add jenkinsci https://charts.jenkins.io
helm repo update
# 可以使用以下指令查詢 Jenkins repo 存在的 charts
helm search repo jenkinsci
建立 Namespace
kubectl create namespace jenkins
建立持久性儲存 PV
- 如果測試環境是使用 minikube,可直接參考 官網範例
- 建立 Local StorageClass,但需要能接受當節點不可被調度(如 CPU 使用率滿載)造成服務中斷跟資料遺失等等風險
- 使用 Dynamic provisioner (例如:Rook),則不用手動建立 PV
這邊使用 Local StorageClass 為例,這種方式有個好處,因為設定 WaitForFirstConsumer 時,可以延遲 Volume Binding 讓 scheduler 可以考慮所有 constrains,因此當 Local PV 建立好後,之後建立的 Pod 使用 Local StorageClass 時就會自動被調度到 PV 的節點上,參考官網 Volume local。
需要先決定 Local PV 要建立在哪個節點,這邊選用我的節點 k8s-1。
首先在節點 k8s-1 建立 Local PV 需要的目錄
mkdir -p /mnt/disks/ssd1
sudo chown 1000:1000 /mnt/disks/ssd1
以下為 StorageClass、PV 的 manifest 檔案,命名為 jenkins-01-volume.yaml ,如果節點名稱不一樣,需要調整 PV 的 nodeAffinity 的值:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner # indicates that this StorageClass does not support automatic provisioning
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: jenkins-pv
spec:
capacity:
storage: 20Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /mnt/disks/ssd1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-1
建立 Resource Objects:
kubectl apply -f jenkins-01-volume.yaml
建立 Service Account
- 檔名:
jenkins-02-sa.yaml - 官網連結:jenkins-02-sa.yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins
namespace: jenkins
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: jenkins
rules:
- apiGroups:
- '*'
resources:
- statefulsets
- services
- replicationcontrollers
- replicasets
- podtemplates
- podsecuritypolicies
- pods
- pods/log
- pods/exec
- podpreset
- poddisruptionbudget
- persistentvolumes
- persistentvolumeclaims
- jobs
- endpoints
- deployments
- deployments/scale
- daemonsets
- cronjobs
- configmaps
- namespaces
- events
- secrets
verbs:
- create
- get
- watch
- delete
- list
- patch
- update
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- list
- watch
- update
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: jenkins
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: jenkins
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:serviceaccounts:jenkins
建立 Resource Objects:
kubectl apply -f jenkins-02-sa.yaml
安裝 jenkins
調整 Helm Chart 參數
從官網下載 jenkins-values.yaml,或者使用以下指令匯出成檔案:
helm show values jenkinsci/jenkins
需要調整的部份如下,:
- controller:
- serviceType: LoadBalancer
- 如果沒有 LoadBalancer 可以使用 NodePort,但我這邊使用 MetalLB,安裝方式可參考另一篇文章 『Kubernetes 外部存取:MetalLB 裸機安裝與連線測試』
- servicePort: 80
- 如果使用 NodePort 就要調整為 30000–32767 其中一項,例如 32000
- serviceType: LoadBalancer
- persistence:
- storageClass: local-storage
- 使用上述建立的 StorageClass
- storageClass: local-storage
- serviceAccount:
- create: false
- name: jenkins
- 使用上述建立的 ServiceAccount 名稱
- rbac:
- create: false
- loadBalancerSourceRanges:
- 0.0.0.0/0
(# 如果使用 MetalLB 將這行移除,問題)
- 0.0.0.0/0
安裝
helm install jenkins jenkinsci/jenkins -n jenkins -f jenkins-values.yaml
取的帳號 admin 的密碼:
kubectl get secret -n jenkins jenkins -o jsonpath="{.data.jenkins-admin-password}" | base64 -d ; echo
如果使用 LoadBalancer 使用以下指令取得 External IP
kubectl get service jenkins -n jenkins -o jsonpath="{.status.loadBalancer.ingress[0].ip}" ; echo
使用 MetalLB 時遇到的問題
當 LoadBalancer 使用 MetalLB 時,如果 jenkins chart 的 value.yaml 設定 controller.loadBalancerSourceRanges: [ 0.0.0.0/0 ] 時,發現 kube-proxy 在調整 iptables 時造成無法連線。
依照下面的資訊,流程如下:
- 可以看到在 chain
KUBE-PROXY-FIREWALL因為符合了 ipsetKUBE-LOAD-BALANCER-FW內容而跳至 chainKUBE-SOURCE-RANGES-FIREWALL - 在 chain
KUBE-SOURCE-RANGES-FIREWALL第一條規則因為 ipsetKUBE-LOAD-BALANCER-SOURCE-CIDR為空所以不符合,繼續在同一條 chain 的下條規則 - 在下條規則中,ipset
KUBE-LOAD-BALANCER-SOURCE-IP因為只有一條紀錄172.18.8.202,tcp:80,172.18.8.202,但是因為這條的第三個欄位 src 是要等於 Service 本身的 IP172.18.8.202,所以外部的來源 IP 都無法符合,故此規則也不符合,繼續在同一條 chain 的下條規則 - 最後一條規則是直接 DROP 掉封包,所以無法連線成功
kube-proxy 的 chain 如下:
-A KUBE-PROXY-FIREWALL -m comment --comment "Kubernetes service load balancer ip + port for load balancer with sourceRange" -m set --match-set KUBE-LOAD-BALANCER-FW dst,dst -j KUBE-SOURCE-RANGES-FIREWALL
-A KUBE-SOURCE-RANGES-FIREWALL -m comment --comment "Kubernetes service load balancer ip + port + source cidr for packet filter purpose" -m set --match-set KUBE-LOAD-BALANCER-SOURCE-CIDR dst,dst,src -j RETURN
-A KUBE-SOURCE-RANGES-FIREWALL -m comment --comment "Kubernetes service load balancer ip + port + source IP for packet filter purpose" -m set --match-set KUBE-LOAD-BALANCER-SOURCE-IP dst,dst,src -j RETURN
-A KUBE-SOURCE-RANGES-FIREWALL -j DROP
ipset 如下:
Name: KUBE-LOAD-BALANCER-FW
Type: hash:ip,port
Revision: 5
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 256
References: 1
Number of entries: 1
Members:
172.18.8.202,tcp:80
Name: KUBE-LOAD-BALANCER-SOURCE-CIDR
Type: hash:ip,port,net
Revision: 7
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 456
References: 1
Number of entries: 0
Members:
Name: KUBE-LOAD-BALANCER-SOURCE-IP
Type: hash:ip,port,ip
Revision: 5
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 280
References: 1
Number of entries: 1
Members:
172.18.8.202,tcp:80,172.18.8.202
可以用以下指令去紀錄封包是否真的進入到 DROP 那條規則,但是因為 chain KUBE-SOURCE-RANGES-FIREWALL 是由 kube-proxy 管理,所以定時會被覆蓋,使用以下指令後要迅速使用瀏覽器或 curl 指令連線測試。
sudo iptables -I KUBE-SOURCE-RANGES-FIREWALL 3 -j LOG --log-prefix "KUBE_SOURCE_DROP_POINT: " --log-level 7 ; iptables-save | grep 'KUBE-SOURCE-RANGES-FIREWALL'
查看紀錄:
sudo dmesg | grep 'KUBE_SOURCE_DROP_POINT'
# 或者
sudo journalctl -k | grep 'KUBE_SOURCE_DROP_POINT'
整個 Kubernetes cluster 重啟後 jenkins 無法啟動
我把 Kubernetes lab 整個關掉,隔天再次啟動後發現 jenkins 無法正常啟動,查看 events 顯示 Back-off restarting failed container init in pod jenkins-0_jenkins,所以透過指令 kubectl -n jenkins logs jenkins-0 -c init 查看 InitContainer init 的訊息,發現最後兩行 logs 如下:
copy plugins to shared volume
cp: overwrite '/var/jenkins_plugins/antisamy-markup-formatter.jpi'? ...
查看 ConfigMap jenkins (apply_config.sh)的最後幾行指令如下:
echo "copy plugins to shared volume"
# Copy plugins to shared volume
yes n | cp -i /usr/share/jenkins/ref/plugins/* /var/jenkins_plugins/;
echo "finished initialization"
我認為跟 yes n | cp -i 那行指令有關,調整成使用 -n 參數,然後重啟 Kubernetes cluster 後 jenkins pod 正常啟動
cp -n /usr/share/jenkins/ref/plugins/* /var/jenkins_plugins/;