Cluster mit Raspberry Pi - Modellbau (1)

Lesezeit
8 Minuten
Bis jetzt gelesen

Cluster mit Raspberry Pi - Modellbau (1)

02.12.2017 - 12:32
Veröffentlicht in:

Verteilte Container-Umgebungen lassen sich leicht mit Hilfe virtueller Maschinen simulieren. Eine interessante Alternative dazu ist, einen Cluster mit Raspberry-Rechnern aufzubauen, um darauf Docker und Co. zu betreiben. Wir geben Tipps zum Aufbau und zur Installation.

Richtig interessant wird der Einsatz von Containern typischerweise erst, wenn mehrere Hosts zur Verfügung stehen, auf denen ein sogenannter Orchestrator Container starten, stoppen und migrieren kann. Dies ermöglicht es beispielsweise, Funktionen wie Loadbalancing und Hochverfügbarkeit von Diensten auf der Basis von Containern zu realisieren. Für Test-Setups lässt sich dieses Szenario mit virtuellen Maschinen aufbauen, die als Host-Systeme für die Container dienen. Eine preisgünstige Alternative besteht darin, Raspberry-Pi-Rechner zu verwenden, um einen entsprechenden realen Cluster aufzubauen. Diesen Ansatz stellen wir im Folgenden näher vor.

Da alle Orchestrierungslösungen wenigstens aus einem Master- und einem Worker-Knoten bestehen, brauchen Sie mindestens zwei Minirechner. Wer etwa den Ausfall eines Nodes oder die Migration von Containern von einem Node auf einen anderen simulieren möchte, muss drei oder mehr Raspberries in seinen Einkaufskorb legen.

Zur Vernetzung der Maschinen gibt es mehrere Optionen. So bietet der Raspberry Pi 3 neben der schon bei den älteren Modellen vorhandenen Ethernet-Buchse auch WLAN. Eine Alternative besteht darin, die Rechner über USB-over-Ethernet zu vernetzen (mit USB/OTG-Port, den nur der Raspberry Zero besitzt). Wir haben uns für die einfachste und üblicherweise am wenigsten fehlerträchtige Methode entschieden, die Boards über Ethernet und einen Mini-Switch zu vernetzen. So funktioniert beispielsweise der virtuelle Netzwerk-Interface-Typ "macvlan" des Linux-Kernels nicht mit Wireless Devices.

Je nach Netzwerktopologie kann der Switch zur Verbindung der Rechner schon genügen. Wir verwenden zusätzlich einen günstigen WLAN-Router/Switch, der den Uplink des Clusters ins Internet bereitstellt und es darüber hinaus ermöglicht, sich über ein WLAN mit dem Cluster-Netzwerk zu verbinden, zum Beispiel von einem Laptop aus. Solche Geräte sind im Handel schon ab etwa 20 Euro zu haben. Die Stromversorgung übernimmt ein passender USB-Hub mit sechs Ports (Bild 1).

Da die Raspberry-Rechner keine SATA-Schnittstelle besitzen, bleiben als Massenspeicher nur die typischen Mini-SD-Karten. Wir haben hier 32 GByte große SD-Karten von Samsung verwendet, die eine vergleichsweise gute Performance bieten. Kleinere Karten mit acht oder 16 GByte genügen in der Regel auch, aber letztlich ist der preisliche Unterschied nicht so groß, dass sich hier das Sparen lohnt. Alles in allem belaufen sich die Kosten für unseren Cluster damit auf etwa 280 Euro.

Bild 1: Der Raspberry-Cluster mit USB-Hub und WLAN-Router.
Bild 1: Der Raspberry-Cluster mit USB-Hub und WLAN-Router.


Installation des Basissystems

Wer im Internet nach Anleitungen für die Installation von Docker auf Raspberry sucht, stößt schnell auf die "Docker Pirates ARMed with explosive stuff", die dazu eine eigene Linux-Distribution namens Hypriot verwenden. Seit geraumer Zeit gibt es aber auch Docker-Support für die offizielle Raspberry-Debian-Distribution namens Raspbian [1], die neuer ist als Hypriot, und die wir deshalb bevorzugen.

