3.9.2015 - Server-Festplatten

Geschwindigkeitsvergleich einiger externer Speichermethoden auf dem Server.

Natürlich sind real-world-examples weniger reproduzierbar und somit weniger aussagekräftig, als standardisierte Benchmarks. Daher der disclaimer: Diese Beispiele gelten nur für mein System und nur für den Zeitpunkt, zu dem sie ausgeführt wurden.

  • System: Fujitsu Primergy TX 150 S2
  • Prozessor: Intel(R) Xeon(R) CPU E3-1265L v3 @ 2.50GHz
  • RAM: 32 GB
  • OS: Ubuntu 14.04 LTS Server x64

Dreimal derselbe Test: eine grosse (10GB) Datei kopieren. Jedesmal eine andere, um cache-Effekte zu minimieren. Quellverzeichnis liegt auf einer internen SATA-Harddisk

1. Kopie auf eine zweite interne SATA Harddisk auf demselben Computer

Kopie auf SATA

2. Kopie auf USB-Platten

(Genauer gesagt: Ein ZFS virtual device, bestehend aus zwei gespiegelten 4-TB-Platten an USB-3-Ports) mit einem transparent komprimierenden Dateisystem (zfs set compression=lz4)

Kopie auf USB

3. Kopie auf NAS

(6 TB Buffalo LinkStation LS 421 an Gigabyte-Lan)

Kopie auf NAS

Fazit

  • Nur beim NAS wird die mögliche Schnittstellengeschwindigkeit ausgenutzt. Sowohl bei SATA als auch bei USB 3 sind allein die Platten der limitierende Faktor.

  • Weder Kompression noch Spiegelung scheinen bei ZFS allzu viel Zeit zu kosten

  • Wieso eigentlich interne Platten? Externe Platten an USB sind einfacher zu handeln, problemlos im laufenden Betrieb ersetzbar und zumindest in diesem Szenario genau so schnell.

  • Dieser Test sollte die maximale Transfergeschwindigkeit messen. Kopie vieler kleiner Dateien statt einer einzelnen Grossen würde die Kopfpositionierung der Platten und die Transfergeschwindigkeit einbeziehen.

4.8.2015 - ZFS

Überblick und Geschichte

ZFS (ursprünglich ‘Zettabyte Filesystem’) ist ein interessantes alternatives Dateisystem. Es wurde ursprünglich von Sun Microsystems ebtwickelt, dann mit OpenSolaris unter einer OpenSource Lizenz freigegeben und letztlich zusammen mit Sun von Oracle gekauft. Wegen dieser Wirren mit nicht zuletzt auch lizenzrechtlichen Unklarheiten, blieb dem System der grosse Erfolg versagt. Apple hatte zumindest Lese-Support schon in OS-X 10.5 eingebaut und wollte, so munkelte man, mittelfristig HFS+ durch ZFS ersetzen. Mit dem Kauf von Sun und der Weiterentwicklung von ZFS unter einer proprietären Lizenz von Oracle wurden diese Pläne aber wieder begraben.

Inzwischen entstand mit OpenZFS ein freier Port, dessen Lizenz eindeutig ist, und der von einer Reihe von Firmen gepusht wird.Man darf die Zukunft von ZFS somit jetzt als gesichert betrachten und sich näher damit befassen.

Installation

  • MacOS-X: Download installer von o3x
  • Linux: ZFS on Linux (Vorsicht: Nicht “zfs-fuse” installieren, das ist kein “echtes” ZFS, sondern simuliert es quasi auf einem fuse-Dateisystem. Damit ist es wesentlich langsamer und fehleranfälliger, als das echte ZFS.) Spezielle Anweisungen für Ubuntu sind hier, für Arch-Linux hier

Besondere Eigenschaften

Vieles von dem, was für ZFS “erfunden” wurde, fand Einzug in das moderne Linux-Filesystem btrfs. ZFS hat allerdings den Vorteil, stabil und ausgereift zu sein, während viele Teile von btrfs immer noch als Beta oder experimentell gelten. Ausserdem hat ZFS den für mich ausschlaggebenden Vorteil, dass es nicht nur für Linux, sondern z.B. auch für MacOS-X existiert.

Was ist es?

