Das Automatisieren des Patch-Deployments auf Windows mit Ansible / AWX, Teil 7: AWX

Teil 7 der Artikelserie über die Automatisierung mit Ansible.

Vorwort

In Teil 7 der Artikelserie über die Automatisierung mit Ansible anhand des Beispiels “Patch Deployment auf Windows” rollen wir einen AWX-Host, das “graphische Frontend” für Ansible, aus und integrieren unser Inventar und unsere Playbooks.

Die Datenbank

AWX benutzt eine PostgreSQL-Datenbank für die Ablage der Applikationsdaten. Sie kann als Docker-Container auf dem AWX-Host oder als dedizierte Maschine implementiert werden.

Wir verwenden hier den Docker-Container. Für unsere produktive Umgebung verwende ich aus Compliance-Gründen einen dedizierten PostgreSQL-Host.

Wichtig zu wissen ist, dass der PostgreSQL Docker-Container bei jedem ausführen des AWX-Playbooks gespühlt wird – bitte stelle sicher, dass Du Deinen AWX-Host ins Backup einpflegst.

AWX hat eine ungewöhnliche Upgrade-Prozedur: man exportiert alle Objekte, zerstört die Instanz, deployed eine neue Instanz und importiert die Objekte wieder. Das Erhalten der Datenbank bei einem Upgrade ist nicht supported. Bitte konsultiere unbedingt das Upgrade-Dokument.

Vorbereitungen

Wir benötigen einen Ubuntu-Server. Die Installation möchte ich hier nicht beschreiben, die ist im offiziellen Handbuch bestens erklärt. In Teil 1 der Artikelserie findest Du ein paar Hinweise zum Ubuntu-Server.

Bitte nimm weder das Git-Repo, welches wir in Teil 1 erstellt haben noch den Ansible-Host aus Teil 4, sondern erstelle ein neues System.

Das neue System pflegen wir auf dem Ansible-Hosts ins inventory ein:

cd git/ansible
nano -w inventory

Füge bitte die Hostgruppe ubuntu ein und bestücke sie mit den Servern. Ich habe der Vollständigkeit halber ebenfalls den Ansible-Host eingetragen:

Für die weiteren Schritte ist es bequem, wenn wir uns am AWX-Host ohne Kennwort anmelden können. Dazu tragen tragen wir unseren SSH public key zu den authorized_keys des Benutzers root auf dem AWX-Host ein.

In meinem Fall liegt der SSH public key in ~/.ssh/id_rsa.pub – eventuell musst Du diesen Pfad anpassen, beispielsweise wenn Du dsa-Keys verwendest.

Wir benutzen Ansible, um dies durchzuführen:

ansible --inventory inventory --become \
--become-method sudo --ask-become-pass --user localadmin \
--ask-pass --module-name authorized_key \
-a "key={{ lookup('file','/home/localadmin/.ssh/id_rsa.pub') }} \
user=root state=present" s-901-0114.lh128.local

Die Parameter bewirken folgendes:

  • –inventory inventory: Diese Inventar-Datei wird verwendet.
  • –become: Signalisiert, dass auf dem Zielsystem ein Mechanismus für das erlangen von höheren Privilegien erforderlich ist.
  • –become-method sudo: Benutze sudo als Mechanismus.
  • –ask-become-pass: Frage nach dem Kennwort für das erlangen von höheren Privilegien.
  • –user localadmin: Mit diesem Benutzernamen wird auf das Zielsystem zugegriffen.
  • –ask-pass: Frage nach dem Kennwort.
  • –module-name authorized_key: Führe das Modul “authorized_key” aus.
  • -a “key={{ lookup(‘file’,’/home/localadmin/.ssh/id_rsa.pub’) }} user=root state=present”: Parameter für das Modul
    • key=…: Der zu verwendete SSH public key, wir verwenden den jinja2-Filter lookup, welcher an dieser Stelle die Datei /home/localadmin/.ssh/id_rsa.pub einliest.
    • user=root: Füge den key zu den authorized_keys dieses Accounts hinzu.
    • state=present: Der Zielzustand – der key soll präsent sein.
  • s-901-0114.lh128.local: Mit diesem Host möchten wir arbeiten.

