Dynamische inventaris simuleren in ontwikkeling
Categories:
Projects:
c2platform/phx/ansible
Problem
Voor organisaties die nieuw zijn met automatisering biedt dynamische inventaris mogelijk beperkte directe waarde, omdat handmatige stappen vaak eerst moeten worden geautomatiseerd. Onbekendheid met Ansible en inventarismechanieken, zoals precedence-regels, kan leiden tot suboptimale oplossingen1.
Context
In Nederlandse overheidsorganisaties die beginnen met automatisering is de vertrouwdheid met Ansible vaak beperkt, laat staan met concepten zoals dynamische inventaris. Dit is de primaire context.
Dynamische inventaris vertegenwoordigt een toekomstige mijlpaal. In een volwassen professionele omgeving kan een aanvraag in tools zoals vRA voor een server of volledige omgeving (meerdere servers) voldoende zijn. Een Ansible operator zou alleen het vRA-portal nodig hebben om te provisionen. De vRA-opzet past labels toe die een dynamische inventaris-plugin gebruikt voor volledige provisioning, zonder het inventarisproject aan te passen.
Solution
Begin in de
ontwikkelomgeving met de
Vagrant inventory plugin voor
Vagrant. Deze plugin is geen dynamische inventaris in de traditionele zin — het bevraagt geen externe cloud-API of database. In plaats daarvan leest het het YAML-bestand Vagrantfile.yml van het C2 Platform en genereert het een inventaris die Ansible kan gebruiken. Hiermee simuleert het hoe dynamische inventaris werkt: het toont hoe een inventaris kan worden gegenereerd vanuit een gegevensbron in plaats van handmatig te worden onderhouden.
Gebruik dit werkende voorbeeld om de voordelen van dynamische inventaris uit te leggen en te illustreren aan organisaties die nieuw zijn met Ansible. Het laat zien hoe volledig geautomatiseerde provisioning werkt zonder handmatige stappen, demonstreert toekomstige schaalbaarheid en vereenvoudigt het beheer van inventarissen naarmate de automatisering volwassener wordt.
Benefits
- Biedt een praktisch voorbeeld om organisaties te begeleiden naar geavanceerde automatisering.
- Vermindert handmatige updates van inventarissen naarmate de infrastructuur schaalt.
- Bouwt begrip van Ansible op door toepassing in de praktijk.
- Bereidt teams voor op productie-achtige automatisering.
Examples and Implementation
VMware vRealize Automation (vRA)
Dit diagram illustreert de collaboratieve rollen van het infra-team en DevOps-team in een op vRA gebaseerde omgeving:
- Het infra-team richt vRA in voor VM-aanvragen via het vRA-portal en wijst labels
toe aan images (bijv.
ubuntu,ubuntu24,rhel,rhel9,win,win2022). - Het DevOps-team selecteert vooraf gedefinieerde omgevingslabels (bijv.
development,test,acceptance,production) en applicatierol-labels (bijv.hello-world) via het portal. - Het DevOps-team gebruikt een Dynamische inventaris plugin om gelabelde nodes op te halen uit de vRA API, waarbij hostinformatie en Ansible-groepen worden gegenereerd.
- Aanvullende statische groepen (bijv.
linux) worden gedefinieerd in eenhosts.inibestand voor bredere targeting, en blijven statisch na de initiële setup. Dit bestand is onderdeel van het Ansible-inventarisproject.
Tabel die het labelingschema illustreert voor images en VMs, met hoe vRA labels overeenkomen met Ansible-groepen en de verantwoordelijkheden van Infra- en DevOps-teams.
| Type | Infra-team | DevOps-team | Label(s) | Ansible-groep(en) | Beschrijving |
|---|---|---|---|---|---|
| VM Image | Verantwoordelijk | ubuntu ubuntu24 | ubuntu ubuntu24 | Labels voor op Ubuntu 24 gebaseerde images; vormt een Ansible-groep voor alle Ubuntu-hosts | |
| VM Image | Verantwoordelijk | rhel rhel9 | rhel rhel9 | Labels voor Red Hat Enterprise Linux 9 images; groepeert RHEL-hosts. | |
| VM Image | Verantwoordelijk | win win2022 | win win2022 | Labels voor Windows 2022 images; groepeert alle Windows-hosts. | |
| DTAP | Biedt opties | Selecteert | development test acceptance production | development test acceptance production | Omgevingslabels; wijst hosts toe aan een specifieke omgeving. |
| Service/Application | Biedt opties | Selecteert | hello-world | hello-world | Applicatierol-label; creëert een groep voor hosts die de “hello-world”-app draaien. |
| Ansible-inventaris | Verantwoordelijk | linux | linux | Statische groep gedefinieerd in hosts.ini voor bredere targeting van Linux-hosts. |
Example in Development
De inventaris-plugin voor Vagrant illustreert hoe het vRA-voorbeeld zou werken. Het werkt op dezelfde fundamentele principes en biedt een concreet voorbeeld van hoe labels in de virtualisatietechnologie — vRA in productie-achtige domeinen of Vagrant in ontwikkeling — worden gebruikt met een plugin om dynamisch hostinformatie te genereren en Ansible-groepen te definiëren.
Inventory Plugin for Vagrant
De PHX
referentie-implementatie
c2platform/phx/ansible bevat een voorbeeld van een aangepaste inventory plugin.
De plugin plugins/inventory/vagrant.py leest Vagrantfile.yml om de inventory te genereren.
Het voegt hosts toe, stelt variabelen in zoals IP-adressen en wijst groepen toe op basis van prefixen, labels en aanvullende INI-bestanden.
Het kan de gegenereerde inventory ook exporteren naar een INI-bestand voor debugging of integratie.
# pylint: disable=super-with-arguments
#!/usr/bin/env python3
import os
import yaml
from ansible.errors import AnsibleError
from ansible.plugins.inventory import BaseInventoryPlugin
import configparser
import re
class InventoryModule(BaseInventoryPlugin):
NAME = "vagrant"
def verify_file(self, path):
return path.endswith((".yml", ".yaml")) and "Vagrantfile" in os.path.basename(
path
)
def parse(self, inventory, loader, path, cache=True):
super(InventoryModule, self).parse(inventory, loader, path, cache)
try:
with open(path, "r", encoding="utf-8") as file_handle:
data = yaml.safe_load(file_handle)
except Exception as exception_error:
raise AnsibleError(
f"Error loading Vagrantfile: {str(exception_error)}"
) from exception_error
boxes = data.get("boxes")
defaults = data.get("defaults", {})
default_prefix = defaults.get("prefix")
for node in data.get("nodes", []):
nodename = node.get("name")
host_prefix = node.get("prefix", default_prefix)
host = f"{host_prefix}-{nodename}"
if not host:
continue
self.inventory.add_host(host)
ip_address = node.get("ip-address")
if ip_address:
self.inventory.set_variable(host, "ansible_host", ip_address)
# Determine environment group based on last letter of prefix
if host_prefix:
last_char = host_prefix[-1].lower()
env_group = None
if last_char == "d":
env_group = "development"
elif last_char == "t":
env_group = "test"
elif last_char == "a":
env_group = "acceptance"
elif last_char == "p":
env_group = "production"
if env_group:
self.inventory.add_group(env_group)
self.inventory.add_child(env_group, host)
# Labels
node_labels = node.get("labels", [])
box_labels = boxes.get(node.get("box", ""), {}).get("labels", [])
for label in node_labels + box_labels:
self.inventory.add_group(label)
if label != host:
self.inventory.add_child(label, host)
# Handle additional inventory files
inventory_files = defaults.get("inventory", [])
for inv_file in inventory_files:
inv_path = os.path.join(os.path.dirname(path), inv_file)
if os.path.exists(inv_path):
self._parse_ini(inv_path)
# Export to INI if configured
inventory_export = defaults.get("inventory_export")
if inventory_export:
export_path = os.path.join(os.path.dirname(path), inventory_export)
self._write_ini(export_path)
def _parse_ini(self, ini_path):
print(f"ini_path: {ini_path}")
config = configparser.ConfigParser(allow_no_value=True)
with open(ini_path, "r", encoding="utf-8") as f:
config.read_file(f)
for section in config.sections():
if ":" in section:
group_name, section_type = section.split(":", 1)
group_name = group_name.strip()
section_type = section_type.strip()
if section_type == "children":
if group_name not in self.inventory.groups:
self.inventory.add_group(group_name)
for child in config.options(section):
child = child.strip()
if child:
self.inventory.add_group(child)
self.inventory.add_child(group_name, child)
elif section_type == "vars":
if group_name not in self.inventory.groups:
self.inventory.add_group(group_name)
for var, value in config.items(section):
value = value.strip() if value else None
self.inventory.set_variable(group_name, var, value)
else:
# Regular group
self.inventory.add_group(section)
for host in config.options(section):
host = host.strip()
if host:
# Check if it's a host with vars, but in simple ini, options are hosts
# If there's value, it might be port or something, but for now assume host
self.inventory.add_host(host, group=section)
# If there are vars like host=var, but in standard ini, hosts are keys with no value
# Handle host vars if any, but in standard ini, host vars are in [host:vars] which we handle above
def _write_ini(self, ini_path):
config = configparser.ConfigParser(allow_no_value=True)
# Add groups and hosts
for group_name, group in self.inventory.groups.items():
if group_name == 'all' or group_name == 'ungrouped':
continue
config.add_section(group_name)
for host in group.hosts:
config.set(group_name, host.name, None)
# Add children
for group_name, group in self.inventory.groups.items():
if group.child_groups:
section = f"{group_name}:children"
config.add_section(section)
for child in group.child_groups:
config.set(section, child.name, None)
# Add group vars
for group_name, group in self.inventory.groups.items():
vars_dict = group.get_vars()
if vars_dict:
section = f"{group_name}:vars"
config.add_section(section)
for k, v in vars_dict.items():
config.set(section, k, str(v))
# Add host vars
for host_name, host in self.inventory.hosts.items():
vars_dict = host.get_vars()
# Remove ansible_host as it's usually set separately
vars_dict = {k: v for k, v in vars_dict.items() if k != 'ansible_host'}
if vars_dict:
section = f"{host_name}:vars"
config.add_section(section)
for k, v in vars_dict.items():
config.set(section, k, str(v))
# Add [host] sections for hosts with ansible_host
for host_name, host in self.inventory.hosts.items():
ansible_host = host.get_vars().get('ansible_host')
if ansible_host:
section = host_name
if not config.has_section(section):
config.add_section(section)
config.set(section, f'ansible_host={ansible_host}')
with open(ini_path, 'w', encoding='utf-8') as f:
config.write(f)
De Vagrant inventory plugin is een Ansible inventory plugin die wordt gebruikt voor de ontwikkelomgeving.
Het doel van deze plugin is tweeledig. Ten eerste biedt het een enkele bron van waarheid voor zowel Vagrant als Ansible tijdens ontwikkeling. Dit elimineert de noodzaak om informatie te dupliceren, zoals IP-adressen, in Vagrant- en Ansible-configuraties.
Ten tweede illustreert het hoe inventory voor Ansible kan worden opgebouwd met een aangepaste module of plugin. De toepassing in de ontwikkelomgeving simuleert dynamische inventaris. Veel overheidsorganisaties worstelen met het concept van dynamische inventory. Deze plugin dient als een praktisch voorbeeld van een aangepaste inventory plugin.
Vagrantfile.yml
De Vagrantfile.yml definieert de pxd-s3-node met behulp van de ubuntu22-lxd-box,
wijst IP 192.168.60.14 toe en specificeert de
playbook mgmt/s3 voor
provisioning. Vagrant gebruikt dit om de node te creëren en te configureren.
22 ubuntu22-lxd:
23 name: c2platform/ubuntu-jammy
24 version: 0.1.1
25 provider: lxd
26 labels: [ubuntu, lxd, ubuntu22]
272 - name: s3
273 short_description: S3
274 description: MinIO S3
275 box: ubuntu22-lxd
276 ip-address: 192.168.60.14
277 plays:
278 - mgmt/s3
279 labels:
280 - s3
281 - s3_download_server
De inventaris-plugin wijst deze node toe aan de Ansible-groepen s3
en s3_download_server op basis van de labels. In deze testopzet behoort pxd-s3
tot beide groepen en dient als zowel de S3-server als de testclient. In
productie-achtige scenario’s zouden deze rollen worden gescheiden.
hosts.ini
Dit statische inventarisbestand biedt basisgroepen en variabelen.
# Note: hosts and groups come from Vagrantfile.yml primarily
[linux:children]
ubuntu
rhel
[win_ssh_server:children]
win
[linux:vars]
ansible_user=vagrant
ansible_password=vagrant
[win:vars]
#ansible_connection=ssh
#ansible_shell_type=cmd
ansible_user=vagrant
ansible_password=vagrant
ansible_connection=winrm
#ansible_port=5985
ansible_winrm_transport=basic
ansible_winrm_server_cert_validation=ignore
Group Membership Example
Deze sectie demonstreert hoe Ansible-groepen dynamisch worden toegewezen aan een node met behulp van de inventaris-plugin en statische definities.
Laten we de node pxd-s3 als voorbeeld nemen. Met de debug-module kunnen we
de group_names voor pxd-s3 opvragen. Zie hieronder.
ansible -m debug -a 'msg={{group_names}}' pxd-s3
θ61° [:ansible-phx]└2 master(+2/-1) ± ansible -m debug -a 'msg={{group_names}}' pxd-s3
ini_path: /home/onknows/git/gitlab/c2/ansible-phx/hosts.ini
pxd-s3 | SUCCESS => {
"msg": [
"development",
"linux",
"lxd",
"s3",
"s3_download_server",
"ubuntu",
"ubuntu22"
]
}
De Ansible-groepen worden als volgt afgeleid:
lxd,ubuntuenubuntu22zijn afkomstig van de definitie van de Vagrant boxubuntu22-lxdinVagrantfile.yml.s3ens3_download_servervan de definitie van de nodepxd-s3inVagrantfile.yml.linuxvanhosts.ini.
Additional Information
- Voor meer informatie over GitLab Runner als controlenode: Gebruik van GitLab Runner als Ansible Control Node
- Voor meer informatie over de
pxd-s3-node: Creëren en testen van een S3-service - Ansible-inventarisproject: Een gestructureerde verzameling bestanden die worden gebruikt voor het beheren van hosts en configuraties. Het omvat doorgaans inventarisbestanden, playbooks, hostconfiguraties, groepsvariabelen en Ansible Vault-bestanden.
In de context van het PHX-project is een suboptimale oplossing onderzocht waarbij het idee ontstond om het toevoegen van servers aan het statische
hosts.ini-bestand in de juiste Ansible-groepen te automatiseren om het werk van de Ansible operator te vereenvoudigen. Destijds gebruikte het project een traditioneel statisch inventarisbestand in plaats van dynamische inventaris, ook al werd vRA gebruikt voor provisioning. Deze aanpak is suboptimaal om twee redenen. Ten eerste is het beheren van het inventarisbestand met Ansible-groepen en nodes niet overdreven complex, en de Ansible operator moet er toch een grondig begrip van hebben, omdat het de basis vormt van effectieve automatisering. Ten tweede is het logischer om Ansible-groepen te creëren op basis van tags of labels in vRA en de inventarisinformatie daaruit af te leiden — een uitdaging die vanzelf wordt opgelost bij het adopteren van dynamische inventaris, in plaats van updates naar een statisch bestand te automatiseren. ↩︎
Feedback
Was deze pagina nuttig?
Fijn om te horen! Vertel ons alstublieft hoe we kunnen verbeteren.
Jammer om dat te horen. Vertel ons alstublieft hoe we kunnen verbeteren.