Automatische provisioning met Proxmox, Ansible en Ceph
Voor een schoolopdracht moest ik een omgeving opleveren waarin geautomatiseerd een Wordpress container en een HA Wordpress VM gedeployed kan worden. Ik kreeg twee LXC containers en drie VM's die zouden moeten dienen als Proxmox cluster. Er werd een subnet beschikbaar gesteld met 10.24.43.0/24. Er draait geen DHCP-server om automatisch IP-adressen uit te delen.
Na de installatie van de Proxmox nodes gaf ik ze allebei een statisch IP-adres en de pakkende maar flauwe namen Linda, Roos & Jessica. De drie meiden vormen samen een cluster die 'ademnood' heet.
Ik begon met het inrichten van het deployen van containers omdat dit vele malen makkelijker is. Dit komt doordat er een turnkey oplossing is in de vorm van een LXC template. Standaard is de library niet gevuld met turnkey LXC's. Hiervoor moet de lijst eerst bijgewerkt worden met het commando:
pveam updateNa het commando uitgevoerd te hebben, ziet de lijst met templates er een stuk beter gevuld uit:

We downloaden op elke PVE node de template zodat we kunnen deployen waar we willen.
Het inrichten van de command & control CT
Om de cluster aan te sturen maken we gebruik van een API. De API van Proxmox is restful, dat houdt in dat er met HTTP gewerkt gaat worden. Ansible heeft een hele fijne community plugin om met de Proxmox API om te kunnen werken. De keuze is dan ook niet moeilijk om Ansible als "weapon of choice" te gebruiken.
Een API key aanmaken in Proxmox is eenvoudig te doen via de WebGUI. Privilege seperation zetten we uit omdat we alle toeters en bellen willen bedienen via de API.
Opslaan van secrets
Als we verder willen werken met Ansible, moeten we een manier vinden om de API key en wachtwoord veilig op te slaan. Dit kan op verschillende manieren zoals:
- Een omgevingsvariabele declareren
- Gebruik maken van Ansible Vault
- Het opschrijven in een bestand
Het gebruiken van omgevingsvariabelen kan risico's met zich meebrengen omdat ze in sommige crash logs zichtbaar zijn. Niet dat ik er ervaring mee heb maar voorkomen is beter dan genezen.
De volgende optie is Ansible Vault. Een degelijke oplossing, echter voegt dit een abstractie laag toe. Ik hou de boel het liefst zo eenvoudig mogelijk en dat doe ik door de secret op te slaan in een bestand. Om te zorgen dat de secrets niet in de verkeerde handen vallen, pas ik een chmod van 400 toe zodat alleen de user root het bestand kan lezen.
Uitdelen van IP-adressen
We hebben een /24 subnet dus dat betekent dat er 254 bruikbare IP-adressen zijn. Trek daar vanaf de adressen van de twee CT's en drie VM's en we komen uit om 249. Omdat er geen DHCP-server draait die de Wordpress deployables van adressen gaat voorzien, moet er nagedacht worden over hoe we dat stokje over kunnen nemen. Met de API kunnen we eenvoudig een statisch IP-adres toewijzen maar er moet wel bijgehouden worden welke er nog beschikbaar zijn. Hiervoor is een script gemaakt die precies bijhoudt welke IP-adressen er nog beschikbaar zijn. Ik bespaar je de details maar dit is hoe het werkt:
Dit script leest een subnet op basis van de eerste regel van het bestand "ip_db" zoals 10.24.43.0/24. de regels die daarna volgen in het bestand zijn ip-adressen die al in gebruik zijn. Als het script gestart wordt met het argument "give", moet het script een IP-adres teruggeven die nog niet in de lijst staat maar wel binnen het subnet valt. Als het script gestart wordt met het argument "remove ", moet het script de regel verwijderen uit het bestand.
root@482629-LXC:~/hansible# cat ip_db
10.24.43.1/24
10.24.43.1
10.24.43.2
10.24.43.3
10.24.43.4
10.24.43.5
root@482629-LXC:~/hansible# ./ip_manager.sh give
10.24.43.6
root@482629-LXC:~/hansible# cat ip_db
10.24.43.1/24
10.24.43.1
10.24.43.2
10.24.43.3
10.24.43.4
10.24.43.5
10.24.43.6
root@482629-LXC:~/hansible# ./ip_manager.sh remove 10.24.43.12
root@482629-LXC:~/hansible# cat ip_db
10.24.43.1/24
10.24.43.1
10.24.43.2
10.24.43.3
10.24.43.4
10.24.43.5
root@482629-LXC:~/hansible#
Op deze manier is het dus dood eenvoudig om bij te houden welke adressen uitgegeven zijn. Ansible kan hier ook heel goed mee overweg want als alle IP's op zijn, gaat het script en dus ook Ansible in de remmen.
Met Ansible aan de bak: CT provisioning
Omdat ik niet de complete playbooks ga toelichten, ga ik conceptueel uitleggen hoe ik met meerdere iteraties, ongeveer ben gekomen op een playbook naar mijn smaak.
CT Playbook Iteratie 1
Omdat we moeten simuleren dat we een soort service provider zijn, stel ik me een situatie voor waarin elk klant een klantnummer heeft. Dit nummer kunnen we mooi gebruiken als VMID. We maken met Ansible een CT aan op basis van de Wordpress template, geven hem een IP-adres die verstrekt is met ip_manager en deployen het op een van de drie PVE nodes.
CT Playbook Iteratie 2
We willen dat de klant kan inloggen via SSH. Hoewel dit niet per se nodig is omdat alles via de Wordpress pagina beheerd kan worden, is dit een extra service wat we geven aan de klant. Het kan voor de klant handig zijn om op deze manier blobs ergens te bewaren. We installeren openssh-server en sudo en staan inloggen toe.
CT Playbook Iteratie 3
We willen niet dat de klant met het beheeraccount van de ademnood cluster kan inloggen. In plaats daarvan willen we dat elke klant een unieke account krijgt. Na het aanmaken van het account en de gebruiker aan de groep sudoers toegevoegd te hebben, moeten we verzinnen hoe de gebruiker gaat inloggen ...
We bedenken ons dat het IP-adres dat de deployables hebben een publiek IP-adres hebben en dat de machine goed beveiligd moet worden. Hiervoor kunnen we het beste een ssh sleutel aanmaken. We laten het playbook op de command & control CT een keypair aanmaken en geven de public key aan de Wordpress CT. De private key delen we met de fictieve klant.
CT Playbook Iteratie 4
Het deployen werkt mooi maar we maken nu continu CT's aan op dezelfde PVE node. Dit schaalt slecht omdat alle CT load op een node leunt. We hebben een bestand, hosts.ini en daarin staan de IP-adressen van alle PVE nodes genoemd. We laten het playbook een node waarop de CT geplaatst wordt, willekeurig kiezen. Hierdoor wordt de compute mooi uitgesmeerd over de verschillende nodes.
CT Playbook Iteratie 5
Om te zorgen dat alleen de noodzakelijke ports open zijn, dienen we degelijke firewall regels toe te passen. Het minimale wat nodig is om de Wordpress CT te laten draaien is port 22(ssh), 80(http) en 443(https). We laten Ansible de firewall regels toepassen via de API.
Hiermee is de strijd nog niet gestreden want op datacenter niveau moet de firewall ook aan staan. Ik was hier iets te snel mee en sloot mezelf buiten doordat er geen PASS regel stond. Gelukkig kon ik dat herstellen door een hypervisor niveau hoger (of lager?) de firewall uit te zetten met de CLI.

