203 Create DHCP jails with auto UUID and iocage_tags

Extending example 202 Create iocage templates. Clone DHCP jails..

Use case

Automatically generated UUID

Automatically generate the jails UUID names. At each iocage host, create three jails from the template ansible_client

swarms:
  sw_01:
    count: 3
    template: ansible_client

The module vbotka.freebsd.iocage doesn’t work with multiple names. Use ansible.builtin.command instead. If the UUID is generated automatically, such a task is not idempotent anyway. Example of the commands

iocage create --short --template ansible_client --count 3  bpf=1 dhcp=1 vnet=1 notes="vmm=iocage_01 swarm=sw_01"
iocage start cd31c2a2 d254f889 158ef36d

The variable iocage_tags

In the inventory plugin, compose the variable iocage_tags

iocage_tags: dict(iocage_properties.notes | regex_findall('(\w+)=([\w\-]+)'))

For example,

iocage_tags:
  vmm: iocage_01
  swarm: sw_01

Create groups from iocage_tags

keyed_groups:
  - prefix: swarm
    key: iocage_tags.swarm
  - prefix: vmm
    key: iocage_tags.vmm

Tree

shell> tree .
.
├── ansible.cfg
├── group_vars
│   └── all
│       └── iocage.yml
├── hosts
│   ├── 02_iocage.yml
│   ├── 04_iocage.yml
│   └── 99_constructed.yml
├── iocage.ini
└── pb-test.yml

Synopsis

  • At two managed nodes:

    • iocage_02

    • iocage_04

    In the playbook vbotka.freebsd.pb_iocage_ansible_clients.yml, use:

    • module vbotka.freebsd.iocage to:

      • create facts only

    • module ansible.builtin.command to:

      • create jails

      • start jails

      • optionally, stop and destroy the jails.

  • At all created jails:

    In the playbook pb-test.yml:

    • connect to the created jails

    • display basic configuration of the jails.

Requirements

Notes

See also

Templates at iocage_02

[iocage_02]# iocage list -lt
+------+----------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+
| JID  |      NAME      | BOOT | STATE |   TYPE   |     RELEASE     |        IP4         | IP6 | TEMPLATE | BASEJAIL |
+======+================+======+=======+==========+=================+====================+=====+==========+==========+
| None | ansible_client | off  | down  | template | 14.3-RELEASE-p8 | DHCP (not running) | -   | -        | no       |
+------+----------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+

Templates at iocage_04

[iocage_04]# iocage list -lt
+------+-----------------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+
| JID  |         NAME          | BOOT | STATE |   TYPE   |     RELEASE     |        IP4         | IP6 | TEMPLATE | BASEJAIL |
+======+=======================+======+=======+==========+=================+====================+=====+==========+==========+
| None | ansible_client        | off  | down  | template | 15.0-RELEASE-p3 | DHCP (not running) | -   | -        | no       |
+------+-----------------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+
| None | ansible_client_apache | off  | down  | template | 15.0-RELEASE-p3 | DHCP (not running) | -   | -        | no       |
+------+-----------------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+
| None | ansible_client_pull   | off  | down  | template | 15.0-RELEASE-p3 | DHCP (not running) | -   | -        | no       |
+------+-----------------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+

ansible.cfg

[defaults]
gathering = explicit
callback_result_format = yaml
display_skipped_hosts = false
host_key_checking = false

[connection]
pipelining = true

Inventory iocage.ini

iocage_02 ansible_host=10.1.0.73
iocage_04 ansible_host=10.1.0.29

[iocage]
iocage_02
iocage_04

[iocage:vars]
ansible_user=admin
ansible_become=true
ansible_python_interpreter=auto_silent

group_vars

group_vars/all/iocage.yml
properties:
  bpf: 1
  dhcp: 1
  vnet: 1
  notes: "vmm={{ inventory_hostname }}"

swarms:
  sw_01:
    count: 3
    template: ansible_client

Playbook output - Create and start swarms

(env) > ansible-playbook vbotka.freebsd.pb_iocage_ansible_clients.yml \
                         -i iocage.ini \
                         -t swarm \
                         -e swarm=true \
                         -e debug=true
PLAY [Create and start jails. Optionally stop and destroy jails.] **************

