Cài đặt và thiết lập Jenkins trên Kubernetes

Share

Bài viết này mình sẽ tóm tắt cách thiết lập Jenkins trên Kubenretes và thực hiện một job đơn giản.

Về cơ bản, chúng ta deploy Jenkins master, con các agent sẽ được khởi tạo mỗi khi job chạy, sau khi job hoàn thành thì tự động được xóa, agent này chính là các pod của Kubernetes cluster.

Như vậy hệ thống Jenkins CI/CD sẽ có khả năng scale up dễ dàng. Vui lòng xem các bài viết trước để biết cách mà mình xây dụng Kubernetes cluster trên máy chủ chạy Microsoft Hyper-V Server. Bây giờ chúng ta sẽ bắt đầu.

Cấu hình các Resources của K8s

Mình không sử dụng Helm, dưới đây là các file yaml cấu hình các resource sẽ khởi tạo trên K8s cluster.

deployment.yaml

Mình sử dụng docker image ghcr.io/nvtienanh/jenkins-master:latest dựa trên image gốc jenkins:alpine nhưng được cài đặt sẵn một vài plugin:

  • kubernetes
  • kubernetes-cli
  • ldap
  • git
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jenkins
  labels:
    app: jenkins
  namespace: jenkins
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jenkins
  template:
    metadata:
      labels:
        app: jenkins
    spec:
      containers:
        - name: jenkins
          image: ghcr.io/nvtienanh/jenkins-master:latest
          imagePullPolicy: Always
          env:
            - name: LIMITS_MEMORY
              valueFrom:
                resourceFieldRef:
                  resource: limits.memory
                  divisor: 1Mi
            - name: JAVA_OPTS
              value: -Djenkins.install.runSetupWizard=false -Xmx$(LIMITS_MEMORY)m -XshowSettings:vm -Dhudson.slaves.NodeProvisioner.initialDelay=0 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
          ports:
            - name: http
              containerPort: 8080
            - name: jnlp
              containerPort: 50000
          volumeMounts:
          - name: jenkins-home
            mountPath: /var/jenkins_home
      volumes:
        - name: jenkins-home
          persistentVolumeClaim:
            claimName: jenkins-pvc

service.yaml

apiVersion: v1
kind: Service
metadata:
  labels:
    app: jenkins
  namespace: jenkins
  name: jenkins
spec:
  type: LoadBalancer
  ports:
    - name: http
      port: 80
      targetPort: 8080
      protocol: TCP
    - name: jnlp
      port: 50000
      targetPort: 50000
      protocol: TCP
  selector:
    app: jenkins

ingress.yaml

  • File này định nghĩa Ingress, cần phải sửa lại jenkins.yourdomain.com thành domain jenkins của bạn.
  • Mình sử dụng ssl letsencrypt-dev để dùng https khi truy cập vào jenkins server xem thêm trong file cluster-issuer.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: jenkins
  namespace: jenkins
  labels:
    app: jenkins
  annotations:
    kubernetes.io/ingress.class: nginx 
    cert-manager.io/cluster-issuer: letsencrypt-dev
spec:
  tls:
  - hosts:
    - jenkins.yourdomain.com
    secretName: ssl-jenkins
  rules:
  - host: jenkins.yourdomain.com
    http:
      paths:
        - path: /
          pathType: Prefix
          backend:
            service:
                name: jenkins
                port:
                  number: 80

cluster-issuer.yaml

  • letsencrypt-dev: là selfSigned certificate dùng trong môi trường dev
  • letsencrypt-staging: letsencrypt staging dùng trong môi trường staging. Sau khi mọi thử ok dùng ssl staging này để kiểm tra.
  • letsencrypt-prod: ssl certificate dùng trong môi trường production.
# Dev ClusterIssue
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-dev
spec:
  selfSigned: {}
---

# Staging ClusterIssue
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    # The ACME server URL
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: your@email.com
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-staging
    # Enable the HTTP-01 challenge provider
    solvers:
    - http01:
        ingress:
          class: nginx

# Production ClusterIssue
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    # The ACME server URL
    server: https://acme-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: your@email.com
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-prod
    # Enable the HTTP-01 challenge provider
    solvers:
    - http01:
        ingress:
          class: nginx

storage.yaml

