[DOCKER] Get along with Java containers in Cloud Run

Introduction

This is the 12th day article of NewsPicks Advent Calendar 2019. Yesterday was @ ncken's "The new team used asana with a little ingenuity".

This time, I'm going to experiment with GCP's Cloud Run, which has recently become GA, and Java applications, which were previously said to be incompatible with containers.

What is Cloud Run?

A service that can run stateless HTTP containers in a fully managed environment or GKE cluster. It's similar to Fargate in AWS, but I think it's easier to set up and handle HTTP containers.

I think the biggest difference between a fully managed environment and GKE is that there are no fixed costs. When running on GKE, there is always a cost for the GCE that makes up the Cluster. On the other hand, in a fully managed environment, you will incur costs for the hours the container is up and running. Depending on the application, which one is more cost effective will be different, so you will have to calculate it yourself.

Other differences are that you can change the machine type when running on GKE, whether you have access to VPC, etc., so check the constraints other than cost and select the optimum environment.

This time, I will not link with other services and want to keep costs down, so I will describe Cloud Run in a fully managed environment.

About compatibility between Java and containers

Nowadays, I think there are more opportunities to run applications with microservices and serverless. In these environments, there are many cases where the operation is different from the web application that is always running, such as scaling using a container or starting it temporarily. Here I would like to briefly write about the compatibility between Java and containers.

JVM and container

When using the JVM, I think that the compatibility with the container is not so good for the following reasons.

  1. JVM application starts slowly
  2. JVM applications use a lot of resources

However, there are an increasing number of cases where servers are autoscaled according to the load, such as microservices and serverless. At that time, Java, which has these two characteristics, has a big disadvantage compared to other languages.

Cloud Run, which is the subject of this article, is also a serverless application on GKE or in a fully managed environment. Therefore, it is necessary to devise to eliminate these disadvantages in order to handle JVM applications on Cloud Run. That's where GraalVM comes in.

GraalVM and container

GraalVM is a multilingual runtime announced by Oracle in 2018. graalvm_architecture.png

GraalVM can be used when building Java to create an immediate executable native image by AOT compilation.

It's important to note that GraalVM launches the JVM faster and uses relatively less resources, but it's by no means faster. In some cases, using the JVM may give better performance, so you should make your choice depending on where you want to use it. This time, we will use GraalVM to optimize for container and serverless.

Creating a service

Now let's actually create the service. To use GraalVM, this time we will use a framework called Quarkus. Quarkus is a Kubernetes native framework customized for GraalVM and HotSpot that currently supports Java and Kotlin. The official version of this Quarkus was also released on November 25, 2019. Therefore, there are still some features that are not enough compared to other frameworks such as Spring, but I think that features will be added little by little in the future.

Creating a project

First, let's create a Java application to run. The following command will create a Quarkus project.

mvn io.quarkus:quarkus-maven-plugin:1.0.1.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=getting-started \
    -DclassName="org.acme.quickstart.GreetingResource" \
    -Dpath="/hello"

Create Dockerfile for build

Next is the creation of the Dockerfile. When you create a project, a Dokcerfile for running the jvm and a Dockerfile for running the native-image are automatically generated. However, if you want to use this Dockerfile, you have to build it in the local environment, so create a Dockerfile that will be built when you create the image of the container. (The one created this time was created by referring to the one in the official Quarkus document.)

Dockerfile.multistage


## Stage 1 : build with maven builder image with native capabilities
FROM quay.io/quarkus/centos-quarkus-maven:19.2.1 AS build
COPY ./ /usr/src/app
USER root
RUN chown -R quarkus /usr/src/app
USER quarkus
RUN mvn -f /usr/src/app/pom.xml -Pnative clean package

