This tutorial shows you how to configure a CI/CD pipeline using Google’s Cloud Platform (GCP) and Weave Cloud. The pipeline consists of automated build, test, packaging and deployment stages taking code through from commit to deployment into the Kubernetes cluster. When it’s up and running you’ll be able to commit new source code to your application and it will immediately deploy it into the cluster - the fastest way from code to a production deployment!

For the different stages of the pipeline we’ll use:

  • Code repository: Google Cloud Platform Source Repositories (GSR)
  • Continuous integration: Google Cloud Builder (GCB)
  • Container registry: Google Container Registry (GCR)
  • Continuous deployment: Weave Cloud Deploy
  • Orchestrator: Google Kubernetes Engine (GKE)

Here’s a visualisation of the deployment pipeline:

All application code and configuration will be stored in a git repository. A commit merged into master will trigger the pipeline, causing a new container image to be built and deployed. We call this way of working ‘GitOps’ as it makes operating Kubernetes simple, repeatable and reversible.

You’ll need a Google Cloud Platform account which you can sign up for. Go into the GCP console and create a new project. For this tutorial we’ll use “GCP Weave CICD” with a project ID of “gcp-weave-cicd”.

Create a Code Repository

The start of the pipeline is a code repository for our application code. We’ll store our source code using GCSR.

Create a new repo by selecting “Source Repositories” then “Repositories” from the left-hand side menu. Click the link at the top of the page called “Create Repository” and create a repo called go-hello-world. The next page gives you three options:

GCSR repository choice

Select the middle option: “Clone your Cloud Repository to a local Git repository”, then follow these steps:

1. Install Google Cloud SDK
Follow the instructions for your platform to OS to install the Google Cloud SDK.

2. Open a terminal and authenticate with GCP
Set the project as gcp-weave-cicd or whatever you’ve named it.

gcloud init

3. Clone your empty Cloud Repository to a local Git repository

gcloud source repos clone go-hello-world --project=gcp-weave-cicd

Note: This may display a ‘Warning: Remote HEAD refers to nonexistent ref. Unable to check out’ message that is safe to ignore.

4. Switch into your new local Git repository

cd go-hello-world

Great! You’ve now created a local git repository that’s linked to your Google Cloud Source Repository.

Building Hello World with GCB

The next step is to add some code, for this tutorial we’re going to create a simple “Hello World” HTTP server to demonstrate the pipeline. We’ll create a Dockerfile and give Google Cloud Builder (GCB) instructions to build the code into a Docker image. The steps are:

1. Create a ‘src’ directory

mkdir src

2. Create a file “src/hello-world.go” with the following contents

package main

import (
    "fmt"
    "log"
    "net/http"
    "os"
)

func main() {
    port := "8080"
    if fromEnv := os.Getenv("port"); fromEnv != "" {
        port = fromEnv
    }

    server := http.NewServeMux()
    server.HandleFunc("/", hello)
    log.Fatal(http.ListenAndServe("0.0.0.0:"+port, server))
    log.Printf("Listening on 0.0.0.0:%s", port)
}

func hello(w http.ResponseWriter, r *http.Request) {
    log.Printf("Serving request: %s", r.URL.Path)
    host, _ := os.Hostname()
    fmt.Fprintf(w, "Hello world!\n")
    fmt.Fprintf(w, "Hostname: %s\n", host)
}

3. Create a Dockerfile in the root of the repository

FROM alpine:3.5

COPY ./hello-world /hello-world
WORKDIR /

EXPOSE 8080

CMD [ "/hello-world", "-port", "8080"]

4. Tell GCB to build the Docker file
The convention is to call the GCB file “cloudbuild.yaml”:

steps:
  - name: 'gcr.io/cloud-builders/go'
    args: ['build', '-o', 'hello-world', 'src/hello-world.go']
  - name: 'gcr.io/cloud-builders/docker'
    args: ['build', 
         '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME:$BRANCH_NAME-$SHORT_SHA',
         '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME:latest',
         '-f', 'Dockerfile', '.']
images:
  - 'gcr.io/$PROJECT_ID/$REPO_NAME:$BRANCH_NAME-$SHORT_SHA'
  - 'gcr.io/$PROJECT_ID/$REPO_NAME:latest'

As you can see there are two build steps; the first compiles the Go source file, the second builds the Dockerfile.

A nice feature of GCB is the ability to use environment variables in the build request file. Whatever you chose for your project and repo name, the file above will work. In our example the project was called ‘GCP Weave CICD’, so the build will result in a container image being created in Google Container Registry, with a path that looks something like this: “gcr.io/gcp-weave-cicd/go-hello-world:master-9709e0e”.

5. Commit and push everything
Commit your local changes and push to the remote GCSR repo:

git stage --all 
git commit --message "initial commit"
git push --set-upstream origin master

You’ll end up with a repository that contains the following:

GCSR app source code

Create a Build Trigger

