Deploy Spring Boot app with Google Cloud Build

· September 6, 2019

In this blog post we look into Cloud Build service from Google, which is great and cheaper CI/CD tool compared to the more known Travis CI, circleci or CodeShip cloud services. It has a simple build configuration (cloudbuild.yaml) with many open-source build steps. It comes with 120 free build-minutes per day and up to 10 concurrent builds included.

Cloud Build with Jib

Our example is a simple spring boot application which can be found here. We are using the Jib Maven plugin to dockerize this application. If you are interested in more details about this checkout my previous blog post

Since with Jib is very easy to create a docker image our initial build step could be the following

- name: gcr.io/cloud-builders/mvn:3.5.0-jdk-8
  id: build
  args: ['clean', 'install', 'jib:build', '-Dimage=eu.gcr.io/${PROJECT_ID}/${_IMAGE_NAME}']

The build is executed as the root user with /builder/home set as home folder, with working directory set to /workspace. This is where our code is checked out. This /workspace directory is persisted between the builds steps.

With the .gcloudignore we can configure which files to ignore in order to save bandwidth when uploading the source code.

In our example only 6.6 KB was upload:

Creating temporary tarball archive of 10 file(s) totalling 6.6 KiB before compression.
Uploading tarball of [.] to [gs://learning-cloud-build-251912_cloudbuild/source/1567805425.2-45e7381b7d114a6b9f7edda290d769c4.tgz]

Caching Maven dependencies

Is not fun to download the Maven dependencies for each build, so what we could do is to package up the dependencies after the build has finished and store it on a bucket.

- name: gcr.io/cloud-builders/gsutil
  waitFor:
  - build
  dir: /root
  entrypoint: bash
  args:
  - -c
  - |
    tar -czf /tmp/m2-cache.tar.gz .m2 &&
    gsutil cp /tmp/m2-cache.tar.gz gs://${_GCS_CACHE_BUCKET}/m2-cache.tar.gz
  volumes:
  - name: m2
    path: /root/.m2/

With the volumes section we declare a Docker volume that is mounted into build steps to persist files between build steps. The waitFor helps to run build steps in parallel. In this case when the build step with id build has finished we can start in parallel uploading the Maven dependencies to GCS and starting the deployment to GKE.

And of course before a new build starts again we need to unpack the dependencies from the bucket.

- name: gcr.io/cloud-builders/gsutil
  dir: /root
  entrypoint: bash
  args:
  - -c
  - |
    (
      gsutil cp gs://${_GCS_CACHE_BUCKET}/m2-cache.tar.gz /tmp/m2-cache.tar.gz &&
      tar -xzf /tmp/m2-cache.tar.gz
    ) || echo 'Cache not found'
  volumes:
  - name: m2
    path: /root/.m2/

Start a new build

We can trigger the build manually using the gcloud CLI:

$ gcloud builds submit

The following command expects a cloudbuild.yaml file in the current directory. We can specify another configuration using the --config <config-file> argument.

Triggers

We can set a build trigger to re-build the image on any changes to the source repository. In the Cloud Console under Cloud Build -> Triggers is easy to set it up.

cloud-build-triggers

This trigger configuration makes sure whenever we push to any branch the build is triggered. Sometimes is handy to skip the trigger, like when we update documentation. In this case using [skip ci] or [ci skip] in the commit message makes sure that the build will be skipped.

Deploying

Cloud Build provides good support for deploying the artifact on various targets like GKE, App Engine, Cloud function, Cloud Run

Here we are going to look into how to deploy it to a GKE cluster.

First we enable the GKE API

$ gcloud services enable container.googleapis.com

Then we create a cluster:

gcloud container clusters create cloud-build-demo \
    --zone europe-west6-a \
    --machine-type g1-small  

After waiting couple of minutes our cluster is up and running

$ gcloud container clusters list

NAME              LOCATION        MASTER_VERSION  MASTER_IP      MACHINE_TYPE  NODE_VERSION   NUM_NODES  STATUS
cloud-build-demo  europe-west6-a  1.12.8-gke.10   34.65.239.226  g1-small      1.12.8-gke.10  3          RUNNING

To deploy to GKE we call the gke-deploy builds step which is a wrapper around kubectl apply deployment

- name: 'gcr.io/cloud-builders/gke-deploy:stable'
  args:
  - run
  - --image=eu.gcr.io/${PROJECT_ID}/${_IMAGE_NAME}:latest
  - --location=europe-west6-a
  - --cluster=cloud-build-demo
  - --expose=8080

Next we need to add the Kubernetes Engine Developer role to the service account used by Cloud Build service.

gke-roles-to-cloudbuild-serviceaccount

The gke-deploy step generated a deployment configuration:

Step #3: ################################################################################
Step #3: > Deployed Objects
Step #3:
Step #3: KIND                       NAME                        READY
Step #3: Deployment                 cloud-build-demo            Yes
Step #3: HorizontalPodAutoscaler    cloud-build-demo-hpa        Yes
Step #3: Service                    cloud-build-demo-service    Yes      34.65.110.195
Step #3:
Step #3: ################################################################################
$ http 34.65.110.195:8080
HTTP/1.1 200
Content-Length: 26
Content-Type: text/plain;charset=UTF-8
Date: Fri, 06 Sep 2019 11:45:14 GMT

Google Cloud Build rocks!!

Notification

Instead of waiting for the build, is better to be notified when the deployment was done. For this a Slack channel is very common and is super easy to set it up with Incoming WebHooks

slack

Using the following the build step

- name: gcr.io/cloud-builders/curl
  args:
  - -X
  - POST
  - -H
  - 'Content-type: application/json'
  - --data
  - '{"text":"New deployment done! --> http://34.65.110.195:8080/"}'
  - https://hooks.slack.com/services/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

we get a notification when the deployment was done.

slack_webbhook

The complete cloudconfig.yaml can be found here: https://github.com/altfatterz/cloud-build-demo/blob/master/cloudbuild.yaml

Twitter