diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg new file mode 100644 index 0000000..c491d26 --- /dev/null +++ b/ansible/ansible.cfg @@ -0,0 +1,3 @@ +[defaults] +remote_tmp = /tmp +inventory = inventory diff --git a/ansible/files/incus.servers.tpl b/ansible/files/incus.servers.tpl new file mode 100644 index 0000000..fa87357 --- /dev/null +++ b/ansible/files/incus.servers.tpl @@ -0,0 +1,5 @@ +{% for host in vars['ansible_play_hosts'] | sort %} +{% if hostvars[host]['incus_name'] == task_name and "cluster" in hostvars[host]['incus_roles'] %} +- {{ host }} +{% endif %} +{% endfor %} diff --git a/ansible/files/incus.sources.tpl b/ansible/files/incus.sources.tpl new file mode 100644 index 0000000..418f342 --- /dev/null +++ b/ansible/files/incus.sources.tpl @@ -0,0 +1,8 @@ +# Managed by Ansible, do not modify. +Enabled: yes +Types: deb +URIs: https://pkgs.zabbly.com/incus/{{ task_release }}/ +Suites: {{ ansible_distribution_release }} +Components: main +Architectures: {{ dpkg_architecture.stdout }} +Signed-By: /etc/apt/keyrings/ansible-zabbly.asc diff --git a/ansible/files/zabbly-key.asc b/ansible/files/zabbly-key.asc new file mode 100644 index 0000000..d7241ca --- /dev/null +++ b/ansible/files/zabbly-key.asc @@ -0,0 +1,41 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=8Uc6 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/ansible/inventory b/ansible/inventory new file mode 100644 index 0000000..040cf6c --- /dev/null +++ b/ansible/inventory @@ -0,0 +1 @@ +bob ansible_ssh_private_key_file=~/.ssh/keys/bob diff --git a/ansible/playbook.yaml b/ansible/playbook.yaml new file mode 100644 index 0000000..1dd7d4c --- /dev/null +++ b/ansible/playbook.yaml @@ -0,0 +1,324 @@ +--- +- name: Update system + hosts: all + tasks: + - name: Ensure aptitude installed + apt: + name: "aptitude" + state: present + tags: + - apt + become: true + + - name: Ensure OS upgraded + apt: + upgrade: dist + tags: + - apt + - update + - os + become: true + + - name: Check if reboot is necessary + register: reboot_required_file + stat: + path: /var/run/reboot-required + get_checksum: false + tags: + - os + - reboot + notify: reboot host + + - name: All packages updated + apt: + name: "*" + state: latest # noqa 403 + tags: + - apt + - update + - packages + become: true + + handlers: + - name: Reboot host + reboot: + msg: "Reboot initiated by Ansible" + connect_timeout: 5 + reboot_timeout: 600 + pre_reboot_delay: 0 + post_reboot_delay: 30 + test_command: whoami + become: true + when: reboot_required_file.stat.exists + +- name: Incus - Add package repository (apt) + hosts: all + gather_facts: true + gather_subset: + - "distribution_release" + vars: + task_release: "{{ incus_release | default('stable') }}" + task_roles: "{{ incus_roles | default(['standalone', 'ui']) }}" + any_errors_fatal: true + tasks: + - name: Check if distribution is supported + meta: end_play + when: 'ansible_distribution not in ("Ubuntu", "Debian")' + + - name: Create apt keyring path + file: + path: /etc/apt/keyrings/ + mode: 0755 + state: directory + when: 'task_roles|length > 0 and task_release != "distro"' + + - name: Add Zabbly repository key + copy: + src: files/zabbly-key.asc + dest: /etc/apt/keyrings/ansible-zabbly.asc + notify: Update apt + become: true + when: 'task_roles|length > 0 and task_release != "distro"' + + - name: Get DPKG architecture + shell: dpkg --print-architecture + register: dpkg_architecture + changed_when: false + check_mode: no + when: 'task_roles|length > 0 and task_release != "distro"' + + - name: Add Zabbly package source + template: + src: files/incus.sources.tpl + dest: /etc/apt/sources.list.d/ansible-zabbly-incus-{{ task_release }}.sources + notify: Update apt + become: true + when: 'task_roles|length > 0 and task_release != "distro"' + + handlers: + - name: Update apt + apt: + force_apt_get: yes + update_cache: yes + cache_valid_time: 0 + become: true + +- name: Incus - Install packages and bootstrap + hosts: all + gather_facts: true + gather_subset: + - "default_ipv4" + - "default_ipv6" + - "distribution_release" + vars: + task_init: "{{ incus_init | default('{}') }}" + task_ip_address: "{{ incus_ip_address | default(ansible_default_ipv6['address'] | default(ansible_default_ipv4['address'])) }}" + task_name: "{{ incus_name | default('') }}" + task_roles: "{{ incus_roles | default(['ui', 'standalone']) }}" + + task_ovn_northbound: "{{ lookup('template', '../files/ovn/ovn-central.servers.tpl') | from_yaml | map('regex_replace', '^(.*)$', 'ssl:[\\1]:6641') | join(',') }}" + task_servers: "{{ lookup('template', 'files/incus.servers.tpl') | from_yaml | sort }}" + any_errors_fatal: true + become: true + tasks: + - name: Install the Incus package (deb) + apt: + name: + - incus + install_recommends: no + state: present + register: install_deb + when: 'ansible_distribution in ("Debian", "Ubuntu") and task_roles | length > 0' + + - name: Install the Incus package (rpm) + ansible.builtin.package: + name: + - incus + state: present + register: install_rpm + when: 'ansible_distribution == "CentOS" and task_roles | length > 0' + + - name: Install the Incus UI package (deb) + apt: + name: + - incus-ui-canonical + install_recommends: no + state: present + when: 'ansible_distribution in ("Debian", "Ubuntu") and "ui" in task_roles' + + # - name: Install btrfs tools + # ansible.builtin.package: + # name: + # - btrfs-progs + # state: present + # when: "task_roles | length > 0 and 'btrfs' in task_init['storage'] | dict2items | json_query('[].value.driver')" + # + # - name: Install ceph tools + # ansible.builtin.package: + # name: + # - ceph-common + # state: present + # when: "task_roles | length > 0 and 'ceph' in task_init['storage'] | dict2items | json_query('[].value.driver')" + # + # - name: Install LVM tools + # ansible.builtin.package: + # name: + # - lvm2 + # state: present + # when: "task_roles | length > 0 and 'lvm' in task_init['storage'] | dict2items | json_query('[].value.driver')" + # + # - name: Install ZFS dependencies + # ansible.builtin.package: + # name: + # - zfs-dkms + # state: present + # when: "task_roles | length > 0 and 'zfs' in task_init['storage'] | dict2items | json_query('[].value.driver') and ansible_distribution == 'Debian'" + # + # - name: Install ZFS tools + # ansible.builtin.package: + # name: + # - zfsutils-linux + # state: present + # when: "task_roles | length > 0 and 'zfs' in task_init['storage'] | dict2items | json_query('[].value.driver')" + + - name: Set uid allocation + shell: + cmd: "usermod root --add-subuids 10000000-1009999999" + when: '(install_deb.changed or install_rpm.changed) and ansible_distribution == "CentOS"' + + - name: Set gid allocation + shell: + cmd: "usermod root --add-subgids 10000000-1009999999" + when: '(install_deb.changed or install_rpm.changed) and ansible_distribution == "CentOS"' + + - name: Enable incus socket unit + systemd: + enabled: true + name: incus.socket + state: started + when: 'install_deb.changed or install_rpm.changed' + + - name: Enable incus service unit + systemd: + enabled: true + name: incus.service + state: started + when: 'install_deb.changed or install_rpm.changed' + + - name: Enable incus startup unit + systemd: + enabled: true + name: incus-startup.service + state: started + when: 'install_deb.changed or install_rpm.changed' + + - name: Set client listen address + shell: + cmd: "incus --force-local config set core.https_address {{ task_ip_address }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + + - name: Set cluster listen address + shell: + cmd: "incus --force-local config set cluster.https_address {{ task_ip_address }}" + when: '(install_deb.changed or install_rpm.changed) and "cluster" in task_roles and task_servers[0] == inventory_hostname' + + # - name: Set OVN NorthBound database + # shell: + # cmd: "incus --force-local config set network.ovn.northbound_connection={{ task_ovn_northbound }} network.ovn.client_cert=\"{{ lookup('file', '../data/ovn/'+ovn_name+'/'+inventory_hostname+'.crt') }}\" network.ovn.client_key=\"{{ lookup('file', '../data/ovn/'+ovn_name+'/'+inventory_hostname+'.key') }}\" network.ovn.ca_cert=\"{{ lookup('file', '../data/ovn/'+ovn_name+'/ca.crt') }}\"" + # notify: Restart Incus + # when: '(install_deb.changed or install_rpm.changed) and task_ovn_northbound and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + + - name: Add networks + shell: + cmd: "incus network create {{ item.key }} --type={{ item.value.type }}{% for k in item.value.local_config | default([]) %} {{ k }}={{ item.value.local_config[k] }}{% endfor %}{% for k in item.value.config | default([]) %} {{ k }}={{ item.value.config[k] }}{% endfor %}" + loop: "{{ task_init['network'] | dict2items }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + + - name: Set network description + shell: + cmd: "incus network set --property {{ item.key }} description=\"{{ item.value.description }}\"" + loop: "{{ task_init['network'] | dict2items }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname)) and item.value.description | default(None)' + + - name: Add storage pools + shell: + cmd: "incus storage create {{ item.key }} {{ item.value.driver }}{% for k in item.value.local_config | default([]) %} {{ k }}={{ item.value.local_config[k] }}{% endfor %}{% for k in item.value.config | default([]) %} {{ k }}={{ item.value.config[k] }}{% endfor %}" + loop: "{{ task_init['storage'] | dict2items }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + + - name: Set storage pool description + shell: + cmd: "incus storage set --property {{ item.key }} description=\"{{ item.value.description }}\"" + loop: "{{ task_init['storage'] | dict2items }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname)) and item.value.description | default(None)' + + - name: Add storage pool to default profile + shell: + cmd: "incus profile device add default root disk path=/ pool={{ item }}" + loop: "{{ task_init['storage'] | dict2items | json_query('[?value.default].key') }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + + - name: Add network to default profile + shell: + cmd: "incus profile device add default eth0 nic network={{ item }} name=eth0" + loop: "{{ task_init['network'] | dict2items | json_query('[?value.default].key') }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + + - name: Bootstrap the cluster + shell: + cmd: "incus --force-local cluster enable {{ inventory_hostname }}" + when: '(install_deb.changed or install_rpm.changed) and "cluster" in task_roles and task_servers[0] == inventory_hostname' + + - name: Create join tokens + delegate_to: "{{ task_servers[0] }}" + shell: + cmd: "incus --force-local --quiet cluster add {{ inventory_hostname }}" + register: cluster_add + when: '(install_deb.changed or install_rpm.changed) and "cluster" in task_roles and task_servers[0] != inventory_hostname' + + - name: Wait 5s to avoid token use before valid + ansible.builtin.wait_for: + timeout: 5 + delegate_to: localhost + when: 'cluster_add.changed' + + - name: Join the cluster + throttle: 1 + shell: + cmd: "incus --force-local admin init --preseed" + stdin: |- + cluster: + enabled: true + cluster_address: "{{ task_ip_address }}" + cluster_token: "{{ cluster_add.stdout }}" + server_address: "{{ task_ip_address }}" + member_config: {% for pool in task_init.storage %}{% for key in task_init.storage[pool].local_config | default([]) %} + + - entity: storage-pool + name: {{ pool }} + key: {{ key }} + value: {{ task_init.storage[pool].local_config[key] }}{% endfor %}{% endfor %}{% for network in task_init.network %}{% for key in task_init.network[network].local_config | default([]) %} + + - entity: network + name: {{ network }} + key: {{ key }} + value: {{ task_init.network[network].local_config[key] }}{% endfor %}{% endfor %} + when: 'cluster_add.changed' + + - name: Apply additional configuration + shell: + cmd: "incus config set {{ item.key }}=\"{{ item.value }}\"" + loop: "{{ task_init['config'] | default({}) | dict2items }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + + - name: Load client certificates + shell: + cmd: "incus config trust add-certificate --name \"{{ item.key }}\" --type={{ item.value.type | default('client') }} -" + stdin: "{{ item.value.certificate }}" + loop: "{{ task_init['clients'] | default({}) | dict2items }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + handlers: + - name: Restart Incus + systemd: + name: incus.service + state: restarted