CI/CD Series - Part 2: How to build a CI/CD pipeline using Bitbucket, Docker & Kubernetes? (Step-by-Step Guide)
As mentioned in our previous Blog, various CI/CD tools have emerged in the market recently. Each tool comes up with different features and aspects. Consequently, this CI/CD Blog-Series is introduced in order to investigate the usage of different tools in that area.
This Blogpost is the second in the CI/CD Blog-Series and within it we will set up a CI/CD pipeline for a containerized (Docker) application on Kubernetes using Bitbucket as a tool.
Please note that in the Blog, we will use Azure as a provider for our Kubernetes Cluster and a private Docker registry. However, you can use any Kubernetes Cluster/Docker registry that you already have on any provider to complete the steps here. You don’t need to touch the application code. All you have to do is to is to configure the environment variables correctly giving them the suitable values and make some changes in the bitbucket-pipelines.yml file!
At the end of the post, you should be able to build a CI/CD pipeline for a sample spring-boot java application using one tool “Bitbucket”! The end goal is to automate the following process:
- Build & Compile code.
- Run test cases.
- Build docker images.
- Push/pull images to/from a private docker registry.
- Deploy the app on Kubernetes using Azure as a platform.
In case you don’t have a kubernetes cluster with a container registry instance on Azure, you can follow the steps mentioned in the section “Create and Configure a Managed Kubernetes Cluster with a Container Registry Instance on Azure” of the previous blog.
Once you have your kubernetes cluster and a container registry instance ready on azure, you need to create a Bitbucket Account in order to automate the workflow.
The sample application code is hosted on Github. A short description of the application can also be found in the previous blog.
Create and Understand the Pipeline
Before we start with that, let’s understand some basics. Bitbucket Pipelines is an integrated CI/CD solution built into Bitbucket, which enables automatic building, testing and deployment of applications. This in turn means that you don’t have to put effort on installation or configuration of additional tools.
A major element of the Bitbucket pipeline is the bitbucket-pipelines.yml file, which contains all the build configurations and needs to be created in the root of your repository. Within that file, you can define different steps, such that each step starts a new Docker container that includes a clone of your repository. Inside each step, you can define a script that contains the list of commands you want to execute in sequence within the step (container).
As Bitbucket relies heavily on build containers, it requires that you build your pipeline in separate steps which in turn leads in creating fine granular pipelines. However, in case you need to exchange some artifacts between the different steps, you have to manage them.
Using Bitbucket, you can easily create a new Repository following these steps:
- In Bitbucket, click the icon in the global sidebar and select Repository.
- In Create a new repository page, give the repository a Name and pick Git for the Repository type.
- Click Create repository.
Now you need to “git clone” the Bitbucket repository to your local system, add the sample application folder to it, and “git push” the changes to Bitbucket again:
Let us now have a deep look into the content of the bitbucket-pipelines.yml file! But before we go more into the details, note that all the variables that are written in capital letters are environment variables which take their values by configuring them globally using Bitbucket.
In order to configure any environment variable using Bitbucket, follow these steps:
- In Bitbucket, navigate to your Repository > Settings > Pipelines > Repository variables.
- Give the Name and the Value of the variable.
- Click on Add.
Note that beside Repository variables, Bitbucket enables you to define Deployment variables which override Repository variables. These variables can only be used in a specific deployment environment which enables you to have independent deployment variables for each environment.
Now, let’s have a look on the different parts of the bitbucket-pipelines.yml file:
- Test the todobackend app with H2 in-memory mode using Maven, by adding the necessary commands inside a script in the step. Note that defining a cache, reduces the load time by downloading the dependencies once to the server and then loading them locally into the build:
YAML12345678# Test with Maven/H2- step:name: Test with Maven/H2script:- cd todobackend- mvn test -Dspring.profiles.active=devcaches:- maven
- Test the todobackend app with Postgres DB using Maven. In order to do that, we create a separate docker container for Postgres service, to which the app can connect on localhost. This is done by adding the service definition to the definitions section of the file and referencing it in the step. Note that defining separate docker containers for services will speed up the build and make changing a service easy without having to edit the whole container image. The DB environment variables should be configured in Bitbucket as mentioned above having the following values:
YAML12345678# Test with Maven/PSQL- step:name: Test with Maven/PSQLscript:- cd todobackend- mvn test -Dspring.profiles.active=prod -DPOSTGRES_HOST=localhostservices:- postgresdb
- DB_USERNAME -> user
- DB_PASSWORD -> password
- DB_NAME -> mydb
YAML12345678definitions:services:postgresdb:image: postgresvariables:POSTGRES_USER: $DB_USERPOSTGRES_PASSWORD: $DB_PASSWORDPOSTGRES_DB: $DB_NAME
- Build the application jars, build the docker images and push the images to the docker registry. After the tests have passed, it is the time to use “Maven” to build the code, compile it, package it and put the jar files in the appropriate folders. Then we build the Docker images of the todobackend and todoui apps using the created jar files. After that, we login to the Docker Registry and push the Docker images created before to the registry. Note that he “BITBUCKET_BUILD_NUMBER” will take its proper value automatically. The Docker environment variables should be configured in Bitbucket having the following values:
YAML12345678910111213141516171819# Build with Maven, Create & Push Docker Images to a Docker Registry- step:name: Build with Maven, Create & Push Docker Images to a Docker Registryscript:- cd todobackend- mvn -B -DskipTests clean install- cd ..- cd todoui- mvn -B -DskipTests clean install- cd ..- docker build -f Dockerfile-todobackend -t $DOCKER_REPOSITORY/todobackend:v$BITBUCKET_BUILD_NUMBER .- docker build -f Dockerfile-todoui -t $DOCKER_REPOSITORY/todoui:v$BITBUCKET_BUILD_NUMBER .- docker login $DOCKER_REPOSITORY --username $USERNAME --password $PASSWORD- docker push $DOCKER_REPOSITORY/todobackend:v$BITBUCKET_BUILD_NUMBER- docker push $DOCKER_REPOSITORY/todoui:v$BITBUCKET_BUILD_NUMBERservices:- dockercaches:- docker
- DOCKER_REPOSITORY -> Your Docker Registry URL
- USERNAME/PASSWORD -> Your Docker Registry Credentials
- After previous stages have competed successfully, a deployment to kubernetes can be triggered. Bitbucket provides an easy way to configure your Azure deployment using Azure Pipes. All you need to do is to choose the azure aks deploy pipe, configure the necessary variables and provide the kubectl commands you want to execute. The pipe environment variables should be configured in Bitbucket having their values from the created service principal in Azure. For more information about that, refer to the previous blog:
- AZURE_APP_ID -> appId
- AZURE_PASSWORD -> password
- AZURE_TENANT_ID -> tenant
The complete K8s deployment steps can be found in the bitbucket-pipelines.yml file in the sample application code on Github.
A very important feature that Bitbucket offers is Bitbucket Deployments. It enables you to define different deployment environments and configure your steps to be deployed within specific environments. This in turn helps you to keep track of the status of your deployment environments, and visibility over each environment’s code changes.
Note that if you want to use a Kubernetes Cluster on a different provider other than Azure, you have to choose the suitable Bitbucket pipe for your needs and configure it in the bitbucket-pipelines.yml file correctly!
YAML12345678910111213# Deploy Application on K8s- step:name: Deploy Application on K8sscript:- pipe: microsoft/azure-aks-deploy:1.0.1variables:AZURE_APP_ID: $AZURE_APP_IDAZURE_PASSWORD: $AZURE_PASSWORDAZURE_TENANT_ID: $AZURE_TENANT_IDAZURE_AKS_NAME: 'CICDAKSCluster'AZURE_RESOURCE_GROUP: 'CICDResourceGroup'KUBECTL_COMMAND: 'apply'KUBECTL_ARGUMENTS: '-f configmap.yml'
Now you can start your CI/CD journey! You can change something in your Bitbucket repository and commit it, then the build of the pipeline will start automatically in Bitbucket!
After it finishes, you should see a view similar to this, in which you can see the details of each step along with the Console Output of the build.
There you go! In this Blogpost, you have successfully created a CI/CD pipeline for a containerized spring-boot java application on Kubernetes using Bitbucket. In Part 3 of this Blog-Series which will follow soon, we will set up a CI/CD pipeline using Concourse CI as a tool.