Verwalten mehrstufiger Umgebungen mit Ansible

Einführung

Ansible ist ein leistungsstarkes Konfigurationsmanagementsystem, mit dem Infrastrukturen und Anwendungen in verschiedenen Umgebungen eingerichtet und verwaltet werden können. Während Ansible leicht lesbare Syntax, flexible Workflows und leistungsstarke Tools bietet, kann es schwierig sein, eine große Anzahl von Hosts zu verwalten, wenn diese je nach Implementierungsumgebung und Funktionalität variieren.

In diesem Handbuch werden einige Strategien für die Verwendung von Ansible für die Arbeit mit mehrstufigen Bereitstellungsumgebungen erläutert. Typischerweise führen die Anforderungen für verschiedene Stufen zu unterschiedlichen Anzahlen und Konfigurationen von Komponenten. Beispielsweise können sich die Speicheranforderungen für einen Entwicklungsserver von denen für Staging und Produktion unterscheiden, und es ist wichtig, explizit zu steuern, wie die Variablen, die diese Anforderungen darstellen, priorisiert werden. In diesem Artikel werden einige Möglichkeiten erörtert, wie diese Unterschiede abstrahiert werden können, und einige von Ansible bereitgestellte Konstrukte, um die Wiederverwendung von Konfigurationen zu fördern.

Unvollständige Strategien zur Verwaltung mehrstufiger Umgebungen mit Ansible

Es gibt eine Reihe von Möglichkeiten, mit denen Sie Umgebungen in Ansible verwalten können, Ansible selbst bietet jedoch keine Lösung mit einer Meinung. Es bietet vielmehr viele Konstrukte, die zum Verwalten von Umgebungen verwendet werden können, und ermöglicht dem Benutzer die Auswahl.

Der Ansatz, den wir in diesem Handbuch demonstrieren werden, basiert auf Ansiblegroup variables undmultiple inventories. Es gibt jedoch mehrere andere Strategien, die eine Überlegung wert sind. Wir werden im Folgenden einige dieser Ideen untersuchen und erläutern, warum sie in komplexen Umgebungen Probleme bereiten können.

Wenn Sie mit der von Ansible empfohlenen Strategie beginnen möchten, fahren Sie mit dem Abschnitt überusing Ansible groups and multiple inventoriesfort.

Ausschließlich auf Gruppenvariablen bauen

Auf den ersten Blick scheint es, dass Gruppenvariablen die gesamte Trennung zwischen Umgebungen ermöglichen, die Ansible benötigt. Sie können bestimmte Server als zu Ihrer Entwicklungsumgebung gehörend festlegen und andere können den Staging- und Produktionsbereichen zugewiesen werden. Ansible erleichtert das Erstellen von Gruppen und das Zuweisen von Variablen.

Gruppenüberschneidungen bringen jedoch schwerwiegende Probleme für dieses System mit sich. Gruppen werden häufig verwendet, um mehr als eine Dimension zu kategorisieren. Zum Beispiel:

  • Bereitstellungsumgebungen (lokal, Entwickler, Bühne, Produkt usw.)

  • Host-Funktionalität (Webserver, Datenbankserver usw.)

  • Rechenzentrumsregion (NYC, SFO usw.)

In diesen Fällen gehören Hosts normalerweise zu einer Gruppe pro Kategorie. Beispielsweise kann ein Host ein Webserver (funktionsfähig) auf der Bühne (Bereitstellungsumgebung) in NYC (Datencenter-Region) sein.

Wenn dieselbe Variable von mehr als einer Gruppe für einen Host festgelegt wird, kann Ansible die Priorität nicht explizit angeben. Möglicherweise möchten Sie, dass die mit den Implementierungsumgebungen verknüpften Variablen andere Werte überschreiben, Ansible bietet jedoch keine Möglichkeit, dies zu definieren.

StattdessenAnsible uses the last loaded value. Da Ansible Gruppen alphabetisch auswertet, gewinnt die Variable, die dem Gruppennamen zugeordnet ist, der in der Reihenfolge der Wörterbücher zuletzt vorkommt. Dies ist ein vorhersehbares Verhalten, aber das explizite Verwalten der Gruppennamenalphabetisierung ist aus administrativer Sicht nicht ideal.

Verwenden von untergeordneten Gruppen zum Einrichten einer Hierarchie

