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.
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.
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.
When using the JVM, I think that the compatibility with the container is not so good for the following reasons.
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 is a multilingual runtime announced by Oracle in 2018.
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.
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.
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"
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.
In Cloud Build, each build step is described in yml or json. This time, I will describe the following steps.
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.
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.
If you try to develop a suitable function and merge the pull request, build will start.
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.
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.
Now, let's actually move the scenario created above.
First, let's check the response time. As far as I can see in this bar chart, most requests received a response within 800ms.
Next, let's check about auto scale. 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.
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!
Recommended Posts