ZFS ist eine Kombination aus LVM (Logical Volume Manager) und Dateisystem: Es kann also physikalische Datenträger verwalten und darauf Dateisysteme organisieren. Bei traditionellen Systemen sind diese Funktionen strikt getrennt (Ein fdisk-Programm erstellt Partitionen, ein RAID-Manager organisiert Schreib- und Lesezugriffe und ein Dateisystem erledigt die “höheren” Funktionen wie Dateinamen und Verzeichnisse). Diese Trennung hat den Nachteil, dass das Dateisystem beispielsweise nichts davon “weiss”, welche Teile des physikalischen Datenträgers überhaupt belegt sind. Das macht Backup- und Restorefunktionen oder das Ersetzen eines RAID-Datenträgers aufwändiger.

Aufbau und Funktionsprinzip

Das Beste an ZFS ist, dass es im Gegensatz zu LVM und btrfs sehr leicht zu verstehen ist. Es gibt eigentlich nur zwei Befehlsgruppen: zpool und zfs. Zpool organisiert Datenpools. Ein solcher Pool kann aus Harddisks oder Partitionen von Harddisks oder sogar Dateien bestehen. Durch einfaches Hinzufügen weiterer Datenträger zu einem bestehenden Pool kann dessen Kapazität erhöht werden, und zwar völlig transparent: Anwendungsprogramme “merken” nichts davon, wieviele Festplatten den Bereich bilden, mit dem sie arbeiten.

In einem solchen Pool kann zfs Dateisysteme erstellen. Das ist optional. Ein root-Dateisystem wird automatisch beim Erzeugen des Pools erstellt. Weitere Dateisysteme entsprechen in etwa den Partitionen traditioneller Dateisysteme. Im Gegensatz zu solchen Partitionen sind ZFS-Dateisysteme aber flexibel: Jede kann beliebig wachsen, so lange, bis entweder eine einzustellende Limite erreicht oder der Pool voll ist. Jedes Dateisystem kann völlig transparent Dinge wie Kompression oder Deduplikation beinhalten.

Der vor allem bei grösseren Systemen wegen seiner Dauer berüchtigte Dateisystem-Check läuft bei ZFS im Hintergrund und ohne, dass man die Partition dafür aushängen muss.

Last but not least sind snapshots unter ZFS sehr einfach zu erstellen. Ein Snapshot ist eine Momentaufnahme des Dateisystems, welches in Sekundenbruchteilen erstellt wird. Einen solchen Snapshot kann man mit “send” auf ein Backupmedium schicken, und zu jedem beliebigen späteren Zeitpunkt dorthin zurückgehen.

Tour d’horizon

Hier ein grundsätzlicher Einstieg (unter MacOSX; für Linux muss man die Devicenamen entsprechend anpassen. Die Namen der Laufwerke bekommt man unter MacOS mit diskutil list, unter Linux z.B. mit sudo lsblk).

 sudo zpool create -o ashift=12 zfs1 disk1

Dies erstellt einen pool namens zfs1 aus der Festplatte ‘/dev/disk1’. Wenn man stattdessen nur die zweite Partition der Platte hätte verwenden wollen, hätte man geschrieben:

 sudo zpool create -o ahsift=12 zfs1 /dev/disk1s2

Man könnte auch zwei ähnlich grosse Festplatten für einen gespiegelten Pool verwenden:

 sudo zpool create -o ashift=12 zfs1 mirror disk2 disk3

Dies im Gegensatz zu:

 sudo zpool create -o ashift=12 zfs1 disk2 disk3

In diesem Fall würde zfs einen Pool erstellen, welcher die Grösse von disk2 und disk3 addiert enthält, während das erste Beispiel einen Pool mit der Grösse der kleineren von disk2 und disk3 erstellt, welcher aber durch Spiegelung vor Datenverlust bei Diskausfall gesichert ist.

Man kann die beiden Optionen auch kombinieren:

 sudo zpool create -o ashift=12 zfs1 mirror disk2 disk3 mirror disk4 disk5

Hier haben wir einen Pool aus zwei aneinandergehängten Spiegelpaaren erstellt, also insgesamt 4 Disks mit der Kapazität von 2.

Der seltsame Parameter -o ashift=12 dient dazu, dass ZFS eine Sektorgrösse von 4096 Bytes (1«12, daher a’shift’, == 2^12 Bytes) annimmt. Das ist für die meisten aktuellen Festplatten korrekt. Leider melden die meisten Platten aus Kompatibilitätsgründen immer noch 512 Byte ans Betriebssystem, und das ist es dann, was ZFS defaultmässig verwenden würde. Und leider kann man diesen ashift-Parameter nur zum Zeitpunkt der Erstellung des Pools angeben. Wenn man es vergisst, passiert aber nichts wirklich Schlimmes. ZFS braucht dann weniger internen Speicher, arbeitet dafür aber langsamer. Bei heutigen Computern und Datenmengen ist man eher gewillt, mehr Speicher zu spendieren, um Geschwindigkeit zu gewinnen. Daher: Im Zweifel ‘ashift=12’.

