206 Create DHCP and fixed IP jails

Extending example 203 Create DHCP jails with auto UUID and iocage_tags.

Use case

In the inventory plugin vbotka.freebsd.iocage configuration file, use the option hooks_results to get the DHCP IP address. This option is common for all jails in this example

hooks_results:
  - /var/db/dhclient-hook.address.epair0b

It will silently fail in jails with fixed IP addresses. If the item fails, the result is the dash character ‘-’

iocage_hooks:
  - '-'

This use case demonstrates the advantage of silently ignoring failed items over the potential explicit error handling. Let the option compose pick what is needed

compose:
  ansible_host: (iocage_hooks.0 == '-') | ternary(iocage_ip4, iocage_hooks.0)

Fixed IP

One jail with fixed IP will be created from the template ansible_client in this example

clones:
  test_131:
    clone_from: ansible_client
    properties:
      ip4_addr: "em0|10.1.0.131/24"
      notes: "swarm=sw_01"

Automatically generated UUID

Two DHCP jails with generated UUID will be created from the template ansible_client

swarms:
  sw_01:
    count: 3
    template: ansible_client
    properties:
      bpf: 1
      dhcp: 1
      vnet: 1

Note

The clone test_131 belongs to the swarm sw_01. Set count: 3 to create two more jails in the swarm sw_01.

The module vbotka.freebsd.iocage doesn’t work with multiple names. We will use ansible.builtin.command instead. Anyway, such a task is not idempotent if the UUID is generated automatically. Example of the commands

shell> iocage create --short --template ansible_client --count 2  bpf=1 dhcp=1 vnet=1 notes="vmm=iocage_02 swarm=sw_01"
shell> iocage start cd31c2a2 d254f889

The variable iocage_tags

The inventory plugin composes the variable iocage_tags

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

For example,

iocage_tags:
  vmm: iocage_04
  swarm: sw_01

This dictionary is used to create groups

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

Tree

shell> tree .
.
├── ansible.cfg
├── hosts
│   └── 04_iocage.yml
├── host_vars
│   └── iocage_04
│       └── iocage.yml
├── iocage.ini
└── pb-test.yml

Synopsis

  • At one managed node:

    • iocage_04

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

    • module vbotka.freebsd.iocage to:

      • create one jail with fixed IP

      • start the jail

    • module ansible.builtin.command to:

      • create two DHCP jails with generated UUID

      • start 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_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_04

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

host_vars

host_vars/iocage_04/iocage.yml
properties:
  notes: "vmm={{ inventory_hostname }}"

clones:
  test_131:
    clone_from: ansible_client
    properties:
      bpf: 1
      vnet: 1
      ip4_addr: 'vnet0|10.1.0.131/24'
      notes: "swarm=sw_01"

swarms:
  sw_01:
    count: 3
    template: ansible_client
    properties:
      bpf: 1
      dhcp: 1
      vnet: 1

start: [test_131]

Create and start clones

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

TASK [Create clones from template] *********************************************
changed: [iocage_04] => (item=test_131 ansible_client)

TASK [Start clones] ************************************************************
changed: [iocage_04]

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

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]

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

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

TASK [Debug create swarms debug=true] ******************************************
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
            - '2'
            - notes=vmm=iocage_04 swarm=sw_01
            - bpf=1
            - dhcp=1
            - vnet=1
            delta: '0:00:00.764700'
            end: '2026-02-24 23:30:41.101203'
            failed: false
            invocation:
                module_args:
                    _raw_params: null
                    _uses_shell: false
                    argv: null
                    chdir: null
                    cmd: |-
                        iocage create --short --template ansible_client --count 2 notes="vmm=iocage_04 swarm=sw_01" bpf=1 dhcp=1 vnet=1
                    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
                    properties:
                        bpf: 1
                        dhcp: 1
                        vnet: 1
                    template: ansible_client
            msg: ''
            rc: 0
            start: '2026-02-24 23:30:40.336503'
            stderr: ''
            stderr_lines: []
            stdout: |-
                2f6cf965 successfully created!
                e4c59701 successfully created!
            stdout_lines:
            - 2f6cf965 successfully created!
            - e4c59701 successfully created!
        skipped: false

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

TASK [Debug cmd_start debug=true] **********************************************
ok: [iocage_04] => (item=sw_01) => 
    msg: |-
        iocage start e4c59701 2f6cf965

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

