Die hybride Cloud mit Docker Swarm und Ansible (2/2)

Seite 3: Den Schwarm zusammenfügen

Inhaltsverzeichnis

Nach den notwendigen Port-Freischaltungen folgt endlich die ersehnte Initialisierung des Docker Swarm. Dazu inkludiert das Haupt-Playbook initialize-docker-swarm.yml im nächsten task das Playbook initialize-swarm-and-join-all-nodes.yml. Wie das im Detail funktioniert, verrät es dem geneigten Leser:

- name: Leave Swarm on Windows master node, if there was a cluster before
win_shell: "docker swarm leave --force"
ignore_errors: yes
when: inventory_hostname == "masterwindows01"

- name: Initialize Docker Swarm cluster on Windows master node
win_shell: "docker swarm init --advertise-addr={{masterwindows_ip}} --listen-addr {{masterwindows_ip}}:2377"
ignore_errors: yes
when: inventory_hostname == "masterwindows01"

- name: Pause a few seconds after new Swarm cluster initialization to prevent later errors on obtaining tokens to ear
pause:
seconds: 5
...

Der erste Schritt mutet dabei etwas kurios an, denn er veranlasst den Windows-Manager-Node, zuallererst einen etwaig bestehenden Schwarm zu verlassen. Natürlich spielt dieser Schritt bei der ersten Ausführung des Playbooks keine Rolle – und erzeugt eine Fehlermeldung, dass ja kein Swarm vorhanden sei. Das Schlüsselwort ignore_errors: yes ignoriert den Fehler. Führt man das Playbook ein weiteres Mal aus, zeigt sich die Wichtigkeit des ersten Schritts. Denn nur in einer definierten Ausgangssituation lässt sich ein Docker Swarm initialisieren. Das heißt in dem vorliegenden Fall, dass der Windows-Manager-Node kein Teil eines "alten" Swarms ist. Damit ist ein wichtiges Design-Prinzip von Ansible-Playbooks umgesetzt: Am Ende der Ausführung lässt sich immer vom gleichen Stand ausgehen, im Beispiel also von einem korrekt initialisierten Docker Swarm. Ohne den ersten Schritt wäre dies nicht möglich, da alle Folgeschritte nicht durchführbar wären.

Danach folgt endlich der magische Moment: Der Windows-Manager-Node erhebt sich zum Docker-Swarm-Manager. Hierfür wurde im Beispielprojekt ein Windows-Node gewählt, um das volle Potenzial eines hybriden Docker-Swarm-Clusters zu zeigen. Im Befehl docker swarm init müssen sich beide Parameter advertise-addr und listen-addr auf den Windows-Manager-Node beziehen. Da die Ausführung des Befehls erfahrungsgemäß etwas Zeit in Anspruch nimmt, folgt im nächsten Schritt das Ansible-Modul pause, das dem Manager-Node ein paar Sekunden Zeit für die Initialisierung einräumt. Die folgenden Schritte laufen ohne diese Pausierung der Skript-Ausführung oftmals in einen Fehler.

In den nächsten Schritten geht es um das Auslesen der sogenannten Join-Tokens. Diese sind im weiteren Verlauf notwendig, um die anderen Cluster-Nodes zum Docker Swarm hinzuzufügen. Dabei gibt es eine Unterscheidung in Manager- und Worker-Join-Tokens, die sich mit dem Befehl docker swarm join-token manager -q und docker swarm join-token worker -q auslesen lassen:

...
- name: Obtain worker join-token from Windows master node
win_shell: "docker swarm join-token worker -q"
register: worker_token_result
ignore_errors: yes
when: inventory_hostname == "masterwindows01"

- name: Obtain manager join-token from Windows master node
win_shell: "docker swarm join-token manager -q"
register: manager_token_result
ignore_errors: yes
when: inventory_hostname == "masterwindows01"

- name: Syncing the worker and manager join-token results to the other hosts
set_fact:
worker_token_result_host_sync: "{{ hostvars['masterwindows01']['worker_token_result'] }}"
manager_token_result_host_sync: "{{ hostvars['masterwindows01']['manager_token_result'] }}"
- name: Extracting and saving worker and manager join-tokens in variables for joining other nodes later
set_fact:
worker_jointoken: "{{worker_token_result_host_sync.stdout.splitlines()[0]}}"
manager_jointoken: "{{manager_token_result_host_sync.stdout.splitlines()[0]}}"
- name: Join-tokens...
debug:
msg:
- "The worker join-token is: '{{worker_jointoken}}'"
- "The manager join-token is: '{{manager_jointoken}}'"
...