Ich glaube, damit ist das Prinzip klar und ich streife nur kurz, dass man statt Spiegeln auch Raids erstellen kann. ZFS erstellt immer eine Variation von Raid-5, die hier raidz heisst. Etwas verwirrenderweise benötigt ein raidz1 3 Disks, von denen eine ausfallen darf, ein raidz2 4 Disks, von denen zwei ausfallen können etc.

Ziemlich ausführliche Infos zu allen möglichen Optionen gibt der Befehl man zpool, sowohl unter Linux als auch unter Mac.

Nachträgliches Erweitern

Das Ganze wäre natürlich nicht viel wert, wenn man sich beim Einrichten schon ganz sicher sein müsste, welche Harddisk-Konfiguration man für alle Zeiten braucht. Daher kann man jede Konfiguration nachträglich erweitern (aber leider dann nicht mehr ohne Weiteres schrumpfen).

sudo zpool add zfs1 disk6s2
sudo zpool add zfs1 mirror disk6 disk7

erweitert den Zpool um das angegebene Device resp. das angegebene Devicepaar.

sudo zpool attach zfs1 disk6 disk7

macht aus disk6 einen mirror, wenn es noch keiner war. Wenn es schon ein mirror war, macht es einen 3-fach mirror daraus. In jedem Fall werden sofort automatisch alle Daten von disk6 im Hintergrund auf disk7 kopiert (“resilvering”).

Das Ganze kann man noch ergänzen, indem man hot spares und schreib- oder lese-Cache-Devices zu zpools hinzufügt. Beispielsweise kann man schnelle SSDs als Cache-Devices konfigurieren und hat dann so etwas wie Apples FusionDrive.

Filesystem check

Man kann die Korrektheit der Daten checken mit:

sudo zpool scrub zfs1

Das Checken und ggf. Reparieren geschieht online und im Hintergrund. Man kann also normal weiterarbeiten. Fehler sind bei ZFS seltener, als bei anderen Dateisystemen, da wichtige Informationen redundant angelegt werden (auch ohne mirror) und mit Checksummen gesichert sind. Im Fall von Mirror- oder Raidz-Pools werden allfällige trotzdem aufgetretene Fehler jeweils direkt korrigiert.

Das obligate Grrrrrr!

Leider ist ZFS in aktuellen MacOS Versionen nicht mehr richtig integriert. Beim Anstöpseln eines ZFS-Devices meckert der Mac öfters, dass er das Laufwerk nicht lesen kann. Das muss man einfach ignorieren (Bloss nicht “initialisieren”). ZFS bindet das Gerät trotzdem ein. Wenn nicht, muss man mit

sudo zpool import zfs1

nachhelfen.

Und um ein ZFS Device abzuhängen, genügt es auch nicht, es im Finder auszuwerfen. Vielmehr muss man

sudo zpool export zfs1

eingeben.

Bei manchen Betriebssystemen (prominentes Beispiel: Neuere Ubuntu-Versionen mit upstart) ist der Bootprozess parallelisiert, so dass die Reihenfolge, in der Datenträger eingebunden werden, nicht mehr garantiert ist. Was beim vorigen Start /dev/sdc war, könnte beim nächsten Start /dev/sdd werden. Dann findet ZFS möglicherweise nicht mehr die richtigen Datenträger für seine Pools. Um das zu vermeiden, muss man es anweisen, die (unveränderliche) Datenträger-UUID anstelle des Devicenamens zu verwenden. Man macht das entweder beim Erstellen eines Pools mit

sudo zpool create -d /dev/disk/by-id zfs1 sdc

Oder, wenn man den Pool schon ohne diese Option erstellt hat mit

sudo export zfs1
sudo import -d /dev/disk/by-id zfs1

Man muss das nur einmal machen; Zfs merkt es sich für spätere Systemstarts.

Wie auch immer: ZFS ist sowieso nicht als Dateisystem für häufige Datenträgerwechsel gedacht, sondern eher für grosse, permanente Datensammlungen.

Datesysteme / Filesystems

Zpool richtet immer automatisch gleich ein Root-Dateisystem in jedem Pool ein. In unserem obigen Beispiel würde das zfs1 heissen, also genauso wie der pool. Man kann diesem Dateisystem Eigenschaften verpassen, zum Beispiel:

sudo zfs set compression=lz4 zfs1