Zweimal das Kennwort eingegeben und der SSH public key ist installiert:

Des weiteren benötigen wir später ein SSL-Zertifikat. Dazu generieren wir einen CSR:

openssl req -nodes -new -newkey rsa:2048 -sha256 -out csr.txt

Bitte achte darauf, dass Du die Felder adäquat ausfüllst und dass der Common Name korrekt ist. Den CSR schickst Du nun an eine Öffentliche CA falls Du dies möchtest oder signierst ihn gegen die Enterprise CA:

pscp localadmin@s-901-0110.lh128.local:/home/localadmin/csr.txt Desktop

Damit liegt der CSR auf dem Desktop eines Benutzers, welcher Zertifikate signieren darf, auf einem geeigneten Server

certreq -submit -attrib "CertificateTemplate:WebServer" CSR.txt

Wir speichern das Zertifikat unter dem Namen cert.pem auf dem Desktop und schieben es zurück auf den Ansible-Host:

pscp Desktop\cert.pem localadmin@s-901-0110.lh128.local:/home/localadmin

Zurück auf dem Ansible-Host erstellen wir im Git-Repo einen neuen Ordner namens files, fügen das Zertifikat und den privaten Schlüssel davon zusammen und legen die Datei im neuen Ordner ab. Zum Schluss entfernen wir die beiden Original-Dateien.

mkdir files
cat ~/cert.pem privkey.pem > files/s-901-0114.lh128.local.pem
shred privkey.pem
rm ~/cert.pem
rm privkey.pem

Die Voraussetzungen installieren

AWX benötigt sowohl auf dem Host, auf welchem das Playbook ausgeführt wird, als auch auf dem Zielhost einige Voraussetzungen. Damit wir diese möglichst bequem istallieren können, erstellen wir ein neues Playbook:

nano -w ubuntu.yml

---
- hosts: s-901-0114.lh128.local
tasks:
- name: install dependencies
apt:
package: '{{ item }}'
state: present
with_items:
- apt-transport-https
- ca-certificates
- curl
- gnupg-agent
- software-properties-common
- name: Add docker's official pgp key
shell:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
- name: add the docker repository
shell: add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
- name: apt update
apt:
update_cache: yes
- name: install docker-ce, docker-ce-cli containerd.io
apt:
package: '{{ item }}'
  state: present
with_items:
- docker-ce
- docker-ce-cli
- containerd.io
- python-docker
- docker-compose
- name: ensure package nginx is installed
apt:
package: nginx
state: present
- name: create the tls cert directory
file:
state: directory
path: /etc/nginx/ssl
owner: root
group: root
mode: 0750
  - name: install certificate
copy:
src: files/s-901-0114.lh128.local.pem
dest: /etc/nginx/ssl/certificate.pem
owner: root
group: root
mode: 0644
- name: install nginx site
template:
src: templates/awx.j2
dest: /etc/nginx/sites-available/awx
owner: root
group: root
mode: 0644
vars:
certifiate_path: /etc/nginx/ssl/certificate.pem
awx_hostname: '{{ inventory_hostname }}'
- name: disable nginx site default
file:
path: /etc/nginx/sites-enabled/default
state: absent
- name: enable nginx site awx
file:
src: /etc/nginx/sites-available/awx
dest: /etc/nginx/sites-enabled/awx
state: link
- name: restart service nginx
service:
name: nginx
state: restarted

Das Playbook sollte eigentlich gut verständlich sein. Lass uns das Wichtigste kurz diskurieren:

  • Der oberste Teil entspricht der Installationsanleitung von docker-ce für ubuntu:
    • name: install dependencies
      Installiert die Abhängigkeiten mit dem Modul apt, welche für die Installation von docker-ce notwendig sind. Da wir mehrere Elemente installieren möchten, iterieren wir mit loop_items durch eine Liste mit Paketnamen.
    • name: add docker’s official pgp key
      Fügt den Docker pgp key hinzu. Dazu wenden wir das Modul shell an, welches einfach stur Kommandos ausführt. Das Modul command kann an dieser Stelle nicht angewendet werden, da wir eine pipe im Aufruf haben.
    • name: add the docker repository
      Fügt die docker-ce repository zu apt hinzu.
    • name: apt update
      Aktualisiert den Paket-cache
    • name: install docker-ce, docker-ce-cli containerd.io
      Installiere die Pakete docker-ce, docker-ce-cli, containerd.io, python-docker und docker-compose. Die letzten Beiden sind Abhängigkeiten von AWX.
      Auch hier bedieren wir uns wieder einem Loop vom Typ with_items.
  • Der untere Teil konfiguriert einen nginx reverse proxy. AWX bietet nur einen nicht-verschlüsselten Zugang hia http an, für die Sicherheit dieser Verbindung sind wir selber verantwortlich. Da AWX äussers sensible Informationen beherrbergt und wir nicht möchten, dass unsere Anmeldungen belauscht werden, benutzen wir einen reverse-proxy und https/tls.
    • name: ensure package nginx is installed
      Installiere das Paket nginx
    • name: create the tls cert directory
      Erstelle den Ordner für das TLS-Zertifikat. Wir bedienen uns des Moduls file und sorgen mit den Parametern owner, group und mode dafür, dass nicht jedermann das Zertifikat mit dem privaten Schlüssel auslesen kann.
    • name: install certificate
      Mit dem copy-Modul laden wir das Zertifikat, welches wir vorgängig im Verzeichnis files/s-901-0114.lh128.local.pem abgelegt haben, auf den Zielserver und legen es unter /etc/nginx/ssl/certificate.pem ab.
    • name: install nginx site
      Nun wenden wir mit Hilfe des template-Moduls eine Vorlage auf eine Konfigurationsdatei an. Die Vorlage ist in jinja2 formuliert, wir werden die Datei später anlegen.
    • name: disable nginx site default
      Deaktiviere die default site …
    • name: enable nginx site awx
      … und aktiviere die neu estellte site.
    • name: restart service nginx
      Starte zum Schluss den Service nginx neu

Erstelle nun bitte den templates-Ordner und erstelle eine neue Datei names awx.j2:

mkdir templates
nano -w templates/awx.j2

server {
listen 443 ssl http2;
server_name {{ awx_hostname }};

ssl on;
ssl_certificate /etc/nginx/ssl/certificate.pem;
ssl_certificate_key /etc/nginx/ssl/certificate.pem;

location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://127.0.0.1:18080;
}
}

server {
listen 80;
server_name {{ awx_hostname }}
add_header Strict-Transport-Security max-age=2592000;
rewrite ^ https://$server_name$request_uri? permanent;
}

Auf die nginx-Config möchte ich nicht weiter eingehen, das würde den Rahmen dieser Einführung definitiv sprengen. Nginx verfügt über eine gute Dokumentation.

Jinja2 ist anfangs etwas gewöhnungsbedürftig, erleichtert aber den Umgang mit Konfigurationsdateien massiv. Hier haben wir eine nginx-spezifische Konfiguration, in welche wir zwei Variablen eingefügt haben. Im Playbook werden diese abgefüllt – dies erhöht die Portabilität der Vorlage.

Nun können wir unseren AWX-Host mit allen Vorbereitungen bestücken lassen:

Bitte checke die Änderungen an dieser Stelle in Dein git-repo ein.

AWX

Endlich kommen wir zur Hauptattraktion: AWX!

Die Installation läuft über Ansible, dazu checknen wir das offizielle AWX-Repo von github:

cd ..
git clone https://github.com/ansible/awx awx
cd awx/installer

Hier editieren wir das inventory:

