Using Jenkins and Kubernetes for Continuous Integration and Delivery
Modern development requires Continuous Integration / Continuous Delivery (CI/CD) and its emphasis on building and running tests on every commit to ensure your development/test environment is always up-to-date. In this tutorial, we’ll show you how CI/CD works with Java services and a Docker/Kubernetes technology stack.
In our example, we’ll be deploying Jenkins in a Kubernetes cluster. We’re using Jenkins because it:
- Has a number of plugins
- Is open source
- Is simple to install and manage
Here’s an overview of how it works:
We’ll implement this scenario in three steps:
- Prepare the application to be CI/CD ready.
- Build a Docker Image with Jenkins, Maven, Docker, and Kubernetes Control.
- Configure Jenkins.
Preparing a CI/CD-ready application
Whenever we use a containerization tool, we deploy the same Docker Image on different environments. Some application properties may need to be changed depending on the environment. Basically, there are two main types of properties:
We use Kubernetes to define the environment-specific property values in a deployment definition, such as:
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: some_application
spec:
..... SKIPPED .....
env:
- name: SOME_ENDPOINT
value: "https://www.linkedin.com/redir/invalid-link-page?url=10%2e222%2e11%2e1"
We can (and should!) use Kubernetes’ ConfigMaps to separate the environment properties’ deployment configuration and values.
In the application’s ENTRYPOINT script, the Docker file will add code to override the application properties with the values from the Linux environment provided by Kubernetes.
Here’s an example of a script used to override application properties in the Red Hat Fuse:
config:edit CONFIG-NAME
config:propset some_endpoint $SOME_ENDPOINT
config:update
Building a Docker Image containing Jenkins, Maven, Docker, and Kubernetes Control
Now that we’ve created a Docker file from the official Jenkins Docker Image (jenkins/jenkins:lts), we need to add Maven and some libraries.
Inside the Kubernetes cluster, you are basically in a Docker-inside-Docker situation. Install the Docker CE and pass-through /var/run/docker.sock from Kubernetes (so we share the same Docker Agent).
You must also install Kubectl to control the Kubernetes cluster. To do this, place the Kubernetes “config” file into the user home directory to have access to the cluster without any additional setup.
The resulting Docker file for our Jenkins will look like this:
FROM jenkins/jenkins:lts
MAINTAINER Evgeny Pishnyuk <maintainer-email@gmail.com>
EXPOSE 8080 50000
USER root
# Install prerequisites for Docker
RUN apt-get update && apt-get install -y sudo maven iptables libsystemd-journal0 init-system-helpers libapparmor1 libltdl7 libseccomp2 libdevmapper1.02.1 && rm -rf /var/lib/apt/lists/*
ENV DOCKER_VERSION=docker-ce_17.03.0~ce-0~ubuntu-trusty_amd64.deb
ENV KUBERNETES_VERSION=v1.6.6
# Set up Docker
RUN wget https://download.docker.com/linux/ubuntu/dists/trusty/pool/stable/amd64/$DOCKER_VERSION
RUN dpkg -i $DOCKER_VERSION
# Set up Kubernetes
RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/$KUBERNETES_VERSION/bin/linux/amd64/kubectl
RUN chmod +x ./kubectl
RUN mv ./kubectl /usr/local/bin/kubectl
# Configure access to the Kubernetes Cluster
ADD install/config ~/.kube
ENTRYPOINT ["/bin/tini", "--", "/usr/local/bin/jenkins.sh"]
Learn more about a production-ready, reliable Kubernetes platform for the enterprise.
Configuring Jenkins
To deploy Jenkins into the Kubernetes cluster, supply the deployment and service definitions:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: jenkins-ci
spec:
replicas: 1
template:
metadata:
labels:
name: jenkins-ci
spec:
imagePullSecrets:
- name: regsecret
containers:
- name: jenkins-ci
imagePullPolicy: Always
image: some-docker-registry/jenkins-ci:latest
ports:
- containerPort: 8080
- containerPort: 50000
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 40
periodSeconds: 20
securityContext:
privileged: true
volumeMounts:
- mountPath: /var/run
name: docker-sock
- mountPath: /var/jenkins_home
name: jenkins-home
volumes:
- name: docker-sock
hostPath:
path: /var/run
- name: jenkins-home
hostPath:
path: /var/jenkins_home
apiVersion: v1
kind: Service
metadata:
name: jenkins-ci-lb
spec:
type: LoadBalancer
ports:
- name: jenkins
port: 8080
targetPort: 8080
- name: jenkins-agent
port: 50000
targetPort: 50000
selector:
name: jenkins-ci
You should configure two low-level items before beginning to work with Jenkins in Kubernetes:
- Set parameter excludeClientIPFromCrumb=true in the file /var/jenkins_home/config.xml (This fixes an annoying “No valid crumb was included in the request” error.)
- Create a “docker login” so Jenkins’ Docker can login and use the correct Docker Registry.
Now we can create the new Jenkins project:
Add build parameters:
Configure an access to the source code repository:
And, finally, add a build step:
cd $ROOT_JAVA
#Maven build
mvn clean install
rm -f $ROOT_DOCKER/install/*
#Copy artifacts for Docker
find . -regex '.*target/[^\/]*\.jar' -exec cp {} $ROOT_DOCKER/install \;
ls $ROOT_DOCKER/install
#Docker build and publish
DOCKER_IMAGE="some-repository/rhesb:$BUILD_NUMBER"
cd $ROOT_DOCKER
docker build -t rhesb .
docker tag rhesb $DOCKER_IMAGE
docker push $DOCKER_IMAGE
#Kubernetes redeploy
kubectl set image statefulset/rhesb rhesb=$DOCKER_IMAGE
kubectl get pod | grep 'rhesb' | cut -d " " -f1 - | awk '{ print $1; system("sleep 60") }' | xargs -n1 kubectl delete pod --v=3
In this script, we build and test our artifacts with Maven, and then copy them to the Docker/install directory.
Next, we build a Docker Image and push it to the Docker Registry. We update the Docker Image version for the Kubernetes deployment and restart each node with a 60-second interval. (Right now we have no roll-out functionality for a Kubernetes StatefulSets.)
For complex-scenario projects, we suggest making several build steps, each invoking its own shell-script (e.g., for Maven build, Docker build, Kubernetes restart), in order to store build code as part of a project and reuse it later if needed.
Conclusion
Let’s check on the results of our efforts. In this example, we’ve set up git polling on every minute (cron expression ‘* * * * *’), and we have committed some code changes to this repository.
As we can see, a full redeployment takes about 5 minutes — generally consider a good response time. Looking at the above timeline, we can see that the Maven Build/Test phase took only 23 seconds. For a more complex scenario, this part will most likely significantly increase .
For more k8s tutorials, check out Kublr’s blog.
Need to simplify your k8s deployment and management? Kublr can help.
0 comments here: