21x9.org | System Administration | Home Automation | Smart Home
21.08.2023

Ansible: Zusammenhängende Konfiguration über mehrere Server

Ich nutze Prometheus1 um einige Sensordaten und Statusinformationen meiner Server zu verarbeiten. Prometheus arbeitet mit sog. Exportern2. Hierbei handelt es sich um kleine Programme, die von Prometheus abgefragt werden und so ihre Daten übermitteln. Die Exporter können über beliebige interne und externe Systeme verteilt sein. Hierzu muss Prometheus aber natürlich wissen unter welchen Adressen die jeweiligen Exporter erreichbar sind.

Ich verwalte sowohl die Prometheusinstanz selbst als auch meine Exporter mit Ansible. Immer wenn ich einen Exporter auf einem System hinzufüge, muss die zentrale Prometheus-Instanz hierüber natürlich informiert werden. In diesem Artikel geht es darum, wie man das ganz einfach bewerkstelligen kann. Die ersten Schritte mit Ansible habe ich unter 3 beschrieben.

Schauen wir uns einmal meine Prometheus Role und die zugehörigen Variablen an. Jeder Host hat einen Abschnitt prometheus_exporter in den host_vars, hier werden u.a. die zu installierenden Exporter aufgeführt:

  zha:
    name: zha
    target: "['localhost:9644']"
  kasa:
    name: kasa
    target: node-collector
  mqtt:
    sensors: "localhost:9641"
    state: "localhost:9642"
    esp: "localhost:9643"

Der eigentliche Prometheus Task kümmert sich um die Installation und Konfiguration von Prometheus selbst (natürlich nur auf einem Host). Hier ist zunächst alles wie immer, ein erwähnenswertes Detail ist noch, dass die grundlegenden Konfigurationseinstellungen von Prometheus in der Konfigurationsdatei (prometheus.yml) mit der Zeile # END BASE CONFIG ANSIBLE MANAGED BLOCK abgeschlossen werden. Das ist wichtig, damit die Konfigurationsabschnitte der Exporter an die korrekte Stelle geschrieben werden:

- name: Configure prometheus
  ansible.builtin.blockinfile:
    path: /etc/prometheus/prometheus.yml
    block: "{{ lookup('file', 'files/prometheus.yml') }}"
    marker: "# {mark} BASE CONFIG ANSIBLE MANAGED BLOCK"
    create: true
    mode: "0640"
  when: "'monitor' in inventory_hostname"
  notify: "Restart prometheus"

Die Zeile {{ lookup('file', 'files/prometheus.yml') }} sorgt dafür, dass der Inhalt der block Anweisung - die normalerweise Inline erfolgt - aus der Datei files/prometheus.yml bezogen wird. Diese sieht bei mir wie folgt aus:

global:
  scrape_interval: 60s
  evaluation_interval: 60s

alerting:
  alertmanagers:
    - static_configs:
        - targets: ["localhost:9093"]

scrape_configs:
  - job_name: "prometheus"
    static_configs:
      - targets: ["localhost:9090"]

  - job_name: "wttr_cgn"
    scheme: https
    static_configs:
      - targets: ["wttr.in"]
    metrics_path: "/cgn"
    params:
      format: ["p1"]
    scrape_interval: 60s
    scrape_timeout: 15s

Neben der Grundkonfiguration sind auch schon ein paar generelle Jobs unterhalb von scrape_configs definiert. Hier werden später die weiteren Exporter angefügt. Durch die marker Anweisung im Task beginnt die tatsächlich geschriebene prometheus.yml natürlich mit der Zeile # BEGIN BASE CONFIG ANSIBLE MANAGED BLOCK und endet - wie erwähnt - auf # END BASE CONFIG ANSIBLE MANAGED BLOCK. Dies ist auch wichtig, damit blockinfile bei späteren Änderungen an files/prometheus.yml den korrekten Abschnitt ändert und nicht einfach zusätzlich in die Datei einfügt.

Soweit, so gut. Neben der main.yml im tasks Verzeichnis meiner Prometheus Rolle gibt es noch weitere Tasks für jeden möglichen Exporter. Diese werden vom main.yml Task importiert, wenn die entsprechenden Namen in den host_vars des entsprechenden Hosts angegeben sind. In unserem Beispiel sind das ja zha, kasa und mqtt. Im main.yml Tasks sieht das wie folgt aus:

- name: Kasa Exporter
  ansible.builtin.include_tasks: kasa.yml
  when: "'kasa' in prometheus_exporter"

- name: MQTT Exporter
  ansible.builtin.include_tasks: mqtt.yml
  when: "'mqtt' in prometheus_exporter"

- name: ZHA Exporter
  ansible.builtin.include_tasks: zha.yml
  when: "'zha' in prometheus_exporter"

