Building service images
Docker images are built through a definition stored in a Dockerfile. With few exceptions, it takes a similar approach as if we would define a simple script. We will not explore all the options we can use when defining a Dockerfile, but only those used for the go-demo service. Please consult the Dockerfile reference (https://docs.docker.com/engine/reference/builder/) page for more info.
The go-demo Dockerfile is as follows:
FROM alpine:3.4
MAINTAINER Viktor Farcic <viktor@farcic.com>
RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2
EXPOSE 8080
ENV DB db
CMD ["go-demo"]
HEALTHCHECK --interval=10s CMD wget -qO- localhost:8080/demo/hello
COPY go-demo /usr/local/bin/go-demo
RUN chmod +x /usr/local/bin/go-demo
Each of the statements will be built as a separate image. A container is a collection of images stacked one on top of the other.
Every Dockerfile starts with the FROM statement. It defines the base image that should be used. In most cases, my preference is to use alpine Linux. With its size being around 2MB it is probably the smallest distribution we can use. That is aligned with the idea that containers should have only things that are needed and avoid any extra overhead.
MAINTAINER is for informational purposes only.
The RUN statement executes any command set as its argument. I won't explain this one since it is very specific to the service we're building.
The EXPOSE statement defines the port the service will be listening to. It is followed by the definition of the environment variable DB that tells the service the address of the database. The default value is db and, as you'll see soon, it can be changed at runtime. The CMD statement represents the command that will be run when containers start.
The HEALTHCHECK instruction tells Docker how to test a container to check that it is still working. This can detect cases such as a web server that is stuck in an infinite loop and unable to handle new connections, even though the server process is still running. When a container has a healthcheck specified, it has a health status in addition to its normal status. This status is initially starting. Whenever a health check passes, it becomes healthy (from whatever state it was previously in). After a certain number of consecutive failures, it becomes unhealthy.
In our case, the healthcheck will be executed every ten seconds. The command sends a simple request to one of the API endpoints. If the service responds with status 200, the wget command will return 0 and Docker will consider the service healthy. Any other response will be considered as unhealthy and Docker Engine will perform certain actions to fix the situation.
Finally, we copy the go-demo binary from the host to the /usr/local/bin/ directory inside the image and give it executable permissions with the chmod command.
To some, the order of the statements might not look logical. However, there is a good reason behind such declarations and their order. Those that are less likely to change are defined before those that are prone to changes. Since go-demo will be a new binary every time we build the images, it is defined last.
The reasons behind such order lie in the way Docker Engine creates images. It starts from the top-most definition and checks whether it changed since the last time the build was run. If it didn't, it moves to the next statement. As soon as it finds a statement that would produce a new image, it, and all the statements following it are built into Docker images. By placing those that are less likely to change closer to the top, we can reduce the build time, disk usage, and bandwidth.
Now that we understand the Dockerfile behind the go-demo service, we can build the images.
The command is very straightforward and is as follows:
docker build -t go-demo .
As an alternative, we can define build arguments inside a Docker Compose file. The service defined in docker-compose-test-local.yml (https://github.com/vfarcic/go-demo/blob/master/docker-compose-test-local.yml) file is as follows:
app:
build: .
image: go-demo
In both cases, we specified that the current directory should be used for the build process . and that the name of the image is go-demo.
We can run the build through Docker compose with the command that is as follows:
docker-compose \
-f docker-compose-test-local.yml \
build app
We'll use the latter method throughout the rest of the book.
We can confirm that the image was indeed built, by executing the docker images command as follows:
docker images
The output is as follows:
REPOSITORY TAG IMAGE ID CREATED SIZE
go-demo latest 5e90126bebf1 49 seconds ago 23.61 MB
golang 1.6 08a89f0a4ee5 11 hours ago 744.2 MB
alpine latest 4e38e38c8ce0 9 weeks ago 4.799 MB
As you can see, go-demo is one of the images we have inside the server.
Now that the images are built, we can run staging tests that depend on the service and its dependencies to be deployed on a server.