Il costo della complessità: Ansible AWX

Andrea Dainese
5 Maggio 2024
Post cover

Da alcuni anni ormai Ansible AWX è nella mia ToDo list. Ho sempre posticipato l’argomento a causa della sua complessità, ma oggi mi trovo a doverlo affrontare. Sapevo che sarebbe stato ostico, ma non immaginavo neanche lontanamente quanto.

Questo post vuole riassumere i passi per installare un’istanza di AWX di sviluppo e discutere sull’enorme, inutile e dannosa complessità che le nostre infrastrutture hanno raggiunto.

AWX Operator

Per prima cosa occorre tenere presente che AWX è disponibile tramite container su piattaforma Kubernetes. A nulla sono valsi i miei tentativi di eseguire l’applicativo in modalità stand alone o come semplice container su Docker.

Ho scelto quindi di preparare una VM Ubuntu Linux 22.04 su cui ho installato Minikube. Minikube a sua volta installa una versione ridotta di Kubernetes.

Riassumendo:

Per i dettagli dei singoli passi vi rimando ai due link di Christopher Hart alla fine del post.

La riflessione che voglio fare riguarda la complessità dell’ambiente necessario all’esecuzione di quello che è un orchestratore di playbook. La probabilità che qualcosa vada storto nel setup è altissima e il debug di eventuali problemi richiede competenze su tanti, troppi, ambienti diversi. Senza parlare di come aggiornare, fare backup, restore o disegnare il processo di disaster recovery per una soluzione di questo tipo.

Reboot

Dopo qualche ora di utilizzo, mi accorgo che la mia istanza di AWX non funziona più correttamente, il motivo appare quasi subito evidente: lo spazio del sistema Ubuntu Linux era finito. Estendo lo spazio disco e riavvio il sistema… ma AWX non sembra partire.

Nell’ordine verifico quindi:

  • docker su Ubuntu Linux;
  • minikube;
  • docker all’interno di kubernetes;
  • i container AWX all’interno di 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

Dopo alcuni minuti il servizio AWX risponde ma non risulta esposto. Qualche altro minuto e riesco a raggiungere nuovamente la pagina di login.

Ansible Galaxy

I playbook che ho scritto usano la collection cisco.ios che è installata di default con Ansible ma sembra non esistere in AWX. Scopro che esiste un formato specifico per definire le dipendenze di un progetto AWX. Occorre creare il file collections/requirements.yml indicando le dipendenze necessarie:

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

Rieseguo il playbook e passo all’errore successivo.

paramiko vs pylibssh

L’errore successivo indica l’uso di paramiko al posto di ansible-pylibssh:

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"}

Tuttavia la collection cisco.ios richiede l’uso di ansible-pylibssh, non presente dei default su Ansible e AWX che usano invece paramiko. Con poche speranze forzo l’uso di pylibssh, configurando nell’inventario:

ansible_network_cli_ssh_type: libssh

Rieseguo il playbook e infatti l’errore riporta l’assenza della libreria:

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"}

L’errore sopra ci mostra anche che il playbook è eseguito da un container creato appositamente ad ogni esecuzione: automation-job-11-m85n8. Indagando scopro che il container viene creato per ogni job dall’immagine quay.io/ansible/awx-ee. L’immagine è definita su AWX in Administration -> Execution Environment.

Ho bisogno quindi di un’immagine awx-ee personalizzata che contenga la libreria ansible-pylibssh. Dopo alcune ricerche scopro che qualcuno ha avuto il mio stesso problema e ha già creato un’immagine, anche se vecchia di un anno. Prima di cimentarmi nel creare una nuova immagine, in modalità quick & dirty decido di usare quell’immagine per verificare se riesco ad ottenere un prototipo funzionante.

In Administrator -> Execution Environments aggiungo una nuova immagine come segue:

  • Name: AWX EE w pylibssh
  • Image: quay.io/repository/azzeddineslimani/eev_awx_cisco
  • Pull: Always pull container before running

Associo questa nuova immagine al playbook, riavvio e ottengo un nuovo errore:

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

