Debugging & Live Reloading Go app within Docker Container
By: Hanan Rok
Tags: Containers, Golang, Docker, Debugging, Microservices

Micro-Service Definition

Photo by Vinicius Amano on Unsplash

Repeater Micro-Service

Let’s create a typical Go micro-service — a repeater.

Our example is a micro-service that reads bytes from a TCP connection and writes this input as output to the same connection.

I wrote a simple Go file doing exactly that

Docker Image Creation

A correspond Dockerfile to main.go above would look something like this

FROM golang:1.12.7-alpine3.10 — the base image we inherit from

ENTRYPOINT [“/build/main”] — Docker image “entry point” is the main program of the container, meaning it’s run on startup when the container is created and kill the container with it when it exit. (TODO better understand how to write it)

Go to the directory that we save the Dockerfile in and run docker build -t micro-service-example . to build our micro-service-example image.

It should print something like this:

$ cd /where/you/saved/the/Dockerfile

$ docker build -t micro-service-example .

Sending build context to Docker daemon 4.608kB

Step 1/6 : FROM golang:1.12.7-alpine3.10

---> 6b21b4c6e7a3

Step 2/6 : RUN mkdir /build/

---> Running in 2a47518560a9

Removing intermediate container 2a47518560a9

---> e29c95001049

Step 3/6 : WORKDIR /build/

---> Running in 92dbd35e35a7

Removing intermediate container 92dbd35e35a7

---> da50ce6fd313

Step 4/6 : COPY main.go /build

---> d3d2fa001914

Step 5/6 : RUN go build /server main.go

---> Running in 5656581424ae

Removing intermediate container 5656581424ae

---> db89e18aaf19

Step 6/6 : ENTRYPOINT ["/server"]

---> Running in 949fa11588e5

Removing intermediate container 949fa11588e5

---> 24c6804ff56c

Successfully built 24c6804ff56c

Successfully tagged micro-service-example:latest

Running the New Service

Fun! Now we are able to run the service

$ docker run -p8080:8080 micro-service-example

2019/09/12 20:25:48 Starting up repeater

-p8080:8080 means that we expose port 8080 . All connections to port 8080 on localhost are forwarded to port 8080 in the container

Yes! seems like our service is up and running. Let’s test it

$ nc localhost 8080

Here is a message

Here is a message

Hey you, how are you?

Hey you, how are you?

What?

What?

Working as expected 👏🏻.

Changing the Application

Now, let’s say we want to change our service, we’ll need to follow these steps:

Change the code according to the new requirement and hope that it’s working as expected

re-build the Docker image

run a new container from the new image

run the nc test.

I don’t like it. Let’s do live reloading instead :)

(I found out that “live reloading” called also “auto reloading”, it means the same thing)

Live Reloading

So we want to automate the process above whenever a Go file is changed. Luckily, Linux has a very cool feature called “iNotify” we can use to monitor files changes.

iNotify

From iNotify man page:

The inotify API provides a mechanism for monitoring filesystem

events. Inotify can be used to monitor individual files, or to

monitor directories. When a directory is monitored, inotify will

return events for the directory itself, and for files inside the

directory.

We will use inotify-tools , Linux package that uses iNotify API, to get events on the application Go files.

So I wrote a script that builds our service and runs it whenever Go file changes:

we use inotifywait command for getting modify events on all files in directory and subdirectories of /build ( -e used to specify an event type, -r for recursively watch all files in the directory and subdirectories -m to specify the file that we monitor in our case /build dir) We check that the file changed is a Go file re-build the application Running the application. Please notice that we run the app in the background so we can still monitor application files

If you try to run this piece of code you’ll have 2 issues

if we’ll change any file the new server couldn’t run because the old one still listening on port 8080 . So we have to kill him. There is a race condition and syncing issues when the building process takes more time than the file changing. So we have to use a lock mechanism to lock before we start building and running the application and unlock after.

So let’s fix those issues:

Now, all we need to do is build the image like we did before and run the container with mounting volume to the outside directory.

$ docker build -t micro-service-example .

... $ docker run -v $PWD:/build -p8080:8080 micro-service-example

Running server

2019/09/13 08:01:16 Starting up repeater

Setting up watches. Beware: since -r was given, this may take a while!

Watches established. File changed: main.go

killing old server PID: 6

Building server

Running server

2019/09/13 08:01:26 Starting up repeater