k8s 中安裝 jenkins

陳順吉發表於2019-03-27

這個系列是描述如何在 kubernetes 環境下建立 jenkins 動態 slave,那什麼是 slave,又為什麼要動態建立呢?首先,大家都使用 jenkins 進行構建以及釋出等操作,但是一旦構建任務多了,一臺 jenkins 可能忙不過來了。此時你就可以建立 slave,將一部分 job 放在 slave 上構建,達到減輕 master 壓力的目的。

但是當構建任務實在太多時,你可能要建立多個 slave 才能滿足需要。問題是可能很多構建任務只是集中在固定的時刻,多數時候 slave 都處於空閒狀態,那麼這時 slave 的資源就處於浪費狀態了。

能不能 slave 在構建任務的時候才建立,構建完成之後自動刪除呢?幸運的是,kubernetes 剛好能夠做到這一點。這個系列的文章就是要實現這一功能,也就是所謂的動態 slave。

使用動態 slave 之後,所有的構建任務都不會在 master 上進行,你也不用為 job 分配 slave,而是隻要有構建任務,kubernetes 就會為這個構建任務建立一個 slave,構建完成之後自動刪除。

聽起來不錯對嗎?那我們就開始吧。我們首先得安裝 jenkins,在這之前你得確保你擁有一個 kubernetes 叢集,因為 jenkins 只能安裝在 kubernetes 叢集中,如果沒有的話就沒有辦法了。

jenkins 的安裝可以使用 helm,也可以手動裝。因為要掛載儲存,手動裝時只能使用 statefulset,而不能使用 deployment。

在這之前,我們要提供一個 nfs 用於存放 jenkins 資料。當然,如果你擁有其他的共享儲存也行,nfs 只是一種最簡單,最廉價的實現。

搭建 nfs

使用 nfs 最大的問題就是寫許可權,你可以 kubernetes 的 securityContext/runAsUser 指定 jenkins 容器中執行 jenkins 的使用者 uid,以此來指定 nfs 目錄的許可權,讓 jenkins 容器可寫;也可以不限制,讓所有使用者都可以寫。這裡為了簡單,就讓所有使用者可寫了。

找一臺主機,安裝 nfs:

# yum install nfs-utils
# systemctl start nfs
# systemctl enable nfs
複製程式碼

建立共享目錄,然後暴露出去:

# mkdir -p /opt/nfs/jenkins-data
# vim /etc/exports
/opt/nfs/jenkins-data 10.20.3.0/24(rw,all_squash)
複製程式碼

這裡的 ip 使用 kubernetes node 節點的 ip 範圍,後面的 all_squash 選項會將所有訪問的使用者都對映成 nfsnobody 使用者,不管你是什麼使用者訪問,最終都會壓縮成 nfsnobody,所以你只要將 /opt/nfs/jenkins-data 的屬主改為 nfsnobody,那麼無論什麼使用者來訪問都具有寫許可權。

這個選項在很多機器上由於使用者 uid 不規範導致啟動程式的使用者不同,但是同時要對一個共享目錄具有寫許可權時很有效。

chown -R nfsnobody. /opt/nfs/jenkins-data/
systemctl reload nfs
複製程式碼

然後在任意一個 node 節點上進行驗證:

# showmount -e NFS_IP
複製程式碼

如果能夠看到 /opt/nfs/jenkins-data 就表示 ok 了。

建立 pv

jenkins 其實只要載入對應的目錄就可以讀取之前的資料,但是由於 deployment 無法定義儲存卷,因此我們只能使用 StatefulSet。

首先建立 pv,pv 是給 StatefulSet 使用的,每次 StatefulSet 啟動都會通過 volumeClaimTemplates 這個模板去建立 pvc,因此你必須得有 pv,才能供 pvc 繫結。

# cd /usr/local/kuberneters/manifests/jenkins
# vim pv.yml
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: jenkins
spec:
  nfs:
    path: /opt/nfs/jenkins-data
    server: 10.20.5.1
  accessModes: ["ReadWriteOnce"]
  capacity:
    storage: 1Ti
複製程式碼

我這裡給了 1t,你看著辦。

建立 serviceAccount

接著是 service account,因為 jenkins 後面需要能夠動態建立 slave,因此它必須具備一些許可權。

# vim service-account.yml
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins

---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: jenkins
rules:
  - 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: RoleBinding
metadata:
  name: jenkins
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: jenkins
subjects:
  - kind: ServiceAccount
    name: jenkins
複製程式碼

建立了一個 RoleBinding 和一個 ServiceAccount,並且將 RoleBinding 的許可權繫結到這個使用者上。所以,jenkins 容器必須使用這個 ServiceAccount 執行才行,不然 RoleBinding 的許可權它將不具備。

RoleBinding 的許可權很容易就看懂了,因為 jenkins 需要建立和刪除 slave,所以才需要上面這些許可權。至於 secrets 許可權,則是 https 證照。