Ik had verdere hardening toe kunnen passen door specifiek egress verkeer te blokkeren of te zorgen dat alleen een bepaalde IP-range toegang heeft. Hier ben ik echter niet voor gegaan omdat ik niet zeker wist of mijn IP-adres welk toegewezen is door de VPN server, statisch is of niet. Als dat niet het geval was zou ik mezelf kunnen buitensluiten en moest ik een beroep doen op de afdeling IT van de hanze.
CT Playbook Iteratie 6
Als voor wat voor reden dan ook het de playbook niet lukt om succesvol alle stappen te doorlopen, wat dan?
Dat zou betekenen dat de VMID en IP-adres niet meer vrij wordt gegeven. Daarnaast zitten we met een SSH sleutelpaar op de C&C CT die geen enkel doel dient. We laten daarom, als er iets mis gaat, De Wordpress container expliciet verwijderen samen met de SSH sleutels en we geven aan ip_manager het IP-adres weer terug.
CT Playbook iteratie 7
De schoolopdracht verlangt dat we ook iets aan monitoring doen. In mijn homelab heb ik ervaring met Zabbix maar ik wil graag een andere smaak proberen. Netdata zou het moeten worden. Op de level 0 hypervisor hebben we nog een LXC CT over die mooi als monitoring server kan draaien. Nu moeten we nog een methode hebben om de agent te installeren. Gelukkig biedt Netdata een installatiescript voor een agent die niet interactief is. Dat houdt in dat we alle parameters aan het script moeten meegeven en dat we daarna klaar zijn. In dit geval betekent het het IP-adres en de API-key van de monitor meegeven. Het resultaat is dat zodra de CT operationeel is, de monitoring automatisch begint te lopen op de server.

