Hannu Hartikainen

By Hannu Hartikainen

Developer & Hacker

hannu.hartikainen@vincit.fi

LinkedIn

Cloud Run is a managed serverless platform for containerized applications in Google Cloud. Very similar to AWS Lambda, that is.

Cloud Run has documentation for containerizing and deploying Java applications with Jib. That makes building and deploying both very fast and very easy, while not requiring deep knowledge of Docker internals and best practices. Since Clojure is a JVM language, Jib is also suitable for it.

Minimal Leiningen project

Leiningen is based on Maven, and Jib has a first-class Maven plugin. That makes it the easiest choice of Clojure build tools for running Jib. Note: replace my-gcp-project with an actual GCP project you control.

;; project.clj
(defproject my-app "0.1.0-SNAPSHOT"
  :main ^:skip-aot my-app.main

  :pom-plugins
  [[com.google.cloud.tools/jib-maven-plugin "2.1.0"
    (:configuration
      [:from [:image "gcr.io/distroless/java:11"]]
      [:to [:image "gcr.io/my-gcp-project/my-app"]]
      [:container
       [:mainClass "my-app.main"]
       [:creationTime "USE_CURRENT_TIMESTAMP"]])]]

  :dependencies
  [[org.clojure/clojure "1.10.1"]
   [ring/ring-core "1.8.1"]
   [ring/ring-jetty-adapter "1.8.1"]])

Simple application

Cloud Run requires that applications run an http server on the port PORT (from environment variable). The easiest way to do that is with ring.adapter.jetty.

;; src/my_app/main.clj
(ns my-app.main
  (:gen-class)
  (:require [ring.adapter.jetty :refer [run-jetty]]))

(defn- handler
  [_req]
  {:status 200
   :body   "Hello world!"})

(defn serve
  [port]
  (run-jetty
    handler
    {:host  "0.0.0.0"
     :port  port
     ;; run-jetty takes over the thread by default, which is bad in a REPL
     :join? false}))

(defn -main
  [& _args]
  (serve (Long/parseLong (System/getenv "PORT"))))

Running locally

Locally, the app can be run with PORT=3000 lein run. Or for a REPL, run lein repl and start the server with (serve 3000). The server answers as expected:

$ http localhost:3000
HTTP/1.1 200 OK
Content-Length: 12
Date: Fri, 22 May 2020 09:15:17 GMT
Server: Jetty(9.4.28.v20200408)

Hello world!

Deploying

Building and deploying happens in three steps. You will need to have the gcloud CLI tools and be logged in.

# 1. Compile the code and generate a pom.xml
lein do compile :all, pom

# 2. Containerize with Jib and upload the container to registry
mvn compile jib:build

# 3. Deploy from registry to Cloud Run
gcloud config set project my-gcp-project  # or set CLOUDSDK_CORE_PROJECT
gcloud run deploy --image gcr.io/my-gcp-project/my-app

Voilà! The gcloud run deploy command will inform you of the app's endpoint and the endpoint answers Hello world!

Alternatives

There are obviously many other possible ways for creating a Docker container that runs Clojure and exposes an HTTP server. The main upside of Jib is fast builds and uploads with best practices and maintenance (I believe, but I've been wrong before). DIY Dockerfile based approaches are an alternative, but not a very inviting one.

Where a custom container might shine, though, is with GraalVM. Building the application as a single-binary container might lower both startup time and memory consumption. But at the moment, Jib seems a safer choice for production use.

Another point of contention might be the use of Leiningen. In this example we technically use lein and mvn in separate passes, so you could get similar results with another build tool and a hand-written pom.xml. This is just a bit quicker to set up. A realistically better option might be to use Jib without Maven, but there's no mature tooling for that. Once jib-cli becomes stable, it's probably the better choice.

Original text published here.

Did you enjoy this article?

Give us a clap!

Comments

  • Ville Vehviläinen

    A while back I wrote a Leiningen plugin for Jib, you might want to take a look (and perhaps contribute?). It avoids the Maven dependency and allows building in a single step.

  • Ville Vehviläinen

    Sorry, forgot to include the Github link: https://github.com/vehvis/lein-jib-build

  • Hannu Hartikainen

    Hi Ville! I did check some alternatives out in May, including lein-jib-build, jib-cli and juxt/pack.alpha. I consider jib-cli the most promising but for production use I preferred only using Jib the recommended way (ie. the official maven plugin).

Leave a Reply

Your email address will not be published. Required fields are marked *