feat(proxmox): automatic vm creation

Signed-off-by: Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>
This commit is contained in:
Tuan-Dat Tran
2025-04-26 21:58:58 +02:00
parent 0e8e07ed3e
commit f2ea03bc01
27 changed files with 247 additions and 90 deletions

View File

@@ -0,0 +1,8 @@
$ANSIBLE_VAULT;1.1;AES256
33333937646463646566653162383830616434336437623065363665323739633331346266333763
3364663264306665626465666133666161626333323462650a353366303331303837316133326135
33623862333036633438343538633161643333663632303362396438316638626338663935353337
3532323337663864640a333765653732393937396561373361393762386565353266343537306161
62303539333837666365323630303836373065343437663433616664376432313135636266663764
36616132383330656165656264346231323039626131646432323935306233643866366439313962
353837396234643739346662316239356134

View File

@@ -2,21 +2,18 @@ proxmox_api_user: root
proxmox_api_host: 192.168.20.12
proxmox_api_password: "{{ vault.pve.aya01.root.sudo }}"
proxmox_vms:
vms:
- name: "test-vm-00"
node: "inko"
vmid: 950
cores: 2
cpu: "x86-64-v2-AES"
memory: 8192 # in MiB
net:
net0: "virtio,bridge=vmbr0,firewall=1"
scsi:
scsi0: "proxmox:64,format=qcow2"
scsihw: "virtio-scsi-single"
ostype: "l26"
sshkeys: "{{ pubkey }}"
boot_image: "{{ proxmox_cloud_init_images.ubuntu.name }}"
ciuser: "{{ user }}"
sshkeys: "{{ pubkey }}"
disk_size: 32 # in Gb
proxmox_lxcs:
lxcs:
- name: "test-lxc-00"

View File

@@ -1,10 +1,10 @@
---
ansible_user: "root"
ansible_host: 192.168.20.12
ansible_port: 22
ansible_ssh_private_key_file: "{{ pk_path }}"
ansible_become_pass: "{{ vault.pve.aya01.root.sudo }}"
host:
hostname: "aya01"
ip: "{{ ansible_host }}"
# ansible_user: "root"
# ansible_host: 192.168.20.12
# ansible_port: 22
# ansible_ssh_private_key_file: "{{ pk_path }}"
# ansible_become_pass: "{{ vault.pve.aya01.root.sudo }}"
#
# host:
# hostname: "aya01"
# ip: "{{ ansible_host }}"

View File

@@ -5,7 +5,6 @@
# ansible_port: 22
# ansible_ssh_private_key_file: "{{ pk_path }}"
ansible_become_pass: "{{ vault.docker.host00.sudo }}"
host:
hostname: "docker-host00"
ip: "192.168.20.34"
# host:
# hostname: "docker-host00"
# ip: "192.168.20.34"

View File

@@ -5,7 +5,7 @@
# ansible_port: 22
# ansible_ssh_private_key_file: "{{ pk_path }}"
ansible_become_pass: "{{ vault.docker.host01.sudo }}"
host:
hostname: "docker-host01"
ip: "192.168.20.35"
#
# host:
# hostname: "docker-host01"
# ip: "192.168.20.35"

View File

@@ -5,7 +5,6 @@
# ansible_port: 22
# ansible_ssh_private_key_file: "{{ pk_path }}"
ansible_become_pass: "{{ vault.docker.host02.sudo }}"
host:
hostname: "docker-host02"
ip: "192.168.20.36"
# host:
# hostname: "docker-host02"
# ip: "192.168.20.36"

View File

@@ -4,7 +4,6 @@
# ansible_port: 22
# ansible_ssh_private_key_file: "{{ pk_path }}"
ansible_become_pass: "{{ vault.docker.lb.sudo }}"
host:
hostname: "docker-lb"
ip: "192.168.20.37"
# host:
# hostname: "docker-lb"
# ip: "192.168.20.37"

View File