Eine vollständige Liste aller möglichen Eigenschaften liefert sudo zfs get all. Wenn man eine feinere Aufteilung möchte, kann man ene Art Partitionen, nämlich sub-filesystems einrichten.

sudo zfs create -o mountpoint=/Volumes/hans -o quota=5G -o casesensitivity=insensitive zfs1/hans
sudo chown hans /Volumes/hans
sudo zfs create -o mountpoint=/Volumes/lisa -o quota=25G -o compression=off zfs1/lisa
sudo chown Lisa /Volumes/lisa

erstellt zwei Partitionen an entsprechenden mountpoints für Hans und Lisa. Die Partition von Hans kann maximal 5 GB gross werden, die von Lisa 25 GB (Immer vorausgesetzt, dass im Pool zfs1 noch so viel Platz frei ist). Ausserdem wird das von Hans Windows-ähnlich nicht zwischen Gross- und Kleinschreibung unterscheiden, während das von Lisa Unix-typisch case sensitive ist (vererbt von übergeordneten Dateisystem zfs1). Bei Lisa wird dagegen die Datenkompression abgeschaltet, die wir im übergeordneten System zfs1 vorhin eingeschaltet haben, und die zfs1/hans darum geerbt hat.

Quotas können auch user-bezogen oder group-bezogen sein, und Dateisysteme können auch tiefer geschachtelt werden. Lisa könnte beispielsweise zfs1/lisa/mp3 erstellen, und in diesem Dateisystem die Kompression abgeschaltet lassen, hingegen in zfs1/lisa/documents set compression=on setzen. ZFS ist da extrem vielseitig.

Snapshots

Alles ist pefekt eingerichtet, und wir wollen uns diesen Zustand des Systems merken.

# Snapshot von zfs1 erstellen, aber nicht von zfs1/hans und zfs1/lisa
sudo zfs snapshot zfs1@ersterSnapshot

# Snapshot von zfs1 und allen darunterliegenden Dateisystemen erstellen
sudo zfs -r snapshot zfs1@ersterSnapshot

# Snapshot von allen Dateisystemen unter zfs1/lisa erstellen
sudo zfs -r snapshot zfs1/lisa@ersterSnapshot

Eine Liste aller Snapshots bekommt man mit

sudo zfs list -t snapshot

Wenn man das System später auf den Zustand eines früheren Snapshots zurücksetzen will, macht man einfach:

sudo zfs rollback zfs1@letzteSicherung

Snapshots betrachten

Noch besser als zfs list ist folgendes: Im Wurzelverzeichnis des ZFS Filesystems, also da, wo es gemountet ist, gibt man ein:

cd .zfs

Ja, man kann in dieses Verzeichnis wechseln, obwohl es von ls -la nicht angezeigt wird. Genauer gesagt: ZFS bindet es dynamisch ein, sobald man es aufsucht. Darin befindet sich unter Anderem ein Verzeichnis “snapshot”, und darin, säuberlich sortiert, sind alle Snapshots. Man kann auf diese Weise jede einzelne Datei jedes einzelnen Snapshots aufsuchen und nötigenfalls herauskopieren. (Ändern kann man nichts, der Snapshot wird read-only gemountet.) Sobald man das .zfs Verzeichnis wieder verlässt, wird es automatisch abgetrennt.

Snapshot senden

Angenommen, Hans möchte sein komplettes Dateisystem auf den Computer ‘hanspc.neuerArbeitsplatz.org’ umziehen.

sudo zfs send zfs1@hans | ssh hanspc.neuerArbeitsplatz.org zfs recv prstaff@hans

Das kann natürlich einige Zeit dauern, wenn viele Daten zu bewegen sind. ZFS kann nicht zaubern. Wenn man einen entfernten Computer als Backup-Device verwendet, kann man auch inkrementelle Snapshot-Differenzen senden:

sudo zfs send -i zfs1@montag zfs1@dienstag| ssh backup-pc zfsbackups/aktuell

Dies sendet nur die Änderungen des dienstag-snapshots seit dem montag-snapshot.

Auch hier liefert man zfs mehr Informationen über alle Befehle und deren Optionen.

Deduplikation

Ein gar nicht mal so seltenes Szenario: Man hat einige VirtualMachines oder einige Docker-Container gespeichert. Das sind ziemlich grosse Dateien, die aber dennoch Bereiche haben, in denen sie sich ähneln: Oft wird man dasselbe Betriebssystem und dieselben Standardprogramme in mehreren Containern installiert haben. Wäre es da nicht schön, wenn solche Bereiche nur einmal gespeichert würden?

