
Dies ist die Transkription findet man :
Ab dem zweiten Commit wird jeder Code zu Legacy-Code, da die ursprünglichen Ideen nicht mehr mit der rauen Realität übereinstimmen. Das ist weder gut noch schlecht, sondern eine Gegebenheit, mit der man leben muss. Teil dieses Prozesses ist das Refactoring. Refactoring von Infrastructure as Code. Lassen Sie uns die Geschichte erzählen, wie man Ansible in einem Jahr refakturiert und dabei den Verstand behält.
Die Entstehung von Legacy
Tag Nr. 1: Der Nullpatient

Es gab ein hypothetisches Projekt. Darauf befand sich ein Dev-Team und Ops-Ingenieure. Sie standen vor derselben Herausforderung: Wie man Server bereitstellt und eine Anwendung startet. Das Problem war, dass jede Gruppe ihr eigenes Vorgehen hatte. Im Projekt wurde beschlossen, Ansible zu verwenden, um das Wissen zwischen den Dev- und Ops-Teams zu synchronisieren.
Tag Nr. 89: Die Entstehung von Legacy

Unbemerkt von uns selbst wollten wir es so gut wie möglich machen, und am Ende resultierte es in Legacy. Wie kann das passieren?
- Wir haben hier eine dringende Aufgabe, wir machen einen schnellen Hack – das reparieren wir später.
- Die Dokumentation kann weggelassen werden; es ist ja so klar, was hier passiert.
- Ich kenne Ansible / Python / Bash / Terraform! Sehen Sie, wie ich mich winden kann!
- Ich bin ein Full Stack Overflow Developer und habe das von Stack Overflow kopiert. Ich weiß nicht, wie es funktioniert, aber es sieht gut aus und löst das Problem.
Am Ende kann man einen völlig unverständlichen Code erhalten, für den es keine Dokumentation gibt, unklar ist, was er macht, und ob er nötig ist. Das Problem ist, dass Sie ihn weiterentwickeln, anpassen und improvisieren müssen, was die Situation nur noch schlimmer macht.
- hosts: localhost
tasks:
- shell: echo -n Z >> a.txt && cat a.txt
register: output
delay: 1
retries: 5
until: not output.stdout.find("ZZZ")Tag Nr. 109: Problembewusstsein

Das ursprünglich gedachte und umgesetzte IaC-Modell entspricht nicht mehr den Anforderungen der Benutzer / des Geschäfts / anderer Teams. Die Zeit für Änderungen in der Infrastruktur wird untragbar. An diesem Punkt wird klar, dass es Zeit ist, Maßnahmen zu ergreifen.
Refactoring von IaC
Tag Nr. 139: Brauchen Sie wirklich ein Refactoring?

Bevor Sie mit dem Refactoring beginnen, sollten Sie eine Reihe wichtiger Fragen beantworten:
- Warum brauchen Sie das alles?
- Haben Sie genug Zeit?
- Haben Sie genügend Wissen?
Wenn Sie nicht wissen, wie Sie auf die Fragen antworten sollen, wird das Refactoring weder beginnen noch möglicherweise sogar nur schlechter werden. Da es Erfahrung gab, kam eine Anfrage vom Projekt, um zu helfen, Rollen zu korrigieren und sie mit Tests zu überdecken.
Tag Nr. 149: Vorbereitung des Refactorings

Das Wichtigste ist, sich vorzubereiten. Bestimmen, was wir tun werden. Dazu sprechen wir, identifizieren Problemstellen und überlegen uns Lösungswege. Die gewonnenen Konzepte halten wir irgendwie fest, zum Beispiel in einem Artikel in Confluence, damit wir bei Fragen wie "Was ist besser?" oder "Was ist richtig?" nicht vom Kurs abkommen. In unserem Fall hielten wir uns an die Idee teile und herrsche: wir zerlegen die Infrastruktur in kleine Stücke / Bausteine. Dieser Ansatz ermöglicht es, einen isolierten Teil der Infrastruktur zu nehmen, zu verstehen, was er tut, ihn mit Tests zu überziehen und Änderungen vorzunehmen, ohne Angst zu haben, etwas kaputt zu machen.