@@ -1,10 +1,10 @@
---
ansible_user: "root"
ansible_host: 192.168.20.14
ansible_port: 22
ansible_ssh_private_key_file: "{{ pk_path }}"
ansible_become_pass: "{{ vault.pve.inko.root.sudo }}"
host:
hostname: "inko"
ip: "{{ ansible_host }}"
# ansible_user: "root"
# ansible_host: 192.168.20.14
# ansible_port: 22
# ansible_ssh_private_key_file: "{{ pk_path }}"
# ansible_become_pass: "{{ vault.pve.inko.root.sudo }}"
#
# host:
# hostname: "inko"
# ip: "{{ ansible_host }}"

View File

@@ -1,10 +1,10 @@
---
ansible_user: "root"
ansible_host: 192.168.20.28
ansible_port: 22
ansible_ssh_private_key_file: "{{ pk_path }}"
ansible_become_pass: "{{ vault.pve.lulu.root.sudo }}"
host:
hostname: "lulu"
ip: "{{ ansible_host }}"
# ansible_user: "root"
# ansible_host: 192.168.20.28
# ansible_port: 22
# ansible_ssh_private_key_file: "{{ pk_path }}"
# ansible_become_pass: "{{ vault.pve.lulu.root.sudo }}"
#
# host:
# hostname: "lulu"
# ip: "{{ ansible_host }}"

View File

@@ -4,9 +4,10 @@
gather_facts: true
vars_files:
- secrets.yml
vars:
is_localhost: "{{ inventory_hostname == '127.0.0.1' }}"
is_proxmox_node: "{{ 'proxmox_nodes' in group_names }}"
roles:
- role: proxmox
tags:
- proxmox
vars:
action: provision

View File

@@ -2,6 +2,9 @@
127.0.0.1 ansible_connection=local
[proxmox:children]
proxmox_nodes
[proxmox_nodes]
aya01
lulu
inko

View File

@@ -4,7 +4,7 @@ Description=PostgresExporter
[Service]
TimeoutStartSec=0
User={{ bin_name }}
ExecStart={{ bin_path }} --web.listen-address={{ host.ip }}:{{ bind_port }} {{ options }}
ExecStart={{ bin_path }} --web.listen-address={{ ansible_host }}:{{ bind_port }} {{ options }}
Environment="DATA_SOURCE_URI=localhost:5432/postgres?sslmode=disable"
Environment="DATA_SOURCE_USER={{ db.user }}"
Environment="DATA_SOURCE_PASS={{ db.password }}"

View File

@@ -0,0 +1,8 @@
---
- name: Prepare Localhost
ansible.builtin.include_tasks: ./01_setup_localhost.yml
when: is_localhost
- name: Prepare Localhost
ansible.builtin.include_tasks: ./05_setup_node.yml
when: is_proxmox_node

View File

@@ -0,0 +1,7 @@
---
- name: Install dependencies
ansible.builtin.apt:
name: "{{ item }}"
update_cache: true
state: present
loop: "{{ proxmox_localhost_dependencies }}"

View File

@@ -0,0 +1,7 @@
---
- name: Install dependencies
ansible.builtin.apt:
name: "{{ item }}"
update_cache: true
state: present
loop: "{{ proxmox_node_dependencies }}"

View File

@@ -23,7 +23,7 @@
- name: Update Vault data
ansible.builtin.include_tasks: 15_create_secret.yml
loop: "{{ proxmox_vms | map(attribute='name') }}"
loop: "{{ vms | map(attribute='name') }}"
loop_control:
loop_var: "vm_name"

View File

@@ -0,0 +1,6 @@
---
- name: Download Cloud Init Isos
ansible.builtin.include_tasks: 42_download_isos.yml
loop: "{{ proxmox_cloud_init_images | dict2items | map(attribute='value') }}"
loop_control:
loop_var: distro

View File

@@ -0,0 +1,12 @@
---
- name: Check if file exists
ansible.builtin.stat:
path: "{{ proxmox_dirs.isos }}/{{ distro.name }}"
register: image_stat
- name: Download image if missing
ansible.builtin.get_url:
url: "{{ distro.url }}"
dest: "{{ proxmox_dirs.isos }}/{{ distro.name }}"
mode: "0644"
when: not image_stat.stat.exists

View File

