Ansible
Ansible ist ein Open-Source Automatisierungs-Werkzeug zur Orchestrierung und allgemeinen Konfiguration und Administration von Computern. Es kombiniert Softwareverteilung, Ad-hoc-Kommando-Ausführung und Konfigurationsmanagement. Es verwaltet Netzwerkcomputer unter anderem über SSH und erfordert keinerlei zusätzliche Software auf dem zu verwaltenden System. Module nutzen zur Ausgabe JSON und können in jeder beliebigen Programmiersprache geschrieben sein. Das System nutzt YAML zur Formulierung wiederverwendbarer Beschreibungen von Systemen. Quelle: Wikipedia
Implementiert ist Ansible in Python und führt Befehle über SSH aus.
Links
- Ansible 2.8 Porting Guide - notwendige Codeanpassungen für Version 2.8
Begriffe
- ad-hoc-Befehl ausführen (nicht interaktiv):
ansible -i hosts -m shell -a "uname -a"
- facts: sind Variablen die auf dem Host erhoben werden, z.B. vom setup-modul:
ansible $HOSTNAME -i hosts -m setup
- inventory: Liste der Ziele (via SSH) auf die Aufgaben angewendet werden sollen
- module: Funktionen von Ansible
- variablen (in Playbooks, als host- oder groupvars, als extra-vars ) siehe auch: Variablen precedence
- handlers: Aufgaben die bei Bedarf ausgeführt werden, z.B service restart
- playbook: Eine Art Drehbuch was zu tun ist (Liste von Anweisungen, Variablen, Tasks und Verweise auf Rollen):
ansible-playbook --limit=$HOSTNAME -i hosts-muca-mucb playbooks/$mein-Playbook.yml
- roles („Rollen“): ausgelagerte Playbooks, die zur Wiederverwendung optimiert sind
- tasks: einzelne Arbeitsschritte die jeder Host durchläuft
- vault: Speicher für Zugangsdaten
- ansible-galaxy (requirements): Sammlung von Rollen unter https://galaxy.ansible.com
- YAML: ist einfache Auszeichnungssprache dessen Syntax alle Dateien haben müssen
Playbook-läufe auswerten
Am Ende jedes Laufes wird ein „Play Recap“ angezeigt:
PLAY RECAP ********************************************************************************************** Host1 : ok=6 changed=1 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
Bedeutung:
- Ok: Zustand vom task ist gegeben
- changed: Aufgabe wurde ausgeführt um das Ziel des taks zu erreichen (ggf. wurden handler ausgelöst)
- unreachable: host nicht erreichbar
- failed: task fehlgeschlagen, Ausführung endet hier für diesen Host
- skipped: tasks wurden wegen Bedingung nicht ausgeführt
- rescued: ein fehlgeschlagener taks wurde durch einen rescue-Block abgefangen
Wiederholbarkeit (Idempotenz)
Definition von Idempotenz (engl. „idempotence“): „the property of certain operations in mathematics and computer science that can be applied multiple times without changing the result beyond the initial application“ (Quelle)
Generell geht darum bestimmte Aufgaben wiederholbar zu machen, d.h. eine Aufgabe sollte nicht bei jedem Lauf neu ausgeführt werden (was weitere Aktionen wie Server-restarts bedeuten kann) sondern nur wenn sich etwas ändert. Das Ergebnis sollte aber auch jedes mal die gleiche sein.
Installation
Grundsätzlich ist es möglich die Paketquelle der Distribution zu benutzen und auch Python und Jinja2 darüber zu installieren. Ich rate allerdings davon ab weil die Entwicklung bei Ansible und Python (incl. jinja2) i.d.R. schneller geht als man den ausführenden Rechner aktuell hält (und der ist entscheidend weil dort der Code geparst wird).
Mit pip können die installierte Version schnell und unabhängig vom System verändert werden.
pip kommt mit dem Paket python-pip oder über andere Methoden.
Systemweit
- aktuelle Version:
sudo -H pip install ansible
- bestimmte-Version:
sudo -H pip install ansible==2.7.8
nur für den aktuellen Benutzer (nach /.local/bin):
pip install ansible
(upgrade später:pip install ansible –upgrade
)- Pfad erweitern (in der .bashrc):
PATH=~/.local/bin/:$PATH
Beispielsetup ansible-master
Anforderung: Login via eigenem Systembenutzer, gemeinsames Repo und Vault 1). gemeinsame Gruppe: admins
- Einrichtung:
sudo chgrp admins /etc/ansible-vault-password sudo chmod g+ws /etc/ansible-vault-password sudo chgrp admins /etc/ansible sudo chmod g+ws /etc/ansible cd /etc/ansible sudo chgrp admins -R * sudo chmod g+ws -R * sudo chmod g+w .gitignore sudo chgrp admins .gitignore sudo chmod g+ws .git sudo chgrp admins .git cd .git sudo chmod g+ws -R * sudo chgrp admins -R *
- das /etc/ansible-Verzeichnis ist für die Gruppe admins schreibbar
- neue Dateien werden durch sticky-bit auf Gruppe admins gesetzt
- umask muss 002 sein, bereits in der
/etc/profiles
eingetragen:if [ $UID -gt 199 ] && ([ "`id -gn`" = "`id -un`" ] || [ "`id -gn`" = "admins" ] ); then umask 002 else umask 022 fi
- vault-passwort außerhalb des Repos
/etc/ansible-vault-password
- ansible.cfg
[defaults] host_key_checking=False remote_user=root private_key_file = /etc/ansible/.priv_key/id_rsa_deploy # format of string {{ ansible_managed }} available within Jinja2: ansible_managed = Ansible managed: {file} modified on %Y-%m-%d %H:%M:%S on {host} # vault # handling: "ansible-vault view|edit|rekey vault_tpi.yml" vault_file = vault_tpi.yml vault_password_file = /etc/ansible-vault-password
Konfiguration
SSH-Fingerprint check
Problem: Bei neue Hosts sind die Fingerprints lokal unbekannt, d.h. Ansible scheitert an der interaktiven Nachfrage von SSH (ob der Fingerprint akzeptiert werden soll).
Das könnte auf zwei Arten gelöst werden:
- Entweder man deaktiviert den Check komplett
- per Config (es werden alle Fingerprints ignoriert): in ansible.cfg (/etc/ansible/ansible.cfg oder ~/.ansible.cfg)
[defaults] host_key_checking = False
- fallweise über Umgebungsvariable (ANSIBLE_HOST_KEY_CHECKING = False), z.B. mit
export ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook …
- etwas unschöner auch über die SSH-Config (fallweise
–ssh-common-args='-o StrictHostKeyChecking=no
' bzw. permanent) - oder einen task schreiben um die neuen Fingerprints lokal abzulegen:
- name: Write the new ec2 instance host key to known hosts connection: local shell: "ssh-keyscan -H {{ inventory_hostname }} >> ~/.ssh/known_hosts"
Quellen: https://stackoverflow.com/questions/32297456/how-to-ignore-ansible-ssh-authenticity-checking
ansible-galaxy
.ansible/roles
ad-hoc Befehle ausführen
Beispiel: das inventory in der Datei hosts enthält ein Gruppe „meine-Server“ auf denen der Befehl „uname -a“ ausgeführt werden soll:
ansible -i hosts meine-Server -a 'uname -a
'
Ansible vault
Passwort (für den vault) angeben:
- Interaktiv:
--ask-vault-pass
- in Skripten:
--vault-password-file ~/.vault_pass
- via Umgebungsvariable: ANSIBLE_VAULT_PASSWORD_FILE (
export ANSIBLE_VAULT_PASSWORD_FILE=~/.vault_pass.txt
) - Anweisung
vault_password_file = /path/to/vault_password_file
via config-Datei:- Umgebungsvariable ANSIBLE_CONFIG (
export ANSIBLE_CONFIG=~/.ansible.cfg
) - pro Benutzer: ~/.ansible.cfg
- systemweit: /etc/ansible/ansible.cfg
Verwaltung des Vault-files:
- anlegen:
ansible-vault create myVaultfile.yml
- editieren 2):
ansible-vault edit myVaultfile.yml
- anzeigen:
ansible-vault view myVaultfile.yml
- entschlüsseln:
ansible-vault decrypt myVaultfile.yml
- Passwort ändern:
ansible-vault rekey myVaultfile.yml
verschlüsselte Zeichenketten
Statt eines vault-files sind auch einzelne verschlüsselte Zeichenketten (encrypt_string) möglich, beispielweise z.B. um einzelne Variablen zu befüllen.
Beispiel: Hier bekommt die Variable DB_password den Inhalt „test“, was mit Passwort „test“ verschlüsselt wird:
- interaktiv nach verschlüsselter Zeichenkette fragen:
ansible-vault encrypt_string
- … oder den Standard-input (=stdin) nehmen (via echo, cat, …) und die Ansible-Variable DB_password befüllen:
echo -n test | ansible-vault encrypt_string --stdin-name 'DB_password' New Vault password: Confirm New Vault password: Reading plaintext input from stdin. (ctrl-d to end input) DB_password: !vault | $ANSIBLE_VAULT;1.1;AES256 34343731306634383662333232613932373137626262643234633561356464663430343430636532 3863333730383735363965643363386462376634366136650a353463663661316163373530386337 38353365383830613935363838653833636530353537383834643865333130323032386661643132 3132306637383466610a626164653830323565333635396662653066636237656262313965373263 6264 Encryption successful
→ diese Ausgabe kann direkt in yaml-Dateien benutzt werden.
Ein Playbook das verschlüsselte vault-strings benutzt muss von nun an bei m Aufruf entschlüsselt werden: ansible-playbook site.yml --ask-vault-pass
In awx/tower muss der credential type=Vault vorhanden sein: https://docs.ansible.com/ansible-tower/latest/html/userguide/credentials.html#id13
Verwendung von vault files in playbooks
Ansible muss wissen das Variablen im vault-file liegen:
vars_files: - myVaultfile.yml
mehrere Benutzer (Vault-IDs)
–vault-id @prompt
https://docs.ansible.com/ansible/latest/user_guide/vault.html#vault-ids-and-multiple-vault-passwords
in awx/tower: https://docs.ansible.com/ansible-tower/3.4.0/html/administration/multi-creds-assignment.html#ag-multi-vault
Verwaltung
awx
Inventory
Datei /etc/ansible/hosts umfasst alle Server die durch Ansible angesprochen/verwaltet werden. Es ist möglich die Server sinnvoll in [Gruppen] zu unterteilen. Es können außerdem Gruppen aus anderen Gruppen erstellt werden.
Wichtig ist dabei zu beachten: Hostname soll als FQDN angegeben werden.
Beispiel: „Gruppen“ setzt sich zusammen aus Gruppe1 und Gruppe2:
[Gruppen:children] Gruppe1 Gruppe2 [Gruppe1] ... [Gruppe2]
Variablen
Variablen können vielseitig überschrieben werden. Ein paar Auswahlmöglichkeiten:
- die role-defaults /roles/$Rollenname/vars/defaults/main.yml (wichtige Variablen werden definiert und sinnvolle/sichere Standardwerte gesetzt)
- inventory-vars (hostspezifisch): neben dem hostname variable=wert
- group-vars (gruppenspezifisch): group_vars/GRUPPENNAME oder all
- playbook-vars (für diesen run, diese Verwendung, z.B. create, destroy-Abzweigungen)
- extra vars (bei der Laufzeit des playbooks übergeben):
export system_variable=test; ansible-playbook …
playbook_Variable: "{{ lookup('env', 'system_variable') }}"
Siehe auch die vollständige Reihenfolge.
Rollen
Wiederverwendbare tasks/Aufgaben sollten möglichst in wiederverwendbare Rollen ausgelagert werden.
Suchreihenfolge von Ansible ist standardmäßig:
- zuerst in ./roles
- dann Benutzerverzeichnis ~/.ansible/roles (das wäre mit einem individuellen Login schlecht weil für andere nicht auffindbar)
- dann in /etc/ansible/roles
Falls auf dem ansible-master mit mehreren Benutzer gearbeitet werden soll, kann auch der Systempfad /etc/ansible/roles benutzt werden.
Für verwendete Rollen lässt sich eine requirements.yml anlegen.
sudo ansible-galaxy install --roles-path=/etc/ansible/roles -r external-requirements-ansible-host.yaml
Beispielrollen
Software:
- Webserver
- Apache (php-fpm ) (https://ansible-manual.readthedocs.io/en/v1.8.4-doc/apache2_module_module.html|Apache2-Module (de-)aktivieren) / Reverse-Proxy role?
- nginx / nginx-Reverse-Proxy role?
- Nextcloud (checken):
Instruktur
Module
Ansible ist modular aufgebaut
- set_fact-Module (die Liste der facts erweitern)
Arten der Kommandoausführung
Datei-handling
weitere Module siehe Files modules.
Modulname (Link zur Doku) | Zweck, geeignet für… | nicht geeignet für … |
---|---|---|
copy | Dateien kopieren oder erzeugen (entweder aus anderen Dateien oder aus Variablen (mit Option „content“). Kann eine valide Datei-Syntax durch Aufruf eines Programm validieren um z. B. Syntaxfehler bei sudoers-Dateien oder sonstiger inhaltliche Fehler zu vermeiden (mit der Konsequenz das Dienste sterben) → siehe validate | wenn der Inhalt verändert werden soll: template oder assemble nehmen. |
file | Dateien und Rechte ändern | |
synchronize | rsync | |
template | Dateien erzeugen (wenn der Inhalt der Datei durch Variablen verändert werden muss) | |
unarchive / archive | Archivhandling (bz2, gz, tar, xz, zip) |
Achtung bei Dateirechten in octal-Schreibweise
die Angaben in octal (z.B. 600 für owner rw) müssen komplett als mode: 0644 oder in einfachen Anführungszeichen mode: '600' geschrieben werden. Alles andere bringt sonderbare Rechtemasken hervor
Paketmanager-module
Spezialmodule (Auswahl)
Plugins
templates
templates sind via jinja2 implementiert, siehe Dokumentation.
Code-Beispiele
Henne-Ei-Problem: Python nicht installiert
Ansible hat das Henne-Ei-Problem Python zu benötigen aber um das zu Installieren braucht man bereits Python. Bei Debian/Ubuntu in der Minimalinstallation ist Python z.B. nicht installiert.
Aber auch das lässt sich mit Ansible-Mitteln lösen, dazu muss
- erstmal das erheben der facts („gather_facts: False“) abgeschaltet werden (weil das bereits mit Python realisiert ist)
- und zuletzt explizit nachgeholt werden (mit dem setup-Modul) um in weiteren tasks auf die facts zugreifen zu können.
Hier ein passendes Playbook:
--- - hosts: all gather_facts: False # become: yes pre_tasks: - name: Install python for Ansible raw: test -e /usr/bin/python || (apt-get -y update && apt install -y python) register: output changed_when: output.stdout != "" tasks: - name: Gathering Facts now setup:
https://gist.github.com/gwillem/4ba393dceb55e5ae276a87300f6b8e6f
Ausgaben und Ausgabe eines Befehls sichern
Achtung: Wiederverwendung der Variable myoutput in anderen taks führt auch bei skip des tasks zur Veränderung des Inhaltes, siehe https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#registering-variables .
--- - hosts: all tasks: - name: uname anzeigen command: uname -a register: myoutput - name: show output (alle attributes) debug: msg: "{{ myoutput }}" - name: show standard output (stdout multiline) debug: msg: "{{ myoutput.stdout_lines }}" when: myoutput.stdout_lines is defined and myoutput.stdout_lines != '' and myoutput.stdout_lines != none - name: show error output (stderr multiline) debug: msg: "{{ myoutput.stderr_lines }}" when: myoutput.stderr_lines is defined and myoutput.stderr_lines != '' and myoutput.stderr_lines != none
Errorhandling
include / import
- include_tasks
include_role: name: meineRolle
- import_playbook
handler verwenden
Bestimmte Aufgaben werden nur bei bestimmten Anlässen gestartet. Das lässt sich einerseits über Bedingungen erledigt (when: Bedingung == true) andererseits können sog. handler von beliebigen Aufgaben aus angesteuert werden um eine Aufgabe wie z.B. einen Service-restart zu erledigen (Anlass: Config geändert).
Dies lässt sich mit „notify“ erledigen (Beispiel vmware-tools installieren + Dienst starten incl. autostart):
- name: "Ensure vmware guest tools are installed" package: name="open-vm-tools" state=present when: - ansible_virtualization_role == "guest" - ansible_virtualization_type == "VMware" notify: Start and enable vmtoolsd
ein entsprechender handler wäre dieser:
handlers/main.yml
--- - name: Start and enable vmtoolsd service: name: vmtoolsd state: started enabled: yes
Der handler wird nur ausgeführt wenn sich etwas ändert (task: „changed“), d.h. ist das Paket installiert ABER der Dienst nicht gestartet oder aktiviert wird der handler nie aufgerufen.
Conditionals
Robuste Abfragen sollten berücksichtigen dass die Variable run_task1 nicht definiert sein könnte und Typumwandlungen beachten (siehe unten):
- include_tasks: Aufgabe1.yml when: run_task1 is defined and run_task1|bool
Hintergrund: Umwandlungen in bool
https://github.com/ansible/ansible/issues/11905. Lösung:
a: !!str yes b: 'yes'
Beispiel und Erklärung: php_display_errors: !!str On
(speichert „On“ explizit als string damit ''php_display_errors: On' erhalten bleibt).
Zusätzlich muss die Auswertung von bool-Variablen in Zukunft (ab Version 2.12) explizit angegeben werden:
[DEPRECATION WARNING]: evaluating ntp_enabled as a bare variable, this behaviour will go away and you might need to add |bool to the expression in the future. Also see CONDITIONAL_BARE_VARS configuration toggle.. This feature will be removed in version 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
Beispiel:
- name: Ensure NTP is running and enabled as configured. service: name: "{{ ntp_daemon }}" state: started enabled: yes when: ntp_enabled
die letzte Zeile sollte verändert werden in „when: ntp_enabled|bool“.