Spring Boot on GCP Cloud Functions

Page content

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

In this article, Spring Boot on Cloud Functions is examined.

Cloud Functions At A Glance

Cloud Functions is a fully managed (serverless) platform that allows you to run stateless functions that can be invoked via HTTP requests, PubSub and other background events and CloudEvents.

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

  • You get a HTTPS endpoint for your function if it is triggered by HTTP
  • Many runtimes supported
  • You just provide the code (no docker image if you don’t like that)
  • A particular function is never invoked concurrently, so your code can be simpler (does have price implications as touched upon in the next section)
  • Very transparent pricing model
  • IMHO especially useful for small “glue” components between you other services

With Cloud Functions memory, from 128 MB up to 8 GB, and cpu go hand in hand. You can only specify memory. The more memory you specify the more cpu is allocated (from 200 MHz with 128 MB to 4.8 GHz for 8 GB memory). High memory (and thereby cpu) means a higher price per invocation. So if your function is very cpu intensive but not using a lot of memory, you are paying for memory that you don’t use.

Non-HTTP Cloud Functions can automatically retry failed requests. A function can run for up to 9 minutes before it times out.

Cold start is discussed in relation to all Functions as a Service platforms. The cold start time is is the time it takes the platform to setup the code and infrastructure needed to handle a request. For a Java runtime, that would mean at least starting the JVM, causing it to read in the class files and in the example below, start Spring. It does not take very long, but comparing to a warm start, cold start time is time the client is doing additional waiting because you chose Cloud Functions. Trying to avoid cold start quickly either prompts you to use a language that has faster cold start times, such as Python and/or writing your code differently .

A Cloud Functions Pricing Example

As an example 256 MB memory where each request takes 500 ms and a total of 25.920.000 requests per month (10 per second) costs EUR 66.51 per 1 month + Egress traffic. Note though, that the Pricing Calculator did not even show the 4 or 8 GB options, so it might have had issues when I did the calculation.

Comparing to Cloud Run where each instance can handle concurrent requests, Cloud Functions is not cheap.

Spring Boot On Cloud Functions

The example is a simple function that responds to a HTTP 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 Functions should pick up nicely.

This article series tries to use canonical Spring, so it uses the Spring Cloud Functions library. More on that later.

The Supplier annotated with @Bean is what will get invoked up a HTTP request:

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

The cloudbuild.yaml file specifies what Cloud Build should to, essentially build, deploy to Cloud Functions:

 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/google.com/cloudsdktool/cloud-sdk'
 9    entrypoint: gcloud
10    args: ['functions', 'deploy', '${_ARTIFACT_NAME}', '--entry-point', 'org.springframework.cloud.function.adapter.gcp.GcfJarLauncher', '--region', '${_REGION}', '--runtime', 'java11', '--trigger-http', '--source', 'target/deploy', '--memory', '256MB', '--allow-unauthenticated']
11substitutions:
12  _ARTIFACT_NAME: springongcp-cloudfunctions
13  _REGION: europe-west1
14 

If you wanted to you would all more parameters to the Cloud Functions deploy command. For instance: different memory, max instances of the function, the request timeout or a different service account. Here we are using a lot of the default settings.

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 Functions

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

  1. Enable the Cloud Functions API and setup the IAM permissions for Cloud Build . (Make sure to add the role Cloud Functions Admin to the CLoud Build IAM member, or else the --allow-unauthenticated parameter below will fail.)
  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/cloudfunctions
  5. Run: gcloud builds submit

Running: gcloud functions describe springongcp-cloudfunctions --region europe-west1 --format='value(httpsTrigger.url)' gives your the url for the function. Now run curl URL_FROM_STEP_5.

  1. Now you know the time from Cloud Functions.

When you are done playing, delete the project (if you created a new one) or the deployment using this command: gcloud functions delete springongcp-cloudfunctions --region europe-west1

Viewing the logs

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

resource.type="cloud_function" resource.labels.function_name="springongcp-cloudfunctions"
logName="projects/cloudfunctions-313015/logs/cloudfunctions.googleapis.com%2Fcloud-functions"

and the logs will be present.

It is worth considering adding this line to the application.properties: spring.main.log-startup-info=false as it generates quite a lot of noise.

Conclusion

Cloud Functions is very simple to work with and allows you to not think about infrastructure. But there is still an " infrastructure leak" that you have to address: cold start time, functions adhering to the best practice of the Cloud Functions platform and my biggest concern: managing the many functions you can end up with and testing them locally. It can all be done, but compared to running Docker locally, Cloud Functions is quite different.

How about Spring Cloud Functions then? I wouldn’t use it. It is an abstraction, but in my opinion it is too abstract. Simple things as getting the HTTP method used to invoke the function, url parameters, setting the http return code etc is not supported.