Earlier on I've shown you how I run docker containers on NAS using systemd. This time, I'll show you an easy way to configure systemd with Ansible that will get you running containers in no time. (and in reproducible and automated way)
I really like how well systemd works with Docker. It is still not ideal, because Docker kind of escapes the cgroups defined in systemd - something like rkt would probably integrate much better, nonetheless it's still very robust configuration and good improvement even from quite decent upstart configuration I had before.
What we need here is to create service file for each container we'd like to run and make sure that service is set to start at boot time in correct order. (after docker)
Note while this configuration is very simple and even Ansible beginner should be able to follow it, I'm not going to explain how to install ansible or how to create your inventory - this might be very specific to your setup and there are good tutorials out there on that topic.
We know, that there will be multiple containers deployed on a single machine, so to save us some repetition, we are going to create a role. For all we need, we are fine with just creating a directory structure, but if you want to get a quick and nice skeleton for your role, use ansible-galaxy like this:
ansible-galaxy init --offline -p roles/ <role_name>
I'm going to call mine docker-systemd. Once we have that done, we can start by..
Let's take a sample template for my Plex server:
[Unit]
Description=Plex in Docker
After=docker.service
Requires=docker.service
[Service]
TimeoutStartSec=0
ExecStartpre=-/usr/bin/docker stop plex
ExecStartpre=-/usr/bin/docker rm plex
ExecStartpre=-/usr/bin/docker pull my_registry:5000/plex
ExecStart=/usr/bin/docker run --rm -t \
-v /data/media/:/media/ \
-v /data/system/plex/:/config/ \
--net host \
--name plex my_registry:5000/plex
ExecStop=-/usr/bin/docker stop -t 3 plex
ExecStop=-/usr/bin/docker rm plex
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
It's quite straightforward stuff so far. On start we make sure that any previously launched instance is stopped and removed. This should be only needed if something weird happened (like unexpected reboot) so I'm prepending the commands with dash to ignore any failures on these steps, most of the time there will be nothing to stop. As the last pre-start step, we try to fetch the latest image, again ignoring any failures. (we still want our services to start even if repository is unreachable)
The actual start is your usual docker run, but this time we're also
starting it with extra flags: --rm -t
to remove container on stop (I
don't want persistent containers, my persistent data resides on mounted
volumes) and to attach to container tty.
The later gives us two things:
Stop is just some cleanup - again unnecessary under normal circumstances, so we ignore any failures on these.
Systemd is also instructed to restart the service if it fails (after 10 seconds) and it has docker set as dependency, so it will start in proper order during boot.
Now let's make a template out of the service file by replacing the service specific stuff with Ansible variables:
[Unit]
Description={{ container_description }}
After=docker.service
Requires=docker.service
[Service]
TimeoutStartSec=0
Restart=always
ExecStartPre=-/usr/bin/docker stop {{ container_name }}
ExecStartPre=-/usr/bin/docker rm {{ container_name }}
{% if container_pull %}
ExecStartPre=-/usr/bin/docker pull {{ container_image }}
{% endif %}
ExecStart=/usr/bin/docker run --rm -t \
{% for port in container_ports %}
-p {{ port }} \
{% endfor %}
{% for volume in container_volumes %}
-v {{ volume }} \
{% endfor %}
{% for option in container_extra_options %}
{{ option }} \
{% endfor %}
--name {{ container_name }} {{ container_image }} {{ container_cmd }}
ExecStop=-/usr/bin/docker stop -t 3 {{ container_name }}
ExecStop=-/usr/bin/docker rm {{ container_name }}
Restart=always
RestartSec=10s
[Install]
WantedBy=multi-user.target
Quite simple, isn't it? The parts that aren't absolutely trivial are:
Let's save the template as templates/systemd-container.service.j2
in
our role directory.
Let's have a look at the tasks we need to define in Ansible:
---
- name: create service file
template:
dest: "/etc/systemd/system/{{ container_service_name }}"
src: "templates/systemd-container.service.j2"
register: service_config
- name: reload daemon
shell: systemctl daemon-reload
when: service_config.changed
- name: restart service
service:
name: "{{ container_service_name }}"
state: restarted
when: service_config.changed
- name: enable service
service:
name: "{{ container_service_name }}"
state: started
enabled: yes
There are just four steps needed:
That was easy one, wasn't it? Let's save it as tasks/main.yml
.
This is not really necessary, but if we can make our life easier in couple lines of code, why wouldn't we? There are some variables, that will have some common setting for most of the containers, so let's define defaults for those and we don't have to define them for each individual role invocation.
---
container_description: "{{ container_name }} in Docker container"
container_pull: true
container_cmd: ""
container_service_name: "container-{{ container_name }}.service"
container_repository: "{{ container_name }}"
container_image: "{{ docker_registry}}{{ '/' if docker_registry else '' }}{{ container_repository }}"
container_ports: []
container_volumes: []
container_extra_options: []
Save that one in vars/main.yml
and we're done with the role. Now while
it isn't the best role I've seen in my life, it's definitely a role we
can use. All we need now is..
---
- hosts: "myhost"
roles:
- role: "docker-systemd"
container_name: "plex"
container_extra_options:
- "--net host"
container_volumes:
- "/data/media/:/media/"
- "/data/system/plex/:/config/"
become: true
Save this to some playbook.yaml
file and that's it. Assuming you have
docker_registry
set properly (mot likely in group_vars?) and the
image exists there, you're done. For multiple containers just invoke the
role multiple times with different settings.
Now we're just one ansible-playbook run away from deploying the configuration and running the container. We can even automate that with Drone for example, but I'll leave that one for some later article. Now that we have Plex running it's movie time.
See you on the next one.
This article is part of Linux category. Last 16 articles in the category:
You can also see all articles in Linux category or subscribe to the RSS feed for this category.