Deploy CRaC Docker Image to Google Cloud Run

Deploy a Docker Image containing a CRaC enabled JDK and a pre-warmed, checkpointed application to Google Cloud Run - a fully managed serverless platform for containerized applications.

Authors: Sergio del Amo

Micronaut Version: 4.4.0

1. Getting Started

In this guide, we will create a Micronaut application written in Kotlin.

You are going to deploy a Docker Image containing a CRaC enabled JDK and a pre-warmed, checkpointed Micronaut application to Google Cloud Run.

2. Costs

This guide uses paid services; you may need to enable Billing in Google Cloud to complete some steps in this guide.

3. What you will need

To complete this guide, you will need the following:

The CRaC JDK currently requires that you are using a machine with x86_64 architecture. If you are using a different architecture, you cannot currently build a CRaC checkpointed application.

4. Solution

We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.

5. Writing the Application

Create an application using the Micronaut Command Line Interface or with Micronaut Launch.

mn create-app example.micronaut.micronautguide \
    --features=crac \
    --build=maven \
    --lang=kotlin \
    --test=junit
If you don’t specify the --build argument, Gradle is used as the build tool.
If you don’t specify the --lang argument, Java is used as the language.
If you don’t specify the --test argument, JUnit is used for Java and Kotlin, and Spock is used for Groovy.

The previous command creates a Micronaut application with the default package example.micronaut in a directory named micronautguide.

If you use Micronaut Launch, select Micronaut Application as application type and add crac features.

If you have an existing Micronaut application and want to add the functionality described here, you can view the dependency and configuration changes from the specified features and apply those changes to your application.

6. Controller

Add a controller which responds with the JSON payload in the root route.

{"message":"Hello World"}
src/main/kotlin/example/micronaut/HelloController.kt
package example.micronaut

import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get

@Controller (1)
class HelloController {

    @Get (2)
    fun index() = mapOf("message" to "Hello World") (3)
}
1 The class is defined as a controller with the @Controller annotation mapped to the path /.
2 The @Get annotation maps the method to an HTTP GET request.
3 The Micronaut framework will automatically convert it to JSON before sending it.
src/test/kotlin/example/micronaut/HelloControllerTest.kt
package example.micronaut

import io.micronaut.context.ApplicationContext
import io.micronaut.crac.test.CheckpointSimulator
import io.micronaut.http.client.HttpClient
import org.junit.jupiter.api.Test
import io.micronaut.runtime.server.EmbeddedServer

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue

class HelloControllerTest {

    @Test
    fun testHello() {
        ApplicationContext.run(EmbeddedServer::class.java).use { server ->
            var client = createHttpClient(server)

            var body: String = client.retrieve("/")

            assertNotNull(body)
            assertEquals("{\"message\":\"Hello World\"}", body)

            val checkpointSimulator = server.applicationContext.getBean(CheckpointSimulator::class.java)
            checkpointSimulator.runBeforeCheckpoint()

            assertFalse(server.isRunning)
            client.close()

            checkpointSimulator.runAfterRestore()
            assertTrue(server.isRunning)

            client = createHttpClient(server)

            body = client.retrieve("/")

            assertNotNull(body)
            assertEquals("{\"message\":\"Hello World\"}", body)

            client.close()
        }
    }

    private fun createHttpClient(server: EmbeddedServer) =
        server.applicationContext.createBean(HttpClient::class.java, server.url).toBlocking()
}

7. Google Cloud Platform

Signup for the Google Cloud Platform

7.1. Cloud SDK

Install the Cloud SDK CLI for your operating system.

Cloud SDK includes the gcloud command-line tool. Run the init command in your terminal:

gcloud init

Log in to your Google Cloud Platform:

gcloud auth login

7.2. Google Cloud Platform Project

Create a new project with a unique name (replace xxxxxx with alphanumeric characters of your choice):

gcloud projects create micronaut-guides-xxxxxx
In GCP, project ids are globally unique, so the id you used above is the one you should use in the rest of this guide.

Change your project:

gcloud config set project micronaut-guides-xxxxxx

If you forget the project id, you can list all projects:

gcloud projects list

7.3. Enable billing

Ensure you have a billing account created, and if not create one via the Google Cloud Platform Console.

To get a list of your billing accounts, run:

❯ gcloud beta billing accounts list
ACCOUNT_ID            NAME                  OPEN   MASTER_ACCOUNT_ID
XXXXXX-XXXXXX-XXXXXX  My Billing Account    True

You can then attach a billing account to your project:

> gcloud beta billing projects link micronaut-guides-xxxxxxx --billing-account=XXXXXX-XXXXXX-XXXXXX
billingAccountName: billingAccounts/XXXXXX-XXXXXX-XXXXXX
billingEnabled: true
name: projects/micronaut-guides-xxxxxxx/billingInfo
projectId: micronaut-guides-xxxxxxx

