Spring Boot on GCP Cloud Run

Page content

This article series will explore various different ways to deploy a Spring Boot application on Google Cloud Platform (GCP).

In this first article, Spring Boot on Cloud Run is examined.

Cloud Run At A Glance

Cloud Run is a fully managed (serverless) platform that allows you to run stateless Docker containers that can be invoked via web requests, GCP Pub/Sub events or Google cloud triggers. If you need to run background tasks even when no requests are processing, then Cloud Run is not the right platform.

๐Ÿš€๐Ÿš€๐Ÿš€ Cloud Run features that are especially awesome: ๐Ÿš€๐Ÿš€๐Ÿš€

  • You get a HTTPS endpoint for your service
  • You can deploy multiple versions of your service, deciding how much traffic should go to any given version, enabling canary deployments
  • It scales with you workload, you only have to consider how many instances you want to allow (and pay for)
  • Automatically replicated across the region
  • The unit of deployment is the familiar Docker container

By default Cloud Run will scale to zero instances if no requests are received in 15 minutes (it has 10 seconds to shutdown once a SIGTERM is received). However, you can specify that a minimum of container instances must always be running (to avoid cold starts).

A given request will time out (504) if no response is returned in 5 minutes, configurable up to 60 minutes. Each container can be configured to receive concurrent requests (up to 250 per instance), but this can be scaled down to 1 request at a time (if your code can’t cope with concurrent requests at all). Note that an instance is terminated if it returns a http 5xx status code more than 20 times in a row.

The container has a filesystem which lives in-memory (using the allocated memory). The instance is allocated a default of 256 MB of memory, configurable up to 8 GB. 1 CPU is allocated by default but this can be set to 2 or 4 depending on the memory configuration.

The above is a summary of this document: Container runtime contract.

A Cloud Run Pricing Example

As an example 512 MB memory and 2 CPUs with 200 concurrent requests per container (default number of Tomcat worker threads in Spring Boot is 200) where each request takes 500 ms and a total of 25.920.000 requests per month (10 per second) costs EUR 8.13 per 1 month + Egress traffic. If the number of requests is 100 per second the cost would be EUR 110.11 per 1 month + Egress. You might also need a Load Balancer if the built is not sufficient.

Spring Boot On Cloud Run

The example is a simple web application that responds to a GET request. It will reply with a payload showing the current UTC time. It will also log a line per request which should be present in Logs Explorer. By default, Spring Boot logs to the console, which Cloud Run should pick up nicely.

The controller itself if very simple (not leveraging build packs or layers which the latest Spring Boot versions support):

 1@SpringBootApplication
 2@RestController
 3@Slf4j
 4public class SpringOnGcpCloudRunApplication {
 5
 6    public static void main(String[] args) {
 7        SpringApplication.run(SpringOnGcpCloudRunApplication.class, args);
 8    }
 9
10    @GetMapping("/time")
11    public String time() {
12        log.info("Request to /time");
13        ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC"));
14        return format("{\"utc\": \"%s\"}", ISO_LOCAL_DATE_TIME.format(now));
15    }
16
17}

The Dockerfile is minimal:

1FROM adoptopenjdk/openjdk11:jdk-11.0.10_9-alpine-slim
2RUN addgroup -S spring && adduser -S spring -G spring
3USER spring:spring
4ARG JAR_FILE=JAR_FILE_MUST_BE_SPECIFIED_AS_BUILD_ARG
5COPY ${JAR_FILE} app.jar
6ENTRYPOINT ["java", "-Djava.security.edg=file:/dev/./urandom","-jar","/app.jar"]

The cloudbuild.yaml file specifies what Cloud Build should to, essentially build, push to repository, deploy from repository to Cloud Run:

 1 steps:
 2  - name: maven:3-jdk-11
 3    entrypoint: mvn
 4    args: ["test"]
 5  - name: maven:3-jdk-11
 6    entrypoint: mvn
 7    args: ["package", "-Dmaven.test.skip=true"]
 8  - name: gcr.io/cloud-builders/docker
 9    args: ["build", "-t", "gcr.io/$PROJECT_ID/${_ARTIFACT_NAME}:${_VERSION}", "--build-arg=JAR_FILE=target/${_ARTIFACT_NAME}-${_VERSION}-SNAPSHOT.jar", "."]
10  - name: 'gcr.io/cloud-builders/docker'
11    args: ['push', 'gcr.io/$PROJECT_ID/${_ARTIFACT_NAME}:${_VERSION}']
12  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
13    entrypoint: gcloud
14    args: ['run', 'deploy', '${_ARTIFACT_NAME}', '--image', 'gcr.io/$PROJECT_ID/${_ARTIFACT_NAME}:${_VERSION}', '--region', '${_REGION}', '--platform', 'managed', '--allow-unauthenticated']
15images: ["gcr.io/$PROJECT_ID/${_ARTIFACT_NAME}:${_VERSION}"]
16substitutions:
17  _VERSION: 0.0.1
18  _ARTIFACT_NAME: springongcp-cloudrun
19  _REGION: europe-north1
20 

If you wanted to you would all more parameters to the Cloud Run deploy command. For instance: memory, cpus, max concurrency, the maximum number of instance (and maybe the minimum), the request timeout etc. Here we are using the defaults.

How to run it?

We will keep it simple as this is an example, not a production pipeline (maybe an article on that will come later?). The overall idea is this:

Spring Boot Cloud Run

Before starting, consider creating a new project in GCP, which you can easily delete again once done.

  1. Enable the Cloud Run Admin API and setup the IAM permissions for Cloud Build
  2. Go to the Cloud Console and start Cloud Shell.
  3. Clone the repository: git clone [email protected]:kimsaabyepedersen/spring-boot-on-gcp.git
  4. Enter the directory: cd spring-boot-on-gcp/cloudrun
  5. Run: gcloud builds submit
  6. Run: gcloud run services list --platform managed and from the service springongcp-cloudrun grab the URL, append /time and curl it (replace the URL in the following example): curl URL_FROM_STEP_5/time.
  7. Now you know the time from Cloud Run.

When you are done playing, delete the project (if you created a new one) or the deployment using this command: gcloud run services delete springongcp-cloudrun --platform managed --region europe-north1

Viewing the logs

Go to Logs Explorer and in the Query builder use this filter:

logName="projects/spring-boot-gcp-cloud-run/logs/run.googleapis.com%2Fstdout"
resource.type="cloud_run_revision" resource.labels.service_name="springongcp-cloudrun"

and the logs will be present.

Conclusion

Cloud Run is very simple to work with and allows you to not think about infrastructure. Using containers as its unit of deployment means that you can easily test on your local machine what you will be deploying in the cloud. That will definitely boost developer productivity.

Note though, that you are deploying a container. If you compare it to Kubernetes, this is different from a pod. So you can’t use a sidecar container in Cloud Run, but very often a pod is a 1:1 with a container and then Cloud Run is worth knowing.

Further Reading