Deploying fatjar Ktor applications on Google Cloud App Engine
Vinicius Carvalho
Posted on September 11, 2019
The original serverless platform
Google App Engine or GAE for short. Has always been an amazing service to run your web applications. Some highlights:
- Supports multiple runtimes (java, python, go, php, ruby, nodejs )
- Serverless execution mode, pay only for requests in flight
- Offers free tier for some services
- Support for versioning and traffic splitting
- Task scheduling and queuing of background tasks
- Many more, visit the official documentation for more information.
However for java developers there have always been a problem, you could only deploy war files, restricting you to the Servlet Spec.
Not anymore, starting with the java11 runtime, GAE now offers a way to run self contained jar files (Ktor, SpringBoot, Micronaut, Quarkus).
Introducing the Java11 runtime
Starting with the java11 runtime, you can now deploy a "fat jar" file and execute it instead of deploying a war file into a Jetty container.
All you need to do is create a new app.yaml
file that contains the correct entrypoint command:
runtime: java11
instance_class: F2
entrypoint: 'java -jar frontend-1.0-SNAPSHOT-all.jar'
service: default
You can pass any valid java argument to the JRE and if you would like to learn all the possible options of the yaml file, check the documentation
Skelleton project for ktor on GAE
Today I'll share my skelleton project for running ktor apps on GAE. Please feel free to clone it from here and use it as starting point.
Project layout
.
├── build.gradle
├── gradle
│ └── wrapper
├── gradle.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
├── main
│ ├── appengine
│ ├── java
│ ├── kotlin
│ └── resources
└── test
├── java
├── kotlin
└── resources
I've always liked the standard gradle project layout. The only difference here is the appengine
folder under our main
directory. This contains the app.yaml
deployment file.
Gradle configuration
First off, this project uses gradle 5.6. Your gradle/wrapper/gradle-wrapper.properties
should look like this:
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
The build.gradle
is generic enough that you can literally just copy and paste and use it for your project. As I mentioned before this is a template I use for all my projects.
buildscript {
ext.kotlin_version = '1.3.50'
ext.ktor_version = '1.2.4'
repositories {
mavenCentral()
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.github.jengelman.gradle.plugins:shadow:5.1.0'
classpath 'com.google.cloud.tools:appengine-gradle-plugin:1.+'
}
}
apply plugin: 'kotlin'
apply plugin: 'application'
apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'com.google.cloud.tools.appengine'
group 'io.igx.cloud'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
mainClassName = "io.ktor.server.netty.EngineMain"
repositories {
mavenCentral()
jcenter()
}
appengine {
stage {
artifact = "build/libs/$project.name-$project.version-all.jar"
}
}
test {
useJUnitPlatform()
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "io.ktor:ktor-server-core:$ktor_version"
implementation "io.ktor:ktor-server-netty:$ktor_version"
implementation "io.ktor:ktor-gson:$ktor_version"
testImplementation "org.assertj:assertj-core:3.13.2"
testImplementation "io.mockk:mockk:1.9.3"
testImplementation "org.junit.jupiter:junit-jupiter:5.5.1"
testImplementation 'org.koin:koin-test:2.0.1'
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
I'm using shadow plugin to create my fat jar. It integrates nicely on my application
gradle phase and generates an artifact appending -all
to its name.
I've also modified the appengine
defaults to point to the new jar created via the shadow
plugin.
And last, I added the gradle appengine plugin.
Very simple, but reusable and saves me a ton of time. To build/deploy
./gradlew clean build
./gradlew appengineDeploy
That's it, hope you enjoy it.
Happy coding
Posted on September 11, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.