Guillermo Robles Guillermo Robles

...with Google Cloud Platform.

Kotlin is a modern language with a rich syntax that has gained tremendous popularity among Android developers in the last years. At Google I/O 19, Google announced that Kotlin will be first platform language in Android.

Kotlin has not been only designed for Android. Actually, it can target different platforms such as JVM (Java Virtual Machine), JavaScript, armv7 and armv8, x64 and x86, web assembly among other targets.

In this post, I am going to show how affordable is to develop with Kotlin and a bit of YML :) a back-end service that follows The Twelve-Factor App with a very little learning curve for those developers who are familiarized already with Kotlin.

Firstly, in my git repository has codebase that illustrates this showcase.

All software dependencies are declared using a build automation tool like Gradle, three main dependencies are Ktor , Jib and logback. Ktor is a web framework built by and for Kotlin developers. Jib is going to help us to achieve containerization with docker with no need to write a dockerfile. Jib will create a distroless docker image which only contains our application and its dependencies for runtime environment. Logback is a logging framework that will stream to stackdriver our application behavior.

Moreover cloud build will provide continuous deployment to our infrastructure and cloud run will bring our docker image to a runtime environment which it is fully managed, and it auto-scales horizontally.

Ktor provides an intuitive DSL for defining our application routing.

fun Routing.gets() {
          get("/hello") {
              call.respondText("Hi! ktor is running!")
        }
      }

This routing is very simple, it defines a GET method in HTTP for path "/hello", it responds in plain text with "Hi! ktor is running!", you can try out here More extended documentation and learning material can be found in Ktor official channel.

If you have worked with Gradle before, this build configuration will seem straightforward for you. It describes plugins and dependency definitions. The most important thing to recall is JVM target compatibility. Jib distroless image is base in OpenJDK 11 which is the last long support version. We need to define the target compatibility in case that build environment is using a higher JVM version. Also, Jib needs to have a reference for an entry point to start up the container, I have defined mainClass which is io.ktor.server.jetty.DevelopmentEngine

 1 buildscript {
 2         ext.kotlin_version = '1.5.31'
 3         ext.ktor_version = '1.6.4'
 4         ext.jib_version = '3.1.4'
 5         ext.logback_version = '1.2.6'
 6 
 7         repositories {
 8             jcenter()
 9             mavenCentral()
10         }
11         dependencies {
12             classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13         }
14     }
15     plugins {
16         id "com.google.cloud.tools.jib" version "1.8.0"
17         id "org.jmailen.kotlinter" version "3.2.0"
18         id "org.jetbrains.kotlin.jvm" version "1.5.31"
19     }
20     apply plugin: 'application' // JVM plugin
21 
22     mainClassName = "io.ktor.server.jetty.DevelopmentEngine"
23 
24     sourceSets {
25         main.kotlin.srcDirs = ['src/main/kotlin']
26         main.resources.srcDirs = ['src/main/resources']
27         test.kotlin.srcDirs = ['src/test/kotlin']
28     }
29 
30     group 'com.leakingcode'
31     version '0.2'
32 
33     repositories {
34         mavenCentral()
35     }
36 
37     dependencies {
38         implementation "io.ktor:ktor-server-core:$ktor_version"
39         implementation "io.ktor:ktor-server-jetty:$ktor_version"
40         implementation "ch.qos.logback:logback-classic:$logback_version"
41 
42         testImplementation "org.jetbrains.kotlin:kotlin-test"
43         testImplementation "org.jetbrains.kotlin:kotlin-test-junit"
44         testImplementation "io.ktor:ktor-server-test-host:$ktor_version"
45     }
46 
47     compileKotlin {
48         kotlinOptions.jvmTarget = "11"
49     }
50     compileTestKotlin {
51         kotlinOptions.jvmTarget = "11"
52     }
53 
54     // base jib is targeting distroless jdk 11 gcr.io/distroless/java:11
55     targetCompatibility = 11
56 
57     jib {
58         container {
59             mainClass = mainClassName
60         }

Cloud Build takes a configuration file in Yaml that describes how is working CI/CD (Continuous integration and continuous delivery) process. First step checks application, it runs unit test and lint. Second step uploads the unit test results to a static web server. In the third step, jib creates a docker image which will be deployed to Gcloud Run in last step.

 1 steps:
 2       - name: 'openjdk:11'
 3         id: Check
 4         args: ['sh', 'check.sh']
 5         env:
 6           - 'REPO_NAME=$REPO_NAME'
 7           - 'PROJECT_ID=$PROJECT_ID'
 8           - 'COMMIT_SHA=$COMMIT_SHA'
 9           - 'BRANCH_NAME=$BRANCH_NAME'
10           - 'BUCKET_NAME=$_BUCKET_NAME'
11     
12       - name: 'gcr.io/cloud-builders/gsutil'
13         id: Store_unit_test_results
14         args: ['-q', 'cp', '-r', '/workspace/build/reports/tests/test/',
15         'gs://$_BUCKET_NAME/$BRANCH_NAME/$COMMIT_SHA']
16         waitFor: ['Check']
17     
18       - name: 'openjdk:11'
19         id: Jib
20         args: ['./gradlew', 'check', 'jib', '--image', 'gcr.io/$PROJECT_ID/$BRANCH_NAME:$SHORT_SHA']
21     
22       - name: 'gcr.io/cloud-builders/gcloud'
23         id: Deploy
24         args: ['beta', 'run', 'deploy', '$_SERVICE_NAME',
25         '--platform=managed', '--region=europe-west1',
26         '--allow-unauthenticated', '--image=gcr.io/$PROJECT_ID/$BRANCH_NAME:$SHORT_SHA']
27     
28     artifacts:
29       objects:
30         location: 'gs://$_BUCKET_NAME/$BRANCH_NAME/$COMMIT_SHA'
31         paths: ["'/workspace/output.txt'"]

In order to start this process, Gcloud Build needs to have a trigger. For this demo, I have set up a trigger that executes for every push to master in repo. It is possible to define triggers with regex expressions for git branches and tags.

Gcloud Run is very simple to manage, once that a build completes successfully, a docker image is deployed into a defined service ( defined environment variable $_SERVICE_NAME). For every success build, new version service is created and all traffic from previous version is migrated to newest. Gcloud Run provides some metrics out of the box which can help to define the cloud run instance capacity, also it is possible to map custom domains to the entry point.

Conclusion

This is an easy approach for software developers who do not want to be concerned about security, deployments and scalability. Just with few lines of code is possible to set up a decent infrastructure at Google Cloud, which is ready to deploy for every single commit into a git repository.

Tags: