Die Kosten der Komplexität: Ansible AWX

Andrea Dainese
05 May 2024
Post cover

Seit einigen Jahren steht Ansible AWX auf meiner ToDo-Liste. Ich habe es immer wieder aufgeschoben, mich damit zu beschäftigen, wegen seiner Komplexität, aber heute sehe ich mich gezwungen, mich damit auseinanderzusetzen. Ich wusste, dass es herausfordernd sein würde, aber ich hätte mir nicht vorgestellt, wie sehr.

Dieser Beitrag zielt darauf ab, die Schritte zur Installation einer Entwicklungsinstanz von AWX zusammenzufassen und die enormen, unnötigen und schädlichen Komplexitäten zu diskutieren, die unsere Infrastrukturen erreicht haben.

AWX Operator

Zuallererst ist es wichtig zu beachten, dass AWX über Container auf der Kubernetes-Plattform verfügbar ist. Meine Versuche, die Anwendung im Standalone-Modus oder als einfachen Container auf Docker auszuführen, waren erfolglos.

Ich entschied mich, eine Ubuntu Linux 22.04 VM vorzubereiten, auf der ich Minikube installierte. Minikube wiederum installiert eine reduzierte Version von Kubernetes.

Zusammengefasst:

Für die Details der einzelnen Schritte verweise ich Sie auf die beiden Links von Christopher Hart am Ende des Beitrags.

Die Reflexion, die ich anstellen möchte, betrifft die Komplexität der Umgebung, die notwendig ist, um das zu betreiben, was ein Orchestrator von Playbooks ist. Die Wahrscheinlichkeit, dass bei der Einrichtung etwas schief geht, ist sehr hoch, und das Debuggen von Problemen erfordert Fachkenntnisse in vielen, zu vielen, verschiedenen Umgebungen. Ganz zu schweigen davon, wie man ein solches System aktualisiert, sichert, wiederherstellt oder den Prozess für die Katastrophenwiederherstellung entwirft.

Neustart

Nach einigen Stunden der Nutzung stelle ich fest, dass meine Instanz von AWX nicht mehr richtig funktioniert. Der Grund wird fast sofort klar: Der Speicherplatz des Ubuntu Linux-Systems war erschöpft. Ich erweitere den Speicherplatz und starte das System neu… aber AWX scheint nicht zu starten.

In Reihenfolge überprüfe ich:

  • Docker auf Ubuntu Linux;
  • Minikube;
  • Docker innerhalb von Kubernetes;
  • Die AWX-Container innerhalb von Kubernetes.
minikube status
minikube restart
minikube kubectl -- get pods -A
minikube service list
kubectl logs --namespace=awx -p svc/awx-service
kubectl get services --namespace awx

Nach einigen Minuten antwortet der AWX-Dienst, aber er ist nicht freigegeben. Ein paar Minuten mehr, und ich schaffe es, die Anmeldeseite wieder zu erreichen.

Ansible Galaxy

Die Playbooks, die ich geschrieben habe, verwenden die cisco.ios-Sammlung, die standardmäßig mit Ansible installiert ist, aber sie scheint in AWX nicht vorhanden zu sein. Ich entdecke, dass es ein spezifisches Format gibt, um die Abhängigkeiten eines AWX-Projekts zu definieren. Sie müssen die Datei collections/requirements.yml erstellen und die notwendigen Abhängigkeiten angeben:

collections:
- name: cisco.ios
  version: 8.0.0
  source: https://galaxy.ansible.com

Ich führe das Playbook erneut aus und gehe zum nächsten Fehler über.

paramiko vs pylibssh

Der nächste Fehler deutet darauf hin, dass paramiko anstelle von ansible-pylibssh verwendet wird:

ASK [cisco_ios_system : SETTING FACTS] ****************************************
[WARNING]: ansible-pylibssh not installed, falling back to paramiko
[WARNING]: ansible-pylibssh not installed, falling back to paramiko
ok: [sw1.example.com]
ok: [sw2.example.com]
TASK [cisco_ios_system : CONFIGURING HOSTNAME AND DOMAIN NAME] *****************
fatal: [sw2.example.com]: FAILED! => {"changed": false, "msg": "No existing session"}
fatal: [sw1.example.com]: FAILED! => {"changed": false, "msg": "No existing session"}

Jedoch erfordert die cisco.ios-Sammlung die Verwendung von ansible-pylibssh, das standardmäßig nicht in Ansible und AWX vorhanden ist, sondern stattdessen paramiko verwendet. Mit wenig Hoffnung erzwinge ich die Verwendung von pylibssh, indem ich es in der Inventur konfiguriere:

ansible_network_cli_ssh_type: libssh

Ich führe das Playbook erneut aus, und tatsächlich meldet der Fehler das Fehlen der Bibliothek:

TASK [cisco_ios_system : CONFIGURING HOSTNAME AND DOMAIN NAME] *****************
fatal: [sw1.example.com]: FAILED! => {"changed": false, "msg": "Failed to import the required Python library (ansible-pylibssh) on automation-job-11-m85n8's Python /usr/bin/python3. Please read the module documentation and install it in the appropriate location. If the required library is installed, but Ansible is using the wrong Python interpreter, please consult the documentation on ansible_python_interpreter"}
fatal: [sw2.example.com]: FAILED! => {"changed": false, "msg": "Failed to import the required Python library (ansible-pylibssh) on automation-job-11-m85n8's Python /usr/bin/python3. Please read the module documentation and install it in the appropriate location. If the required library is installed, but Ansible is using the wrong Python interpreter, please consult the documentation on ansible_python_interpreter"}

Der obige Fehler zeigt auch, dass das Playbook von einem Container ausgeführt wird, der speziell für jede Ausführung erstellt wurde: automation-job-11-m85n8. Bei der Untersuchung entdecke ich, dass der Container für jeden Job aus dem Image quay.io/ansible/awx-ee erstellt wird. Das Bild ist in AWX in Administration -> Execution Environment definiert.

Also benötige ich ein benutzerdefiniertes awx-ee-Image, das die ansible-pylibssh-Bibliothek enthält. Nach einigen Recherchen finde ich heraus, dass jemand dasselbe Problem wie ich hatte und bereits ein Bild erstellt hat, obwohl es ein Jahr alt ist. Bevor ich mich auf die Erstellung eines neuen Bildes einlasse, im Schnell-und-Schmutzig-Modus, entscheide ich mich, dieses Bild zu verwenden, um zu sehen, ob ich einen funktionierenden Prototyp erhalten kann.

In Administrator -> Execution Environments füge ich ein neues Bild wie folgt hinzu:

  • Name: AWX EE mit pylibssh
  • Bild: quay.io/repository/azzeddineslimani/eev_awx_cisco
  • Pull: Container vor dem Ausführen immer ziehen

Ich verknüpfe dieses neue Bild mit dem Playbook, starte neu, und erhalte einen neuen Fehler:

Receptor detail:
Error creating pod: container failed to start, ImagePullBackOff

Ich entdecke, dass es sich um einen Kubernetes-Fehler handelt, der durch die Unfähigkeit verursacht wird, das Bild herunterzuladen. Ich überprüfe manuell und sehe tatsächlich, dass das Bild im Docker-Repository innerhalb von Kubernetes nicht vorhanden ist:

minikube image ls

Ich versuche den manuellen Weg und erzwinge den Download:

minikube image pull quay.io/azzeddineslimani/eev_awx_cisco

Ein paar Minuten, und das Bild wird korrekt heruntergeladen.

Ich führe das Playbook erneut aus, aber der Fehler ist der gleiche.

$ kubectl logs -f -n awx automation-job-18-gcwfn
Error from server (BadRequest): container "worker" in pod "automation-job-18-gcwfn" is waiting to start: trying and failing to pull image

Der Fehler ist mein: Ich nahm an, dass das Nichtangeben der Bildversion das neueste verfügbare (latest) herunterladen würde, aber das ist nicht der Fall. Ich korrigiere die URL mit quay.io/azzeddineslimani/eev_awx_cisco:latest und führe das Playbook erneut aus.

Der gleiche Fehler.

Ich stoße auf die folgende Beschreibung:

If you believe everything is appropriately configured, try pulling the image directly from the command line (using the docker image pull command) with the same values that are specified in your application manifest. If this works, you know the image is accessible, and that the root cause of the problem lies somewhere in Kubernetes. In this case, lack of available resources or networking configuration issues are your most likely culprit.

Da das Bild korrekt ist und existiert, schließe ich daraus, dass der Fehler an der Anwendung liegt. Ich lösche die Bildreferenz, erstelle sie neu und verweise sie auf das Playbook.

Ich führe das Playbook erneut aus und treffe auf den nächsten Fehler.

Bastion-Host

