====== 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. [[wpde>Ansible|Quelle: Wikipedia]] Ansible ist in Python implementiert und führt Befehle über SSH aus. ===== Links ===== * [[Vergleich zwischen ansible und puppet]] * [[https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#ansible-variable-precedence|Variable precedence: Where should I put a variable?]] * [[https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html|Using filters to manipulate data]] * [[https://docs.ansible.com/ansible/latest/porting_guides/porting_guide_2.8.html|Ansible 2.8 Porting Guide]] - notwendige Codeanpassungen für Version 2.8 * Ansible escaping: https://stackoverflow.com/questions/43091919/ansible-escaping-in-shell-module * shell vs. reboot: http://www.mydailytutorials.com/introduction-shell-command-module-ansible/ * ansible reboot: https://stackoverflow.com/questions/29955605/how-to-reboot-centos-7-with-ansible * Ansible playbooks Shellskript-style: [[https://github.com/Uberspace/paternoster|paternoster]] * [[https://github.com/cmprescott/ansible-xml|ansible-xml]] [[https://docs.ansible.com/ansible/latest/modules/xml_module.html#|ab Version 2.4 in ansible enthalten]] * [[https://kasapi.kasserver.com/dokumentation/phpdoc/|API von all-inkl]] * [[https://stackoverflow.com/questions/39658198/how-to-parse-the-xml-response-of-a-uri-in-ansible|How to Parse the XML response of a URI in ansible]] * **[[https://riptutorial.com/ebook/ansible|Learning ansible eBook (PDF)]] gut: [[https://riptutorial.com/ansible/topic/6095/loops|looping]]** * [[https://tedboy.github.io/jinja2/templ6.html|Whitespace Control]] * [[https://radeksprta.eu/posts/control-whitespace-in-ansible-templates/|Control Whitespace in Ansible Templates]] ===== Begriffe ===== * ad-hoc-Befehl ausführen (**nicht interaktiv**, hier auf Gruppe "all"): ansible -i hosts -m shell -a "uname -a" all * ad-hoc-Befehl ausführen (**interaktiv** mit [[https://docs.ansible.com/ansible/latest/cli/ansible-console.html|ansible-console]]): ansible-console -i hosts anschließend kann auf der Gruppe "all" (oder mit cd $GRUPPE auf einer anderen Gruppe) module benutzt werden. Beispiel: ''shell date'' führt auf allen hosts den Befehl date via shell-modul aus. * **facts**: sind Variablen die auf dem Host erhoben werden, z.B. vom setup-modul: ansible $HOSTNAME -i hosts -m setup * gather_facts: False|True * **subsets** (network, hardware, virtual, facter, ohai): gather_subset: network * 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: [[https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#ansible-variable-precedence|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 playbooks/$mein-Playbook.yml * --limit grenzt auf bestimmte hosts ein, kann aber auch umgekehrt zum Ausschluss benutzt werden: --limit 'all:!not_this_host_or_group' * der **[[https://docs.ansible.com/ansible/latest/user_guide/playbooks_checkmode.html|Checkmode]]** liefert eine Abschätzung ob alles in Ordnung ist -C, --check: don’t make any changes; instead, try to predict some of the changes that may occur * **Diff** was in Dateien geändert werden würde: -D, --diff when changing (small) files and templates, show the differences in those files; works great with –check * roles ("Rollen"): ausgelagerte Playbooks, die zur Wiederverwendung optimiert sind * collections: Verbreitungsformat für playbooks, roles, modules, and plugins * tags ((die tags sind in dem fact ansible_run_tags)): nur einzelne ausführen --tags foo oder --skip-tags always|tagged|untagged. Aber Achtung: eine Aufzählung in folgendem Format ist eine "ODER"-Verknüpfung- name: debug baz debug: msg=baz tags: - foo - bar * tasks: einzelne Arbeitsschritte die jeder Host durchläuft * vault: Speicher für Zugangsdaten * ansible-galaxy (requirements): Sammlung von Rollen unter https://galaxy.ansible.com * [[wpde>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 [[https://docs.ansible.com/ansible/latest/user_guide/playbooks_blocks.html#blocks-error-handling|rescue-Block]] abgefangen === Ausgaben mit callback plugins verändern === * https://docs.ansible.com/ansible/latest/plugins/callback.html * https://docs.ansible.com/ansible/latest/collections/community/general/diy_callback.html ===== 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" ([[https://shadow-soft.com/ansible-idempotency-configuration-drift/|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. * [[https://ryaneschinger.com/blog/ensuring-command-module-task-is-repeatable-with-ansible/|Ensuring a Command Module Task is Repeatable with Ansible]] * [[https://symfonycasts.com/screencast/ansible/idempotency-changed-when|Idempotency, changed_when & Facts]] ===== 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) schneller geht als man den ausführenden Rechner aktuell halten kann (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 **python3-pip** oder über [[https://pip.pypa.io/en/stable/installing/|andere Methoden]]. Systemweit * aktuelle Version: ''sudo -H pip3 install ansible'' * bestimmte-Version: ''sudo -H pip3 install ansible==2.9.22'' ((verfügbare Versionen mit diesem "Trick" anzeigen: ''pip3 install ansible=='')) nur für den aktuellen Benutzer (nach /.local/bin): * ''pip3 install ansible'' (bei Versionsänderung später: ''pip3 install ansible --upgrade'') * Pfad erweitern (in der .bashrc): ''PATH=~/.local/bin/:$PATH'' :!: **Nach der Installation sollte verifiziert werden das ansible auch wirklich python3 benutzt** (mit Version 2 gibt es zunehmend Problem aktuelle Module nachzuinstallieren): ansible --version | grep "python version" Der Ablauf der [[https://docs.ansible.com/ansible/latest/reference_appendices/interpreter_discovery.html|Interpreter discovery wird hier erklärt]], auf den Zielhost wird oft noch python2 benutzt wenn der Stand-Python-Interpreter auf python2 steht. Überschreiben lässt sich dies z.B. mit group_vars/all: ansible_python_interpreter: /usr/bin/python3 ==== Ansible 2.9.x issues ==== * [[https://github.com/ansible/ansible/issues/65556|win_get_url doesn't follow redirects]] ==== ansible versionsupgrade auf 2.10.x ==== # direkt auf ansible 2.10 geht nicht: # pip3 install --upgrade ansible pip3 uninstall ansible pip3 install ansible # ansible-base ist dependency! Neuer Begriff: fcdn (fully qualified community module)-> module clash wenn Name gleich in unterschiedlichen collections. Siehe Ausgaben von "-vv" Text ähnlich "redirecting (type: modules)". ansible-galaxy collection install community.general ==== upgrades allgemein ==== [[https://docs.ansible.com/ansible/devel/porting_guides/porting_guides.html|Portin guides]] === upgrade auf ansible 2.10 === Integrierte module sollten mit dem fully-qualified collection name (FQCN) angesprochen werden, siehe auch: [[https://docs.ansible.com/ansible/2.10/collections/index_module.html|Liste aller Module]]. https://docs.ansible.com/ansible/devel/porting_guides/porting_guide_2.10.html === upgrade auf ansible 3.0/4.0 === Statt 2.11 wurde Version 3.x veröffentlicht, 2.12 ist dann 4.x. minor releases sind dann 3.1, 3.2, ... bzw 4.1, 4.2 ... usw. Neben der Migration von Rollen in Community-Roles wurde eine gemeinsame requirements-Datei für roles und collections eingeführt: --- # ansible-galaxy install -r requirements.yml roles: - src: geerlingguy.git collections: - name: community.zabbix :!: ansible-lint kann aber weiterhin nur automatisch roles/requirements.yml bzw. collections/requirements.yml benutzen. Und ansible-semaphore hat dafür [[https://github.com/ansible-semaphore/semaphore/issues/1144|auch keinen support]] und nur einen [[https://github.com/ansible-semaphore/semaphore/pull/1240|teilweise fix]]. Eine saubere Installation via pip: ''pip3 uninstall ansible ansible-base ansible-core && pip3 install 'ansible<4.4' '' === upgrade von modulen === Mittlerweile sind die meisten module in die comunity (oder andere) collection gewandert. Diese lassen sich einzeln upgraden: ''ansible-galaxy collection install community.general --upgrade'' eine spezielle Version wird so installiert: ''ansible-galaxy collection install community.general:==X.Y.Z'' Diese liegen dann in ''~/.ansible/collections/ansible_collections''. === git Struktur === ansible.cfg .ansible-lint collections/requirements.yml group_vars/all host_vars/.gitkeep hosts playbook1.yml README.md roles/requirements.yml templates/.gitkeep vault.yml Einen Ordner "playbooks" anzulegen, würde ich nicht unbedingt empfehlen, dann müssen die Rollen und Collections unterhalb dieses Ordner legen (u.a. ansible semaphore, ansible-lint suchen dann immer unterhalb des Ausführungsordners nach roles/requirements.yml bzw. collections/requirements.yml, da müsste man mit u.U. symlinks arbeiten was die Sache evtl. nicht wert ist). ==== Beispielsetup ansible-master ==== Anforderung: Login via eigenem Systembenutzer, gemeinsames Repo und Vault (()). 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 = MyVault.yml vault_password_file = /etc/ansible-vault-password ==== Setup des Editors ==== Yaml verträgt keine Tabs, die müssen vom Editor in Leerzeichen umgewandelt werden. Jeder Editor hat hier seine eigenes Einstellungsformat. Beispiel für globale Einstellungen: **vim** ((vim kann auto-Erkennung der Dateitypen siehe https://github.com/chase/vim-ansible-yaml)) ''~/.vimrc'' oder ''~/.vim/vimrc'': set tabstop=2 set shiftwidth=2 set expandtab # autocmd Filetype yml setlocal tabstop=2 **nano** (''~/.nanorc''): set tabsize 2 set tabstospaces ==== Playbook sudo mit passwort ==== Das Passwort kann aus einer Umgebungsvariable kommen (hier ansible_become_pass: ansible-playbook --extra-vars "ansible_sudo_pass=$ansible_become_pass" [...] oder durch andere methoden (aus einer subshell, keepass etc. siehe https://stackoverflow.com/questions/21870083/specify-sudo-password-for-ansible ). per playbook: - hosts: all become: yes become_user: 'root' become_method: 'sudo' tasks: - name: whoami ansible.builtin.command: id register: id - name: Show whoami ansible.builtin.debug: msg="{{ id.stdout_lines }}" das ganze geht auch per task oder global in der **ansible.cfg**: [privilege_escalation] become = True become_method=sudo Siehe auch: https://docs.ansible.com/ansible/latest/user_guide/become.html ===== 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 ansible.builtin.shell: "ssh-keyscan -H {{ inventory_hostname }} >> ~/.ssh/known_hosts" Quellen: https://stackoverflow.com/questions/32297456/how-to-ignore-ansible-ssh-authenticity-checking ==== Firewallfreischaltungen ==== Firewallfreischaltungen für ansible-Rollen: **PIP**: * pypi.python.org (oder *.python.org) * pypi.org * python.org * files.pythonhosted.org **github**: * github.com * *.github.com oder api.github.com + codeload.github.com) * raw.githubusercontent.com * //objects.githubusercontent.com (self-hosted agents in GitHub workflows)// **docker** * hub.docker.com ===== ansible-galaxy ===== * Rollen/Collections suchen: ''ansible-galaxy role search $NAME'' * Installieren ''ansible-galaxy install $NAME'' * einzelne git-Rolle auschecken:'' ansible-galaxy install git+$GIT-Adresse'' Ablage: ''.ansible/roles'' bzw. ''~/.ansible/collections/ansible_collections/'' ===== 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'' * mehrere Passwörter (via ansible.cfg: # $HOME does not work! # just lowercase to prevent problems on different OS (windows ignores upper/lower-case): vault_identity_list=~/.ansible_vault_devops_password,~/.ansible_vault_dev_common_password,~/.ansible_vault_dev_specific_password * 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 * via gnome linux session keyring: https://stackoverflow.com/a/70346557 * external vault-plugin ==== Verwaltung des Vault-files: ==== * anlegen: ''ansible-vault create myVaultfile.yml'' * editieren ((es wird der Standard-Editor aus der Umgebungsvariablen $EDITOR benutzt)): ''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 ===== ansible-pull ===== [[https://docs.ansible.com/ansible/latest/cli/ansible-pull.html|ansible-pull]] kann sich selbst playbooks aus einem entfernten Repo ziehen, für einige Anwendungsfälle (kein Zugriff von Extern) ist das nötig. ansible-pull -U $url_gitrepo ===== Verwaltung ===== ==== Verwaltungoberflächen ==== * [[https://docs.rundeck.com/docs/learning/howto/using-ansible.html|rundeck und ansible-plugin]] === ansible automation plattform (AAP) / AWX / Tower === [[https://access.redhat.com/support/policy/updates/ansible-automation-platform|Red Hat Ansible Automation Platform Life Cycle]] [[https://www.ansible.com/compare|Vergleich]] [[https://github.com/ansible/awx]] / ansible automation plattform: unterstützt hop und excecution nodes ((standardmäßig über receptor Port 27199)) zur Ausführung komplexen Topologien (in anderen Netzen/Umgebungen), siehe https://media.ccc.de/v/clt23-147-konfigurationsmanagement-uber-verschiedene-netze-mit-awx#t=1522 . AWX: simple Anleitung (Installation ohne k8s!): https://github.com/selfhostx/ansible/blob/main/instructions/awx-manual-install.txt === ansible Semaphore === [[https://ansible-semaphore.com/|ansible semaphore]] ist relativ simpel (aber funktional!), API, zeitgesteuert templates ("cron"), interaktive Abfrage ("survey"), alles wichtige ist dabei. playbook: https://github.com/selfhostx/ansible/blob/main/playbooks/semaphore.yml / https://github.com/stefanux/ansible-role-semaphore **Reihenfolge bei der Einrichtung**: - Projekt anlegen - deploy key oder access tokens (z.B. gitlab) bei git-Repo authorisieren - Key store - SSH-Zugriff System -> Typ: ssh - Repository-Zugriff -> Typ: ssh oder access token (->Typ: login_password) - vault password -> Typ: login_password - repo+branch hinzufügen - Inventory (plain oder yaml): Achtung: User credentials wird für die Verbindung zum Host benutzt (SSH), also hier **nicht** den Access zum Repo angeben! - Environment (minimal zwei mal { } ) - Template (task) anlegen mit den Details von oben **Zu beachten**: * **pro Mandant ein semaphore** einsetzen, da die ansible-runs noch nicht hart voneinander isoliert sind: https://github.com/ansible-semaphore/semaphore/issues/162 bzw. https://github.com/ansible-semaphore/semaphore/issues/1185 . * [[https://github.com/ansible-semaphore/semaphore/projects/22|Status von LDAP]] == dynamische inventory == dynamische inventories werden nicht direkt unterstützt, können aber über override in templates angelegt werden: [ "-i", "./library/SKRIPT.py" ] ===== 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: * Hostnamen sollen als FQDN angegeben werden. * Gruppennamen in Kleinbuchstaben (mit Unterstrichen als Trenner) wie bei [[https://www.python.org/dev/peps/pep-0008/#function-and-variable-names|Pythons Variablennamen]] Beispiel: "gruppen" setzt sich zusammen aus gruppe1 und gruppe2: [gruppen:children] gruppe1 gruppe2 [gruppe1] host1 ... [gruppe2] Anzeige als Graph (**inklusive aller Variablen aus host_vars/group_vars und deren Werten!**): ''ansible-inventory -i hosts --graph'' @all: |--@gruppe1: | |--@host1: | | |--{ansible_python_interpreter = /usr/bin/python3} [...] ==== dynamic inventories ==== * [[https://github.com/productsupcom/ansible-dyninv-mysql|aus SQL]] ===== Variablen ===== [[https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html|Special Variables]] Alle Variablen (facts) anzeigen: ansible -i hosts -m setup all Gefiltert auf ansible_distribution*: ansible -i hosts -m setup all -a "filter=ansible_distribution*" ==== Variable-Präzedenz ==== 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) * [[https://stackoverflow.com/questions/30662069/how-can-i-pass-variable-to-ansible-playbook-in-the-command-line|extra vars]] (bei der Laufzeit des playbooks übergeben): ''export system_variable=test; ansible-playbook ...'' playbook_Variable: "{{ lookup('env', 'system_variable') }}" Siehe auch [[https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable|die vollständige Reihenfolge]]. ==== Variablentypen ==== Variablen können verschiedene Typen haben, die teilweise intern umgewandelt werden könnten: * **boolean**: true, false (yes, no) * **Zahl** (integer/int): Variablenname: 1 * **Zeichenkette** (string/str): Variable: "1" oder Variablenname: '1' * **Liste** (list): Variablenname: [a, b, c] oder Variable: - a - b - c * **Dictionary** (dict): Variablenname: {key1: value1, key2: value2}oderVariablenname: - key1: value1 - key2: value2 * **Dictionary** UND **Liste** zusammen: Variablenname: - key1: value1 attribute: value1 - key2: value2 attribute: value2 Den Typ der Variable kann mit dem Filter type_debug bestimmt werden (hier eine Zahl): - debug: msg: "Datentyp der Variable Variablenname ist {{ Variablenname | type_debug }}" Ergäbe dann bei einer Zahl eine Ausgabe wie: Datentyp der Variable Variablenname ist int **Links** * [[https://www.redhat.com/sysadmin/ansible-lists-dictionaries-yaml|How to work with lists and dictionaries in Ansible]] * [[https://serverfault.com/questions/888200/loop-over-ansible-variable-array-in-jinja2-template|loop over dictionaries]] * [[https://serverfault.com/questions/1129844/ansible-loop-inside-loop|Ansible : Loop inside loop]] ==== mehrzeilige Zeichenketten ==== [[https://adminswerk.de/multi-line-string-yaml-ansible-I/|Understanding multi line strings in YAML and Ansible (Part I - YAML)]] Oft benutzt: der "literal style" (Format bleibt erhalten): inhalt_bleibt_exakt_so: |+ inhalt1 inhalt2 ===== 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 [[https://stackoverflow.com/questions/25230376/how-to-automatically-install-ansible-galaxy-roles|requirements.yml]] anlegen. sudo ansible-galaxy install --roles-path=/etc/ansible/roles -r external-requirements-ansible-host.yaml ==== neue Rolle erzeugen ==== ''ansible-galaxy init role_name'' Use the --remove option to disable and remove a Travis integration: ansible-galaxy setup --remove ID siehe auch: [[https://docs.ansible.com/ansible/latest/galaxy/dev_guide.html|Galaxy Developer Guide]]. ==== Beispielrollen ==== Siehe Liste: https://github.com/selfhostx/ansible/blob/main/ROADMAP.md ===== Collections ===== collections sind ein Verbreitungsformat für playbooks, roles, modules, and plugins. Link: [[https://galaxy.ansible.com/docs/using/installing.html|Installing content]] ansible-galaxy collection install my_namespace.my_collection aus einem tar-ball: ansible-galaxy collection install my_namespace-my_collection-1.0.0.tar.gz -p ./collections Collections werden in ''~/.ansible/collections/ansible_collections'' abgelegt. **Limitationen**: * es ist eine aktuelle Version von Ansible nötig (mind. 2.9 besser 2.10) * [[https://github.com/ansible/ansible/issues/62847|Rollen können nicht von collections abhängen]] * Installation via ansible-galaxy und einer requirements.yml funktioniert nur für roles ODER collections: ansible-galaxy role install -r requirements.yml ansible-galaxy collection install -r requirements.yml ==== neue collection erzeugen ==== im collection Verzeichnis: ''ansible-galaxy collection init my_namespace.my_collection'' siehe [[https://docs.ansible.com/ansible/latest/dev_guide/developing_collections_creating.html|Creating collections]]. ===== Module ===== Ansible ist modular aufgebaut * [[https://docs.ansible.com/ansible/latest/modules/set_fact_module.html|set_fact-Module]] (die Liste der facts erweitern) ==== Arten der Kommandoausführung ==== * [[https://docs.ansible.com/ansible/latest/modules/shell_module.html|shell – Execute commands in nodes]] * [[https://docs.ansible.com/ansible/latest/modules/command_module.html|command]] / [[https://docs.ansible.com/ansible/latest/modules/win_command_module.html#win-command-module|win_command]] * [[https://docs.ansible.com/ansible/latest/modules/script_module.html#script-module|script – Runs a local script on a remote node after transferring it]] * [[https://docs.ansible.com/ansible/latest/modules/raw_module.html|raw – Executes a low-down and dirty SSH command]] * [[https://docs.ansible.com/ansible/latest/user_guide/playbooks_strategies.html|Controlling playbook execution: strategies and more]] * [[https://docs.ansible.com/ansible/latest/user_guide/playbooks_delegation.html|Delegation, Rolling Updates, and Local Actions]] Stichworte: ansible-playbook --forks=1 any_errors_fatal: true max_fail_percentage: 30 serial: 11 einzelne tasks: "run_once: true" auf einem bestimmten host - name: "run on that_one_host host" ansible.builtin.shell: this_command_should_run_on_one_host when: ansible_hostname == 'that_one_host' ==== Datei-handling ==== weitere Module siehe [[https://docs.ansible.com/ansible/latest/modules/list_of_files_modules.html|Files modules]]. ^ Modulname (Link zur Doku) ^ Zweck, geeignet für... ^ nicht geeignet für ... ^ | [[https://docs.ansible.com/ansible/latest/modules/copy_module.html|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. | | [[https://docs.ansible.com/ansible/latest/modules/fetch_module.html|fetch]] | | | | [[https://docs.ansible.com/ansible/latest/modules/file_module.html|file]] | Dateien und Rechte ändern | | | [[https://docs.ansible.com/ansible/latest/modules/synchronize_module.html|synchronize]] | rsync | | | [[https://docs.ansible.com/ansible/latest/modules/template_module.html|template]] | Dateien erzeugen (wenn der Inhalt der Datei durch Variablen verändert werden muss) | | | [[https://docs.ansible.com/ansible/latest/modules/unarchive_module.html|unarchive]] / [[https://docs.ansible.com/ansible/latest/modules/archive_module.html|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 ==== * [[https://docs.ansible.com/ansible/latest/modules/package_module.html|package – Generic OS package manager]] [[https://docs.ansible.com/ansible/latest/modules/win_package_module.html#win-package-module|win-package]] * [[https://docs.ansible.com/ansible/latest/modules/apt_module.html|apt module]] * [[https://docs.ansible.com/ansible/latest/modules/yum_module.html|yum]] === Spezialmodule (Auswahl) === * [[https://docs.ansible.com/ansible/latest/modules/ovirt_module.html?highlight=dns|ovirt]] * [[https://docs.ansible.com/ansible/latest/plugins/lookup/dig.html|dig - query DNS using the dnspython library]] * [[https://docs.ansible.com/ansible/latest/collections/ansible/utils/docsite/filters_ipaddr.html|ipaddr filter]] ===== Plugins ===== * [[https://docs.ansible.com/ansible/latest/plugins/action.html#id2|Action Plugins]] * [[https://stackoverflow.com/questions/32324120/arbitrary-host-name-resolution-in-ansible|Arbitrary host name resolution in Ansible]] ===== templates ===== templates sind via jinja2 implementiert, [[https://jinja.palletsprojects.com/en/2.10.x/templates/|siehe Dokumentation]]. ===== ansible-lint ===== pip install ansible-lint bzw. **upgrade**: pip install ansible-lint --upgrade **[301] Commands should not change things if nothing needs doing** changed_when: false check_mode: yes https://docs.ansible.com/ansible/latest/user_guide/playbooks_checkmode.html **[602] Don't compare to empty string** - changed_when: output.stdout != "" + changed_when: output.stdout | length > 0 **[403] Package installs should not use latest** Vorgeschlagene Lösung: "Package installs should use state=present with or without a version"** Je nach Situation: Fehlalarm wenn explizit die letzte, aktuell verfügbare, Version installiert werden soll (aber die Version egal ist). ==== Code-Konventionen ==== ansible-lint listet die Fehler auf * Variable-Namen: nur Kleinbuchstaben und "_" * Rollen-Namen: ^[a-z][a-z0-9_]+$ * Task-Bennennung: möglichst prägnant, Modul-Name als Präfix im Namen? * Modul-Coding: https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_best_practices.html * ==== Warnungen gezielt ausschalten ==== Beispie: Warnung "no-changed-when" ausschalten: - name: 'Task mit unnützer Warnung durch ansible-lint' # noqa no-changed-when ===== Code-Beispiele ===== ==== Henne-Ei-Problem: Python nicht installiert ==== Ansible hat das Henne-Ei-Problem [[programmiersprachen:Python]] zu benötigen aber um das zu Installieren braucht man bereits Python. Bei [[debian:Debian]]/[[ubuntu:Ubuntu]] in der Minimalinstallation ist [[programmiersprachen: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 [[programmiersprachen:Python]] realisiert ist) * dann mittels raw-Modul direkt ein [[netzwerke:SSH]]-Kommando abgesetzt werden (Achtung: das beachtet kein Umgebungsvariablen wie z.B. [[server:proxyserver|Proxy]]-Einstellungen!) * 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 ansible.builtin.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 ansible.builtin.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 ansible.builtin.command: uname -a register: myoutput - name: show output (alle attributes) ansible.builtin.debug: msg: "{{ myoutput }}" - name: show standard output (stdout multiline) ansible.builtin.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) ansible.builtin.debug: msg: "{{ myoutput.stderr_lines }}" when: myoutput.stderr_lines is defined and myoutput.stderr_lines != '' and myoutput.stderr_lines != none ==== Prüfen ob Variablen gesetzt sind ==== Die Verwendung undefinierter Variablen führt zu Fehlern (Abbruch), Benutzern kann in einem extra tasks ein Hinweis gegeben werden. - name: DNS sanity checks ansible.builtin.assert: that: - variable1 is defined - variable1|length>0 msg: "Variable 1 muss definiert sein und einen Wert enthalten" run_once: true ==== Errorhandling ==== https://docs.ansible.com/ansible/latest/user_guide/playbooks_error_handling.html ==== 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" ansible.builtin.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 ansible.builtin.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 ==== Durch das Zuammenspiel von YAML, Jinja2 und Python ergeben sich mögliche Probleme bei der Verwendung von True, true, False, false und yes, no: https://chronicler.tech/red-hat-ansible-yes-no-and/ [[Ansible konvertiert die Zeichenketten "yes" und "no" ungewollt in boolean (true oder false)|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). Ein weiteres Beispiel ist javascript: Hier muss in templates explizit "true" herauskommen (nicht True). Hier kann mit dem ''Filter $Variable|lower'' gearbeitet werden (Unwandlung in einen "lowercase string"). 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. ansible.builtin.service: name: "{{ ntp_daemon }}" state: started enabled: yes when: ntp_enabled -> when: ntp_enabled|bool ==== fact dynamisch setzen ==== Die Bedingung (when: ...) ist optional. - name: Set my_variable to true if undefined ansible.builtin.set_fact: my_variable: true when: my_variable is undefined ==== gruppe dynamisch setzen ==== * die Bedingung (when: ...) ist optional * changed_when ist hier aktiviert weil ein changed-Status hier keinen einen Sinn macht - name: Set machine_is_virtual when virtualization_role is guest ansible.builtin.group_by: key: machine_is_virtual changed_when: false when: ansible_facts['virtualization_role'] == "guest" oder (etwas eleganter mit entweder Gruppe machine_is_virtual oder machine_is_metal in einer Zeile. - name: Classify hosts (virtual or bare metal) ansible.builtin.group_by: key: machine_is_{{ "virtual" if ansible_facts['virtualization_role'] == "guest" else "metal" }} changed_when: false ==== Blöcke in config-files managen: blockinfile ==== statt lineinfile: [[https://docs.ansible.com/ansible/latest/collections/ansible/builtin/blockinfile_module.html|blockinfile module – Insert/update/remove a text block surrounded by marker lines]], Beispiel: https://stackoverflow.com/questions/22844905/how-to-create-a-directory-using-ansible ==== Passwörter erzeugen ==== Dynamische passwort-Lookups würden jedes mal ein neues Passwort erzeugen, oft will man pro Host ein Passwort erzeugen und dies in einer Variable speichern. Statt "/dev/null" kann das Passwort auch (auf dem wo es läuft) in eine Datei geschrieben werden. - name: Demo hosts: all gather_facts: False tasks: - set_fact: host_individual_password: "{{ lookup('password', '/dev/null length=16 chars=ascii_letters') }}" no_log: True - debug: msg: "{{ host_individual_password }}" Es kann aber auch ein Passwort aus dem environment des masters geholt werden (das gilt dann aber für alle Hosts gleichermaßen!): - name: Demo2 hosts: all gather_facts: False vars: password_from_master: "{{ lookup('env', 'password_from_master') }}" tasks: - set_fact: password_from_master: "{{ lookup('env', 'env_password') }}" no_log: True - debug: msg: "{{ password_from_master }}"