Continuous Delivery with Jenkins in Kubernetes Engine

This is from the Arcade October 2024 Series.

This tutorial covers the Lab "Continuous Delivery with Jenkins in Kubernetes Engine", code: GSP051.

We will learn how to use Jenkins to set up a continuous delivery pipeline on the Kubernetes engine. Developers who often integrate their code in a common repository utilize Jenkins as their preferred automation server. The following link covers

Modern CI/CD with GKE: Build a CI/CD system | Google Kubernetes Engine (GKE) | Google Cloud

What you will discover

To get knowledge about using Jenkins on Kubernetes, you will need to finish the following tasks in this lab:

  • Installing a Jenkins program on a Kubernetes Engine Cluster
  • Configure the Helm Package Manager application for your Jenkins project.
  • Examine the attributes of a Jenkins program.
  • Construct and test a Jenkins pipeline.

Prerequisites

This is a lab for advanced users. You should be familiar with Jenkins, Kubernetes, and shell programming at the very least before taking the test. The following labs will help you catch up:

Once you are prepared, scroll down to learn more about Kubernetes, Jenkins, and Continuous Delivery.


What is Continuous Delivery/Continuous Deployment (CI/CD)?

When setting up a continuous delivery (CD) pipeline, deploying Jenkins on Kubernetes Engine offers significant advantages over traditional VM-based deployments.

Using containers in your build process allows a single virtual host to run jobs across multiple operating systems. Kubernetes Engine provides ephemeral build executors, which are only active during builds, freeing up resources for other cluster tasks like batch processing. These ephemeral executors also offer speed, launching in just seconds.

Additionally, Kubernetes Engine comes with Google’s global load balancer, which automates web traffic routing to your instances. This load balancer handles SSL termination and uses a global IP address configured with Google’s backbone network. Combined with your web front, it ensures users always have the fastest path to an application instance.

With this understanding of Kubernetes, Jenkins, and their interaction in a CD pipeline, you’re ready to start building one.


What is Jenkins?

Build, test, and deployment pipelines can be dynamically orchestrated with Jenkins, an open-source automation server. Jenkins enables developers to work on projects rapidly and without concern about overhead problems that may arise from continuous delivery.


To start we will activate the GCP Cloudshell.

Cloud Shell is a virtual machine that is loaded with development tools. It offers a persistent 5GB home directory and runs on the Google Cloud. Cloud Shell provides command-line access to your Google Cloud resources.

  1. Click Activate Cloud Shell

at the top of the Google Cloud console.

When you are connected, you are already authenticated, and the project is set to your Project_ID.

All commands listed will be executed in the Cloudshell.

Task 1. Download the source code

To get set up, open a new session in Cloud Shell and run the following command to set your zone:

gcloud config set compute/zone 

Copy the lab's sample code:

gsutil cp gs://spls/gsp051/continuous-deployment-on-kubernetes.zip .
unzip continuous-deployment-on-kubernetes.zip

Change to the correct directory:

cd continuous-deployment-on-kubernetes

SAMPLE OUTPUT:

student_00_1a87387c348d@cloudshell:~ (qwiklabs-gcp-02-9241fd14328d)$ export ZONE=us-east1-c
student_00_1a87387c348d@cloudshell:~ (qwiklabs-gcp-02-9241fd14328d)$ gcloud config set compute/zone us-east1-c
WARNING: Property validation for compute/zone was skipped.
Updated property [compute/zone].
student_00_1a87387c348d@cloudshell:~ (qwiklabs-gcp-02-9241fd14328d)$ gsutil cp gs://spls/gsp051/continuous-deployment-on-kubernetes.zip .
Copying gs://spls/gsp051/continuous-deployment-on-kubernetes.zip...
\ [1 files][  3.3 MiB/  3.3 MiB]                                                
Operation completed over 1 objects/3.3 MiB.                                      
student_00_1a87387c348d@cloudshell:~ (qwiklabs-gcp-02-9241fd14328d)$ unzip continuous-deployment-on-kubernetes.zip
Archive:  continuous-deployment-on-kubernetes.zip
   creating: continuous-deployment-on-kubernetes/
   creating: continuous-deployment-on-kubernetes/sample-app/
  inflating: continuous-deployment-on-kubernetes/sample-app/Gopkg.toml  
  inflating: continuous-deployment-on-kubernetes/sample-app/Dockerfile  