@@ -4,8 +4,14 @@
file: "{{ proxmox_vault_file }}"
name: vm_secrets
- name: Create vms
ansible.builtin.include_tasks: 55_create_vm.yml
loop: "{{ proxmox_vms }}"
- name: Destroy vms (Only during rapid testing)
ansible.builtin.include_tasks: 54_destroy_vm.yml
loop: "{{ vms }}"
loop_control:
loop_var: "vm"
- name: Create vms
ansible.builtin.include_tasks: 55_create_vm.yml
loop: "{{ vms }}"
loop_control:
loop_var: "vm"

View File

@@ -0,0 +1,30 @@
---
- name: Gather info about VM
community.general.proxmox_vm_info:
api_user: root@pam
api_password: "{{ vault.pve.aya01.root.sudo }}"
api_host: "192.168.20.12"
vmid: "{{ vm.vmid }}"
register: vm_info
- name: Stop VM
community.general.proxmox_kvm:
api_user: root@pam
api_password: "{{ vault.pve.aya01.root.sudo }}"
api_host: "192.168.20.12"
node: "{{ vm.node }}"
vmid: "{{ vm.vmid }}"
state: stopped
force: true
when: vm_info.proxmox_vms | length > 0
- name: Destroy VM
community.general.proxmox_kvm:
api_user: root@pam
api_password: "{{ vault.pve.aya01.root.sudo }}"
api_host: "192.168.20.12"
node: "{{ vm.node }}"
vmid: "{{ vm.vmid }}"
state: absent
force: true
when: vm_info.proxmox_vms | length > 0

View File

@@ -1,34 +1,94 @@
---
- name: Create VM
community.general.proxmox_kvm:
api_user: root@pam
api_password: "{{ vault.pve.aya01.root.sudo }}"
api_host: "192.168.20.12"
agent: true
name: "{{ vm.name }}"
vmid: "{{ vm.vmid }}"
node: "{{ vm.node }}"
cpu: "{{ vm.cpu }}"
cores: "{{ vm.cores }}"
memory: "{{ vm.memory }}"
net: "{{ vm.net }}"
scsi: "{{ vm.scsi }}"
scsihw: "{{ vm.scsihw }}"
ostype: "{{ vm.ostype }}"
sshkeys: "{{ vm.sshkeys }}"
scsihw: "virtio-scsi-pci"
ostype: "l26"
tags: "{{ proxmox_tags }}"
description: "Created via Ansible with cloud-init"
boot: "order=scsi0"
cpu: "x86-64-v2-AES"
ciuser: "{{ vm.ciuser }}"
cipassword: "{{ vm_secrets[proxmox_secrets_prefix + '_' + vm.name.replace('-', '_')] }}"
ide:
ide2: "proxmox:cloudinit,format=qcow2"
register: temp
ipconfig:
ipconfig0: "ip=dhcp"
sshkeys: "{{ vm.sshkeys }}"
register: proxmox_deploy_info
- name: Debug temp
ansible.builtin.debug:
msg: "{{ temp }}"
- name: Set mac
- name: Get MAC Address of new machine
ansible.builtin.set_fact:
mac: "{{ temp.mac }}"
mac_address: "{{ proxmox_deploy_info.mac.net0 }}"
- name: debug mac
- name: Import disk
ansible.builtin.shell: |
qm importdisk {{ vm.vmid }} {{ proxmox_dirs.isos }}/{{ vm.boot_image }} {{ proxmox_storage }}
delegate_to: "{{ vm.node }}"
- name: Attach disk and cloud-init
ansible.builtin.shell: |
qm set {{ vm.vmid }} --scsi0 {{ proxmox_storage }}:{{ vm.vmid }}/vm-{{ vm.vmid }}-disk-0.raw --ide2 {{ proxmox_storage }}:cloudinit --boot order=scsi0
delegate_to: "{{ vm.node }}"
- name: Resize scsi0 disk if needed
ansible.builtin.shell: |
qm resize {{ vm.vmid }} scsi0 {{ vm.disk_size }}G
delegate_to: "{{ vm.node }}"
- name: Start VM
community.general.proxmox_kvm:
api_user: root@pam
api_password: "{{ vault.pve.aya01.root.sudo }}"
api_host: "192.168.20.12"
node: "{{ vm.node }}"
vmid: "{{ vm.vmid }}"
state: started
- name: Wait for VM to appear on network
ansible.builtin.shell: |
nmap -sn -n -PR 192.168.20.0/24 | grep -B2 "{{ mac_address }}" | grep "Nmap scan report for"
register: vm_nmap_scan
retries: 30
delay: 5
until: vm_nmap_scan.stdout != ""
delegate_to: "{{ vm.node }}"
- name: Extract the IP address from Nmap output
ansible.builtin.set_fact:
vm_found_ip: "{{ vm_nmap_scan.stdout | regex_search('Nmap scan report for ([0-9\\.]+)', '\\1') | first }}"
- name: Debug IP address
ansible.builtin.debug:
msg: "{{ mac }}"
msg: "Found VM IP address: {{ vm_found_ip }}"
- name: Define SSH config block
ansible.builtin.set_fact:
ssh_entry: |
Host {{ vm.name }}
HostName {{ vm_found_ip }}
Port 22
User tudattr
IdentityFile /media/veracrypt1/genesis
ProxyJump {{ vm.node }}
- name: Append new VM to SSH config
ansible.builtin.blockinfile:
path: "{{ ansible_env.HOME }}/.ssh/config_homelab"
marker: "# {mark} HOMELAB VMS BLOCK"
block: |
{{ ssh_entry }}
- name: Add the new VM to the proxmox_nodes group in production.ini
ansible.builtin.lineinfile:
path: "../inventory.ini"
line: "{{ proxmox_inventory_entry }}"
insertafter: "[proxmox_nodes]"
state: present