Hier kommt Deduplikation ins Spiel. Dabei hält ZFS für jeden einzelnen Block im pool einen Hash im Speicher. Wenn es nun den Auftrag erhält, einen neuen Block zu schreiben, prüft es zunächst, ob der Hash dieses Blocks mit einem der vorhandenen Hashs übereinstimmt. Wenn ja, speichert es den Block nicht, sondern nur eine Referenz auf den existierenden Block. Wenn man viele ähnliche Dateien hat, kann man damit ganz erheblich Festplatenspeicher sparen. Allerdings zu einem Preis: Um alle Hashes im Speicher zu halten, ist entsprechend viel RAM nötig. Als Faustregel sollte man pro Terabyte Plattenkapazität 1 bis 1.5 GB RAM haben. Und um nachzuschauen, ob ein Block schon existiert, muss beim Schreiben jeder einzelne Blockhash mit jedem bereits existierenden Hash verglichen werden. Es ist nicht schwer, sich vorzustellen, dass dies einiges an Prozessorzeit kostet. Dazu kommen Sicherheitsüberlegungen: Wenn ein Block, der in zehn VMs benötigt wird, wegen eines Festplattenfehlers ausfällt, dann sind zehn VMs kaputt und nicht nur eine, wie es ohne Deduplikation der Fall wäre.

Leider benutzt Deduplikation auch immer den ganzen Pool. Wenn man es nur für ein Filesystem des Pools aktiviert, dann werden zwar nur die Dateien dieses Filesystems dedupliziert, aber es werden dennoch Hashes sämtlicher Blöcke des gesamten Pools erstellt und verwaltet.

Also zusammengefasst: Ein cooles Feature, das man aber nur einschalten sollte, wenn man wirklich viele “ähnliche” Dateien hat, und wenn der Computer genug RAM und einen ausreichend schnellen Prozessor hat. Die Entscheidung, wann das wirklich sinnvoll ist, ist schwierig zu treffen und wird durchaus auch kontrovers diskutiert. In den meisten Fällen sollte man Deduplikation wohl eher deaktiviert lassen (was auch das Standardverhalten von ZFS ist). Insbesondere, weil Festplattenpreise schneller fallen, als RAM-Kosten, und weil es oft einfacher ist, mehr Harddisks anzuschliessen, als den Speicher zu erweitern. Eine recht ausführliche Behandlung des Themas ist hier zu finden.

Freigabe übers Netzwerk

Wenn man mit Windows- und Mac Computern auf ein ZFS Dateisystem zugreifen möchte, genügt folgendes:

sudo set sharesmb=on zfs1

Das ist alles. Sofort wird ‘zfs1’ übers Netz freigegeben. Auch hier kann man selbstverständlich den Parameter für darunterliegende Dateisysteme unterschiedlich definieren:

sudo set sharesmb=off zfs1/lisa

Wenn man nicht (nur) mit dem SMB-Protokoll freigeben möchte, sondern (auch) mit dem (in Unix gebräuchlicheren) NFS-Protokoll, dann kann man

sudo set sharenfs=on zfs1

angeben.

Herausfinden, welche physikalischen Laufwerke zu einem zpool gehören

Zumindest mir geht es manchmal so, dass ich nach einiger Zeit nicht mehr weiss, welche physischen Disks ich welchem zpool zugeordnet hatte.

zpool status -v

hilft leider nur bedingt, da hier zwar die Disks angezeigt werden, aber mit einem Namen, den man zum Beispiel mit

lsblk -o name,uuid,partuuid,ptuuid

nicht findet.

Abhilfe bietet folgender Befehl:

udevadm info --query=property --name=/dev/sdX | grep WWN

wobei man für X nacheinander alle Festplatten einsetzt. Damit erhält man den WWN (World Wide Name) der jeweiligen Platte, und das ist der, den zpool anzeigt.

Schluss und weiterführende Literatur

ZFS wurde für sehr grosse Dateisysteme entwickelt. Daher auch der ursprüngliche Name “Zettabyte File System”. Ein Zettabyte sind eine Milliarde Terabytes. Eine schnelle Festplatte, die 200MB/s schreiben kann, braucht 5 Sekunden, um ein GB vollzuschreiben und 5000 Sekunden (=etwas weniger als eineinhalb Stunden) für ein Terabyte. Eine solche Platte bräuchte mehr als 158000 Jahre, um ein Zettabyte zu füllen. ZFS kann mit Systemgrössen bis 256 Billionen Zettabytes umgehen, und jedes Verzeichnis kann bis zu 256 Billiarden Einträge enthalten.