...  
 extracting: continuous-deployment-on-kubernetes/.git/COMMIT_EDITMSG  
  inflating: continuous-deployment-on-kubernetes/.git/FETCH_HEAD  
student_00_1a87387c348d@cloudshell:~ (qwiklabs-gcp-02-9241fd14328d)$

Task 2. Provisioning Jenkins

Creating a Kubernetes cluster

Next, execute the following command to create a Kubernetes cluster:

gcloud container clusters create jenkins-cd \
--num-nodes 2 \
--machine-type e2-standard-2 \
--scopes "https://www.googleapis.com/auth/source.read_write,cloud-platform"

SAMPLE OUTPUT:

student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes (qwiklabs-gcp-02-9241fd14328d)$ gcloud container clusters create jenkins-cd \
--num-nodes 2 \
--machine-type e2-standard-2 \
--scopes "https://www.googleapis.com/auth/source.read_write,cloud-platform"
Default change: VPC-native is the default mode during cluster creation for versions greater than 1.21.0-gke.1500. To create advanced routes based clusters, please pass the `--no-enable-ip-alias` flag
Note: The Kubelet readonly port (10255) is now deprecated. Please update your workloads to use the recommended alternatives. See https://cloud.google.com/kubernetes-engine/docs/how-to/disable-kubelet-readonly-port for ways to check usage and for migration instructions.
Note: Your Pod address range (`--cluster-ipv4-cidr`) can accommodate at most 1008 node(s).
Creating cluster jenkins-cd in us-east1-c... Cluster is being health-checked (Kubernetes Cont
rol Plane is healthy)...done.                                                                
Created [https://container.googleapis.com/v1/projects/qwiklabs-gcp-02-9241fd14328d/zones/us-east1-c/clusters/jenkins-cd].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-east1-c/jenkins-cd?project=qwiklabs-gcp-02-9241fd14328d
kubeconfig entry generated for jenkins-cd.
NAME: jenkins-cd
LOCATION: us-east1-c
MASTER_VERSION: 1.30.3-gke.1969001
MASTER_IP: 34.74.11.204
MACHINE_TYPE: e2-standard-2
NODE_VERSION: 1.30.3-gke.1969001
NUM_NODES: 2
STATUS: RUNNING
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes (qwiklabs-gcp-02-9241fd14328d)$ 

This process may take several minutes to finish. The additional scopes allow Jenkins to interact with Cloud Source Repositories and Google Container Registry.

After the execution has completed, click on check my progress.

Confirm that your cluster is running by executing the following command:

gcloud container clusters list

Now, get the credentials for your cluster:

gcloud container clusters get-credentials jenkins-cd

Kubernetes Engine uses these credentials to access your newly provisioned cluster—confirm that you can connect to it by running the following command:

kubectl cluster-info

SAMPLE OUTPUT:

student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes (qwiklabs-gcp-02-9241fd14328d)$ gcloud container clusters list
NAME: jenkins-cd
LOCATION: us-east1-c
MASTER_VERSION: 1.30.3-gke.1969001
MASTER_IP: 34.74.11.204
MACHINE_TYPE: e2-standard-2
NODE_VERSION: 1.30.3-gke.1969001
NUM_NODES: 2
STATUS: RUNNING
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes (qwiklabs-gcp-02-9241fd14328d)$ gcloud container clusters get-credentials jenkins-cd
Fetching cluster endpoint and auth data.
kubeconfig entry generated for jenkins-cd.
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes (qwiklabs-gcp-02-9241fd14328d)$ kubectl cluster-info
Kubernetes control plane is running at https://34.74.11.204
GLBCDefaultBackend is running at https://34.74.11.204/api/v1/namespaces/kube-system/services/default-http-backend:http/proxy
KubeDNS is running at https://34.74.11.204/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
Metrics-server is running at https://34.74.11.204/api/v1/namespaces/kube-system/services/https:metrics-server:/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes (qwiklabs-gcp-02-9241fd14328d)$ 

Task 3. Setup Helm

In this lab, you’ll utilize Helm to install Jenkins from the Charts repository. Helm, a package manager, simplifies the configuration and deployment of Kubernetes applications. After installing Jenkins, you’ll be ready to establish your CI/CD pipeline.

Add Helm's stable chart repo:

helm repo add jenkins https://charts.jenkins.io

Ensure the repo is up to date:

helm repo update

SAMPLE OUTPUT:

student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes (qwiklabs-gcp-02-9241fd14328d)$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "jenkins" chart repository
Update Complete. ⎈Happy Helming!⎈
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes (qwiklabs-gcp-02-9241fd14328d)$

Task 4. Configure and Install Jenkins

When setting up Jenkins, you can use a values file as a template to supply the required setup values.

You’ll employ a custom values file to automatically configure your Kubernetes Cloud and include the necessary plugins.

  • Kubernetes:latest
  • Workflow-multibranch:latest
  • Git:latest
  • Configuration-as-code:latest
  • Google-oauth-plugin:latest
  • Google-source-plugin:latest
  • Google-storage-plugin:latest

Use the Helm CLI to deploy the chart with your configuration settings:

helm install cd jenkins/jenkins -f jenkins/values.yaml --wait

After the execution has completed, click on check my progress.

Once that command completes ensure the Jenkins pod goes to the Running state and the container is in the READY state

kubectl get pods

Configure the Jenkins service account to be able to deploy to the cluster:

kubectl create clusterrolebinding jenkins-deploy --clusterrole=cluster-admin --serviceaccount=default:cd-jenkins

Run the following command to setup port forwarding to the Jenkins UI from the Cloud Shell:

export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/component=jenkins-master" -l "app.kubernetes.io/instance=cd" -o jsonpath="{.items[0].metadata.name}")
kubectl port-forward $POD_NAME 8080:8080 >> /dev/null &

Now, check that the Jenkins Service was created properly:

kubectl get svc

SAMPLE OUTPUT:

student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes (qwiklabs-gcp-02-9241fd14328d)$ helm install cd jenkins/jenkins -f jenkins/values.yaml --wait
NAME: cd
LAST DEPLOYED: Wed Oct  2 12:32:26 2024
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
1. Get your 'admin' user password by running:
  kubectl exec --namespace default -it svc/cd-jenkins -c jenkins -- /bin/cat /run/secrets/additional/chart-admin-password && echo
2. Get the Jenkins URL to visit by running these commands in the same shell:
  echo http://127.0.0.1:8080
  kubectl --namespace default port-forward svc/cd-jenkins 8080:8080

3. Login with the password from step 1 and the username: admin
4. Configure security realm and authorization strategy
5. Use Jenkins Configuration as Code by specifying configScripts in your values.yaml file, see documentation: http://127.0.0.1:8080/configuration-as-code and examples: https://github.com/jenkinsci/configuration-as-code-plugin/tree/master/demos

For more information on running Jenkins on Kubernetes, visit:
https://cloud.google.com/solutions/jenkins-on-container-engine

For more information about Jenkins Configuration as Code, visit:
https://jenkins.io/projects/jcasc/


NOTE: Consider using a custom image with pre-installed plugins
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes (qwiklabs-gcp-02-9241fd14328d)$ kubectl get pods
NAME           READY   STATUS    RESTARTS   AGE
cd-jenkins-0   2/2     Running   0          2m4s
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes (qwiklabs-gcp-02-9241fd14328d)$ kubectl create clusterrolebinding jenkins-deploy --clusterrole=cluster-admin --serviceaccount=default:cd-jenkins
clusterrolebinding.rbac.authorization.k8s.io/jenkins-deploy created
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes (qwiklabs-gcp-02-9241fd14328d)$ export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/component=jenkins-master" -l "app.kubernetes.io/instance=cd" -o jsonpath="{.items[0].metadata.name}")
kubectl port-forward $POD_NAME 8080:8080 >> /dev/null &
[1] 1215
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes (qwiklabs-gcp-02-9241fd14328d)$ kubectl get svc
NAME               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)     AGE
cd-jenkins         ClusterIP   34.118.227.93   <none>        8080/TCP    2m32s
cd-jenkins-agent   ClusterIP   34.118.236.8    <none>        50000/TCP   2m32s
kubernetes         ClusterIP   34.118.224.1    <none>        443/TCP     7m26s
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes (qwiklabs-gcp-02-9241fd14328d)$ 

