สร้าง CI/CD ด้วย Jenkins บน Kubernetes แบบง่ายๆ
ในเรื่องของ DevOps นั้นที่ผสานขั้นตอนของ Development และ Operation เข้าด้วยกันอย่างไร้รอยต่อ การทำ automation สำหรับขั้นตอนการ build + test + release เป็นสิ่งที่จำเป็นอย่างยิ่ง และ deploy บนระบบที่มีความเสถียรสูงอย่าง Kubernetest นั้นก็เป็นที่นิยมอย่างแพร่หลาย การใช้ Jenkins สำหรับรวมกระบวนการ CI/CD เข้าเป็น pipeline ก็ทำให้สะดวกและลดงานที่ต้องทำลงได้เยอะ ในบทความนี้จะลองสร้าง CI/CD pipeline ด้วย Jenkins บน Kubernetest ที่ใช้ Minikube ที่เป็น Cluster เดี่ยวและทำงานบนเครื่องทั่วไปได้ ของ Spring Boot application อย่างง่ายๆ
เริ่มต้นใช้งาน Minikube
ในบทความนี้จะใช้งาน Minikube
เป็น Kubernetes ซึ่งมีเพียง Cluster เดียวเท่านั้นสำหรับให้ใช้งานในเครื่อง ดังนั้นก่อนอื่นต้องเริ่มต้นใช้งาน Minikube ในเครื่องก่อน ด้วยคำสั่ง minikube start
หลังจากนั้นรอสักครู่เพื่อให้ Kubernetest cluster
ได้เริ่มทำงานในเครื่องของเรา
$ minikube start
Starting local Kubernetes v1.10.0 cluster...
Starting VM...
Getting VM IP address...
Moving files into cluster...
Setting up certs...
Connecting to cluster...
Setting up kubeconfig...
Starting cluster components...
Kubectl is now configured to use the cluster.
Loading cached images from config file.
สามารถทดสอบว่า Minikube cluster ทำงานได้หรือไม่ด้วยการเปิด dashboard ด้วยคำสั่ง minikube dashboard
จะเปิด browser ให้เองอัตโนมัติ ซึ่งจะแสดงค่า default ต่างๆ อย่างเช่น Service
และ Secrets
ของ Kubernetes สามารถใช้ดูข้อมูลได้ง่าย
สร้าง Jenkins Docker image
ก่อนอื่นต้องเตรียม Docker image สำหรับ Jenkins โดยที่ปรับเปลี่ยนเพิ่ม default admin user ของ Jenkins ซึ่ง base จาก jenkinsci
LTS และติดตั้ง Docker, Maven และ Kubeclt เพิ่มเติม ด้วย Dockerfile นี้
FROM jenkinsci/jenkins:lts# Running as root to have an easy support for Docker
USER root# A default admin user
ENV ADMIN_USER=admin \
ADMIN_PASSWORD=password# Jenkins init scripts
COPY security.groovy /usr/share/jenkins/ref/init.groovy.d/# Install plugins at Docker image build time
COPY plugins.txt /usr/share/jenkins/plugins.txt
RUN /usr/local/bin/install-plugins.sh $(cat /usr/share/jenkins/plugins.txt) && \
mkdir -p /usr/share/jenkins/ref/ && \
echo lts > /usr/share/jenkins/ref/jenkins.install.UpgradeWizard.state && \
echo lts > /usr/share/jenkins/ref/jenkins.install.InstallUtil.lastExecVersion# Install Docker
RUN apt-get -qq update && \
apt-get -qq -y install curl && \
curl -sSL https://get.docker.com/ | sh# Install Maven
RUN curl -LO https://www-eu.apache.org/dist/maven/maven-3/3.6.0/binaries/apache-maven-3.6.0-bin.tar.gz && \
tar xzf apache-maven-3.6.0-bin.tar.gz && \
mv ./apache-maven-3.6.0 /opt/apache-maven | sh
ENV PATH=/opt/apache-maven/bin:$PATH
ENV _JAVA_OPTIONS=-Djdk.net.URLClassPath.disableClassPathURLCheck=true# Install kubectl
RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && \
chmod +x ./kubectl && \
mv ./kubectl /usr/local/bin/kubectl | bash
สร้าง Docker image ด้วยคำสั่ง docker build
แล้ว docker push
ไปที่ DockerHub เพื่อใช้งานตอนที่สร้าง Jenkins ต่อไป
$ docker build -t phayao/jenkins-k8s:lts .
$ docker push phayao/jenkins-k8s:lts
ตรวจสอบ Docker image บน DockerHub ว่า push สำเร็จหรือไม่
สร้าง Jenkins บน Minikube
หลังจากที่เริ่มใช้งาน Minikube แล้วก็จะสร้าง Jenkins บน Kerbernetes ด้วยการสร้าง resource ต่างๆ ดังนี้
- Deployment — เป็น workload สำหรับรัน Jenkins container โดยมี
- PersistentVolumeClaim — เป็น storage เพื่อเก็บข้อมูลถาวรสำหรับ Jenkins
- Service — สำหรับ expose Jenkins container ให้ภายนอก cluster ได้ใช้งาน
ด้วยไฟล์ YAML ที่นี้จะสร้างส่วนทั้งหมดด้วยไฟล์เดียว jenkins-deployment.yml
โดยใช้คำสั่ง kubectl apply
เพื่อสร้าง Service, PersistentVolumnClaim และ Deployment ตามลำดับ
ด้วย resource config ไฟล์ YAML , jenkins-deployment.yml
สำหรับ Deployment, jenkins-volume.yml
สำหรับ PersistentVolumeClaim และ jenkins-service.yml
สำหรับ Service
jenkins-pvc.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jenkins-k8s-claim
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 2Gi
storageClassName: standard
jenkins-deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: jenkins-k8s-deployment
labels:
app: jenkins-k8s
spec:
replicas: 1
selector:
matchLabels:
app: jenkins-k8s
template:
metadata:
labels:
app: jenkins-k8s
spec:
containers:
- name: jenkins-k8s
image: phayao/jenkins-k8s:lts
imagePullPolicy: Always
ports:
- containerPort: 8080
volumeMounts:
- name: docker-sock-volume
mountPath: /var/run/docker.sock
- name: jenkins-home
mountPath: "/var/jenkins_home"
volumes:
- name: jenkins-home
persistentVolumeClaim:
claimName: jenkins-k8s-claim
- name: docker-sock-volume
hostPath:
path: /var/run/docker.sock
jenkins-service.yml
apiVersion: v1
kind: Service
metadata:
name: jenkins-k8s-service
spec:
type: NodePort
selector:
app: jenkins-k8s
ports:
- protocol: TCP
port: 8080
targetPort: 8080
โดยใช้คำสั่ง kubectl create
เพื่อสร้าง resource ต่างๆ โดยต้องสร้าง Storage (PersistentVolumeClaim) ก่อน เพราะว่าใช้เวลาสร้างนานกว่า resource อื่นๆ
$ kubectl create -f jenkins-pvc.yml
persistentvolumeclaim/jenkins-k8s-claim create$ kubectl create -f jenkins-deployment.yml
deployment.apps/jenkins-k8s-deployment created$ kubectl create -f jenkins-service.yml
service/jenkins-k8s-service created
หลังจากที่ใช้คำสั่งสร้าง resource ทั้งหมด ตรวจสอบที่ dashboard ดูว่า resource ทั้งหมดสร้างได้ถูกต้องไหม ถ้าสร้างเรียบร้อยจะเห็นว่า workloads ทั้งหมดเป็นสีเขียว
หรือใช้คำสั่ง kubectl
เพื่อตรวจสอบ resource ต่างๆ ดังนี้
$ kubectl get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE
jenkins-k8s-deployment 1 1 1 1 $ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP
jenkins-k8s-service NodePort 10.101.27.216 <none>
kubernetes ClusterIP 10.96.0.1 <none> $ kubectl get pvc
NAME STATUS VOLUME CAPACITY
jenkins-k8s-claim Bound pvc-7d0dd395-fc85-11e8.. 2Gi
และใช้คำสั่ง minikube service jenkins-k8s-service
เพื่อเปิด Service บน web browser ถ้า work จะเปิดหน้าเว็บ Jenkins และ login ด้วย
user: admin / password: password
สร้าง Global credential
สำหรับ DockerHub โดยใช้ username/password เดียวกับ DockerHub เพื่อใช้สำหรับ push Docker image ใน CI/CD pipeline
เป็นอันว่าตอนนี้เราสามารถรัน Jenkins ได้บน Kubernetes (Minikube) ได้แล้ว ต่อไปจะลองสร้าง Jenkins CI/CD pipeline เพื่อดูว่าทำงานได้หรือไม่
เปลี่ยน ClusterRole
ของ ServiceAccount
(name: default, namespace: default) เป็น cluster-admin ด้วยไฟล์ YAML ดังนี้ เพื่อให้ ServiceAccount สามารถ deploy จาก Jenkins มาที่ Kubernetes ได้ ด้วยคำสั่ง kubectl create
$ kubectl create -f fabric8-rbac.yml
clusterrolebinding.rbac.authorization.k8s.io/fabric8-rbac created
fabric8-rbac.yml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: fabric8-rbac
subjects:
- kind: ServiceAccount
name: default
namespace: default
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
สร้าง Spring Boot project
เพื่อที่จะทดลองสร้าง Jenkins job ด้วย Spring Boot โดยสร้าง web service project แบบง่ายๆ และมี Dockerfile
เพื่อสร้าง Docker image และ Jenkinsfile
เพื่อสร้าง CI/CD pipeline ดังนี้
Dockerfile
# Use official base image of Java Runtim
FROM openjdk:8-jdk-alpine
# Set volume point to /tmp
VOLUME /tmp
# Make port 8080 available to the world outside container
EXPOSE 8080
# Set application's JAR file
ARG JAR_FILE=target/simple-spring-restful-app-0.0.1-SNAPSHOT
# Add the application's JAR file to the container
ADD ${JAR_FILE} app.jar
# Run the JAR file
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app.jar"]
Jenkinsfile
สร้าง Jenkins pipeline มี stage ต่างๆ สำรับ CI/CD โดย Build/Test project และ Build/Push Docker image สุดท้าย Deploy Docker image ไปที่ Kubernetes
pipeline {
agent any
environment {
dockerImage = ''
}
stages {
stage('Build') {
steps {
sh 'mvn -B -DskipTests clean package'
}
}
stage('Test') {
steps {
sh 'mvn test'
}
post {
always {
junit 'target/surefire-reports/*.xml'
}
}
}
stage('Build image') {
steps {
script {
dockerImage = docker.build("phayao/my-app")
}
}
}
stage('Push image') {
steps {
script {
withDockerRegistry(
credentialsId: 'docker-credential',
url: 'https://index.docker.io/v1/') {
dockerImage.push()
}
}
}
}
stage('Deployment') {
steps {
sh 'kubectl apply -f myapp-deployment.yml';
}
}
}
}
สร้าง CI/CD pipeline
กลับมาที่ Jenkins บน Kubernetes แล้ว create new jobs
สร้าง Pipeline
ด้วย Pipeline script from SCM
และเลือก SCM เป็น Git
โดยใส่ repo เป็น Git URL (ในที่นี้ใช้ https://github.com/iphayao/simple-spring-restful-app.git)
กด Build Now
เพื่อ build project แต่ครั้งแรกจะนานหน่อยเพราะว่าต้อง download maven dependency ที่จำเป็นสำหรับการ build/test ถ้าสำเร็จทุก stage จะเขียวหมด แต่ถ้า fail ก็จะแดงใน build นั้น โดยที่เราสามารถเข้าไปดูที่ Console Output ได้ว่ามี error อะไรบ้าง
ในรูปแสดงให้เห็นว่า build ที่ #1–3 failed แต่หลังจากแก้ไขแล้วก็ pass ที่ build #4
กว่าจะผ่านได้ 5555
กลับมาดูที่ Kubernetes dashboard ก็จะเห็น my-app ทั้ง Deployment และ Service ถ้าเข้าไปดูที่ Replica Sets ของ my-app-deployment-* จะเห็น Pod และ Service ที่เกี่ยวข้องกับ my-app ที่ได้จากการ deploy ของ Jenkins CI/CD pipeline
ถ้า Service ทำงานได้ถูกต้องเมื่อใช้คำสั่ง minikube service my-app-service
จะเปิด browser ของของ my-app-service และลองใช้ Postman request ไปที่ api endpoint ก็จะได้ response กลับมา
สรุป
การสร้าง CI/CD pipeline ด้วย Jenkins บน Kubernetes โดยใช้ Minikube นั้นสามารถทำงานได้แบบง่ายๆ ซึ่งเราจำเป็นต้องสร้าง Jenkins Docker image ที่แก้ไขเพิ่มเติม tools ที่จำเป็นต้องใช้ใน CI/CD pipeline นั้นคือ Maven สำหรบ build และทำ Unit test ของ application, Docker สำหรับสร้าง Docker image และ Kubectl สำหรับใช้คำสั่งติดต่อกับ Kubernetes API สำหรับ Deploy image ที่เราสร้างขึ้นกับ Kubernetes cluster (Minikube) และจากขึ้นตอนทั้งหมดอาจจะทำให้เข้าใจยากบ้างสำหรับคนที่ไม่คุ้นเคยกับ Jenkins และ Kubernetes แต่ถ้าลองทำความเข้าใจแล้วจะเห็นว่าไม่ยากจนเกินไป และตัวอย่างนี้อาจจะเน้นไปที่ Spring Boot project ซึ่ใช้ Maven ในการ build แต่ก็สามารถประยุกต์ใช้ได้กับ Stack อื่นๆได้เช่นกัน และอาจจะใช้ tool อื่น อย่างเช่น Helm หรือ Ansible มาช่วยในการ Deploy ได้ก็จะสะดวกขึ้น