Generic role for creating systemd services to manage docker containers.
Example of a Systemd unit for your app "myapp" that links to an already existing container "mysql":
- name: Start WebApp
include_role:
name: mhutter.docker-systemd-service
vars:
container_name: myapp
container_image: myapp:latest
container_links: ["mysql"]
container_volumes:
- "/data/uploads:/data/uploads"
container_ports:
- "3000:3000"
container_hosts:
- "host.docker.internal:host-gateway"
container_env:
MYSQL_ROOT_PASSWORD: "{{ mysql_root_pw }}"
container_labels:
- "traefik.enable=true"This will create:
- A file containing the env vars (either
/etc/sysconfig/myappor/etc/default/myapp). - A systemd unit which starts and stops the container. The unit will be called
<name>_container.serviceto avoid name clashes.
container_name(required) - name of the container
container_image(required) - Docker image the service usescontainer_args- arbitrary list of arguments to thedocker runcommand as a stringcontainer_cmd(default: []) - optional list of commands to the container run command (the part after the image name)container_env- key/value pairs of ENV vars that need to be presentcontainer_volumes(default: []) - List of-vargumentscontainer_host_network(default: false) - Whether the host network should be usedcontainer_ports(default: []) - List of-pargumentscontainer_hosts(default: []) - List of--add-hostargumentscontainer_links(default: []) - List of--linkargumentscontainer_labels(default: []) - List of-largumentscontainer_docker_pull(default: yes) - whether the docker image should be pulledcontainer_docker_pull_force_source(default: yes) - whether the docker image pull should be executed at every time (seedocker_image.force_source)container_cap_add(default []) - List of capabilities to addcontainer_cap_drop(default {}) - List of capabilities to dropcontainer_network(default "") - Network settingscontainer_user(default "") - User settingscontainer_hostname(default "") - Container host name:--hostnameflagcontainer_devices(default []) - List of devices to addcontainer_privileged(default false) - Whether the container should be privilegedcontainer_start_post- Optional command to be run by systemd after the container has started
service_enabled(default: yes) - whether the service should be enabledservice_masked(default: no) - whether the service should be maskedservice_state(default: started) - state the service should be in - set toabsentto remove the service.service_restart(default: yes) - whether the service should be restarted on changesservice_name(default:<container_name>_container) - name of the systemd serviceservice_systemd_options(default: []) - Extra options to include in systemd service fileservice_systemd_unit_options: (default{"After": "docker.service", "PartOf": "docker.service", "Requires": "docker.service"}), key/value defining the content of the[Unit]service section.
This role requires the docker python module. Install it with pip3 install docker or apt install python3-docker (or drop the 3 for python 2.x).
Put this in your requirements.yml:
- role: mhutter.docker-systemd-serviceand run ansible-galaxy install -r requirements.yml.
- When the unit or env file is changed, systemd gets reloaded but existing containers are NOT restarted.
- Make sure to quote values for
container_ports,container_hosts,container_volumesand so on, especially if they contain colons (:). Otherwise YAML will interpret them as hashes/maps and ansible will throw up.
The concept behind this is to define systemd units for every docker container. This has some benefits:
systemdis a well-known interface- all services are controllable via the same tool (
systemctl) - all logs are accessible via the same tool (
journalctl) - dependencies can be defined
- startup behaviour can be defined
- by correctly defining the unit (see below), we can ensure we always have a clean container.
Here is an example myapp_container.service unit file (about what's produced by above code):
[Unit]
# define dependencies
After=docker.service
PartOf=docker.service
Requires=docker.service
[Service]
# Load ENV vars from a file. Note that this env vars will only be
# accessible in the context of the Exec* commands, and not within the
# container itself. To make env-vars accessible within the Container, we use
# the `--env-file` flag for the `docker run` command.
EnvironmentFile=/etc/sysconfig/myapp
# Even though we explicitly run the container using the `--rm` flag, there
# may be leftover containers (eg. after a system-, docker- or app-crash).
# Starting a container with an existing name will always fail.
ExecStartPre=-/usr/bin/docker rm -f myapp
# actually run the container.
# `--name` to identify the container
# `--rm` ensure the container is removed after stopping
# `--env-file` make ENV vars accessible to app
# `--link mysql` link to a container named `mysql`. The DB will then be
# accesible at `mysql:3306`
# `-v` mount `/data/uploads` into the container
# `-p 3000:3000` expose port 3000 on the network
ExecStart=/usr/bin/docker run --name myapp --rm --env-file /etc/sysconfig/myapp --link mysql -v /data/uploads:/data/uploads -p 3000:3000 registry.cust.net/myapp/myapp:latest
# note that there is no `--restart` parameter. This is because restarting
# is taken care of by `systemd`.
# Stop command.
ExecStop=/usr/bin/docker stop myapp
# Ensure log messages are correctly tagged in the system log.
SyslogIdentifier=myapp
# Auto-Restart the container after a crash.
Restart=always
[Install]
# make sure service is started after docker is up
WantedBy=docker.service