By using the Kubernetes Plugin, builder nodes will be automatically launched as needed when requested by the Jenkins master. Once their tasks are completed, they will be automatically terminated, and their resources will be returned to the cluster’s resource pool.

This service exposes ports 8080 and 50000 for any pods matching the selector, making the Jenkins web UI and builder/agent registration ports accessible within the Kubernetes cluster. Additionally, the jenkins-ui service is exposed using a ClusterIP, ensuring it is not accessible from outside the cluster.


Task 5. Connect to Jenkins

The Jenkins chart will automatically create an admin password. To retrieve it, run:

printf $(kubectl get secret cd-jenkins -o jsonpath="{.data.jenkins-admin-password}" | base64 --decode);echo

SAMPLE OUTPUT:

student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes (qwiklabs-gcp-02-9241fd14328d)$ printf $(kubectl get secret cd-jenkins -o jsonpath="{.data.jenkins-admin-password}" | base64 --decode);echo
XnYKcjkoJWY5HebvCESuqt
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes (qwiklabs-gcp-02-9241fd14328d)$ 

To get to the Jenkins user interface, click on the Web Preview button in Cloud Shell, then click Preview on port 8080:

If asked, log in with username admin and your auto-generated password.

You now have Jenkins set up in your Kubernetes cluster! Jenkins will drive your automated CI/CD pipelines in the next sections.