Das Auslesen der Join-Tokens ist durch die Bedingung when: inventory_hostname == "masterwindows01" explizit auf den Windows-Manager-Node beschränkt. Das heißt ebenfalls, dass die per Schlüsselwort register gespeicherten Join-Tokens ausschließlich auf diesem Node zur Verfügung stehen. Doch für die Initialisierung sind die Join-Tokens natürlich auf den anderen Cluster-Nodes ebenfalls notwendig. Deshalb muss eine Weitergabe während der Ausführung des Ansible-Skripts an die anderen Ansible-hosts stattfinden. Dazu muss man etwas tiefer in die Ansible-Trickkiste greifen. Mit dem Ansible-Modul set_fact definiert man zur Lauftzeit Variablen, auf die später die anderen Ansible-hosts zugreifen können. Befüllt werden diese dynamischen Variablen mit dem hostvars-Schlüsselwort:

worker_token_result_host_sync: "{{ hostvars['masterwindows01']['worker_token_result'] }}" 

Die Anweisung hostvars['masterwindows01'] ermöglicht direkten Zugriff auf die Windows-Manager-Node-exklusiven Variablen. Das angehängte ['worker_token_result'] ermöglicht den Zugriff auf das registrierte Ergebnis des docker swarm join-token-Kommandos. Im anschließenden Schritt gibt es eine Extraktion der eigentlichen Join-Tokens aus den nun synchronisierten Ergebnissen, denn sie enthalten eine ganze Reihe an Rückgabewerten, die Ansible definiert. Die Join-Tokens befinden sich im Rückgabewert stdout, der zusätzlich noch ein Zeilenende enthält, das per splitlines()[0]abgeschnitten werden muss.

Ein Blick in die Konsole, die das Ansible-Skript ausführt, zeigt die konkreten Join-Tokens, die dasdebug-Modul ausgibt.

Ansible verrät alle Join-Tokens auf der Kommandozeile

Alle weiteren Cluster-Nodes lassen sich mit den passenden Join-Tokens dem Schwarm hinzufügen. Hier ist wieder jedem docker swarm join-Kommando der Befehl zum Verlassen eines etwaig vorher bestandenen Schwarms vorangestellt. Um einen Worker-Node dem Schwarm hinzuzufügen, kommt das Kommando docker swarm join --token {{worker_jointoken}} {{masterwindows_ip}} zum Einsatz. Das Hinzufügen eines Manager-Nodes erledigt das sehr ähnliche docker swarm join --token {{manager_jointoken}} {{masterwindows_ip}} erledigt:

...
- name: Leave Swarm on Windows worker nodes, if there was a cluster before
win_shell: "docker swarm leave"
ignore_errors: yes
when: inventory_hostname in groups['workerwindows']

- name: Add Windows worker nodes to Docker Swarm cluster
win_shell: "docker swarm join --token {{worker_jointoken}} {{masterwindows_ip}}"
ignore_errors: yes
when: inventory_hostname in groups['workerwindows']

- name: Leave Swarm on Linux worker nodes, if there was a cluster before
shell: "docker swarm leave"
ignore_errors: yes
when: inventory_hostname in groups['workerlinux']

- name: Add Linux worker nodes to Docker Swarm cluster
shell: "docker swarm join --token {{worker_jointoken}} {{masterwindows_ip}}"
ignore_errors: yes
when: inventory_hostname in groups['workerlinux']

- name: Leave Swarm on Linux manager nodes, if there was a cluster before
shell: "docker swarm leave --force"
ignore_errors: yes
when: inventory_hostname in groups['masterlinux']

- name: Add Linux manager nodes to Docker Swarm cluster
shell: "docker swarm join --token {{manager_jointoken}} {{masterwindows_ip}}"
ignore_errors: yes
when: inventory_hostname in groups['masterlinux']
...

Mit der Ausführung dieser Zeilen steht ein voll funktionsfähiger Docker Swarm zur Verfügung!