To automatically trigger building the source every time there’s a new commit we use a build trigger.

  • In Google Cloud, select Container RegistryBuild triggers from the main menu.
  • Click Create trigger in the main content area
  • Select Cloud Source Repository as the source
  • Select go-hello-world (the git repository with the code)
  • In the trigger settings go to the Build configuration section and select cloudbuild.yaml. Note that you may need to specify the precise location of the /cloudbuild.yaml file.
  • Create trigger to complete the wizard

Test that the build and trigger work by clicking Run trigger. The results will be put into Build historyImages pages, from the left-hand side navigation. You should have pair of image tags, entitled latest and one similar to master-7c6ce76:

GCSR app source code

Configure Kubernetes Manifests

The next stage is to create a configuration file (a manifest) that tells Kubernetes how to deploy the application. Every time we deploy a new version of the code we’ll update these manifests. We’ll store these operational configuration files in separate Git repo, this time on GitHub.

1. Create a repo in Github called go-hello-world-config.
Select the box Initialize this repository with a README.

2. Clone it to a local git repository:

git clone <github repo url> go-hello-world-config

3. Switch to your new local Git repository: Create a directory called manifestsin the repo. We’ll add a couple of files to this directory that describe how the application should be run on the cluster.

cd go-hello-world-config
mkdir manifests

4. Create a file called manifests/hello-world-dep.yaml.
The first file describes how the application should be deployed, called a ‘Deployment’ in Kubernetes terminology. The contents of the file are:

---
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  annotations:
    flux.weave.works/automated: "true"
  name: hello-world
  labels:
    name: hello-world
spec:
  replicas: 1
  template:
    metadata:
      labels:
        name: hello-world
    spec:
      containers:
      - name: hello-world
        image: gcr.io/gcp-weave-cicd/go-hello-world:master-9709e0e
        ports:
        - containerPort: 8080

Edit the image line for the container so that it has your project name, repo name and the tag that we created using the build trigger above. To find this go to GCP’s Container Registry, in the Build history section each build shows Artifacts. Cut and paste your specific configuration into the image line of your hello-world-dep.yaml file.

Note: If you edit image line incorrectly the deployment will fail.

5. Create a Service manifest
Next, create a Service, which tells Kubernetes how the application should be exposed to the world. Create a file manifests/hello-world-svc.yaml:

---
apiVersion: v1
kind: Service
metadata:
  name: hello-world
  labels:
    name: hello-world
spec:
  type: LoadBalancer
  ports:
  - port: 8080
    targetPort: 8080
  selector:
    name: hello-world

This will tell Kubernetes to expose the application named ‘hello-world’, created by the ‘Deployment’ above, on port 8080 of an IP assigned to the cluster.

Running the application on GKE

If you don’t have one already, create a cluster in Google Kubernetes Engine (GKE). Follow the wizard to create a 3 node cluster.

Create GKE cluster

In your terminal window connect to the cluster, the command is in the Connect button for the cluster. You only need the first command at this point.

Connect to GKE cluster

Your local machine is now connected the cluster. We can deploy the application, using the manifests we created above. In the go-hello-world-config git repository root, run the following command:

kubectl apply -f ./manifests/

You should see output like this:

kubectl apply -f ./manifests/
deployment "hello-world" configured
service "hello-world" configured

To check the status of the deployment do:

$ kubectl get deployments
NAME          DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
hello-world   1         1         1            1           2d

To check the service status do:

$ kubectl get services
NAME          TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)          AGE
hello-world   LoadBalancer   10.11.253.202   35.202.175.232   8080:32316/TCP   2d
kubernetes    ClusterIP      10.11.240.1     <none>           443/TCP          2d

The application is quite simple, so it should’nt take long to be ready. We can use curl to check it, using the EXTERNAL-IP from the output above. The application is running on port 8080:

$ curl http://35.202.175.232:8080/
Hello world!
Hostname: hello-world-256096363-jq5d5

The final step is to push the manifests up to the GitHub repo:

git stage --all
git commit --message "Initial go-hello-world manifests"
git push

Set-up Weave Cloud

If you haven’t installed Weave Cloud through the Google Launcher yet, do that now. See Weave Cloud on Google Container Engine for the individual steps.

Configure Continuous Deployment

For Weave Cloud to automatically deploy new versions of the application it needs to know where the Kubernetes manifests are stored. Each time a deployment takes place it will update the manifest with the new container image tag before applying them to the cluster. In Weave Cloud click the cog icon on the top bar to go to the Configuration area. Under Config click on the Deploy menu.

There are two stages to the set-up:

  • Give Weave Cloud access to the github repository
  • Allow the agent on the cluster to deploy the latest version

GitHub configuration

For Weave Cloud to access and write to the Kuberenetes maniftests we tell it where the git repository is on GitHub.

Switch tabs to the GitHub page for your go-hello-world-config repository and select the Clone or download button. Make sure it’s showing Clone with SSH and then copy the URL for the repository.

Switch back to the Weave Cloud tab and paste this into the URL box in the Config Repository section. Remove the .git bit at the end so that it’s a proper URL. Set the Path to config files to manifests which is the directory we stored the manifests in. It should set Branch to be master for you automatically.

Setting the configure repository