Folgende Zeilen ändern wir:

  • Die erste Zeile:
    • Ersetze bitte localhost durch den hostnamen Deines AWX-Hosts.
    • ersetze bitte ansible_connection=”local” durch ansible_connection=”ssh”, da wir AWX nicht auf dem lokalen Host ausrollen möchten.
  • host_port=127.0.0.1:18080
    AWX soll auf 127.0.0.1:18080 lauschen, nicht auf 0.0.0.0:80.
  • pg_password=awxpass
    Setze bitte ein sicheres Kennwort für die Datenbank.
  • create_preload_data=False
    Lässt Du dies auf True, wird AWX mit Demodaten abgefüllt.
  • secret_key=awxsecret
    Setze hier ein wirklich sicheres “Geheimwort”. Es wird verwendet, um Deine Anmeldedaten symmetrisch zu verschlüsseln. 12345 ist kein sicheres Kennwort!
  • awx_alternate_dns_servers=”10.10.17.17,10.10.17.19″
    Diese Variable füllst Du bitte mit den DNS-Servern Deiner Active Directory ab.
  • awx_container_search_domains=lh128.local
    Und diese Variable mit Deiner search domain.
  • ca_trust_dir=/etc/pki/ca-trust/source/anchors
    Mit dieser Variable kannst Du ein Verzeichnis angeben, welcher Zertifikate von vertrauenswürdigen Zertifizierungsstellen enthält. Damit könntest Du das Zertifikat Deiner Enterprise CA in AWX einfügen.
  • docker_compose_dir=”/root/awxcompose”
    Die docker-compose-Konfiguration wird in diesem Ordner abgelegt. Standardmässig zeigt dieser nach /tmp/awxcompose, was keinen Neustart überlebt.

Sind die Änderungen abgeschlossen, wir können AWX ausrollen:

ansible-playbook --inventory inventory --user root install.yml

Das Rollout kann dauern.

Nun können wir in den Bowser wechseln:

Bitte melde Dich mit den Standard-Anmeldedaten (admin/password) an. Nach dem Login findest Du oben rechts eine Verknüpfung, mit derer Du das Kennwort wechseln kannst.

Nach dem Loin begrüsst uns AWX mit dem Dashboard – es zeigt uns eine Übersicht über unser System.

In den nächsten Schritten werden wir

  • Eine Organisation anlegen.
  • Die Anmeldedaten für dss Git-Repo und die Windows-Maschinen hinterlegen.
  • Das git-repo anziehen.
  • Das Inventar aus dem repo importieren.
  • Ein Job Template erstellen.

Lass uns mit dem Anlegen der Oanisation beginnen: Klicke im Menü Organizations auf Add (+).

Speichere die Organisation mit save ab.

Als nächstes hinterlegen wir die Anmeldedaten für das git-repo und den Windows-Account.

Für den Zugriff aus git-repo erstellst Du bitte ein neues ssh public/private key Pärchen und erteilst dem public key read-Rechte auf das Repo. Die dazu notwendigen Schritte kannst Du aus Teil 1 ableiten.

Im Menü Credential fügen wir mit Add (+) ein neues Anmeldeobjekt hinzu:

Bitte fülle folgende Felder ab:

  • Name: Bezeichnung für dieses Anmeldeobjekt.
  • Organization: Das eben erstellte Organisations-Objekt.
  • Credential type: Source control für die Anmeldung an einem scm wie git.
  • Username: Anmeldename für das git-repo. Beispielsweise git oder gitolite3.
  • Und den SSH private key.

… und speichere das Anmeldeobjekt und erstelle ein Weiteres für Windows:

Bitte fülle folgende Felder ab:

  • Name: Bezeichnung für dieses Anmeldeobjekt.
  • Organization: Das eben erstellte Organisations-Objekt.
  • Credential type: Machine für die Anmeldung an einem anderen System.
  • Username: Einen adäquaten Benutzernamen in UPN-Notation. Bitte verwende einen dedizierten Service-Account mit den notwendigen Rechten, nicht deinen persönlichen Account. One account, one usage.
  • Password: Das Kennwort.