Somit scheint der Anspruch der ZFS-Entwickler gerechtfertigt, ein Dateisystem gebaut zu haben, das “für immer” genügt. Natürlich hat diese Auslegung auf gigantische Datenmengen auch Nachteile. Zum Beispiel verwendet ZFS durchgehend 128 Bit Pointer, was für heutige (64 Bit) Prozessoren nicht die optimale Datengrösse ist. Dies führt einerseits zu erhöhtem Platzbedarf für Verwaltungsstrukturen und andererseits auch zu Geschwindigkeitsnachteilen. ZFS braucht relativ viel Speicher und einen einigermassen modernen Prozessor. (Da ZFS aber auch in einigen NAS Geräten eingesetzt wird, scheint der Anspruch an die Hardware doch auch nicht unerträglich hoch zu sein. Limitierend ist ohnehin die Schreibgeschwindigkeit auf die Platte selbst, nicht die Rechenarbeit des Dateisystems)

Die Auslegung von ZFS für grosse Systeme führt auch dazu, dass es eher in der Domäne der Systemadministratoren, als der Hobbyisten und Privatnutzer ist. Recherchen in Newsgroups sind darum für ZFS Einsteiger oft nicht sehr ergiebig. Oft findet man nur die Fragen, auf die niemand geantwortet hat, oder Antworten des Typs “wenn du das fragen musst, ist zfs sowieso nichts für dich”.

Aber wie ich oben gezeigt habe, hat ZFS durchaus auch Eigenschaften, die für kleinere, privat betreute Netzwerke sinnvoll sein können. Und manchmal hat man ja auch einfach Lust, mal etwas Neues auszuprobieren, und es geht auch nicht immer im absolut lebenswichtige oder systemkritische Datenbestände.

Eine erste Quelle für Informationen sind wie gesagt die beiden Kommandos man zfs und man zpool. Sehr viel Information gibt es auf der OpenZFS und der OpenZFS on X Website, allerdings in einer Struktur, die es dem Einsteiger schwer macht, Antworten zu finden.

Sehr gut strukturiert und ausführlich, ausserdem mit vielen Beispielen anschaulich gemacht, ist Oracles ZFS-Manual Hier geht es allerdings um das proprietäre Oracle ZFS und nicht um OpenZFS. Aber die Unterschiede sind derzeit noch klein genug, dass das Handbuch auch für OpenZFS geeignet ist.

Eine gute Zusammenfassung finde ich auch diesen Artikel: https://blog.wyraz.de/linux/das-optimale-zfs-dateisystem-teil-1-erzeugen-eines-pools-mit-optionaler-ssd-beschleunigung/

21.7.2015 - Adressierung

Teil 1 Teil 2 Teil 3 Teil 4

Docker zum Vierten

Im vorherigen Teil habe ich gezeigt, wie man mit docker-compose eine ganze Gruppe von Containern auf einmal erstellen und starten kann.

Am Ende hatten wir drei Webservices, die man mit http://server.xy:5050, http://server.xy:4040 und https://server.xy erreichen konnte.

Das ist machbar, aber nicht besonders elegant, und schon gar nicht intuitiv: Wer kann sich schon merken, welcher Service jetzt 4040 war und welcher 5050? Vor allem, wenn später noch mehr Dienste dazu kommen, wird es leicht unübersichtlich. Ganz davon abgesehen, dass man, wenn man die Dienste von aussen erreichbar machen will, immer mehr Löcher in die Firewall bohren muss.

Wir müssen also noch einen Schritt weiter gehen. Git soll über http://git.server.xy erreichbar sein, calibre über http://books.server.xy und webelexis über https://termine.server.xy.

Damit das klappt, muss man erst mal sicher stellen, dass diese drei Subdomains via DNS sichtbar sind. Im Fall eines nicht-öffenlichen Servers muss man die Namen in /etc/hosts (Linux) bzw. /private/etc/hosts (Mac), bzw irgendwo anders (Windows) bei allen Clients, die auf den Server zugreifen sollen, eintragen. Beispielsweise mit folgenden Zeilen, die auf ein VPN verweisen:

10.72.82.1    server.xy 
10.72.82.1    termine.server.xy
10.72.82.1    git.server.xy
10.72.82.1    books.server.xy