Task 6. Understanding the Application

You’ll deploy the sample application, gceme, as part of your continuous deployment pipeline. This Go language application is found in the sample-app directory of the repository. Running the gceme binary on a Compute Engine instance will display the instance’s metadata in an info card.

The application simulates a microservice with two operational modes:

  • Backend mode: gceme listens on port 8080 and provides Compute Engine instance metadata in JSON format.
  • Frontend mode: gceme queries the backend gceme service and displays the resulting JSON in the user interface.

Task 7. Deploying the Application

You will deploy the application into two distinct environments:

  • Production: This is the live site that your users will access.
  • Canary: A smaller-capacity site that receives a limited portion of user traffic. This environment is used to validate your software with live traffic before it is fully released to all users.

In Google Cloud Shell, navigate to the sample application directory:

cd sample-app

Create the Kubernetes namespace to logically isolate the deployment:

kubectl create ns production

Create the production and canary deployments, and the services using the kubectl apply commands:

kubectl apply -f k8s/production -n production
kubectl apply -f k8s/canary -n production
kubectl apply -f k8s/services -n production

SAMPLE OUTPUT:

student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes (qwiklabs-gcp-02-9241fd14328d)$ cd sample-app
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ kubectl create ns production
namespace/production created
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ kubectl apply -f k8s/production -n production; kubectl apply -f k8s/canary -n production; kubectl apply -f k8s/services -n production
deployment.apps/gceme-backend-production created
deployment.apps/gceme-frontend-production created
deployment.apps/gceme-backend-canary created
deployment.apps/gceme-frontend-canary created
service/gceme-backend created
service/gceme-frontend created
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ 

After the execution has completed, click on check my progress.

By default, only one replica of the frontend is deployed. Use the kubectl scale command to ensure that there are at least 4 replicas running at all times.

Scale up the production environment frontends by running the following command:

