This tutorial shows you how to set up a continuous delivery pipeline using Jenkins and Google Kubernetes Engine, as described in the following diagram.
Objectives
- Understanding a sample application.
- Deploying an application to Kubernetes.
- Uploading code to Google Cloud Source Repositories.
- Creating deployment pipelines in Jenkins.
- Deploying development environments.
- Deploying a canary release.
- Deploying production environments.
Costs
This tutorial uses billable components of Google Cloud Platform (GCP), including:
- Google Compute Engine
- Google Kubernetes Engine
Use the Pricing Calculator to generate a cost estimate based on your projected usage for this tutorial. New GCP users might be eligible for a free trial.
Before you begin
-
Sign in to your Google Account.
If you don't already have one, sign up for a new account.
-
Select or create a GCP project.
-
Make sure that billing is enabled for your project.
- Enable the Google Compute Engine, Google Kubernetes Engine APIs.
Preparing your environment
-
Complete the Setting up Jenkins on Kubernetes Engine tutorial.
-
In Google Cloud Shell, navigate to the sample application directory.
cd continuous-deployment-on-kubernetes/sample-app
Understanding the application
You'll deploy the sample application, gceme
, in your
continuous deployment pipeline. The application is written in the Go language,
and is located in the repo's sample-app
directory. When you run the gceme
binary on a Compute Engine instance, the app displays the instance's metadata
in an info card.
The application mimics a microservice by supporting two operation modes.
-
In backend mode,
gceme
listens on port 8080 and returns Compute Engine instance metadata in JSON format. -
In frontend mode,
gceme
queries the backendgceme
service, and renders the resulting JSON in the user interface.
The frontend and backend modes support two additional URLs.
/version
prints the running version./healthz
reports the application's health. In frontend mode, the health displays asOK
if the backend is reachable.
Deploying the sample app to Kubernetes
Deploy the gceme
frontend and backend to Kubernetes using
manifest files that describe the deployment environment. The files use a default
image that is updated later in this tutorial.
Deploy the applications into two environments.
-
Production. The live site that your users access.
-
Canary. A smaller-capacity site that receives a percentage of your user traffic. Use this environment to sanity check your software with live traffic before it's released to the live environment.
First, deploy your application into the production environment to seed the pipeline with working code.
-
Create the Kubernetes namespace to logically isolate the production deployment.
kubectl create ns production
-
Create the canary and production deployments and services.
kubectl --namespace=production apply -f k8s/production kubectl --namespace=production apply -f k8s/canary kubectl --namespace=production apply -f k8s/services
-
Scale up the production environment frontends.
kubectl --namespace=production scale deployment gceme-frontend-production --replicas=4
-
Retrieve the external IP for the production services. It can take several minutes before you see the load balancer IP address.
kubectl --namespace=production get service gceme-frontend
When complete, an IP address displays in the
EXTERNAL-IP
column.NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE gceme-frontend 10.79.241.131 104.196.110.46 80/TCP 5h
-
Store the frontend service load balancer IP in an environment variable.
export FRONTEND_SERVICE_IP=$(kubectl get -o jsonpath="{.status.loadBalancer.ingress[0].ip}" --namespace=production services gceme-frontend)
-
Confirm that both services are working by opening the frontend external IP address in your browser.
-
Open a separate terminal and poll the production endpoint's
/version
URL so you can observe rolling updates in the next section.while true; do curl http://$FRONTEND_SERVICE_IP/version; sleep 1; done
Creating a repository to host the sample app source code
Next, create a copy of the gceme
sample app and push it to
Cloud Source Repositories.
-
Create the Cloud Source Repository.
gcloud beta source repos create default
-
Initialize the local Git repository.
git init git config credential.helper gcloud.sh
Replace
[PROJECT_ID]
with your current project ID in the following command. To find your current project ID you can rungcloud config list project
.git remote add origin https://source.developers.google.com/p/[PROJECT_ID]/r/default
-
Set the username and email address for your Git commits in this repository.
git config --global user.email "[EMAIL_ADDRESS]"
Replace
[EMAIL_ADDRESS]
with your Git email address.git config --global user.name "[USERNAME]"
Replace
[USERNAME]
with your Git username. -
Add, commit, and push the files.
git add . git commit -m "Initial commit" git push origin master
Creating a pipeline
Use Jenkins to define and run a pipeline for testing, building, and
deploying your copy of gceme
to your Kubernetes cluster.
Adding your service account credentials
Configure your credentials to allow Jenkins to access the code repository.
- In the Jenkins user interface, Click Credentials in the left navigation.
-
Click the Jenkins link in the Credentials table.
-
Click Global Credentials.
- Click Add Credentials in the left navigation.
- Select Google Service Account from metadata from the Kind dropdown.
- Click OK.
There are now two global credentials. Make a note of second credential's name for use later on in this tutorial.
Creating a Jenkins job
Next, use the Jenkins Pipeline feature to configure the build pipeline. Jenkins Pipeline files are written using a Groovy-like syntax.
Navigate to your Jenkins user interface and follow these steps to configure a Pipeline job.
-
Click the Jenkins link in the top left of the interface.
-
Click the New Item link in the left navigation.
-
Name the project sample-app, then choose the Multibranch Pipeline option, then click OK.
-
On the next page, click Add Source and select git.
-
Paste the HTTPS clone URL of your
sample-app
repo in Cloud Source Repositories into the Project Repository field. Replace[PROJECT_ID]
with your project ID.https://source.developers.google.com/p/[PROJECT_ID]/r/default
-
From the Credentials dropdown, select the name of the credentials you created when adding your service account.
-
Select the checkbox Build Periodically under Build Triggers, and enter five asterisks (* * * * *) into the Schedule field. This ensures that Jenkins checks your code repository for changes once every minute. This field uses the CRON expression syntax to define the schedule.
-
Click Save.
After you complete these steps, a job named "Branch indexing" runs.
This meta-job identifies the branches in your repository and ensures
changes haven't occurred in existing branches. If you refresh Jenkins,
the master
branch displays this job.
The first run of the job fails until you make a few code changes in the next step.
Modifying the Pipeline definition
Create a branch for the canary environment, called canary
.
git checkout -b canary
The Jenkinsfile container that defines that pipeline is written using the Jenkins Pipeline Groovy syntax. Using a Jenkinsfile allows an entire build pipeline to be expressed in a single file that lives alongside your source code. Pipelines support powerful features like parallelization and requiring manual user approval.
Modify the Jenkinsfile so that it contains your project ID on line 2.
Deploying a canary release
Now that your pipeline is configured properly, it's time to make a change to the
gceme
application and let your pipeline test, package, and deploy it.
The canary environment is set up as a canary release.
As such, your change is released to a small percentage of the pods behind
the production load balancer. You accomplish this in Kubernetes by maintaining
multiple deployments that share the same labels. For this application, the
gceme-frontend
services load balance across all pods that have the labels
app: gceme
and role: frontend
. The k8s/frontend-canary.yaml
canary
manifest file sets the replicas to 1
and includes labels required for the
gceme-frontend
service.
Currently, you have 1 out of 5 of the frontend pods running the canary code while the other 4 are running the production code. This helps ensure that the canary code doesn't negatively affect users before rolling out to your full fleet of pods.
- Open
html.go
and replace the two instances ofblue
withorange
. -
Open
main.go
and change the version number from1.0.0
to2.0.0
.const version string = "2.0.0"
-
Next, add and commit those files to your local repository.
git add Jenkinsfile html.go main.go git commit -m "Version 2"
-
Finally, push your changes to the remote Git server.
git push origin canary
-
After the change is pushed to the Git repository, navigate to the Jenkins user interface where you can see that your build started.
-
After the build is running, click the down arrow next to the build in the left navigation and select Console Output.
-
Track the output of the build for a few minutes and watch for the
kubectl --namespace=production apply...
messages to begin. When they start, check back on the terminal that was polling the production/version
URL and observe begin to change in some of the requests. You have now rolled out that change to a subset of users. -
After the change is deployed to the canary environment, you can continue to roll it out to the rest of your users by merging the code with the
master
branch and pushing that to the Git server.git checkout master git merge canary git push origin master
-
In approximately one minute, the
master
job in thesample-app
folder kicks off. -
Clicking on the
master
link shows you the stages of your pipeline, as well as pass/fail information and timing characteristics. -
Poll the production URL to verify that the new version 2.0.0 has been rolled out and is serving requests from all users.
export FRONTEND_SERVICE_IP=$(kubectl get -o jsonpath="{.status.loadBalancer.ingress[0].ip}" --namespace=production services gceme-frontend) while true; do curl http://$FRONTEND_SERVICE_IP/version; sleep 1; done
You can look at the Jenkinsfile in the project to see the workflow.
Deploying a development branch
Sometimes you need to work with non-trivial changes that can't be pushed directly to the canary environment. A development branch is a set of environments your developers use to test their code changes before submitting them for integration into the live site. These environments are a scaled-down version of your application, but are deployed using the same mechanisms as the live environment.
To create a development environment from a feature branch, you can push the branch to the Git server and let Jenkins deploy your environment. In a development scenario, you wouldn't use a public-facing load balancer. To help secure your application you can use kubectl proxy. The proxy authenticates itself with the Kubernetes API, and proxies requests from your local machine to the service in the cluster without exposing your service to the Internet.
-
Create another branch and push it to the Git server.
git checkout -b new-feature git push origin new-feature
A new job is created and your development environment is in the process of being created. At the bottom of the console output of the job are instructions for accessing your environment.
-
Start the proxy in the background.
kubectl proxy &
-
Verify that your application is accessible by using localhost.
curl http://localhost:8001/api/v1/proxy/namespaces/new-feature/services/gceme-frontend:80/
-
You can now push code to this branch to update your development environment. When you are done, merge your branch back into
canary
to deploy that code to the canary environment.git checkout canary git merge new-feature git push canary
-
When you are confident that your code won't cause problems in the production environment, merge from the
canary
branch to themaster
branch to kick off the deployment.git checkout master git merge canary git push master
-
When you are done with the development branch, delete it from the server and delete the environment from your Kubernetes cluster.
git push origin :new-feature kubectl delete ns new-feature
Cleaning up
To avoid incurring charges to your Google Cloud Platform account for the resources used in this tutorial:
Deleting the project
The easiest way to eliminate billing is to delete the project you created for the tutorial.
To delete the project:
- In the GCP Console, go to the Projects page.
-
In the project list, select the project you
want to delete and click Delete project.
- In the dialog, type the project ID, and then click Shut down to delete the project.
What's next
-
Learn more about Jenkins on Kubernetes Engine best practices.
-
Learn about how to configure Jenkins for Kubernetes Engine.
-
Try out other Google Cloud Platform features for yourself. Have a look at our tutorials.