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
-->
FROM dockyard.armada.sh/microservice
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]>
-->
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/
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
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.
-->
EXPOSE 80
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
RUN apt-get install -y python python-dev python-pip
RUN pip install -U web.py
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.
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.
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 -
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.