TASK [Get iocage facts] ********************************************************
ok: [iocage_04]
ok: [iocage_02]

TASK [Debug cmd_create debug=true] *********************************************
ok: [iocage_02] => (item=sw_01) => 
    msg: |-
        iocage create --short --template ansible_client --count 3 bpf=1 dhcp=1 vnet=1 notes="vmm=iocage_02 swarm=sw_01"
ok: [iocage_04] => (item=sw_01) => 
    msg: |-
        iocage create --short --template ansible_client --count 3 bpf=1 dhcp=1 vnet=1 notes="vmm=iocage_04 swarm=sw_01"

TASK [Create swarms] ***********************************************************
changed: [iocage_04] => (item=sw_01)
changed: [iocage_02] => (item=sw_01)

TASK [Debug create swarms debug=true] ******************************************
ok: [iocage_02] => 
    out:
        changed: true
        msg: All items completed
        results:
        -   ansible_loop_var: item
            changed: true
            cmd:
            - iocage
            - create
            - --short
            - --template
            - ansible_client
            - --count
            - '3'
            - bpf=1
            - dhcp=1
            - vnet=1
            - notes=vmm=iocage_02 swarm=sw_01
            delta: '0:00:04.581922'
            end: '2026-02-24 23:26:23.563259'
            failed: false
            invocation:
                module_args:
                    _raw_params: null
                    _uses_shell: false
                    argv: null
                    chdir: null
                    cmd: |-
                        iocage create --short --template ansible_client --count 3 bpf=1 dhcp=1 vnet=1 notes="vmm=iocage_02 swarm=sw_01"
                    creates: null
                    executable: null
                    expand_argument_vars: true
                    removes: null
                    stdin: null
                    stdin_add_newline: true
                    strip_empty_ends: true
            item:
                key: sw_01
                value:
                    count: 3
                    template: ansible_client
            msg: ''
            rc: 0
            start: '2026-02-24 23:26:18.981337'
            stderr: ''
            stderr_lines: []
            stdout: |-
                a2508697 successfully created!
                fd54d2f8 successfully created!
                44dcc34f successfully created!
            stdout_lines:
            - a2508697 successfully created!
            - fd54d2f8 successfully created!
            - 44dcc34f successfully created!
        skipped: false
ok: [iocage_04] => 
    out:
        changed: true
        msg: All items completed
        results:
        -   ansible_loop_var: item
            changed: true
            cmd:
            - iocage
            - create
            - --short
            - --template
            - ansible_client
            - --count
            - '3'
            - bpf=1
            - dhcp=1
            - vnet=1
            - notes=vmm=iocage_04 swarm=sw_01
            delta: '0:00:00.853984'
            end: '2026-02-24 23:26:20.579261'
            failed: false
            invocation:
                module_args:
                    _raw_params: null
                    _uses_shell: false
                    argv: null
                    chdir: null
                    cmd: |-
                        iocage create --short --template ansible_client --count 3 bpf=1 dhcp=1 vnet=1 notes="vmm=iocage_04 swarm=sw_01"
                    creates: null
                    executable: null
                    expand_argument_vars: true
                    removes: null
                    stdin: null
                    stdin_add_newline: true
                    strip_empty_ends: true
            item:
                key: sw_01
                value:
                    count: 3
                    template: ansible_client
            msg: ''
            rc: 0
            start: '2026-02-24 23:26:19.725277'
            stderr: ''
            stderr_lines: []
            stdout: |-
                16ecd72b successfully created!
                c62c0b8a successfully created!
                dbb0a0b0 successfully created!
            stdout_lines:
            - 16ecd72b successfully created!
            - c62c0b8a successfully created!
            - dbb0a0b0 successfully created!
        skipped: false

TASK [Get iocage facts] ********************************************************
ok: [iocage_04]
ok: [iocage_02]

TASK [Debug cmd_start debug=true] **********************************************
ok: [iocage_04] => (item=sw_01) => 
    msg: |-
        iocage start 16ecd72b c62c0b8a dbb0a0b0
ok: [iocage_02] => (item=sw_01) => 
    msg: |-
        iocage start 44dcc34f fd54d2f8 a2508697

