We have built our Cloud Native - Continuous Integration / Continuous Deployment pipeline with GCP to build, test and deploy an application (Next.js) with interim status notifications.
Docker has been used extensively in this pipeline, from building the application, testing the application through webdriverIO docker service in Selenium Hub - chrome/firefox setup and then persist application image in the repository and finally deploy the image.
At this juncture, we would also like to highlight the challenges that we have resolved while setting up this pipeline, While the first was more related to
Ui tests as a git submodule to Next.js application repository(please have a look into our blog here) and next one was related to
passing multiple NEXT_PUBLIC env variables with docker at runtime(have a look into our past blog here for more detail).
Let’s get into our Cloud Native CI/CD pipeline as shown below with multiple steps. Each step gets executed with a cloud component. Within Cloud Build, we have
Cloud Builder to execute that step in the build pipeline. Cloud Builders are container images used to execute the step and have added a list of cloud builders used in this pipeline at last in the Appendix section. All the steps are defined in the
cloudbuild.yml file and the pipeline gets started with the help of
Cloud Build Trigger. With this pipeline, build gets triggered with a push to main branch.
Build process gets started by pulling the code with submodules from
cloud source repositories using cloud builder
gcr.io/cloud-builders/git and then pipeline gets to the next step, to build application images with docker. For this, we have a
docker-compose.yml file, with cloud builder
docker/compose:1.19.0 executing this step, to bring up services like application (Next.js app), selenium-hub, chrome/firefox and have them running on their defined ports and within the
cloudbuild network. This
cloudbuild network allows them to communicate with each other like, Ui tests will use application service to test the image.
With multiple NEXT_PUBLIC env variables, application service will take time to load them. So, we have added a
healthcheck to the application service that ensures the application is running. Also, If docker takes more time to have the service up or has some issues, passing
timeout value (seconds) will stop the pipeline.
Moving on to the next step - Test Execution, webdriverIO tests get executed by installing the test dependencies from package.json and start running the test on host with cloud builder
node:18. At the end of test execution, with hook class, we are writing the test
Exit Code (0 Pass / 1 Fail) to a file and sharing the test status via Google Chat webhook. Here, tests might fail. So, setting
allow_failure: true to proceed with the next pipeline step. After test execution gets completed, we are pushing the test results to cloud bucket with cloud builder
gcr.io/cloud-builders/gsutill. With this, our Continuous Integration pipeline steps gets completed and let’s get into Continuous Deployment pipeline steps.
Before pushing the image to
Container Registry, we have Check Gate to verify the test results and in check gate, we are reading the test exit code file using cloud builder
ubuntu. For test failures with exit code 1 - Cloud Build pipeline gets stopped ! With exit code 0 (all tests passed), pipeline gets to the next step - Push Image. Using cloud builder
gcr.io/cloud-builders/docker we are tagging the image to
latest and pushing it to the Container Registry. Then, the image with
latest tag from Container Registry is deployed to Cloud Run with cloud builder
gcr.io/google.com/cloudsdktool/cloud-sdk and the application is available to public users.
Between the deployment pipeline steps, we are using cloud builder
curlimages/curl to share pipeline step notifications via GoogleChat webhook. Also, we have a step to clear last run test results using cloud builder
gcr.io/cloud-builders/gsutill and it gets executed first before pulling the code. Sometimes the last build might have failed in between and no test results to clear. So, we are setting
allow_failure: true to proceed with the next pipeline step.