200 Create Ansible client templates and clone Ansible client jails

Use case

Create templates for Ansible clients.

Tree

shell> tree .
.
├── ansible.cfg
├── files
│   └── pk_admins.txt
├── hosts
│   ├── 01_iocage.yml
│   ├── 02_iocage.yml
│   └── 03_constructed.yml
├── host_vars
│   ├── iocage_01
│   │   └── iocage.yml
│   └── iocage_02
│       └── iocage.yml
├── iocage-hosts.ini
├── pb-iocage-ansible-clients -> ../../../../playbooks/pb-iocage-ansible-clients
├── pb-iocage-ansible-clients.yml -> ../../../../playbooks/pb-iocage-ansible-clients.yml
├── pb-iocage-template -> ../../../../playbooks/pb-iocage-template
├── pb-iocage-template.yml -> ../../../../playbooks/pb-iocage-template.yml
└── pb-test-01.yml

Synopsis

  • On two iocage hosts:

    • iocage_01

    • iocage_02

    In the playbook pb-iocage-template.yml, use the modules:

    • vbotka.freebsd.iocage to create, start, stop, and convert jails to templates.

    • vbotka.freebsd.iocage exec to create a user and set .ssh ownership.

    • community.general.pkgng to install packages.

    • ansible.posix.authorized_key to configure public keys.

    • ansible.builtin.lineinfile to configure /etc/rc.conf and /usr/local/etc/sudoers

    In the playbook pb-iocage-ansible-clients.yml, use the module vbotka.freebsd.iocage to:

    • create jails from the Ansible client templates

    • start all jails

    • optionally display the lists of jails.

  • On all created jails:

    In the playbook pb-test-01.yml:

    • connect created jails

    • display basic configuration of the jails.

Requirements

Notes

Configuration ansible.cfg

[defaults]
gathering = explicit
callback_result_format = yaml

[connection]
pipelining = true

host_vars/iocage_01/iocage.yml

---
ansible_python_interpreter: /usr/local/bin/python3.9
ansible_become_password: admin

freebsd_iocage_pool: pool2
freebsd_iocage_pool_mount: /mnt/pool2
freebsd_iocage_mount: "{{ freebsd_iocage_pool_mount }}/iocage"

templates:
  ansible_client:
    release: 13.4-RELEASE
    properties:
      ip4_addr: 'vnet0|10.1.0.198/24'
    dhclient: "{{ act_dhclient | dict2items }}"
    rcconf: "{{ act_rcconf | dict2items }}"

# ansible client template defaults
act_pkg:
  - security/sudo
  - lang/python39
act_user: admin
act_pk: pk_admins.txt
act_sudo: true
act_rcconf:
  iocage_enable: '"YES"'
  sshd_enable: '"YES"'
act_dhclient: {}

clones:
  test_101:
    clone_from: ansible_client
    properties:
      ip4_addr: 'vnet0|10.1.0.101/24'
  test_102:
    clone_from: ansible_client
    properties:
      ip4_addr: 'vnet0|10.1.0.102/24'
  test_103:
    clone_from: ansible_client
    properties:
      ip4_addr: 'vnet0|10.1.0.103/24'

# clones default properties
properties:
  vnet: 'on'
  defaultrouter: 10.1.0.10
  notes: "vmm={{ inventory_hostname }}"

start: "{{ clones.keys() }}"

host_vars/iocage_02/iocage.yml

---
ansible_python_interpreter: /usr/local/bin/python3.11

freebsd_iocage_runner_env:
  CRYPTOGRAPHY_OPENSSL_NO_LEGACY: 1

freebsd_iocage_pool: zroot
freebsd_iocage_pool_mount: /zroot
freebsd_iocage_mount: "{{ freebsd_iocage_pool_mount }}/iocage"

templates:
  ansible_client:
    release: 14.1-RELEASE
    properties:
      ip4_addr: 'em0|10.1.0.199/24'
    dhclient: "{{ act_dhclient | dict2items }}"
    rcconf: "{{ act_rcconf | dict2items }}"

# ansible client template defaults
act_pkg:
  - security/sudo
  - lang/python311
act_user: admin
act_pk: pk_admins.txt
act_sudo: true
act_rcconf:
  iocage_enable: '"YES"'
  sshd_enable: '"YES"'
act_dhclient: {}

clones:
  test_111:
    clone_from: ansible_client
    properties:
      ip4_addr: 'em0|10.1.0.111/24'
  test_112:
    clone_from: ansible_client
    properties:
      ip4_addr: 'em0|10.1.0.112/24'
  test_113:
    clone_from: ansible_client
    properties:
      ip4_addr: 'em0|10.1.0.113/24'