Wie beim Raspberry üblich, wird das Betriebssystem von einem PC oder Mac aus installiert, indem man das heruntergeladene Image von dort auf die Micro-SD-Karte schreibt. Dies kann entweder auf der Commandline mit dem Tool "dd" passieren oder mit einem grafischen Frontend wie Etcher [2], das für Windows, Linux und macOS zur Verfügung steht. Für unsere Zwecke genügt das Image von Raspbian Stretch Lite, das keine grafische Oberfläche mitbringt.

Da wir außerdem möglichst darauf verzichten wollen, jeden Node unseres Clusters an einen Bildschirm anzuschließen und mit Keyboard und Maus auszustatten, um sie manuell einzurichten, legen wir die Netzwerkeinstellungen vor der Installation fest. Dazu ist es zunächst nötig, die Zip-Datei des Raspbian-Images zu entpacken. Anschließend mounten wir die darauf enthaltenen Partitionen als Loopback-Devices auf einem Linux-System. Die Loop-Devices für die Partitionen legt das Tool "losetup" mit dem entsprechenden Parameter automatisch an:

# losetup -P /dev/loop0 2017-09-07-raspbian-stretch-lite.img

In dem Image sind zwei Partitionen vorhanden: eine Boot-Partition und die Root-Partition des Debian-Systems. Die Boot-Partition mounten Sie mit dem folgenden Befehl:

# mount /dev/loop0p1 /mnt/

Nun finden Sie die Dateien der Boot-Partition unter "/mnt". Aus Sicherheitsgründen ist der Secure-Shell-Daemon per Default deaktiviert. Automatisch gestartet wird er, wenn auf der Boot-Partition eine Datei namens "ssh" mit beliebigem Inhalt existiert. Der folgende Befehl legt sie an und bindet die Boot-Partition wieder aus:

# touch /mnt/ssh
# umount /mnt

Nun fehlt noch die Netzwerkkonfiguration der Raspberry-Rechner. Dabei verwendet Raspbian nicht die von Debian gewohnten Konfigurationsdateien "/etc/network/interfaces", sondern startet automatisch den DHCP-Daemon, in dessen Konfiguration die statische IP-Adresse eingetragen werden muss. Wer einen Router mit DHCP betreibt und auf diesem die MAC-Adressen der Pi-Rechner einträgt, kann auf diesen Schritt gegebenenfalls verzichten.

Wir verwenden jedenfalls statische IP-Adressen und mounten dazu erst einmal die Root-Partition des Raspbian-Images auf dem Linux-Rechner, bevor wir die Konfigurationsdatei des DHCP-Servers editieren:

# mount /dev/loop0p2 /mnt/
# vi /mnt/etc/dhcpcd.conf

Suchen Sie in der Konfiguration nach dem Abschnitt für die statische IP-Adresse und setzen Sie dort etwa die folgenden Zeilen ein:

interface eth0
static ip_address=10.0.0.1/24
static routers=10.0.0.254
static domain_name_servers=8.8.8.8

Jetzt binden Sie die Partition wieder aus und entfernen die Loop-Devices, bevor Sie das geänderte Image auf die SD-Karte schreiben:

# umount /mnt
# losetup -d /dev/loop0
# sudo dd if=2017-09-07-raspbian-stretch-lite.img of=/dev/mmcblk0 bs=4M status=progress

Wenn Sie nun den Raspberry mit der Karte booten und an den Switch angeschlossen haben, sollten Sie sich über die Adresse 10.0.0.1 per SSH einloggen können (Login "pi", Passwort "raspberry"). Wiederholen Sie nun den zweiten Schritt (mit der Root-Partition) für die restlichen Raspberry-Rechner und passen Sie jeweils die IP-Adresse an. In unserem Fall sind anschließend alle Nodes unter den Adressen 10.0.0.1-4 erreichbar.

Konfiguration mit Ansible

