CI/CD - Secure Deployment in GCP Cloud Run / App Engine using Cloud Build and Secret Manager - Part 1

Introduction

In this article, you will see how you can use CI/CD pipeline using Cloud Build and Secret Manager in GCP to deploy the application in Cloud Run or App Engine. In Part 2 we will connect to a MySQL database using Private IP.

STEPS

Create a repository in GCP

image.png Open the repository in Cloud Shell editior.

Download a starter spring project

image.png

Upload the demo.zip into the Cloud shell and unzip the file into the repository folder in Cloud Shell.

Update DemoApplication.java code

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.core.env.Environment;
import org.springframework.beans.factory.annotation.Autowired;

@SpringBootApplication
@RestController
public class DemoApplication {

    @Autowired
    Environment env;
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @RequestMapping
    public String home(){
        return "hello gcp:" +"jdbcuser=" + env.getProperty("jdbc.user")+ " jdbcpassword=" + env.getProperty("jdbc.password")+ "jdbcurl=" + env.getProperty("jdbc.url");
    }

}

We have added environment properties in the code to check the values passed.

Add cloudbuild-cr.yaml in the root folder

steps:
  - name: gradle:7.4.2-jdk17
    entrypoint: gradle
    args: ["assemble"]
  - name: gcr.io/cloud-builders/docker
    args: ["build", "-t", "us-docker.pkg.dev/$PROJECT_ID/gcr.io/springbootgcp:$COMMIT_SHA", "--build-arg=JAR_FILE=build/libs/springbootgcp-0.0.1-SNAPSHOT.jar", "."]
  - name: gcr.io/cloud-builders/docker
    args: [ "push", "us-docker.pkg.dev/$PROJECT_ID/gcr.io/springbootgcp:$COMMIT_SHA" ]
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: gcloud
    args: ['run', 'deploy', 'springbootgcpcr', '--image',  "us-docker.pkg.dev/$PROJECT_ID/gcr.io/springbootgcp:$COMMIT_SHA" , '--allow-unauthenticated','--port=8080','--region', 'us-central1' ,'--set-secrets=--jdbc.user=jdbcuser:latest,  --jdbc.password=jdbcpassword:latest, --jdbc.url=jdbcurl:latest']
timeout: "1600s"
options:
  logging: CLOUD_LOGGING_ONLY

Cloud build runs three steps: assemble, build, push and finally run.

Add Dockerfile in the root folder

FROM openjdk:17
ARG JAR_FILE=build/libs/springbootgcp-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

Add jdbc.user, jdbc.password and jdbc.url secrets in Secret Manager

image.png Give developer and cloudbuild service account "Secret Manager Secret Accessor" role.

Create a gcr.io Artifact repository for Docker images

image.png

Now create a Cloud Build trigger using cloudbuild-cr.yaml

image.png

Running the trigger will deploy the Cloud Run application

When you run the first time you may see an error in the build but it deploys the application properly in Cloud Run.

Next Step is to deploy into App Engine

Create the app first

gcloud app create --region=us-central

Add app.yaml in root folder

runtime: java17
entrypoint: java -jar app.jar

Add cloudbuild-app.yaml in root folder

steps:
  - name: gcr.io/cloud-builders/gcloud
    entrypoint: 'bash'
    args: [ '-c', "gcloud secrets versions access latest --secret=appprop >  src/main/resources/application.properties" ]
  - name: gradle:7.4.2-jdk17
    entrypoint: gradle
    args: [ "assemble" ]
  - name: "gcr.io/cloud-builders/gcloud"
    entrypoint: bash
    args: ['-c', 'mkdir appdeploy && cp  /workspace/app.yaml /workspace/appdeploy/ && cp /workspace/build/libs/springbootgcp-0.0.1-SNAPSHOT.jar /workspace/appdeploy/app.jar ' ]
  - name: "gcr.io/cloud-builders/gcloud"
    entrypoint: gcloud
    args: ['app', 'deploy','./appdeploy/app.yaml' ]
timeout: "1600s"
options:
  logging: CLOUD_LOGGING_ONLY

Add an "appprop" secret key in Secret Manager and upload a file with the following keys

jdbc.user=root
jdbc.password=password
jdbc.url=jdbc:mysql://10.11.0.3:3306/testdb

Run the trigger and it would deploy the application in App Engine as a default service.

Conclusion

I have posted a detailed youtube video of this article. Also, the full source code is available on GitHub. App Engine doesn't have direct integration with Secret Manager currently that's why I had to use bash script. The next step is to build on this tutorial and add a MySql connection using Private IP.