Vergiss nicht, auch dieses Anmeldeobjekt zu speichern.

Als nächstes ziehen wir das git-repo im Menü Projects mit Add (+) an:

Bitte fülle folgende Felder ab:

  • Name: Bezeichnung für dieses Pojekt.
  • Organization: Das eben erstellte Organisations-Objekt.
  • Scm url: Url des git-repos.
  • Scm branch/tag/commit: Den branch/tag/commit, welcher angezogen werden soll. Wir verwenden master.
  • Scm credential: Das Anmeldeobjekt für das git-repo.

Nach dem Speichern wird das Repo direkt angezogen:

Mit dem Reesh-Icon kannst Du zum aktuellen commit im branch vorpreschen.

Nun erstellen wir ein Inventory im entsprechenden Menü:

Wir füllen folgende Felder ab:

  • Name: Bezeichnung für dieses Inventar.
  • Organization: Das eben erstellte Organisations-Objekt.

Im Inventar öffnen wir die sources und fügen eine neue source hininzu:

Wir füllen folgende Felder ab:

  • Name: Bezeichnung für diese Inventarquelle.
  • Source: Sourced from a project – erstelle das Inventar aus einem Projekt.
  • Project: Unser erstelltes Projekt.
  • Inventory file: Die Inventar-Datei aus dem Projekt – inventory.

Zum Schluss erstellen wir im Menü Template mit Add (+) ein Job Template:

Folgende Felder füllen wir ab:

  • Name: Der Name des neuen Job Template Objekts.
  • Job Type: Run – das Playbook ausführen.
  • Inventory: Das Inventar, welches wir gemeinsam erstellt haben.
  • Project: Unser erstelltes Projekt.
  • Playbook: Das Playbook, welches ausgeführt werden soll.
  • Credential: Die Anmeldedaten für das Zielsystem
  • Forks: Anzahl der parallel auszuführenden Operationen. Dies ist abhängig von der Infrastruktur und des Playbooks.
    Der Wert bedeutet für unser Playbook, dass maximal 5 Server gleichzeitig Windows Update durchführen werden.
    In der Bude habe ich normalerweise 256 konfiguriert – unsere Infra macht das problemlos mit – wir haben aber auch nur 3 produktive Windows-Server. Eine Kundeninfrastruktur ist mit 15 parallelen Operationen im identischen Kontext (Windows Update) bereits überfordert.
    Du wirst diesen Wert empirisch ermitteln müssen, um unnötige Ausfälle zu vermeiden würde ich empfehlen, klein anzufangen und hoch zu skalieren.
    Entspricht dem ansible-playbook Parameter –forks.
  • Limit: Beschränke die Zielsysteme auf diesen Host(s) oder diese Gruppe(n)
    Entspricht dem ansible-playbook Parameter –limit.

Mit einem Klick auf den Button mit der Rakete wird ein Job anhand unseres eben erstellten Templates ausgeführt:

Damit sind wir am Ende des Tutorial. Ab hier stehen Dir nun alle Möglichkeiten offen, ab Sofort kannst Du Windows Updates per Kopfdruck deployen – natürlich auch Zeitgesteuert! Mehr dazu findest Du in der offiziellen Dokumentation.

Wichtig ist, dass Du weiter machst. Ansible und AWX benötigen Übung und Zeit – mit jedem Szenario und dem dazugehörigen Playbook, welches Du formuliertst, wachsen Dein Wissen und Deine Erfahrung, und die Zeit, welches Du in das Umsetzen Deiner Ziele aufwenden, wird mal um mal kürzer.

Das Ansible-Ökosystem ist aktiv und reichhaltig, Du wirst, wenn Du sie benötigst, kompetente Hilfe im IRC, im Usenet, in Blogs und in Foren finden.

Irgendwann wird Ansible Spass machen! 🙂

DevOps ist die Zukunft, ob wir wollen oder nicht.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.