TASK [Start swarms] ************************************************************
changed: [iocage_04] => (item=sw_01)
changed: [iocage_02] => (item=sw_01)

TASK [Debug start swarms debug=true] *******************************************
ok: [iocage_02] => 
    out:
        changed: true
        msg: All items completed
        results:
        -   ansible_loop_var: item
            changed: true
            cmd:
            - iocage
            - start
            - 44dcc34f
            - fd54d2f8
            - a2508697
            delta: '0:00:38.471950'
            end: '2026-02-24 23:27:24.707952'
            failed: false
            invocation:
                module_args:
                    _raw_params: null
                    _uses_shell: false
                    argv: null
                    chdir: null
                    cmd: |-
                        iocage start 44dcc34f fd54d2f8 a2508697
                    creates: null
                    executable: null
                    expand_argument_vars: true
                    removes: null
                    stdin: null
                    stdin_add_newline: true
                    strip_empty_ends: true
            item:
                key: sw_01
                value:
                    count: 3
                    template: ansible_client
            msg: ''
            rc: 0
            start: '2026-02-24 23:26:46.236002'
            stderr: |-
                No default gateway found for ipv6.
                No default gateway found for ipv6.
                No default gateway found for ipv6.
            stderr_lines:
            - No default gateway found for ipv6.
            - No default gateway found for ipv6.
            - No default gateway found for ipv6.
            stdout: |-
                * Starting 44dcc34f
                  + Started OK
                  + Using devfs_ruleset: 1000 (iocage generated default)
                  + Configuring VNET OK
                  + Using IP options: vnet
                  + Starting services OK
                  + Executing poststart OK
                  + DHCP Address: 10.1.0.181/24
                * Starting fd54d2f8
                  + Started OK
                  + Using devfs_ruleset: 1001 (iocage generated default)
                  + Configuring VNET OK
                  + Using IP options: vnet
                  + Starting services OK
                  + Executing poststart OK
                  + DHCP Address: 10.1.0.141/24
                * Starting a2508697
                  + Started OK
                  + Using devfs_ruleset: 1002 (iocage generated default)
                  + Configuring VNET OK
                  + Using IP options: vnet
                  + Starting services OK
                  + Executing poststart OK
                  + DHCP Address: 10.1.0.241/24
            stdout_lines:
            - '* Starting 44dcc34f'
            - '  + Started OK'
            - '  + Using devfs_ruleset: 1000 (iocage generated default)'
            - '  + Configuring VNET OK'
            - '  + Using IP options: vnet'
            - '  + Starting services OK'
            - '  + Executing poststart OK'
            - '  + DHCP Address: 10.1.0.181/24'
            - '* Starting fd54d2f8'
            - '  + Started OK'
            - '  + Using devfs_ruleset: 1001 (iocage generated default)'
            - '  + Configuring VNET OK'
            - '  + Using IP options: vnet'
            - '  + Starting services OK'
            - '  + Executing poststart OK'
            - '  + DHCP Address: 10.1.0.141/24'
            - '* Starting a2508697'
            - '  + Started OK'
            - '  + Using devfs_ruleset: 1002 (iocage generated default)'
            - '  + Configuring VNET OK'
            - '  + Using IP options: vnet'
            - '  + Starting services OK'
            - '  + Executing poststart OK'
            - '  + DHCP Address: 10.1.0.241/24'
        skipped: false
