import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.jetbrains.kotlin.jvm").version("1.3.50")
id("com.google.cloud.tools.jib") version "1.7.0"
application
}
repositories {
jcenter()
}
dependencies { (1)
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("io.vertx:vertx-web:3.8.3")
implementation("org.asciidoctor:asciidoctorj:1.5.6")
}
application { (2)
mainClassName = "io.vertx.howtos.knative.serving.AppKt"
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
jib { (3)
to {
image = "dev.local/jponge/knative-vertx-asciidoctor"
tags = setOf("v1")
}
container {
mainClass = application.mainClassName
ports = listOf("8080")
}
}
tasks.wrapper {
gradleVersion = "5.6.3"
}
Deploying a Knative service powered by Vert.x
This how-to shows you how to deploy a Vert.x-based Knative service.
What you will build
-
You will write a service that accepts Asciidoc text, and produces a HTML rendering with Asciidoctor.
-
This service will be written in Kotlin.
-
A container image with the function will be created with Jib.
-
This service will be deployed with Knative/serving.
What you need
-
A text editor or IDE
-
Java 8 higher
-
Maven or Gradle
-
Docker
-
A working Kubernetes cluster with Knative/serving.
What is a Knative service and why is Vert.x a good match?
Knative starts container images (3 by default) to respond to requests, and scales down to 0 after some delay without traffic. A "function" served by Knative/serving is simply a HTTP service written in any language, and packaged as a container image.
Vert.x is ideal for writing Knative services on the JVM, because:
-
Vert.x applications start very fast since there is no magic happening at run time,
-
GraalVM can be used compilation to further reduce the startup and memory footprint (out of the scope of this how-to),
-
Vert.x applications are resource-efficient and remain responsive even under heavy load,
-
Vert.x offers a large ecosystem of reactive clients to other middlewares (databases, messaging, etc),
-
a main method / function suffices to bootstrap a Vert.x application!
Create a project
The code of this project contains Maven and Gradle build files that are functionally equivalent.
With Gradle
Here is the content of the build.gradle.kts
file that you should be using:
1 | We need Kotlin, vertx-web and asciidoctorj . |
2 | The Gradle application plugin allows us to run the application locally with the run command. |
3 | Jib configuration to produce an image. |
With Maven
Here is the content of the pom.xml
file that you should be using:
<?xml version="1.0" encoding="UTF-8"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>io.vertx.howtos</groupId>
<artifactId>knative-serving-howto</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<kotlin.version>1.3.50</kotlin.version>
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
<main.class>io.vertx.howtos.knative.serving.AppKt</main.class>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies> (1)
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
<version>3.8.3</version>
</dependency>
<dependency>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctorj</artifactId>
<version>1.5.6</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId> (2)
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>${main.class}</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>com.google.cloud.tools</groupId> (3)
<artifactId>jib-maven-plugin</artifactId>
<version>1.7.0</version>
<configuration>
<to>
<image>dev.local/jponge/knative-vertx-asciidoctor</image>
<tags>
<tag>v1</tag>
</tags>
</to>
<container>
<mainClass>${main.class}</mainClass>
<ports>
<port>8080</port>
</ports>
</container>
</configuration>
</plugin>
</plugins>
</build>
</project>
1 | We need Kotlin, vertx-web and asciidoctorj . |
2 | Allows running with mvn exec:java . |
3 | Jib configuration to produce an image. |
Writing the service
The service exposes a HTTP server. Asciidoc is passed to the function through HTTP POST requests. For each request, Asciidoctor is used to perform the conversion to HTML:
package io.vertx.howtos.knative.serving
import io.vertx.core.Vertx
import io.vertx.ext.web.Router
import io.vertx.ext.web.handler.BodyHandler
import org.asciidoctor.Asciidoctor
import org.asciidoctor.OptionsBuilder
import org.asciidoctor.SafeMode
fun main() {
val vertx = Vertx.vertx() (1)
val asciidoctor = Asciidoctor.Factory.create() (2)
val options = OptionsBuilder.options()
.safe(SafeMode.SAFE)
.backend("html5")
.headerFooter(true)
val router = Router.router(vertx)
router.route().handler(BodyHandler.create()) (3)
router.post().handler { ctx -> (4)
ctx.response()
.putHeader("Content-Type", "text/html")
.end(asciidoctor.render(ctx.bodyAsString, options))
}
vertx.createHttpServer()
.requestHandler(router)
.listen(8080)
}
1 | We create a Vert.x context. |
2 | We configure a Asciidoctor render. |
3 | We install a HTTP request body handler, so we can just process the whole body rather than manually assemble buffers. |
4 | For each request, we render the HTML from the Asciidoc. |
We could have written the Vert.x code as a verticle, but doing so in the main
function reduces the boilerplate code since we would only be deploying a single verticle here anyway.
Running the function locally
We can easily test that the service works:
-
from your IDE, run the
main
function, or -
with Gradle:
./gradlew run
(Linux, macOS) orgradle run
(Windows). -
with Maven:
mvn compile exec:java
.
You can then upload the content of a Asciidoc file, like the README.adoc
file at the root of this repository. With HTTPie, you would run a command similar to:
$ http POST :8080/ @README.adoc
Preparing your cluster
You should go to the Knative documentation for installation instructions.
The commands in this how-to assume that you have installed Knative with Minikube.
Building your container image
The Jib plugin in your Gradle or Maven build will automatically assemble a container image with the correct entry point to run the application, and port 8080 being exposed. The container image then has to be pushed to your favorite repository.
If you are using Minikube, you can directly build and tag the container image:
$ eval $(minikube docker-env) $ ./gradlew jibDockerBuild # or mvn package jib:dockerBuild
Docker should then list the image:
$ docker image ls REPOSITORY TAG IMAGE ID (...) dev.local/jponge/knative-vertx-asciidoctor latest 4ca7aafd590c dev.local/jponge/knative-vertx-asciidoctor v1 4ca7aafd590c (...)
Describing a service for Knative
Here is the descriptor in file service.yaml
for exposing our service with Knative/serving:
apiVersion: serving.knative.dev/v1alpha1
kind: Service
metadata:
name: knative-vertx-asciidoctor
namespace: default
spec:
template:
spec:
containers:
- image: dev.local/jponge/knative-vertx-asciidoctor:v1
We can then apply the configuration and check that the service is available:
$ kubectl apply -f service.yaml service.serving.knative.dev/knative-vertx-asciidoctor created $ kubectl get ksvc NAME DOMAIN LATESTCREATED LATESTREADY READY REASON knative-vertx-asciidoctor knative-vertx-asciidoctor.default.example.com knative-vertx-asciidoctor-jc4sw knative-vertx-asciidoctor-pztr6 Unknown
Testing the service exposed by Knative
In our case the service is exposed under the knative-vertx-asciidoctor.default.example.com
domain. It is available via the istio-ingressgateway
service (check all available services with kubectl get services --all-namespaces
).
The Knative documentation has instructions to figure out what IP, ports and host names you should use.
With Minikube, making a request to the function is similar to:
$ http POST $(minikube ip):31380 'Host:knative-vertx-asciidoctor.default.example.com' @README.adoc
You should see HTML in the response. You should also see pods for your service:
$ kubectl get pods NAME READY STATUS RESTARTS AGE knative-vertx-asciidoctor-mlhwq-deployment-5cc999bdb7-jx2ff 3/3 Running 0 2m5s
After a while, you can check that the Knative auto-scaler has removed all pods:
$ kubectl get pods No resources found.
Issue a new request, and see that new pods have been created again.