Im Fall eines öffentlich erreichbaren Servers muss man die entsprechenden Einträge beim Nameserver machen (A records). Viele Hosting-Provider nehmen einem diese Arbeit auch ab und ermöglichen in ihrer Administrationsoberfläche die Erstellung von Subdomains. In diesem Fall erledigen sie dann auch gleich den Eintrag im DNS. (Problematischer dürfte eher sein, einen Hosting-Provider zu finden, der Docker anbietet - somit landet man doch wieder beim virtual root server, bei dem man alles selber machen muss.)

Wunderbar, jetzt ist ein und derselbe Server unter vier verschiedenen Namen erreichbar. Oder sogar fünf, wenn man www.server.xy mit rechnet. Aber wie kommt nun eine Anfrage zum richtigen Docker-Container? Diesen Job erledigt ein sogenannter reverse proxy. Ein reverse Proxy nimmt alle Anfragen von aussen entgegen und verteilt sie auf passende interne Server. Was ‘passend’ für welchen Server ist, muss man in der Konfiguration des Proxys definieren.

Bekanntes Beispiel für einen reverse proxy ist ‘nginx’ (Genauer gesagt: nginx ist vieles, unter anderem auch ein reverse proxy). Leider ist die Konfiguration von nginx ein wenig tricky. Aber glücklicherweise haben andere Docker-User ähnliche Wünsche und Probleme und haben längst passende Lösungen entwickelt. Wir ändern unsere docker-compose.yml wie folgt:

    proxy:
      image: jwilder/nginx-proxy
      ports:
        - 80:80
      volumes:
        - /var/run/docker.sock:/tmp/docker.sock:ro
    
    owncloud:
      image: rgwch/owncloud-client:latest
      volumes: 
       - /srv/ebooks:/srv/owncloud
      env_file: ../ebenv.txt
    
    calibre:
       image: rgwch/calibre-server:1.0.2
       volumes: 
         - /srv/ebooks:/srv/calibre
       environment:
         VIRTUAL_HOST: books.meinserver.xy
    
    gitserver:
       image: rgwch/git-server:2.0.2
       volumes:
         - /srv/repositories:/opt/git
       environment:
         VIRTUAL_HOST: git.meinserver.xy
    
    
    webelexis:
       build: docker-webelexis-server
       volumes: 
         - /home/gerry/dockerfiles/cfglocal.json:/home/webelexis/cfglocal.json:ro
         - /home/gerry/dockerfiles/wlxks.jks:/home/webelexis/ks.jks:ro
       environment:
         VERSION: 1.0.0
         VIRTUAL_HOST: termine.meinserver.xy
         VIRTUAL_PROTO: https
       ports:
         - 443:2015

Also mit anderen Worten: Wir starten als erstes jwilder/nginx-proxy und ändern dann die Konfiguration der anderen Dienste so, das wir eine environment-variable namens VIRTUAL_HOST mit dem Namen der gewünschten Subdomain (die dem Eintrag in /etc/hosts bzw. DNS entsprechen muss) ergänzen. Wenn Verbindung über TLS erwünscht ist, muss man zusätzlich VIRTUAL_PROTO: https ergänzen (und natürlich die zur Subdomain passenden Zertifikate bereitstellen)

Das ist eigentlich alles. Im Bruchteil der Zeit, die man zum Aufsetzen eines konventionellen Systems mit diesen Diensten gebraucht hätte, kann man mit Docker und Docker-compose eine ganze Server-Farm aufziehen. Und das Beste: Man kann das alles problemlos in Ruhe auf dem heimischen Computer entwickeln und testen und dann ohne Änderungen auf ein produktives System transferieren.

Das Tüpfelchen auf dem i

Wenn unser eBook-Server öffentlich zugänglich ist, dann stört uns das vermutlich nicht so (Man kann ja nur lesen, nichts kaputt machen), aber möglicherweise verstossen wir damit gegen das Urheberrecht. Wir benötigen daher eine Grenze, die klar macht, dass dies ein privater Server für einen geschlossenen Benutzerkreis ist. Besonders bombensicher muss diese Absicherung nicht sein, es ist die deutliche Grenze, die juristisch relevant ist.

Das macht man so:

  • Erstellen Sie ein Unterverzeichnis auth (der Name kann beliebig sein)
  • Wechseln Sie in dieses Verzeichnis und geben Sie folgendes ein: htpasswd -c books.meinserver.xy username (wobei Sie für username den gewünschten Zugangsnamen einsetzen). Sie werden dann noch nach einem Passwort gefragt, dass Sie Ihrem Lesezirkel anvertrauen müssen.

Dann ergänzen Sie die docker-compose.yml wie folgt:

     proxy:
          image: jwilder/nginx-proxy
          ports:
            - 80:80
          volumes:
            - /var/run/docker.sock:/tmp/docker.sock:ro
            - /home/gerry/dockerfiles/auth:/etc/nginx/htpasswd

