For a while now, I’ve used bash scripts called by systemd unit files to control containers I want running at all times. And while it works well, I decided to look into Ansible for a more repeatable and organized approach that would also hopefully have the benefit of easy container host migrations should the need arise. Having spent a couple days re-tooling, I thought I’d share a quick example using Hauk - a super cool self-hosted location sharing app.
Pre-Ansible Setup
Using Hauk as an example..
1. Create bash scripts for starting and stopping the container
START:
#!/bin/bash
#
/usr/bin/podman run -d -ti --name hauk --hostname hauk.cthudson.com \
-v /containers/hauk:/etc/hauk:z \
-l traefik.enable="true" \
-l traefik.http.routers.hauk.rule=Host\(\`hauk.cthudson.com\`\) \
-l traefik.http.middlewares.hauk-https-redirect.redirectscheme.scheme="https" \
-l traefik.http.routers.hauk.middlewares="hauk-https-redirect" \
-l traefik.http.routers.hauk-secure.entrypoints="websecure" \
-l traefik.http.routers.hauk-secure.rule=Host\(\`hauk.cthudson.com\`\) \
-l traefik.http.routers.hauk-secure.tls="true" \
-l traefik.http.routers.hauk-secure.tls.certresolver=le \
-l traefik.http.services.hauk.loadbalancer.server.port="80" \
-l traefik.http.routers.hauk-admin.rule='(Host(`hauk.cthudson.com`) && PathPrefix(`/index.html`))' \
-l traefik.http.routers.hauk-admin.entrypoints=websecure \
-l traefik.http.middlewares.hauk-admin-ipwhitelist.ipwhitelist.sourcerange='127.0.0.1/32, 192.168.5.0/24, 10.8.0.0/24' \
-l traefik.http.routers.hauk-admin.middlewares=hauk-admin-ipwhitelist \
docker.io/bilde2910/hauk
STOP:
#!/bin/bash
/usr/bin/podman stop hauk; /usr/bin/podman rm hauk
2. Create a systemd unit file to control and automatically run the container
[Unit]
Description=Hauk Podman Container
After=network.target containers.mount
ExecStartPre=-/bin/bash /usr/local/sbin/stop_hauk.sh
[Service]
Type=simple
TimeoutStartSec=5m
ExecStart=/bin/bash /usr/local/sbin/start_hauk.sh
RemainAfterExit=true
ExecStop=-/bin/bash /usr/local/sbin/stop_hauk.sh
ExecReload=-/bin/bash /usr/local/sbin/stop_hauk.sh
ExecReload=-/bin/bash /usr/local/sbin/start_hauk.sh
Restart=always
RestartSec=60
[Install]
WantedBy=multi-user.target
While this is simple enough, it’s a bit painful if the host dies and I need to head over to my backups and place everything back in the right place on a new host, etc, etc. So now let’s see what Ansible brings to the table.
With Ansible Setup
Continuing with the Hauk example..
1. Create Ansible playbook (hauk.yml in my case)
Change the hosts
value to your host or a variable that you can specify during playbook execution.
---
- hosts: localhost
tasks:
- name: Create hauk container
containers.podman.podman_container:
name: hauk
hostname: hauk.cthudson.com
image: docker.io/bilde2910/hauk
rm: true
state: started
volume:
- "/containers/hauk:/etc/hauk:z"
label:
{
'traefik.enable': 'true',
'traefik.http.routers.hauk.rule': 'Host(`hauk.cthudson.com`)',
'traefik.http.middlewares.hauk-https-redirect.redirectscheme.scheme': 'https',
'traefik.http.routers.hauk.middlewares': 'hauk-https-redirect',
'traefik.http.routers.hauk-secure.entrypoints': 'websecure',
'traefik.http.routers.hauk-secure.rule': 'Host(`hauk.cthudson.com`)',
'traefik.http.routers.hauk-secure.tls': 'true',
'traefik.http.routers.hauk-secure.tls.certresolver': 'le',
'traefik.http.services.hauk.loadbalancer.server.port': '80',
'traefik.http.routers.hauk-admin.rule': '(Host(`hauk.cthudson.com`) && PathPrefix(`/index.html`))',
'traefik.http.routers.hauk-admin.entrypoints': 'websecure',
'traefik.http.middlewares.hauk-admin-ipwhitelist.ipwhitelist.sourcerange': '127.0.0.1/32, 192.168.5.0/24, 10.8.0.0/24',
'traefik.http.routers.hauk-admin.middlewares': 'hauk-admin-ipwhitelist'
}
- name: Create systemd unit file for Hauk container
containers.podman.podman_generate_systemd:
name: hauk
new: true
requires: containers.mount
restart_policy: always
no_header: true
dest: /etc/systemd/system
- name: Ensure hauk container is started and enabled
ansible.builtin.systemd:
name: container-hauk
daemon_reload: true
state: started
enabled: true
And that’s really it. Put these playbooks in source control and you can deploy at will just about anywhere. As for what’s happening here, the name of each task gives a good hint:
1. Create hauk container
This task is using the podman_container
module from the containers.podman
collection to create the container itself. You’ll notice the similarities to the bash script I noted earlier, which made the shift quite painless. There aren’t many callouts aside from maybe familiarizing yourself with lists and dictionaries and looking at the docs to understand what each parameter expects. You can see here that labels expect dictionaries (as do container environmnet variables) and volumes expect lists.
2. Create systemd unit file for Hauk container
This task is using the podman_generate_systemd
module from the same containers.podman
collection and generates a systemd unit file within /etc/systemd/system
. In this case, it would be named container-hauk.service
(NOTE: If container_prefix
is not specified, it prepends container-
to your specified unit filename
- similarly, if creating a pod it would prepend pod-
). As for the unit file, here it is:
# container-hauk.service
[Unit]
Description=Podman container-hauk.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
RequiresMountsFor=%t/containers
# User-defined dependencies
Requires=containers.mount
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=always
TimeoutStopSec=70
ExecStart=/usr/bin/podman container run \
--cidfile=%t/%n.ctr-id \
--cgroups=no-conmon \
--rm \
--sdnotify=conmon \
--replace \
--name hauk \
--hostname hauk.cthudson.com \
--volume /containers/hauk:/etc/hauk:z \
--label traefik.enable=true \
--label traefik.http.routers.hauk.rule=Host(`hauk.cthudson.com`) \
--label traefik.http.middlewares.hauk-https-redirect.redirectscheme.scheme=https \
--label traefik.http.routers.hauk.middlewares=hauk-https-redirect \
--label traefik.http.routers.hauk-secure.entrypoints=websecure \
--label traefik.http.routers.hauk-secure.rule=Host(`hauk.cthudson.com`) \
--label traefik.http.routers.hauk-secure.tls=true \
--label traefik.http.routers.hauk-secure.tls.certresolver=le \
--label traefik.http.services.hauk.loadbalancer.server.port=80 \
--label "traefik.http.routers.hauk-admin.rule=(Host(`hauk.cthudson.com`) && PathPrefix(`/index.html`))" \
--label traefik.http.routers.hauk-admin.entrypoints=websecure \
--label "traefik.http.middlewares.hauk-admin-ipwhitelist.ipwhitelist.sourcerange=127.0.0.1/32, 192.168.5.0/24, 10.8.0.0/24" \
--label traefik.http.routers.hauk-admin.middlewares=hauk-admin-ipwhitelist \
--detach=True docker.io/bilde2910/hauk
ExecStop=/usr/bin/podman stop \
--ignore -t 10 \
--cidfile=%t/%n.ctr-id
ExecStopPost=/usr/bin/podman rm \
-f \
--ignore -t 10 \
--cidfile=%t/%n.ctr-id
Type=notify
NotifyAccess=all
[Install]
WantedBy=default.target
Pretty slick, eh?
3. Ensure hauk container is started and enabled
This final task is calling on Ansible’s built-in systemd
module to start and enable (ie: start on boot) the new container-hauk
service.
Running the playbook
Once you have ansible installed on whatever you are running (in my case, this is Fedora and I ran dnf install ansible
), you just need to make sure you also install the podman collection via ansible-galaxy. At the time of this writing, you can do so by running: ansible-galaxy collection install containers.podman
.
After that, you can fire off the playbook via:
# ansible-playbook hauk.yml
And you should see something similar to:
# ansible-playbook hauk.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [localhost] ****************************************************************************************************************************************************************************************************
TASK [Gathering Facts] **********************************************************************************************************************************************************************************************
ok: [localhost]
TASK [Create hauk container] ****************************************************************************************************************************************************************************************
ok: [localhost]
TASK [Create systemd unit file for Hauk container] ******************************************************************************************************************************************************************
ok: [localhost]
TASK [Ensure hauk container is started and enabled] *****************************************************************************************************************************************************************
ok: [localhost]
PLAY RECAP **********************************************************************************************************************************************************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
With my container already running, everything was OK and nothing changed. But it is that simple!
Moving Forward
As I get everything migrated over to these playbooks, I will likely start refining things by bringing in an inventory and variabalizing anything that makes sense. But this was a cool 24hr exploration into how Ansible can make my container deployments a little more streamlined and consistent. I hope others find it interesting as well. Cheers!