Deploying fatjar Ktor applications on Google Cloud App Engine

viniciusccarvalho

Vinicius Carvalho

Posted on September 11, 2019

Deploying fatjar Ktor applications on Google Cloud App Engine

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

💖 💪 🙅 🚩
viniciusccarvalho
Vinicius Carvalho

Posted on September 11, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related