# clones default properties
properties:
  notes: "vmm={{ inventory_hostname }}"

start: "{{ clones.keys() }}"

Note

  • The user act_user will be created in the template.

  • The user act_user will serve as Ansible remote_user.

  • The file act_pk provides the public keys allowed to ssh to act_user.

Warning

  • The user act_user must exist on the iocage host. Otherwise, the module ansible.posix.authorized_key will crash. See pb-iocage-template/pk.yml

  • The file files/pk_admins.txt was sanitized. Fit the public keys to your needs

    shell> cat files/pk_admins.txt
    ssh-rsa <sanitized> admin@controller
    

Inventory iocage-hosts.ini

iocage_01 ansible_host=10.1.0.18
iocage_02 ansible_host=10.1.0.73

[iocage]
iocage_01
iocage_02

[iocage:vars]
ansible_user=admin
ansible_become=true

Playbook pb-iocage-template.yml

- name: Create Ansible client templates.
  hosts: iocage
  environment: "{{ freebsd_iocage_runner_env | d({}) }}"

  vars:

    debug: false
    debug2: false

  tasks:

    - name: Setup
      tags: [always, setup]
      ansible.builtin.import_tasks: pb-iocage-template/setup.yml

    - name: Create jails
      tags: create
      ansible.builtin.import_tasks: pb-iocage-template/create.yml

    - name: Start jails
      tags: start
      ansible.builtin.import_tasks: pb-iocage-template/start.yml

    - name: Install packages
      when: act_pkg | length > 0
      tags: pkg
      ansible.builtin.import_tasks: pb-iocage-template/pkg.yml

    - name: Create user
      when: act_user | length > 0
      tags: user
      ansible.builtin.import_tasks: pb-iocage-template/user.yml

    - name: Configure public keys
      when: act_pk | length > 0
      tags: pk
      ansible.builtin.import_tasks: pb-iocage-template/pk.yml

    - name: Configure sudo
      when: act_sudo | bool
      tags: sudo
      ansible.builtin.import_tasks: pb-iocage-template/sudo.yml

    - name: Configure dhclient
      when: act_dhclient | length > 0
      tags: dhclient
      ansible.builtin.import_tasks: pb-iocage-template/dhclient.yml

    - name: Configure /etc/rc.conf
      when: act_rcconf | length > 0
      tags: rcconf
      ansible.builtin.import_tasks: pb-iocage-template/rc_conf.yml

    - name: Stop jails
      tags: stop
      ansible.builtin.import_tasks: pb-iocage-template/stop.yml

    - name: Convert jails to templates
      tags: template
      ansible.builtin.import_tasks: pb-iocage-template/template.yml

Playbook output

(env) > ansible-playbook pb-iocage-template.yml -i iocage-hosts.ini -e debug=true

  ...

List templates at iocage_01

[iocage_01]# iocage list -lt
+------+----------------+------+-------+----------+-----------------+---------------------+-----+----------+----------+
| JID  |      NAME      | BOOT | STATE |   TYPE   |     RELEASE     |         IP4         | IP6 | TEMPLATE | BASEJAIL |
+======+================+======+=======+==========+=================+=====================+=====+==========+==========+
| None | ansible_client | off  | down  | template | 13.4-RELEASE-p2 | vnet0|10.1.0.198/24 | -   | -        | no       |
+------+----------------+------+-------+----------+-----------------+---------------------+-----+----------+----------+

List 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.1-RELEASE-p6 | em0|10.1.0.199/24 | -   | -        | no       |
+------+----------------+------+-------+----------+-----------------+-------------------+-----+----------+----------+

Playbook pb-iocage-ansible-clients.yml

- name: Create and start jails. Optionally stop and destroy jails.
  hosts: iocage
  environment: "{{ freebsd_iocage_runner_env | d({}) }}"

  vars:

    debug: false
    debug2: false
    dry_run: false

    swarm: false
    swarm_destroy: false
    clone: false
    clone_destroy: false

    _tags: "{{ dict(iocage_jails.keys()
                    | zip(iocage_jails.values()
                          | map(attribute='properties.notes')
                          | map('split')
                          | map('map', 'split', '=')
                          | map('community.general.dict'))) }}"
    _started: "{{ iocage_jails | dict2items
                               | selectattr('value.state', 'eq', 'up')
                               | map(attribute='key') }}"

  tasks:

    - name: Create and start swarms
      when: swarm | bool
      tags: swarm
      ansible.builtin.import_tasks: pb-iocage-ansible-clients/swarm.yml

    - name: Stop and destroy swarms
      when: swarm_destroy | bool
      tags: swarm_destroy
      ansible.builtin.import_tasks: pb-iocage-ansible-clients/swarm_destroy.yml

    - name: Create and start clones
      when: clone | bool
      tags: clone
      ansible.builtin.import_tasks: pb-iocage-ansible-clients/clone.yml

    - name: Stop and destroy clones
      when: clone_destroy | bool
      tags: clone_destroy
      ansible.builtin.import_tasks: pb-iocage-ansible-clients/clone_destroy.yml

    - name: Display iocage_jails
      when: debug | bool or debug2 | bool
      tags: list
      ansible.builtin.import_tasks: pb-iocage-ansible-clients/list.yml