Das Testen der Infrastruktur wird zum Grundpfeiler, und hier ist es wichtig, die Testpyramide der Infrastruktur zu erwähnen. Genau die gleiche Idee wie in der Entwicklung, jedoch für die Infrastruktur: Wir beginnen mit kostengünstigen, schnellen Tests, die einfache Dinge wie Abstände überprüfen, und steigen dann zu teureren, umfassenden Tests auf, die eine gesamte Infrastruktur bereitstellen.
Versuche zur Ansible-Testung
Bevor wir beschreiben, wie wir Ansible in unserem Projekt getestet haben, möchte ich die früheren Versuche und Ansätze skizzieren, die wir verwendet haben, um den Kontext der getroffenen Entscheidungen zu verstehen.
Tag Nr. -997: SDS-Bereitstellung

Das erste Mal, dass ich Ansible testen durfte, geschah im Rahmen eines Projekts zur Entwicklung von SDS (Software Defined Storage). Es gibt einen separaten Artikel zu diesem Thema.
, aber kurz gesagt, wir haben eine umgekehrte Test-Pyramide erstellt, und wir haben 60-90 Minuten für eine Rolle benötigt, was zu lange ist. Die Grundlage waren e2e-Tests, d.h. wir haben eine vollständige Installation bereitgestellt und diese dann getestet. Hinzu kam das Rad selber zu erfinden. Aber man muss zugeben, dass diese Lösung funktionierte und es uns ermöglichte, stabil zu releasen.
Tag № -701: Ansible und Test Kitchen

Die Weiterentwicklung der Idee des Testens in Ansible war die Nutzung von bestehenden Tools, nämlich Test Kitchen / Kitchen-CI und Inspec. Die Wahl fiel aufgrund der Ruby-Kenntnisse (mehr dazu in dem Artikel auf Habré: ) arbeitete schneller, etwa 40 Minuten für 10 Rollen. Wir haben eine Gruppe von virtuellen Maschinen erstellt und dort die Tests durchgeführt.

Insgesamt funktionierte die Lösung, aber es gab einen Nachgeschmack wegen der Uneinheitlichkeit. Als wir jedoch die Anzahl der zu testenden Rollen auf 13 Grundrollen und 2 Meta-Rollen, die kleinere Rollen kombinieren, erhöhten, begannen die Tests plötzlich 70 Minuten zu dauern, was fast doppelt so lange ist. Über XP (Extreme Programming) Praktiken zu sprechen, war schwierig, denn niemand möchte 70 Minuten warten. Das war der Anlass, unseren Ansatz zu ändern.
Tag № -601: Ansible und Molecule

Konzeptionell ähnelt es testkitchen, allerdings haben wir das Rollentesting in Docker übertragen und den Stack gewechselt. Das Ergebnis ist, dass die Zeit auf stabile 20-25 Minuten für 7 Rollen verkürzt wurde.

Indem wir die Anzahl der getesteten Rollen auf 17 erhöhten und 45 Rollen linpten, haben wir dies in 28 Minuten auf 2 Jenkins-Slaves durchlaufen.
Tag Nr. 167: Wir fügen dem Projekt Ansible-Tests hinzu.

Die Aufgabe des Refactorings kann voraussichtlich nicht im Handumdrehen erledigt werden. Die Aufgabe sollte messbar sein, damit Sie sie in kleine Stücke aufteilen und den Elefanten mit einem Teelöffel nach und nach essen können. Es sollte ein Verständnis darüber bestehen, ob Sie sich in die richtige Richtung bewegen und wie lange der Weg noch dauern wird.