Scroll down the page a little bit and click on the PUSH KEY button.

Push deployment key

This will take you to a page on Github that asks you to Authorize Weave Cloud. These permissions let Weave Cloud read and write the Kubernetes manifests from Github. After accepting these you’ll be taken back to Weave Cloud.

Configure the Agent

Next configure the agents on the cluster. Click the cog icon on the main bar to go to the configuration page. Take the ConfigDeploy menu.

Re-fill out the Config Repository section with the URL and the Path to config files. As you do this it will update the code snippet just below. Copy the code snippet from the page (use the Copy button on the right) and run it in your terminal:

$ kubectl apply -f "https://cloud.weave.works/k8s/flux.yaml?t=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&flux-version=%5E1&k8s-version=$(kubectl version | base64 | tr -d '\n')&git-label=flux-muddy-fire-55&git-url=git%40github.com%3Auser_name%2Fgo-hello-world&git-path=manifests&git-branch=master"

serviceaccount "weave-flux" configured
clusterrole "weave-flux" configured
clusterrolebinding "weave-flux" configured
secret "flux-git-deploy" configured
deployment "weave-flux-memcached" configured
service "weave-flux-memcached" configured
deployment "weave-flux-agent" configured

Return to the Weave Cloud Config page and scroll down and click on the PUSH KEY button. To test that it’s correctly configured you can take the link next to the button which will take you to the Deploy keys section of Github in a new tab.

We’ve now told the agents on the cluster how to access the repository and set-up a deployment key. As the agents reconfigure themselves the page will show a yellow warning notice. This can take a few moments, and then it will show a green notice with AGENTS CONFIGURED.

When you have the green notice click the Deploy button in the main navigation. You’ll now see a list of the services in the cluster. Select the one entitled default:deployment/hello-world and check that it says DEAUTOMATE. If the button says AUTOMATE click it to change it. When it’s set to automated the service name on the left will show a small truck icon:

Automate deploying a service

By putting it into automate mode we’re telling Weave Cloud that for this service we want it to:

  1. Monitor the container image specified in the Deployment, for new tag versions
  2. Whenever a new tag is discovered, update the Deployment manifests
  3. Commit and push the change in git
  4. Apply the updated manifest to the Kubernetes cluster

Deploy a change, completely automatically!

The time has come to deploy a new version of the containerized “hello world” web server, just by committing a change to the application code.

1. Change the application source code
In your terminal change into the go-hello-world/src/ source repository. In hello-world.go change the output statement text from “Hello world!” to something new:

func hello(w http.ResponseWriter, r *http.Request) {
    log.Printf("Serving request: %s", r.URL.Path)
    host, _ := os.Hostname()
    fmt.Fprintf(w, "Hello! The CICD pipeline works!\n")
    fmt.Fprintf(w, "Hostname: %s\n", host)
}

2. Commit and kick off the pipeline
When we commit and push the new code to the source repository it will automatically kick off the pipeline.

$ git add src/hello-world.go
$ git commit -m 'change text'
[master 840c5ed] change text
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git push
Counting objects: 4, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 354 bytes | 354.00 KiB/s, done.
Total 4 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2)
To https://source.developers.google.com/p/$PROJECT_NAME/r/go-hello-world
   7c6ce76..840c5ed  master -> master

3. Check each stage of the pipeline automation
We can check that each stage of the deployment pipeline has worked.

  • Check the code is pushed by visiting Source Repositories on GCP. Go into Source Code to view the code repository.
  • Check a build happened by going to Container Registry on GCP. Check in Build history for a new build.
  • Also check a new container image was created in Container Registry. In “Images” go into the go-hello-world repo where you should see a new container image with new tags.

4. Check Weave Cloud deployed the new version

Go into “Deploy” on Weave Cloud into the default:deployment/hello-world service. In the “Containers section on the page it tells you about the container that it deployed, something like gcr.io/gcp-weave-cicd/go-hello-world:master-cc97017 which should be the same tags as in GCP’s Container Registry.

In the History on the right it will say something like Synced to cluster: default:deployment/hello-world (4c5837b022ff) click on the git has to be taken across to the config repository on GitHub where you can see the change in the manifest.

Deploy automatically deploying the service

5. Test the application

When the new image has been deployed, you can test the application:

$ curl http://35.202.175.232:8080/
Hello! The CICD pipeline works!
Hostname: hello-world-256096363-3dkxt

As you can see we get the new updated message! Note that the pod name has changed, that’s because Kubernetes deployed a new pod when the image was updated.

Summary

Congratulations, you now have a fully automated CI/CD pipeline - the fastest way from commit to production deployment! The pipeline joins together Google Source Repositories, Google Cloud Builder, Google Container Registry and Weave Cloud. To take this further add your own application and build out more complex deployments through the pipeline.

Weave Cloud joins together continuous deployment, observability and metrics monitoring. As you deploy more regularly you’ll want to partner that with monitoring your applications for performance and reliability. To learn more take these links:

  • Learn about different aspects of Weave Cloud in our Tutorials
  • Read about metrics monitoring using the RED method