Diese zusätzlichen Tasks sind in der Regel sehr ähnlich aufgebaut, beispielhaft nehme ich hier daher einfach mal nur den ZHA Exporter:

- name: Install zigbee/zha exporter
  ansible.builtin.copy:
    dest: /opt/zha-exporter/
    src: files/zha-exporter/
    mode: "0755"

- name: Create supervisor config
  ansible.builtin.template:
    dest: /etc/supervisor/conf.d/zha-exporter.conf
    src: templates/zha.conf.j2
    mode: "0640"
  notify: "Restart zha exporter"

- name: Add exporter to prometheus config
  ansible.builtin.blockinfile:
    path: /etc/prometheus/prometheus.yml
    block: |
        - job_name: "{{ prometheus_exporter['zha']['name'] }}"
          static_configs:
            - targets: {{ prometheus_exporter['zha']['target'] }}
    insertafter: "# END BASE CONFIG ANSIBLE MANAGED BLOCK"
    marker: "# {mark} ZHA ANSIBLE MANAGED BLOCK"
  delegate_to: monitor
  notify: "Restart prometheus"

Der erste Abschnitt Install zigbee/zha exporter kümmert sich ganz einfach nur darum, dass die Binärdatei des Exporters auf den Host kopiert wird. Im zweiten Abschnitt Create supervisor config wird eine Konfigurationsdatei für supervisord4 geschrieben (um dessen Installation kümmert sich der main.yml Task).

Interessant wird es im letzten Abschnitt Add exporter to prometheus config. Zum besseren Verständnis machen wir uns noch einmal bewusst, wie Ansible hier arbeitet. Für jeden Host für den unse Prometheus Role relevant ist, werden die Playbooks auf dem jeweiligen Node selbst ausgeführt. Die Exporter können natürlich auf dem Prometheus Server selbst laufen, tun dies aber in der Regel nicht. Sie haben meist auch keine Zugriffsmöglichkeit auf den Prometheus Server. Dennoch müssen wir der prometheus.yml auf dem Prometheus Server bescheid geben, dass es den gerade eingerichteten Exporter gibt und wo er ihn findet. Unser Task Add exporter to prometheus config muss daher nicht auf dem Exporter Host ausgeführt werden, sondern auf dem Prometheus Host (hier monitor genannt). Und genau das passiert auch aufgrund der Anweisung delegate_to: monitor.

Auch hier nutzen wir wieder blockinfile diesmal tatsächlich mit einer Inline Definition von block und erneut mit einem marker. Außerdem nutzen wir die zuvor schon vom main.yml Tasks eingefügte # END BASE CONFIG ANSIBLE MANAGED BLOCK Zeile um unsere Exporter Konfiguration direkt hinter diese Zeile anzufügen (insertafter). Außerdem informieren wir noch dem Restart prometheus Handler (notify) über die geänderte Konfiguration. Da dies im delegate_to Kontext stattfindet, wird auch der Handler auf dem monitor Host ausgeführt.

Mit der deletate_to Anweisung ist es also kein Problem mehr, Konfigurationen über mehrere voneinander abhängige Server hinweg vorzunehmen. Wir können so sogar Informationen vom eigentlichen Hosts an einen anderen Hosts übergeben. In den host_vars unseres Exporter Hosts haben wir ja für zha die folgenden Variablen angegeben:

    name: zha
    target: "['localhost:9644']"

Im Tasks lesen wir diese entsprechend aus:

    block: |
        - job_name: "{{ prometheus_exporter['zha']['name'] }}"
          static_configs:
            - targets: {{ prometheus_exporter['zha']['target'] }}

Diese Variablen werden auf dem Exporter Host befüllt bevor die Anweisung an den Prometheus Host übergeben werden. So können natürlich auch Passwörter oder ähnliches übertragen werden, ohne diese mehrfach in allen host_vars bzw. group_vars aufführen zu müssen.

Ich finde die delegate_to5 Funktion von Ansible daher wirklich elegant und einfach gelöst und sie sollte zum fest zum Werkzeugkasten eines jeden Ansible Nutzers gehören.


  1. https://prometheus.io/ ↩

  2. https://prometheus.io/docs/instrumenting/exporters/ ↩

  3. https://blog.21x9.org/serververwaltung-mit-ansible ↩

  4. http://supervisord.org ↩

  5. https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_delegation.html ↩

Tags: ansible delegate_to blockinfile

Mehr

  • Ansible Secrets mit Lookup Plugins
  • Ansible: Best Practice
  • Serververwaltung mit Ansible

Tags

ansible delegate_to blockinfile

Archiv

  • Mar 2025 (2)
  • May 2024 (2)
  • Oct 2023 (1)
  • Aug 2023 (5)
  • Jul 2023 (31)

  • Ältere Einträge (95)

Feeds

Atom 1.0 RSS JSON
  • Datenschutz
  • Impressum
  • Archiv