Writing the Dockerfile.

Armada services are run as Docker containers. In order to build one with our service, we need to create its Dockerfile. It consists of a list of steps that need to be executed to install and run our service. Let's look at an example:

Dockerfile
FROM dockyard.armada.sh/microservice MAINTAINER Peter Gibbons <[email protected]> RUN apt-get install -y python python-dev python-pip RUN pip install -U web.py ADD . /opt/coffee-counter ADD ./supervisor/coffee-counter.conf /etc/supervisor/conf.d/ EXPOSE 80

Explanation of the above line by line:

-->
FROM dockyard.armada.sh/microservice

FROM must be the first instruction in the Dockerfile. It tells docker which base image to use. On top of this image all other work will be done.
In Armada's case we recommend to base your service on microservice image. Currently it is based on Ubuntu 14.04 and provides some extra features like ssh access, auto-registering in Armada service catalog and few more goodies which we'll cover later on.
If you are armadizing a service which already has it's own Dockerfile and you want to take advantage of it without changing your base image to microservice, this case is covered in guide here.

-->
MAINTAINER Peter Gibbons <[email protected]>

This instruction will stamp your name on generated image. It's a good practice to insert it after FROM.

-->
RUN apt-get install -y python python-dev python-pip RUN pip install -U web.py

Here we provide a list of commands that will setup all necessary packages, libraries etc. that our service relies on. The steps can be as simple as above or a bit more complicated such as downloading files from the web, compiling libraries, modifying configuration files and so on. All those commands will be run as root in the container used to build our image. If you are unsure about the best way to install framework X, library Y or service Z, you can consult Dockerfiles of some existing services. Usually there will already be a service with similar requirements.

-->
ADD . /opt/coffee-counter ADD ./supervisor/coffee-counter.conf /etc/supervisor/conf.d/

Above instructions will "upload" content from our filesystem into the image. First one will put whole service code into the directory /opt/coffee-counter. We use the convention /opt/SERVICE_NAME in all core Armada services and we recommend you to use it as well. Your fellow developers will appreciate not having to search for service code elsewhere.
Second line will put only one file into the directory /etc/supervisor/conf.d/. Here is that file contents:

coffee-counter.conf
[program:coffee-counter] directory=/opt/coffee-counter/src command=python coffee-counter.py 80

If you know supervisord this should be easy to understand. If not, you can probably guess that this will be used to actually run our service inside the container. If you need to run more processes in your service you can add separate similar config files and drop them into /etc/supervisor/conf.d/ directory. Although you should always consider whether splitting the big service into smaller services isn't more appropriate solution.

Why can't we just insert RUN cd /opt/coffee-counter/src && python coffee-counter.py 80 in the Dockerfile? That's because in the process of building our image, docker executes every RUN command, but in the end it saves only the resulting filesystem state in the image (plus some extra metadata like MAINTAINER). Information about running processes is lost.

When docker runs previously saved image it recreates the filesystem and runs only single command. That command is specified in Dockerfile with the instruction CMD. To allow more separate processes to run inside single container we can use some process manager like supervisord that spawns the other ones.

You can read more about it in an article: Using Supervisor with Docker.

Unless you want to fiddle with "Armada way" of running services, you don't need to specify CMD instruction in your Dockerfile since it is already defined in the microservice image.

-->
EXPOSE 80

Here we declare what TCP/UDP ports from our service container should be exposed to the outside world. We should insert every port that we would like to access after the service is run. As a convention for REST services we recommend running it on port 80. That way it will also be automatically registered in Armada service catalog. If you want to use another port/ports look here.

Exposing port 80 from the container doesn't mean that it will be mapped to that same port number on the host machine. This is decided at the moment of running the image. By default every exposed port will be assigned more or less random free port on the host. That way, even if internally many services bind to the port 80 they will be viewed as different ports to the outside world.



Additional info about Dockerfiles.

  • FROM instruction must be the first in the Dockerfile. But you can freely mix other ones as is convenient for you.

  • Image microservice has supervisord configured such that everything inside the container is run as root.
    We find it quite convenient convention, but if you need more security inside your container you are free to run your service as less privileged user, e.g. by using sudo. That can make sense if you are running more than one service inside one container and want to prevent compromising one of them to escalate on the others.

  • Armada provides more than just the microservice base image. There are base images suitable for use with various languages and frameworks, such as microservice_python, microservice_node, microservice_php etc. Each of them is based on the microservice so they share all the same good stuff but with something extra. In fact if we would use:
    FROM dockyard.armada.sh/microservice_python
    in the Dockerfile, we could skip the lines:
    RUN apt-get install -y python python-dev python-pip RUN pip install -U web.py
    as they are already included in the microservice_python image.
    Indeed in the final version we should do it like that. Using common base images (such as microservice_python) has more advantages, such as faster image building, faster image uploading to dockyards and taking less space.

  • Every docker image that you build can be used as a base image for another service. It's very easy to build an image (or images) that provide some common functionality required by few of your services. For example it may be an image with some of your proprietary stack preinstalled etc.

  • Some good practices for writing Dockerfiles can be found here.

  • Docker documentation on writing Dockerfiles: https://docs.docker.com/reference/builder/.


Building and running the service.

After writing the Dockerfile we should be ready to build and run our service:

coffee-counter$ armada build coffee-counter
Pulling repository dockyard.armada.sh/microservice 691329d6b51c: Download complete 511136ea3c5a: Download complete ...(skipped)... Removing intermediate container 40ebb74e2419 Successfully built b595776d8041
coffee-counter$ armada run coffee-counter -d local
Running microservice coffee-counter from dockyard: (alias: local) locally... Service is running in container 93697f6e1847 available at addresses: 192.168.3.168:49227 (80/tcp)
coffee-counter$ curl -X POST http://192.168.3.168:49227/drink/Peter/2
Peter's coffee count is now 2.

Looks good!

If your service doesn't respond as expected, make sure that it binds to every network interface. Listening on localhost is not enough. Armada runs containers with bridged networking enabled so docker creates separate interface eth0 inside the container for communication with the host. You can either read its IP address in your service or just go the easy way and bind to every interface by listening on 0.0.0.0.

Now let's see if our service registered in Armada catalog properly:

coffee-counter$ armada list
Name Address ID Status Tags armada 192.168.3.168:49212 a765ce08de9f passing - coffee-counter 192.168.3.168:49227 93697f6e1847 passing -

It has!
The service is on the list, and its status is passing. That's because it is using default health check, that just checks if we are able to connect to the service's specified port. If you need more sophisticated health checks that topic is covered here.

Now that we have our service up and running properly, let's move on to other interesting topics.