Clone jails

(env) > ansible-playbook pb-iocage-ansible-clients.yml -i iocage-hosts.ini \
                                                       -t clone \
						       -e clone=true

PLAY [Create and start jails. Optionally stop and destroy jails.] ******************************

TASK [Create clones from template] *************************************************************
changed: [iocage_01] => (item=test_101 ansible_client)
changed: [iocage_01] => (item=test_102 ansible_client)
changed: [iocage_02] => (item=test_111 ansible_client)
changed: [iocage_01] => (item=test_103 ansible_client)
changed: [iocage_02] => (item=test_112 ansible_client)
changed: [iocage_02] => (item=test_113 ansible_client)

TASK [Debug clones from template debug2=false] *************************************************
skipping: [iocage_01]
skipping: [iocage_02]

TASK [Start clones] ****************************************************************************
changed: [iocage_01] => (item=test_101)
changed: [iocage_02] => (item=test_111)
changed: [iocage_01] => (item=test_102)
changed: [iocage_01] => (item=test_103)
changed: [iocage_02] => (item=test_112)
changed: [iocage_02] => (item=test_113)

TASK [Debug start clones debug2=false] *********************************************************
skipping: [iocage_01]
skipping: [iocage_02]

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

List jails

(env) > ansible-playbook pb-iocage-ansible-clients.yml -i iocage-hosts.ini \
                                                       -t list \
						       -e debug=true

PLAY [Create and start jails. Optionally stop and destroy jails.] ******************************

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

TASK [Display iocage_jails dictionary debug2=False] ********************************************
skipping: [iocage_01]
skipping: [iocage_02]

TASK [Display iocage_jails keys debug=true] ****************************************************
ok: [iocage_01] => 
    iocage_jails.keys():
    - test_101
    - test_102
    - test_103
ok: [iocage_02] => 
    iocage_jails.keys():
    - test_111
    - test_112
    - test_113

PLAY RECAP *************************************************************************************
iocage_01: ok=2    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0
iocage_02: ok=2    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

List jails at iocage_01

[iocage_01]# iocage list -l
+-----+----------+------+-------+------+-----------------+---------------------+-----+----------------+----------+
| JID |   NAME   | BOOT | STATE | TYPE |     RELEASE     |         IP4         | IP6 |    TEMPLATE    | BASEJAIL |
+=====+==========+======+=======+======+=================+=====================+=====+================+==========+
| 134 | test_101 | off  | up    | jail | 13.4-RELEASE-p2 | vnet0|10.1.0.101/24 | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+---------------------+-----+----------------+----------+
| 135 | test_102 | off  | up    | jail | 13.4-RELEASE-p2 | vnet0|10.1.0.102/24 | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+---------------------+-----+----------------+----------+
| 136 | test_103 | off  | up    | jail | 13.4-RELEASE-p2 | vnet0|10.1.0.103/24 | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+---------------------+-----+----------------+----------+

List jails at iocage_02

[iocage_02]# iocage list -l
+-----+----------+------+-------+------+-----------------+-------------------+-----+----------------+----------+
| JID |   NAME   | BOOT | STATE | TYPE |     RELEASE     |        IP4        | IP6 |    TEMPLATE    | BASEJAIL |
+=====+==========+======+=======+======+=================+===================+=====+================+==========+
| 170 | test_111 | off  | up    | jail | 14.1-RELEASE-p6 | em0|10.1.0.111/24 | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+-------------------+-----+----------------+----------+
| 171 | test_112 | off  | up    | jail | 14.1-RELEASE-p6 | em0|10.1.0.112/24 | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+-------------------+-----+----------------+----------+
| 172 | test_113 | off  | up    | jail | 14.1-RELEASE-p6 | em0|10.1.0.113/24 | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+-------------------+-----+----------------+----------+

Inventory hosts/01_iocage.yml