Mit Ansible können Sie Gruppen anderen Gruppen mithilfe der[groupname:children]-Syntax im Inventar zuweisen. Dies gibt Ihnen die Möglichkeit, bestimmte Gruppenmitglieder anderer Gruppen zu benennen. Untergeordnete Gruppen können die von den übergeordneten Gruppen festgelegten Variablen überschreiben.

In der Regel wird dies zur natürlichen Klassifizierung verwendet. Zum Beispiel könnten wir eine Gruppe namensenvironments haben, die die Gruppendev,stage,prod enthält. Dies bedeutet, dass wir Variablen in der Gruppeenvironmentetzen und in der Gruppedevüberschreiben können. Sie könnten auch eine übergeordnete Gruppe namensfunctions haben, die die Gruppenweb,database undloadbalancer enthält.

Diese Verwendung löst nicht das Problem der Gruppenüberschneidung, da untergeordnete Gruppen nur ihre Eltern überschreiben. Untergeordnete Gruppen können Variablen innerhalb des übergeordneten Elements überschreiben, aber die oben genannte Organisation hat keine Beziehung zwischen Gruppenkategorien wieenvironments undfunctions hergestellt. Die variable Rangfolge zwischen den beiden Kategorien ist noch nicht definiert.

Es istismöglich, dieses System auszunutzen, indem eine nicht natürliche Gruppenmitgliedschaft festgelegt wird. Wenn Sie beispielsweise die folgende Priorität festlegen möchten, von der höchsten zur niedrigsten Priorität:

  • Entwicklungsumgebung

  • Region

  • Funktion

Sie können eine Gruppenmitgliedschaft zuweisen, die folgendermaßen aussieht:

Beispiel Inventar

. . .
[function:children]
web
database
loadbalancer
region

[region:children]
nyc
sfo
environments

[environments:children]
dev
stage
prod

Wir haben hier eine Hierarchie eingerichtet, mit der regionale Variablen die Funktionsvariablen überschreiben können, da die Grupperegionein Kind der Gruppefunctionist. Ebenso können Variablen, die in den Gruppen vonenvironmentsfestgelegt sind, alle anderen überschreiben. Dies bedeutet, dass, wenn wir dieselbe Variable in den Gruppendev,nyc undweb auf einen anderen Wert setzen, ein Host, der zu jeder dieser Gruppen gehört, die Variable vondevverwenden würde ) s.

Dies erzielt das gewünschte Ergebnis und ist auch vorhersehbar. Es ist jedoch nicht intuitiv und verwirrt die Unterscheidung zwischen echten Kindern und Kindern, die zum Aufbau der Hierarchie erforderlich sind. Ansible ist so konzipiert, dass seine Konfiguration selbst für neue Benutzer klar und einfach zu befolgen ist. Diese Art der Umgehung gefährdet dieses Ziel.

Verwenden von Ansible-Konstrukten, die eine explizite Ladereihenfolge ermöglichen

Es gibt einige Konstrukte in Ansible, die eine explizite variable Lastreihenfolge ermöglichen, nämlichvars_files undinclude_vars. Diese können innerhalb vonAnsible plays verwendet werden, um zusätzliche Variablen in der in der Datei definierten Reihenfolge explizit zu laden. Die Direktivevars_files ist im Kontext eines Spiels gültig, während das Modulinclude_vars in Aufgaben verwendet werden kann.

Die allgemeine Idee besteht darin, nur grundlegende Identifizierungsvariablen ingroup_vars festzulegen und diese dann zu nutzen, um die richtigen Variablendateien mit den restlichen gewünschten Variablen zu laden.

Beispielsweise könnten einige dergroup_vars-Dateien folgendermaßen aussehen:

group_vars/dev

---
env: dev

group_vars/stage

---
env: stage

group_vars/web

---
function: web

group_vars/database

---
function: database

Wir hätten dann eine separate vars-Datei, die die wichtigen Variablen für jede Gruppe definiert. Diese werden aus Gründen der Übersichtlichkeit normalerweise in einem separatenvars-Verzeichnis gespeichert. Im Gegensatz zugroup_vars-Dateien müssen Dateien beim Umgang mitinclude_vars die Dateierweiterung.yml enthalten.

Stellen wir uns vor, wir müssen die Variableserver_memory_sizein jedervars-Datei auf einen anderen Wert setzen. Ihre Entwicklungsserver sind wahrscheinlich kleiner als Ihre Produktionsserver. Darüber hinaus haben Ihre Webserver und Datenbankserver möglicherweise unterschiedliche Speicheranforderungen:

vars/dev.yml

---
server_memory_size: 512mb

vars/prod.yml