kubectl scale deployment gceme-frontend-production -n production --replicas 4

Now confirm that you have 5 pods running for the frontend, 4 for production traffic and 1 for canary releases (changes to the canary release will only affect 1 out of 5 (20%) of users):

kubectl get pods -n production -l app=gceme -l role=frontend

Also confirm that you have 2 pods for the backend, 1 for production and 1 for canary:

kubectl get pods -n production -l app=gceme -l role=backend

Retrieve the external IP for the production services:

kubectl get service gceme-frontend -n production

SAMPLE OUTPUT:

student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ kubectl get pods -n production -l app=gceme -l role=frontend
NAME                                         READY   STATUS    RESTARTS   AGE
gceme-frontend-canary-9b8c5bc59-grv55        1/1     Running   0          3m23s
gceme-frontend-production-5b869558c8-7mm4h   1/1     Running   0          17s
gceme-frontend-production-5b869558c8-rfmrn   1/1     Running   0          17s
gceme-frontend-production-5b869558c8-wxjv7   1/1     Running   0          17s
gceme-frontend-production-5b869558c8-zjh7r   1/1     Running   0          3m26s
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ kubectl get pods -n production -l app=gceme -l role=backend
NAME                                        READY   STATUS    RESTARTS   AGE
gceme-backend-canary-6d687cbb57-zp5n5       1/1     Running   0          3m31s
gceme-backend-production-65df8c6874-qskl9   1/1     Running   0          3m34s
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ kubectl get service gceme-frontend -n production
NAME             TYPE           CLUSTER-IP       EXTERNAL-IP      PORT(S)        AGE
gceme-frontend   LoadBalancer   34.118.230.108   35.196.125.101   80:32022/TCP   3m31s
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ 

Paste External IP into a browser to see the info card displayed on a card—you should get a similar page:

Now, store the frontend service load balancer IP in an environment variable for use later:

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.

Check the version output of the service by running the following command (it should read 1.0.0):

curl http://$FRONTEND_SERVICE_IP/version

SAMPLE OUTPUT:

student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ export FRONTEND_SERVICE_IP=$(kubectl get -o jsonpath="{.status.loadBalancer.ingress[0].ip}" --namespace=production services gceme-frontend)
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ curl http://$FRONTEND_SERVICE_IP/version
1.0.0
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ 

You have successfully deployed the sample application! Next, you will set up a pipeline for deploying your changes continuously and reliably.

Task 8. Creating the Jenkins Pipeline

Creating a repository to host the sample app source code

Create a copy of the gceme sample app and push it to a Cloud Source Repository:

gcloud source repos create default

After the execution has completed, click on check my progress.

Initialize the sample-app directory as its own Git repository:

git init
git config credential.helper gcloud.sh

Run the following command:

git remote add origin https://source.developers.google.com/p/$DEVSHELL_PROJECT_ID/r/default

Set the username and email address for your Git commits. Replace [EMAIL_ADDRESS] with your Git email address and [USERNAME] with your Git username:

git config --global user.email "[EMAIL_ADDRESS]"
git config --global user.name "[USERNAME]"

Add, commit, and push the files:

git add .
git commit -m "Initial commit"
git push origin master

Configure Jenkins Cloud for Kubernetes

In the Jenkins user interface, select Manage Jenkins > Nodes.

Click Clouds in the left navigation pane.

Click New cloud.

Type any name under Cloud name and then select Kubernetes for Type.

Click Create.

In the Jenkins URL field, enter the following value: http://cd-jenkins:8080

In the Jenkins tunnel field, enter the following value: cd-jenkins-agent:50000

Click Save.

Creating the Jenkins job

Navigate to your Jenkins user interface and follow these steps to configure a Pipeline job.

Click Dashboard > New Item in the left panel.

Name the project sample-app, then choose the Multibranch Pipeline option and click OK.

On the next page, in the Branch Sources section, select Git from Add Source dropdown.

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 drop-down, select the name of the credentials you created when adding your service account in the previous steps.

