
Questa è la trascrizione con :
A partire dal secondo commit, qualsiasi codice diventa legacy, poiché le idee iniziali iniziano a divergere dalla dura realtà. Non è né buono né cattivo, è una condizione con cui è difficile discutere e con la quale è necessario convivere. Parte di questo processo è il refactoring. Refactoring dell'Infrastructure as Code. Inizia la storia su come rifattorizzare Ansible in un anno senza perdere la testa.
Nascita del Legacy
Giorno n. 1: Il paziente zero

C'era una volta un progetto condizionale. Aveva un team di sviluppo Dev e ingegneri Ops. Entrambi cercavano di risolvere la stessa questione: come distribuire server e avviare l'applicazione. Il problema era che ciascun team affrontava questa questione a modo suo. Sul progetto si decise di utilizzare Ansible per sincronizzare le conoscenze tra i team Dev e Ops.
Giorno n. 89: Nascita del Legacy

Senzi rendersene conto, volevano fare del loro meglio, ma è diventato legacy. Come è possibile?
- Abbiamo un compito urgente, faremo un hack veloce — poi lo sistemeremo.
- Non è necessario scrivere documentazione, è tutto chiaro su cosa sta succedendo qui.
- So Ansible / Python / Bash / Terraform! Guardate come posso destreggiarmi!
- Sono un Full Stack Overflow Developer e ho copiato questo da stackoverflow; non so come funzioni, ma sembra interessante e risolve il problema.
Alla fine si può ottenere un codice di aspetto strano, per il quale non esiste documentazione, non è chiaro cosa faccia, se sia necessario, ma il problema è che è necessario svilupparlo, apportare modifiche e aggiungere soluzioni palliative, peggiorando ulteriormente la situazione.
- hosts: localhost
tasks:
- shell: echo -n Z >> a.txt && cat a.txt
register: output
delay: 1
retries: 5
until: not output.stdout.find("ZZZ")Giorno n. 109: Comprensione del problema

Il modello IaC originariamente concepito e implementato smette di rispondere alle richieste degli utenti / del business / di altri team, il tempo per apportare modifiche all'infrastruttura smette di essere accettabile. In questo momento si comprende che è giunto il momento di agire.
Refactoring IaC
Giorno n. 139: Hai davvero bisogno di un refactoring?

Prima di lanciarti nel refactoring devi rispondere a una serie di domande importanti:
- Perché hai bisogno di tutto questo?
- Hai tempo?
- Hai conoscenze sufficienti?
Se non sai come rispondere a queste domande, il refactoring potrebbe non mai iniziare o potrebbe semplicemente peggiorare. Poiché hai già avuto esperienze simili. ), è arrivata una richiesta dal progetto per aiutare a correggere i ruoli e coprirli con test.
Giorno n. 149: Preparazione del refactoring

La priorità è prepararsi. Stabilire cosa vogliamo fare. Per questo comunicamo, identifichiamo i punti problematici e valutiamo le possibili soluzioni. Fissiamo in qualche modo i concetti ottenuti, ad esempio con un articolo in Confluence, in modo da non perderci nel caso sorgessero domande su "come è meglio?" o "qual è la soluzione corretta?". Nel nostro caso abbiamo seguito l'idea dividi e conquista: scomponiamo l'infrastruttura in piccoli pezzi / mattoncini. Questo approccio consente di prendere un pezzo isolato dell'infrastruttura, capire cosa fa, coprirlo con test e modificarlo senza temere di rompere qualcosa.

Di conseguenza, il testing dell'infrastruttura diventa la pietra angolare e qui vale la pena menzionare la piramide del testing dell'infrastruttura. Proprio come nell'ambito dello sviluppo, ma per l'infrastruttura: partiamo da test rapidi e poco costosi che verificano semplici aspetti, come i margini, fino a test completi e costosi che distribuiscono un'infrastruttura intera.
Tentativi di testing Ansible
Prima di descrivere come abbiamo testato Ansible nel progetto, descriverò i tentativi e gli approcci che ho dovuto utilizzare in precedenza per capire il contesto delle decisioni prese.
Giorno n° -997: Provisionamento SDS

