Setup Jenkins Pipeline and Blue Ocean in Kubernetes
Jenkins Pipeline (or simply "Pipeline" with a capital "P") is a suite of plugins which supports implementing and integrating continuous delivery pipelines into Jenkins.
Blue Ocean rethinks the user experience of Jenkins. Designed from the ground up for Jenkins Pipeline, but still compatible with freestyle jobs, Blue Ocean reduces clutter and increases clarity for every member of the team.
Install Jenkins in K8s
Create namespace
kubectl create namespace devops
Create Jenkins containers
Create pv/pvc/deployment/service/ingress by yaml files
kubectl create -f ops-storage.yml
kubectl create -f rbac.yml
kubectl create -f jenkins.yml
Storage yaml
The content of ops-storage.yml is as bellow.
apiVersion: v1
kind: PersistentVolume
metadata:
name: opspv
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /mnt/data
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: node
operator: In
values:
- dev
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: opspvc
namespace: devops
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-storage
resources:
requests:
storage: 20Gi
RBAC yaml
The content of rbac.yml is as bellow.
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins
namespace: devops
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: jenkins
rules:
- apiGroups: ["extensions", "apps"]
resources: ["deployments"]
verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
- apiGroups: [""]
resources: ["services"]
verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
- 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: ClusterRoleBinding
metadata:
name: jenkins
namespace: devops
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: jenkins
subjects:
- kind: ServiceAccount
name: jenkins
namespace: devops
Jenkins yaml
The content of jenkins.yml is as bellow. Remember to replace the Traefik ingress configuration [domain] to your domain.
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: jenkins
namespace: devops
spec:
template:
metadata:
labels:
app: jenkins
spec:
terminationGracePeriodSeconds: 10
serviceAccount: jenkins
containers:
- name: jenkins
image: jenkins/jenkins:lts
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
name: web
protocol: TCP
- containerPort: 50000
name: agent
protocol: TCP
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 500m
memory: 512Mi
livenessProbe:
httpGet:
path: /login
port: 8080
initialDelaySeconds: 60
timeoutSeconds: 5
failureThreshold: 12
readinessProbe:
httpGet:
path: /login
port: 8080
initialDelaySeconds: 60
timeoutSeconds: 5
failureThreshold: 12
volumeMounts:
- name: jenkinshome
subPath: jenkins
mountPath: /var/jenkins_home
env:
- name: LIMITS_MEMORY
valueFrom:
resourceFieldRef:
resource: limits.memory
divisor: 1Mi
- name: JAVA_OPTS
value: -Xmx$(LIMITS_MEMORY)m -XshowSettings:vm -Dhudson.slaves.NodeProvisioner.initialDelay=0 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85 -Duser.timezone=Asia/Shanghai
securityContext:
fsGroup: 1000
volumes:
- name: jenkinshome
persistentVolumeClaim:
claimName: opspvc
---
apiVersion: v1
kind: Service
metadata:
name: jenkins
namespace: devops
labels:
app: jenkins
spec:
selector:
app: jenkins
type: NodePort
ports:
- name: web
port: 8080
targetPort: web
nodePort: 30002
- name: agent
port: 50000
targetPort: agent
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: jenkins-ingress
namespace: devops
spec:
rules:
- host: [domain]
http:
paths:
- path: /
backend:
serviceName: jenkins
servicePort: web
Wait until running
Check if pods runs
kubectl get pods -n devops
Login Jenkins
Check the Jenkins password and login, then install plugins.
#Please check the jenkins startup logs or check the content in this file
cat /mnt/data/jenkins/secrets/initialAdminPassword
Install plugins
Installed plugins include: * Blue Ocean * Kubernetes plugin (Job and Stage Monitoring Plugin) * NodeJS Plugin * Embeddable Build Status Plugin
Jenkins Slave Image
Jenkins Slave jobs will run in your Kubernetes cluster, so it is important that the kubectl version for you Kubernetes cluster is the same with the Jenkins Slave docker image, otherwise you may meet some version conflict issues for Jenkins Pipelines jobs.
I build my own Jenkins Slave docker image hustakin/jenkins-slave:latest with the kubectl version v1.14.2. For different version, you should build your own jenkins slave docker image with another kubectl version based on image cnych/jenkins:jnlp.
Kuberctl replace
The easiest way to customize different version kubectl is to replace the executable file "kubectl" from image cnych/jenkins:jnlp. You can find the file in your kubernetes cluster or download it.
#Find current kubectl
which kubectl
#/usr/bin/kubectl
#Download v1.14.2 kubectl and modify it to executable
curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.14.2/bin/darwin/amd64/kubectl
chmod +x ./kubectl
Dockerfile
Copy the kubectl file to current folder and build the Dockerfile.
FROM cnych/jenkins:jnlp
MAINTAINER Frankie Fan "hustakin@gmail.com"
ENV REFRESHED_AT 2019-6-26
COPY kubectl /usr/local/bin/kubectl
ENTRYPOINT ["jenkins-slave"]
Jenkins Configurations
Configure Kubernetes
Click Manage Jenkins->Configure System->Add a new cloud->Kubernetes. Test the connection and input namespace and Jenkins inner url by format (
Configure Jenkins Slave k8s template
Add pod template, and remember to clean the "Command to run" and "Arguments to pass to the command" (This's really important!).
The image hustakin/jenkins-slave:latest has the kubectl version of v1.14.2
Configure Jenkins Slave k8s volumes
Add volumes in the pod template including docker, .kube config files as well as the maven cache. (Each node of the cluster will cache the maven into /home/ec2-user/.m2)
Configure service account
The service account is created in previous RBAC yaml step.
Configure global tool
Click Manage Jenkins->Global Tool Configuration. Configure maven installations.
Configure NodeJS installations
Click Manage Jenkins->Global Tool Configuration. Configure NodeJS installations.
Configure DockerHub credentials
Click Jenkins->Credentials->(global)->Add credentials. Specify your DockerHub username and password. Remember the ID here will be used in your Jenkinsfile to specify "credentialsId".
Configure Pipelines
Write Dockerfile and Jenkinsfile and k8s.yaml in your project root folder and push it to GitHub. Create the repository in Dockerhub for your project in order to save docker built images.
Jenkinsfile content for SpringBoot
The Jenkinsfile content is as bellow
node('jenkins-jnlp') {
env.MVN_HOME = "${tool 'Maven'}"
env.PATH="${env.MVN_HOME}/bin:${env.PATH}"
stage('Prepare') {
echo "1.Prepare Stage"
checkout scm
script {
build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
if (env.BRANCH_NAME != 'master') {
build_tag = "${env.BRANCH_NAME}-${build_tag}"
}
}
}
stage('Compile') {
echo "2.Compile SpringBoot App Stage"
sh "mvn clean package -Dmaven.test.skip=true"
}
stage('Build') {
echo "3.Build Docker Image Stage"
sh "docker build -t hustakin/test:${build_tag} ."
}
stage('Push') {
echo "4.Push Docker Image Stage"
withCredentials([usernamePassword(credentialsId: 'dockerHub', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) {
sh "docker login -u ${dockerHubUser} -p ${dockerHubPassword}"
sh "docker push hustakin/test:${build_tag}"
}
}
stage('Deploy') {
echo "5. Deploy To K8S Cluster Stage"
sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml"
sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s.yaml"
sh "kubectl apply -f k8s.yaml --record"
}
}
Dockerfile content for SpringBoot
The Dockerfile content is as bellow
FROM openjdk:8-jre
MAINTAINER Frankie Fan "hustakin@gmail.com"
ENV REFRESHED_AT 2019-7-3
VOLUME /tmp
ADD web/target/test.jar test.jar
ENTRYPOINT ["java","-XX:-BytecodeVerificationLocal","-XX:-BytecodeVerificationRemote","-XX:CICompilerCount=3","-XX:InitialHeapSize=268435456","-XX:+ManagementServer","-XX:MaxHeapSize=4263510016","-XX:MaxNewSize=2147483648","-XX:MinHeapDeltaBytes=524288","-XX:NewSize=89128960","-XX:OldSize=179306496","-XX:TieredStopAtLevel=1","-XX:+UseCompressedClassPointers","-XX:+UseCompressedOops","-XX:-UseLargePagesIndividualAllocation","-XX:+UseParallelGC","-Djava.security.egd=file:/dev/./urandom","-Dspring.profiles.active=dev","-jar","/test.jar"]
EXPOSE 80 443
K8s content for SpringBoot
The k8s.yaml content is as bellow
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: test-server
namespace: agric
spec:
replicas: 2
minReadySeconds: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
metadata:
labels:
app: test-server
spec:
containers:
- name: test-server
image: hustakin/test:<BUILD_TAG>
env:
- name: branch
value: <BRANCH_NAME>
ports:
- containerPort: 80
protocol: TCP
name: web
- containerPort: 443
protocol: TCP
name: https
# inventory probe
readinessProbe:
httpGet:
path: /ready
port: 80
initialDelaySeconds: 15
periodSeconds: 5
failureThreshold: 5
imagePullSecrets:
- name: docker-hub
Jenkinsfile content for Angular
The Jenkinsfile content is as bellow
node('jenkins-jnlp') {
env.NODEJS_HOME = "${tool 'Node'}"
env.PATH="${env.NODEJS_HOME}/bin:${env.PATH}"
stage('Prepare') {
echo "1.Prepare Stage"
checkout scm
script {
build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
if (env.BRANCH_NAME != 'master') {
build_tag = "${env.BRANCH_NAME}-${build_tag}"
}
}
}
stage('Compile') {
echo "2.Compile Angular App Stage"
sh "npm install"
sh "npm install node-sass"
sh "npm run prod"
}
stage('Build') {
echo "3.Build Docker Image Stage"
sh "docker build -t hustakin/test-front:${build_tag} ."
}
stage('Push') {
echo "4.Push Docker Image Stage"
withCredentials([usernamePassword(credentialsId: 'dockerHub', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) {
sh "docker login -u ${dockerHubUser} -p ${dockerHubPassword}"
sh "docker push hustakin/test-front:${build_tag}"
}
}
stage('Deploy') {
echo "5. Deploy To K8S Cluster Stage"
sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml"
sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s.yaml"
sh "kubectl apply -f k8s.yaml --record"
}
}
Dockerfile content for Angular
The Dockerfile content is as bellow
FROM nginx:1.16.0
MAINTAINER Frankie Fan "hustakin@gmail.com"
ENV REFRESHED_AT 2019-6-5
COPY nginx/default.conf /etc/nginx/conf.d/
RUN rm -rf /usr/share/nginx/html/*
COPY /dist /usr/share/nginx/html
CMD ["nginx", "-g", "daemon off;"]
EXPOSE 80
Nginx conf content for Angular
The nginx/default.conf content is as bellow.
server {
listen 80;
sendfile on;
default_type application/octet-stream;
gzip on;
gzip_http_version 1.1;
gzip_disable "MSIE [1-6]\.";
gzip_min_length 256;
gzip_vary on;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
gzip_comp_level 9;
root /usr/share/nginx/html;
location / {
try_files $uri $uri/ /index.html =404;
}
}
K8s content for Angular
The k8s.yaml content is as bellow
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: test-front-dev
namespace: agric
spec:
replicas: 2
minReadySeconds: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
metadata:
labels:
app: test-front-dev
spec:
containers:
- name: test-front-dev
image: hustakin/test-front:<BUILD_TAG>
env:
- name: branch
value: <BRANCH_NAME>
ports:
- containerPort: 80
protocol: TCP
name: web
- containerPort: 443
protocol: TCP
name: https
imagePullSecrets:
- name: docker-hub
Create Github Pipeline
Click Jenkins->Open Blue Ocean->New Pipeline, and create pipeline for your project.
Create Github Pipeline
The jenkins pipeline should work now. Enter your jenkins job and master branch, you could see the stage view here.
Trigger pipeline
Push your code changes to Github. Click Jenkins->Open Blue Ocean. You can trigger the build for all pipelines you created by clicking the Run button.
Blue Ocean build
Click Jenkins->Open Blue Ocean, and click the pipeline you just created, you will see the Jenkinsfile stages and related logs here.