## Stage 2 : create the docker final image
FROM registry.access.redhat.com/ubi8/ubi-minimal
WORKDIR /work/
COPY --from=build /usr/src/app/target/*-runner /work/application
RUN chmod 775 /work
EXPOSE 8080
ENV DISABLE_SIGNAL_HANDLERS true
CMD ["./application", "-Dquarkus.http.host=0.0.0.0", "-Dquarkus.http.port=8080"]

Build and run this image to launch your application.

CI/CD Next, we will prepare the CI / CD environment. This time I will use Cloud Build.

Creating cloudbuild.yml

In Cloud Build, each build step is described in yml or json. This time, I will describe the following steps.

  1. Docker Image build
  2. Docker Image push
  3. deploy to Cloud Run

The actual yml file looks like this:

cloudbuild.yml


steps:
  - name: 'gcr.io/cloud-builders/docker'
    id: 'Build Image'
    args: ['build', '-t', 'asia.gcr.io/{MyProject}/quarkus-example', '.', '-f', './src/main/docker/Dockerfile.multistage']
    dir: './'

  - name: 'gcr.io/cloud-builders/docker'
    id: 'Push to Container Registry'
    args: ['push', 'asia.gcr.io/{MyProject}/quarkus-example']
    dir: './'

  - name: 'gcr.io/cloud-builders/docker'
    id: 'Deploy to Cloud Run'
    args: ['beta', 'run', 'deploy', 'quarkus-example', '--image', 'asia.gcr.io/{MyProject}/quarkus-example', '--platform', 'managed', '--region', 'asia-northeast1', '--allow-unauthenticated']
    dir: './'

The gcr.io/cloud-builders/docker and gcr.io/cloud-builders/docker that appear here are called cloud builders and are pre-built that you can refer to in the build steps for task execution. It is an image. These are provided by Cloud Build and other communities. This time I'm using docker and gcloud images, but there are many other images such as kubectl and gradle.

Creating a trigger

Set the Cloud Build job to run when a commit occurs in master. When you kick the trigger, you can visualize the state of each step from the screen as shown below.

スクリーンショット 2019-12-09 23.57.21.png

If you try to develop a suitable function and merge the pull request, build will start.

Operation check

I will check the response time of the service created last and the operation of autoscale.

This time, we will use Scala's test tool Gatling. With Gatling, the test results are output as html, so you can easily grasp the results.

It is possible to acquire the following metrics on GCP, but I wanted to acquire the data from the client side as well, so I am using Gatling this time. スクリーンショット 2019-12-11 2.47.10.png

Gatling test scenario creation

First, create an empty sbt project and add the libraries to plugins.sbt, build.sbt. Then create and execute the following code.

plugins.sbt


addSbtPlugin("io.gatling" % "gatling-sbt" % "3.1.0")

build.sbt


libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % "3.3.1" % "test,it"
libraryDependencies += "io.gatling"            % "gatling-test-framework"    % "3.3.1" % "test,it"

BasicSimulation.scala


package computerdatabase

import io.gatling.core.Predef._
import io.gatling.core.structure.ScenarioBuilder
import io.gatling.http.Predef._
import io.gatling.http.protocol.HttpProtocolBuilder
import scala.concurrent.duration._

class BasicSimulation extends Simulation {

  val url = "your app url"

  val httpProtocol: HttpProtocolBuilder = http
    .baseUrl(url)
    .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
    .acceptEncodingHeader("gzip, deflate")
    .acceptLanguageHeader("en-US,en;q=0.5")
    .userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0")

  val scn: ScenarioBuilder = scenario("cloud run example scinario")
    .exec(http("cloud run example scinario")
      .get("/"))

  setUp(scn.inject(rampUsersPerSec(1) to (150) during (600 seconds)).protocols(httpProtocol))
}

A brief code description would be to gradually load the deployed Cloud Run application over a period of 10 minutes, up to 150 rps. This time we are only accessing one endpoint, but it is possible to load multiple endpoints.

test results

Now, let's actually move the scenario created above.

Response time

First, let's check the response time. スクリーンショット 2019-12-11 2.39.33.png As far as I can see in this bar chart, most requests received a response within 800ms.

Auto scale

Next, let's check about auto scale. スクリーンショット 2019-12-11 2.34.35.png The orange line represents rps and the stacked area chart represents response time. Looking at this, there are some parts where the response time is slow. Perhaps this is due to a cold start. However, even if it is a cold start, most of the requests are returned within 2 seconds, so I think it can withstand the service operation.

At the end

When I touch Cloud Run, there are situations where I have to launch multiple processes in one container because communication between containers is not possible, and because I can not connect to VPC, I wonder if I can hit the wall because what I can do is limited. think. However, it's very easy to launch a web app, so if you have the chance, why not make it one of your options?

One last note. There is a risk of charges for the items created this time, so be sure to delete them at the end.

Tomorrow is NewsPicks' Tegoshi, @ kohei1218's "Sign with Apple". looking forward to!

Reference article

Recommended Posts

Get along with Java containers in Cloud Run
[Java] Get the file path in the folder with List
I want to get along with Map [Java beginner]
Serverless Java EE starting with Quarkus and Cloud Run
Run batch with docker-compose with Java batch
[Java] Get KClass in Java [Kotlin]
Run Java VM with WebAssembly
Morphological analysis in Java with Kuromoji
[For beginners] Run Selenium in Java
Run Java application in Azure Batch
Get Null-safe Map values in Java
Use Java 11 with Google Cloud Functions
Get stuck in a Java primer
Run an application made with Java8 with Java6
Run an external process in Java
Get the result of POST in Java
[Java] Get List / Map elements with Iterator
For the time being, run the war file delivered in Java with Docker
Concurrency Method in Java with basic example
Get in touch with Eclipse MicroProfile Health
Java development with Codenvy: Hello World! Run
Read xlsx file in Java with Selenium
How to get the date in java
Get history from Zabbix server in Java
Split a string with ". (Dot)" in Java
Working with huge JSON in Java Lambda
Get Timestamp with Azure BlobStorage Java SDK
Try remote debugging of Java with Remote Containers in Visual Studio Code Insiders
[Java] Get the date with the LocalDateTime class
Run Rust from Java with JNA (Java Native Access)
Read a string in a PDF file with Java
Create a CSR with extended information in Java
Refactored GUI tools made with Java8 + JavaFX in 2016
Static code analysis with Checkstyle in Java + Gradle
Get block information with Hyperledger Iroha's Java SDK
How to get Class from Element in Java
Text extraction in Java from PDF with pdfbox-2.0.8
Get unixtime (seconds) from ZonedDateTime in Scala / Java
Using Gradle with VS Code, build Java → run
[Java] Get images with Google Custom Search API
Library "OSHI" to get system information in Java
Practice working with Unicode surrogate pairs in Java
[JAVA] [Spring] [MyBatis] Use IN () with SQL Builder
[Beginner] Install java development tool in cloud9 development environment.
[LeJOS] Get EV3 sensor value remotely with Java
Encrypt / decrypt with AES256 in PHP and Java
Programming with direct sum types in Java (Neta)
Partization in Java
Changes in Java 11
Rock-paper-scissors in Java
Pi in Java
FizzBuzz in Java
Get the URL of the HTTP redirect destination in Java
[Swift] Get document Id as well as document in Cloud Firestore
How to call functions in bulk with Java reflection
[Tutorial] Download Eclipse → Run the application with Java (Pleiades)
I tried using Google Cloud Vision API in Java
Experienced Java users get started with Android application development
[My memo] Let's get along with Pry / DB with Rails
Include image in jar file with java static method
[Tutorial] Download Eclipse → Run Web application with Java (Pleiades)