initial commit
This commit is contained in:
118
roles/proxmox-lxc-provision/README.md
Executable file
118
roles/proxmox-lxc-provision/README.md
Executable file
@@ -0,0 +1,118 @@
|
||||
# Ansible Role: proxmox-lxc-provision
|
||||
|
||||
## Description
|
||||
|
||||
This Ansible role manages the provision of LXC containers and templates on a Proxmox host. By default the role will create and start an LXC container. It allows creating a new container from a [container image](https://pve.proxmox.com/wiki/Linux_Container#pct_container_images) or cloning an existing container or template container.
|
||||
|
||||
It also includes tasks which may be used individually:
|
||||
- `clone.yaml`: Creates a new LXC container by cloning another container or template
|
||||
- `convert.yaml`: Converts an LXC container to a template
|
||||
- `create.yaml`: Creates a new LXC container
|
||||
- `delete.yaml`: Deletes an LXC container given its vmid or hostname
|
||||
- `start.yaml`: Starts an LXC container
|
||||
- `stop.yaml`: Stops an LXC container
|
||||
- `update.yaml`: Updates an existing LXC container
|
||||
- `wait.yaml`: Waits for SSH to be available on the container
|
||||
|
||||
## Requirements
|
||||
|
||||
- Ansible 2.9 or higher
|
||||
- Python 3.6 or higher
|
||||
- requests
|
||||
- proxmoxer
|
||||
- passlib
|
||||
|
||||
## Role Variables
|
||||
|
||||
### Required Variables
|
||||
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `os_template` | The OS template to create the LXC from. Mutually exclusive with `ct_id`| `local:vztmpl/debian-12_amd64.tar.zst` |
|
||||
| `ct_id` | The vmid of the container or template container to clone the LXC from. Mutually exclusive with `os_template` | `201` |
|
||||
|
||||
### Required Proxmox API Authentication Variables
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `proxmox_api_user` | The username for Proxmox authentication, typically in format `username@realm` | `ansible@pve` |
|
||||
| `proxmox_api_token_id` | The API token ID used for authentication | `token` |
|
||||
| `proxmox_api_token_secret` | The secret key associated with the API token | `xxx-yyy-zzz` (should be stored securely) |
|
||||
| `proxmox_api_host` | The IP address or hostname of the Proxmox server | `192.168.1.10` |
|
||||
| `proxmox_api_port` | The port on which the Proxmox API is listening | `8006` |
|
||||
| `proxmox_node` | The name of the Proxmox node to target | `server1` |
|
||||
| `proxmox_api_validate_certs` | Whether to validate SSL certificates (set to false for self-signed certs) | `false` |
|
||||
|
||||
|
||||
### Optional Variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `clone_type` | Only use with `ct_id`. Supports `full` and `linked` clones. | `full` |
|
||||
| `container_storage` | Target storage for the container | `local-zfs` |
|
||||
| `disk` | The target storage and storage size | `local-zfs:16` |
|
||||
| `container_password` | The password for the root account | `password123` |
|
||||
| `container_cores` | The number of CPU cores | `4` |
|
||||
| `container_memory` | Memory size in MB for container | `2048` |
|
||||
| `swap_memory` | Swap memory size in MB | `2048` |
|
||||
| `container_ipv4` | The IPv4 address | `dhcp` |
|
||||
| `container_ipv6` | The IPv6 address | `auto` |
|
||||
| `container_pubkey_file` | The SSH public key for authentication to root user | Creates a temp key in `/tmp` |
|
||||
| `container_features` | List of additional container features | `- nesting=1` |
|
||||
|
||||
|
||||
## Example Playbook
|
||||
*Assuming Proxmox authentication variables are set*
|
||||
#### Creating a new LXC
|
||||
```yaml
|
||||
- name: Create and start an LXC container
|
||||
hosts: localhost
|
||||
connection: local
|
||||
vars:
|
||||
- container_hostname: new-debian-container
|
||||
- os_template: "local:vztmpl/debian-12_amd64.tar.zst"
|
||||
- container_ipv4: "10.0.0.99"
|
||||
roles:
|
||||
- role: proxmox-provision
|
||||
```
|
||||
|
||||
#### Creating a new LXC by cloning an existing container with vmid 200
|
||||
```yaml
|
||||
- name: Create and start an LXC container
|
||||
hosts: localhost
|
||||
connection: local
|
||||
vars:
|
||||
- container_hostname: new-debian-container
|
||||
- ct_id: 200
|
||||
- container_ipv4: "10.0.0.99"
|
||||
roles:
|
||||
- role: proxmox-lxc-provision
|
||||
```
|
||||
|
||||
|
||||
#### Creating an LXC Container and Converting it to a Template
|
||||
```yaml
|
||||
---
|
||||
- name: Create and start an LXC container
|
||||
hosts: localhost
|
||||
connection: local
|
||||
vars:
|
||||
- container_hostname: "{{ container_hostname }}"
|
||||
- os_template: "local:vztmpl/debian-12_amd64.tar.zst"
|
||||
- container_ipv4: "10.0.0.99"
|
||||
roles:
|
||||
- role: proxmox-lxc-provision
|
||||
|
||||
# Run configuration tasks on the container
|
||||
# ...
|
||||
|
||||
- name: Convert the created container to a template
|
||||
hosts: localhost
|
||||
vars:
|
||||
container_hostname: "{{ container_hostname }}"
|
||||
tasks:
|
||||
- include_role:
|
||||
name: proxmox-lxc-provision
|
||||
tasks_from: convert
|
||||
```
|
||||
20
roles/proxmox-lxc-provision/defaults/main.yaml
Executable file
20
roles/proxmox-lxc-provision/defaults/main.yaml
Executable file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
container_template: "local:vztmpl/debian-12-standard_12.12-1_amd64.tar.zst"
|
||||
container_cores: 4
|
||||
container_memory: 2048
|
||||
container_swap: 2048
|
||||
container_storage: local-zfs
|
||||
container_size: 16
|
||||
container_disk: "{{ container_storage }}:{{ container_size }}"
|
||||
container_ipv4: dhcp
|
||||
container_ipv6: auto
|
||||
container_nameserver: 10.0.0.7
|
||||
container_gateway: 10.0.0.1
|
||||
container_pubkey_file: "~/.ssh/id_ed25519.pub"
|
||||
container_unprivileged: true
|
||||
container_features:
|
||||
- nesting=1
|
||||
container_nvidia_gpu_mount: false
|
||||
container_tags: ["ansible-managed"]
|
||||
clone_type: full
|
||||
container_start: true
|
||||
42
roles/proxmox-lxc-provision/tasks/clone.yaml
Executable file
42
roles/proxmox-lxc-provision/tasks/clone.yaml
Executable file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
- name: Create a full clone of the container
|
||||
community.general.proxmox:
|
||||
api_user: "{{ proxmox_api_user }}"
|
||||
api_token_id: "{{ proxmox_api_token_id }}"
|
||||
api_token_secret: "{{ proxmox_api_token_secret }}"
|
||||
api_host: "{{ proxmox_api_host }}"
|
||||
node: "{{ proxmox_node }}"
|
||||
|
||||
vmid: "{{ container_vmid | default(0) }}"
|
||||
clone: "{{ clone_from }}"
|
||||
clone_type: "{{ clone_type }}"
|
||||
hostname: "{{ container_hostname }}"
|
||||
storage: "{{ container_storage }}"
|
||||
register: clone_result
|
||||
|
||||
- name: Debug container_mounts
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "container_mounts: {{ container_mounts }}"
|
||||
- "Type: {{ container_mounts | type_debug }}"
|
||||
- "Defined: {{ container_mounts is defined }}"
|
||||
- "Length: {{ container_mounts | length }}"
|
||||
|
||||
- name: Add bind mounts via pct
|
||||
become: yes
|
||||
ansible.builtin.shell: |
|
||||
pct set {{ clone_result.vmid | default(container_vmid) }} {% for key, value in container_mounts.items() %}-{{ key }} {{ value }} {% endfor %}
|
||||
delegate_to: "{{ proxmox_api_host }}"
|
||||
when: container_mounts is defined
|
||||
|
||||
- name: Resize rootfs after clone
|
||||
ansible.builtin.command:
|
||||
cmd: "pct resize {{ clone_result.vmid }} rootfs {{ container_size }}G"
|
||||
delegate_to: "{{ proxmox_api_host }}"
|
||||
become: yes
|
||||
register: resize_result
|
||||
changed_when: resize_result.rc == 0 and 'already at specified size' not in resize_result.stderr
|
||||
failed_when:
|
||||
- resize_result.rc != 0
|
||||
- "'already at specified size' not in resize_result.stderr"
|
||||
when: container_size is defined
|
||||
12
roles/proxmox-lxc-provision/tasks/convert.yaml
Executable file
12
roles/proxmox-lxc-provision/tasks/convert.yaml
Executable file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
- ansible.builtin.include_tasks: stop.yaml
|
||||
- name: Convert container to template
|
||||
community.general.proxmox:
|
||||
api_user: "{{ proxmox_api_user }}"
|
||||
api_token_id: "{{ proxmox_api_token_id }}"
|
||||
api_token_secret: "{{ proxmox_api_token_secret }}"
|
||||
api_host: "{{ proxmox_api_host }}"
|
||||
node: server
|
||||
|
||||
state: template
|
||||
hostname: "{{ container_hostname }}"
|
||||
32
roles/proxmox-lxc-provision/tasks/create.yaml
Executable file
32
roles/proxmox-lxc-provision/tasks/create.yaml
Executable file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
- name: Create an LXC container
|
||||
community.general.proxmox:
|
||||
api_host: "{{ proxmox_api_host }}"
|
||||
api_port: "{{ proxmox_api_port }}"
|
||||
api_user: "{{ proxmox_api_user }}"
|
||||
api_token_id: "{{ proxmox_api_token_id }}"
|
||||
api_token_secret: "{{ proxmox_api_token_secret }}"
|
||||
validate_certs: "{{ proxmox_api_validate_certs }}"
|
||||
|
||||
node: "{{ proxmox_node }}"
|
||||
vmid: "{{ container_vmid | default(omit) }}"
|
||||
hostname: "{{ container_hostname }}"
|
||||
password: "{{ container_password }}"
|
||||
ostemplate: "{{ container_template }}"
|
||||
cores: "{{ container_cores }}"
|
||||
memory: "{{ container_memory }}"
|
||||
swap: "{{ container_swap }}"
|
||||
disk: "{{ container_disk }}"
|
||||
mounts: "{{ container_mounts | default(omit) }}"
|
||||
netif: >
|
||||
{"net0": "name=eth0,gw={{ container_gateway }},ip={{ container_ipv4 }},ip6={{ container_ipv6 | default(omit) }},bridge=vmbr0"}
|
||||
pubkey: "{{ lookup('file', container_pubkey_file) | default(omit) }}"
|
||||
onboot: "{{ container_onboot | default(false) }}"
|
||||
startup: "{{ container_startup | default(omit) }}"
|
||||
unprivileged: "{{ container_unprivileged | default(true) }}"
|
||||
features: "{{ container_features | default(omit) }}"
|
||||
timezone: "{{ container_timezone | default(omit) }}"
|
||||
nameserver: "{{ container_nameserver | default(omit) }}"
|
||||
state: present
|
||||
tags: "{{ container_tags | default(omit) }}"
|
||||
register: container_result
|
||||
18
roles/proxmox-lxc-provision/tasks/delete.yaml
Executable file
18
roles/proxmox-lxc-provision/tasks/delete.yaml
Executable file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
- ansible.builtin.include_tasks: stop.yaml
|
||||
- name: Delete a container
|
||||
community.general.proxmox:
|
||||
api_host: "{{ proxmox_api_host }}"
|
||||
api_port: "{{ proxmox_api_port }}"
|
||||
api_user: "{{ proxmox_api_user }}"
|
||||
api_token_id: "{{ proxmox_api_token_id }}"
|
||||
api_token_secret: "{{ proxmox_api_token_secret }}"
|
||||
validate_certs: "{{ proxmox_api_validate_certs }}"
|
||||
|
||||
vmid: "{{ container_vmid | default(omit) }}"
|
||||
hostname: "{{ container_hostname | default(omit) }}"
|
||||
state: absent
|
||||
register: delete_result
|
||||
failed_when: |
|
||||
delete_result.failed and
|
||||
('does not exist' not in delete_result.msg)
|
||||
43
roles/proxmox-lxc-provision/tasks/edit-config.yaml
Executable file
43
roles/proxmox-lxc-provision/tasks/edit-config.yaml
Executable file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
|
||||
- name: Remove all existing ID mappings
|
||||
lineinfile:
|
||||
path: "/etc/pve/lxc/{{ container_vmid }}.conf"
|
||||
regexp: '^lxc\.idmap:'
|
||||
state: absent
|
||||
when: container_id_mappings is defined
|
||||
|
||||
- name: Add ID mappings
|
||||
blockinfile:
|
||||
path: "/etc/pve/lxc/{{ container_vmid }}.conf"
|
||||
block: "{{ container_id_mappings }}"
|
||||
insertafter: EOF
|
||||
when: container_id_mappings is defined
|
||||
|
||||
- name: Remove existing GPU configuration
|
||||
lineinfile:
|
||||
path: "/etc/pve/lxc/{{ container_vmid }}.conf"
|
||||
regexp: "{{ item }}"
|
||||
state: absent
|
||||
loop:
|
||||
- '^lxc\.cgroup2\.devices\.allow: c {{ gpu_device_id }}:\* rwm'
|
||||
- '^lxc\.cgroup2\.devices\.allow: c {{ uvm_device_id }}:\* rwm'
|
||||
- '^lxc\.mount\.entry: /dev/nvidia0'
|
||||
- '^lxc\.mount\.entry: /dev/nvidiactl'
|
||||
- '^lxc\.mount\.entry: /dev/nvidia-uvm '
|
||||
- '^lxc\.mount\.entry: /dev/nvidia-uvm-tools'
|
||||
when: container_nvidia_gpu_mount
|
||||
|
||||
- name: Add GPU device for passthrough
|
||||
blockinfile:
|
||||
path: /etc/pve/lxc/{{ container_vmid }}.conf
|
||||
block: |
|
||||
lxc.cgroup2.devices.allow: c {{ gpu_device_id }}:* rwm
|
||||
lxc.cgroup2.devices.allow: c {{ uvm_device_id }}:* rwm
|
||||
lxc.mount.entry: /dev/nvidia0 dev/nvidia0 none bind,optional,create=file
|
||||
lxc.mount.entry: /dev/nvidiactl dev/nvidiactl none bind,optional,create=file
|
||||
lxc.mount.entry: /dev/nvidia-uvm dev/nvidia-uvm none bind,optional,create=file
|
||||
lxc.mount.entry: /dev/nvidia-uvm-tools dev/nvidia-uvm-tools none bind,optional,create=file
|
||||
when: container_nvidia_gpu_mount
|
||||
|
||||
|
||||
38
roles/proxmox-lxc-provision/tasks/main.yaml
Executable file
38
roles/proxmox-lxc-provision/tasks/main.yaml
Executable file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
- name: Container source must be defined (clone_from or container_template)
|
||||
ansible.builtin.fail:
|
||||
msg: "Neither clone_from or container_template are defined"
|
||||
when: clone_from is undefined and container_template is undefined
|
||||
|
||||
- name: Clone container from another container or template, then update
|
||||
when: clone_from is defined
|
||||
block:
|
||||
- name: Clone from template
|
||||
ansible.builtin.include_tasks: clone.yaml
|
||||
register: clone_result
|
||||
|
||||
- name: Update container
|
||||
ansible.builtin.include_tasks: update.yaml
|
||||
vars:
|
||||
container_vmid: "{{ clone_result.vmid }}"
|
||||
register: container_result
|
||||
|
||||
- name: Create the new container
|
||||
ansible.builtin.include_tasks: create.yaml
|
||||
when: container_template is defined and clone_from is undefined
|
||||
|
||||
- name: Start the created container and wait for ssh
|
||||
vars:
|
||||
container_vmid: "{{ container_result.vmid }}"
|
||||
ansible.builtin.include_tasks: "{{ item }}"
|
||||
loop:
|
||||
- start.yaml
|
||||
- wait.yaml
|
||||
when: container_start
|
||||
|
||||
- name: Post clone updates
|
||||
when: clone_from is defined
|
||||
delegate_to: "{{ container_hostname }}"
|
||||
block:
|
||||
- name: Include post-clone tasks
|
||||
ansible.builtin.include_tasks: post-clone.yaml
|
||||
27
roles/proxmox-lxc-provision/tasks/post-clone.yaml
Executable file
27
roles/proxmox-lxc-provision/tasks/post-clone.yaml
Executable file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
- name: Change root password
|
||||
ansible.builtin.user:
|
||||
name: root
|
||||
password: "{{ container_password | password_hash('sha512') }}"
|
||||
update_password: always
|
||||
when: container_password is defined
|
||||
|
||||
- name: Change admin password
|
||||
ansible.builtin.user:
|
||||
name: admin
|
||||
password: "{{ password | password_hash('sha512') }}"
|
||||
update_password: always
|
||||
when: password is defined
|
||||
|
||||
- name: Regenerate SSH host keys
|
||||
ansible.builtin.include_role:
|
||||
name: system-setup
|
||||
tasks_from: ssh
|
||||
vars:
|
||||
regenerate_ssh_keys: true
|
||||
|
||||
- name: Remove previous entry from known hosts
|
||||
ansible.builtin.known_hosts:
|
||||
name: "{{ hostvars[container_hostname]['ansible_host'] }}"
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
17
roles/proxmox-lxc-provision/tasks/start.yaml
Executable file
17
roles/proxmox-lxc-provision/tasks/start.yaml
Executable file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
- name: Start the LXC container
|
||||
community.general.proxmox:
|
||||
api_host: "{{ proxmox_api_host }}"
|
||||
api_port: "{{ proxmox_api_port }}"
|
||||
api_user: "{{ proxmox_api_user }}"
|
||||
api_token_id: "{{ proxmox_api_token_id }}"
|
||||
api_token_secret: "{{ proxmox_api_token_secret }}"
|
||||
validate_certs: "{{ proxmox_api_validate_certs }}"
|
||||
|
||||
vmid: "{{ container_result.vmid }}"
|
||||
state: started
|
||||
register: start_result
|
||||
retries: 3
|
||||
delay: 5
|
||||
until: start_result is success
|
||||
failed_when: start_result.failed and ('already running' not in start_result.msg)
|
||||
16
roles/proxmox-lxc-provision/tasks/stop.yaml
Executable file
16
roles/proxmox-lxc-provision/tasks/stop.yaml
Executable file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
- name: Stop container if it is running
|
||||
community.general.proxmox:
|
||||
api_user: "{{ proxmox_api_user }}"
|
||||
api_token_id: "{{ proxmox_api_token_id }}"
|
||||
api_token_secret: "{{ proxmox_api_token_secret }}"
|
||||
api_host: "{{ proxmox_api_host }}"
|
||||
|
||||
vmid: "{{ container_vmid | default(omit) }}"
|
||||
hostname: "{{ container_hostname | default(omit) }}"
|
||||
state: "stopped"
|
||||
register: stop_result
|
||||
failed_when: |-
|
||||
stop_result.failed and
|
||||
('not running' not in stop_result.msg) and
|
||||
('does not exist' not in stop_result.msg)
|
||||
29
roles/proxmox-lxc-provision/tasks/update.yaml
Executable file
29
roles/proxmox-lxc-provision/tasks/update.yaml
Executable file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
- name: Update an LXC container
|
||||
community.general.proxmox:
|
||||
api_host: "{{ proxmox_api_host }}"
|
||||
api_port: "{{ proxmox_api_port }}"
|
||||
api_user: "{{ proxmox_api_user }}"
|
||||
api_token_id: "{{ proxmox_api_token_id }}"
|
||||
api_token_secret: "{{ proxmox_api_token_secret }}"
|
||||
validate_certs: "{{ proxmox_api_validate_certs }}"
|
||||
node: "{{ proxmox_node }}"
|
||||
|
||||
vmid: "{{ container_vmid }}"
|
||||
hostname: "{{ container_hostname }}"
|
||||
password: "{{ container_password | default(omit) }}" # Updating password does not work
|
||||
cores: "{{ container_cores }}"
|
||||
memory: "{{ container_memory }}"
|
||||
swap: "{{ container_swap }}"
|
||||
disk: "{{ container_disk }}"
|
||||
netif: '{"net0": "name=eth0,gw={{ container_gateway }},ip={{ container_ipv4 }},ip6={{ container_ipv6 | default(omit) }},bridge=vmbr0"}'
|
||||
pubkey: "{{ lookup('file', container_pubkey_file) | default(omit) }}"
|
||||
onboot: "{{ container_onboot | default(false) }}"
|
||||
startup: "{{ container_startup | default(omit) }}"
|
||||
features: "{{ container_features | default(omit) }}"
|
||||
timezone: "{{ container_timezone | default(omit) }}"
|
||||
nameserver: "{{ container_nameserver | default(omit) }}"
|
||||
state: present
|
||||
tags: "{{ container_tags | default(omit) }}"
|
||||
update: true
|
||||
register: container_result
|
||||
20
roles/proxmox-lxc-provision/tasks/wait.yaml
Executable file
20
roles/proxmox-lxc-provision/tasks/wait.yaml
Executable file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
- ansible.builtin.meta: refresh_inventory
|
||||
|
||||
- name: Wait a moment for container to be available in inventory
|
||||
ansible.builtin.pause:
|
||||
seconds: 2
|
||||
|
||||
- name: Debug - Check if container is in inventory
|
||||
ansible.builtin.debug:
|
||||
msg: "Container {{ container_hostname }} found with IP: {{ hostvars[container_hostname]['ansible_host'] | default('NOT FOUND') }}"
|
||||
|
||||
|
||||
- name: Wait for SSH to become available
|
||||
ansible.builtin.wait_for:
|
||||
host: "{{ hostvars[container_hostname]['ansible_host'] }}"
|
||||
port: 22
|
||||
delay: 3
|
||||
state: started
|
||||
register: ssh_wait_result
|
||||
until: ssh_wait_result is not failed
|
||||
Reference in New Issue
Block a user