Statt jeden Rechner einzeln als Docker-Host einzurichten, verwenden wir im Weiteren das Konfigurationsmanagement-Tool Ansible, da bis zu einem bestimmten Punkt das Setup der Nodes identisch ist. Als Erstes legen wir eine "Inventory"-Datei an, die die von Ansible gemanagten Nodes enthält, etwa unter dem Namen "hosts", mit folgendem Inhalt:

[cluster]
10.0.0.1
10.0.0.2
10.0.0.3
10.0.0.4

Jetzt können Sie die grundlegende Funktion von Ansible testen:

$ ansible -i hosts cluster -m ping -u pi -k

Hinter den Kulissen loggt sich Ansible dabei als User "pi" auf dem Node ein, wozu es beim Aufruf das Passwort abfragt ("-k"). Funktioniert alles, bekommen Sie für jeden Host eine Meldung ähnlich der folgenden zu sehen:

10.0.0.2 | SUCCESS => {
      "changed": false,
      "ping": "pong"
}

Um die Verwendung von Ansible und die Installation von Software zu vereinfachen, legen wir einen Benutzer mit SSH-Key an, der sich ohne Passwort einloggen darf. Außerdem wird er der Gruppe "docker" hinzugefügt, damit er später Docker ohne Root-Rechte bedienen darf. Diese Änderungen nehmen wir über das Playbook in Listing 1 vor. Um das Passwort direkt im Playbook angeben zu können (nicht unbedingt eine Security-Best-Practice), wird der Filter "password_hash" verwendet, der die Installation des Python-Moduls "passlib" voraussetzt (sudo -H pip install passlib). Abgespielt wird das Playbook mit dem Kommando "ansible-playbook", das über den Schalter "-b" (become) per sudo Root-Rechte erlangt. Ein Passwort ist für sudo nicht nötig, da die Default-Konfiguration von Raspbian dafür kein Passwort verlangt.

$ ansible-playbook -i hosts -bk -u pi setup-users.yml

Listing 1: setup-users.yml

---
- hosts: cluster
   remote_user: pi
   become: yes
   become_method: sudo
   tasks:
   - name: create new user
     user:
         name: oliver
         shell: /bin/bash
         groups: docker,sudo
         update_password: always
         password: "{{ 'pi' | password_hash('sha512') }}"
     - name: add key to authorized_keys
       authorized_key:
         user: oliver
         state: present
         key: "{{ lookup('file', '/home/oliver/.ssh/id_rsa.pub') }}"

Wer einen Cluster betreibt, muss sicherstellen, dass die Nodes zeitlich synchronisiert sind. Dazu verwenden wir NTP, das wir per Ansible und mit einer Community-Rolle konfigurieren, die Ansible Galaxy im lokalen Verzeichnis installiert:

$ ansible-galaxy install -p roles geerlingguy.ntp

Das entsprechende Playbook, das auch die Timezone auf "Europe/Berlin" setzt, ist in Listing 2 zu sehen.

Listing 2: setup-time-ntp.yml

---
- hosts: cluster
   remote_user: oliver
   become: true
   tasks:
      - name: set timezone
        timezone
           name: Europe/Berlin
   roles
      - geerlingguy.ntp

Viele Softwarepakete setzen außerdem eine funktionierende Zuordnung von Hostnamen zu IP-Adressen voraus. Da wir aber keinen ausgewachsenen Nameserver betreiben wollen, pflegen wir per Ansible ein Hosts-File, das wir auf alle beteiligten Knoten verteilen.

Listing 3: hosts

[cluster]
10.0.0.1 hostname=pi1
10.0.0.2 hostname=pi2
10.0.0.3 hostname=pi3
10.0.0.4 hostname=pi4

Auch dies lässt sich per Ansible einigermaßen einfach umsetzen. Wir erweitern dazu das Inventory-File um eine Variable, in der der Hostname steht, den der Node bekommen soll (Listing 3). Eine Schleife im Ansible-Playbook stellt sicher, dass alle am Cluster beteiligten Knoten auch im Hosts-File landen. Nützlich ist darüber hinaus die Playbook-Anweisung "linein", die sicherstellt, dass die entsprechende Zeile in einer Datei vorhanden ist, und nur dann eine Änderung vornimmt, wenn sie fehlt. Das Playbook in Listing 4 spielen sie so ab:

$ ansible-playbook -i hosts -bK set-hostnames.yml

Wenn Sie sich jetzt auf einem Node einloggen, sollten Sie in der Datei "/etc/hosts" alle beteiligten Rechner sehen und von jedem Knoten per Hostnamen alle anderen erreichen können.

ARM ist nicht gleich ARM

Bevor Sie nun anfangen, auf dem Cluster Docker zu betreiben, noch ein paar Worte zur Plattform, die hier entsteht. Da auf den Raspberry-Rechnern ein ARM-Prozessor verbaut ist und es sich bei der Container-Technologie nicht um Virtualisierung handelt, kann in den Containern auch nur Software laufen, die für ARM-Prozessoren geschrieben ist.

Während in den letzten Jahren die Intel- und AMD-Plattformen mit "AMD64" respektive "x86-64" mehr oder weniger zu einem einzigen Binärformat konvergiert sind, gibt es bei ARM-Rechnern noch ein relativ großes Chaos. So koexistieren in der ARM-Welt weiterhin 32- und 64-Bit-Prozessoren mit zum Teil unterschiedlicher Nomenklatur und unterschiedlichen ABIs (Application Binary Interface). Darüber hinaus gibt es die ARMv7-Architektur (32 Bit) mit einer in Hardware realisierten Floating-Point-Einheit. Letztere hat sich, zumindest für die Embedded Boards wie Raspberry, zu einer Art Standard entwickelt, auf die sich die meisten Linux-Distributionen wie Debian konzentrieren, während der Serverbereich mit 64 Bit und ARMv8 noch relativ exotisch ist. Gängige Kurzbezeichnungen dafür sind etwa "armv7", "armhf" oder "armhfp".

Listing 4: set-hostnames.yml

---
- hosts: cluster
   become: true
   gather_facts: False
   tasks:
        - name: set hostname for all nodes in cluster
           hostname: name: "{{hostname}}"
        -name: add all cluster nodes to the hosts file on each host
         lineinfile: dest=/etc/hosts regexp='{{hostvars[item].hostname}}' line="{{ item }} {{hostvars[item].hostname}}" state=present with_items: "{{groups.cluster}}"

Unzureichender Architektur-Support in Docker

Docker-Images besitzen zwar mittlerweile in den Metadaten ein Feld für die Architektur, das aber von den Tools ignoriert wird, sodass es dem Anwender überlassen bleibt, auf die richtige Architektur zu achten. Eine weitere Organisationsstruktur, um die passenden Images zu finden, sind die sogenannten Organizations in der Docker Registry, die aber nach Belieben verwendet oder zweckentfremdet werden. Typischerweise sind sie dafür gedacht, dass etwa eine Firma ihre Images innerhalb dieses Namespaces ablegt.

Weil aber eine andere Ordungsstruktur fehlt, gibt es im Docker Hub eben auch "Organizations", in denen Images für die ARM-Architektur zu finden sind, beispielsweise "armv7" und "armhf". Zuletzt gibt es auch noch Anwender, die die Architektur im Image-Namen codieren, etwa "easypi/alpine-arm" oder etwa die schon erwähnte Hypriot mit "hypriot/rpi-mysql" oder "hypriot/armhf-hello-world".

Das bislang letzte und offizielle Wort dazu ist jedenfalls, dass "armhf" zugunsten der spezifischeren Organizations "arm32v7" und "arm32v6" abgelöst wird (siehe [3]). "arm32v6" ist dabei für Raspberry Pi 1, Raspberry Pi Zero gedacht, während der Raspberry Pi 3 (der im Übrigen einen 64-Bit-Prozessor besitzt) die Images aus "arm32v7" verwenden kann. Künftig sollen alle Tools, insbesondere diejenigen, die das Open-Container-Initiative-Format implementieren, automatisch die richtige Architektur wählen – sofern sie in der entsprechenden Image-Registry am richtigen Ort bereitsteht.

Installation von Docker