CT Playbook iteratie 8
Als laatste hebben we een playbook nodig die een CT kan verwijderen met hetzelfde gemak als het aanmaken ervan. We maken een playbook die de CT van de willekeurig gekozen PVE node verwijdert, de SSH sleutels verwijderd uit ~/keys/... en het uitgegeven IP-adres weer teruggeeft aan ip_manager.
Met Ansible aan de bak: VM provisioning
Het is ons met het deployen van containers redelijk makkelijk gemaakt:
- Er is al een turnkey Wordpress template
- Als de gebruiker SSHd naar de server, krijgt ie een installatie-wizard
- Er hoeft geen HA toegepast te worden
Bij VM's is dit een stuk lastiger. De slimste aanpak is denk ik om een VM aan te maken en beetje bij beetje de boel als een kerstboom optuigen en daarna de VM converteren naar een VM template.
Een VM optuigen
Dan volgt de keuze voor een *nix distributie. Er valt veel te kiezen! Ik besluit een slanke Debian distributie te gebruiken. Proxmox is ook gebaseerd op Debian, ik ben bekend met Debian en de hele filosofie van Debian is om een stabiel OS op te leveren (met hele oude packages en libraries maar dat laten we even buiten beschouwing ...).
Nadat de installatie is voltooid kunnen we nog niet aan de slag met Wordpress installeren. Laatste puntjes op de i: We updaten de Debian repositories, installeren openssh-server en Netdata alvast. We geven het adres van de Netdata monitor en de API key mee en installeren een RMDB. Ik kies voor MariaDB en maak alvast een account en database aan. Vervolgens installeer ik PHP met al zijn toeters en bellen en download ik Wordpress. Als laatste moeten we nog een webdaemon hebben. Apache2 is de keuze geworden. We configureren een en ander en voila! De setup pagina van wordpress is te bewonderen.

Een Wordpress site kapen, wie doet dat nou?
Alles lijkt in kannen en kruiken echter is niets minder waar. Iedereen die het (fictieve) domeinnaam of IP-adres weet van de server, kan gebruik maken van de setup page. Dit kan natuurlijk niet de bedoeling zijn! Voor hetzelfde geld koopt onze klant in een dronken bui een Wordpress VM van ons en gaat dan slapen. In zijn dutje kan iedereen aan de haal met zijn pagina! Weten we nog hoe het bij de turnkey container zat? Daar moest eerst ingelogd worden met SSH en een installatiescript voltooid worden. Misschien kunnen we iets soortgelijks implementeren.
We schrijven een script die gestart wordt als de gebruiker voor de eerste keer inlogt. Hierin kan de gebruiker zijn admin username, password, site URL en email adres opgeven. Als die stap doorlopen is, wordt een bestand aangemaakt in /var/lib/wordpress/ genaamd setup_done. Het bestaan van dat bestand toont aan dat de wizard is voltooid en de admin kan nu veilig met zijn eigen wachtwoord inloggen om de site te beheren.
De provisioning ding
Zoals we inmiddels weten, kunnen we met de Proxmox API best wel veel dingen configureren. Bij LXC Containers hadden we de optie om een hostname, IP-adres, gateway etc. mee te geven. Bij een VM werkt dat anders omdat, in tegenstelling tot een container, een virtuele machine een compleet besturingssysteem moet bootstrappen.


cloud-init
Gelukkig heeft Proxmox hier een oplossing voor gevonden: cloud-init. Wat het programma doet is tijdens het opstarten van init of systemd bepaalde metadata (lees IP-adres, hostname, etc.) inladen; nog voordat de services geladen worden. De gegevens kunnen geladen worden vanaf een virtuele harde schijf.
De harde schijf kunnen we eenvoudig toevoegen aan onze deels opgetuigde VM.


Om cloud-init te laten werken met onze VM moet het wel geïnstalleerd zijn. Dit is eenvoudig via apt te doen. In onze ansible playbook om een VM te maken kunnen we de metadata die we willen gebruiken later met de API pushen naar de VM.