plugin: vbotka.freebsd.iocage
host: 10.1.0.18
user: admin
get_properties: true
compose:
  ansible_host: iocage_ip4
groups:
  test_01: inventory_hostname.startswith('test')

Inventory hosts/02_iocage.yml

plugin: vbotka.freebsd.iocage
host: 10.1.0.73
user: admin
get_properties: true
env:
  CRYPTOGRAPHY_OPENSSL_NO_LEGACY: 1
compose:
  ansible_host: iocage_ip4
groups:
  test_02: inventory_hostname.startswith('test')

Inventory hosts/03_constructed.yml

plugin: ansible.builtin.constructed
groups:
    test: inventory_hostname.startswith('test')

Display inventory

(env) > ansible-inventory -i hosts --graph
@all:
  |--@ungrouped:
  |--@test_01:
  |  |--test_101
  |  |--test_102
  |  |--test_103
  |--@test_02:
  |  |--test_111
  |  |--test_112
  |  |--test_113
  |--@test:
  |  |--test_101
  |  |--test_102
  |  |--test_103
  |  |--test_111
  |  |--test_112
  |  |--test_113

Playbook pb-test-01.yml

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

  vars:

    ansible_python_interpreter: auto_silent
    
  tasks:

    - ansible.builtin.command: hostname
      register: out_host
    
    - ansible.builtin.debug:
        msg: >
          {{ out_host.stdout }}
          {{ iocage_properties.host_hostuuid }}
          {{ iocage_properties.hostid }}
          {{ discovered_interpreter_python }}

    - ansible.builtin.command: whoami
      register: out_user

    - ansible.builtin.command: whoami
      register: out_root
      become: true
    
    - ansible.builtin.debug:
        msg: >
          {{ out_host.stdout }}
          {{ out_user.stdout }}
          {{ out_root.stdout }}

Playbook output

(env) > ansible-playbook pb-test-01.yml -i hosts

PLAY [Connect to the group test.] *************************************************************

TASK [Gathering Facts] ************************************************************************
ok: [test_103]
ok: [test_101]
ok: [test_112]
ok: [test_102]
ok: [test_111]
ok: [test_113]

TASK [ansible.builtin.command] ****************************************************************
changed: [test_102]
changed: [test_103]
changed: [test_101]
changed: [test_112]
changed: [test_111]
changed: [test_113]

TASK [ansible.builtin.debug] ******************************************************************
ok: [test_101] => 
  msg: |-
    test-101 test_101 34333834-3734-5a43-3331-313342464631 /usr/local/bin/python3.9
ok: [test_102] => 
  msg: |-
    test-102 test_102 34333834-3734-5a43-3331-313342464631 /usr/local/bin/python3.9
ok: [test_103] => 
  msg: |-
    test-103 test_103 34333834-3734-5a43-3331-313342464631 /usr/local/bin/python3.9
ok: [test_111] => 
  msg: |-
    test-111 test_111 ea2ba7d1-4fcd-f13f-82e4-8b32c0a03403 /usr/local/bin/python3.11
ok: [test_112] => 
  msg: |-
    test-112 test_112 ea2ba7d1-4fcd-f13f-82e4-8b32c0a03403 /usr/local/bin/python3.11
ok: [test_113] => 
  msg: |-
    test-113 test_113 ea2ba7d1-4fcd-f13f-82e4-8b32c0a03403 /usr/local/bin/python3.11

TASK [ansible.builtin.command] ****************************************************************
changed: [test_101]
changed: [test_111]
changed: [test_112]
changed: [test_103]
changed: [test_102]
changed: [test_113]

TASK [ansible.builtin.command] ****************************************************************
changed: [test_101]
changed: [test_103]
changed: [test_102]
changed: [test_111]
changed: [test_112]
changed: [test_113]

TASK [ansible.builtin.debug] ******************************************************************
ok: [test_101] => 
  msg: |-
    test-101 admin root
ok: [test_102] => 
  msg: |-
    test-102 admin root
ok: [test_103] => 
  msg: |-
    test-103 admin root
ok: [test_111] => 
  msg: |-
    test-111 admin root
ok: [test_112] => 
  msg: |-
    test-112 admin root
ok: [test_113] => 
  msg: |-
    test-113 admin root

PLAY RECAP ************************************************************************************
test_101: ok=6    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
test_102: ok=6    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
test_103: ok=6    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
test_111: ok=6    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
test_112: ok=6    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
test_113: ok=6    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Hint

The below command stops and destroys the cloned jails

ansible-playbook pb-iocage-ansible-clients.yml -i iocage-hosts.ini \
                                               -t clone_destroy \
                                               -e clone_destroy=true