ok: [iocage_04] => 
    out:
        changed: true
        msg: All items completed
        results:
        -   ansible_loop_var: item
            changed: true
            cmd:
            - iocage
            - start
            - 16ecd72b
            - c62c0b8a
            - dbb0a0b0
            delta: '0:00:24.510726'
            end: '2026-02-24 23:27:11.477919'
            failed: false
            invocation:
                module_args:
                    _raw_params: null
                    _uses_shell: false
                    argv: null
                    chdir: null
                    cmd: |-
                        iocage start 16ecd72b c62c0b8a dbb0a0b0
                    creates: null
                    executable: null
                    expand_argument_vars: true
                    removes: null
                    stdin: null
                    stdin_add_newline: true
                    strip_empty_ends: true
            item:
                key: sw_01
                value:
                    count: 3
                    template: ansible_client
            msg: ''
            rc: 0
            start: '2026-02-24 23:26:46.967193'
            stderr: |-
                No default gateway found for ipv6.
                No default gateway found for ipv6.
                No default gateway found for ipv6.
            stderr_lines:
            - No default gateway found for ipv6.
            - No default gateway found for ipv6.
            - No default gateway found for ipv6.
            stdout: |-
                * Starting 16ecd72b
                  + Started OK
                  + Using devfs_ruleset: 1000 (iocage generated default)
                  + Configuring VNET OK
                  + Using IP options: vnet
                  + Starting services OK
                  + Executing poststart OK
                  + DHCP Address: 10.1.0.109/24
                * Starting c62c0b8a
                  + Started OK
                  + Using devfs_ruleset: 1001 (iocage generated default)
                  + Configuring VNET OK
                  + Using IP options: vnet
                  + Starting services OK
                  + Executing poststart OK
                  + DHCP Address: 10.1.0.112/24
                * Starting dbb0a0b0
                  + Started OK
                  + Using devfs_ruleset: 1002 (iocage generated default)
                  + Configuring VNET OK
                  + Using IP options: vnet
                  + Starting services OK
                  + Executing poststart OK
                  + DHCP Address: 10.1.0.217/24
            stdout_lines:
            - '* Starting 16ecd72b'
            - '  + Started OK'
            - '  + Using devfs_ruleset: 1000 (iocage generated default)'
            - '  + Configuring VNET OK'
            - '  + Using IP options: vnet'
            - '  + Starting services OK'
            - '  + Executing poststart OK'
            - '  + DHCP Address: 10.1.0.109/24'
            - '* Starting c62c0b8a'
            - '  + Started OK'
            - '  + Using devfs_ruleset: 1001 (iocage generated default)'
            - '  + Configuring VNET OK'
            - '  + Using IP options: vnet'
            - '  + Starting services OK'
            - '  + Executing poststart OK'
            - '  + DHCP Address: 10.1.0.112/24'
            - '* Starting dbb0a0b0'
            - '  + Started OK'
            - '  + Using devfs_ruleset: 1002 (iocage generated default)'
            - '  + Configuring VNET OK'
            - '  + Using IP options: vnet'
            - '  + Starting services OK'
            - '  + Executing poststart OK'
            - '  + DHCP Address: 10.1.0.217/24'
        skipped: false

PLAY RECAP *********************************************************************
iocage_02                  : ok=8    changed=2    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
iocage_04                  : ok=8    changed=2    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   

Jails at iocage_02

[iocage_02]# iocage list -l
+-----+----------+------+-------+------+-----------------+--------------------+-----+----------------+----------+
| JID |   NAME   | BOOT | STATE | TYPE |     RELEASE     |        IP4         | IP6 |    TEMPLATE    | BASEJAIL |
+=====+==========+======+=======+======+=================+====================+=====+================+==========+
| 82  | 44dcc34f | off  | up    | jail | 14.3-RELEASE-p8 | epair0b|10.1.0.181 | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+--------------------+-----+----------------+----------+
| 84  | a2508697 | off  | up    | jail | 14.3-RELEASE-p8 | epair0b|10.1.0.241 | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+--------------------+-----+----------------+----------+
| 83  | fd54d2f8 | off  | up    | jail | 14.3-RELEASE-p8 | epair0b|10.1.0.141 | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+--------------------+-----+----------------+----------+

Jails at iocage_04

[iocage_04]# iocage list -l
+-----+----------+------+-------+------+-----------------+--------------------+-----+----------------+----------+
| JID |   NAME   | BOOT | STATE | TYPE |     RELEASE     |        IP4         | IP6 |    TEMPLATE    | BASEJAIL |
+=====+==========+======+=======+======+=================+====================+=====+================+==========+
| 61  | 16ecd72b | off  | up    | jail | 15.0-RELEASE-p3 | epair0b|10.1.0.109 | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+--------------------+-----+----------------+----------+
| 62  | c62c0b8a | off  | up    | jail | 15.0-RELEASE-p3 | epair0b|10.1.0.112 | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+--------------------+-----+----------------+----------+
| 63  | dbb0a0b0 | off  | up    | jail | 15.0-RELEASE-p3 | epair0b|10.1.0.217 | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+--------------------+-----+----------------+----------+

