Beispiel - Ansible inkl. Jinja Template

foxit

Well-Known Member
Hallo zusammen

Ich persönlich verwalte meine Systeme seit Jahren mit Ansible, Puppet oder Saltstack. Ich habe dazu diverse Konfigurationen geschrieben. Eine dieser Ansible Rollen, möchte ich euch hier zeigen, damit ihr sehen könnt, wie einfach es ist dies mit Ansible umzusetzen. Zuerst ein kleiner Abriss zu Ansible.

Was ist Ansible?

Ansible ist ein Automatisierungs-Werkzeug zur Orchestrierung und Administration diverser Geräte.

Ansible hat folgende Vorteile:
  • Ansible hat eine grosse Verbreitung
  • Die Dokumentation ist vorbiedlich
  • Auf dem Zielsystem muss nur SSH Verfügbar sein

Der Aufbau ist wie folgt:
Playbooks -> Collections/Rollen -> Tasks
  • Ein Playbook beinhaltet Rollen und/oder Collections
  • Eine Rolle ist der primäre Mechanismus zum Aufteilen eines Playbooks in mehrere Dateien.
  • Rollen vereinfachen das Schreiben komplexer Playbooks
  • Eine Rolle wiederrum beinhaltet Tasks. In diesen Tasks wird definiert, was ausgeführt werden soll

Beispiel Rolle

Hier nun ein kleines Beispiel für eine Rolle, welche die Datei: /etc/rc.conf bearbeitet. Der Aufbau der Rolle sieht so aus:

Bash:
freebsd_etc_rc_conf
├── defaults
│   └── main.yml
├── tasks
│   └── main.yml
├── templates
│   └── _frebsd_etc_rc_conf.j2
└── vars
    └── main.yml

defaults/main.yml:
Bash:
---
freebsd_etc_rc_conf: '/etc/rc.conf'
Hier wird eingetragen, welche Datei geändert werden soll.

tasks/main.yml:
Bash:
---
- name: Update file rc.conf
  template:
    src: _frebsd_etc_rc_conf.j2
    dest: "{{ freebsd_etc_rc_conf }}"
    mode: 0644
Damit wird die Template-Datei kopiert bzw. erstellt.

templates/_frebsd_etc_rc_conf.j2:
Bash:
# ---
# {{ ansible_managed }}!
# ---
{% for key, value in freebsd_base_etc_rc.items() %}

# {{ key }}
{% for x,y in value and value.items() %}
{{ x }}="{{ y | replace("True", "YES") | replace("False", "NO") }}"
{% endfor %}
{% endfor %}
Hier ist die ganze Magie zu finde.

vars/main.yml:
YAML:
---
freebsd_base_etc_rc:
  network:
    hostname: 'testvm01'
    ifconfig_em0: 'DHCP'
  services:
    clear_tmp_enable: true
    dumpdev: false
    moused_enable: false
    nginx_enable: true
    nrpe3_enable: true
    sshd_enable: true
    syslogd_flags: '-s -s'
    zfs_enable: true
  openntpd:
    openntpd_enable: true
    openntpd_flags: '-v'
  sendmail:
    sendmail_enable: 'NONE'
  snmp:
    snmpd_conffile: '/usr/local/etc/snmpd.conf'
    snmpd_enable: true
    snmpd_flags: '-a'
  telegraf:
    telegraf_enable: true
In YAML definierte Werte.

Die Rolle kann man einfach zu einem Playbook hinzufügen. Wenn das Playbook durchgelaufen ist, sollte die Datei: /etc/rc.conf danach so aussehen:

Bash:
# ---
# Ansible managed!
# ---

# network
hostname="testvm01"
ifconfig_em0="DHCP"

# services
clear_tmp_enable="YES"
dumpdev="NO"
moused_enable="NO"
nginx_enable="YES"
nrpe3_enable="YES"
sshd_enable="YES"
syslogd_flags="-s -s"
zfs_enable="YES"

# openntpd
openntpd_enable="YES"
openntpd_flags="-v"

# sendmail
sendmail_enable="NONE"

# snmp
snmpd_conffile="/usr/local/etc/snmpd.conf"
snmpd_enable="YES"
snmpd_flags="-a"

