{"id":3070,"date":"2022-01-17T07:00:00","date_gmt":"2022-01-17T06:00:00","guid":{"rendered":"https:\/\/www.my-it-brain.de\/wordpress\/?p=3070"},"modified":"2023-05-01T21:51:52","modified_gmt":"2023-05-01T19:51:52","slug":"labor-umgebung-mit-ansible-in-kvm-erstellen","status":"publish","type":"post","link":"https:\/\/www.my-it-brain.de\/wordpress\/labor-umgebung-mit-ansible-in-kvm-erstellen\/","title":{"rendered":"Labor-Umgebung mit Ansible in KVM erstellen"},"content":{"rendered":"\n<p>Inspiriert durch die Artikel von Ricardo Geradi [<a href=\"#quellen-links\">1<\/a>] und Alex Callejas [<a href=\"#quellen-links\">3<\/a>] schreibe ich diesen, um zu erkl\u00e4ren, wie mithilfe von Ansible eine Labor-Umgebung bestehend aus einer oder mehreren virtuellen Maschinen (VMs) auf einem <a href=\"https:\/\/de.wikipedia.org\/wiki\/Kernel-based_Virtual_Machine\">KVM-Hypervisor<\/a> provisioniert werden kann.<\/p>\n\n\n\n<p>Dabei handelt es sich weniger um ein Tutorial, sondern mehr um eine exemplarische Beschreibung einer m\u00f6glichen Vorgehensweise, die euch als Vorlage f\u00fcr die eigene Umgebung dienen kann.<\/p>\n\n\n\n<p>Ich gehe nicht darauf ein, wie KVM oder Ansible installiert werden. Hierzu verweise ich auf die Dokumentation der jeweiligen Projekte und der verwendeten Linux-Distributionen. Unter Punkt <a href=\"#quellen-links\">[8] und [9]<\/a> findet ihr die Links, wo ich die hier vorgestellte Ansible-Rolle ver\u00f6ffentlicht habe.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"Motivation\">Motivation<\/h2>\n\n\n\n<p>Um Anwendungen zu testen, ben\u00f6tigt man in der Regel ein Betriebssystem, auf welchem diese ausgef\u00fchrt werden k\u00f6nnen. Ein Betriebssystem l\u00e4uft dieser Tage meist innerhalb einer virtuellen Maschine (VM). Um bei Tests stets gleiche Rahmenbedingungen zu haben, wird empfohlen, f\u00fcr jeden Test eine neue VM mit einer definierten Konfiguration zu provisionieren, die geplanten Tests durchzuf\u00fchren, die Ergebnisse zu sichern und die VM zu dekommissionieren.<\/p>\n\n\n\n<p>M\u00f6chte man Infrastrukturdienste testen, werden h\u00e4ufig gleich mehrere VMs ben\u00f6tigt. Diese werden auch als Labor-Umgebung bezeichnet.<\/p>\n\n\n\n<p>Um nicht unn\u00f6tig Zeit mit der Provisionierung der VMs zu verlieren &#8212; immerhin m\u00f6chte man ja seine Anwendungen bzw. Dienste testen &#8212; bietet es sich an, diesen Prozess zu automatisieren.<\/p>\n\n\n\n<p><strong>Doch warum mit Ansible und nicht mit [hier Lieblings-Werkzeug eurer Wahl einsetzen]?<\/strong><\/p>\n\n\n\n<p>Viele Wege f\u00fchren nach Rom. Und es gibt vermutlich \u00e4hnlich viele Werkzeuge, um eine Labor-Umgebung in KVM zu provisionieren. Ich habe mich in diesem Fall f\u00fcr Ansible entschieden, da:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Ich fast t\u00e4glich damit arbeite.<\/li>\n\n\n\n<li>Mit <code>ansible-galaxy role init<\/code> erstellte Rollen meiner bescheidenen Meinung nach (mbMn) eine sch\u00f6ne Struktur zur Organisation des Codes vorgeben.<\/li>\n\n\n\n<li>Mit <code>ansible-vault<\/code> ein Werkzeug dabei ist, um Dateien mit sensiblen Informationen zu verschl\u00fcsseln und diese im weiteren Verlauf einfach zu nutzen.<\/li>\n\n\n\n<li>Ich meine YAML-Dateien n\u00e4chstes Jahr leichter lesen und verstehen kann als meine Shell-Skripte.<\/li>\n\n\n\n<li>Ich in einem zuk\u00fcnftigen Artikel zeigen m\u00f6chte, wie man mit Ansible eine Labor-Umgebung in einem VMware vSphere Cluster provisioniert.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"Umgebung\">Umgebung<\/h2>\n\n\n\n<p>KVM-Hypervisor: Debian 11 Bullseye<\/p>\n\n\n\n<p><em>Update 2022-04-23<\/em>: Seit heute wird auch RHEL 9 als KVM-Hypervisor unterst\u00fctzt.<\/p>\n\n\n\n<p>Die .qcow2-Image-Dateien f\u00fcr die VMs werden auf dem KVM-Hypervisor im Verzeichnis <code>\/var\/lib\/libvirt\/images\/<\/code> vorgehalten.<\/p>\n\n\n\n<p>Getestete Ansible Versionen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>ansible 2.10.8 ( auf Debian 11 Bullseye)<\/li>\n\n\n\n<li>ansible [core 2.12.1] (auf Fedora 35)<\/li>\n<\/ul>\n\n\n\n<p>Die Verzeichnisstruktur f\u00fcr meine Ansible-Umgebung entspricht der aus dem Artikel <a href=\"https:\/\/www.my-it-brain.de\/wordpress\/linux-benutzerkonten-mit-ansible-verwalten\/\" data-type=\"post\" data-id=\"1482\">Linux-Benutzerkonten mit Ansible verwalten<\/a>, wie sie im dortigen Abschnitt Vorbereitung beschrieben ist.<\/p>\n\n\n\n<p>Die im Laufe dieses Artikels provisionierte Labor-Umgebung wird aus einer RHEL-7 und einer RHEL-8-VM bestehen. Selbstverst\u00e4ndlich ist es m\u00f6glich, durch einfache Anpassungen weitere VMs sowie andere Linux-Distributionen zu provisionieren.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"Vorarbeit\">Vorarbeit<\/h2>\n\n\n\n<p>Ricardo Geradi [<a href=\"https:\/\/www.my-it-brain.de\/wordpress\/wp-admin\/post.php?post=3070&amp;action=edit#quellen-links\">1<\/a>] und Alex Callejas [<a href=\"https:\/\/www.my-it-brain.de\/wordpress\/wp-admin\/post.php?post=3070&amp;action=edit#quellen-links\">3<\/a>] beziehen in ihren Artikeln die qcow2-Images, welche sie als Vorlage (engl. Template) f\u00fcr weitere VMs verwenden, aus diversen Internet-Quellen. Ich bin kein Freund davon, mir Images aus dem Netz zu laden und zu nutzen, f\u00fcr die es keine ordentliche Dokumentation gibt, mit welchen Paketen und Einstellungen diese erzeugt wurden.<\/p>\n\n\n\n<p>Wer kauft schon gern die Katze im Sack? Daher erstelle ich mir meine Vorlagen selbst. Dazu f\u00fchre ich f\u00fcr jede Distribution, f\u00fcr die ich eine Vorlage erstellen m\u00f6chte, eine manuelle Installation durch. Um die Vorlagen unter all den anderen VMs leicht identifizieren zu k\u00f6nnen, gebe ich ihnen Namen wie z.B.:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>rhel7-template<\/li>\n\n\n\n<li>rhel8-template<\/li>\n\n\n\n<li>debian11-template<\/li>\n<\/ul>\n\n\n\n<p><s>Dabei hinterlege ich beim User root bereits den SSH-Public-Key, den ich sp\u00e4ter mit Ansible verwenden m\u00f6chte, um diese Systeme weiter zu konfigurieren.<\/s> Dies tue ich zwar bisher. Es ist f\u00fcr die Verwendung der hier beschriebenen Rolle nicht erforderlich.<\/p>\n\n\n\n<p>M\u00f6chte ich eine Vorlage aktualisieren, fahre ich die dazugeh\u00f6rige VM hoch, f\u00fchre ein Paket-Update durch, fahre die VM wieder herunter und bin fertig. Dies mache ich in der Regel alle paar Monate, wenn mir das Paket-Update bei neu provisionierten VMs zu lange dauert und sp\u00e4testens nach Erscheinen eines neuen Minor-Release.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"Ansible-Rolle\">Die Ansible-Rolle<\/h2>\n\n\n\n<p>Eine Ansible-Rolle wird mit dem Befehl <code>ansible-galaxy role init role_name<\/code> initialisiert. In meinem Fall sieht dies wie folgt aus:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ ansible-galaxy role init kvm_provision_lab\n- Role kvm_provision_lab was created successfully\n$ tree kvm_provision_lab\nkvm_provision_lab\n\u251c\u2500\u2500 defaults\n\u2502&nbsp;&nbsp; \u2514\u2500\u2500 main.yml\n\u251c\u2500\u2500 meta\n\u2502&nbsp;&nbsp; \u2514\u2500\u2500 main.yml\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 tasks\n\u2502&nbsp;&nbsp; \u2514\u2500\u2500 main.yml\n\u251c\u2500\u2500 templates\n\u2514\u2500\u2500 vars\n    \u2514\u2500\u2500 main.yml<\/code><\/pre>\n\n\n\n<p>In obiger Ausgabe fehlen die Verzeichnisse <em>Files <\/em>und <em>Handlers<\/em>. Diese hatte ich bereits gel\u00f6scht, da sie nicht ben\u00f6tigt werden. Die erstellte Verzeichnisstruktur kann, je nach verwendeter Version von <code>ansible-galaxy<\/code>, leicht unterschiedlich aussehen. Ben\u00f6tigt werden in diesem Beispiel nur die oben dargestellten Verzeichnisse und Dateien. Streng genommen k\u00f6nnen das Verzeichnis <em>meta<\/em> und die Datei <em>README.md<\/em> ebenfalls entfernt werden, wenn man nicht vorhat, die Rolle zu ver\u00f6ffentlichen. Ich behalte beide bei und nutze die Dateien zur Dokumentation der Rolle.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"Variablen\">Variablen<\/h3>\n\n\n\n<p>Es ist gute Praxis alle Variablen, die von einer Ansible-Rolle verarbeitet werden, in der Datei <code>defaults\/main.yml<\/code> zu dokumentieren und mit Standardwerten zu versehen. Genannte Datei hat hier folgenden Inhalt:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ cat -n defaults\/main.yml \n     1\t---\n     2\tlibvirt_pool_dir: \"\/var\/lib\/libvirt\/images\"\n     3\tvm_root_pass: \"123456\"\n     4\tssh_key: \"\/path\/to\/ssh-pub-key\"\n     5\t\n     6\tguests:\n     7\t  test:\n     8\t    vm_ram_mb: 512\n     9\t    vm_vcpus: 1\n    10\t    vm_net: default\n    11\t    os_type: rhel7\n    12\t    file_type: qcow2\n    13\t    base_image_name: rhel7-template\n    14\t    vm_template: \"rhel7-template\"\n    15\t    second_hdd: false\n    16\t    second_hdd_size: \"\"\n    17\t  test2:\n    18\t    vm_ram_mb: 512\n    19\t    vm_vcpus: 1\n    20\t    vm_net: default\n    21\t    os_type: rhel8\n    22\t    file_type: qcow2\n    23\t    base_image_name: rhel8-template\n    24\t    vm_template: \"rhel8-template\"\n    25\t    second_hdd: true\n    26\t    second_hdd_size: \"100M\"<\/code><\/pre>\n\n\n\n<p>In Zeile 2-4 werden Variablen definiert, die unabh\u00e4ngig von einzelnen VMs f\u00fcr die gesamte Rolle gelten. Dies sind der Speicherort f\u00fcr Image-Dateien, das Passwort f\u00fcr den Root-Benutzer der VMs, sowie der Pfad zu dem SSH-Public-Key, welcher beim Root-Benutzer hinterlegt werden soll.<\/p>\n\n\n\n<p>In Zeile 6 beginnt ein sogenanntes Ansible-Dictionary (siehe [<a href=\"#quellen-links\">6<\/a>]) namens <em>guests<\/em>. Es enth\u00e4lt als <em>Keys<\/em> die Namen der VMs (hier test und test2) und ordnet diesen diverse Variablen als Werte zu (z.B. vm_ram_mb). Die hierf\u00fcr gew\u00e4hlten Strings m\u00fcssen g\u00fcltige Ansible-Variablen sein (siehe [<a href=\"#quellen-links\">7<\/a>]).<\/p>\n\n\n\n<p>Die einzelnen Variablen kurz erkl\u00e4rt:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>vm_ram_mb<\/code> gibt die Gr\u00f6\u00dfe des Gast-Arbeitsspeichers in Megabyte (MB) an.<\/li>\n\n\n\n<li><code>vm_vcpus<\/code> spezifiziert die Anzahl CPUs der VM.<\/li>\n\n\n\n<li><code>vm_net<\/code> bezeichnet das KVM-Netzwerk, mit dem die VM verbunden wird.<\/li>\n\n\n\n<li><code>os_type<\/code> wird aktuell noch nicht verwendet.<\/li>\n\n\n\n<li><code>file_type<\/code> gibt den Typ der Image-Datei an.<\/li>\n\n\n\n<li><code>base_image_name<\/code> verweist auf den Namen der zu verwendenden Vorlage, die zuvor manuell installiert wurde.<\/li>\n\n\n\n<li><code>vm_template<\/code> referenziert eine Jinja2-Template-Datei, welche wir uns im n\u00e4chsten Abschnitt anschauen werden.<\/li>\n\n\n\n<li><code>second_hdd<\/code> kann auf <code>true<\/code> oder <code>false<\/code> gesetzt werden und bestimmt, ob einer VM eine zweite Festplatte hinzugef\u00fcgt werden soll.<\/li>\n\n\n\n<li><code>second_hdd_size<\/code> gibt die Gr\u00f6\u00dfe der zweiten Festplatte in Megabyte (MB) an.<\/li>\n<\/ul>\n\n\n\n<p>F\u00fchrt man diese Rolle mit einem Playbook aus, ohne eigene Variablen zu definieren, werden also zwei VMs mit den Namen test und test2 sowie den obigen Parametern erstellt.<\/p>\n\n\n\n<p>Um die Rolle m\u00f6glichst flexibel einsetzen und wiederverwenden zu k\u00f6nnen, werden die gew\u00fcnschten Labor-Umgebungen in separaten Dateien definiert. F\u00fcr mein RHEL-Lab habe ich die ben\u00f6tigten Variablen in die Datei <code>vars\/rhel_lab.yml<\/code> geschrieben, welche ich mit <code>ansible-vault create vars\/rhel_lab.yml<\/code> erstellt habe. So bleiben mein gew\u00e4hltes Passwort sowie Pfad zu und Name von meinem SSH-Public-Key vor neugierigen Blicken gesch\u00fctzt. Der Inhalt der Datei entspricht vom Aufbau her jedoch dem aus obigem Code-Block der <code>defaults\/main.yml<\/code>. Wie die Datei <code>rhel_lab.yml<\/code> genutzt wird, wird in Abschnitt &#8222;<a href=\"#Playbook\">Das Playbook<\/a>&#8220; erl\u00e4utert.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"Templates\">Templates<\/h3>\n\n\n\n<p>In der KVM-Terminologie wird eine VM auch als Gast-Domain (engl. guest domain) bezeichnet. Die Definition der Gast-Domain kann in Form einer XML-Datei erfolgen.  In diesem Abschnitt werde ich zeigen, wie man die Konfiguration einer bestehenden VM in eine XML-Datei schreibt, um diese anschlie\u00dfend als Template f\u00fcr neue VMs zu benutzen.<\/p>\n\n\n\n<p>Im Vorfeld habe ich die VMs rhel7-template und rhel8-template manuell installiert. Diese werde ich nun nutzen, um daraus Jinja2-Templates abzuleiten, welche ich innerhalb der Rollen-Verzeichnisstruktur im Verzeichnis <em>templates<\/em> ablege. Der folgende Codeblock zeigt den Befehl exemplarisch f\u00fcr das rhel7-template:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ sudo virsh dumpxml rhel7-template &gt;templates\/rhel7-template.xml.j2<\/code><\/pre>\n\n\n\n<p>Das <em>rhel8-template.xml.j2<\/em> wird auf die gleiche Weise erzeugt. Der Inhalt wird im Folgenden auszugsweise dargestellt:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;domain type='kvm'&gt;\n  &lt;name&gt;rhel8-template&lt;\/name&gt;\n  &lt;uuid&gt;cb010068-fe32-4725-81e8-ec24ce237dcb&lt;\/uuid&gt;\n  &lt;metadata&gt;\n    &lt;libosinfo:libosinfo xmlns:libosinfo=\"http:\/\/libosinfo.org\/xmlns\/libvirt\/domain\/1.0\"&gt;\n      &lt;libosinfo:os id=\"http:\/\/redhat.com\/rhel\/8-unknown\"\/&gt;\n    &lt;\/libosinfo:libosinfo&gt;\n  &lt;\/metadata&gt;\n  &lt;memory unit='KiB'&gt;2097152&lt;\/memory&gt;\n  &lt;currentMemory unit='KiB'&gt;2097152&lt;\/currentMemory&gt;\n  &lt;vcpu placement='static'&gt;1&lt;\/vcpu&gt;\n&#91;...]\n  &lt;devices&gt;\n    &lt;emulator&gt;\/usr\/bin\/qemu-system-x86_64&lt;\/emulator&gt;\n    &lt;disk type='file' device='disk'&gt;\n      &lt;driver name='qemu' type='qcow2'\/&gt;\n      &lt;source file='\/var\/lib\/libvirt\/images\/rhel8-template.qcow2'\/&gt;\n      &lt;target dev='vda' bus='virtio'\/&gt;\n      &lt;address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'\/&gt;\n    &lt;\/disk&gt;\n    &lt;disk type='file' device='cdrom'&gt;\n      &lt;driver name='qemu' type='raw'\/&gt;\n      &lt;target dev='hdb' bus='ide'\/&gt;\n      &lt;readonly\/&gt;\n      &lt;address type='drive' controller='0' bus='0' target='0' unit='1'\/&gt;\n    &lt;\/disk&gt;\n&#91;...]\n    &lt;interface type='network'&gt;\n      &lt;mac address='52:54:00:0c:8d:05'\/&gt;\n      &lt;source network='default'\/&gt;\n      &lt;model type='virtio'\/&gt;\n      &lt;address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'\/&gt;\n    &lt;\/interface&gt;\n&#91;...]\n  &lt;\/devices&gt;\n&lt;\/domain&gt;<\/code><\/pre>\n\n\n\n<p>Die Template-Dateien sind zu bearbeiten, um aktuell statisch konfigurierte Werte durch Variablen zu ersetzen. Die zu bearbeitenden Zeilen sehen anschlie\u00dfend wie folgt aus:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>&lt;name&gt;{{ item.key }}&lt;\/name&gt;<\/code><\/li>\n\n\n\n<li><code>&lt;memory unit='MiB'&gt;{{ item.value.vm_ram_mb }}&lt;\/memory&gt;<\/code><\/li>\n\n\n\n<li><code>&lt;vcpu placement='static'&gt;{{ item.value.vm_vcpus }}&lt;\/vcpu&gt;<\/code><\/li>\n\n\n\n<li><code>&lt;source file='{{ libvirt_pool_dir }}\/{{ item.key }}.qcow2'\/&gt;<\/code><\/li>\n\n\n\n<li><code>&lt;source network='{{ item.value.vm_net }}'\/&gt;<\/code><\/li>\n<\/ul>\n\n\n\n<p>Dar\u00fcber hinaus sind weitere Zeilen, welche f\u00fcr jede VM einmalig sind, aus den Template-Dateien zu l\u00f6schen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>&lt;uuid&gt;...&lt;\/uuid&gt;<\/code><\/li>\n\n\n\n<li><code>&lt;mac address='...'\/&gt;<\/code><\/li>\n<\/ul>\n\n\n\n<p>In der fertigen <em>rhel8-template.xml.j2<\/em>-Datei sieht es dann wie folgt aus:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;domain type='kvm'&gt;\n  &lt;name&gt;{{ item.key }}&lt;\/name&gt;\n  &lt;metadata&gt;\n    &lt;libosinfo:libosinfo xmlns:libosinfo=\"http:\/\/libosinfo.org\/xmlns\/libvirt\/domain\/1.0\"&gt;\n      &lt;libosinfo:os id=\"http:\/\/redhat.com\/rhel\/8-unknown\"\/&gt;\n    &lt;\/libosinfo:libosinfo&gt;\n  &lt;\/metadata&gt;\n  &lt;memory unit='MiB'&gt;{{ item.value.vm_ram_mb }}&lt;\/memory&gt;\n  &lt;vcpu placement='static'&gt;{{ item.value.vm_vcpus }}&lt;\/vcpu&gt;\n&#91;...]\n  &lt;devices&gt;\n    &lt;emulator&gt;\/usr\/bin\/qemu-system-x86_64&lt;\/emulator&gt;\n    &lt;disk type='file' device='disk'&gt;\n      &lt;driver name='qemu' type='qcow2'\/&gt;\n      &lt;source file='{{ libvirt_pool_dir }}\/{{ item.key }}.qcow2'\/&gt;\n      &lt;target dev='vda' bus='virtio'\/&gt;\n      &lt;address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'\/&gt;\n    &lt;\/disk&gt;\n&#91;...]\n    &lt;interface type='network'&gt;\n      &lt;source network='{{ item.value.vm_net }}'\/&gt;\n      &lt;model type='virtio'\/&gt;\n      &lt;address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'\/&gt;\n    &lt;\/interface&gt;\n&#91;...]\n  &lt;\/devices&gt;\n&lt;\/domain&gt;<\/code><\/pre>\n\n\n\n<p>Solltet ihr zu diesem Abschnitt noch Fragen haben, weil z.B. etwas unverst\u00e4ndlich ist, stellt diese bitte in den Kommentaren oder meldet euch per E-Mail. Ich werde den Abschnitt dann je nach Bedarf erg\u00e4nzen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"Tasks\">Tasks<\/h3>\n\n\n\n<p>Als N\u00e4chstes stelle ich die Tasks vor, welche von dieser Rolle ausgef\u00fchrt werden. Dabei beginne ich mit dem Inhalt der Datei <code>tasks\/main.yml<\/code>, deren Inhalt ich nach dem folgenden Codeblock erl\u00e4utern werde.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ cat -n tasks\/main.yml \n     1\t---\n     2\t# tasks file for kvm_provision_lab\n     3\t- name: Ensure requirements are in place\n     4\t  apt:\n     5\t    name:\n     6\t      - libguestfs-tools\n     7\t      - python3-libvirt\n     8\t    state: present\n     9\t  become: yes\n    10\t\n    11\t- name: Get VMs list\n    12\t  community.libvirt.virt:\n    13\t    command: list_vms\n    14\t  register: existing_vms\n    15\t  changed_when: no\n    16\t\n    17\t- name: Copy base image\n    18\t  copy:\n    19\t    dest: \"{{ libvirt_pool_dir }}\/{{ item.key }}.{{ item.value.file_type }}\"\n    20\t    src: \"{{ libvirt_pool_dir }}\/{{ item.value.base_image_name }}.{{ item.value.file_type }}\"\n    21\t    force: no\n    22\t    remote_src: yes\n    23\t    mode: 0660\n    24\t    group: libvirt-qemu\n    25\t  register: copy_results\n    26\t  with_dict: \"{{ guests }}\"\n    27\t\n    28\t- name: Create VMs if not exist\n    29\t  include_tasks: create_vms.yml\n    30\t  when: \"item.key not in existing_vms.list_vms\"\n    31\t  with_dict: \"{{ guests }}\"<\/code><\/pre>\n\n\n\n<p>Der Task in Zeile 3-9 stellt sicher, dass die notwendigen Voraussetzungen erf\u00fcllt sind, um sich mit <code>libvirt<\/code> verbinden zu k\u00f6nnen. Der Paketname <code>libguestfs-tools<\/code> existiert unter CentOS Stream, Debian und RHEL. Unter Fedora hei\u00dft das Paket <code>guestfs-tools<\/code>. Der Name muss an die entsprechende Distribution angepasst werden.<\/p>\n\n\n\n<p>In Zeile 11-15 wird das Modul <code>community.libvirt.virt<\/code> verwendet, um die Liste der bereits existierenden VMs abzurufen und in der Variablen <code>existing_vms<\/code> zu speichern. Diese wird sp\u00e4ter genutzt, um nur dann eine VM zu provisionieren, wenn nicht bereits eine VM mit dem gleichen Namen existiert. Es ist quasi ein schmutziger Trick, um der Rolle ein wenig <a href=\"https:\/\/de.wikipedia.org\/wiki\/Idempotenz\">Idempotenz<\/a> einzuhauchen. Da mit diesem Task nur Informationen abgefragt werden, jedoch keinerlei \u00c4nderungen vorgenommen werden, setzt man <code>changed_when: no<\/code>.<\/p>\n\n\n\n<p>Das Copy-Modul in Zeile 17-26 kopiert die qcow2-Image-Dateien an den vorgesehenen Zielort und setzt Zugriffsrechte entsprechend. Zeile 19 sorgt daf\u00fcr, dass die Zieldatei den Namen der neuen VM beinhaltet. Da das Copy-Modul bereits idempotent arbeitet, werden die Dateien nur kopiert, wenn das Ziel nicht bereits existiert. Das Ergebnis des Kopiervorgangs wird in <code>copy_results<\/code> gespeichert.<\/p>\n\n\n\n<p>Der letzte Task f\u00fchrt die Task-Datei <code>create_vms.yml<\/code> f\u00fcr die VMs aus, die nicht bereits existieren. Daf\u00fcr sorgt die Bedingung <code>when: \"item.key not in existing_vms.list_vms\"<\/code>, die diesem Task zu Idempotenz verhilft. Das Playbook selbst hat folgenden Inhalt:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ cat -n tasks\/create_vms.yml \n     1\t---\n     2\t- name: Configure the image\n     3\t  command: |\n     4\t    virt-customize -a {{ libvirt_pool_dir }}\/{{ item.key }}.qcow2 \\\n     5\t    --hostname {{ item.key }} \\\n     6\t    --root-password password:{{ vm_root_pass }} \\\n     7\t    --ssh-inject 'root:file:{{ ssh_key }}' \\\n     8\t    --uninstall cloud-init --selinux-relabel\n     9\t  when: copy_results is changed\n    10\t\n    11\t- name: Define VMs\n    12\t  community.libvirt.virt:\n    13\t    command: define\n    14\t    xml: \"{{ lookup('template', '{{ item.value.vm_template }}.xml.j2') }}\"\n    15\t\n    16\t- name: Create second disk images if needed\n    17\t  command: |\n    18\t    qemu-img create -f {{ item.value.file_type }} \\\n    19\t    {{ libvirt_pool_dir }}\/{{ item.key }}-vdb.{{ item.value.file_type }} {{ item.value.second_hdd_size }}\n    20\t  become: yes\n    21\t  when: item.value.second_hdd|bool == true\n    22\t\n    23\t- name : Attach second disk image to domain\n    24\t  command: |\n    25\t    virsh attach-disk {{ item.key }} \\\n    26\t    --source \"{{ libvirt_pool_dir }}\/{{ item.key }}-vdb.{{ item.value.file_type }}\" \\\n    27\t    --target vdb \\\n    28\t    --persistent\n    29\t  become: yes\n    30\t  when: item.value.second_hdd|bool == true\n    31\t\n    32\t- name: Ensure VMs are startet\n    33\t  community.libvirt.virt:\n    34\t    name: \"{{ item.key }}\"\n    35\t    state: running\n    36\t  register: vm_start_results\n    37\t  until: \"vm_start_results is success\"\n    38\t  retries: 15\n    39\t  delay: 2<\/code><\/pre>\n\n\n\n<p>Der Task in Zeile 2-9 konfiguriert den Inhalt der qcow2-Image-Datei. Die Bedingung <code>when: copy_results is changed<\/code> stellt sicher, dass dies nur passiert, wenn die Image-Datei zuvor an ihren Zielort kopiert wurde. Damit wird sichergestellt, dass nicht eine bereits vorhandene Image-Datei einer existierenden VM nochmals ver\u00e4ndert wird. Der Task konfiguriert den Hostnamen, setzt das Root-Passwort und hinterlegt den SSH-Public-Key.<\/p>\n\n\n\n<p>Der n\u00e4chste Task ab Zeile 11 definiert\/erstellt die neue VM aus den XML-Template-Dateien.<\/p>\n\n\n\n<p>Die beiden Tasks in den Zeilen 16-30 f\u00fcgen einer VM eine zweite Festplatte hinzu, wenn dies in <code>defaults\/main.yml<\/code> bzw. <code>vars\/rhel_lab.yml<\/code> entsprechend definiert wurde.<\/p>\n\n\n\n<p>Der letzte Task sorgt schlie\u00dflich daf\u00fcr, dass die neu erstellten VMs eingeschaltet werden.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"Playbook\">Das Playbook<\/h2>\n\n\n\n<p>Im Vergleich zu den Dateien mit den einzelnen Tasks f\u00e4llt das Playbook eher kurz aus:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code> cat -n kvm_provision_rhel_lab.yml \n     1\t---\n     2\t- name: Provision RHEL lab VMs\n     3\t  hosts: localhost\n     4\t  vars_files:\n     5\t    - roles\/kvm_provision_lab\/vars\/rhel_lab.yml\n     6\t  tasks:\n     7\t    - name: Run role kvm_provision_lab\n     8\t      include_role:\n     9\t        name: kvm_provision_lab<\/code><\/pre>\n\n\n\n<p>In Zeile 3 ist der KVM-Hypervisor anzugeben, auf dem die Rolle ausgef\u00fchrt werden soll. Dies kann, wie in meinem Fall, der gleiche Host wie der Ansible-Control-Node sein.<\/p>\n\n\n\n<p>In Zeile 4 und 5 wird die Datei geladen, welche die Variablen f\u00fcr die zu erstellende Laborumgebung enth\u00e4lt. Ohne diese Anweisung werden die Werte aus <code>defaults\/main.yml<\/code> verwendet.<\/p>\n\n\n\n<p>Abschlie\u00dfend wird die Ansible-Rolle inkludiert. Dies ist auch schon alles.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"Zusammenfassung\">Zusammenfassung<\/h2>\n\n\n\n<p>Das Schreiben dieses Artikels hat deutlich l\u00e4nger gedauert als die Erstellung der eigentlichen Ansible-Rolle zur Erstellung einer Laborumgebung unter KVM.<\/p>\n\n\n\n<p>Die einzelnen Abschnitte beschreiben das Vorgehen und die Bestandteile der Rolle im Detail. Ich hoffe, damit deren Funktionsweise deutlich gemacht zu haben. <\/p>\n\n\n\n<p>Ich kann nun meine Labor-Umgebungen in Dateien wie <code>rhel_lab.yml<\/code>, <code>debian_lab.yml<\/code>, etc. definieren und die Rolle dazu verwenden, diese zu provisionieren. So steht mir in kurzer Zeit eine frische Testumgebung bereit. Und zwar jedes Mal aufs neue, wenn ich sie ben\u00f6tige.<\/p>\n\n\n\n<p>Wenn euch dieser Artikel dabei hilft, eigene Labor-Umgebungen mithilfe von Ansible zu provisionieren freut mich dies umso mehr.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"quellen-links\">Quellen und weiterf\u00fchrende Links<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><a href=\"https:\/\/www.redhat.com\/sysadmin\/build-VM-fast-ansible\">Build a lab in 36 seconds with Ansible<\/a>. <a href=\"https:\/\/www.redhat.com\/sysadmin\/users\/ricardo-gerardi\">Ricardo Gerardi (Red Hat, Sudoer)<\/a>. Enable Sysadmin. 2021-10-22.<\/li>\n\n\n\n<li><a href=\"https:\/\/www.redhat.com\/sysadmin\/virsh-subcommands\">8 Linux virsh subcommands for managing VMs on the command line<\/a>. <a href=\"https:\/\/www.redhat.com\/sysadmin\/users\/ricardo-gerardi\">Ricardo Gerardi (Red Hat, Sudoer)<\/a>. Enable Sysadmin. 2021-09.09.<\/li>\n\n\n\n<li><a href=\"https:\/\/www.redhat.com\/sysadmin\/build-lab-quickly\">Build a lab in five minutes with three simple commands<\/a>. <a href=\"https:\/\/www.redhat.com\/sysadmin\/users\/alex-callejas\">Alex Callejas (Red Hat)<\/a>. Enable Sysadmin. 2021-08-20.<\/li>\n\n\n\n<li><a href=\"https:\/\/mangolassi.it\/topic\/15257\/ansible-create-kvm-guests\">Ansible Create KVM Guests<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/docs.ansible.com\/ansible\/latest\/collections\/community\/libvirt\/virt_module.html#ansible-collections-community-libvirt-virt-module\">community.libvirt.virt \u2013 Manages virtual machines supported by libvirt<\/a><\/li>\n\n\n\n<li>Ansible Dictionary Variables. URL: <a href=\"https:\/\/docs.ansible.com\/ansible\/latest\/user_guide\/playbooks_variables.html#dictionary-variables\">https:\/\/docs.ansible.com\/ansible\/latest\/user_guide\/playbooks_variables.html#dictionary-variables<\/a><\/li>\n\n\n\n<li>Creating valid variable names. URL: <a href=\"https:\/\/docs.ansible.com\/ansible\/latest\/user_guide\/playbooks_variables.html#creating-valid-variable-names\">https:\/\/docs.ansible.com\/ansible\/latest\/user_guide\/playbooks_variables.html#creating-valid-variable-names<\/a><\/li>\n\n\n\n<li>Die Rolle kvm_provision_lab auf Galaxy: <a href=\"https:\/\/galaxy.ansible.com\/Tronde\/kvm_provision_lab\">https:\/\/galaxy.ansible.com\/Tronde\/kvm_provision_lab<\/a><\/li>\n\n\n\n<li>Die Rolle kvm_provision_lab auf GitHub: <a href=\"https:\/\/github.com\/Tronde\/kvm_provision_lab\">https:\/\/github.com\/Tronde\/kvm_provision_lab<\/a><\/li>\n<\/ol>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Inspiriert durch die Artikel von Ricardo Geradi [1] und Alex Callejas [3] schreibe ich diesen, um zu erkl\u00e4ren, wie mithilfe von Ansible eine Labor-Umgebung bestehend aus einer oder mehreren virtuellen Maschinen (VMs) auf einem KVM-Hypervisor provisioniert werden kann. Dabei handelt es sich weniger um ein Tutorial, sondern mehr um eine exemplarische Beschreibung einer m\u00f6glichen Vorgehensweise,<span class=\"continue-reading\"> <a href=\"https:\/\/www.my-it-brain.de\/wordpress\/labor-umgebung-mit-ansible-in-kvm-erstellen\/\">[Weiterlesen&#8230;]<\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_metis_text_type":"","_metis_text_length":0,"_post_count":0,"footnotes":""},"categories":[532],"tags":[410,674,676,430,305,675],"class_list":["post-3070","post","type-post","status-publish","format-standard","hentry","category-ansible","tag-ansible","tag-kvm","tag-libvirt","tag-osbn","tag-planet","tag-provisionierung"],"_links":{"self":[{"href":"https:\/\/www.my-it-brain.de\/wordpress\/wp-json\/wp\/v2\/posts\/3070","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.my-it-brain.de\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.my-it-brain.de\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.my-it-brain.de\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.my-it-brain.de\/wordpress\/wp-json\/wp\/v2\/comments?post=3070"}],"version-history":[{"count":13,"href":"https:\/\/www.my-it-brain.de\/wordpress\/wp-json\/wp\/v2\/posts\/3070\/revisions"}],"predecessor-version":[{"id":3490,"href":"https:\/\/www.my-it-brain.de\/wordpress\/wp-json\/wp\/v2\/posts\/3070\/revisions\/3490"}],"wp:attachment":[{"href":"https:\/\/www.my-it-brain.de\/wordpress\/wp-json\/wp\/v2\/media?parent=3070"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.my-it-brain.de\/wordpress\/wp-json\/wp\/v2\/categories?post=3070"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.my-it-brain.de\/wordpress\/wp-json\/wp\/v2\/tags?post=3070"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}