View File

@@ -6,6 +6,6 @@
- name: Create vms
ansible.builtin.include_tasks: 65_create_container.yml
loop: "{{ proxmox_lxcs }}"
loop: "{{ lxcs }}"
loop_control:
loop_var: "container"

View File

@@ -1,6 +0,0 @@
---
- name: Download Debian Image
ansible.builtin.get_url:
url: "{{ proxmox_debian_image_url }}"
dest: "{{ proxmox_image_path }}"
mode: "0644"

View File

@@ -1,12 +1,18 @@
---
- name: Setup user
ansible.builtin.include_tasks: 01_setup_user.yml
- name: Prepare Machines
ansible.builtin.include_tasks: 00_setup_machines.yml
- name: Create VM vault
ansible.builtin.include_tasks: 10_create_secrets.yml
when: is_localhost
- name: Prime node for VM
ansible.builtin.include_tasks: 40_prepare_vm_creation.yml
when: is_proxmox_node
- name: Create VMs
ansible.builtin.include_tasks: 50_create_vms.yml
when: is_localhost
- name: Create LXC containers
ansible.builtin.include_tasks: 60_create_containers.yml
when: is_localhost

View File

@@ -1,10 +1,25 @@
author: tuan-dat.tran@tudattr.dev
creator: ansible
proxmox_author: tuan-dat.tran@tudattr.dev
proxmox_creator: ansible
proxmox_storage: proxmox
proxmox_vault_file: ../group_vars/proxmox/secrets_vm.yml
proxmox_secrets_prefix: secrets_vm
proxmox_debian_image_url: https://cdimage.debian.org/images/cloud/bookworm/20250316-2053/debian-12-genericcloud-amd64-20250316-2053.qcow2
proxmox_image_path: /opt/template/iso/
proxmox_cloud_init_images:
debian:
name: debian-12-genericcloud-amd64.qcow2
url: https://cdimage.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2
ubuntu:
name: noble-server-cloudimg-amd64.img
url: https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
proxmox_dirs:
isos: /opt/proxmox/template/iso/
proxmox_tags:
- "{{ creator }}"
- "{{ proxmox_creator }}"
proxmox_node_dependencies:
- libguestfs-tools
- nmap
proxmox_localhost_dependencies: []

View File

@@ -10,7 +10,7 @@
{% if http_port %}
{{ service.name }}.{{ domain }} {
{% for vm in service.vm %}
reverse_proxy {{ hostvars[vm].host.ip }}:{{ http_port[0] }}
reverse_proxy {{ hostvars[vm].ansible_host }}:{{ http_port[0] }}
{% endfor %}
log {
output file /var/log/caddy/{{ service.name }}.log