Use Clojure with Docker and Kubernetes!

Cursive REPL output window. Just listing the buckets using my repl scratch file.

Introduction

At my new company Metosin I have been implementing AWS infrastructure using Pulumi and implementing applications using Clojure. We are deploying applications into a Kubernetes cluster and to verify that our applications run with the infrastructure in a dockerized environment as they should we are running tests locally using Drone pipeline (see my previous blog post about it: Using Drone CI ) which spins the required infrastructure using docker containers (Postgres and Minio) and the actual application as another docker container. This week I was wondering that one application behaved a bit differently running tests in my local workstation hitting containers using localhost and running tests inside a docker container. I was discussing this with one Metosin colleague and he told me how to add an nrepl server inside the application so that I can connect to the application running in a docker container and experiment with the running application there using my IntelliJ IDEA / Cursive REPL. Because this trick was pretty neat I thought that I write a short blog post about it. (By the way, I haven’t regretted a second joining Metosin — one of the best places to be if you want to learn Clojure with the best clojurians on the planet, see more: My First Weeks at Metosin!)

Setting Up Nrepl Server Inside a Clojure Application

Our Clojure applications use Integrant as a state management library and we have our configuration in an Aero configuration file. Setting up an nrepl server inside the application is pretty straightforward using these libraries. First the configuration file:

So, in prod don’t use nrepl, in local profile when running application locally use localhost and when running application inside a docker container use 0.0.0.0 address. Nrepl server listens to 3177 port.

Then in your Integrant configuration provide functions so that Integrant knows what to do when you want to init, halt, suspend and resume your state:

So, in init we start the nrepl server and in halt, we stop it. In suspend we store the nrepl instance and in resume we use the old nrepl instance - i.e. when you keep hitting that Integrant reset hot key (mine is Alt-J) to reset your state you don’t want to stop the nrepl server.

Docker Configuration

You can use e.g. Docker Compose when providing the docker configuration for your application. Example follows:

The important trick here is to port forward the nrepl port 3177 from docker container to your host (your workstation) so that Cursive REPL can connect to it. (NOTE: This is just a test image configuration, I use another Dockerfile for building the real application image running in Kubernetes, of course - therefore using dummy passwords.)

If you wonder why we have the MINIO_HOST in this yaml file, let’s see the Aero configuration once more:

… so, in production the endpoint configuration is nil (not needed, the Kube EKS uses AWS IAM to get access to the S3 buckets…), but in other environments, we simulate S3 using Minio and thus providing the endpoint to Minio server.

Connecting to the Nrepl Server From Your IDE

I use excellent Cursive REPL Clojure IDE as an example, but you can connect to the nrepl server pretty much same way using any Clojure IDE.

Then just click the IntelliJ IDEA “Start configuration” button and Cursive REPL connects immediately into your application’s nrepl port. Then you can do in the application code whatever you do also locally: reset state, compile namespaces, etc. The picture at the beginning of the blog post shows one REPL output from my repl scratch file while checking if I can connect to the Minio server running in another container in the same docker network.

Let’s try another trick here. Add something like this in your project’s deps.edn or in ~/.clojure/deps.edn:

This is the excellent Hashp debugging print tool.

And then in your clojure scratch file send the following form to repl:

Then you can add #p in front of any form you want to have debugging information. Let’s add some printouts and see if we are using either AWS Profile or access key / secret key in that docker environment:

Then send the namespace to repl and reset Integrant state (in my Cursive IDE I have the following hot keys: Alt-N: switch to namespace your cursor is, Alt-M: send namespace to repl, Alt-J: reset Integrant state — takes less than a second to click these three buttons). I see this in my Cursive REPL output windows:

… so, we are using access key / secret key (as you could see in the docker compose configuration file earlier).

How About If I Want to Connect to a Clojure Application inside Kubernetes Pod?

For connecting to a nrepl server running in an application running inside Kubernetes cluster I created a simple Justfile recipe so that my team members can easily connect to the applications:

Just recipe delegates task to the run-nrepl-port-forward.sh which does something like this:

… and when I run the Just recipe:

… and then provide the same configuration in your Clojure IDE as provided earlier, but this time the local port is 6677. You can run both repls at the same time and use repl to examine the differences, e.g.

So, in my docker test environment I’m not using real AWS S3 but Minio which provides exactly the same api interface so that the clojure code actually doesn’t know if we are using real AWS S3 service or a simulated environment (Minio). In test environment I use some bucket name for development purposes (creates a bucket inside Minio), in real life the application gets the bucket name from AWS Parameter Store. The use-s3 is just for the Integrant state to create the bucket in Minio if we are running in a test environment (in real AWS environment the bucket is already there, of course):

Conclusions

If you want to learn an excellent functional language which is ready for the cloud era look no further: Clojure.

The writer is working at Metosin using Clojure in cloud projects. If you are interested to start a Clojure project in Finland or you are interested to get Clojure training in Finland you can contact me by sending email to my Metosin email address or contact me via LinkedIn.

Kari Marttila

The article was first published in https://www.karimarttila.fi/

I’m a Software architect and developer. Currently implementing systems on AWS / GCP / Azure / Docker / Kubernetes using Java, Python, Go and Clojure.