TASK [Debug start swarms debug=true] *******************************************
ok: [iocage_04] => 
    out:
        changed: true
        msg: All items completed
        results:
        -   ansible_loop_var: item
            changed: true
            cmd:
            - iocage
            - start
            - e4c59701
            - 2f6cf965
            delta: '0:00:08.353942'
            end: '2026-02-24 23:30:52.553598'
            failed: false
            invocation:
                module_args:
                    _raw_params: null
                    _uses_shell: false
                    argv: null
                    chdir: null
                    cmd: |-
                        iocage start e4c59701 2f6cf965
                    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
                    properties:
                        bpf: 1
                        dhcp: 1
                        vnet: 1
                    template: ansible_client
            msg: ''
            rc: 0
            start: '2026-02-24 23:30:44.199656'
            stderr: |-
                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.
            stdout: |-
                * Starting e4c59701
                  + 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.200/24
                * Starting 2f6cf965
                  + 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.228/24
            stdout_lines:
            - '* Starting e4c59701'
            - '  + 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.200/24'
            - '* Starting 2f6cf965'
            - '  + 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.228/24'
        skipped: false

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

Jails at iocage_04

[iocage_04]# iocage list -l
+-----+----------+------+-------+------+-----------------+---------------------+-----+----------------+----------+
| JID |   NAME   | BOOT | STATE | TYPE |     RELEASE     |         IP4         | IP6 |    TEMPLATE    | BASEJAIL |
+=====+==========+======+=======+======+=================+=====================+=====+================+==========+
| 69  | 2f6cf965 | off  | up    | jail | 15.0-RELEASE-p3 | epair0b|10.1.0.228  | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+---------------------+-----+----------------+----------+
| 68  | e4c59701 | off  | up    | jail | 15.0-RELEASE-p3 | epair0b|10.1.0.200  | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+---------------------+-----+----------------+----------+
| 67  | test_131 | off  | up    | jail | 15.0-RELEASE-p3 | vnet0|10.1.0.131/24 | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+---------------------+-----+----------------+----------+

Inventory hosts

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\-]+)'))
keyed_groups:
  - prefix: swarm
    key: iocage_tags.swarm
  - prefix: vmm
    key: iocage_tags.vmm

Note

The option get_properties: True is needed to compose the dictionary iocage_tags

Display inventory

(env) > ansible-inventory -i hosts --graph
@all:
  |--@ungrouped:
  |--@swarm_sw_01:
  |  |--2f6cf965
  |  |--e4c59701
  |  |--test_131
  |--@vmm_iocage_04:
  |  |--2f6cf965
  |  |--e4c59701
  |  |--test_131

Playbook pb-test.yml

- hosts: swarm_sw_01
  remote_user: admin

  vars:

    ansible_python_interpreter: auto_silent
    
  tasks:

    - ansible.builtin.command: hostname
      register: out

    - ansible.builtin.debug:
        msg: |
          out.stdout: {{ out.stdout }}
          ansible_host: {{ ansible_host }}
          iocage_hooks: {{ iocage_hooks }}
          iocage_tags:
            {{ iocage_tags | to_nice_yaml(2) | indent(2) }}

Playbook output - Display jails in the swarm

(env) > ansible-playbook pb-test.yml -i hosts
PLAY [swarm_sw_01] *************************************************************

TASK [ansible.builtin.command] *************************************************
changed: [2f6cf965]
changed: [test_131]
changed: [e4c59701]

TASK [ansible.builtin.debug] ***************************************************
ok: [2f6cf965] => 
    msg: |-
        out.stdout: 2f6cf965
        ansible_host: 10.1.0.228
        iocage_hooks: ['10.1.0.228']
        iocage_tags:
          swarm: sw_01
          vmm: iocage_04
ok: [e4c59701] => 
    msg: |-
        out.stdout: e4c59701
        ansible_host: 10.1.0.200
        iocage_hooks: ['10.1.0.200']
        iocage_tags:
          swarm: sw_01
          vmm: iocage_04
ok: [test_131] => 
    msg: |-
        out.stdout: test-131
        ansible_host: 10.1.0.131
        iocage_hooks: ['-']
        iocage_tags:
          swarm: sw_01
          vmm: iocage_04

PLAY RECAP *********************************************************************
2f6cf965                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
e4c59701                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
test_131                   : 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