alex3305 schreef op zondag 8 maart 2026 @ 21:42:
@
blackd Altijd interessant. Molecule staat ook nog steeds ergens op een lijstje. Tegelijkertijd heb ik er nog geen noodzaak voor gehad

. Dus ben wel benieuwd hoe jij dat doet

.
Ik gebruik nu alweer een tijdje een Ansible anti-patroon om mijn Docker Compose applicaties uit te rollen. Namelijk een 'common' role. Ik ben er zelf ook geen fan van, maar de alternatieven zijn een git submodule.
Dit heb ik dus ook:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| docker_compose_base
├── defaults
│ └── main.yml
├── tasks
│ ├── build.yml
│ ├── chown_dataset.yml
│ ├── copy.yml
│ ├── create_dataset.yml
│ ├── delete.yml
│ ├── delete_app.yml
│ ├── delete_dataset.yml
│ ├── down.yml
│ └── up.yml
└── vars
└── main.yml |
en die gebruik ik in mijn docker compose roles als volgt (dockge als voorbeeld):
YAML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| ---
- name: Copy tasks
ansible.builtin.import_role:
name: docker_compose_base
tasks_from: copy
vars:
docker_compose_base__stack_base_dir: "{{ dockge__stack_base_dir }}"
# place custom docker compose project setup here
- name: Docker Compose Up
ansible.builtin.import_role:
name: docker_compose_base
tasks_from: up
vars:
docker_compose_base__stack_base_dir: "{{ dockge__stack_base_dir }}" |
De
copy.yml gebruikt ansible.builtin.template om
templates/compose.yml en
templates/.env.j2 te plaatsen in de project directory.
De
up.yml doet een docker compose up in de project directory.
Ik had al verklapt dat ik molecule collections tests had gebruikt. Qua structuur ziet dat er zo uit:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| extensions
└── molecule
├── config.yml
├── default
│ ├── create.yml
│ ├── destroy.yml
│ ├── molecule.yml
│ └── tasks
│ ├── assert-container.yml
│ ├── create-fail.yml
│ └── set-permissions.yml
├── dockge
│ ├── cleanup.yml
│ ├── converge.yml
│ ├── molecule.yml
│ └── verify.yml
[snip]
└── uptime_kuma_v2
[snip] |
Het staat ook uitgelegd in de docs maar eerst wordt het scenario default/create.yml playbook uitgevoerd, daarna per scenario de converge, verify en cleanup, vervolgens scenario default/destroy.yml.
De belangrijkste config hiervoor is
extensions/molecule/config.yml.
Hier is dus het test_sequence scenario expliciet gemaakt. Zelf gebruik ik alleen converge, verify, cleanup.
De shared_state=true zorgt ervoor dat default/create.yml als eerste (voor alle andere scenario's) wordt uitgevoerd, en default/destroy.yml als laatste (na alle andere scenario's) wordt uitgevoerd.
Om dit te laten werken, moest ik wel een galaxy.yml in mijn root project folder aanmaken (via ansible-creator
init collection <namespace>.<collection>). Zie
deze documentatie.
Dan heb ik het default scenario als volgt:
default/create.yml:
YAML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
| ---
- name: Create
hosts: localhost
connection: local
gather_facts: false
tasks:
- name: Create dind-network
community.docker.docker_network:
name: dind-network
state: present
- name: Create dind-daemon container
community.docker.docker_container:
name: my-dind-daemon
image: docker:dind
docker_host: unix:///var/run/docker.sock
state: started
command: ["--data-root", "/var/lib/docker"]
privileged: true
env:
DOCKER_TLS_CERTDIR: ""
networks:
- name: dind-network
aliases:
- docker
- name: Create a container
community.docker.docker_container:
name: "{{ item }}"
image: "{{ hostvars[item].image }}"
docker_host: unix:///var/run/docker.sock
state: started
command: sleep 1d
log_driver: json-file
networks:
- name: dind-network
env:
DOCKER_HOST: "tcp://docker:2375"
register: result
loop: "{{ groups['molecule'] }}"
- name: Fail if container is not running
when: >
item.container.State.ExitCode != 0 or
not item.container.State.Running
ansible.builtin.include_tasks:
file: tasks/create-fail.yml
loop: "{{ result.results }}"
loop_control:
label: "{{ item.container.Name }}"
- name: Prepare
hosts: molecule
gather_facts: true
pre_tasks:
[ hier alle voorwaarden waaraan de test-resource moet voldoen,
in mijn geval een bepaalde uid/gid ]
roles:
[ hier alle voorwaarden van roles (in mijn geval galaxy roles)
die geinstalleerd moeten staan, vóór testuitvoer ]
post_tasks:
[ hier alle voorwaarden die afhankelijk zijn van de roles,
in mijn geval een shared docker netwerk voor traefik ] |
default/destroy.yml:
YAML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| ---
- name: Destroy molecule containers
hosts: molecule
gather_facts: false
tasks:
- name: Stop and remove molecule container
delegate_to: localhost
community.docker.docker_container:
name: "{{ inventory_hostname }}"
state: absent
auto_remove: true
docker_host: unix:///var/run/docker.sock
- name: Stop and remove dind container
delegate_to: localhost
community.docker.docker_container:
name: my-dind-daemon
state: absent
auto_remove: true
docker_host: unix:///var/run/docker.sock
- name: Destroy networks
delegate_to: localhost
community.docker.docker_network:
name: "{{ item }}"
state: absent
docker_host: unix:///var/run/docker.sock
loop:
- [ mijn traefik netwerk ]
- dind-network |
Je ziet dat ik een inventory heb met een molecule test resource, deze wordt in config.yml geconfigureerd en ziet er als volgt uit:
extensions/molecule/inventory.yml:
YAML:
1
2
3
4
5
6
7
8
9
10
| ---
all:
children:
molecule:
hosts:
molecule-debian12:
image: geerlingguy/docker-debian12-ansible
ansible_connection: community.docker.docker
vars:
[ evt noodzakelijke host vars ] |
Tot zover alle 'scaffolding' en je moet een beetje door de DinD heen kijken om het te begrijpen.
Dan duiken we nu in één scenario, van dockge, daar doe ik eerst de converge (role toepassen), daarna verify (controleren op gewenste resultaat), cleanup (opruimen).
Om dat uberhaupt te laten werken, moet er een molecule.yml aanwezig zijn, die leeg is:
extensions/molecule/dockge/molecule.yml:
Molecule zoekt gewoon naar de glob
extensions/molecule/*/molecule.yml
Dan de converge stap, het toepassen van de role op de test resource:
extensions/molecule/dockge/converge.yml:
YAML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| ---
# Purpose: bring the instance to the desired state by running the role under test.
# Molecule calls this playbook with `molecule converge`.
- name: Converge
hosts: molecule
gather_facts: false # Disable if your role does not rely on facts
# Set Docker host to communicate with the Docker daemon inside the DinD container
environment:
DOCKER_HOST: tcp://docker:2375/
tasks:
- name: Apply role under test
ansible.builtin.include_role:
name: <namespace>.<collection>.dockge |
Je ziet dat DOCKER_HOST verwijst naar de DinD container (my-dind-container heeft een alias docker op het dind-network).
Onderwater worden de roles in mijn collection als dependency geinstalleerd bij het uitvoeren van Molecule, vandaar de include_role met volledige galaxy namespace (uit galaxy.yml). Hierdoor zijn ingewikkelde relatieve paden niet nodig.
Dan de verify stap om te controleren of alles goed is gegaan:
extensions/molecule/dockge/verify.yml:
YAML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| ---
# Purpose: assert that the instance really ended up in the expected state.
# Molecule calls this playbook with `molecule verify`.
- name: Verify
hosts: molecule
gather_facts: false # Quicker, if you do not need facts
tasks:
- name: Assert container running
ansible.builtin.include_tasks:
file: ../default/tasks/assert-container.yml
loop:
- dockge-dockge-1 |
Ik heb hier een reusable task van gemaakt die ik in alle scenario's hergebruik.
extensions/molecule/default/tasks/assert-container.yml:
YAML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| ---
- name: Exit if item not provided
ansible.builtin.assert:
that:
- item is defined
fail_msg: >
Error running assert container task
Variable {{ item }} should be provided in a loop, but is not present.
- name: Obtain container info
community.docker.docker_container_info:
name: "{{ item }}"
register: container_info
- name: Assert container exists
ansible.builtin.assert:
that:
- container_info.container is not none
fail_msg: Container {{ item }} is not running
- name: Assert container is running correctly
ansible.builtin.assert:
that:
- container_info.container.State.Running == true
- container_info.container.State.ExitCode == 0
- container_info.container.State.Restarting == false
fail_msg: "The container {{ item }} is not running as expected."
success_msg: "The container {{ item }} is running as expected." |
Je ziet dat ik controleer of de state van de container juist is, maar je kan natuurlijk ook andere zaken controleren.
Alleen al voor het testen van templates zou molecule best een goede tool zijn.
Dan de cleanup playbook, die haalt de container down en gooit de files weer weg.
Heb weer DOCKER_HOST laten verwijzen naar DinD container.
extensions/molecule/dockge/cleanup.yml:
YAML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| ---
- name: Cleanup
hosts: molecule
gather_facts: false
vars:
compose_project_dir: "{{ dockge__stack_base_dir | default('/opt/stacks/dockge') }}"
# Set Docker host to communicate with the Docker daemon inside the DinD container
environment:
DOCKER_HOST: tcp://docker:2375/
tasks:
- name: Check if compose_project_dir exists
ansible.builtin.stat:
path: "{{ compose_project_dir }}"
register: compose_dir_stat
- name: Docker compose down
become: true
community.docker.docker_compose_v2:
project_src: "{{ compose_project_dir }}"
state: absent
when: compose_dir_stat.stat.exists
- name: Remove folder
ansible.builtin.file:
path: "{{ compose_project_dir }}"
state: absent
become: true |
Als ik nu molecule test uitvoer in mijn project root, worden alle tests uitgevoerd.
Met -s kan ik één (of meerdere) scenario's uitvoeren.
In mijn CI check ik met een script welke files in
/roles/ worden gewijzigd en als er een overeenkomstige folder in
extensions/molecule is, trap ik die met molecule -s af.
Ik heb nog niet voor alle roles een scenario, maar wel voor alle docker compose projects op mijn TrueNAS machine.
Wat nog wel lastig was is omgaan met volumes (gemount op docker host), deze komen ook in de DinD container terecht.
Mijn docker compose projects draaien op TrueNAS en per applicatie maak ik een dataset aan. Ik heb dus een reusable task in docker_compose_base om deze datasets aan te maken. Deze stap sla ik over bij het uitvoeren van Molecule tests (met de tag "notest") maar om de permissies wel goed te hebben tijdens de tests, voer ik pre_tasks uit in de converge playbook om permissies goed te zetten. Hieronder de reusable task die met docker_container_exec wat magic uitvoert in de DinD container.
extensions/molecule/default/tasks/set-permissions.yml:
YAML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| ---
- name: Exit if item not provided
ansible.builtin.assert:
that:
- item is defined
- item_uid is defined
- item_gid is defined
fail_msg: >
Error running set permissions task.
Check if all required variables are set correctly.
Variable {{ item }} should be provided in a loop.
Variables {{ item_uid }} and {{ item_gid }} should be defined.
- name: Set permissions inside my-dind-daemon container
delegate_to: localhost
community.docker.docker_container_exec:
container: my-dind-daemon
command: >
sh -c "mkdir -p {{ item }} &&
chown -R {{ item_uid }}:{{ item_gid }} {{ item }} &&
chmod -R 775 {{ item }}"
docker_host: unix:///var/run/docker.sock |
Al met al was het best een klus om dit allemaal werkend te krijgen, maar als je één simpel scenario op een gegeven moment werkend hebt, volgt de rest daarna vrij snel. Ik ben begonnen met simpele compose projecten zonder docker volume mounts, daarna de complexere projecten met volume mounts opgepakt.
Voor Traefik heb ik nog op het lijstje om met Let's Encrypt staging te gaan werken of met een stub wat betreft de certificaten. Ook zou ik daar de Cloudflare tunnel willen 'stubben' o.i.d.
Zo is er altijd ruimte voor verbetering

.