File này định nghĩa phương thức lưu dữ liệu của Jenkins, các log, build report sẽ được lưu trên này. Trong này mình lưu trên NFS server, các bạn có thể sử dụng theo loại storage của mình (nhớ tên jenkins-pvc dùng ở Deployment)

apiVersion: v1
kind: PersistentVolume
metadata:
  name: jenkins-pv
  labels:
    app: jenkins
    nfs-subdir-external-provisioner: nfs-subdir-external-provisioner
spec:
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Delete
  storageClassName: ""
  mountOptions:
    - nfsvers=3
  nfs:
    server: 10.10.0.1
    path: /c/jenkins
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: jenkins-pvc
  namespace: jenkins
  labels:
    app: jenkins
spec:
  accessModes:
    - ReadWriteMany
  volumeMode: Filesystem
  storageClassName: ""
  selector:
    matchLabels:
      app: jenkins
      nfs-subdir-external-provisioner: nfs-subdir-external-provisioner
  resources:
    requests:
      storage: 10Gi

jenkins-service-account.yaml

File này sẽ định nghĩa 1 Service Account (SA) dành cho Jenkins master, nhờ đó mà jenkins có thể sử dụng các resource của Kubernetes cluster.

Tùy theo nhu cầu riêng mà có thể thêm bớt các quyền và phạm vi sử dụng của của SA. Tham khảo thêm trên google nhé.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins
  namespace: jenkins
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: jenkins
  namespace: 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: ["events"]
  verbs: ["watch"]
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: jenkins
  namespace: jenkins
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: jenkins
subjects:
- kind: ServiceAccount
  name: jenkins

Cài đặt Jenkins

Upload các file yaml lên Microsoft Hyper-V Server bằng Windows Admin Center, sau đó mở PowerShell của server.

kubectl create namespace jenkins
kubectl apply -f .\deployment.yaml
kubectl apply -f .\service.yaml
kubectl apply -f .\storage.yaml
kubectl apply -f .\cluster-issuer.yaml
kubectl apply -f .\ingress.yaml
kubectl apply -f .\jenkins-service-account.yaml

Bạn có thể mở Kubernetes Dashboard để kiểm tra xem các resources đã được deploy thành công hay chưa.

Cấu hình Jenkins và Kubernetes

Truy cập vào Jenkins tại đường dẫn https://jenkins.yourdomain.com để thiết lập.

Vào Manage Jenkins -> Manage Nodes and Clouds -> Configure Cloud. Rồi chọn Add a new cloud Kubernetes.

  • Kubernetes URL: https://ip-master-node:6443, giá trị này lấy từ lệnh kubectl cluster-info
  • Jenkins URL: http://jenkins
  • Tạo mới Pod Template:
    • Name: jenkins-slave
    • Namespace: jenkins
    • Label: jenkins-slave
    • Usage: Only build jobs with label expressions matching this node.
  • Tạo mới Container Template
    • Name: jnlp
    • Docker image: ghcr.io/nvtienanh/jenkins-dind-kubectl:latest
  • Host Path Volume:
    • Host Path: /var/run/docker.sock
    • Mount Path: /var/run/docker.sock

Bước tiếp theo, lưu token của K8s Service Account vào Jenkins Credential. Vào Powershell của Hyper-V Server:

$secret_name = (kubectl get sa jenkins -n jenkins -o json | ConvertFrom-Json).secrets[0].name
$token = (kubectl get secret $secret_name -n jenkins -o json | ConvertFrom-Json).data.token
$DecodedToken=[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($token))
Write-Host $DecodedToken

Copy kết quả trả về vào 1 trình soạn thảo để xóa các ký tự xuống dòng. Tiếp theo bên phía Jenkins:

Vào Manage Jenkins -> Manage Credentials -> Add Credentials:

  • Kind: Secret text
  • Scope: Global
  • Secret: token từ Powershell đã xóa các ký tự xuống dòng ở trên.
  • ID: đặt tên gợi nhớ tùy ý

Ok, bây giờ trở lại phần Manage Jenkins -> Manage Nodes and Clouds -> Configure Cloud. Trong phần Credentials chọn Jenkins Service Account.

Chạy thử Jenkins Pipeline

Tạo thử một Pipeline

pipeline {
    agent {
        label 'jenkins-slave'
    }

    stages {
        stage('Check version') {
            steps {
                sh 'docker version'
                sh 'kubectl version'
            }
        }
    }
}