Inventory hosts

hosts/02_iocage.yml
plugin: vbotka.freebsd.iocage
host: 10.1.0.73
user: admin
get_properties: True
hooks_results:
  - /var/db/dhclient-hook.address.epair0b
compose:
  ansible_host: (iocage_hooks.0 == '-') | ternary(iocage_ip4, iocage_hooks.0)
  iocage_tags: dict(iocage_properties.notes | regex_findall('(\w+)=([\w\-]+)'))
hosts/04_iocage.yml
plugin: vbotka.freebsd.iocage
host: 10.1.0.29
user: admin
get_properties: True
hooks_results:
  - /var/db/dhclient-hook.address.epair0b
compose:
  ansible_host: (iocage_hooks.0 == '-') | ternary(iocage_ip4, iocage_hooks.0)
  iocage_tags: dict(iocage_properties.notes | regex_findall('(\w+)=([\w\-]+)'))
hosts/99_constructed.yml
plugin: ansible.builtin.constructed
keyed_groups:
  - prefix: swarm
    key: iocage_tags.swarm
  - prefix: vmm
    key: iocage_tags.vmm

Note

The option get_properties: True is needed to get the dictionary iocage_properties

Display inventory

(env) > ansible-inventory -i hosts --graph
@all:
  |--@ungrouped:
  |--@swarm_sw_01:
  |  |--44dcc34f
  |  |--a2508697
  |  |--fd54d2f8
  |  |--16ecd72b
  |  |--c62c0b8a
  |  |--dbb0a0b0
  |--@vmm_iocage_02:
  |  |--44dcc34f
  |  |--a2508697
  |  |--fd54d2f8
  |--@vmm_iocage_04:
  |  |--16ecd72b
  |  |--c62c0b8a
  |  |--dbb0a0b0

Playbook pb-test.yml

- name: Connect to the group test.
  hosts: swarm_sw_01
  gather_facts: false
  remote_user: admin

  vars:

    ansible_python_interpreter: auto_silent
    
  tasks:

    - ansible.builtin.command: hostname
      register: out_host
    
    - ansible.builtin.debug:
        msg: >
          ansible_host={{ ansible_host }}
          iocage_tags={{ iocage_tags | to_yaml }}

Playbook output - Display iocage_tags

(env) > ansible-playbook pb-test.yml -i hosts
PLAY [Connect to the group test.] **********************************************

TASK [ansible.builtin.command] *************************************************
changed: [c62c0b8a]
changed: [16ecd72b]
changed: [dbb0a0b0]
changed: [fd54d2f8]
changed: [44dcc34f]
changed: [a2508697]

TASK [ansible.builtin.debug] ***************************************************
ok: [44dcc34f] => 
    msg: |-
        ansible_host=10.1.0.181 iocage_tags={swarm: sw_01, vmm: iocage_02}
ok: [a2508697] => 
    msg: |-
        ansible_host=10.1.0.241 iocage_tags={swarm: sw_01, vmm: iocage_02}
ok: [fd54d2f8] => 
    msg: |-
        ansible_host=10.1.0.141 iocage_tags={swarm: sw_01, vmm: iocage_02}
ok: [16ecd72b] => 
    msg: |-
        ansible_host=10.1.0.109 iocage_tags={swarm: sw_01, vmm: iocage_04}
ok: [c62c0b8a] => 
    msg: |-
        ansible_host=10.1.0.112 iocage_tags={swarm: sw_01, vmm: iocage_04}
ok: [dbb0a0b0] => 
    msg: |-
        ansible_host=10.1.0.217 iocage_tags={swarm: sw_01, vmm: iocage_04}

PLAY RECAP *********************************************************************
16ecd72b                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
44dcc34f                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
a2508697                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
c62c0b8a                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
dbb0a0b0                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
fd54d2f8                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Hint

The below command stops and destroys the jails in swarms

ansible-playbook vbotka.freebsd.pb_iocage_ansible_clients.yml \
                 -i iocage.ini \
                 -t swarm_destroy \
                 -e swarm_destroy=true