Under Scan Multibranch Pipeline Triggers section, check the Periodically if not otherwise run box and set the Interval value to 1 minute.

Your job configuration should look like this:

Click Save leaving all other options with their defaults.

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 click sample-app in the top left, the master job should be seen.

Note: The first run of the master job might fail until you make a few code changes in the next step.

You have successfully created a Jenkins pipeline! Next, you'll create the development environment for continuous integration.


Task 9. Creating the development environment

Development branches are environments where developers test their code changes before integrating them into the live site. These environments are scaled-down versions of the application but must be deployed using the same methods as the live environment.

Creating a development branch

To create a development environment from a feature branch, you can push the branch to the Git server and let Jenkins deploy your environment.

  • Create a development branch and push it to the Git server:
git checkout -b new-feature

Modifying the pipeline definition

The Jenkinsfile, which defines the pipeline, is written in Jenkins Pipeline Groovy syntax. This file allows the entire build pipeline to be described in a single file that resides with your source code. Pipelines offer powerful features such as parallelization and require manual user approval.

To ensure the pipeline functions correctly, you need to modify the Jenkinsfile to include your project ID.

Open the Jenkinsfile in your terminal editor, for example vi:

vim Jenkinsfile

Add your PROJECT_ID to the  REPLACE_WITH_YOUR_PROJECT_ID value. (Your PROJECT_ID is your Project ID found in the CONNECTION DETAILS section of the lab. You can also run gcloud config get-value project to find it.

Change the value of CLUSTER_ZONE to . You can get this value by running gcloud config get compute/zone.

PROJECT = "REPLACE_WITH_YOUR_PROJECT_ID"
APP_NAME = "gceme"
FE_SVC_NAME = "${APP_NAME}-frontend"
CLUSTER = "jenkins-cd"
CLUSTER_ZONE = ""
IMAGE_TAG = "gcr.io/${PROJECT}/${APP_NAME}:${env.BRANCH_NAME}.${env.BUILD_NUMBER}"
JENKINS_CRED = "${PROJECT}"

Modify the site

To demonstrate changing the application, you will change the gceme cards from blue to orange.

vi html.go

Change the two instances of <div class="card blue"> with following:

<div class="card orange">

Open main.go:

vim main.go

The version is defined in this line:

const version string = "1.0.0"

Update it to the following:

const version string = "2.0.0"

Save the main.go file one more time.


Task 10. Kick off Deployment

Commit and push your changes:

git add Jenkinsfile html.go main.go
git commit -m "Version 2.0.0"
git push origin new-feature

SAMPLE OUTPUT:

gcp-02-9241fd14328d)$ git add Jenkinsfile html.go main.go
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ git commit -m "Version 2.0.0"
[new-feature ca7d6b1] Version 2.0.0
 3 files changed, 5 insertions(+), 5 deletions(-)
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ git push origin new-feature
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 2 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 492 bytes | 246.00 KiB/s, done.
Total 5 (delta 4), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (4/4)
remote: Waiting for private key checker: 3/3 objects left
To https://source.developers.google.com/p/qwiklabs-gcp-02-9241fd14328d/r/default
 * [new branch]      new-feature -> new-feature
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ 

This will initiate a build of your development environment.

Once the change is pushed to the Git repository, go to the Jenkins user interface. There, you will see that the build has started for the new-feature branch. It may take up to a minute for the changes to be detected.

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=new-feature apply... messages to begin. Your new-feature branch will now be deployed to your cluster.

Note: In a development environment, avoid using a public-facing load balancer. To enhance your application’s security, you can utilize kubectl proxy. This proxy authenticates with the Kubernetes API and forwards requests from your local machine to the service within the cluster, without exposing the service to the Internet.

If nothing appears in the Build Executor, don’t worry. Simply navigate to the Jenkins homepage and select the sample app. Confirm that the new-feature pipeline has been created.

Once that's all taken care of, start the proxy in the background:

kubectl proxy &