部署 jenkins

我這裡使用了私有的 registry 來下載公網的 jenkins 映象,我之前的文章介紹了它的實現,有興趣可以看看。因為私有映象需要認證,所以這裡也提供了 nexus-pull 這個 secret 進行認證,它的建立很簡單。

kubectl create secret docker-registry nexus-pull --docker-username=admin --docker-password="admin123" --docker-server="registry.ntpstat.com:2222"
複製程式碼

jenkins 部署時需要注意它的副本數,你的副本數有多少就要有多少個 pv,同樣,儲存會有多倍消耗。這裡我只使用了一個副本,因此前面也只建立了一個 pv。

# vim statefulset.yml
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
  name: jenkins
  labels:
    name: jenkins
spec:
  serviceName: jenkins
  replicas: 1
  updateStrategy:
    type: RollingUpdate
  template:
    metadata:
      name: jenkins
      labels:
        name: jenkins
    spec:
      terminationGracePeriodSeconds: 10
      serviceAccountName: jenkins
      imagePullSecrets:
        - name: nexus-pull
      containers:
        - name: jenkins
          image: registry.ntpstat.com:2222/jenkins/jenkins:lts
          # 時刻保持映象最新
          imagePullPolicy: Always
          ports:
            - containerPort: 8080
            - containerPort: 50000
          resources:
            limits:
              cpu: 4
              memory: 4Gi
            requests:
              cpu: 4
              memory: 4Gi
          env:
            - name: LIMITS_MEMORY
              valueFrom:
                resourceFieldRef:
                  resource: limits.memory
                  divisor: 1Mi
            - name: JAVA_OPTS
              # value: -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -XshowSettings:vm -Dhudson.slaves.NodeProvisioner.initialDelay=0 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
              value: -Xmx$(LIMITS_MEMORY)m -XshowSettings:vm -Dhudson.slaves.NodeProvisioner.initialDelay=0 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
          volumeMounts:
            - name: jenkins-home
              mountPath: /var/jenkins_home
          livenessProbe:
            httpGet:
              path: /login
              port: 8080
            initialDelaySeconds: 60
            timeoutSeconds: 5
            failureThreshold: 12 # ~2 minutes
          readinessProbe:
            httpGet:
              path: /login
              port: 8080
            initialDelaySeconds: 60
            timeoutSeconds: 5
            failureThreshold: 12 # ~2 minutes
  # pvc 模板,對應之前的 pv
  volumeClaimTemplates:
    - metadata:
        name: jenkins-home
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 1Ti
複製程式碼

建立 service

為 ingress 建立 service。

# vim service.yml
---
apiVersion: v1
kind: Service
metadata:
  name: jenkins
spec:
  # type: LoadBalancer
  selector:
    name: jenkins
  # ensure the client ip is propagated to avoid the invalid crumb issue when using LoadBalancer (k8s >=1.7)
  #externalTrafficPolicy: Local
  ports:
    - name: http
      port: 80
      targetPort: 8080
      protocol: TCP
    - name: agent
      port: 50000
      protocol: TCP
複製程式碼

安裝 ingress

jenkins 的 web 介面需要從叢集外訪問,這裡我們選擇的是使用 ingress,ingress controller 的實現有多種,我選擇 traefik。關於 traefik 的安裝和使用可以檢視我之前的文章

traefik 安裝好後,我們需要定義 ingress,也就是所謂的 nginx 虛擬主機的定義。因為我們已經有了證照,所以這裡同樣為 jenkins 的訪問開啟 https。

# vim ingress.yml
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: jenkins
spec:
  rules:
    - http:
        paths:
          - path: /
            backend:
              serviceName: jenkins
              servicePort: 80
      host: jenkins.ntpstat.com
  tls:
    - hosts:
        - jenkins.ntpstat.com
      secretName: ntpstat.com
複製程式碼

此時你的目錄下一共存在 5 個檔案,可以直接一條命令直接部署:

kubectl apply -f .
複製程式碼

通過 kubectl get pods 檢視是否執行成功,一旦出錯,可以通過 kubectl describe/log 來檢視哪裡出現問題。

訪問 jenkins

安裝成功後,你就可以直接訪問了。這裡要注意你的 traefik 安裝在了哪些節點上,如果像我一樣只安裝在了某些節點而不是所有節點的話,你的 jenkins 的域名應該解析到 traefik 所在的節點。所有 dns 記錄的直接寫 hosts,但是最好還是使用 dns。

我這裡寫好 hosts 之後,直接在瀏覽器上訪問 jenkins.ntpstat.com 就 ok 了,它會直接跳轉到 https。

然後在 nfs 儲存的目錄下找到 secrets/initialAdminPassword,接著下載它推薦的外掛即可。如果不能直接訪問外網下載,jenkins 可以直接配置代理,關於代理的配置,我這篇檔案講的很清楚。

好了,這篇文章就到這裡了,下篇就會提到如何實現動態 slave。

相關文章