Im Grunde ist es nicht entscheidend, wie es gemacht wird: Man kann es auf ein Blatt Papier schreiben, Post-its am Schrank anbringen, Aufgaben in Jira erstellen oder ein Google-Dokument erstellen, um den aktuellen Status festzuhalten. Der Ursprung liegt darin, dass der Prozess nicht sofort abgeschlossen ist; er wird langwierig und mühsam sein. Es ist unwahrscheinlich, dass jemand möchte, dass Sie während des Refactorings die Ideen verlieren, ermüden und aufgeben.
Refactoring ist einfach:
- Essen.
- Schlafen.
- Programmieren.
- IaC-Test.
- Wiederholen.
Und so wiederholen wir, bis wir das angestrebte Ziel erreichen.

Es kann nicht alles auf einmal getestet werden, daher war unsere erste Aufgabe die Überprüfung und das Linting des Codes.
Tag Nr. 181: Green Build Master

Linting ist ein kleiner erster Schritt zum Green Build Master. Es bricht fast nichts, ermöglicht aber die Feinabstimmung der Prozesse und erzeugt grüne Builds in Jenkins. Die Idee ist, Gewohnheiten im Team zu entwickeln:
- Rote Tests sind schlecht.
- Wenn du kommst, um etwas zu korrigieren, verbessere auch den Code ein wenig im Vergleich zu dem, was du vorgefunden hast.
Tag Nr. 193: Vom Linting zu Unit-Tests

Nachdem der Prozess, wie der Code in den Master gelangt, etabliert ist, kann der schrittweise Verbesserungsprozess beginnen – indem man das Linting durch Rollenübergaben ersetzt, sogar ohne Idempotenz. Es ist wichtig zu verstehen, wie man Rollen anwendet und wie sie funktionieren.
Tag Nr. 211: Von Unit- zu Integrationstests

Wenn die meisten Rollen durch Unit-Tests abgedeckt und alles gelintet ist, kann man mit der Hinzufügung von Integrationstests beginnen. Das heißt, man testet nicht nur einen einzelnen Baustein in der Infrastruktur, sondern deren Kombination, zum Beispiel die vollständige Konfiguration eines Instanz.

In Jenkins haben wir zahlreiche Stufen generiert, die parallel Rollen/Playbooks linten, dann Unit-Tests in Containern durchführen und schließlich Integrationstests.
Jenkins + Docker + Ansible = Tests

- Repo auschecken und Build-Phasen generieren.
- Lint-Playbook-Phasen parallel ausführen.
- Lint-Rollen-Phasen parallel ausführen.
- Syntaxprüfung der Rollen-Phasen parallel ausführen.
- Test-Rollen-Phasen parallel ausführen.
- Lint-Rolle.
- Abhängigkeit von anderen Rollen überprüfen.
- Syntax überprüfen.
- Docker-Instanz erstellen.
- Molecule/default/playbook.yml ausführen.
- Idempotenz überprüfen.
- Integrationstests durchführen.
- Fertigstellen
Tag Nr. 271: Bus-Faktor

In der Anfangszeit war eine kleine Gruppe von zwei bis drei Personen mit dem Refactoring beschäftigt. Sie führten Code-Reviews im Master durch. Im Laufe der Zeit entwickelte das Team ein Wissen darüber, wie man Code schreibt, und die Code-Überprüfungen förderten die Verbreitung des Wissens über die Infrastruktur und deren Funktionsweise. Das Besondere war, dass die Reviewer nach einem festgelegten Plan ausgewählt wurden, was bedeutete, dass du mit einer gewissen Wahrscheinlichkeit einen neuen Bereich der Infrastruktur erkunden konntest.

Und es sollte hier bequem sein. Es sollte einfach sein, Reviews durchzuführen, zu sehen, in welchem Rahmen sie gemacht werden und die Diskussionen nachzuverfolgen. Wir integrierten Jenkins + Bitbucket + Jira.
Aber so ist das Review an sich keine Allheilmittel. Irgendwie hat bei uns ein Code in den Master gelangt, der uns flappende Tests beschert hat.
- 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 }}"Das wurde später behoben, aber der Eindruck blieb.
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 }}"Tag Nr. 311: Wir beschleunigen die Tests

