it en

Lo Zen dell'Infrastructure as Code

IaC vs. ClickOps

Strucare botoni (ClickOps) è la pratica comune di utilizzare interfacce grafiche per compiere un certo lavoro. Un esempio: creare un LXC in Proxmox significa partire dal pulsante Create CT nella schermata principale, assegnarli le risorse necessarie, installare pacchetti e configurare SSH, Docker e firewall a mano, per poi ripetere per ogni container.

Ecco, sto cercando di ridurre al minimo questa pratica, sostituendola con Infrastructure as Code dove possibile. Per ora sono un semplice esploratore inesperto, ma mi sto divertendo un mondo!


TLDR

Sto sperimentando diversi strumenti di orchestrazione per rendere la mia infrastruttura automatizzata, riproducibile e documentata. In questo post spiego come creare template LXC riutilizzabili in Proxmox.

La mia infrastruttura

La creazione e la manutenzione delle risorse sul mio server avvengono principalmente tramite Opentofu per la genesi di LXC e macchine virtuali, Ansible per la gestione degli aggiornamenti di sistema e dei backup dei servizi.

Ogni servizio ha il proprio file docker-compose in una repository git che mantengo nella mia istanza di Forgejo. Ogni giorno Renovate controlla la disponibilità di aggiornamenti per i servizi. Se ne trova, crea una pull request con le modifiche necessarie. Una volta controllate le release notes dei servizi, posso unire le PR e Komodo si occupa di aggiornare i servizi interessati.

Rimane però un passaggio ancora manuale: dopo aver creato un container con Opentofu devo collegarmi alla sua shell tramite la UI di Proxmox. Il motivo? Il template di Alpine che utilizzo non include python, necessario per eseguire Ansible e completare il setup iniziale.

Poco male, basta effettuare il setup in un nuovo container, convertirlo in template e il gioco è fatto! Ci sono solo un paio di accorgimenti da tenere a mente per rendere il processo più fluido.

Un concetto simile, forse ancora più interessante, lo sto applicando sul mio portatile. Utilizzo un'immagine personalizzata costruita con BlueBuild a partire da Bluefin. La repository si trova qui.

Creare un Template in Proxmox

Configurare l'LXC

Inizio creando un LXC Alpine che servirà come template di base per tutti i container futuri:

  1. Mi collego alla shell del container appena creato e lo configuro con ciò che mi interessa:

    # System update and base software installation
    apk update && apk upgrade
    apk add python3 openssh doas bash bash-completion shadow curl vim nano \
    docker docker-compose openrc
    
    # Tailscale setup
    curl -fsSL https://tailscale.com/install.sh | sh
    
    # Timezone (adapt to your own)
    setup-timezone -z Europe/Rome
    
    # Enable SSH and Docker
    rc-update add sshd
    rc-update add docker boot
    rc-service sshd start
    rc-service docker start
    
    # Configure doas for wheel group
    mkdir -p /etc/doas.d
    echo "permit persist :wheel" > /etc/doas.d/20-wheel.conf
    chmod 644 /etc/doas.d/20-wheel.conf
    
    # Create non-root user (docker and wheel group)
    adduser -D -u 1000 -G docker -s /bin/bash username
    echo "username:temporary" | chpasswd
    addgroup username wheel
    
    # Setup SSH
    mkdir -p /home/username/.ssh
    chmod 700 /home/username/.ssh
    touch /home/username/.ssh/authorized_keys
    chmod 600 /home/username/.ssh/authorized_keys
    chown -R username:usergroup /home/username/.ssh
    
    # Hardening SSH
    sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
    sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
    rc-service sshd restart
  2. Aggiungo le chiavi SSH necessarie all'utente appena creato;

  3. Spengo il container e rimuovo la scheda di rete con il comando pct set <CID> --delete net0.

Generare il template

Per creare il template basta generare un backup nella cartella dedicata (nel mio caso in /tank/isos/template/cache) con il comando:

vzdump <CID> --mode stop --compress gzip --dumpdir /tank/isos/template/cache/

Rinomino il file risultante con mv new_vz_dump.tar.gz custom_alpine_3.23.tar.gz e il template è pronto all'uso!

A differenza della creazione del template tramite l'interfaccia di Proxmox, questo metodo non distrugge il container originale, che può essere eliminato o riutilizzato in caso di aggiornamenti futuri.

Creazione di nuovi LXC dal template

Ora posso generare un nuovo container tramite Opentofu utilizzando il template personalizzato al posto dell'Alpine generico. Una volta creato spengo il container, in modo da poter aggiungere le due righe di configurazione necessarie affinché Tailscale possa funzionare:

# /etc/pve/lxc/<CID>.conf
lxc.cgroup2.devices.allow: c 10:200 rwm
lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file

Non resta che creare un nuovo playbook Ansible che esegua gli aggiornamenti di sistema, acceda a Tailscale e modifichi la password dell'utente con una nuova (quella di root viene generata da Opentofu).

Assicurati di avere passlib installato: sudo pacman -S python-passlib

lxc-setup.yaml

---
- name: LXC initial configuration
  hosts: new_lxcs
  remote_user: username
  become: true
  become_method: doas
  gather_facts: true
  vars_file: [vault.yaml]
  vars:
    tailscale_auth_key: "{{ vault_tailscale_auth_key }}"
  tasks:
    - name: Update all packages
      apk:
        update_cache: true
        upgrade: true
    - name: Ensure Docker is running
      service:
        name: docker
        enabled: true
        state: started

    - name: Check if tailscale already authenticated
      command: tailscale status
      register: tailscale_status
      failed_when: false
      changed_when: false
    - name: Authenticate and start Tailscale
      command: tailscale up --operator=username --auth-key={{ tailscale_auth_key }}
      when: "'Logged out' in tailscale_status.stdout or tailscale_status.rc != 0"
      register: tailscale_up
    - name: Display Tailscale status
      debug:
        msg: Tailscale is now connected
      when: tailscale_up.changed or ('BackendState=Running' in tailscale_status.stdout)

    - name: Set user password
      user:
        name: username
        password: "{{ new_host_sudo_pass | password_hash('sha512') }}"

hosts.ini

[new_lxcs:vars]
ansible_user=username
ansible_become=yes
ansible_become_method=doas

[new_lxcs]
new-host ansible_host=new-host-ip ansible_become_pass='temporary'

vault.yaml

new_host_sudo_pass: your-password
vault_tailscale_auth_key: tskey-auth-xx..x-yy..y

Esegui il playbook con:

ansible-playbook -i hosts.ini lxc-setup.yaml --ask-vault-pass

A questo punto il container dovrebbe essere pronto all'uso!

Ultimi passaggi

Aggiornare il file hosts.ini:

Non è la soluzione più elegante, ma sono le due di notte e il mio cervello mi sta abbandonando.

Per concludere

Non ho inventato nulla di rivoluzionario. Tutto quello che ho fatto è stato applicare principi di IaC consolidati al mio piccolo homelab, che ora è ben definito in codice versionato, documentato e riproducibile.

Ho ancora molta strada da fare, ma vedere tutto prendere forma da file di configurazione invece che da sessioni struca botoni è incredibilmente soddisfacente (·ω·)

← Precedente: Introduzione

Taggato con: