Skip to content

Google Cloud Build

Vojtech Mašek edited this page Aug 19, 2019 · 2 revisions

General info

Documentation is here

Dev vs Prod environment

It's nice to have two configurations - cloudbuild.yaml which is invoked automatically on push. This configuration represents development pipeline, therefore it should build everything in development mode and deploy to staging environment.

Second configuration is called cloudbuild-prod.yaml and is invoked manually from gcloud console. This pipeline builds components in production mode and deploys application to production environment.

Backend

Following example shows build of microservice project with caching of Go in Google Cloud Storage. Output artifact of this build is Docker image, that is build and pushed in last two steps.

steps:
  #  Cache cloning with the following priority: branch cache -> master cache -> no cache
  - name: gcr.io/cloud-builders/gsutil
    entrypoint: '/bin/bash'
    args:
      [
        '-c',
        'gsutil -m -q cp gs://flowup-golang-cache/${_PROJECT}-$BRANCH_NAME.${_EXTENSION} /workspace || gsutil -m -q cp gs://flowup-golang-cache/${_PROJECT}-${_MASTER_BRANCH}.${_EXTENSION} /workspace || exit 0',
      ]

  #  Cache extraction
  - name: ubuntu
    entrypoint: '/bin/bash'
    args:
      [
        '-c',
        'tar -xf ${_PROJECT}-$BRANCH_NAME.${_EXTENSION} || tar -xf ${_PROJECT}-${_MASTER_BRANCH}.${_EXTENSION} || exit 0',
      ]

  #  Creating binaries folder
  - name: ubuntu
    entrypoint: '/bin/bash'
    args: ['-c', 'mkdir bin']

  #  Go install with dependencies download, saving binaries to previously created bin folder
  - name: golang:1.11
    entrypoint: 'go'
    args: ['install', '-installsuffix', 'cgo', './...']
    env:
      - 'GOCACHE=/workspace/.cache'
      - 'GOBIN=/workspace/bin'
      - 'CGO_ENABLED=0'
      - 'GOOS=linux'

  #  Cache compression with upload to bucket
  - name: ubuntu
    entrypoint: tar
    args: ['-cf', '${_PROJECT}-$BRANCH_NAME.${_EXTENSION}', '.cache']
  - name: gcr.io/cloud-builders/gsutil
    args: ['-m', '-q', 'cp', '/workspace/${_PROJECT}-$BRANCH_NAME.${_EXTENSION}', 'gs://flowup-golang-cache']

  #  Building backend dockerfile
  - name: gcr.io/cloud-builders/docker
    args: ['build', '-t', 'gcr.io/$PROJECT_ID/project-name:$BRANCH_NAME-$COMMIT_SHA', '-f', 'ci.Dockerfile', '.']

  - id: backend-push
    name: gcr.io/cloud-builders/docker
    args: ['push', 'gcr.io/$PROJECT_ID/project-name:$BRANCH_NAME-$COMMIT_SHA']

substitutions:
  _PROJECT: project-name
  _EXTENSION: tar.gz
  _MASTER_BRANCH: master
options:
  substitution_option: 'ALLOW_LOOSE'

Dockerfile is in the root of project and is named ci.Dockerfile:

FROM alpine:latest

WORKDIR /root/

RUN apk --no-cache add ca-certificates
COPY /bin .

cloudbuild.yaml can also have one more step that tests the code.

Frontend

Most of the project use monorepo, therefore, frontend is stored in repository in directory ui. Frontend can be hosted by multiple ways: from bucket or from container (in Kubernetes).

Served from bucket

# Build frontend

- id: build-frontend
  name: gcr.io/cloud-builders/npm:node-10.10.0
  entrypoint: '/bin/bash'
  dir: 'ui'
  args:
  - '-c'
  - 'npm ci && npm run lint && npm run build -- --progress=false --base-href /$BRANCH_NAME/'
  env:
  - 'BRANCH_NAME=$BRANCH_NAME'
  - 'COMMIT_SHA=$COMMIT_SHA'
  - 'BUILD_ID=$BUILD_ID'
  - 'TAG_NAME=$TAG_NAME'

# Deploy frontend
- id: deploy-frontend
  name: gcr.io/cloud-builders/gsutil
  dir: 'ui'
  args: ['-m', '-h', 'Cache-Control:public,max-age=60', 'rsync', '-d', '-r', '-a', 'public-read', 'dist', 'gs://some-bucket/project-name/$BRANCH_NAME']
  waitFor: ['build-frontend']

substitutions:
  _PROJECT: project-name
  _EXTENSION: tar.gz
  _MASTER_BRANCH: master
options:
  substitution_option: 'ALLOW_LOOSE'

Served from container

When frontend is server from container, it needs to have Docker image build and pushed to repository. Deployment is similar to deployment of backend and it is described in next section

cloudbuilder.yaml:

steps:
  - id: build-docker-frontend
    name: gcr.io/cloud-builders/docker
    dir: ui
    args: ['build', '-t', 'gcr.io/$PROJECT_ID/project-name-fe:$BRANCH_NAME-$COMMIT_SHA', '.']

  - id: push-docker-frontend
    name: gcr.io/cloud-builders/docker
    args: ['push', 'gcr.io/$PROJECT_ID/project-name-fe:$BRANCH_NAME-$COMMIT_SHA']
    waitFor:
      - build-docker-frontend

substitutions:
  _PROJECT: project-name
  _EXTENSION: tar.gz
  _MASTER_BRANCH: master
options:
  substitution_option: 'ALLOW_LOOSE'

and ui/Dockerfile contains following:

FROM node:10

WORKDIR /app
COPY . .

RUN npm ci --silent
RUN npm run lint

RUN npm run build -- --progress=false --base-href "/"

# prod build
FROM flowup/noginx

ENV BASE_HREF /
ENV STATIC_SITE_PATH /static
COPY --from=0 /app/dist /static

Steps in Dockerfile are equivalent to steps in cloudbuilder.yaml for building frontend served from bucket.

Deployment

Application is deployed using Helm, which has special cloud builder step, which runs only on master branch. Helm step can look as following:

steps:
  - id: deploy-backend
    name: gcr.io/$PROJECT_ID/helm
    args:
      - 'upgrade'
      - '--install'
      - 'project-name'
      - '--namespace'
      - 'project-name'
      - '--set'
      - 'global.image.tag=$BRANCH_NAME-$COMMIT_SHA'
      - '--set'
      - 'frontend.image.tag=$BRANCH_NAME-$COMMIT_SHA'
      - './helm/dashly'
    env:
      - CLOUDSDK_COMPUTE_ZONE=europe-west1-d # Cluster's zone
      - CLOUDSDK_CONTAINER_CLUSTER=staging-cluster # Cluster's name
      - BRANCH_NAME=$BRANCH_NAME
    waitFor:
      - push-backend
      - push-docker-frontend

Questions & Answers

Why are not images pushed by providing list of images to push?

Because images are pushed after all steps are successful. Despite that, application cannot be deployed correctly in one of the steps, because deployment depends on pushing images to Docker registry.

Some step doesn't have permission to do something. How to fix it?

Some steps need specific permission to access components of Google Cloud. These permission can be added to Cloud Builder service account in IAM. Name of the account is in form id@cloudbuild.gserviceaccount.com.

Table of permission:

Step Permissions
Deploy of frontend to bucket Storage Object Admin + correct ACL's in bucket
Deploy of application to Kubernetes cluster (Helm) Kubernetes Engine Admin
Clone this wiki locally