I spend a lot of my time helping application development and operations teams transition to new ways of working, involving Kubernetes and the Cloud (ideally Google Cloud, but sometimes AWS). A big part of this is creating a robust, flexible and observable CI/CD pipeline using tools such as Jenkins, Spinnaker and Flux (amongst others). All of this takes time and effort and sometimes, just sometimes, all I want is a pipeline, without any bells and whistles, that takes little time, little effort and just works. This article describes how I’ve achieved this for a Java application hosted in a Kubernetes cluster in Google Cloud (other Clouds are available and I expect with a little effort my approach could be adapted).

Prerequisites

Some knowledge of Google Cloud and Kubernetes

At least Editor access to a Google Cloud project with a working GKE cluster

A Java application you want to deploy via a CI/CD pipeline, with the source in a GitHub repo

TL;DR

There are 3 major components to making this CI/CD pipeline work:

The Jib Maven/Gradle plugin (https://github.com/GoogleContainerTools/jib) which “…builds optimized Docker and OCI images for your Java applications without a Docker daemon” Cloud Build (https://cloud.google.com/cloud-build) which provides the pipeline execution capability An OSS project called Keel (https://keel.sh/) — a Kubernetes Operator which scans your Docker registries for image changes and updates deployments if any are found

Detailed instructions

If you don’t have a suitable Java app I’ve prepared a ‘Hello World’ Spring Boot app that you can clone from here: https://github.com/cyberbliss/spring-boot-greetings

1: Enable GCP APIs

Go into the GCP Console → APIs & Services → Library and make sure the Google Container Registry (GCR) API and Cloud Build API are enabled.

2: Install & configure Jib

Jib is used to produce a Docker image containing our Java application as well as the entrypoint to execute it. As well as GCR it also supports AWS’ and Azure’s Container Registries and Docker Hub. There are many configuration options, which are documented here: https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#quickstart

Jib installs as a Maven plugin by adding the following to your application’s pom file:

<build>

<plugins>

… <plugin>

<groupId>com.google.cloud.tools</groupId>

<artifactId>jib-maven-plugin</artifactId>

<version>1.8.0</version>

<configuration>

<to>

<image>gcr.io/GCP_PROJECT_ID/greetings</image>

</to>

<container>

<jvmFlags>

<jvmFlag>-Xms512m</jvmFlag>

<jvmFlag>-Xdebug</jvmFlag>

</jvmFlags>

<ports>

<port>8080</port>

</ports>

</container>

</configuration>

</plugin>

</plugins>

</build>

Don’t forget to change the image name in the <configuration> section

For other configuration settings review Jib’s GitHub repo.

Jib can also be used as a Gradle plugin; see here for details: https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin

3: Enable Cloud Build

I am using Cloud Build to execute a compile/test/build pipeline which results in a Docker image being pushed into GCR. The pipeline definition is held in a cloudbuild.yaml file stored in the root directory of the Java application; here is an example:

substitutions:

_IMAGE_NAME: greetings



steps:



- # Uses the Cloud Builder Maven image since it is cached.

name: gcr.io/cloud-builders/mvn

dir: /root

entrypoint: bash

args:

- -c

- # Links the Docker config to /root/.docker/config.json so Jib picks it up.

# Note that this is only a temporary workaround.

# See https://github.com/GoogleContainerTools/jib/pull/1479.

|

mkdir .docker &&

ln -s $$HOME/.docker/config.json .docker/config.json



volumes:

- name: user.home

path: /root



- # Uses the Cloud Builder Maven image.

name: gcr.io/cloud-builders/mvn

args:

# Compiles the application.

- compile



# Runs the Jib build by using the latest version of the plugin.

# To use a specific version, configure the plugin in the pom.xml.

- com.google.cloud.tools:jib-maven-plugin:build

- -X



# Sets the target image reference to push to.

- -Dimage=gcr.io/${PROJECT_ID}/${_IMAGE_NAME}



volumes:

- name: user.home

path: /root

Before you can link Cloud Build with Github you need to install Google Cloud Build as an application within Github. Do this by going into Settings for your Github account, selecting ‘Applications’ from the options on the left hand side and following the instructions. You can choose to give Google Cloud Build access to all your repos or select specific ones.

You now need to connect Cloud Build with your Github repo, so go to the Cloud Build option in the GCP console and under the ‘Triggers’ menu option click on the ‘Connect Repository’ link:

Select the GitHub (Cloud Build GitHub App) source. Once you click continue you may be asked to authenticate to Github and then you will need to select the repo and connect. You can now create a Push trigger. The trigger created will be triggered on a push to any branch in the repo — if you only want it triggered on, say, a push to Master you can edit the trigger and alter the trigger type:

4: Commit your code

Once you are happy with your code, commit and push your changes to the repo in Github and switch back to Cloud Build in the GCP Console. Select the History option and you will see the build executing. Once it has finished if you look at the bottom of the build log there will be a message confirming that a Docker image has been built and pushed to the Project’s GCR. If you want you can use the Console to visit the Container Registry — under images will be a registry with the name you specified in the Jib configuration.

5: Deploy Keel into GKE

To deploy Keel you need kubectl and to be authenticated to the GKE cluster. It is straightforward to install and instructions can be found here: https://keel.sh/docs/#installation

6: Initial deployment of your application

The first deployment of your application needs to be done manually using kubectl. As a minimum you will need a Deployment manifest yaml file which looks similar to the one below (it’s also included in my demo Spring Boot app in the /kubernetes directory).

apiVersion: apps/v1

kind: Deployment

metadata:

name: greetings

labels:

app: greetings

keel.sh/policy: all

spec:

replicas: 1

selector:

matchLabels:

app: greetings

template:

metadata:

labels:

app: greetings

spec:

containers:

- name: greetings

image: gcr.io/GCP_PROJECT_ID/greetings:latest

imagePullPolicy: Always

ports:

- containerPort: 8080

livenessProbe:

httpGet:

path: /actuator/health

port: 8080

initialDelaySeconds: 30

timeoutSeconds: 10

readinessProbe:

httpGet:

path: /actuator/health

port: 8080

initialDelaySeconds: 30

timeoutSeconds: 10

resources:

requests:

memory: "512Mi"

cpu: "500m"

limits:

memory: "512Mi"

cpu: "500m"

The emphasised image: line you will need to change to specify your GCP Project ID.

The emphasised metadata.labels line shows how Keel knows what image and deployment to monitor for changes. Keel policies define when this deployment of your app should be updated — a policy of ‘all’ means any change but many others are supported; this page explains the others: https://keel.sh/docs/#policies.

Use kubectl apply -f <deployment yaml file> to deploy your app into GKE and notify Keel that there is an image repository that it needs to monitor.

7: Automated build and deploy

You have now set up everything required to automate the build and deployment of your Java application. As soon as you push a change to the repo in Github a Cloud Build job will be triggered and a few seconds after the new Docker image has been pushed into GCR, Keel will update the deployment in GKE (and the Pod will be restarted).