La prima volta che ho testato Ansible è stata in un progetto di sviluppo SDS (Software Defined Storage). Esiste un articolo separato su questo argomento.
, ma per dirla brevemente, abbiamo ottenuto una piramide rovesciata di test e spendevamo dai 60 ai 90 minuti su un singolo ruolo, il che è lungo. La base era composta da test e2e, ovvero implementavamo un'installazione completa e poi la testavamo. Inoltre, pesava il fatto che stessimo inventando la nostra bicicletta. Ma devo ammettere che questa soluzione funzionava e permetteva di rilasciare in modo stabile.
Giorno n° -701: Ansible e test kitchen

Lo sviluppo dell'idea di testare Ansible ha portato all'uso di strumenti pronti, in particolare test kitchen / kitchen-ci e inspec. La scelta è stata influenzata dalla conoscenza di Ruby (più dettagli nell'articolo su Habr: ) funzionava più velocemente, circa 40 minuti per 10 ruoli. Creavamo un insieme di macchine virtuali e all'interno eseguivamo i test.

In generale, la soluzione funzionava, ma c'era un certo malessere a causa dell'eterogeneità. Quando abbiamo aumentato il numero di test di 13 ruoli di base e 2 meta-ruoli combinando ruoli più piccoli, i test sono impazziti, durando 70 minuti, quasi il doppio. Era difficile parlare di pratiche di XP (extreme programming) poiché nessuno vorrebbe aspettare 70 minuti. Questo ha portato a un cambiamento di approccio.
Giorno n° -601: Ansible e molecule

Concettualmente è simile a testkitchen, solo che abbiamo trasferito il test dei ruoli in docker e cambiato stack. Di conseguenza, il tempo si è ridotto a stabili 20-25 minuti per 7 ruoli.

Aumentando il numero dei ruoli testati a 17 e il linting di 45 ruoli, siamo riusciti a eseguire tutto in 28 minuti su 2 jenkins slave.
Giorno n° 167: Aggiungiamo test Ansible al progetto

Affrontare subito la refactoring del task, probabilmente non sarà possibile. Il task deve essere misurabile, in modo da poterlo suddividere in pezzi più piccoli e mangiare l'elefante a piccoli bocconi. È importante capire se si sta muovendosi nella giusta direzione e quanto lungo sarà il percorso.

In generale, non è così importante come venga fatto, si possono scrivere appunti, attaccare post-it all'armadio, creare task in jira oppure utilizzare Google Docs per annotare lo stato attuale. Il punto è che il processo non è immediato, sarà lungo e noioso. È improbabile che qualcuno desideri che, durante il refactoring, tu possa esaurire le idee, stancarti e rinunciare.
Il refactoring è semplice:
- Mangia.
- Riposa.
- Codifica.
- Test IaC.
- Ripeti.
E così continuiamo finché non raggiungiamo l'obiettivo.

Potrebbe non essere possibile iniziare a testare tutto subito, quindi il nostro primo compito è stato cominciare con il linting e il controllo della sintassi.
Giorno n. 181: Green Build Master

Il linting è un piccolo primo passo verso il Green Build Master. Non romperà quasi nulla, ma aiuterà a perfezionare i processi e a ottenere build verdi in Jenkins. L'idea è di sviluppare abitudini nel team:
- I test rossi sono un problema.
- Se arrivi a correggere qualcosa, migliora anche il codice, rendendolo un po' migliore rispetto a com'era prima.
Giorno n. 193: Dal linting ai test unitari.

Stabilendo il processo di deployment del codice, si può avviare il processo di miglioramento graduale: sostituendo il linting con l'esecuzione dei ruoli, anche senza idempotenza. È fondamentale comprendere come applicare i ruoli e come funzionano.
Giorno n. 211: Dai test unitari ai test di integrazione

Quando la maggior parte dei ruoli è coperta da test unitari e tutto è correttamente lintato, si può passare all'aggiunta dei test di integrazione. Ossia, testare non un singolo mattone dell'infrastruttura, ma le loro combinazioni, per esempio una configurazione completa di un'istanza.

Su Jenkins abbiamo generato molte fasi che lintano i ruoli/ playbook in parallelo, poi i test unitari nei container e alla fine i test di integrazione.
Jenkins + Docker + Ansible = Test

- Controlla il repo e genera le fasi di build.
- Esegui le fasi di linting dei playbook in parallelo.
- Esegui le fasi di linting dei ruoli in parallelo.
- Esegui le fasi di controllo della sintassi dei ruoli in parallelo.
- Esegui le fasi di test dei ruoli in parallelo.
- Lint del ruolo.
- Controlla le dipendenze dagli altri ruoli.
- Controlla la sintassi.
- Crea un'istanza Docker
- Esegui molecule/default/playbook.yml.
- Controlla l'idempotenza.
- Esegui i test di integrazione
- Fine
Giorno n. 271: Bus Factor

Inizialmente, un piccolo gruppo di persone si occupava del refactoring. Effettuavano revisioni del codice nel master. Con il tempo, il team ha sviluppato competenze su come scrivere codice e le revisioni hanno contribuito alla diffusione delle conoscenze sull'infrastruttura e su come è strutturata. La peculiarità era che i revisori venivano selezionati a turno, secondo un calendario, cioè con una certa dose di probabilità si finiva per lavorare su una nuova area dell'infrastruttura.

E qui deve essere comodo. Comodo fare revisioni, vedere in quale compito sono state effettuate, la cronologia delle discussioni. Abbiamo integrato jenkins + bitbucket + jira.
Ma la revisione di per sé non è una panacea. Ad un certo punto, è passato nel master un codice che ci ha causato test intermittenti:
- get_url:
url: "{{ actk_certs }}/{{ item.1 }}"
dest: "{{ actk_src_tmp }}/"
username: "{{ actk_mvn_user }}"
password: "{{ actk_mvn_pass }}"
with_subelements:
- "{{ actk_cert_list }}"
- "{{ actk_certs }}"
delegate_to: localhost
- copy:
src: "{{ actk_src_tmp }}/{{ item.1 }}"
dest: "{{ actk_dst_tmp }}"
with_subelements:
- "{{ actk_cert_list }}"
- "{{ actk_certs }}"Poi è stato corretto, ma è rimasto un retrogusto amaro.
get_url:
url: "{{ actk_certs }}/{{ actk_item }}"
dest: "{{ actk_src_tmp }}/{{ actk_item }}"
username: "{{ actk_mvn_user }}"
password: "{{ actk_mvn_pass }}"
loop_control:
loop_var: actk_item
with_items: "{{ actk_cert_list }}"
delegate_to: localhost
- copy:
src: "{{ actk_src_tmp }}/{{ actk_item }}"
dest: "{{ actk_dst_tmp }}"
loop_control:
loop_var: actk_item
with_items: "{{ actk_cert_list }}"Giorno n. 311: Acceleriamo i test

Con il tempo i test sono aumentati, e i build rallentavano fino a un'ora in situazioni negative. Durante uno dei retro, c'era una frase del tipo "è bello avere test, ma sono lenti". Alla fine abbiamo abbandonato i test di integrazione su macchine virtuali e ci siamo adattati a Docker per velocizzare il processo. Abbiamo anche sostituito Testinfra con Ansible Verifier per ridurre il numero di strumenti utilizzati.

A rigor di termini, qui sono state adottate una serie di misure:
- Passaggio a Docker.
- Eliminare il testing dei ruoli, che si duplicava a causa delle dipendenze.
- Aumentare il numero di slave.
- Ordine di esecuzione dei test.
- Possibilità di lintare TUTTO localmente con un solo comando.

Alla fine, il pipeline su Jenkins è diventato unificato.
- Generare fasi di build.
- Lintare tutto in parallelo.
- Esegui le fasi di test dei ruoli in parallelo.
- Finito.
Lezioni apprese
Evitare variabili globali
Ansible utilizza variabili globali, esiste una parziale soluzione tramite , ma non è una panacea.
Farò un esempio. Supponiamo di avere role_a e role_b
# cat role_a/defaults/main.yml
---
msg: a
# cat role_a/tasks/main.yml
---
- debug:
msg: role_a={{ msg }}# cat role_b/defaults/main.yml
---
msg: b
# cat role_b/tasks/main.yml
---
- set_fact:
msg: b
- debug:
msg: role_b={{ msg }}- hosts: localhost
vars:
msg: hello
roles:
- role: role_a
- role: role_b
tasks:
- debug:
msg: play={{msg}}
La cosa divertente è che il risultato di esecuzione dei playbook dipenderà da fattori non sempre evidenti, come l'ordine delle dichiarazioni delle role. Sfortunatamente, questo è nella natura di Ansible, e la cosa migliore che si può fare è utilizzare alcune convenzioni, come ad esempio utilizzare solo variabili definite all'interno della role.
BAD: utilizzare una variabile globale.
# cat roles/some_role/tasks/main.yml
---
debug:
var: java_homeGOOD: In defaults definire le variabili necessarie e poi utilizzare solo quelle.
# cat roles/some_role/defaults/main.yml
---
r__java_home:
"{{ java_home | default('/path') }}"
# cat roles/some_role/tasks/main.yml
---
debug:
var: r__java_home
Prefix role variables
BAD: utilizzare una variabile globale.
# cat roles/some_role/defaults/main.yml
---
db_port: 5432GOOD: Nella role, utilizzare variabili con un prefisso di nome della role rende più facile comprendere cosa sta succedendo guardando l'inventario.
# cat roles/some_role/defaults/main.yml
---
some_role__db_port: 5432Use loop control variable
BAD: Utilizzare nella loops la variabile standard item, se questo task/playbook fosse incluso altrove, potrebbe portare a comportamenti imprevisti.
---
- hosts: localhost
tasks:
- debug:
msg: "{{ item }}"
loop:
- item1
- item2
GOOD: Ridefinire la variabile in un ciclo tramite loop_var.
---
- hosts: localhost
tasks:
- debug:
msg: "{{ item_name }}"
loop:
- item1
- item2
loop_control:
loop_var: item_name
Verifica le variabili di input
Abbiamo concordato di utilizzare i prefissi delle variabili; è utile verificare che siano definiti come ci si aspetta e, ad esempio, non siano stati sovrascritti con un valore vuoto.
GOOD: Controllare le variabili.
- name: "Verifica che le variabili di stringa richieste siano definite"
assert:
that: ahs_var is defined and ahs_var | length > 0 and ahs_var != None
fail_msg: "{{ ahs_var }} deve essere impostata affinché il ruolo funzioni"
success_msg: "Le variabili richieste {{ ahs_var }} sono definite"
loop_control:
loop_var: ahs_var
with_items:
- ahs_item1
- ahs_item2
- ahs_item3Evitare i dizionari hash, utilizzare una struttura piatta.
Se il ruolo si aspetta un hash/dizionario in uno dei parametri, se vogliamo modificare uno dei parametri secondari, dovremo ridefinire l'intero hash/dizionario, il che aumenterà la complessità della configurazione.
BAD: Utilizzare hash/dizionario.
---
user:
name: admin
group: adminGOOD: Utilizzare una struttura piatta per le variabili.
---
user_name: admin
user_group: "{{ user_name }}"Crea playbook e ruoli idempotenti.
I ruoli e i playbook devono essere idempotenti poiché riducono la deriva della configurazione e il timore di rompere qualcosa. Tuttavia, se si utilizza Molecule, questo comportamento è predefinito.
Evitare l'uso dei moduli shell dei comandi.
L'uso del modulo shell porta a una descrizione imperativa, invece di quella dichiarativa, che è fondamentale per Ansible.
Testa i tuoi ruoli tramite Molecule.
Molecule è uno strumento piuttosto flessibile; vediamo alcuni scenari.
Molecule istanze multiple
In molecule.yml nella sezione piattaforme è possibile descrivere più host da distribuire.
---
driver:
name: docker
platforms:
- name: postgresql-instance
hostname: postgresql-instance
image: registry.example.com/postgres10:latest
pre_build_image: true
override_command: false
network_mode: host
- name: app-instance
hostname: app-instance
pre_build_image: true
image: registry.example.com/docker_centos_ansible_tests
network_mode: hostDi conseguenza, questi host possono essere poi usati in converge.yml utilizzare:
---
- name: Convergere tutto
hosts: all
vars:
ansible_user: root
roles:
- role: some_role
- name: Convergere db
hosts: db-instance
roles:
- role: some_db_role
- name: Convergere app
hosts: app-instance
roles:
- role: some_app_roleVerificatore Ansible
In molecule c'è la possibilità di utilizzare ansible per verificare che l'istanza sia stata configurata correttamente; inoltre, questo è attivo di default dalla versione 3. Questo non è flessibile come testinfra/inspec, ma si possono verificare le aspettative relative al contenuto del file:
---
- name: Verifica
hosts: all
tasks:
- name: copia config
copy:
src: expected_standalone.conf
dest: /root/wildfly/bin/standalone.conf
mode: "0644"
owner: root
group: root
register: config_copy_result
- name: Certifica che standalone.conf sia cambiato
assert:
that: not config_copy_result.changedOppure puoi avviare il servizio, attendere la sua disponibilità e effettuare un test di smoke:
---
- name: Verifica
hosts: solr
tasks:
- command: /blah/solr/bin/solr start -s /solr_home -p 8983 -force
- uri:
url: http://127.0.0.1:8983/solr
method: GET
status_code: 200
register: uri_result
until: uri_result is not failed
retries: 12
delay: 10
- name: Invia documenti a solr
command: /blah/solr/bin/post -c master /exampledocs/books.csvMetti la logica complessa in moduli e plugin
Ansible adotta un approccio dichiarativo, quindi quando effettui ramificazioni nel codice, trasformazioni dei dati o utilizzi moduli shell, il codice diventa difficile da leggere. Per affrontare questa complessità e mantenerlo comprensibile, non è superfluo combattere contro di essa creando i propri moduli.
Riepiloga suggerimenti e trucchi
- Evita le variabili globali.
- Aggiungi un prefisso alle variabili di ruolo.
- Usa variabili di controllo del ciclo.
- Controlla le variabili di input.
- Evita le strutture dizionario hash, usa una struttura piatta.
- Crea playbook e ruoli idempotenti.
- Evita di usare moduli di shell per i comandi.
- Testa i tuoi ruoli tramite molecule.
- Metti la logica complessa in moduli e plugin.
Conclusione

Non è possibile semplicemente rifattorizzare l'infrastruttura di un progetto, anche se hai IaC. È un processo lungo che richiede pazienza, tempo e conoscenze.
Links
- Diapositive
- Video
UPD1 2020.05.01 20:30 — Per la profilazione iniziale dei playbook è possibile utilizzare callback_whitelist = profile_tasks per capire cosa sta impiegando più tempo. Dopodiché, esaminiamo . Si può provare
UPD2 2020.05.03 16:34 —
Fonte: habr.com