If it stalls, press Ctrl + C to exit out. Verify that your application is accessible by sending a request to localhost and letting kubectl proxy forward it to your service:

curl \
http://localhost:8001/api/v1/namespaces/new-feature/services/gceme-frontend:80/proxy/version

You should see it respond with 2.0.0, which is the version that is now running.

If you receive a similar error:

{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {
  },
  "status": "Failure",
  "message": "no endpoints available for service \"gceme-frontend:80\"",
  "reason": "ServiceUnavailable",
  "code": 503

It means your frontend endpoint hasn't propagated yet—wait a little bit and try the curl command again. Move on when you get the following output:

2.0.0

You have set up the development environment! Next, you will build on what you learned in the previous module by deploying a canary release to test out a new feature.

SAMPLE OUTPUT:

student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ kubectl proxy &
[2] 1940
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ Starting to serve on 127.0.0.1:8001

student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ 
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ curl \
http://localhost:8001/api/v1/namespaces/new-feature/services/gceme-frontend:80/proxy/version
2.0.0
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ 

Task 11. Deploying a canary release

You have verified that your app is running the latest code in the development environment, so now deploy that code to the canary environment.

Create a canary branch and push it to the Git server:

git checkout -b canary
git push origin canary

In Jenkins, you should see the canary pipeline has kicked off. Once complete, you can check the service URL to ensure that some of the traffic is being served by your new version. You should see about 1 in 5 requests (in no particular order) returning version 2.0.0.

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

If you keep seeing 1.0.0, try running the above commands again. Once you've verified that the above works, end the command with Ctrl + C.

SAMPLE OUTPUT:

gcp-02-9241fd14328d)$ export FRONTEND_SERVICE_IP=$(kubectl get -o \
jsonpath="{.status.loadBalancer.ingress[0].ip}" --namespace=production services gceme-frontend)
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ while true; do curl http://$FRONTEND_SERVICE_IP/version; sleep 1; done
1.0.0
2.0.0
1.0.0
2.0.0
1.0.0
1.0.0
^C
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ 

That's it! You have deployed a canary release. Next you will deploy the new version to production.


Task 12. Deploying to production

Now that our canary release was successful and we haven't heard any customer complaints, deploy to the rest of your production fleet.

Create a canary branch and push it to the Git server:

git checkout master
git merge canary
git push origin master

In Jenkins, you should see the master pipeline has kicked off.

Once complete (which may take a few minutes), you can check the service URL to ensure that all of the traffic is being served by your new version, 2.0.0.

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

Once again, if you see instances of 1.0.0 try running the above commands again. You can stop this command by pressing Ctrl + C.

gcp-02-9241fd14328d)$ git checkout master
Switched to branch 'master'
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ git merge canary
Updating 3d6be99..ca7d6b1
Fast-forward
 Jenkinsfile | 4 ++--
 html.go     | 4 ++--
 main.go     | 2 +-
 3 files changed, 5 insertions(+), 5 deletions(-)
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ git push origin master
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To https://source.developers.google.com/p/qwiklabs-gcp-02-9241fd14328d/r/default
   3d6be99..ca7d6b1  master -> master
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ while true; do curl http://$FRONTEND_SERVICE_IP/version; sleep 1; done
2.0.0
2.0.0
2.0.0
2.0.0
2.0.0
2.0.0
2.0.0
2.0.0
^C
student_00_1a87387c348d@cloudshell:~/continuous-deployment-on-kubernetes/sample-app (qwiklabs-gcp-02-9241fd14328d)$ 

You can also navigate to site on which the gceme application displays the info cards. The card color changed from blue to orange.

Here's the command again to get the external IP address. Paste the External IP into a new tab to see the info card displayed:

kubectl get service gceme-frontend -n production

Task 13. Test your Understanding

Below are multiple-choice questions to reinforce your understanding of this lab's concepts. Answer them to the best of your abilities.

Question: Which are the following Kubernetes namespaces used in the lab?

Answer: production, kube-system, default

Question: The Helm chart is a collection of files that describe a related set of Kubernetes resources

Answer: True