Im ersten Teil meines Quickstart Guides habe ich gezeigt, wie man per Terraform Script neue VMs bzw. Guests unter KVM libvirt sehr flexibel und schnell an den Start bringt.
Sobald die virtuellen Maschinen erst mal grundsätzlich laufen, kann man mit Ansible die weitere Konfiguration vornehmen und zusätzliche Software installieren. Ansible ist ein Open-Source Konfigurationsmanagement und Administrations Tool, um immer wiederkehrende Arbeiten an Systemen zu automatisieren. Es ist quasi des Admins bester Freund, um ihm lästige Wartungsarbeiten abzunehmen. Ansible ist eine leicht erlernbare und lesbare Skriptbeschreibungssprache im YAML Format. Es braucht dabei selbst sehr wenig Ressourcen und ist sehr flexibel. Ansible arbeitet agentless. Das bedeutet, dass kein Ansible Agent auf den Zielsystemen installiert werden muss (so wie z.B. für Puppet). Ansible benutzt stattdessen beispielsweise auf Linux-Systemen die SSH Verbindung mit private/public Keys für eine sichere Kommunikation mit den Zielsystemen.
Ansible verwendet sogenannte Playbooks (YAML Format und Syntax) in denen die Plays als Tasks beschrieben sind, die auf den Zielsystemen abgearbeitet werden sollen. Daneben gibt es ein Inventory-File, in dem unter anderem die Hostnamen hinterlegt sind. Wiederkehrende Aufgaben können auch in Module ausgelagert werden. Ansible bietet hier schon bereits selbst viel an, um die Standardaufgaben zu erledigen, es können aber auch eigene Module entwickelt werden. Ansible Playbooks sind außerdem idempotent. D.h. ein Playbook kann mehrmals ausgeführt werden und liefert immer das gleiche Ergebnis zurück. Ein Playbook beschreibt damit den Zielzustand eines Systems.
Aber fangen wir endlich an …
Vorbereitungen und Installation
Die Installation von Ansible unter Linux geht einfach vonstatten, siehe auch hier: https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html#installing-ansible-on-rhel-centos-or-fedora
juergen ~ sudo dnf install ansible
Letzte Prüfung auf abgelaufene Metadaten: vor 0:37:27 am Mi 18 Nov 2020 15:23:03 CET.
Abhängigkeiten sind aufgelöst.
======================================================================================================================================================
Package Architecture Version Repository Size
======================================================================================================================================================
Installieren:
ansible noarch 2.9.14-1.fc32 updates 15 M
Abhängigkeiten werden installiert:
libsodium x86_64 1.0.18-3.fc32 fedora 169 k
python3-babel noarch 2.8.0-2.fc32 fedora 5.7 M
python3-bcrypt x86_64 3.1.7-4.fc32 fedora 44 k
python3-fluidity-sm noarch 0.2.0-18.fc32 fedora 19 k
python3-jinja2 noarch 2.11.2-1.fc32 updates 490 k
python3-jmespath noarch 0.9.4-4.fc32 fedora 46 k
python3-lexicon noarch 1.0.0-10.fc32 fedora 18 k
python3-ntlm-auth noarch 1.1.0-9.fc32 fedora 50 k
python3-pynacl x86_64 1.3.0-6.fc32 fedora 102 k
python3-pyyaml x86_64 5.3.1-1.fc32 fedora 202 k
python3-requests_ntlm noarch 1.1.0-10.fc32 fedora 18 k
python3-xmltodict noarch 0.12.0-7.fc32 fedora 23 k
Schwache Abhängigkeiten werden installiert:
python3-invoke noarch 1.4.1-1.fc32 fedora 149 k
python3-paramiko noarch 2.7.1-2.fc32 fedora 287 k
python3-pyasn1 noarch 0.4.8-1.fc32 fedora 133 k
python3-winrm noarch 0.3.0-9.fc32 fedora 59 k
Transaktionsübersicht
======================================================================================================================================================
Installieren 17 Pakete
Gesamte Downloadgröße: 23 M
Installationsgröße: 131 M
Mit ansible –help kann überprüft werden, ob die Installation geklappt hat.
Ansible benötigt zur Kommunikation mit den Zielsystemen unter Linux eine SSH Verbindung, die per SSH-Keypair aufgebaut wird. Dafür erstellen wir uns zuerst einen eigenen Benutzer.
juergen ~ sudo useradd -m ansible
juergen ~ sudo passwd ansible
ändere Passwort für Benutzer ansible.
Geben Sie ein neues Passwort ein:
Geben Sie das neue Passwort erneut ein:
passwd: alle Authentifizierungsmerkmale erfolgreich aktualisiert.
Dann erstellen wir uns ein SSH-Keypair für den Ansible User.
juergen ~ su - ansible
Passwort:
[ansible@localhost ~]$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/ansible/.ssh/id_rsa):
Created directory '/home/ansible/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/ansible/.ssh/id_rsa
Your public key has been saved in /home/ansible/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:2 ansible@localhost.localdomain
The key's randomart image is:
+---[RSA 3072]----+
| . |
| o |
+----[SHA256]-----+
[ansible@localhost ~]$ ll .ssh
insgesamt 8
-rw-------. 1 ansible ansible 2622 18. Nov 16:13 id_rsa
-rw-r--r--. 1 ansible ansible 583 18. Nov 16:13 id_rsa.pub
[ansible@localhost ~]$
Falls wir unsere VMs, wie im ersten Teil meiner Installationsanleitung beschrieben, per Terraform aufsetzen, ist es sinnvoll den Public Key gleich darüber bereitzustellen. Den Inhalt aus der Datei id_rsa.pub kopieren wir dazu in die Terraform cloudinit.cfg Datei, wie in Teil 1 beschrieben und verteilen somit den Public Key auf alle neuen Zielsysteme. Man kann das zwar auch jederzeit nachträglich manuell tun. Dazu muss man aber auf jedem System zuerst einmal einen Benutzer ansible anlegen und dann vom Ansible Server aus als Benutzer ansible per ssh-copy-id Befehl den Public-Key an alle Zielserver verteilen.
Ansible Projektdateien
Zunächst benötigen wir einen Projektordner, in meinem Beispiel „newsrv“, in dem wir die Ansible Dateien erstellen werden. Ich werde nachfolgend die benötigten Dateien im Detail erläutern und möchte damit auch anregen eigene Erweiterungen zu machen.
ansible.cfg
Dann erstellen wir eine Konfigurationsdatei Namens ansible.cfg für allgemeine Ansible Settings unseres Projektes. Daneben kann man seine Einstellungen auch in der globalen Konfigurationsdatei unter /etc/ansible vornehmen, die dann für alle Projekte gleichermaßen gilt. In meiner Beispiel-Config ist zu erwähnen, dass ich host_key_checking=false gesetzt habe. Das verringert zwar die Sicherheit der SSH Verbindung, ist in meinem Fall aber hilfreich, wenn man die VM öfter mal neu aufsetzt, da sich dann der Hostkey logischerweise jedesmal ändert.
[defaults]
hash_behaviour=merge
host_key_checking=false
[ssh_connection]
pipelining = True
timeout=30
Dann müssen wir noch den private SSH Key des ansible Users, den wir oben erstellt haben, in den Projektordner kopieren. Damit authentifiziert sich nämlich der ausführende ansible user gegenüber den Zielsystemen.
juergen ~ Scripts ansible newsrv sudo cp /home/ansible/.ssh/id_rsa .
juergen ~ Scripts ansible newsrv sudo chown juergen:juergen id_rsa
hosts.yml
Als nächstes erstellen wir uns eine hosts.yml Datei, die den oder die Hostnamen der Zielserver enthält und einige globale Variablen.
all:
vars:
ansible_connection: ssh
ansible_user: ansible
ansible_become: true
ansible_ssh_private_key_file: id_rsa
ansible_python_interpreter: /usr/bin/python3
hosts:
mysrv.mydomain.vm:
install.yml
Dann brauchen wir noch ein Playbook das die eigentlichen Tasks ausführt. Ich werde die Datei hier wieder schrittweise erläutern. Mein Playbook enthält auf der einen Seite Einstellungen und Konfigurationen, die ich grundsätzlich für alle VMs vornehmen möchte. Und dann befinden sich im Playbook auch noch ein paar Beispiele für Software Installationen. Dies soll anregen eigene Tasks und Playbooks zu erstellen. Denn wenn man mal das Prinzip verstanden hat ist das auch gar nicht schwer das Playbook nach seinen Vorstellungen zu erweitern.
Hinweis: Eine Yaml-Datei wird immer mit drei Bindestrichen — eingeleitet und es muss auch strikt auf die richten Einrückungen geachtet werden.
Als erstes bekommt das Playbook einen Namen, in meinem Beispiel wenig kreativ: „Install some packages“. hosts: all zeigt an, dass das Playbook für alle definierten Systeme angewendet werden soll und tasks: leitet schließlich die nachfolgenden Augaben-Blöcke ein.
---
- name: Install some packages
hosts: all
tasks:
Im ersten Task-Block wird IPv6 komplett deaktiviert, da es in meiner VM-Umgebung überflüssig ist.
# Disable ipv6
- name: Disable IPv6 with sysctl
sysctl: name={{ item }} value=1 state=present reload=yes
with_items:
- net.ipv6.conf.all.disable_ipv6
- net.ipv6.conf.default.disable_ipv6
- net.ipv6.conf.lo.disable_ipv6
- name: RedHat | placeholder true for ipv6 in modprobe
lineinfile: "dest=/etc/modprobe.conf line='install ipv6 /bin/true' create=yes"
when: ansible_os_family == 'RedHat'
- name: RedHat | disable ipv6 in sysconfig/network
lineinfile:
dest: /etc/sysconfig/network
regexp: "^{{ item.regexp }}"
line: "{{ item.line }}"
backup: yes
create: yes
with_items:
- { regexp: 'NETWORKING_IPV6=.*', line: 'NETWORKING_IPV6=NO' }
- { regexp: 'IPV6INIT=.*', line: 'IPV6INIT=no' }
notify:
- restart NetworkManager.service
when: ansible_os_family == 'RedHat'
Dann werden alle Pakete auf den neuesten Stand gebracht. Will man unterschiedliche Linux Derivate mit dem gleichen Playbook bedienen, dann muss man eine Abfrage der OS-Family mit einbauen, denn die Betriebssystemfamilien haben bekanntermaßen unterschiedliche Paketmanager (Debian: apt, SuSe: zypper …)
Wenn man außerdem noch sogenannte „tags“ mit in sein Playbook einbaut, dann kann man später auch nur bestimmte Teile seines Playbooks gezielt ausführen. Das bietet sich zum Beispiel beim Upgrade-Task an, weil dieser Vorgang regelmäßig durchgeführt wird.
Der Debug-Task gibt den Inhalt der Variable „result.changed“ an der Konsole aus. Das ist hilfreich, wenn man den Übergabewert nicht von vornherein kennt und somit ausgeben will. Weiter unten im Playbook wird der Status dieser Variable dann relevant für die Entscheidung, ob am Schluss ein Reboot des Servers durchgeführt werden soll.
# Upgrade all packages
- name: upgrade all packages on CentOS/Fedora
yum:
name: "*"
state: latest
when: ansible_os_family == 'RedHat'
register: result
tags:
- upgrade
- debug:
var: result.changed
tags:
- upgrade
Anschließend werden einige Pakete installiert, die ich gern auf allen meinen Systemen hätte und die richtige Zeitzone wird gesetzt.
# Some usefull packages for CentOS
- name: install bind-utils for CentOS
package:
name: bind-utils
state: present
when: ansible_os_family == 'RedHat'
- name: install cronie to create cronjobs for CentOS
package:
name: cronie
state: present
when: ansible_os_family == 'RedHat'
# Install for all distributions Debian/Redhat
- name: install cockpit to monitor server
package:
name: cockpit
state: present
- name: install bash-completion
package:
name: bash-completion
state: present
# Set timezone
- name: set timezone
timezone:
name: Europe/Berlin
Als Beispiel Applikation wird hier die CentOS Software Gruppe „Server mit GUI“ und Firefox als Browser installiert.
# Install Server with GUI
- name: install server with GUI for CentOS
package:
name: '@graphical-server-environment'
state: present
when: ansible_os_family == 'RedHat'
# Install Webbrowser
- name: install firefox
package:
name: firefox
state: present
when: ansible_os_family == 'RedHat'
Außerdem wäre es noch nett, wenn Autologin für den Server aktiviert wäre. Zum Abschluss wird noch ein Reboot durchgeführt falls die Variable des Upgrade Tasks „true“ zurückgibt.
# Activate autologin to console for a user
- name: Creates getty tty1_service_de directory
file:
path: "/etc/systemd/system/getty@tty1.service.d"
state: directory
owner: root
group: root
mode: 0775
tags:
- autologin
- name: Activate console autologin for a user
copy:
dest: "/etc/systemd/system/getty@tty1.service.d/override.conf"
owner: root
group: root
mode: 0644
content: |
# Provided by Ansible
[Service]
ExecStart=
ExecStart=-/sbin/agetty --noissue --autologin juergen %I $TERM
Type=idle
backup: yes
tags:
- autologin
# Reboot when all things done
- reboot:
msg: "Reboot to finish setup."
reboot_timeout: 60
when: result.changed == "true"
Wichtige Ansible Befehle
Nachdem wir nun alle Ansible Dateien in unserem Projektordner zusammen haben, können wir mit dem folgenden Befehl z.B. alle Hosts in diesem Projekt anzeigen lassen:
juergen ~ Scripts ansible newsrv ansible --list-hosts all -i hosts.yml
hosts (1):
mysrv.mydomain.vm
Als nächstes sollten wir die Verbindung zu dem oder die Zielserver durch einen Ansible Ping testen:
juergen ~ Scripts ansible newsrv ansible -m ping all -i hosts.yml
mysrv.mydomain.vm | SUCCESS => {
"changed": false,
"ping": "pong"
}
Wenn das erfolgreich war, dann ist es Zeit unser Playbook laufen zu lassen.
juergen ~ Scripts ansible newsrv ansible-playbook -i hosts.yml install.yml
Wenn nicht gleich alles auf Anhieb funktioniert, nicht gleich aufgeben. Oft sind es sehr banale Fehler, die die Ausführung verhindern. Zum Beispiel ist es mir zu Beginn oft passiert, dass ich Fehler bei den Einrückungen im YAML Code gemacht habe, weil ich etwa den Code aus dem Internet direkt kopiert habe. Am Anfang ist es auch möglich, dass der SSH Verbindungsaufbau per Key-Pair nicht richtig funktioniert. Meistens hilft googlen weiter.
Mit den Tags im Playbook lassen sich dann auch einfach nur Teile des Playbooks, wie z.B. der Upgrade Task, ausführen. So kann man gezielte Aufgaben erledigen, ohne immer gleich das ganze Playbook durchlaufen lassen zu müssen.
juergen ~ Scripts ansible newsrv ansible-playbook -i hosts.yml install.yml --tags upgrade
PLAY [Install some packages] ***************************************************************************************
TASK [Gathering Facts] *********************************************************************************************
ok: [mysrv.mydomain.vm]
TASK [upgrade all packages on CentOS/Fedora] ***********************************************************************
ok: [mysrv.mydomain.vm]
TASK [debug] *******************************************************************************************************
ok: [mysrv.mydomain.vm] => {
"result.changed": false
}
PLAY RECAP *********************************************************************************************************
mysrv.mydomain.vm : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Damit sind wir am Ende des zweiten Teils angelangt. Ich hoffe ich konnte euch inspirieren zukünftig eure KVM/libvirt Infrastruktur per Terraform und Ansible zu verwalten. Würde mich freuen, wenn ihr mir euer Feedback dazu gebt.