---
server_memory_size: 4gb

vars/web.yml

---
server_memory_size: 1gb

vars/database.yml

---
server_memory_size: 2gb

Wir könnten dann ein Playbook erstellen, das explizit die richtigevars-Datei lädt, basierend auf den Werten, die dem Host aus dengroup_vars-Dateien zugewiesen wurden. Die Reihenfolge der geladenen Dateien bestimmt den Vorrang, wobei der letzte Wert gewinnt.

Mitvars_files würde ein Beispielspiel folgendermaßen aussehen:

example_play.yml

---
- name: variable precedence test
  hosts: all
  vars_files:
    - "vars/{{ env }}.yml"
    - "vars/{{ function }}.yml"
  tasks:
    - debug: var=server_memory_size

Da die Funktionsgruppen zuletzt geladen werden, wird der Wert vonserver_memory_sizeaus den Dateienvar/web.yml undvar/database.yml entnommen:

ansible-playbook -i inventory example_play.yml
Output. . .
TASK [debug] *******************************************************************
ok: [host1] => {
    "server_memory_size": "1gb"      # value from vars/web.yml
}
ok: [host2] => {
    "server_memory_size": "1gb"      # value from vars/web.yml
}
ok: [host3] => {
    "server_memory_size": "2gb"      # value from vars/database.yml
}
ok: [host4] => {
    "server_memory_size": "2gb"      # value from vars/database.yml
}
. . .

Wenn wir die Reihenfolge der zu ladenden Dateien ändern, können wir die Prioritäten der Implementierungsumgebungsvariablen erhöhen:

example_play.yml

---
- name: variable precedence test
  hosts: all
  vars_files:
    - "vars/{{ function }}.yml"
    - "vars/{{ env }}.yml"
  tasks:
    - debug: var=server_memory_size

Wenn Sie das Playbook erneut ausführen, werden Werte angezeigt, die aus den Bereitstellungsumgebungsdateien übernommen wurden:

ansible-playbook -i inventory example_play.yml
Output. . .
TASK [debug] *******************************************************************
ok: [host1] => {
    "server_memory_size": "512mb"      # value from vars/dev.yml
}
ok: [host2] => {
    "server_memory_size": "4gb"        # value from vars/prod.yml
}
ok: [host3] => {
    "server_memory_size": "512mb"      # value from vars/dev.yml
}
ok: [host4] => {
    "server_memory_size": "4gb"        # value from vars/prod.yml
}
. . .

Das äquivalente Playbook mitinclude_vars, das als Aufgabe fungiert, würde folgendermaßen aussehen:

---
- name: variable precedence test
  hosts: localhost
  tasks:
    - include_vars:
        file: "{{ item }}"
      with_items:
        - "vars/{{ function }}.yml"
        - "vars/{{ env }}.yml"
    - debug: var=server_memory_size

Dies ist ein Bereich, in dem Ansible eine explizite Bestellung ermöglicht, was sehr nützlich sein kann. Wie bei den vorhergehenden Beispielen gibt es jedoch einige signifikante Nachteile.

Wenn Sievars_files undinclude_vars verwenden, müssen Sie zunächst Variablen, die eng mit Gruppen verbunden sind, an einem anderen Ort platzieren. Die Position vongroup_varswird zu einem Stub für die tatsächlichen Variablen im Verzeichnisvars. Dies erhöht erneut die Komplexität und verringert die Klarheit. Der Benutzer muss die richtigen Variablendateien mit dem Host abgleichen, was Ansible bei Verwendung vongroup_vars automatisch tut.

Das Verlassen auf diese Techniken macht sie vor allem obligatorisch. Für jedes Playbook ist ein Abschnitt erforderlich, in dem die richtigen Variablendateien explizit in der richtigen Reihenfolge geladen werden. Playbooks ohne diese Option können die zugehörigen Variablen nicht verwenden. Darüber hinaus ist das Ausführen des Befehlsansible für Ad-hoc-Aufgaben für alles, was auf Variablen basiert, fast unmöglich.

Bisher haben wir uns einige Strategien für die Verwaltung mehrstufiger Umgebungen angesehen und die Gründe dafür diskutiert, warum sie möglicherweise keine vollständige Lösung darstellen. Das Ansible-Projekt bietet jedoch einige Vorschläge, wie Sie Ihre Infrastruktur am besten umgebungsübergreifend abstrahieren können.