Scopro che è un errore di kubernetes, causato dall’impossibilità di scaricare l’immagine. Verifico manualmente e in effetti vedo che l’immagine non è presente nell’archivio di docker all’interno di Kubernetes:

minikube image ls

Tento la via manuale e forzo il download:

minikube image pull quay.io/azzeddineslimani/eev_awx_cisco

Qualche minuto e l’immagine viene correttamente scaricata.

Rieseguo il playbook ma l’errore è il medesimo.

$ 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

L’errore è mio: ho ipotizzato che non inserendo la versione dell’immagine venisse scaricata l’ultima disponibile (latest), ma non è così. Correggo l’URL usando quay.io/azzeddineslimani/eev_awx_cisco:latest ed eseguo nuovamente il playbook.

Stesso errore.

Mi imbatto nella seguente descrizione:

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.

Poiché l’immagine è corretta, esiste, deduco che l’errore sia applicativo. Cancello il riferimento all’immagine, lo creo nuovamente e lo referenzio al playbook.

Eseguo nuovamente il playbook e mi imbatto nel prossimo errore.

Bastion host

L’errore seguente riguarda l’irraggiungibilità dei dispositivi:

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"}

Il motivo è semplice: il mio ambiente di laboratorio prevede di passare per un bastion host, che richiede un’autenticazione tramite chiave. La soluzione in un ambiente senza container sarebbe immediata, ma i container non hanno persistenza, tantomeno il container automation che viene creato per ogni job. La soluzione è la medesima vista per il paragrafo precedente: occorre creare un’immagine awx-ee personalizzata.

Decido nuovamente di andare per la via quick & dirty. Il mio scopo è arrivare ad un prototipo funzionante e poi capire come risolvere in maniera pulita gli errori che ho incontrato.

Aggiungo una rotta in modo che il sistema Ubuntu Linux possa raggiungere direttamente i dispositivi senza bisogno del bastion host.

Eseguo il playbook e passo al nuovo errore.

Autenticazione

Il prossimo errore è riguarda l’autenticazione ai dispositivi:

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"}

Anche qui l’errore è mio: ho sbagliato la tipologia di Ansible Tower: Credentials. La tipologia Network è da usarsi per i dispositivi che richiedono una connessione locale, mentre il modulo cisco.ios è stato migrato alla modalità network_cli che richiede la tipologia Machine. Ricreo le credenziali utilizzando la tipologia corretta, aggiorno il playbook e lo rieseguo:

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   

Finalmente, dopo circa 8 ore di lavoro, ottengo il risultato.

Conclusioni

Sto sicuramente invecchiando, ma credo anche di avere alcune buone ragioni dalla mia parte: se per installare un orchestratore necessario per automatizzare una serie di task dobbiamo scomodare una complessità come quella descritta in questo post, forse stiamo sbagliando qualcosa.

Non solo abbiamo difficoltà a trovare le competenze necessarie a gestire un sistema del genere, non solo stiamo ignorando le basilari regole che la sicurezza imporrebbe, ma non abbiamo idea di come applicazioni complesse interagiscono tra di loro. E non abbiamo nemmeno più gli strumenti per capirlo.

Sto esagerando?

Forse, ma per il progetto su cui sto lavorando, ho bisogno di:

  • network engineer, che fanno il low level design per gli apparati coinvolti;
  • automation engineer, che scrivono le automazioni sulla base dei requisiti dati;
  • specialisti di kubernetes che mi gestiscono l’orchestratore;
  • specialisti applicativi (AWX nel caso specifico) che mi aiutano a capire se e come posso adattare l’orchestratore secondo le necessità del progetto;
  • guru che riescano a capire il linguaggio proprio di ciascuna figura descritta riuscendo a creare un unico team che parli e lavori assieme. Nel caso specifico ricordo una discussione tra un esperto Kubernetes/OpenShift e un esperto di rete sul tema routing. Stessa parola con due significati completamente diversi.

Buona fortuna.

Riferimenti