7.4. Enable the Google Cloud Container Registry API

We need somewhere to store our docker images, so we need to enable the Google Cloud Container Registry API for your project via the Google Cloud CLI:

gcloud services enable containerregistry.googleapis.com

7.5. Configure Google Cloud Docker

Run auth configure-docker via the Google Cloud CLI:

> gcloud auth configure-docker
Adding credentials for all GCR repositories.
WARNING: A long list of credential helpers may cause delays running 'docker build'. We recommend passing the registry name to configure only the registry you are using.
After update, the following will be written to your Docker config file located at [~/.docker/config.json]:
 {
  "credHelpers": {
    "gcr.io": "gcloud",
    "us.gcr.io": "gcloud",
    "eu.gcr.io": "gcloud",
    "asia.gcr.io": "gcloud",
    "staging-k8s.gcr.io": "gcloud",
    "marketplace.gcr.io": "gcloud"
  }
}

8. CRaC Docker Image

Generate a Docker Image containing a CRaC enabled JDK and a pre-warmed, checkpointed application:

./mvnw package -Dpackaging=docker-crac

9. Docker Push

Push the Docker image of your application to Google Cloud Container Registry or to Google Cloud Artifact Registry.

./mvnw deploy \
     -Dpackaging=docker-crac \
     -Djib.to.image=gcr.io/micronaut-guides-xxxxxxx/micronautguide:latest
In the image name above, micronaut-guides-xxxxxxx should be replaced by your project name you created earlier in the guide.

You get an output such as:

> Task :dockerPushCrac
Pushing image 'gcr.io/micronaut-guides-xxxxxxx/micronautguide:latest'.

10. Google Cloud Run Deploy

You can deploy to Google Cloud Run via the CLI. Use the value you configured in your build as the image argument’s value.

gcloud run deploy \
    --image=gcr.io/micronaut-guides-xxxxxx/micronautguide:latest \
    --platform managed \
    --execution-environment=gen2 \
    --allow-unauthenticated

You will see an output such as:

Service name (micronautguide):
Please specify a region:

...

 [22] us-central1
 [23] us-east1
 [24] us-east4

...

 [29] cancel
Please enter your numeric choice:  23

To make this the default region, run `gcloud config set run/region us-east1`.

Deploying container to Cloud Run service [micronautguide] in project [micronaut-guides-xxxxxx] region [us-east1]
✓ Deploying... Done.
  ✓ Creating Revision...
  ✓ Routing traffic...
  ✓ Setting IAM Policy...
Done.
Service [micronautguide] revision [micronautguide-00002-fat] has been deployed and is serving 100 percent of traffic.
Service URL: https://micronautguide-63kwrzytgq-ue.a.run.app

11. Running the Application

curl -i https://micronautguide-63kwrzytgq-ue.a.run.app
HTTP/2 200
content-type: application/json
x-cloud-trace-context: c3845249bc01f6c6a5bb7c01c4500bb8;o=1
date: Thu, 13 Apr 2023 11:09:58 GMT
server: Google Frontend
content-length: 25
alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000

{"message":"Hello World"}

12. Cleanup

12.1. Deleting the Cloud Run Service

Use the command gcloud run service delete to delete the service.

gcloud run services delete micronautguide

12.2. Deleting the project

The easiest way to eliminate billing is to delete the project you created for the tutorial.

Deleting a project has the following consequences:

  • If you used an existing project, you’ll also delete any other work you’ve done in the project.

  • You can’t reuse the project ID of a deleted project. If you created a custom project ID that you plan to use in the future, you should delete the resources inside the project instead. This ensures that URLs that use the project ID, such as an appspot.com URL, remain available.

  • If you are exploring multiple tutorials and quickstarts, reusing projects instead of deleting them prevents you from exceeding project quota limits.

12.2.1. Via the CLI

To delete the project using the Cloud SDK, run the following command, replacing YOUR_PROJECT_ID with the project ID:

gcloud projects delete YOUR_PROJECT_ID

12.2.2. Via the Cloud Platform Console

In the Cloud Platform Console, go to the Projects page.

In the project list, select the project you want to delete and click Delete project. After selecting the checkbox next to the project name, click Delete project

In the dialog, type the project ID, and then click Shut down to delete the project.

Deleting or turning off specific resources

You can individually delete or turn off some of the resources that you created during the tutorial.

14. Help with the Micronaut Framework

The Micronaut Foundation sponsored the creation of this Guide. A variety of consulting and support services are available.

15. License

All guides are released with an Apache license 2.0 license for the code and a Creative Commons Attribution 4.0 license for the writing and media (images…​).