Deploy Microservice with Docker

Docker Containers

"Docker provides an integrated technology suite that enables development and IT operations teams to build, ship, and run distributed applications anywhere."
Docker is an open source project that enables you to package any application in a lightweight, portable container. Docker has the ability to package applications in such a way that they can run anywhere.
Docker is a powerful technology and it's supported with the majority of large public cloud. It is important to know that Docker is not a virtualization platform, it bases its operation on Linux Container.

Docker provides many benefits when used properly:

  • It facilitates the development and packaging of applications in a way that leverages the skills developers already have
  • It allows developers to easily create test environments
  • It simplifies the maintenance operations

Check Docker installation

This article does not explain how to install Docker, so refer to the documentation on the site www.docker.com to properly install the software.
In the example that follows it has been used an installation of Docker on Ubuntu (see
https://docs.docker.com/engine/installation/ubuntulinux/ )


To verify that the installation of Docker is up and running type
$ sudo docker info
To test a simple container type
$ sudo docker run hello-world


The animated GIF service

I will use the example published in the post "Provide a service for creating animated gif" to create and deploy a Microservice API on Docker.
The example code of the API can be downloaded from GitHub
https://github.com/maxzerbini/packagemain/tree/master/html/gif

Get the source code and copy it in the GOPATH and get also its dependencies (gin-gonic ,
freetype ).
The environment is configured in this way:
$ env
HOME=/home/ubuntu
GOPATH=$HOME/gocode
GOROOT=/usr/local/go
The paths that will be seen in the following examples will refer to these folders.

Get the dependences, the API code and build the project
$ go get github.com/gin-gonic/gin
$ go get github.com/golang/freetype
$ go get github.com/maxzerbini/packagemain
$ cd $GOPATH/src/github.com/maxzerbini/packagemain/html/gif$ go build -i -o gif

Adding the Dockerfile

Docker allows all of the dependency issues to be discovered during the development and test cycles. That’s a lot simpler and saves a lot of time.
Add the Dockerfile to the project directory:


Dockerfile
FROM golang:latest
# Copy the local package files to the container’s workspace.
ADD . /go/src/github.com/maxzerbini/packagemain/html/gif
# Install our dependencies
RUN go get github.com/gin-gonic/gin
RUn go get github.com/golang/freetype
# Install api binary globally within container
RUN go install github.com/maxzerbini/packagemain/html/gif
# Add data volume
VOLUME /ttf
# Set binary as entrypoint
ENTRYPOINT /go/bin/gif
# Expose default port (8000)
EXPOSE 8000


The base image is latest version of the official golang, it is used as a base for the container. Then the configuration file asks Docker to copy (ADD) the source code of the application and after RUN some go get to install the dependencies.
This API requires a data volume for reading the fonts file. The Dockerfile mounts a data volume on path /ttf
Finally the ENTRYPOINT is declared specifying the executable that Docker will start, and it's exported to the port on which the application listens.


Update the Animated GIF code

The service developed in the example Animated GIF is fine to be distributed and launched with Docker, but it requires a small change to gain access to the data volume and read the TrueType font.
This is the updated code:

// Generate an animated gif
func GenerateAnimation(text string, fontfile string, out io.Writer) {
    const (
        nframes = 64 // number of animation frames
        delay =8 // delay between frames in 10ms units
    )
    var xsize int = 40 + 30 * len(text)
    var ysize int = 200
    // Read the font data.
    ttfpath := os.Getenv("TTF_PATH")
    fontBytes, err := ioutil.ReadFile(ttfpath+"/"+fontfile)
    if err != nil {
        log.Println(err)
        return
    }
    f, err := freetype.ParseFont(fontBytes)
    if err != nil {
        log.Println(err)
        return
    }
    var palette = make([]color.Color,0)
    // generate palette
    for i := 0; i < nframes; i++ {
        palette = append(palette, color.RGBA{R:0,G:0,B:uint8(4*i), A:255})
    }
    anim := gif.GIF{LoopCount: nframes}
    for i := 0; i < nframes; i++ {
        rect := image.Rect(0, 0, xsize, ysize)
        img := image.NewPaletted(rect, palette)
        for x := 0; x < xsize; x++ {
            for y := 0; y < ysize; y++ {
                img.SetColorIndex(x, y, uint8(i))
            }
        }
        WriteText(text, f, img)
        anim.Delay = append(anim.Delay, delay)
        anim.Image = append(anim.Image, img)
    }
    gif.EncodeAll(out, &anim) // NOTE: ignoring encoding errors
}

The path of the date volume is read by means of an environment variable TTF_PATH .


The Docker-Compose tool

Docker-Compose is a tool for defining and running multi-container Docker applications. This tool uses a Compose file to configure our application’s services. Using a single command, we can create and start all the services from our configuration.
We need only to define the services that make up the application in docker-compose.yml file so they can be run together in an isolated environment.
We can use the usual commands to install or upgrade Docker-Compose
$ sudo -i
$ curl -L https://github.com/docker/compose/releases/download/1.5.1/docker
$ chmod +x /usr/local/bin/docker-compose



Defining the docker-compose.yml file

This file can be added in the home directory or in some other directory, but better not to include it in the project files.

docker-compose.yml
api: 
  build: ./gocode/src/github.com/maxzerbini/packagemain/html/gif
  ports:
    - 8000:8000

  volumes:
    - /home/ubuntu/gocode/src/github.com/maxzerbini/packagemain/html/gif:/ttf
 
environment:
    - TTF_PATH=/ttf

The file defines the application api and it specifies how to compile the application, which ports are necessary and which data volume is mounted. It can also define environment variables.

Running and testing

Run this command to create the image
$ sudo docker-compose build
The execution of the container can be started with
$ sudo docker-compose up

We can test the microservice calling the animated.gif endpoint on a browser
http://localhost:8000/animated.gif?name=Massimo


Minimizing the Docker image size

Running the command
$ sudo docker images
we can see that the image list includes our service ubuntu_api
ubuntu_api   latest  f9cad0c4bb85  1 minutes ago   742.8 MB
golang       latest  f2675afabc6a  7 days ago      709.3 MB
ubuntu       latest  91e54dfb1179  12 weeks ago    188.4 MB
hello-world  latest  af340544ed62  3 months ago    960 B
The size of our container may seem surprising but depends on the size of the base image of golang that is over 700 MB.

How can we reduce the size?


Compile the application

To remove the need to use golang as the basis for our image we need to compile the source code. The compilation must take place including all libraries, even those dynamics will be added to the executable statically.
We must return to the project folder and run this command
$ CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o gif
We will produce the executable gif for the Linux platform linking all static libraries and after this we can change the Dockerfile this way:


Dockerfile
FROM alpine:latest
WORKDIR $HOME/gocode/src/github.com/maxzerbini/packagemain/html/gif
ADD gif /
# Add data volume
VOLUME /ttf
# Set binary as entrypoint
CMD ["/gif"]
# Expose default port (8000)
EXPOSE 8000

This image is produced from alpine, a Linux distribution in just 5MB, we could also use scratch (the void container image) to create our image.

Run this command to build the image and start it
$ sudo docker-compose build | sudo docker-compose up
Test the service calling the API endpoint
http://localhost:8000/animated.gif?name=Thin%20API
Now watching the images on Docker we see that the new image has a size considerably smaller:
$ sudo docker images
ubuntu_api  latest   ecd3e5b36615        1 minutes ago   15.5 MB

<none>       <none>    f9cad0c4bb85   15 minutes ago  742.8 MB

golang       latest    f2675afabc6a    7 days ago     709.3 MB
ubuntu       latest    91e54dfb1179   12 weeks ago    188.4 MB
hello-world  latest    af340544ed62    3 months ago   960 B

Conclusions

APIs built into the Go language can easily be installed in Docker containers. Docker Hub  already hosts all basis images. Docker-Compose helped us to simplify the creation of the image and to launch the application.
Finally, in this example we exploited the ability of Go to create a statically linked binary that fully contains all the application and we published a Microservice API in a lightweight Docker container.




Commenti

Post popolari in questo blog

OAuth 2.0 Server & Authorization Middleware for Gin-Gonic

From the database schema to RESTful API with DinGo

OVO Key/Value Storage