สร้าง CI/CD ด้วย Jenkins บน Kubernetes แบบง่ายๆ

Phayao Boonon
6 min readDec 10, 2018

--

ในเรื่องของ 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 ได้ก็จะสะดวกขึ้น

--

--

Phayao Boonon
Phayao Boonon

Written by Phayao Boonon

Software Engineer 👨🏻‍💻 Stay Hungry Stay Foolish

Responses (1)