Das Playbook, das in Listing 5 zu sehen ist, installiert zunächst den Docker-GPG-Key, um die Echtheit der Pakete zu verifizieren, fügt das Docker-Repository hinzu und installiert die Docker-Engine. Aufgerufen wird es analog zu den bisherigen Playbooks, allerdings verwendet es den vorher angelegten User und benötigt deshalb kein Login-Passwort mehr. Dafür wird aber jetzt ein Sudo-Passwort benötigt, das "ansible-playbook" mit dem Schalter "-K" abfragt. Wer sich das Leben vereinfachen will, kann in der Sudo-Konfiguration eintragen, dass der Benutzer auch ohne Passwort Sudo-Rechte bekommt. Wie das Playbook zeigt, verwendet Debian das Architekturkürzel "armhf" für unsere Plattform.

Bild 2:  Über den Befehl "docker info" alle wichtigen Daten anzeigen lassen.
Bild 2:  Über den Befehl "docker info" alle wichtigen Daten anzeigen lassen.


Listing 5: setup-docker.yml

---
- hosts: cluster
   remote_user: oliver
   become: true
   gather_facts: False
   tasks:
        - apt_key: url=https://download.docker.com/linux/raspbian/gpg
        - apt_repository:
              repo: deb [arch=armhf] https://apt.dockerproject.org/repo raspbian-jessie main
        - apt:
              name: docker-engine
              update_cache: yes

Ein Aufruf von docker info sollte jetzt die Informationen zur installierten Docker-Engine ausgeben. Ansible kann auch dazu verwendet werden, sogenannte Ad-Hoc-Kommandos auszuführen, um etwa Docker auf allen Knoten zu testen:

$ ansible -i hosts cluster -a "docker version"

Mit dem folgenden Befehl testen Sie, ob sich Container ausführen lassen:

$ docker run arm32v7/hello-world

Damit können Sie auf allen Knoten fertige oder eigene Docker-Images ausführen. Wie Sie diese mit Kubernetes orchestrieren, verrät die nächste Folge dieses Workshops. Eine Einkaufsliste für den Cluster ist im Kasten "Bauteile" zu finden.

Bauteile

Für unseren Raspberry-Cluster haben wir die folgenden Bauteile verwendet:- vier Raspberry Pi 3- vier Micro-USB-Karten mit 32 Gbyte- Ethernet-Switch mit fünf Ports- vier kurze Ethernet-Kabel- USB-Hub mit sechs Ports- vier Micro-USB-Kabel (Stromversorgung)- WLAN-Router

Fazit

Mit relativ geringem finanziellen Einsatz lässt sich ein echter Cluster aus Raspberry-Rechnern aufbauen, der sich für Experimente mit verteilter Software eignet. Da Docker auf ARM-Prozessoren offiziell unterstützt wird, kann ein solcher Raspberry-Cluster insbesondere als Basis für den Umgang mit Containern dienen.

Als Orchestrierungs-Tools für den ARM-Container-Cluster kommt das mitgelieferte Swarm in Frage oder auch der "Marktführer" Kubernetes, der ebenfalls für ARM zur Verfügung steht. Die Installation und Bedienung von Kubernetes beschreibt die nächste Folge dieses Workshops. Wegen der fehlenden Kompatibilität zu x86-Prozessoren ist man allerdings bei der Software-Auswahl gegenüber den Intel/AMD-Plattformen eingeschränkt.

Aus dem IT-Administrator Magazin Ausgabe 12/2017: Infrastruktur physisch & virtuell, Seite 48-51
Hier geht es zum zweiten Teil von Cluster mit Raspberry Pi - Im Gleichklang

Ähnliche Beiträge

Cluster-Features in vSphere 7 (1)

VMware stattet vSphere mit einer Reihe von Enterprise-Funktionen aus, die nur im Cluster verfügbar sind. Dazu zählen Hochverfügbarkeit, Lastausgleich, vMotion-Kompatibilitäts-Modus und Object Storage. Im ersten Teil der Serie erklären wir, was vSphere 7 überhaupt unter einem Cluster versteht und wie Sie mithilfe des Schnellstart-Assistenten die Erstkonfiguration vornehmen.