Azure Entra ID automatisieren: Benutzer, Gruppen und Apps mit Ansible
TL;DR Ansible kann Azure Entra ID (ehem. Azure AD) über die azure.azcollection vollständig …
backup → update → verify ausführt – inklusive Rollback über Ansible block/rescue.pg_dump + tar + Upload nach S3 mit rclone (S3-kompatibles Object Storage), Updates via apt upgrade plus Service-Restart und Health-Checks, Verifikation über Systemd-Status und HTTP-Response.Wenn du „Workflow“ hörst, denkst du vielleicht an GitHub Actions oder GitLab CI. Polycrate-Workflows sind etwas anderes:
backup, update, verify) zu einem benannten Prozess.Ein Workflow in Polycrate ist ein Stück deklarative Orchestrierung: „Führe erst diese Action in diesem Block aus, dann jene Action in einem anderen Block, …“. Kein CI/CD-Server nötig, keine YAML-Speziallogik – nur dein Workspace und die Polycrate-CLI.
Dokumentation passiert damit im Code: Der Workflow ist die Dokumentation des Betriebsprozesses. Kein zusätzliches Word-Dokument, das nicht zur Realität passt.
Mehr Details zum Konzept findest du in der Dokumentation zu Workflows und Workspaces.
Ausgangssituation – sehr typisch für Linux-/System-Admins:
acme-app.service)Der Prozess, den wir als Polycrate-Workflow abbilden:
backup
Datenbank-Dump mit pg_dump, Tarball erzeugen, mit rclone nach S3 hochladen und einen latest.tar.gz-Symlink pflegen.
update
apt update && apt upgrade -y, Service neu starten, Health-Check – alles in einem Ansible-Play mit block / rescue:
block laufen Updates und Checks.rescue wird bei Fehlern automatisch aus latest.tar.gz wiederhergestellt.verify
Explizite Verifikation, dass Systemd-Service läuft und ein HTTP-Endpoint mit Status 200 antwortet.
Das Ganze orchestrieren wir als Workflow nightly-maintenance, den du z.B. über einen systemd-Timer jede Nacht ausführen lässt.
Wir starten mit einem Polycrate-Workspace acme-corp-automation. Die Struktur:
workspace.poly – zentrale Definition von Blocks und Workflowsinventory.yml – Ansible-Inventory (YAML, nicht INI!)blocks/registry.acme-corp.com/acme/infra/linux-maintenance/ – Block mit Actions und Playbooks, sobald er lokal vorliegt (beim ersten polycrate run … oder Workflow-Lauf kann Polycrate einen fehlenden Block automatisch installieren; polycrate blocks pull bleibt optional)artifacts/secrets/ – z.B. Ausschnitte für rclone (Zugangsdaten zum Object Storage), bei Bedarf verschlüsseltname: acme-corp-automation
organization: acme
blocks:
- name: linux-maintenance
from: registry.acme-corp.com/acme/infra/linux-maintenance:0.1.0
config:
db_name: "acme_app"
db_user: "acme"
db_host: "localhost"
backup_dir: "/var/backups/acme-db"
rclone_destination: "acme-s3:acme-backups-db/postgres"
hosts: "db_servers"
web_hosts: "web_servers"
service_name: "acme-app.service"
health_url: "http://localhost:8080/health"
workflows:
- name: nightly-maintenance
description: "Nightly backup → update → verify for Linux application servers"
steps:
- name: backup
block: linux-maintenance
action: backup
- name: update
block: linux-maintenance
action: update
- name: verify
block: linux-maintenance
action: verifyWichtig:
registry.acme-corp.com (kein Bezug zu ayedos Produkt-Registry); in from: steht die vollständige Referenz inkl. Tag (0.1.0 entspricht der Block-Version). Ein vorheriges polycrate blocks pull ist nicht nötig: Fehlt der Block lokal, erkennt Polycrate das beim Aufruf von polycrate run … oder eines Workflows und fragt, ob der Block automatisch installiert werden soll.config. Sie stehen in den Actions über block.config.* zur Verfügung.nightly-maintenance referenziert nur Block- und Action-Namen – keine Pfade, keine Shell-Skripte.Mehr zu Best Practices bei Blocks und Workflows: Polycrate Best Practices.
Polycrate setzt automatisch ANSIBLE_INVENTORY auf inventory.yml im Workspace-Root. Alle Hosts stehen unter all.hosts; unter children verweisen die Gruppen nur noch per Hostnamen darauf (kanonisches Format wie im Beitrag Multi-Server-Inventories mit Ansible):
all:
vars:
ansible_user: "ubuntu"
ansible_ssh_port: 22
ansible_ssh_common_args: "-o StrictHostKeyChecking=no"
ansible_python_interpreter: /usr/bin/python3
hosts:
db01.acme-corp.com: {}
app01.acme-corp.com: {}
children:
db_servers:
hosts:
db01.acme-corp.com:
web_servers:
hosts:
app01.acme-corp.com:Welche Gruppe oder welcher Host pro Action läuft, steuern Sie über Block-config (hosts / web_hosts) und im Playbook hosts: "{{ block.config.… }}" – nicht über fest eingetragene Gruppennamen allein. Kein localhost für SSH-basierte Administration: Polycrate führt Ansible im Container aus, die Zielhosts sind die Einträge im Inventory. Details: Ansible-Integration.
Die folgenden Listings zeigen den Block wie bei der Entwicklung unter dem vollständigen Registry-Pfad blocks/registry.acme-corp.com/acme/infra/linux-maintenance/ – so wie Sie einen OCI-Block mit echter from:-Referenz versionieren und veröffentlichen, nicht als kurzer „Demo-Ordnername“. In anderen Workspaces referenzieren Sie ihn über from: registry.acme-corp.com/acme/infra/linux-maintenance:0.1.0. Fehlt der Block lokal, bietet Polycrate beim ersten polycrate run … oder Workflow-Lauf die automatische Installation an; optional bleibt polycrate blocks pull. Die block.poly:
name: linux-maintenance
version: 0.1.0
kind: generic
config:
db_name: "acme_app"
db_user: "acme"
db_host: "localhost"
backup_dir: "/var/backups/acme-db"
rclone_destination: "acme-s3:acme-backups-db/postgres"
hosts: "db_servers"
web_hosts: "web_servers"
service_name: "acme-app.service"
health_url: "http://localhost:8080/health"
actions:
- name: backup
type: ansible
playbook: backup.yml
- name: update
type: ansible
playbook: update.yml
- name: verify
type: ansible
playbook: verify.ymlHinweis: block.poly ist reines YAML – kein Jinja-Templating. Defaults stehen hier; Abweichungen tragen Sie in workspace.poly unter config ein (Merge). Ausdrücke wie {{ block.config.* }} und Ansible-Logik gehören nur in Playbooks, Tasks und Templates.
Hier siehst du typische Polycrate-Stärken:
backup, update, verify – die CLI-Befehle sind lesbar und copy-paste-bar.Run-Befehle für einzelne Actions:
polycrate run linux-maintenance backup
polycrate run linux-maintenance update
polycrate run linux-maintenance verifyWorkflow:
polycrate workflows run nightly-maintenanceDie Datei blocks/registry.acme-corp.com/acme/infra/linux-maintenance/backup.yml (vereinfachtes Beispiel – Ziel-Hosts über block.config.hosts wie oben):
- name: Backup PostgreSQL database
hosts: "{{ block.config.hosts }}"
become: true
gather_facts: false
vars:
backup_dir: "{{ block.config.backup_dir }}"
db_name: "{{ block.config.db_name }}"
db_user: "{{ block.config.db_user }}"
db_host: "{{ block.config.db_host }}"
rclone_destination: "{{ block.config.rclone_destination }}"
timestamp: "{{ lookup('ansible.builtin.pipe', 'date +%Y%m%d-%H%M%S') }}"
backup_file: "{{ backup_dir }}/{{ db_name }}-{{ timestamp }}.sql"
backup_tar: "{{ backup_dir }}/{{ db_name }}-{{ timestamp }}.tar.gz"
latest_symlink: "{{ backup_dir }}/latest.tar.gz"
tasks:
- name: Ensure backup directory exists
ansible.builtin.file:
path: "{{ backup_dir }}"
state: directory
owner: postgres
group: postgres
mode: "0750"
- name: Dump PostgreSQL database
ansible.builtin.command:
cmd: >
pg_dump -h {{ db_host }} -U {{ db_user }} -F p -f {{ backup_file }} {{ db_name }}
environment:
PGPASSWORD: "{{ hostvars[inventory_hostname].pg_password | default('') }}"
become_user: postgres
- name: Create tar archive from SQL dump
ansible.builtin.archive:
path: "{{ backup_file }}"
dest: "{{ backup_tar }}"
format: gz
- name: Update latest backup symlink
ansible.builtin.file:
src: "{{ backup_tar }}"
dest: "{{ latest_symlink }}"
state: link
force: true
- name: Upload tarball to S3 with rclone
ansible.builtin.command:
argv:
- rclone
- copy
- "{{ backup_tar }}"
- "{{ rclone_destination }}/"Anmerkungen:
rclone copy … remote:bucket/pfad/) auf S3 oder S3-kompatibles Object Storage; Ziel kommt aus rclone_destination (Remote-Name aus rclone.conf auf dem Zielhost, z.B. acme-s3, plus Bucket und Präfix). Authentifizierung wie gewohnt über rclone (Konfiguration, Umgebungsvariablen; siehe rclone S3).latest.tar.gz-Symlink ist unser Rollback-Anker.Polycrate bringt eingebaute Workspace-Verschlüsselung mit age: Es gibt keinen Befehl polycrate secrets add. Sensible Dateien legen Sie direkt unter artifacts/secrets/ ab (workspace-weit oder block-spezifisch unter artifacts/secrets/BLOCKNAME/); polycrate workspace encrypt verschlüsselt sie zu *.age und pflegt u. a. .gitignore. Ohne Polycrate API setzen Sie vor dem ersten Verschlüsseln typischerweise WORKSPACE_ENCRYPTION_KEY – siehe Workspace-Verschlüsselung (Abschnitte zu Key-Quellen, Key-Erzeugung und „Verschlüsseln“).
mkdir -p artifacts/secrets
# z. B. rclone-Konfiguration (siehe https://rclone.org/s3/) – Klartext nur lokal, nicht committen
# cp /pfad/zur/rclone.conf artifacts/secrets/rclone.conf
# Ohne Polycrate API: age Private Key (siehe Doku „Encryption Key erzeugen“)
# export WORKSPACE_ENCRYPTION_KEY="AGE-SECRET-KEY-..."
polycrate workspace encryptKein externer Vault nötig – Compliance wird damit deutlich einfacher.
Die Update-Action kümmert sich um Paketupdates, Service-Restart und initiale Health-Checks. Der Rollback-Mechanismus steckt in einem Ansible-block mit rescue.
blocks/registry.acme-corp.com/acme/infra/linux-maintenance/update.yml:
- name: Update application servers with rollback
hosts: "{{ block.config.web_hosts }}"
become: true
gather_facts: false
vars:
service_name: "{{ block.config.service_name }}"
health_url: "{{ block.config.health_url }}"
backup_dir: "{{ block.config.backup_dir }}"
latest_backup: "{{ backup_dir }}/latest.tar.gz"
tasks:
- name: Ensure latest backup exists before updating
ansible.builtin.stat:
path: "{{ latest_backup }}"
register: backup_stat
- name: Fail if no latest backup is available
ansible.builtin.fail:
msg: "No latest backup found at {{ latest_backup }}. Aborting update."
when: not backup_stat.stat.exists
- name: Perform apt upgrade with rollback safety
block:
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
- name: Upgrade all packages
ansible.builtin.apt:
upgrade: dist
autoremove: true
- name: Restart application service
ansible.builtin.systemd:
name: "{{ service_name }}"
state: restarted
enabled: true
- name: Wait for application port 8080
ansible.builtin.wait_for:
port: 8080
state: started
delay: 5
timeout: 60
- name: HTTP health-check on application
ansible.builtin.uri:
url: "{{ health_url }}"
status_code: 200
validate_certs: false
rescue:
- name: Log update failure and start rollback
ansible.builtin.debug:
msg: "Update failed on {{ inventory_hostname }}, starting rollback from {{ latest_backup }}"
- name: Stop application service before restore
ansible.builtin.systemd:
name: "{{ service_name }}"
state: stopped
- name: Restore application data from latest backup
ansible.builtin.unarchive:
src: "{{ latest_backup }}"
dest: "/"
remote_src: true
- name: Start application service after rollback
ansible.builtin.systemd:
name: "{{ service_name }}"
state: started
enabled: true
- name: HTTP health-check after rollback
ansible.builtin.uri:
url: "{{ health_url }}"
status_code: 200
validate_certs: false
- name: Fail play to signal rollback occurred
ansible.builtin.fail:
msg: "Update failed and rollback from {{ latest_backup }} was executed."So funktioniert der Safety-Net-Mechanismus:
block fehl (z.B. Service startet nicht, HTTP-Check liefert 500), springt Ansible in den rescue-Block.rescue-Block spielt das Backup ein und startet den Service wieder.failt der Task explizit, damit der Workflow (und du) wissen: Es gab ein Problem, aber der Zustand wurde zurückgesetzt.Das ist deutlich robuster als ein README mit „Bitte vor Updates ein Backup machen“.
Die verify-Action ist bewusst simpel – sie codiert das, was sonst oft nur „gefühlte“ Checks sind.
blocks/registry.acme-corp.com/acme/infra/linux-maintenance/verify.yml:
- name: Verify application servers
hosts: "{{ block.config.web_hosts }}"
become: true
gather_facts: false
vars:
service_name: "{{ block.config.service_name }}"
health_url: "{{ block.config.health_url }}"
tasks:
- name: Check service status
ansible.builtin.systemd:
name: "{{ service_name }}"
register: service_state
- name: Fail if service is not running
ansible.builtin.fail:
msg: "Service {{ service_name }} is not running."
when: not service_state.status.ActiveState == "active"
- name: HTTP health-check
ansible.builtin.uri:
url: "{{ health_url }}"
status_code: 200
validate_certs: false
register: health_response
- name: Show response
ansible.builtin.debug:
msg: "Health-check OK: {{ health_response.status }} {{ health_response.msg }}"Dieser Schritt gibt dem ganzen Prozess einen klaren Abschluss: „Ist die Applikation nach Backup und Update in einem guten Zustand?“
Manuell ausführbar ist der Workflow sofort:
# Im Workspace-Root
polycrate workflows run nightly-maintenanceIm Gegensatz zu plain Ansible musst du dir keinen eigenen Wrapper bauen:
ansible-playbook-Aufrufe verkettet.Dank Containerisierung löst Polycrate das klassische Dependency-Problem:
Mehr dazu in der CLI-Referenz.
Angenommen, du betreibst einen zentralen Automation-Host (z.B. automation01.acme-corp.com), auf dem Polycrate installiert ist. Dann kannst du deinen Workflow mit einem systemd-Timer planen.
Service-Unit /etc/systemd/system/polycrate-nightly-maintenance.service:
[Unit]
Description=Polycrate nightly maintenance workflow
[Service]
Type=oneshot
WorkingDirectory=/opt/polycrate-workspaces/acme-corp-automation
ExecStart=/usr/local/bin/polycrate workflows run nightly-maintenance
User=automation
Group=automationTimer-Unit /etc/systemd/system/polycrate-nightly-maintenance.timer:
[Unit]
Description=Run Polycrate nightly maintenance workflow at 2:00
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.targetAktivieren:
sudo systemctl daemon-reload
sudo systemctl enable --now polycrate-nightly-maintenance.timerAb diesem Zeitpunkt läuft dein kompletter Prozess jede Nacht um 2:00 Uhr – klar definiert, versioniert und wiederholbar.
Mit plain Ansible würdest du typischerweise:
backup.yml, update.yml, verify.yml) schreiben.ansible-playbook legen.Das funktioniert – aber du kämpfst mit:
Mit Polycrate:
Code ist damit nicht nur Implementierung, sondern auch Dokumentation: Wer den Workflow liest, versteht den Prozess.
Nein. CI/CD-Workflows (GitHub Actions, GitLab CI, Jenkins-Pipelines) laufen typischerweise bei Code-Änderungen im Kontext eines Repositories. Polycrate-Workflows laufen dort, wo du Infrastruktur und Systeme verwaltest:
Du kannst beides kombinieren: Ein CI/CD-Workflow kann zum Beispiel polycrate workflows run nightly-maintenance auf einem Automation-Host ausführen, wenn bestimmte Bedingungen erfüllt sind.
Wenn du lieber cron nutzt, definierst du einfach einen Cronjob, der in das Workspace-Verzeichnis wechselt und den Workflow ausführt:
0 2 * * * cd /opt/polycrate-workspaces/acme-corp-automation && /usr/local/bin/polycrate workflows run nightly-maintenance >> /var/log/polycrate-nightly.log 2>&1Der Vorteil von Polycrate bleibt derselbe:
Wir sehen solche Workflows als Kern von professioneller Operations-Automatisierung:
Weitere Fragen? Siehe unsere FAQ
In diesem Beitrag hast du ein komplettes Beispiel für einen realen Betriebsprozess als Polycrate-Workflow gesehen:
block/rescue-Mechanik Paketupdates durchführt und bei Problemen automatisch ein Rollback aus dem letzten Backup fährt.Statt fragmentierter Skripte, impliziten Abhängigkeiten und veralteten README-Dateien hast du jetzt:
Bei ayedo arbeiten wir täglich mit Teams, die genau diesen Schritt gehen: von Ad-hoc-Automatisierung hin zu wiederverwendbaren, nachvollziehbaren Betriebsprozessen. Ob du gerade deine ersten Ansible-Playbooks strukturierst oder bestehende Runbooks in Code gießen willst – wir unterstützen dich dabei, ohne deine bestehende Umgebung über den Haufen zu werfen.
Wenn du sehen möchtest, wie ein solcher Workflow in deiner Umgebung aussehen kann – mit deinen Hosts, deinen Compliance-Anforderungen und deinen Toolchains – findest du passende Formate und den nächsten Schritt auf der Workshop-Übersicht (z. B. Operations-Automatisierung Demos).
TL;DR Ansible kann Azure Entra ID (ehem. Azure AD) über die azure.azcollection vollständig …
TL;DR Active-Directory-Änderungen per GUI oder unversionierten PowerShell-Skripten sind …
TL;DR Ansible ist ein starkes Fundament: agentless, idempotent, menschenlesbares YAML und ein …