Der empfohlene Ansatz besteht darin, mit mehrstufigen Umgebungen zu arbeiten, indem jede Betriebsumgebung vollständig getrennt wird. Anstatt alle Hosts in einer einzigen Inventardatei zu verwalten, wird für jede Ihrer individuellen Umgebungen ein Inventar geführt. Separategroup_vars-Verzeichnisse werden ebenfalls verwaltet.

Die grundlegende Verzeichnisstruktur sieht ungefähr so ​​aus:

.
├── ansible.cfg
├── environments/         # Parent directory for our environment-specific directories
│   │
│   ├── dev/              # Contains all files specific to the dev environment
│   │   ├── group_vars/   # dev specific group_vars files
│   │   │   ├── all
│   │   │   ├── db
│   │   │   └── web
│   │   └── hosts         # Contains only the hosts in the dev environment
│   │
│   ├── prod/             # Contains all files specific to the prod environment
│   │   ├── group_vars/   # prod specific group_vars files
│   │   │   ├── all
│   │   │   ├── db
│   │   │   └── web
│   │   └── hosts         # Contains only the hosts in the prod environment
│   │
│   └── stage/            # Contains all files specific to the stage environment
│       ├── group_vars/   # stage specific group_vars files
│       │   ├── all
│       │   ├── db
│       │   └── web
│       └── hosts         # Contains only the hosts in the stage environment
│
├── playbook.yml
│
└── . . .

Wie Sie sehen, ist jede Umgebung unterschiedlich und unterteilt. Die Umgebungsverzeichnisse enthalten eine Inventardatei (willkürlichhosts genannt) und ein separates Verzeichnisgroup_vars.

Es gibt einige offensichtliche Duplikate in der Verzeichnisstruktur. Für jede einzelne Umgebung gibt esweb- unddb-Dateien. In diesem Fall ist die Vervielfältigung wünschenswert. Variablenänderungen können über mehrere Umgebungen verteilt werden, indem zunächst Variablen in einer Umgebung geändert und nach dem Testen in die nächste verschoben werden, genau wie bei Code- oder Konfigurationsänderungen. Die Variablengroup_varsverfolgen die aktuellen Standardeinstellungen für jede Umgebung.

Eine Einschränkung besteht darin, dass nicht alle Hosts nach Funktion in allen Umgebungen ausgewählt werden können. Glücklicherweise fällt dies in die gleiche Kategorie wie das oben beschriebene Problem der Variablenverdopplung. Es ist zwar gelegentlich nützlich, alle Ihre Webserver für eine Aufgabe auszuwählen, Sie möchten jedoch Änderungen fast immer einzeln in Ihrer Umgebung implementieren. Dies hilft zu verhindern, dass Fehler Ihre Produktionsumgebung beeinträchtigen.

Umgebungsübergreifende Variablen festlegen

Eine Sache, die in der empfohlenen Konfiguration nicht möglich ist, ist die gemeinsame Nutzung von Variablen in verschiedenen Umgebungen. Es gibt verschiedene Möglichkeiten, die gemeinsame Nutzung von Umgebungsvariablen zu implementieren. Eine der einfachsten Möglichkeiten besteht darin, Ansibles Fähigkeit zu nutzen, Verzeichnisse anstelle von Dateien zu verwenden. Wir können dieall-Datei in jedemgroup_vars-Verzeichnis durch einall-Verzeichnis ersetzen.

Innerhalb des Verzeichnisses können wir alle umgebungsspezifischen Variablen in einer Datei wieder setzen. Anschließend können wir einen symbolischen Link zu einem Dateispeicherort erstellen, der umgebungsübergreifende Variablen enthält. Beide werden auf alle Hosts in der Umgebung angewendet.

Beginnen Sie mit der Erstellung einer umgebungsübergreifenden Variablendatei an einer beliebigen Stelle in der Hierarchie. In diesem Beispiel wird es im Verzeichnisenvironmentsabgelegt. Platzieren Sie alle umgebungsübergreifenden Variablen in dieser Datei:

cd environments
touch 000_cross_env_vars

Wechseln Sie anschließend in eines dergroup_vars-Verzeichnisse, benennen Sie dieall-Datei um und erstellen Sie dasall-Verzeichnis. Verschieben Sie die umbenannte Datei in das neue Verzeichnis:

cd dev/group_vars
mv all env_specific
mkdir all
mv env_specific all/

Als Nächstes können Sie eine symbolische Verknüpfung zur umgebungsübergreifenden Variablendatei erstellen:

cd all/
ln -s ../../../000_cross_env_vars .

