Der Autor hat die Wikimedia Foundation dazu ausgewählt, im Rahmen des Programms Write for DOnations eine Spende zu erhalten.
etcd ist ein verteilter Schlüsselwertspeicher, auf den sich viele Plattformen und Tools verlassen, darunter Kubernetes, Vulcand und Doorman. Innerhalb von Kubernetes dient etcd als globaler Konfigurationsspeicher, der den Status des Clusters speichert. Kenntnisse zur Verwaltung von etcd sind unerlässlich für die Verwaltung eines Kubernetes-Clusters. Zwar gibt es viele verwaltete Kubernetes-Produkte (auch als Kubernetes-as-a-Service bekannt), die diese Administrationsaufgaben für Sie übernehmen, doch entscheiden sich viele Unternehmen wegen der damit verbundenen Flexibilität immer noch für selbstverwaltete Kubernetes-Cluster.
Die erste Hälfte dieses Artikels führt Sie durch die Einrichtung eines etcd-Clusters mit drei Knoten auf Ubuntu 18.04-Servern. In der zweiten Hälfte geht es um das Sichern des Clusters mit Transport Layer Security oder TLS. Um jede Einrichtung automatisiert auszuführen, verwenden wir durchgehend Ansible. Ansible ist ein Konfigurationsmanagement-Tool ähnlich wie Puppet, Chef, und SaltStack; damit können wir die einzelnen Einrichtungsschritte auf deklarative Weise definieren, und zwar in Dateien namens Playbooks.
Am Ende dieses Tutorials verfügen Sie über einen sicheren etcd-Cluster mit drei Knoten, der auf Ihren Servern ausgeführt wird. Außerdem werden Sie über ein Ansible-Playbook verfügen, mit dem Sie die gleiche Einrichtung auf einem neuen Satz von Servern wiederholt und konsequent nachbilden können.
Bevor Sie diese Anleitung beginnen, benötigen Sie Folgendes:
Python, pip
und das auf Ihrem lokalen Computer installierte pyOpenSSL
-Paket. Um zu erfahren, wie Sie Python3, pip und Python-Pakete installieren können, lesen Sie Installieren von Python 3 und Einrichten einer lokalen Programmierumgebung unter Ubuntu 18.04.
Drei Ubuntu 18.04-Server im gleichen lokalen Netzwerk mit mindestens 2 GB RAM und root SSH-Zugriff. Außerdem sollten Sie die Server so konfigurieren, dass sie die Hostnamen etcd1, etcd2 und etcd3 tragen. Die in diesem Artikel beschriebenen Schritte würden auf jedem generischen Server funktionieren, nicht nur bei DigitalOcean Droplets. Wenn Sie Ihre Server aber in DigitalOcean hosten möchten, können Sie dem Leitfaden Erstellen eines Droplets über das DigitalOcean Control Panel folgen, um diese Anforderung zu erfüllen. Beachten Sie, dass Sie bei der Erstellung Ihres Droplets die Option Private Networking aktivieren müssen. Um für vorhandene Droplets private Netzwerke zu aktivieren, lesen Sie Aktivieren von Private Networking in Droplets.
Warnung: Da der Zweck dieses Artikels darin besteht, eine Einführung in das Einrichten eines etcd-Clusters in einem privaten Netzwerk zu liefern, wurden die drei Ubuntu 18.04-Server in dieser Einrichtung nicht mit einer Firewall getestet und als root user aufgerufen. In einer Produktionsumgebung würde jeder dem öffentlichen Internet ausgesetzte Knoten eine Firewall und einen Sudo-Benutzer erfordern, damit sich bewährte Sicherheitspraktiken einhalten lassen. Weitere Informationen finden Sie im Tutorial Ersteinrichtung des Servers mit Ubuntu 18.04.
Ein SSH-Schlüsselpaar, das Ihrem lokalen Rechner Zugriff auf die Server etcd1, etcd2 und etcd3 erlaubt. Wenn Sie nicht wissen, was SSH ist oder über kein SSH-Schlüsselpaar verfügen, können Sie hier mehr darüber erfahren: SSH Essentials: Working with SSH Servers, Clients, and Keys (SSH-Grundlagen: Arbeiten mit SSH-Servern, -Clients und -Schlüsseln).
Auf Ihrem lokalen Rechner installiertes Ansible. Wenn Sie beispielsweise Ubuntu 18.04 ausführen, können Sie Ansible installieren, indem Sie Schritt 1 des Artikels Installieren und Konfigurieren von Ansible unter Ubuntu 18.04 befolgen. Dadurch werden die Befehle ansible
und ansible-playbook
auf Ihrem Computer verfügbar. Vielleicht möchten Sie auch How to Use Ansible: A Reference Guide (Verwenden von Ansible: Ein Referenzhandbuch) parat halten. Die Befehle in diesem Tutorial sollten mit Ansible v2.x funktionieren; wir haben sie in Ansible v2.9.7 unter Ausführung von Python v3.8.2 getestet.
Ansible ist ein Tool, das zum Verwalten von Servern dient. Die Server, die Ansible verwaltet, werden verwaltete Knoten genannt. Das Gerät, auf dem Ansible ausgeführt wird, wird als Steuerknoten bezeichnet. Ansible arbeitet mit SSH-Schlüsseln im Steuerknoten, um Zugriff auf die verwalteten Knoten zu erhalten. Sobald eine SSH-Sitzung eingerichtet ist, führt Ansible eine Reihe von Skripten aus, um die verwalteten Knoten bereitzustellen und zu konfigurieren. In diesem Schritt testen wir, ob wir Ansible zur Verbindungsherstellung mit den verwalteten Knoten verwenden und den Befehl hostname
ausführen können.
Ein typischer Tag für einen Systemadministrator kann das Verwalten verschiedener Sätze von Knoten beinhalten. Beispielsweise können Sie Ansible verwenden, um neue Server bereitzustellen; später aber verwenden Sie es, um einen anderen Satz von Servern neu zu konfigurieren. Um Administratoren eine bessere Organisation des Satzes von verwalteten Knoten zu ermöglichen, verfügt Ansible über das Konzept des Hostinventars (oder kurz Inventar). Sie können jeden Knoten, den Sie mit Ansible verwalten möchten, in einer Inventardatei definieren und in Gruppen anordnen. Wenn Sie dann die Befehle ansible
und ansible-playbook
ausführen, können Sie angeben, für welche Hosts oder Gruppen der Befehl gelten soll.
Standardmäßig liest Ansible die Inventardatei von /etc/ansible/hosts
; wir können jedoch eine andere Inventardatei angeben, indem wir das Flag --inventory
(oder kurz -i
) verwenden.
Erstellen Sie zunächst ein neues Verzeichnis auf Ihrem lokalen Rechner (dem Steuerknoten), in dem alle Dateien für dieses Tutorial installiert werden:
- mkdir -p $HOME/playground/etcd-ansible
Rufen Sie dann das gerade erstellte Verzeichnis auf:
- cd $HOME/playground/etcd-ansible
Erstellen und öffnen Sie im Verzeichnis mit Ihrem Editor eine leere Inventardatei namens hosts
:
- nano $HOME/playground/etcd-ansible/hosts
Listen Sie in der Datei hosts
alle Ihre verwalteten Knoten im folgenden Format auf und ersetzen Sie die markierten öffentlichen IP-Adressen durch die wahren öffentlichen IP-Adressen Ihrer Server:
[etcd]
etcd1 ansible_host=etcd1_public_ip ansible_user=root
etcd2 ansible_host=etcd2_public_ip ansible_user=root
etcd3 ansible_host=etcd3_public_ip ansible_user=root
Die Zeile [etcd]
definiert eine Gruppe namens etcd
. Unter der Gruppendefinition listen wir alle unsere verwalteten Knoten auf. Jede Zeile beginnt mit einem Alias (z. B. etcd1
), mit dem wir unter Verwendung eines leicht zu merkenden Namens anstelle einer langen IP-Adresse auf jeden einzelnen Host verweisen können. Die Variablen ansible_host
und ansible_user
sind Ansible-Variablen. In diesem Fall dienen sie zur Bereitstellung von Ansible mit den öffentlichen IP-Adressen und SSH-Benutzernamen, die beim Herstellen einer Verbindung über SSH verwendet werden.
Um zu prüfen, ob Ansible eine Verbindung mit unseren verwalteten Knoten herstellen kann, testen wir mithilfe von Ansible durch Ausführung des Befehls hostname
auf den einzelnen Hosts in der Gruppe etcd
die Konnektivität:
- ansible etcd -i hosts -m command -a hostname
Lassen Sie uns diesen Befehl genauer ansehen, um zu erfahren, was die einzelnen Teile bedeuten:
etcd
: gibt das Hostmuster an, mit dem ermittelt wird, welche Hosts aus dem Inventar mit diesem Befehl verwaltet werden. Hier verwenden wir den Gruppennamen als Hostmuster.-i hosts
: gibt die zu verwendende Inventardatei an.-m command
: Die Funktionalität hinter Ansible wird von Modulen bereitgestellt. Das command
-Modul nimmt das übergebene Argument und führt es als Befehl auf den einzelnen verwalteten Knoten aus. Im Verlauf dieses Tutorials werden noch einige weitere Ansible-Module eingeführt.-a hostname
: das Argument, das an das Modul übergeben wird. Die Zahl und Arten von Argumenten hängen vom Modul ab.Nach Ausführung des Befehls sehen Sie die folgende Ausgabe, was bedeutet, dass Ansible richtig konfiguriert wurde:
Outputetcd2 | CHANGED | rc=0 >>
etcd2
etcd3 | CHANGED | rc=0 >>
etcd3
etcd1 | CHANGED | rc=0 >>
etcd1
Jeder Befehl, den Ansible ausführt, wird als Aufgabe bezeichnet. Die Verwendung von ansible
in der Befehlszeile zum Ausführen von Aufgaben wird Ausführung von ad-hoc-Befehlen genannt. Der Vorteil von Ad-hoc-Befehlen besteht darin, dass sie schnell sind und wenig Einrichtung benötigen; der Nachteil ist, dass sie manuell ausgeführt werden und sich somit nicht für ein Versionskontrollsystem wie Git verwenden lassen.
Eine kleine Verbesserung wäre es, ein Shell-Skript zu schreiben und unsere Befehle mit dem script
-Modul von Ansible auszuführen. So könnten wir die Konfigurationsschritte, die wir ergriffen haben, in die Versionskontrolle aufnehmen. Allerdings sind Shell-Skripte imperativ. Das bedeutet, dass wir die auszuführenden Befehle (die „wie“s) ermitteln müssen, um das System mit Blick auf den gewünschten Zustand zu konfigurieren. Ansible hingegen setzt auf einen deklarativen Ansatz, bei dem wir definieren, „was“ der gewünschte Zustand unseres Servers in Konfigurationsdateien sein sollte. Ansible ist dafür verantwortlich, den Server in den gewünschten Zustand zu bringen.
Der deklarative Ansatz wird bevorzugt, da die Absicht der Konfigurationsdatei sofort übermittelt wird, was bedeutet, dass er leichter zu verstehen und zu verwalten ist. Außerdem wird dabei die Verantwortung für die Bearbeitung von Edge-Fällen vom Administrator auf Ansible übertragen, was uns eine Menge Arbeit spart.
Nachdem Sie nun den Ansible-Steuerknoten zur Kommunikation mit den verwalteten Knoten konfiguriert haben, stellen wir Ihnen im nächsten Schritt Ansible-Playbooks vor, mit denen Sie Aufgaben deklarativ angeben können.
In diesem Schritt werden wir replizieren, was wir in Schritt 1 getan haben: das Ausdrucken der Hostnamen der verwalteten Knoten. Anstatt Ad-hoc-Aufgaben auszuführen, werden wir die einzelnen Aufgaben jedoch deklarativ als Ansible-Playbook definieren und ausführen. Ziel dieses Schritt ist es, zu zeigen, wie Ansible Playbooks funktionieren. Wir werden mit Playbooks in späteren Schritten noch deutlich umfangreichere Aufgaben ausführen.
Erstellen Sie in Ihrem Projektverzeichnis mit Ihrem Editor eine neue Datei namens playbook.yaml
:
- nano $HOME/playground/etcd-ansible/playbook.yaml
Fügen Sie in playbook.yaml
die folgenden Zeilen hinzu:
- hosts: etcd
tasks:
- name: "Retrieve hostname"
command: hostname
register: output
- name: "Print hostname"
debug: var=output.stdout_lines
Schließen und speichern Sie die Datei playbook.yaml
, indem Sie Strg+X
drücken, gefolgt von J
.
Das Playbook enthält eine Liste von Plays; jedes Play enthält eine Liste von Aufgaben, die auf allen Hosts ausgeführt werden sollen, die mit dem vom Schlüssel hosts
angegebenen Hostmuster übereinstimmen. In diesem Playbook verfügen wir über ein Play, das zwei Aufgaben enthält. Die erste Aufgabe führt den Befehl hostname
mit dem command
-Modul aus und registriert die Ausgabe in einer Variable namens output
. In der zweiten Aufgabe verwenden wir das debug
-Modul, um die Eigenschaft stdout_lines
der output
-Variablen auszugeben.
Wir können dieses Playbook nun mit dem Befehl ansible-playbook
ausführen:
- ansible-playbook -i hosts playbook.yaml
Sie erhalten die folgende Ausgabe, was bedeutet, dass Ihr Playbook ordnungsgemäß funktioniert:
OutputPLAY [etcd] ***********************************************************************************************************************
TASK [Gathering Facts] ************************************************************************************************************
ok: [etcd2]
ok: [etcd3]
ok: [etcd1]
TASK [Retrieve hostname] **********************************************************************************************************
changed: [etcd2]
changed: [etcd3]
changed: [etcd1]
TASK [Print hostname] *************************************************************************************************************
ok: [etcd1] => {
"output.stdout_lines": [
"etcd1"
]
}
ok: [etcd2] => {
"output.stdout_lines": [
"etcd2"
]
}
ok: [etcd3] => {
"output.stdout_lines": [
"etcd3"
]
}
PLAY RECAP ************************************************************************************************************************
etcd1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
etcd2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
etcd3 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Anmerkung: ansible-playbook
verwendet zum Teil cowsay
als verspielte Methode zur Ausgabe der Überschriften. Wenn Sie in Ihrem Terminal viele ASCII-artige Kühe sehen, wissen Sie jetzt warum. Um diese Funktion zu deaktivieren, setzen Sie die Umgebungsvariable ANSIBLE_NOCOWS
vor dem Ausführen von ansible-playbook
auf 1
, indem Sie in Ihrer Shell export ANSIBLE_NOCOWS=1
ausführen.
In diesem Schritt sind wir von der Ausführung von imperativen Ad-hoc-Aufgaben zum Ausführen von deklarativen Playbooks übergegangen. Im nächsten Schritt ersetzen wir diese beiden Vorführaufgaben durch Aufgaben, die für die Einrichtung unseres etcd-Clusters sorgen werden.
In diesem Schritt zeigen wir Ihnen die Befehle zur manuellen Installation von etcd
und demonstrieren, wie Sie die gleichen Befehle in unserem Ansible-Playbook in Aufgaben übersetzen können.
etcd
und dessen Client etcdctl
sind als Binärdateien verfügbar, die wir herunterladen, extrahieren und in einem Verzeichnis platzieren werden, das Teil der PATH
-Umgebungsvariablen ist. Bei manueller Konfiguration sind dies die Schritte, die wir auf jedem der verwalteten Knoten ausführen würden:
- mkdir -p /opt/etcd/bin
- cd /opt/etcd/bin
- wget -qO- https://storage.googleapis.com/etcd/v3.3.13/etcd-v3.3.13-linux-amd64.tar.gz | tar --extract --gzip --strip-components=1
- echo 'export PATH="$PATH:/opt/etcd/bin"' >> ~/.profile
- echo 'export ETCDCTL_API=3" >> ~/.profile
Die ersten vier Befehle sorgen dafür, dass die Binärdateien heruntergeladen und im Verzeichnis /opt/etcd/bin/
extrahiert werden. Standardmäßig nutzt der etcdctl
-Client API v2 zur Kommunikation mit dem etcd
-Server. Da wir etcd v3.x ausführen, setzt der letzte Befehl die Umgebungsvariable ETCDCTL_API
auf 3
.
Anmerkung: Hier verwenden wir etcd v3.3.13, was für Rechner mit Prozessoren entwickelt wurde, die das AMD64-Anweisungsset verwenden. Auf der offiziellen GitHub Release-Seite finden Sie Binärdateien für andere Systeme und Versionen.
Um die gleichen Schritte in einer standardisierten Form zu replizieren, können wir unserem Playbook Aufgaben hinzufügen. Öffnen Sie die Playbook-Datei playbook.yaml
in Ihrem Editor:
- nano $HOME/playground/etcd-ansible/playbook.yaml
Ersetzen Sie die gesamte Datei playbook.yaml
durch folgende Inhalte:
- hosts: etcd
become: True
tasks:
- name: "Create directory for etcd binaries"
file:
path: /opt/etcd/bin
state: directory
owner: root
group: root
mode: 0700
- name: "Download the tarball into the /tmp directory"
get_url:
url: https://storage.googleapis.com/etcd/v3.3.13/etcd-v3.3.13-linux-amd64.tar.gz
dest: /tmp/etcd.tar.gz
owner: root
group: root
mode: 0600
force: True
- name: "Extract the contents of the tarball"
unarchive:
src: /tmp/etcd.tar.gz
dest: /opt/etcd/bin/
owner: root
group: root
mode: 0600
extra_opts:
- --strip-components=1
decrypt: True
remote_src: True
- name: "Set permissions for etcd"
file:
path: /opt/etcd/bin/etcd
state: file
owner: root
group: root
mode: 0700
- name: "Set permissions for etcdctl"
file:
path: /opt/etcd/bin/etcdctl
state: file
owner: root
group: root
mode: 0700
- name: "Add /opt/etcd/bin/ to the $PATH environment variable"
lineinfile:
path: /etc/profile
line: export PATH="$PATH:/opt/etcd/bin"
state: present
create: True
insertafter: EOF
- name: "Set the ETCDCTL_API environment variable to 3"
lineinfile:
path: /etc/profile
line: export ETCDCTL_API=3
state: present
create: True
insertafter: EOF
Jede Aufgabe nutzt ein Modul; für diesen Satz von Aufgaben verwenden wir folgende Module:
file
: zum Erstellen des Verzeichnisses /opt/etcd/bin
und zum späteren Festlegen der Dateiberechtigungen für die Binärdateien etcd
und etcdctl
.get_url
: zum Herunterladen des gzip-ten Tarball auf die verwalteten Knoten.unarchive
: zum Extrahieren und Entpacken der Binärdateien etcd
und etcdctl
aus dem gzip-ten Tarball.lineinfile
: zum Hinzufügen eines Eintrags in die Datei .profile
.Um diese Änderungen anzuwenden, schließen und speichern Sie die Datei playbook.yaml
, indem Sie Strg+X
drücken, gefolgt von J
. Führen Sie dann im Terminal den gleichen Befehl ansible-playbook
erneut aus:
- ansible-playbook -i hosts playbook.yaml
Der Abschnitt PLAY RECAP
der Ausgabe wird nur ok
und changed
anzeigen:
Output...
PLAY RECAP ************************************************************************************************************************
etcd1 : ok=8 changed=7 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
etcd2 : ok=8 changed=7 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
etcd3 : ok=8 changed=7 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Um die ordnungsgemäße Installation von etcd zu prüfen, stellen Sie manuell eine SSH-Verbindung zu einem der verwalteten Knoten her und führen Sie etcd
und etcdctl
aus:
- ssh root@etcd1_public_ip
etcd1_public_ip
ist die öffentliche IP-Adresse des Servers namens etcd1. Sobald Sie sich SSH-Zugriff verschafft haben, führen Sie etcd --version
aus, um die installierte Version von etcd auszudrucken:
- etcd --version
Sie werden eine Ausgabe erhalten, die in etwa der folgenden ähnelt. Das bedeutet, dass die Binärdatei etcd
erfolgreich installiert wurde:
Outputetcd Version: 3.3.13
Git SHA: 98d3084
Go Version: go1.10.8
Go OS/Arch: linux/amd64
Um sich zu vergewissern, dass etcdctl
erfolgreich installiert wurde, führen Sie etcdctl version
aus:
- etcdctl version
Sie werden eine Ausgabe sehen, die etwa folgendermaßen aussieht:
Outputetcdctl version: 3.3.13
API version: 3.3
Beachten Sie, dass die Ausgabe API version: 3.3
lautet, wodurch bestätigt wird, dass unsere Umgebungsvariable ETCDCTL_API
richtig festgelegt wurde.
Beenden Sie den etcd1-Server, um zu Ihrer lokalen Umgebung zurückzukehren.
Wir haben etcd
und etcdctl
nun erfolgreich auf allen unseren verwalteten Knoten installiert. Im nächsten Schritt fügen wir unserem Play zusätzliche Aufgaben hinzu, sodass etcd als Hintergrunddienst ausgeführt wird.
Die schnellste Methode zur Ausführung von etcd mit Ansible scheint die Verwendung des command
-Moduls zur Ausführung von /opt/etcd/bin/etcd
zu sein. Das funktioniert jedoch nicht, da etcd
dadurch als Vordergrundprozess ausgeführt wird. Durch die Verwendung des command
-Moduls wird Ansible hängenbleiben, da es auf die Rückgabe des Befehl etcd
wartet, was nie geschehen wird. In diesem Schritt werden wir unser Playbook also so aktualisieren, dass stattdessen unsere Binärdatei etcd
als Hintergrunddienst ausgeführt wird.
Ubuntu 18.04 verwendet systemd als sein init-System. Das bedeutet, dass wir neue Dienste erstellen können, indem wir Unit-Dateien schreiben und im Verzeichnis /etc/systemd/system/
platzieren.
Erstellen Sie zunächst in Ihrem Projektverzeichnis ein neues Verzeichnis namens files/
:
- mkdir files
Erstellen Sie dann in diesem Verzeichnis mit Ihrem Editor eine neue Datei namens etcd.service
:
- nano files/etcd.service
Kopieren Sie als Nächstes den folgenden Codeblock in die Datei files/etcd.service
:
[Unit]
Description=etcd distributed reliable key-value store
[Service]
Type=notify
ExecStart=/opt/etcd/bin/etcd
Restart=always
Diese Unit-Datei definiert einen Dienst, der die ausführbare Datei unter /opt/etcd/bin/etcd
ausführt, systemd benachrichtigt, sobald die Initialisierung beendet ist, und immer neu startet, sollte sie je beendet werden.
Anmerkung: Wenn Sie mehr über systemd und Unit-Dateien erfahren möchten oder die Unit-Datei an Ihre Bedürfnisse anpassen möchten, lesen Sie den Leitfaden Understanding Systemd Units and Unit Files (systemd-Units und Unit-Dateien verstehen).
Schließen und speichern Sie die Datei files/etcd.service
, indem Sie Strg+X
drücken, gefolgt von Y
.
Als Nächstes müssen wir eine Aufgabe in unserem Playbook hinzufügen, die die lokale Datei files/etcd.service
für die einzelnen verwalteten Knoten in das Verzeichnis /etc/systemd/system/etcd.service
kopiert. Wir können dies mit dem copy
-Modul tun.
Öffnen Sie Ihr Playbook:
- nano $HOME/playground/etcd-ansible/playbook.yaml
Fügen Sie am Ende der bestehenden Aufgaben die folgende hervorgehobene Aufgabe an:
- hosts: etcd
become: True
tasks:
...
- name: "Set the ETCDCTL_API environment variable to 3"
lineinfile:
path: /etc/profile
line: export ETCDCTL_API=3
state: present
create: True
insertafter: EOF
- name: "Create a etcd service"
copy:
src: files/etcd.service
remote_src: False
dest: /etc/systemd/system/etcd.service
owner: root
group: root
mode: 0644
Durch Kopieren der Unit-Datei in /etc/systemd/system/etcd.service
wird nun ein Dienst definiert.
Speichern und schließen Sie das Playbook.
Führen Sie den gleichen Befehl ansible-playbook
erneut aus, um die neuen Änderungen anzuwenden:
- ansible-playbook -i hosts playbook.yaml
Um zu prüfen, ob die Änderungen angewendet wurden, stellen Sie zunächst eine SSH-Verbindung mit einem der verwalteten Knoten her:
- ssh root@etcd1_public_ip
Führen Sie dann systemctl status etcd
aus, um systemd über den Status des Diensts etcd
abzufragen:
- systemctl status etcd
Sie erhalten die folgende Ausgabe, in der angegeben wird, dass der Dienst geladen wurde:
Output● etcd.service - etcd distributed reliable key-value store
Loaded: loaded (/etc/systemd/system/etcd.service; static; vendor preset: enabled)
Active: inactive (dead)
...
Anmerkung: Die letzte Zeile (Active: inactive (dead)
) der Ausgabestatus gibt an, dass der Dienst inaktiv ist. Das bedeutet, dass er beim Starten des Systems nicht automatisch ausgeführt würde. Dies ist zu erwarten und kein Fehler.
Drücken Sie q
, um zur Shell zurückzukehren, und führen Sie dann exit
aus, um den verwalteten Knoten zu verlassen und zu Ihrer lokalen Shell zurückzukehren:
- exit
In diesem Schritt haben wir unser Playbook so aktualisiert, das es die Binärdatei etcd
als systemd-Dienst ausführt. Im nächsten Schritt werden wir etcd weiter einrichten, indem wir Platz zur Speicherung seiner Daten zur Verfügung stellen.
etcd ist ein Datenspeicher für Schlüsselwerte. Das bedeutet, dass wir ihm Platz zur Speicherung seiner Daten zur Verfügung stellen müssen. In diesem Schritt werden wir unser Playbook so aktualisieren, dass ein dediziertes Datenverzeichnis zur Verwendung durch etcd definiert wird.
Öffnen Sie Ihr Playbook:
- nano $HOME/playground/etcd-ansible/playbook.yaml
Fügen Sie am Ende der Liste der Aufgaben die folgende Aufgabe an:
- hosts: etcd
become: True
tasks:
...
- name: "Create a etcd service"
copy:
src: files/etcd.service
remote_src: False
dest: /etc/systemd/system/etcd.service
owner: root
group: root
mode: 0644
- name: "Create a data directory"
file:
path: /var/lib/etcd/{{ inventory_hostname }}.etcd
state: directory
owner: root
group: root
mode: 0755
Hier verwenden wir /var/lib/etcd/hostname.etcd
als Datenverzeichnis, wobei hostname
der Hostname des aktuellen verwalteten Knotens ist. inventory_hostname
ist eine Variable, die den Hostnamen des aktuellen verwalteten Knoten darstellt; ihr Wert wird automatisch von Ansible ausgefüllt. Die Syntax mit geschweiften Klammern (d. h. {{ inventory_hostname }}
) wird zur Variablenersetzung genutzt, unterstützt durch die Jinja2-Vorlagen-Engine, die die standardmäßige Vorlagen-Engine für Ansible ist.
Schließen Sie den Texteditor und speichern Sie die Datei.
Als Nächstes müssen wir etcd anweisen, dieses Datenverzeichnis zu verwenden. Dazu übergeben wir den Parameter data-dir
an etcd. Zum Festlegen von etcd-Parametern können wir eine Kombination aus Umgebungsvariablen, Befehlszeilen-Flags und Konfigurationsdateien verwenden. In diesem Tutorial verwenden wir eine Konfigurationsdatei, da es deutlich eleganter ist, alle Konfigurationen in einer Datei zu isolieren, anstatt die Konfiguration über unser ganzes Playbook zu verteilen.
Erstellen Sie in Ihrem Projektverzeichnis ein neues Verzeichnis namens templates/
:
- mkdir templates
Erstellen Sie dann in dem Verzeichnis mit Ihrem Editor eine neue Datei namens etcd.conf.yaml.j2
:
- nano templates/etcd.conf.yaml.j2
Kopieren Sie als Nächstes die folgende Zeile und fügen Sie sie in die Datei ein:
data-dir: /var/lib/etcd/{{ inventory_hostname }}.etcd
Diese Datei verwendet die gleiche Jinja2-Variablenersetzungssyntax wie unser Playbook. Um die Variablen zu ersetzen und das Ergebnis in die einzelnen verwalteten Hosts hochzuladen, können wir das template
-Modul verwenden. Es funktioniert auf ähnliche Weise wie copy
, nimmt vor dem Upload jedoch eine Variablenersetzung vor.
Beenden Sie etcd.conf.yaml.j2
und öffnen Sie dann Ihr Playbook:
- nano $HOME/playground/etcd-ansible/playbook.yaml
Fügen Sie der Liste der Aufgaben die folgenden Aufgaben an, um ein Verzeichnis zu erstellen und die vorlagenbasierte Konfigurationsdatei darin hochzuladen:
- hosts: etcd
become: True
tasks:
...
- name: "Create a data directory"
file:
...
mode: 0755
- name: "Create directory for etcd configuration"
file:
path: /etc/etcd
state: directory
owner: root
group: root
mode: 0755
- name: "Create configuration file for etcd"
template:
src: templates/etcd.conf.yaml.j2
dest: /etc/etcd/etcd.conf.yaml
owner: root
group: root
mode: 0600
Speichern und schließen Sie diese Datei.
Da wir diese Änderung vorgenommen haben, müssen wir nun die Unit-Datei unseres Diensts aktualisieren, damit ihr der Speicherort unserer Konfigurationsdatei übergeben wird (d. h. /etc/etcd/etcd.conf.yaml
).
Öffnen Sie die Datei etcd.service auf Ihrem lokalen Rechner:
- nano files/etcd.service
Aktualisieren Sie die Datei files/etcd.service
, indem Sie das im Folgenden hervorgehobene Flag --config-file
hinzufügen:
[Unit]
Description=etcd distributed reliable key-value store
[Service]
Type=notify
ExecStart=/opt/etcd/bin/etcd --config-file /etc/etcd/etcd.conf.yaml
Restart=always
Speichern und schließen Sie die Datei.
In diesem Schritt haben wir unser Playbook zur Bereitstellung eines Datenverzeichnisses für etcd zum Speichern seiner Daten verwendet. Im nächsten Schritt werden wir noch einige Aufgaben hinzufügen, um den etcd
-Dienst neu zu starten und für ein Ausführen beim Systemstart zu sorgen.
Jedes Mal wenn wir Änderungen an der Unit-Datei eines Diensts vornehmen, müssen wir diesen Dienst neu starten, damit die Änderungen wirksam werden. Wir können dazu den Befehl systemctl restart etcd
ausführen. Damit der etcd
-Dienst beim Systemstart automatisch gestartet wird, müssen wir systemctl enable etcd
ausführen. In diesem Schritt werden wir mit dem Playbook diese beiden Befehle ausführen.
Um Befehle auszuführen, können wir das command
-Modul verwenden:
- nano $HOME/playground/etcd-ansible/playbook.yaml
Fügen Sie am Ende der Aufgabenliste die folgenden Aufgaben an:
- hosts: etcd
become: True
tasks:
...
- name: "Create configuration file for etcd"
template:
...
mode: 0600
- name: "Enable the etcd service"
command: systemctl enable etcd
- name: "Start the etcd service"
command: systemctl restart etcd
Speichern und schließen Sie die Datei.
Führen Sie ansible-playbook -i hosts playbook.yaml
erneut aus:
- ansible-playbook -i hosts playbook.yaml
Um zu überprüfen, ob der Dienst etcd
neu gestartet und aktiviert wurde, stellen Sie eine SSH-Verbindung zu einem der verwalteten Knoten her:
- ssh root@etcd1_public_ip
Führen Sie dann systemctl status etcd
aus, um den Status des etcd
-Diensts zu überprüfen:
- systemctl status etcd
Sie werden im Folgenden enabled
und active (running)
als hervorgehoben sehen; das bedeutet, dass die Änderungen, die wir in unserem Playbook vorgenommen haben, wirksam geworden sind:
Output● etcd.service - etcd distributed reliable key-value store
Loaded: loaded (/etc/systemd/system/etcd.service; static; vendor preset: enabled)
Active: active (running)
Main PID: 19085 (etcd)
Tasks: 11 (limit: 2362)
In diesem Schritt haben wir das command
-Modul zur Ausführung von systemctl
-Befehlen verwendet, die den Dienst etcd
neu starten und auf unseren verwalteten Knoten aktivieren. Nachdem wir eine etcd-Installation eingerichtet haben, testen wir nun im nächsten Schritt ihre Funktionalität durch Ausführung von CRUD-Operationen zum Erstellen, Lesen, Aktualisieren und Löschen.
Zwar verfügen wir über eine funktionierende etcd-Installation, doch ist diese unsicher und noch nicht bereit für den Produktionseinsatz. Bevor wir aber unsere etcd-Installation in späteren Schritten sichern, sollten wir zunächst wissen, was etcd für Funktionen bietet. In diesem Schritt werden wir manuell Anfragen an etcd senden, um Daten hinzuzufügen, abzurufen, zu aktualisieren und zu löschen.
Standardmäßig macht etcd eine API verfügbar, die an Port 2379
auf Client-Kommunikation lauscht. Das bedeutet, dass wir mit einem HTTP-Client rohe API-Anfragen an etcd senden können. Es ist jedoch schneller, den offiziellen etcd-Client etcdctl
zu verwenden. Damit können Sie Schlüsselwertpaare mit den Unterbefehlen put
, get
bzw. del
erstellen/aktualisieren, abrufen und löschen.
Stellen Sie sicher, dass Sie sich noch im verwalteten Knoten etcd1 befinden, und führen Sie die folgenden etcdctl
-Befehle aus, um sich zu vergewissern, dass Ihre etcd-Installation richtig funktioniert.
Erstellen Sie zunächst mit dem Unterbefehl put
einen neuen Eintrag.
Der Unterbefehl put
weist die folgende Syntax auf:
etcdctl put key value
Führen Sie für etcd1 folgenden Befehl aus:
- etcdctl put foo "bar"
Der Befehl, den wir gerade ausgeführt haben, weist etcd an, den Wert "bar"
im Speicher in den Schlüssel foo
zu schreiben.
Dann werden Sie OK
in der Ausgabe sehen, was angibt, dass die Daten persistiert wurden:
OutputOK
Wir können diesen Eintrag dann mit dem Unterbefehl get
abrufen, der die Syntax etcdctl get key
hat:
- etcdctl get foo
Sie werden diese Ausgabe finden, die den Schlüssel in der ersten Zeile und den Wert anzeigt, den Sie zuvor in der zweiten Zeile eingefügt haben:
Outputfoo
bar
Wir können den Eintrag mit dem Unterbefehl del
löschen, der die Syntax etcdctl del key
hat:
- etcdctl del foo
Sie werden die folgende Ausgabe sehen, die die Anzahl der gelöschten Einträge angibt:
Output1
Lassen Sie uns nun den Unterbefehl get
erneut ausführen, um zu versuchen, ein gelöschtes Schlüssel-Wert-Paar abzurufen:
- etcdctl get foo
Sie erhalten keine Ausgabe, was bedeutet, dass etcdctl
das Schlüssel-Wert-Paar nicht abrufen kann. Dadurch wird bestätigt, dass der Eintrag nach dem Löschen nicht mehr abgerufen werden kann.
Nachdem Sie die grundlegenden Operationen von etcd und etcdctl
getestet haben, verlassen wir nun unseren verwalteten Knoten und kehren zurück zu der lokalen Umgebung:
- exit
In diesem Schritt haben wir den etcdctl
-Client zum Senden von Anfragen an etcd verwendet. An diesem Punkt führen wir drei separate Instanzen von etcd aus, die jeweils unabhängig voneinander agieren. Jedoch ist etcd als verteilter Schlüssel-Wert-Speicher konzipiert; das bedeutet, dass sich mehrere etcd-Instanzen gruppieren können, um einen einzelnen Cluster zu bilden; jede Instanz wird dann ein Member (Mitglied) des Clusters. Nach der Einrichtung eines Clusters könnten Sie ein Schlüssel-Wert-Paar abrufen, das von einem anderen Memberknoten des Clusters eingefügt wurde. Im nächsten Schritt werden wir unser Playbook verwenden, um unsere drei 1-Node-Cluster in einen einzigen 3-Node-Cluster zu verwandeln.
Um anstelle von drei 1-Node-Clustern einen 3-Node-Cluster zu erstellen, müssen wir die etcd-Installationen so konfigurieren, dass sie miteinander kommunizieren. Das bedeutet, dass alle die IP-Adressen der anderen kennen müssen. Dieser Prozess wird Erkennung genannt. Erkennung kann entweder mit statischer Konfiguration oder mit einer dynamischen Diensterkennung erfolgen. In diesem Schritt werden wir den Unterschied zwischen den beiden erörtern sowie unser Playbook aktualisieren, um einen etcd-Cluster mit statischer Erkennung einzurichten.
Erkennung mit statischer Konfiguration ist die Methode, die die geringste Einrichtung erfordert; hier werden die Endpunkte der einzelnen Memberknoten in den Befehl etcd
übergeben, bevor er ausgeführt wird. Um statische Konfiguration zu verwenden, müssen vor der Initialisierung des Clusters folgende Bedingungen erfüllt sein:
Wenn sich diese Bedingungen nicht erfüllen lassen, können Sie einen dynamischen Erkennungsdienst verwenden. Bei der dynamischen Diensterkennung würden sich alle Instanzen beim Erkennungsdienst registrieren, damit jeder Memberknoten Informationen über den Ort anderer Memberknoten abrufen kann.
Da wir wissen, dass wir einen 3-Node-etcd-Cluster nutzen möchten und alle unsere Server über statische IP-Adressen verfügen, werden wir statische Erkennung verwenden. Um unseren Cluster mit statischer Erkennung zu initiieren, müssen wir unserer Konfigurationsdatei mehrere Parameter hinzufügen. Verwenden Sie einen Editor, um die Vorlagendatei templates/etcd.conf.yaml.j2
zu öffnen:
- nano templates/etcd.conf.yaml.j2
Fügen Sie dann die folgenden hervorgehobenen Zeilen hinzu:
data-dir: /var/lib/etcd/{{ inventory_hostname }}.etcd
name: {{ inventory_hostname }}
initial-advertise-peer-urls: http://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2380
listen-peer-urls: http://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2380,http://127.0.0.1:2380
advertise-client-urls: http://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2379
listen-client-urls: http://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2379,http://127.0.0.1:2379
initial-cluster-state: new
initial-cluster: {% for host in groups['etcd'] %}{{ hostvars[host]['ansible_facts']['hostname'] }}=http://{{ hostvars[host]['ansible_facts']['eth1']['ipv4']['address'] }}:2380{% if not loop.last %},{% endif %}{% endfor %}
Schließen und speichern Sie die Datei templates/etcd.conf.yaml.j2
, indem Sie Strg+X
drücken, gefolgt von J
.
Hier ist eine kurze Erläuterung der einzelnen Parameter:
name
- ein menschenlesbarer Name für den Memberknoten. Standardmäßig verwendet etcd eine eindeutige, zufällig generierte ID zur Identifizierung der einzelnen Memberknoten; ein menschlich lesbarer Name erleichtert jedoch in Konfigurationsdateien und in der Befehlszeile das Verweisen darauf. Hier werden wir die Hostnamen als Membernamen (d. h. etcd1
, etcd2
und etcd3
) verwenden.initial-advertise-peer-urls
- eine Liste mit IP-Adress-/Port-Kombinationen, die andere Memberknoten zur Kommunikation mit diesem Memberknoten verwenden können. Neben dem API-Port (2379
) macht etcd auch Port 2380
für Peer-Kommunikation zwischen etcd-Memberknoten verfügbar, sodass sie Nachrichten aneinander senden und Daten austauschen können. Beachten Sie, dass diese URLs von ihren Peers erreichbar sein müssen (und keine lokalen IP-Adressen sein dürfen).listen-peer-urls
- eine Liste mit IP-Adress-/Port-Kombinationen, bei denen der aktuelle Memberknoten auf Kommunikation von anderen Memberknoten lauscht. Sie muss alle URLs aus dem Flag --initial-advertise-peer-urls
enthalten, aber auch lokale URLs wie 127.0.0.1:2380
. Die Ziel-IP-Adresse/der Port eingehender Peer-Nachrichten müssen mit einer der hier aufgeführten URLs übereinstimmen.advertise-client-urls
- eine Liste mit IP-Adress-/Port-Kombinationen, die Clients zur Kommunikation mit diesem Memberknoten verwenden sollen. Diese URLs müssen vom Client erreichbar sein (und dürfen keine lokalen Adressen sein). Wenn der Client über das öffentliche Internet auf den Cluster zugreift, muss dies eine öffentliche IP-Adresse sein.listen-client-urls
- eine Liste mit IP-Adress-/Port-Kombinationen, bei denen der aktuelle Memberknoten auf Kommunikation von Clients lauscht. Sie muss alle URLs aus dem Flag --advertise-client-urls
enthalten, aber auch lokale URLs wie 127.0.0.1:2379
. Die Ziel-IP-Adresse/der Port eingehender Client-Nachrichten müssen mit einer der hier aufgeführten URLs übereinstimmen.initial-cluster
- eine Liste mit Endpunkten für jeden Memberknoten des Clusters. Jeder Endpunkt muss mit einer der initial-advertise-peer-urls
-URLs des entsprechenden Memberknotens übereinstimmen.initial-cluster-state
- entweder new
oder existing
.Zur Gewährleistung der Konsistenz kann etcd nur Entscheidungen treffen, wenn eine Mehrheit der Knoten integer ist. Dies wird als Einrichten eines Quorum bezeichnet. Mit anderen Worten,: In einem Cluster mit drei Memberknoten wird das Quorum erreicht, wenn zwei oder mehr der Mitglieder integer sind.
Wenn der Parameter initial-cluster-state
auf new
gesetzt ist, weiß etcd
, dass dies ein neuer Cluster ist, für den Bootstrapping ausgeführt wird; so können Memberknoten parallel gestartet werden, ohne dass auf das Erreichen des Quorums gewartet werden muss. Konkret: Nachdem das erste Mitglied gestartet wurde, wird kein Quorum erreicht, da ein Drittel (33,33 %) nicht mehr als 50 % ist. Normalerweise wird etcd anhalten und sich weigern, weitere Aktionen zu übergeben; der Cluster wird nie erstellt. Wenn der initial-cluster-state
jedoch auf new
gesetzt ist, wird das anfängliche Fehlen des Quorums ignoriert.
Wenn der Wert auf existing
gesetzt ist, wird der Memberknoten versuchen, einem vorhandenen Cluster beizutreten, und erwarten, dass das Quorum bereits eingerichtet wurde.
Anmerkung: Weitere Details zu allen unterstützten Konfigurations-Flags finden Sie im Abschnitt Konfiguration der etcd-Dokumentation.
In der aktualisierten Vorlagendatei templates/etcd.conf.yaml.j2
gibt es einige Instanzen von hostvars
. Wenn Ansible ausgeführt wird, erfassen sie Variablen aus verschiedenen Quellen. Wir haben bereits die Variable inventory_hostname
verwendet, aber es gibt noch viel mehr. Diese Variablen sind verfügbar unter hostvars[inventory_hostname]['ansible_facts']
. Hier extrahieren wir die privaten IP-Adressen der einzelnen Knoten und nutzen Sie zur Erstellung unseres Parameterwerts.
Anmerkung: Da wir bei der Erstellung unserer Server die Option Private Networking aktiviert haben, würde jeder Server über drei IP-Adressen verfügen, die mit ihm verknüpft sind:
127.0.0.1
.178.128.169.51
.10.131.82.225
.Jede dieser IP-Adressen ist mit einer anderen Netzwerkschnittstelle verbunden: Die Loopback-Adresse ist mit der lo
-Schnittstelle verbunden, die öffentliche IP-Adresse mit der eth0
-Schnittstelle und die private IP-Adresse mit der eth1
-Schnittstelle. Wir verwenden die eth1
-Schnittstelle, damit der gesamte Datenverkehr im privaten Netzwerk bleibt, ohne je das Internet zu erreichen.
Für diesen Artikel ist kein Verständnis von Netzwerkschnittstellen erforderlich; wenn Sie jedoch mehr erfahren möchten, ist An Introduction to Networking Terminology, Interfaces, and Protocols( Eine Einführung in Netzwerkbegriffe, Schnittstellen und Protokolle) ein guter Ausgangspunkt.
Die Jinja2-Syntax {% %}
definiert die for
-Schleifenstruktur, die über jeden Knoten in der Gruppe etcd
iteriert, um die Zeichenfolge initial-cluster
in einem Format zu erstellen, das etcd benötigt.
Um den neuen Cluster mit drei Memberknoten zu erstellen, müssen Sie zunächst den etcd
-Dienst stoppen und das Datenverzeichnis löschen, bevor Sie den Cluster starten. Verwenden Sie einen Editor, um die Datei playbook.yaml
auf Ihrem lokalen Rechner zu öffnen:
- nano $HOME/playground/etcd-ansible/playbook.yaml
Fügen Sie dann vor der Aufgabe „Create a Data directory“
eine Aufgabe hinzu, um den etcd
-Dienst anzuhalten:
- hosts: etcd
become: True
tasks:
...
group: root
mode: 0644
- name: "Stop the etcd service"
command: systemctl stop etcd
- name: "Create a data directory"
file:
...
Aktualisieren Sie als Nächstes die Aufgabe "Create a Data directory"
, um das Datenverzeichnis zunächst zu löschen und dann neu zu erstellen:
- hosts: etcd
become: True
tasks:
...
- name: "Stop the etcd service"
command: systemctl stop etcd
- name: "Create a data directory"
file:
path: /var/lib/etcd/{{ inventory_hostname }}.etcd
state: "{{ item }}"
owner: root
group: root
mode: 0755
with_items:
- absent
- directory
- name: "Create directory for etcd configuration"
file:
...
Die Eigenschaft with_items
definiert eine Liste von Zeichenfolgen, über die diese Aufgabe iterieren wird. Es ist genauso, als würden Sie die gleiche Aufgabe zweimal wiederholen, aber mit verschiedenen Werten für die Eigenschaft state
. Hier iterieren wir über die Liste mit Elementen absent
und directory
, was gewährleistet, dass das Datenverzeichnis zunächst gelöscht und danach neu erstellt wird.
Schließen und speichern Sie die Datei playbook.yaml
, indem Sie Strg+X
drücken, gefolgt von J
. Führen Sie dann ansible-playbook
erneut aus. Ansible erstellt nun einen einzelnen etcd
-Cluster mit drei Memberknoten:
- ansible-playbook -i hosts playbook.yaml
Sie können dies überprüfen, indem Sie eine SSH-Verbindung zu einem beliebigen etcd-Memberknoten herstellen:
- ssh root@etcd1_public_ip
Führen Sie dann etcdctl endpoint health --cluster
aus:
- etcdctl endpoint health --cluster
Dadurch wird der Zustand der einzelnen Memberknoten im Cluster aufgelistet:
Outputhttp://etcd2_private_ip:2379 is healthy: successfully committed proposal: took = 2.517267ms
http://etcd1_private_ip:2379 is healthy: successfully committed proposal: took = 2.153612ms
http://etcd3_private_ip:2379 is healthy: successfully committed proposal: took = 2.639277ms
Wir haben nun erfolgreich einen etcd-Cluster mit drei Knoten erstellt. Wir können dies prüfen, indem wir auf einem Memberknoten einen Eintrag zu etcd hinzufügen und diesen dann auf einem anderen Memberknoten abrufen. Führen Sie auf einem der Memberknoten etcdctl put
aus:
- etcdctl put foo "bar"
Verwenden Sie dann ein neues Terminal, um eine SSH-Verbindung zu einem anderen Memberknoten herzustellen:
- ssh root@etcd2_public_ip
Versuchen Sie als Nächstes, mit folgendem Schlüssel den gleichen Eintrag abzurufen:
- etcdctl get foo
Sie können den Eintrag abrufen, was beweist, dass der Cluster funktioniert:
Outputfoo
bar
Verlassen Sie abschließend die einzelnen verwalteten Knoten und kehren Sie zurück zu Ihrem lokalen Rechner:
- exit
- exit
In diesem Schritt haben wir einen neuen Cluster mit drei Knoten bereitgestellt. Derzeit erfolgt die Kommunikation zwischen etcd
-Memberknoten sowie ihren Peers und Clients über HTTP. Das bedeutet, dass die Kommunikation unverschlüsselt ist und jede Person, die den Verkehr abfangen kann, auch die entsprechenden Nachrichten lesen kann. Dies ist kein großes Problem, wenn der etcd
-Cluster und die Clients alle in einem privaten Netzwerk oder einem virtuellen privaten Netzwerk (VPN) bereitgestellt werden, das Sie vollständig kontrollieren. Wenn jedoch Teile des Datenverkehrs über ein freigegebenes Netzwerk (privat oder öffentlich) übertragen werden, sollten Sie sicherstellen, dass dieser Datenverkehr verschlüsselt ist. Außerdem muss für Clients oder Peers ein Mechanismus eingerichtet werden, der die Authentizität des Servers überprüft.
Im nächsten Schritt werden wir uns ansehen, wie wir Client-to-Server- sowie Peer-Kommunikation mit TLS sichern können.
Um Nachrichten zwischen Memberknoten zu verschlüsseln, verwendet etcd Hypertext Transfer Protocol Secure oder HTTPS, was eine Ebene über der Transport Layer Security oder dem TLS-Protokoll ist. TLS nutzt ein System aus privaten Schlüsseln, Zertifikaten und vertrauenswürdigen Entitäten namens Zertifizierungsstellen (CAs) zum Authentifizieren und gegenseitigen Senden verschlüsselter Nachrichten.
In diesem Tutorial muss jeder Memberknoten ein Zertifikat generieren, um sich selbst zu identifizieren, und von einer Zertifizierungsstelle signieren lassen. Wir werden konfigurieren alle Memberknoten so, dass sie dieser Zertifizierungsstelle vertrauen und somit auch Zertifikaten vertrauen, die von ihr signiert wurden. Dadurch können sich Mitgliedsknoten gegenseitig authentifizieren.
Das Zertifikat, das ein Memberknoten generiert, muss es anderen Memberknoten erlauben, sich selbst zu identifizieren. Alle Zertifikate umfassen den Common Name (CN) der Entität, mit der sie verknüpft sind. Dies wird oft als Identität der Entität verwendet. Bei der Prüfung eines Zertifikats können Clientimplementierungen jedoch vergleichen, ob die von ihnen erfassten Informationen über die Entität mit dem übereinstimmen, was im Zertifikat angegeben wurde. Wenn beispielsweise ein Client das TLS-Zertifikat mit dem Betreff CN=foo.bar.com
herunterlädt, der Client in Wahrheit aber mit einer IP-Adresse (z. B. 167.71.129.110
) verbunden ist, gibt es einen Konflikt und der Client vertraut dem Zertifikat ggf. nicht. Indem Sie im Zertifikat einen Subject Alternative Name (SAN) angeben, erfährt der Überprüfer, dass beide Namen zur gleichen Entität gehören.
Da unsere etcd-Memberknoten über ihre privaten IP-Adressen Peering betreiben, müssen wir diese privaten IP-Adressen, wenn wir unsere Zertifikate definieren, als alternative Antragstellernamen (SANs) angeben.
Um die private IP-Adresse eines verwalteten Knoten zu erfahren, stellen Sie eine SSH-Verbindung damit her:
- ssh root@etcd1_public_ip
Führen Sie dann den folgenden Befehl aus:
- ip -f inet addr show eth1
Sie sehen eine Ausgabe, die den folgenden Zeilen ähnelt:
Output3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
inet 10.131.255.176/16 brd 10.131.255.255 scope global eth1
valid_lft forever preferred_lft forever
In unserer Beispielausgabe ist 10.131.255.176
die private IP-Adresse des verwalteten Knotens und die einzige Information, an der wir interessiert sind. Um alles mit Ausnahme der privaten IP-Adresse herauszufiltern, können wir die Ausgabe des Befehls ip
an das Dienstprogramm
sed übergeben, das zum Filtern und Umformen von Text dient.
- ip -f inet addr show eth1 | sed -En -e 's/.*inet ([0-9.]+).*/\1/p'
Die einzige Ausgabe ist nun die private IP-Adresse selbst:
Output10.131.255.176
Sobald Sie damit zufrieden sind, dass der vorhergehende Befehl funktioniert, verlassen Sie den verwalteten Knoten:
- exit
Um die vorherigen Befehle in Ihr Playbook aufzunehmen, öffnen Sie zunächst die Datei playbook.yaml
:
- nano $HOME/playground/etcd-ansible/playbook.yaml
Fügen Sie dann vor dem vorhandenen Play ein neues Play mit einer einzelnen Aufgabe hinzu:
...
- hosts: etcd
tasks:
- shell: ip -f inet addr show eth1 | sed -En -e 's/.*inet ([0-9.]+).*/\1/p'
register: privateIP
- hosts: etcd
tasks:
...
Die Aufgabe verwendet das shell
-Modul, um die Befehle ip
und sed
auszuführen, wodurch die private IP-Adresse des verwalteten Knoten abgerufen wird. Dann registriert sie den Rückgabewert des Shell-Befehls in einer Variable namens privateIP
, die wir später verwenden werden.
In diesem Schritt haben wir dem Playbook eine Aufgabe hinzugefügt, um die private IP-Adresse der verwalteten Knoten zu erhalten. Im nächsten Schritt werden wir diese Informationen verwenden, um für jeden der Knoten ein Zertifikat zu generieren und die Zertifikate von einer Zertifizierungsstelle (CA) signieren zu lassen.
Damit ein Memberknoten verschlüsselten Datenverkehr erhält, muss der Absender den öffentlichen Schlüssel des Memberknotens verwenden, um die Daten zu verschlüsseln. Der Memberknoten muss den privaten Schlüssel nutzen, um den verschlüsselten Text zu entschlüsseln und die Originaldaten abzurufen. Der öffentliche Schlüssel ist in einem Zertifikat verpackt und wurde von einer CA signiert, um sicherzustellen, dass er echt ist.
Daher müssen wir für jeden etcd-Memberknoten einen privaten Schlüssel und eine Zertifikatsignaturanforderung (CSR) generieren. Um es einfacher zu machen, erstellen wir alle Schlüsselpaare und signieren alle Zertifikate lokal auf dem Steuerknoten und kopieren die entsprechenden Dateien dann auf die verwalteten Hosts.
Erstellen Sie zunächst ein Verzeichnis namens artifacts/
, in dem Sie die in dem Prozess generierten Dateien (Schlüssel und Zertifikate) platzieren werden. Öffnen Sie die Datei playbook.yaml
mit einem Editor:
- nano $HOME/playground/etcd-ansible/playbook.yaml
Verwenden Sie darin das file
-Modul, um das Verzeichnis artifacts/
zu erstellen:
...
- shell: ip -f inet addr show eth1 | sed -En -e 's/.*inet ([0-9.]+).*/\1/p'
register: privateIP
- hosts: localhost
gather_facts: False
become: False
tasks:
- name: "Create ./artifacts directory to house keys and certificates"
file:
path: ./artifacts
state: directory
- hosts: etcd
tasks:
...
Fügen Sie als Nächstes am Ende des Plays eine weitere Aufgabe hinzu, um den privaten Schlüssel zu generieren:
...
- hosts: localhost
gather_facts: False
become: False
tasks:
...
- name: "Generate private key for each member"
openssl_privatekey:
path: ./artifacts/{{item}}.key
type: RSA
size: 4096
state: present
force: True
with_items: "{{ groups['etcd'] }}"
- hosts: etcd
tasks:
...
Das Erstellen von privaten Schlüsseln und CSRs kann mit den Modulen openssl_privatekey
bzw. openssl_csr
erfolgen.
Das Attribut force: True
stellt sicher, dass der private Schlüssel jedes Mal neu generiert wird, auch wenn er bereits existiert.
Fügen Sie nun die folgende neue Aufgabe demselben Play an, um die CSRs für die einzelnen Memberknoten zu generieren; nutzen Sie dazu das Modul openssl_csr
:
...
- hosts: localhost
gather_facts: False
become: False
tasks:
...
- name: "Generate private key for each member"
openssl_privatekey:
...
with_items: "{{ groups['etcd'] }}"
- name: "Generate CSR for each member"
openssl_csr:
path: ./artifacts/{{item}}.csr
privatekey_path: ./artifacts/{{item}}.key
common_name: "{{item}}"
key_usage:
- digitalSignature
extended_key_usage:
- serverAuth
subject_alt_name:
- IP:{{ hostvars[item]['privateIP']['stdout']}}
- IP:127.0.0.1
force: True
with_items: "{{ groups['etcd'] }}"
Wir geben an, dass sich dieses Zertifikat für den Zweck der Serverauthentifizierung an einem digitalen Signaturmechanismus beteiligen kann. Das Zertifikat ist mit dem Hostnamen (z. B. etcd1
) verknüpft; der Überprüfer soll jedoch auch die privaten und lokalen Loopback-IP-Adressen der einzelnen Knoten als alternative Namen behandeln. Beachten Sie, dass wir die Variable privateIP
verwenden, die wir im vorherigen Play registriert haben.
Schließen und speichern Sie die Datei playbook.yaml
, indem Sie Strg+X
drücken, gefolgt von J
. Führen Sie das Playbook dann erneut aus:
- ansible-playbook -i hosts playbook.yaml
Wir werden in unserem Projektverzeichnis nun ein neues Verzeichnis namens Artefakte
sehen; verwenden Sie ls
, um den Inhalt aufzulisten:
- ls artifacts
Sie sehen die privaten Schlüssel und CSRs für die einzelnen etcd-Memberknoten:
Outputetcd1.csr etcd1.key etcd2.csr etcd2.key etcd3.csr etcd3.key
In diesem Schritt haben wir verschiedene Ansible-Module verwendet, um für die einzelnen Memberknoten private Schlüssel und öffentliche Schlüsselzertifikate zu generieren. Im nächsten Schritt werden wir uns ansehen, wie eine Zertifikatsignaturanforderung (CSR) signiert wird.
Innerhalb eines etcd-Clusters verschlüsseln Memberknoten Nachrichten mit dem öffentlichen Schlüssel des Empfängers. Um sicherzustellen, dass der öffentliche Schlüssel echt ist, verpackt der Empfänger den öffentlichen Schlüssel in eine Zertifikatsignaturanforderung (CSR) und lässt diese von einer vertrauenswürdigen Entität (d. h. der CA) signieren. Da wir alle Memberknoten und die Zertifizierungstellen, denen sie vertrauen, kontrollieren, müssen wir keine externe CA nutzen und können als eigene CA fungieren. In diesem Schritt werden wir als eigene CA agieren; das bedeutet, dass wir einen privaten Schlüssel und ein selbstsigniertes Zertifikat erstellen müssen, um als CA zu fungieren.
Öffnen Sie die Datei playbook.yaml
mit Ihrem Editor:
- nano $HOME/playground/etcd-ansible/playbook.yaml
Fügen Sie dann ähnlich wie im vorherigen Schritt eine Aufgabe im Play localhost
an, um einen privaten Schlüssel für die CA zu generieren:
- hosts: localhost
...
tasks:
...
- name: "Generate CSR for each member"
...
with_items: "{{ groups['etcd'] }}"
- name: "Generate private key for CA"
openssl_privatekey:
path: ./artifacts/ca.key
type: RSA
size: 4096
state: present
force: True
- hosts: etcd
become: True
tasks:
- name: "Create directory for etcd binaries"
...
Als Nächstes verwenden Sie das Modul openssl_csr
, um eine neue CSR zu generieren. Dies ähnelt dem vorherigen Schritt; in dieser CSR fügen wir jedoch die Basiseinschränkung und Schlüsselverwendungserweiterung hinzu, um anzugeben, dass dieses Zertifikat als CA-Zertifikat verwendet werden kann:
- hosts: localhost
...
tasks:
...
- name: "Generate private key for CA"
openssl_privatekey:
path: ./artifacts/ca.key
type: RSA
size: 4096
state: present
force: True
- name: "Generate CSR for CA"
openssl_csr:
path: ./artifacts/ca.csr
privatekey_path: ./artifacts/ca.key
common_name: ca
organization_name: "Etcd CA"
basic_constraints:
- CA:TRUE
- pathlen:1
basic_constraints_critical: True
key_usage:
- keyCertSign
- digitalSignature
force: True
- hosts: etcd
become: True
tasks:
- name: "Create directory for etcd binaries"
...
Verwenden Sie schließlich das Modul openssl_certificate
, um das CSR selbst zu signieren:
- hosts: localhost
...
tasks:
...
- name: "Generate CSR for CA"
openssl_csr:
path: ./artifacts/ca.csr
privatekey_path: ./artifacts/ca.key
common_name: ca
organization_name: "Etcd CA"
basic_constraints:
- CA:TRUE
- pathlen:1
basic_constraints_critical: True
key_usage:
- keyCertSign
- digitalSignature
force: True
- name: "Generate self-signed CA certificate"
openssl_certificate:
path: ./artifacts/ca.crt
privatekey_path: ./artifacts/ca.key
csr_path: ./artifacts/ca.csr
provider: selfsigned
force: True
- hosts: etcd
become: True
tasks:
- name: "Create directory for etcd binaries"
...
Schließen und speichern Sie die Datei playbook.yaml
, indem Sie Strg+X
drücken, gefolgt von J
. Führen Sie dann das Playbook erneut aus, um die Änderungen anzuwenden:
- ansible-playbook -i hosts playbook.yaml
Außerdem können Sie ls
ausführen, um den Inhalt des Verzeichnisses artifacts/
zu überprüfen:
- ls artifacts/
Sie sehen nun das neu generierte CA-Zertifikat (ca.crt
):
Outputca.crt ca.csr ca.key etcd1.csr etcd1.key etcd2.csr etcd2.key etcd3.csr etcd3.key
In diesem Schritt haben wir einen privaten Schlüssel und ein selbstsigniertes Zertifikat für die CA generiert. Im nächsten Schritt verwenden wir das CA-Zertifikat nutzen, um die CSRs der einzelnen Memberknoten zu signieren.
In diesem Schritt signieren wir die CSRs der einzelnen Memberknoten. Dies ähnelt der Methode, mit der wir das Modul openssl_certificate
verwendet haben, um das CA-Zertifikat selbst zu signieren. Aber anstelle des Anbieters selfsigned
nutzen wir den Anbieter ownca
, damit wir unsere eigenen CA-Zertifikate signieren können.
Öffnen Sie Ihr Playbook:
- nano $HOME/playground/etcd-ansible/playbook.yaml
Fügen Sie der Aufgabe "Generate self-signed CA certificate"
die folgende hervorgehobene Aufgabe an:
- hosts: localhost
...
tasks:
...
- name: "Generate self-signed CA certificate"
openssl_certificate:
path: ./artifacts/ca.crt
privatekey_path: ./artifacts/ca.key
csr_path: ./artifacts/ca.csr
provider: selfsigned
force: True
- name: "Generate an `etcd` member certificate signed with our own CA certificate"
openssl_certificate:
path: ./artifacts/{{item}}.crt
csr_path: ./artifacts/{{item}}.csr
ownca_path: ./artifacts/ca.crt
ownca_privatekey_path: ./artifacts/ca.key
provider: ownca
force: True
with_items: "{{ groups['etcd'] }}"
- hosts: etcd
become: True
tasks:
- name: "Create directory for etcd binaries"
...
Schließen und speichern Sie die Datei playbook.yaml
, indem Sie Strg+X
drücken, gefolgt von ``Y. Führen Sie dann das Playbook erneut aus, um die Änderungen anzuwenden:
- ansible-playbook -i hosts playbook.yaml
Listen Sie nun den Inhalt des Verzeichnisses artifacts/
auf:
- ls artifacts/
Sie finden den privaten Schlüssel, die CSR und das Zertifikat für jeden einzelnen etcd-Memberknoten und die CA:
Outputca.crt ca.csr ca.key etcd1.crt etcd1.csr etcd1.key etcd2.crt etcd2.csr etcd2.key etcd3.crt etcd3.csr etcd3.key
In diesem Schritt haben wir die CSRs der einzelnen Memberknoten mithilfe des Schlüssels der CA signiert. Im nächsten Schritt kopieren wir die relevanten Dateien in die einzelnen verwalteten Knoten, damit etcd zum Einrichten von TLS-Verbindungen Zugriff auf die entsprechenden Schlüssel und Zertifikate hat.
Jeder Knoten muss eine Kopie des selbstsignierten Zertifikats der CA (ca.crt
) haben. Jeder etcd
-Memberknoten muss auch über einen eigenen privaten Schlüssel und ein Zertifikat verfügen. In diesem Schritt laden wir die Dateien hoch und platzieren sie in einem neuen Verzeichnis namens /etc/etcd/ssl/
.
Öffnen Sie zunächst die Datei playbook.yaml
mit Ihrem Editor:
- nano $HOME/playground/etcd-ansible/playbook.yaml
Um diese Änderungen an unserem Ansible-Playbook vorzunehmen, aktualisieren Sie zunächst die Eigenschaft path
der Aufgabe Create directory for etcd configuration
, um das Verzeichnis /etc/etcd/ssl/
zu erstellen:
- hosts: etcd
...
tasks:
...
with_items:
- absent
- directory
- name: "Create directory for etcd configuration"
file:
path: "{{ item }}"
state: directory
owner: root
group: root
mode: 0755
with_items:
- /etc/etcd
- /etc/etcd/ssl
- name: "Create configuration file for etcd"
template:
...
Fügen Sie dann nach der modifizierten Aufgabe drei weitere Aufgaben hinzu, um die Dateien zu kopieren:
- hosts: etcd
...
tasks:
...
- name: "Copy over the CA certificate"
copy:
src: ./artifacts/ca.crt
remote_src: False
dest: /etc/etcd/ssl/ca.crt
owner: root
group: root
mode: 0644
- name: "Copy over the `etcd` member certificate"
copy:
src: ./artifacts/{{inventory_hostname}}.crt
remote_src: False
dest: /etc/etcd/ssl/server.crt
owner: root
group: root
mode: 0644
- name: "Copy over the `etcd` member key"
copy:
src: ./artifacts/{{inventory_hostname}}.key
remote_src: False
dest: /etc/etcd/ssl/server.key
owner: root
group: root
mode: 0600
- name: "Create configuration file for etcd"
template:
...
Schließen und speichern Sie die Datei playbook.yaml
, indem Sie Strg+X
drücken, gefolgt von J
.
Führen Sie ansible-playbook
erneut aus, um diese Änderungen vorzunehmen:
- ansible-playbook -i hosts playbook.yaml
In diesem Schritt haben wir die privaten Schlüssel und Zertifikate erfolgreich in die verwalteten Knoten hochgeladen. Nachdem wir die Dateien kopiert haben, müssen wir nun unsere etcd-Konfigurationsdatei so aktualisieren, dass sie sie nutzt.
Im letzten Schritt dieses Tutorials werden wir einige Ansible-Konfigurationen aktualisieren, um TLS in einem etcd-Cluster zu aktivieren.
Öffnen Sie zunächst die Vorlagendatei templates/etcd.conf.yaml.j2
mit Ihrem Editor:
- nano $HOME/playground/etcd-ansible/templates/etcd.conf.yaml.j2
Ändern Sie darin alle URLs so, dass sie https
als Protokoll anstelle von http
verwenden. Fügen Sie außerdem am Ende der Vorlage einen Abschnitt hinzu, um den Speicherort des CA-Zertifikats, des Serverzertifikats und des Serverschlüssels anzugeben:
data-dir: /var/lib/etcd/{{ inventory_hostname }}.etcd
name: {{ inventory_hostname }}
initial-advertise-peer-urls: https://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2380
listen-peer-urls: https://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2380,https://127.0.0.1:2380
advertise-client-urls: https://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2379
listen-client-urls: https://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2379,https://127.0.0.1:2379
initial-cluster-state: new
initial-cluster: {% for host in groups['etcd'] %}{{ hostvars[host]['ansible_facts']['hostname'] }}=https://{{ hostvars[host]['ansible_facts']['eth1']['ipv4']['address'] }}:2380{% if not loop.last %},{% endif %}{% endfor %}
client-transport-security:
cert-file: /etc/etcd/ssl/server.crt
key-file: /etc/etcd/ssl/server.key
trusted-ca-file: /etc/etcd/ssl/ca.crt
peer-transport-security:
cert-file: /etc/etcd/ssl/server.crt
key-file: /etc/etcd/ssl/server.key
trusted-ca-file: /etc/etcd/ssl/ca.crt
Schließen und speichern Sie die Datei templates/etcd.conf.yaml.j2
.
Führen Sie als Nächstes Ihr Ansible-Playbook aus:
- ansible-playbook -i hosts playbook.yaml
Stellen Sie dann eine SSH-Verbindung zu einem der verwalteten Knoten her:
- ssh root@etcd1_public_ip
Führen Sie darauf den Befehl etcdctl endpoint health
aus, um zu überprüfen, ob die Endpunkte HTTPS verwenden und ob alle Memberknoten integer sind:
- etcdctl --cacert /etc/etcd/ssl/ca.crt endpoint health --cluster
Da unser CA-Zertifikat standardmäßig kein vertrauenswürdiges CA-Stammzertifikat ist, das im Verzeichnis /etc/ssl/certs/
installiert ist, müssen wir es mit dem Flag --cacert
an etcdctl
übergeben.
Dadurch erhalten Sie folgende Ausgabe:
Outputhttps://etcd3_private_ip:2379 is healthy: successfully committed proposal: took = 19.237262ms
https://etcd1_private_ip:2379 is healthy: successfully committed proposal: took = 4.769088ms
https://etcd2_private_ip:2379 is healthy: successfully committed proposal: took = 5.953599ms
Um zu bestätigen, dass der etcd
-Cluster tatsächlich funktioniert, können wir erneut einen Eintrag auf einem etcd-Memberknoten erstellen und ihn dann von einem anderen etcd-Memberknoten abrufen:
- etcdctl --cacert /etc/etcd/ssl/ca.crt put foo "bar"
Verwenden Sie ein neues Terminal, um eine SSH-Verbindung zu einem anderen Knoten herzustellen:
- ssh root@etcd2_public_ip
Rufen Sie nun mit dem Schlüssel foo
den gleichen Eintrag ab:
- etcdctl --cacert /etc/etcd/ssl/ca.crt get foo
Dadurch wird der Eintrag zurückgegeben, der die folgende Ausgabe anzeigt:
Outputfoo
bar
Sie können das Gleiche mit dem dritten Knoten tun, um zu prüfen, ob alle drei Memberknoten ausgeführt werden.
Sie haben nun erfolgreich einen etcd-Cluster mit drei Knoten bereitgestellt, mit TLS gesichert und sich vergewissert, dass er funktioniert.
etcd ist eine ursprünglich von CoreOS entwickelte Software. Um die Verwendung von etcd in Bezug auf CoreOS zu verstehen, können Sie Folgendes lesen: How To Use Etcdctl and Etcd, CoreOS’s Distributed Key-Value Store. Der Artikel führt Sie außerdem durch die Einrichtung eines dynamischen Erfassungsmodells, das in diesem Tutorial diskutiert, aber nicht vorgeführt wurde.
Wie am Anfang dieses Tutorials erwähnt, ist etcd ein wichtiger Teil des Kubernetes-Ökosystems. Um mehr über Kubernetes und die Rolle von etcd darin zu erfahren, können Sie An Introduction to Kubernetes (Eine Einführung in Kubernetes) lesen. Wenn Sie etcd als Teil eines Kubernetes-Clusters bereitstellen, sollten Sie wissen, dass es andere Tools gibt, wie z. B. kubespray und kubeadm
. Weitere Details dazu finden Sie unter Erstellen eines Kubernetes-Clusters unter Ubuntu 18.04.
Schließlich wurden in diesem Tutorial auch viele Tools verwendet, die einzeln nicht genau besprochen werden konnten. Im Folgenden finden Sie Links, die Ihnen genauere Informationen zu den einzelnen Tools liefern:
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!