# telegraf
telegraf_enable="YES"

Man sieht wie die einzelnen Keys und Werte aus der YAML Datei in ein anderes Format konvertiert werden. Natürlich kann man noch etliche Sachen anpassen oder auch anders definieren. Der Vorteil besteht jetzt darin, dass ich mir zu 100% sicher sein kann, dass die Datei immer gleich erstellt bzw. angepasst wird.

foxit
 
Ja das ist auch möglich aber mit der Datei Lösung, bin ich schneller am Ziel wie für jeden Service einen Task zu schreiben.
 
Hättest du auch ein Beispiel für das erstellen einer jail mit bsdinstall?
Wie gehst du generell bei jails vor, ich würde ungern in jeder jail sshd aktivieren.
 
templates/_frebsd_etc_rc_conf.j2:
Bash:
# ---
# {{ ansible_managed }}!
# ---
{% for key, value in freebsd_base_etc_rc.items() %}

# {{ key }}
{% for x,y in value and value.items() %}
{{ x }}="{{ y | replace("True", "YES") | replace("False", "NO") }}"
{% endfor %}
{% endfor %}
.items() ist nicht deterministisch.
Code:
| dictsort
ist besser. Statt replace nimm
Code:
| ternary('YES', 'NO')
. Ansonsten ist gutes Naming auch nie verkehrt. "key" und "value" für Dinge, die keine Keys und Values sind, um dann x und y zu nehmen, naja...

Spontan hätte ich hier section/sdict und var/value oder so genommen.
 
Kann dictsort die Werte nicht sortiert darstellen? Weil das ist es, was ich machen möchte. Sortiert sind die Ausgaben nicht immer sinnvoll.

| ternary('YES', 'NO') klappt bei mir nicht. Kannst du mir bitte das Beispiel so zeigen, dass es damit funktioniert?

Danke
 
Kann dictsort die Werte nicht sortiert darstellen? Weil das ist es, was ich machen möchte. Sortiert sind die Ausgaben nicht immer sinnvoll.
Dann darfst du strenggenommen kein Dict nehmen, denn das sind per Definition nicht-deterministische Datenstrukturen. Die mögen per Zufall immer gleich rauskommen, sobald du damit aber was machst oder das Playbook von zwei verschiedenen Rechnern aus anwendest, kann es sein, dass das Ergebnis zwischen verschiedenen Repräsentationen flappt.
| ternary('YES', 'NO') klappt bei mir nicht. Kannst du mir bitte das Beispiel so zeigen, dass es damit funktioniert?

Danke
Der Input muss ein Bool sein, dann https://docs.ansible.com/ansible/la...-different-values-for-true-false-null-ternary

Edit: Ich sehe gerade, dass du Bools und Strings gemischt hast. Dann ist das mit dem replace sowieso heikel, wenn ein String mal True enthält. Dafür lohnt es sich, einen custom Filter zu schreiben, der die Datentypen respektiert:

Code:
def ternary_or_value(value, value_true, value_false):
    if isinstance(value, bool):
        return value_true if value else value_false
    return value

class FilterModule(object):
    def filters(self):
        return {
            'ternary_or_value': ternary_or_value
        }

Das kannst du als ternary_or_value.py in das filter-Plugin-Dir kippen und hast dann einen Filter | ternary_or_value('YES', 'NO'), der für Bools YES/NO ausgibt und alles andere einfach durchreicht.
 
Ich sehe gerade, dass du Bools und Strings gemischt hast. Dann ist das mit dem replace sowieso heikel, wenn ein String mal True enthält.
Darauf bin ich heute selber schon gestossen und hatte mir sogar von ein paar Infos über Plugins/Filter angeschaut. Dein Python Code funktioniert. Danke für die Infos.
 
Auch wenn ich selbst aus unterschiedlichen Gründen nicht der größte Fan von Ansible bin möchte ich ergänzend eine nette Role zum Managen von Jails erwähnen, falls mal jemand durch diesen Thread drüberstolpert.

Rein interessehalber: welches Tool mit ebenbürtigem FreeBSD-Support würdest du zum Einsatz bringen?
 
Zurück
Oben