Wenn Sie die obigen Schritte für jede Ihrer Umgebungen ausgeführt haben, sieht Ihre Verzeichnisstruktur folgendermaßen aus:

.
├── ansible.cfg
├── environments/
│   │
│   ├── 000_cross_env_vars
│   │
│   ├── dev/
│   │   ├── group_vars/
│   │   │   ├── all/
│       │   │   ├── 000_cross_env_vars -> ../../../000_cross_env_vars
│   │   │   │   └── env_specific
│   │   │   ├── db
│   │   │   └── web
│   │   └── hosts
│   │
│   ├── prod/
│   │   ├── group_vars/
│   │   │   ├── all/
│   │   │   │   ├── 000_cross_env_vars -> ../../../000_cross_env_vars
│   │   │   │   └── env_specific
│   │   │   ├── db
│   │   │   └── web
│   │   └── hosts
│   │
│   └── stage/
│       ├── group_vars/
│       │   ├── all/
│       │   │   ├── 000_cross_env_vars -> ../../../000_cross_env_vars
│       │   │   └── env_specific
│       │   ├── db
│       │   └── web
│       └── hosts
│
├── playbook.yml
│
└── . . .

Die in der Datei000_cross_env_varsfestgelegten Variablen stehen jeder Umgebung mit niedriger Priorität zur Verfügung.

Festlegen eines Standardumgebungsinventars

Es ist möglich, eine Standardinventardatei in der Dateiansible.cfg festzulegen. Dies ist aus mehreren Gründen eine gute Idee.

Erstens können Sie explizite Inventarflags aufansible undansible-playbook lassen. Also anstatt zu tippen:

ansible -i environments/dev -m ping

Sie können auf das Standardinventar zugreifen, indem Sie Folgendes eingeben:

ansible -m ping

Zweitens hilft das Festlegen eines Standardinventars zu verhindern, dass unerwünschte Änderungen versehentlich die Staging- oder Produktionsumgebungen beeinflussen. In der Standardeinstellung Ihrer Entwicklungsumgebung ist die am wenigsten wichtige Infrastruktur von Änderungen betroffen. Das Heraufstufen von Änderungen an neuen Umgebungen ist dann eine explizite Aktion, für die das Flag-ierforderlich ist.

Öffnen Sie die Dateiansible.cfg, um ein Standardinventar festzulegen. Dies kann sich je nach Konfiguration im Stammverzeichnis Ihres Projekts oder bei/etc/ansible/ansible.cfg befinden.

[.note] #Note: Das folgende Beispiel zeigt das Bearbeiten eineransible.cfg-Datei in einem Projektverzeichnis. Wenn Sie die Datei/etc/ansibile/ansible.cfg für Ihre Änderungen verwenden, ändern Sie den folgenden Bearbeitungspfad. Wenn Sie bei Verwendung von/etc/ansible/ansible.cfg Ihre Bestände außerhalb des Verzeichnisses/etc/ansible verwalten, müssen Sie beim Festlegen desinventory-Werts einen absoluten Pfad anstelle eines relativen Pfads verwenden.
#

nano ansible.cfg

Wie oben erwähnt, wird empfohlen, Ihre Entwicklungsumgebung als Standardinventar festzulegen. Beachten Sie, wie Sie das gesamte Umgebungsverzeichnis anstelle der darin enthaltenen Hosts-Datei auswählen können:

[defaults]
inventory = ./environments/dev

Sie sollten jetzt in der Lage sein, Ihr Standardinventar ohne die Option-izu verwenden. Für die nicht standardmäßigen Bestände muss weiterhin-i verwendet werden, um sie vor versehentlichen Änderungen zu schützen.

Fazit

In diesem Artikel haben wir die Flexibilität untersucht, die Ansible für die Verwaltung Ihrer Hosts in mehreren Umgebungen bietet. Auf diese Weise können Benutzer viele verschiedene Strategien für den Umgang mit variabler Priorität anwenden, wenn ein Host Mitglied mehrerer Gruppen ist. Die Mehrdeutigkeit und der Mangel an offizieller Leitung können jedoch eine Herausforderung darstellen. Wie bei jeder Technologie hängt die optimale Anpassung für Ihr Unternehmen von Ihren Anwendungsfällen und der Komplexität Ihrer Anforderungen ab. Der beste Weg, eine Strategie zu finden, die Ihren Bedürfnissen entspricht, ist zu experimentieren. Teilen Sie Ihren Anwendungsfall und Ihre Vorgehensweise in den Kommentaren unten mit.