Der nächste Fehler betrifft die Unzugänglichkeit der Geräte:

fatal: [sw1.example.com]: FAILED! => {"changed": false, "msg": "ssh connection failed: ssh connect failed: Timeout connecting to 169.254.1.111"}
fatal: [sw2.example.com]: FAILED! => {"changed": false, "msg": "ssh connection failed: ssh connect failed: Timeout connecting to 169.254.1.112"}

Der Grund ist einfach: Meine Laborumgebung erfordert die Durchführung über einen Bastion-Host, der eine Authentifizierung über Schlüssel erfordert. Die Lösung in einer Umgebung ohne Container wäre sofort, aber Container haben keine Persistenz, genauso wenig wie der automation-Container, der für jeden Job erstellt wird. Die Lösung ist dieselbe wie im vorherigen Absatz beschrieben: Ich muss ein benutzerdefiniertes awx-ee-Image erstellen.

Noch einmal entscheide ich mich für den schnellen und schmutzigen Weg. Mein Ziel ist es, einen funktionierenden Prototypen zu erhalten und dann herauszufinden, wie ich die aufgetretenen Fehler sauber lösen kann.

Ich füge eine Route hinzu, damit das Ubuntu Linux-System die Geräte ohne den Bedarf an dem Bastion-Host direkt erreichen kann.

Ich führe das Playbook aus und gehe zum nächsten Fehler über.

Authentifizierung

Der nächste Fehler betrifft die Authentifizierung bei den Geräten:

fatal: [sw1.example.com]: FAILED! => {"changed": false, "msg": "ssh connection failed: Failed to authenticate public key: Access denied for 'none'. Authentication that can continue: publickey,keyboard-interactive,password"}
fatal: [sw2.example.com]: FAILED! => {"changed": false, "msg": "ssh connection failed: Failed to authenticate public key: Access denied for 'none'. Authentication that can continue: publickey,keyboard-interactive,password"}

Auch hier ist der Fehler mein: Ich habe den falschen Typ von Ansible Tower: Credentials ausgewählt. Der Network-Typ soll für Geräte verwendet werden, die eine lokale Verbindung benötigen, während das cisco.ios-Modul in den network_cli-Modus migriert wurde, der den Machine-Typ erfordert. Ich erstelle die Anmeldeinformationen erneut mit dem richtigen Typ, aktualisiere das Playbook und führe es erneut aus:

PLAY RECAP *********************************************************************
sw1.example.com            : ok=26   changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
sw2.example.com            : ok=26   changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Schließlich, nach etwa 8 Stunden Arbeit, erhalte ich das Ergebnis.

Schlussfolgerungen

Ich werde definitiv älter, aber ich glaube auch, dass ich einige gute Gründe auf meiner Seite habe: Wenn wir einen Orchestrator installieren müssen, um eine Reihe von Aufgaben zu automatisieren, müssen wir auf eine Komplexität zurückgreifen, wie sie in diesem Beitrag beschrieben wird, vielleicht machen wir etwas falsch.

Nicht nur haben wir Schwierigkeiten, die notwendigen Fähigkeiten zu finden, um ein solches System zu verwalten, nicht nur ignorieren wir die grundlegenden Regeln, die die Sicherheit vorschreiben würde, aber wir haben keine Ahnung, wie komplexe Anwendungen miteinander interagieren. Und wir haben nicht einmal mehr die Werkzeuge, um es zu verstehen.

Übertreibe ich?

Vielleicht, aber für das Projekt, an dem ich arbeite, benötige ich:

  • Netzwerkingenieure, die das Low-Level-Design für die beteiligten Geräte durchführen;
  • Automatisierungsingenieure, die Automatisierungen basierend auf den gegebenen Anforderungen schreiben;
  • Kubernetes-Spezialisten, die den Orchestrator für mich verwalten;
  • Anwendungsspezialisten (AWX in diesem speziellen Fall), die mir helfen zu verstehen, ob und wie ich den Orchestrator an die Bedürfnisse des Projekts anpassen kann;
  • Gurus, die die Sprache jeder beschriebenen Figur verstehen können, und die ein einziges Team schaffen, das gemeinsam spricht und arbeitet. In diesem speziellen Fall erinnere ich mich an eine Diskussion zwischen einem Kubernetes/OpenShift-Experten und einem Netzwerkexperten zum Thema Routing. Dasselbe Wort mit zwei völlig verschiedenen Bedeutungen.

Viel Glück.

Referenzen