und starten die docker-composition neu. Nun wird jeder Zugriff auf books.meinserver.xy mit einem Passwort gesichert. Sie können beliebig viele dieser Sicherungen anlegen (einfach ein File mit dem VIRTUAL_HOST Namen des zu schützenden Containers um auth Verzeichnis erstellen. Und natürlich kann jede Datei in auth beliebig viele user-Einträge haben.

das Programm htpasswd, das benötigt wird, um die Password-Hashes zu erstellen, ist Teil der apache2-utils. Installieren Sie diese, falls htpasswd nicht gefunden wird. (Sie brauchen nicht den “ganzen” Apache zu installieren).

19.7.2015 - Docker compose

Teil 1 Teil 2 Teil 3 Teil 4

Weiter geht’s mit Docker.

Wenn man sich erst mal an die ‘Docker way of life’ gewöhnt hat, wird man feststellen, dass man immer weniger Dienste direkt auf dem Server installiert. Stattdessen wird man für jeden Dienst einen Docker container verwenden, weil das eben viel einfacher in der Handhabung ist: Es gibt nie Kompatibilitätsprobleme, man muss nie lang über Konfigurationen nachdenken, und last but not least: Migration auf neue oder ganz andere Hardware ist trivial: Es ist einem Docker container schlicht egal, ob er auf Mac, Linux, Windows oder in der Amazon-ec2 cloud läuft. Er verhält sich immer genau gleich. Und es ist ihm auch egal, ob ein anderer Container, mit dem er kooperiert, sich auf demselben Computer oder auf einem anderen Kontinent befindet.

Doch einen Nachteil gibt es: Wenn man eine Menge Docker container laufen hat, muss man die bei einem Neustart des Servers alle korrekt wieder erstellen und/oder hochfahren, was manchmal schwierig sein kann, wenn das letzte Mal eine Weile her ist, und was auch eine Menge Tipparbeit an der Konsole bedeutet.

Hier kommt docker-compose ins Spiel. Damit kann man ein System aus beliebig vielen Containern mit einem simplen sudo docker-compose up & hochfahren und mit sudo docker-compose stop stoppen.

Dazu muss man sich einmal die Mühe machen, eine Steuerdatei (docker-compose.yml) zu erstellen. Die sieht zum Beispiel so aus:

    owncloud:
      image: rgwch/owncloud-client:latest
      volumes:
       - /srv/ebooks:/srv/owncloud
      env_file: ../ebenv.txt
    
    calibre:
       image: rgwch/calibre-server:latest
       volumes:
         - /srv/ebooks:/srv/calibre
       ports:
         - 4040:8080
    
    gitserver:
       build: docker-git
       volumes:
         - /srv/repositories:/opt/git
       ports:
         - 5050:80
    
    
    webelexis:
       build: docker-webelexis-server
       volumes:
         - /home/gerry/dockerfiles/cfglocal.json:/home/webelexis/cfglocal.json
         - /home/gerry/dockerfiles/wlxks.jks:/home/webelexis/ks.jks
       ports:
         - 443:2015
       environment:
         VERSION: 1.0.0

Das ist im Prinzip ja selbsterklärend. Hier werden zwei Container aus images und zwei aus build-instructions (Dockerfiles in den angegebenen Verzeichnissen) erstellt und anschliessend konfiguriert und hochgefahren. Anschliessend kann man mit http://<adresse>:4040 auf den eBook-Server, mit http://<adresse>:5050 auf den GIT-Server und mit https://<adresse> auf den Webelexis-Server zugreifen.

Docker-compose ist dabei auch schlau genug, um bereits existierende Container wiederzuverwenden, anstatt sie bei jedem Start neu zu erstellen. Neu erstellt wird nur, wenn es neuere Versionen gibt, oder wenn sich (im Fall von build:) Etwas an den build-instructions geändert hat.

Wenn man sudo docker-compose up ohne & am Schluss eingibt, dann bleibt der Prozess im Vordergrund und kann mit einmal CTRL-C sauber und mit zweimal CTRL_C “hart” gestoppt werden. Wenn man ihn mit & in den Hintergrund schickt, kann man ihn mit sudo docker-compose stop sauber und mit kill hart stoppen.

Wenn man alle Container der Komposition auf einmal löschen will, genügt ein simples sudo docker-compose rm. Man muss sich also nicht mehr mit jeder Menge angry_einsteins und condescent_newtons herumschlagen, um alle loszuwerden.