Mit der Zeit nahmen die Tests zu, die Builds waren im schlechtesten Fall langsam und dauerten bis zu einer Stunde. Bei einem der Retros gab es einen Kommentar wie "gut, dass es Tests gibt, aber sie sind langsam". Schließlich haben wir auf Integrations-Tests in virtuellen Maschinen verzichtet und diese für Docker angepasst, um die Geschwindigkeit zu erhöhen. Außerdem haben wir Testinfra durch Ansible Verifier ersetzt, um die Anzahl der verwendeten Tools zu reduzieren.

Genauer gesagt, es gab ein Maßnahmenpaket:
- Umstieg auf Docker.
- Die Testung von Rollen entfernen, die durch Abhängigkeiten dupliziert wird.
- Die Anzahl der Slaves erhöhen.
- Reihenfolge der Testausführung.
- Möglichkeit zur Linting. ALLES lokal von einem Team.

So wurde auch die Pipeline in Jenkins vereinheitlicht.
- Bauen Sie die Phasen.
- Linten Sie alles parallel.
- Test-Rollen-Phasen parallel ausführen.
- Fertigstellen.
Erfahrungen gesammelt
Globale Variablen vermeiden
Ansible verwendet globale Variablen; es gibt teilweise einen Workaround in Form von , aber das ist kein Allheilmittel.
Hier ein Beispiel. Angenommen, wir haben role_a und 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}}
Eine interessante Sache ist, dass das Ergebnis der Playbooks von nicht immer offensichtlichen Dingen abhängt, wie beispielsweise der Reihenfolge der Rollen. Leider ist das in der Natur von Ansible, und das Beste, was man tun kann, ist, irgendwelche Absprachen zu verwenden, wie zum Beispiel, innerhalb der Rolle nur die Variablen zu verwenden, die in dieser Rolle beschrieben sind.
SCHLECHT: eine globale Variable verwenden.
# cat roles/some_role/tasks/main.yml
---
debug:
var: java_homeGUT: In defaults die notwendigen Variablen definieren und später nur diese verwenden.
# 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
Präfix-Rollenvariablen
SCHLECHT: eine globale Variable verwenden.
# cat roles/some_role/defaults/main.yml
---
db_port: 5432GUT: Für Variablen in der Rolle Variablen mit dem Präfix des Rollennamens verwenden; das vereinfacht das Verständnis, wenn man das Inventory betrachtet.
# cat roles/some_role/defaults/main.yml
---
some_role__db_port: 5432Verwenden Sie die Schleifensteuerungsvariable
SCHLECHT: Verwenden Sie in Schleifen die Standardvariable item, da dies zu unvorhergesehenem Verhalten führen kann, wenn dieser Task/Playbook irgendwo inkludiert wird.
---
- hosts: localhost
tasks:
- debug:
msg: "{{ item }}"
loop:
- item1
- item2
GUT: Variablen im Loop überschreiben durch loop_var.
---
- hosts: localhost
tasks:
- debug:
msg: "{{ item_name }}"
loop:
- item1
- item2
loop_control:
loop_var: item_name
Eingangsvariablen überprüfen
Wir haben uns darauf geeinigt, Variablenpräfixe zu verwenden. Es ist sinnvoll zu überprüfen, ob sie so definiert sind, wie wir es erwarten, und dass sie beispielsweise nicht mit einem leeren Wert überschrieben wurden.
GUT: Variablen überprüfen.
- name: "Überprüfen, ob erforderliche String-Variablen definiert sind"
assert:
that: ahs_var is defined and ahs_var | length > 0 and ahs_var != None
fail_msg: "{{ ahs_var }} muss gesetzt werden, damit die Rolle funktioniert"
success_msg: "Erforderliche Variablen {{ ahs_var }} sind definiert"
loop_control:
loop_var: ahs_var
with_items:
- ahs_item1
- ahs_item2
- ahs_item3Vermeiden Sie Hash-Dictionaries, verwenden Sie eine flache Struktur
Wenn die Rolle ein Hash/Dictionary in einem der Parameter erwartet, dann müssen wir, wenn wir einen der darunterliegenden Parameter ändern wollen, das gesamte Hash/Dictionary neu definieren, was die Konfiguration komplizierter macht.
SCHLECHT: Hash/Dictionary verwenden.
---
user:
name: admin
group: adminGUT: Flache Variablenstruktur verwenden.
---
user_name: admin
user_group: "{{ user_name }}"Idempotente Playbooks & Rollen erstellen
Rollen und Playbooks sollten idempotent sein, da dies die Konfigurationsdrift reduziert und die Angst verringert, etwas zu beschädigen. Wenn Sie jedoch Molecule verwenden, ist dieses Verhalten standardmäßig gegeben.
Vermeiden Sie die Verwendung von Shell-Modulen.
Die Verwendung des Shell-Moduls führt zu einer imperativen Beschreibung, anstelle der deklarativen, die das Kernelement von Ansible ist.
Testen Sie Ihre Rollen mit Molecule.
Molecule ist ein ziemlich flexibles Tool, schauen wir uns einige Szenarien an.
Molecule Mehrere Instanzen.
In molecule.yml im Abschnitt platforms Man kann mehrere Hosts beschreiben, die bereitgestellt werden sollen.
---
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: hostDiese Hosts können dann in converge.yml verwendet werden:
---
- name: Alle zusammenführen
hosts: all
vars:
ansible_user: root
roles:
- role: some_role
- name: DB zusammenführen
hosts: db-instance
roles:
- role: some_db_role
- name: App zusammenführen
hosts: app-instance
roles:
- role: some_app_roleAnsible Verifier
In molecule besteht die Möglichkeit, Ansible zu verwenden, um zu überprüfen, ob die Instanz korrekt konfiguriert wurde. Zudem ist dies seit der dritten Version standardmäßig der Fall. Diese Methode ist nicht so flexibel wie testinfra/inspec, aber man kann prüfen, ob der Inhalt einer Datei unseren Erwartungen entspricht.
---
- name: Überprüfen
hosts: all
tasks:
- name: Konfiguration kopieren
copy:
src: expected_standalone.conf
dest: /root/wildfly/bin/standalone.conf
mode: "0644"
owner: root
group: root
register: config_copy_result
- name: Bestätigen, dass standalone.conf geändert wurde
assert:
that: not config_copy_result.changedOder einen Dienst bereitstellen, auf dessen Verfügbarkeit warten und einen Smoke-Test durchführen:
---
- name: Überprüfen
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: Dokumente an Solr übermitteln
command: /blah/solr/bin/post -c master /exampledocs/books.csvKomplexe Logik in Module & Plugins einfügen
Ansible propagiert einen deklarativen Ansatz, weshalb der Code bei Verzweigungen, Datenumwandlungen und Shell-Modulen schwer lesbar werden kann. Um dem entgegenzuwirken und die Verständlichkeit zu erhalten, ist es sinnvoll, diese Komplexität durch die Erstellung eigener Module zu bekämpfen.
Zusammenfassung von Tipps & Tricks
- Vermeiden Sie globale Variablen.
- Präfixrollenvariablen.
- Verwenden Sie Schleifensteuerungsvariablen.
- Überprüfen Sie Eingangsvariablen.
- Vermeiden Sie Hash-Dictionaries, verwenden Sie flache Strukturen.
- Erstellen Sie idempotente Playbooks & Rollen.
- Vermeiden Sie die Verwendung von Befehlszeilenmodulen.
- Testen Sie Ihre Rollen mit Molecule.
- Setzen Sie komplexe Logik in Module & Plugins ein.
Fazit

Man kann die Infrastruktur in einem Projekt nicht einfach mal so refaktorisieren, selbst wenn Sie IaC haben. Es ist ein langwieriger Prozess, der Geduld, Zeit und Wissen erfordert.
Links
- Folien
- Video
UPD1 2020.05.01 20:30 — Für die erste Profilierung von Playbooks kann verwendet werden callback_whitelist = profile_tasks um herauszufinden, was genau lange dauert. Danach gehen wir zu . Man kann es mit
UPD2 2020.05.03 16:34 —
Quelle: habr.com
