Cloud Build for CICD
In this tutorial you’ll walk through the steps needed to set up an end to end pipeline where GitHub triggers Cloud Build to process changes based on branches and deploy to GKE
What you’ll learn
- Enable services
- Prepare source files
- Build simple Dockerfile
- Build with a config file
- Connect to Git repository
- Setup automated builds
- Configure branch based deploys
Start Cloudshell Editor
This lab was designed and tested for use with Google Cloud Shell Editor. To access the editor,
- Access your google project at https://console.cloud.google.com.
- In the top right corner click on the cloud shell editor icon
- A new pane will open in the bottom of your window
- You can toggle between the terminal and an editor by clicking the Open Editor button in the top bar
- Then click Open Terminal to switch back to the terminal view
Environment Setup
In Cloud Shell, set your project ID and the project number for your project. Save them as PROJECT_ID and PROJECT_ID variables.
export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID \
--format='value(projectNumber)')
Enable services
Enable all necessary services:
gcloud services enable \
cloudbuild.googleapis.com \
container.googleapis.com \
containerregistry.googleapis.com \
artifactregistry.googleapis.com \
containerscanning.googleapis.com
Create a Docker repository in Artifact Registry
Artifact Registry is a single place for your organization to manage container images and language packages (such as Maven and npm). In this lab, we will use artifact registry to manage container images.
Create a new Docker repository named quickstart-docker-repo in the location us-west2 with the description “Docker repository”:
gcloud artifacts repositories create quickstart-docker-repo --repository-format=docker \
--location=us-west2 --description="Docker repository"
Verify that your repository was created:
gcloud artifacts repositories list
You will see quickstart-docker-repo in the list of displayed repositories.
Prepare source files
Download the source code from github.
git clone https://github.com/GoogleCloudPlatform/software-delivery-workshop.git
cd software-delivery-workshop && rm -rf .git
cd labs/gke-progression
In this section, you’ll build a simple Python app and Dockerfile. A Dockerfile is a text document that contains instructions for Docker to build an image.
Review the sample application located in src/app.py
import os
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World v1.0'
if __name__ == "__main__":
app.run(debug=True,host='0.0.0.0',port=8080)
Review the file src/Dockerfile with the following contents:
FROM python:3.7-slim
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . ./
RUN pip install Flask gunicorn
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 app:app
Build simple Dockerfile
Cloud Build allows you to build a Docker image using a Dockerfile. You don’t require a separate Cloud Build config file.
To build using a Dockerfile:
gcloud builds submit --region=us-west2 --tag us-west2-docker.pkg.dev/${PROJECT_ID}/quickstart-docker-repo/quickstart-image:tag1 src/
Note the value assigned to the tag parameter in the above command. You’ve just built a container image named quickstart-image using a Dockerfile and pushed the image to Artifact Registry into the docker repository created earlier.
Build with a config file
In this section you will use a Cloud Build config file to build the same container image as above. The build config file instructs Cloud Build to perform tasks based on your specifications.
In the same directory that contains quickstart.sh and the Dockerfile, create a file named cloudbuild.yaml with the following contents.
cat > ./cloudbuild.yaml << EOF
steps:
- name: 'gcr.io/cloud-builders/docker'
args: [ 'build', '-t', 'us-west2-docker.pkg.dev/${PROJECT_ID}/quickstart-docker-repo/quickstart-image:tag1', './src' ]
images:
- 'us-west2-docker.pkg.dev/${PROJECT_ID}/quickstart-docker-repo/quickstart-image:tag1'
EOF
Start the build specifying the use of the cloudbuild.yaml by running the following command:
gcloud builds submit --region=us-west2 --config cloudbuild.yaml
You’ve just built an image using the build config file and pushed the image to Artifact Registry.
Substitution Variables
It’s often useful to provide variables to a build to configure specific parameters. In the cloudbuild.yaml file you just created, the region was hard coded to us-west2. In this step you will utilize substitution variables to provide the region dynamically
Built in Cloud Build Variables are accessible using the standard convention of ${VAR}, and user-defined substitutions must begin with an underscore (_) such as ${_VAR}
Rewrite the cloudbuild.yaml to use the build id variable provided by Cloud Build for the tag name and also use a user-defined variable for the region which will be passed in to the build.
cat > ./cloudbuild.yaml << EOF
steps:
- name: 'gcr.io/cloud-builders/docker'
args: [ 'build', '-t', '\${_REGION}-docker.pkg.dev/${PROJECT_ID}/quickstart-docker-repo/quickstart-image:\${BUILD_ID}', './src' ]
images:
- '\${_REGION}-docker.pkg.dev/$PROJECT_ID/quickstart-docker-repo/quickstart-image:\${BUILD_ID}'
EOF
Start the build by running the following command that passes in the region variable:
gcloud builds submit --region=us-west2 --config cloudbuild.yaml \
--substitutions=_REGION="us-west2"
Connect to Git repository
If you don’t have one already, create an account on GitHub.
NOTE: If you have two-factor authentication set up on GitHub, create a personal access token with Repo permissions to use instead of a GitHub password with the command line. Save the token, you will use it multiple times in the future steps.
- Set your github username in an environment variable for later use
export GIT_USER=<your user name>
- Set the git user name and email
git config --global user.email "$(gcloud config list account --format "value(core.account)")"
git config --global user.name "$(gcloud config list account --format "value(core.account)")
- Optionally store credentials so you don’t have to enter them multiple times
git config --global credential.helper store
- Create a new public repository in GitHub called cloud-build-cicd
- Initialize a local git repo and push the contents to Github
Note: If using an access token, use the generated access token when prompted for password.
git init && git add . && git commit -m "initial commit"
git remote add origin https://github.com/${GIT_USER}/cloud-build-cicd.git
git branch -M main
git push -u origin main
To build source code from GitHub, you must first connect Cloud Build to your GitHub repository. In this section, you’ll use one way to connect your cloud-build-cicd repository to Cloud Build, but other mechanisms are also available.
- In the Google Cloud console navigation menu, click Cloud Build > Triggers.
Open Triggers page - At the bottom of the page, Click Connect repository.
- Under Select source, select GitHub (Cloud Build GitHub App).
- Click Continue.
- Authenticate your GitHub account.
- Under Repository, select GITHUB_USERNAME/cloud-build-cicd as your repository.
- Click the checkmark to agree to terms and conditions for trigger connection.
- Click Connect.
- Click Done.
Setup automated builds
Trigger
Open the Triggers page in the Google Cloud console:
Open Triggers page
At the bottom of the Triggers page, click Create trigger.
On the Create trigger page, enter the following settings:
- Name: Enter sample-trigger as the name of your trigger.
- Event: Select Push to a branch as the repository event to invoke your trigger.
- Source: Select the GITHUB_USERNAME/cloud-build-cicd repository as your source, which contains your source code and your build config file.
- Configuration: Choose Cloud Build configuration file as your build config file.
- Cloud Build configuration file location: Specify the path to your Cloud Build configuration file as /cloudbuild.yaml
Under Substitution variables click Add Variable and enter the following values:
- Variable 1 = _REGION
- Value = us-west2
Click Create to save your build trigger.
Make a change
In this section, you will commit a change to your cloud-build-cicd repository on your GitHub account.
Open the src/app.py file and update the line containing “Hello World v1.0” to “Hello World v1.1”
@app.route('/')
def hello_world():
return 'Hello World v1.1'
Commit your changes to GitHub by running the following commands:
git add .
git commit -m "update to v1.1"
git push origin main
You may be prompted to enter your credentials when pushing code to your repository. If prompted, enter your username and an authentication token.
You have now pushed a change to your repository. Your push will result in an automatic build by your trigger.
Review build details
In this section, you will view the build details associated with your invoked build after committing a change.
- In the Google Cloud console navigation menu, click Cloud Build > History.
Open the Cloud Build page - You will see the Build history page:
- In the Build column, click the name of a build.
- On the Build details page, click Build Artifacts.
Review the output locations of the assets build by this job
To view the build log, click the download icon and view the downloaded file.
You have successfully invoked a Cloud Build build using a trigger and viewed the build details.
Delete the trigger
You won’t use this trigger for the remainder of the lab so run the command below to delete it
gcloud beta builds triggers delete sample-trigger
Capstone: Full CI/CD Pipeline
In this section you will use what you’ve learned so far to build a typical developer workflow that incorporates dynamic branch based deployments for dev testing, Canary testing of commits to the main branch, and production rollout for tagged releases.
Create a GKE Cluster
This section will deploy to GKE so create a small cluster with the following commands
export CLUSTER=mycluster
export ZONE=us-central1-c
gcloud container clusters create ${CLUSTER} --zone=${ZONE} --num-nodes "2"
Give Cloud Build rights to your cluster.
Cloud Build will be deploying the application to your GKE Cluster and will need rights to do so.
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
--member=serviceAccount:${@cloudbuild.gserviceaccount.com">PROJECT_NUMBER}@cloudbuild.gserviceaccount.com \
--role=roles/container.developer
Replace placeholder values in the sample repository with your PROJECT_ID:
This command creates instances of the various config files unique to your current environment. It searches all the template files with extension tmpl, replaces the environment variables with the values and creates new files without tmpl extension.
export APP_NAME=sample-app
for template in $(find . -name '*.tmpl'); do envsubst '${PROJECT_ID} ${ZONE} ${CLUSTER} ${APP_NAME}' < ${template} > ${template%.*}; done
Branch Triggers for developers
Open the cloudshell editor in the current directory with the following command
cloudshell workspace .
Create a file called cloudbuild-branch.yaml with the following contents
steps:
### Build
- id: 'build'
name: 'gcr.io/cloud-builders/docker'
entrypoint: 'bash'
args:
- '-c'
- |
docker build -t ${_REGION}-docker.pkg.dev/${PROJECT_ID}/quickstart-docker-repo/$_APP_NAME:${BRANCH_NAME}-${SHORT_SHA} ./src
### Publish
- id: 'publish'
name: 'gcr.io/cloud-builders/docker'
entrypoint: 'bash'
args:
- '-c'
- |
docker push ${_REGION}-docker.pkg.dev/${PROJECT_ID}/quickstart-docker-repo/$_APP_NAME:${BRANCH_NAME}-${SHORT_SHA}
### Deploy
- id: 'deploy'
name: 'gcr.io/cloud-builders/gcloud'
env:
- 'KUBECONFIG=/kube/config'
entrypoint: 'bash'
args:
- '-c'
- |
PROJECT=$(gcloud config get-value core/project)
gcloud container clusters get-credentials "${_CLUSTER}" \
--project "${PROJECT}" \
--zone "${_ZONE}"
sed -i 's|gcr.io/$PROJECT_ID/$_APP_NAME:.*|${_REGION}-docker.pkg.dev/${PROJECT_ID}/quickstart-docker-repo/$_APP_NAME:${BRANCH_NAME}-${SHORT_SHA}|' ./k8s/deployments/dev/*.yaml
kubectl get ns ${BRANCH_NAME} || kubectl create ns ${BRANCH_NAME}
kubectl apply --namespace ${BRANCH_NAME} --recursive -f k8s/deployments/dev
kubectl apply --namespace ${BRANCH_NAME} --recursive -f k8s/services
images:
- '${_REGION}-docker.pkg.dev/${PROJECT_ID}/quickstart-docker-repo/$_APP_NAME:${BRANCH_NAME}-${SHORT_SHA}'
Toggle back to the terminal by clicking the Open Terminal button in the top nav bar
Create the trigger
gcloud beta builds triggers create github \
--name="branch-trigger" \
--repo-name=cloud-build-cicd \
--repo-owner=${GIT_USER} \
--branch-pattern="[^(.main)]" \
--build-config=cloudbuild-branch.yaml \
--substitutions=_APP_NAME="sample-app",_CLUSTER="mycluster",_REGION="us-west2",_ZONE="us-central1-c"
You can view the newly create trigger on the Cloud Build Triggers page
The trigger you just created will fire for commits to any branch that is not named main.
Create a branch
git checkout -b mybranch
Edit src/app.py change the output to v2.0
Commit the change and push to the repository
git add .
git commit -m "Updated to v2.0"
git push origin mybranch
- Review the build in progress on the build history page
Review GKE workloads. A new namespace ‘mybranch’ was created that matches the name of the branch and a sample-app-dev application was deployed.
Main Triggers for Canary deploys
Create a file called cloudbuild-main.yaml with the following contents
steps:
### Build
- id: 'build'
name: 'gcr.io/cloud-builders/docker'
entrypoint: 'bash'
args:
- '-c'
- |
docker build -t ${_REGION}-docker.pkg.dev/${PROJECT_ID}/quickstart-docker-repo/$_APP_NAME:${SHORT_SHA} ./src
### Test
### Publish
- id: 'publish'
name: 'gcr.io/cloud-builders/docker'
entrypoint: 'bash'
args:
- '-c'
- |
docker push ${_REGION}-docker.pkg.dev/${PROJECT_ID}/quickstart-docker-repo/$_APP_NAME:${SHORT_SHA}
### Deploy
- id: 'deploy'
name: 'gcr.io/cloud-builders/gcloud'
env:
- 'KUBECONFIG=/kube/config'
entrypoint: 'bash'
args:
- '-c'
- |
PROJECT=$$(gcloud config get-value core/project)
gcloud container clusters get-credentials "${_CLUSTER}" \
--project "$${PROJECT}" \
--zone "${_ZONE}"
sed -i 's|gcr.io/$PROJECT_ID/$_APP_NAME:.*|${_REGION}-docker.pkg.dev/${PROJECT_ID}/quickstart-docker-repo/$_APP_NAME:${SHORT_SHA}|' ./k8s/deployments/canary/*.yaml
kubectl get ns production || kubectl create ns production
kubectl apply --namespace production --recursive -f k8s/deployments/canary
kubectl apply --namespace production --recursive -f k8s/services
images:
- '${_REGION}-docker.pkg.dev/${PROJECT_ID}/quickstart-docker-repo/$_APP_NAME:${SHORT_SHA}'
Add the new yaml you just created file to the repo
git add . && git commit -m "Added Main Trigger"
Create the trigger for commits to the main branch
gcloud beta builds triggers create github \
--name="main-trigger" \
--repo-name=cloud-build-cicd \
--repo-owner=${GIT_USER} \
--branch-pattern=main \
--build-config=cloudbuild-main.yaml \
--substitutions=_APP_NAME="sample-app",_CLUSTER="mycluster",_REGION="us-west2",_ZONE="us-central1-c"
You can view the newly created trigger on the Cloud Build Triggers page
Merge the branch into main
git checkout main
git merge mybranch
git push origin main
This will trigger the build job for the main branch
Review the build in progress on the build history page
Review GKE workloads. Production name space was created and a sample-app-canary application was deployed.
Tag Triggers for production releases
Create a file called cloudbuild-tag.yaml with the following contents
steps:
### Add Tag
- id: 'add-tag'
name: 'gcr.io/cloud-builders/gcloud'
entrypoint: 'bash'
args:
- '-c'
- |
gcloud artifacts docker tags add ${_REGION}-docker.pkg.dev/${PROJECT_ID}/quickstart-docker-repo/$_APP_NAME:${SHORT_SHA} \
${_REGION}-docker.pkg.dev/${PROJECT_ID}/quickstart-docker-repo/$_APP_NAME:$TAG_NAME \
--quiet
### Deploy
- id: 'deploy'
name: 'gcr.io/cloud-builders/gcloud'
env:
- 'KUBECONFIG=/kube/config'
entrypoint: 'bash'
args:
- '-c'
- |
PROJECT=$$(gcloud config get-value core/project)
gcloud container clusters get-credentials "${_CLUSTER}" \
--project "$${PROJECT}" \
--zone "${_ZONE}"
sed -i 's|gcr.io/$PROJECT_ID/$_APP_NAME:.*|${_REGION}-docker.pkg.dev/${PROJECT_ID}/quickstart-docker-repo/$_APP_NAME:$TAG_NAME|' ./k8s/deployments/prod/*.yaml
kubectl apply --namespace production --recursive -f k8s/deployments/prod
kubectl apply --namespace production --recursive -f k8s/services
Notice that in this configuration you’re not rebuilding the source, but reusing the previously built image.
Add the new yaml file to the repo so it’s available to the trigger
git add . && git commit -m "Added Tag Trigger" && git push origin main
Wait for the main build to finish
Create the trigger
gcloud beta builds triggers create github \
--name="tag-trigger" \
--repo-name=cloud-build-cicd \
--repo-owner=${GIT_USER} \
--tag-pattern=.* \
--build-config=cloudbuild-tag.yaml \
--substitutions=_APP_NAME="sample-app",_CLUSTER="mycluster",_REGION="us-west2",_ZONE="us-central1-c"
Exercise the trigger by pushing a tag to the repo
git tag 2.0
git push origin 2.0
Review GKE workloads. Sample-app-production application was deployed to production namespace.