Aufbau einer Docker Private Registry

Und wieder ein kleiner Artikel über Docker & Co. – es sieht so aus, als ließe mich das Thema noch nicht los. Zugegebenermaßen ist es ja auch spannend und bietet momentan sehr viel Raum für Experimente. Denn nachdem man die ersten Docker-Images gebaut hat, stellt sich unweigerlich die Frage, wie sich diese transportieren und auf dem Zielsystem installieren lassen.

Wie immer gibt es mehrere Optionen. Die einfachste lautet, Docker Hub zu nutzen. Dort bietet Docker selbst die Infrastruktur, um Docker-Images zu verwalten, d.h. Images hochzuladen und auf beliebigen Servern zu verwenden. Und nicht zuletzt ist Docker Hub auch der Ort, an dem bereits laut eigenen Angaben über 100000 Images vorliegen, die allesamt zur freien Verwendung zur Verfügung stehen. Das Ganze hat nur einen Nachteil – mit dem kostenlosen Account lässt sich nur ein privates Repository anlegen, alle anderen müssen öffentlich verfügbar sein. Zwar ist das kleinste Kontingent, in dem immerhin fünf private Repositories inbegriffen sind, auch nicht teuer, aber auch dann liegen die Daten nicht mehr auf eigenen Servern und noch dazu in einem anderen Land, vielleicht sollen die Images auch innerhalb eines geschlossenen Netzwerkes liegen usw., insgesamt gibt es durchaus Gründe, die den Aufbau einer privaten Registry rechtfertigen. Meiner war gar nicht so kompliziert, ich wollte letztlich wissen, wie es geht, und zeigen, dass es geht.

Docker bietet dafür bereits ein fertiges Docker-Image an und stellt zudem eine Beschreibung bereit, wie eine private Registry zum Laufen gebracht werden kann. Der Code steht als Open Source unter der Apache Lizenz 2.0 zur Verfügung,

An dieser Stelle soll nicht verschwiegen werden, dass es weitere Anbieter von privaten Registries für Docker-Images gibt. Beispielsweise lassen sich Images bei Tutum anlegen, in der Beta-Phase ist dies bis dato kostenlos, und mit Hilfe der Tutum-Infrastruktur deployen. Da Tutum jedoch inzwischen (zu) Docker gehört, dürfte es über kurz oder lang eine Verschmelzung der Registry-Server geben. Das ist natürlich nur eine Vermutung, aber Docker wird hier sicherlich alle Möglichkeiten zur Optimierung und Konsolidierung nutzen. Des Weiteren bietet Google im Rahmen der Google Container Engine eine Container Registry an, die sich zwar auch außerhalb der Google Infrastruktur nutzen lässt, aber aufgrund anderer Authentifizierungsmechanismen nur innerhalb dieser wirklich praktisch ist. Ein weiterer kommerzieller Anbieter ist Quay, der wiederum zu CoreOS gehört.

Aber zurück zur Installation einer eigenen privaten Registry. Zunächst ein paar Gedanken zur Vorbereitung. Beim Namen der Registry habe ich mich am Vorbild von Docker Hub orientiert, also soll der Server-Name hub.kuerbis.org lauten. Da die eigene Registry nicht nur von localhost aus erreichbar sein soll, muss ein SSL-Zertifikat vorliegen, ansonsten müsste der jeweilige Docker-Host, der die Registry verwendet, mit der Option „–insecure-registry myregistry.example.com:5000“ gestartet werden. Das ist jedoch keine gute Idee, denn laut Docker schließen sich Authentifizierung per Basis-Authentication und die „insecure registry“-Option aus. Die günstigsten SSL-Zertifikate lassen sich für wenige Dollar pro Jahr erstehen, beispielsweise bei SSLs.com. Das sieht zwar jetzt nach Werbung aus, aber rein persönlich bin ich mit dem Anbieter sehr zufrieden und kann es wiederum nicht wirklich nachvollziehen, weshalb es hierzulande nichts Vergleichbares zu geben scheint. Allerdings geht es auch noch günstiger, dazu seien die Begriffe StartSSL und WoSign genannt, die SSL-Zertifikate kostenlos anbieten. In jedem Fall sollte die Erstellung des Schlüssels nicht auf dem Server des Zertifikat-Anbieters erfolgen, sondern auf einem eigenen Server. Ansonsten hätte der Anbieter auch den privaten Schlüssel, was im Allgemeinen keine gute Idee ist, auch wenn man sehr vertrauensvoll ist. Eine Schritt-für-Schritt-Anleitung zur Erstellung eines CSR, oder auch „Antrag zur Signierung eines Zertifikats“ findet sich z.B. auf den Hilfe-Seiten von Ubuntu. Noch eine Anmerkung – zwar begrüße ich die Initiative von Let’s Encrypt sehr, auch hier werden kostenlose Zertifikate bereit gestellt. Die bisherige Variante zur Zertifikatsgenerierung wird hingegen als einfach dargestellt, erscheint mir aber extrem umständlich und setzt zudem die Verwendung der Let’s-Encrypt-Software voraus, die wiederum die Web-Server-Einstellungen auf dem Server ändert… Hier bleibt zu hoffen, dass in einer späteren Fassung auch normale CSR verarbeitet werden können. Wenn nun der DNS noch entsprechend konfiguriert wurde, dass der entsprechende Server für die Registry auch unter dem gewählten Namen erreichbar ist, kann es auch schon los gehen.

Zunächst werden Verzeichnisse für die Daten der Registry und die SSL-Zertifikate erstellt. Die einfachste Variante nutzt für die Daten das Filesystem des Hosts, jedoch werden auch andere Storage-Backends bereit gestellt, mehr dazu in der genannten Dokumentation.


Anschließend werden die Zertifikatsdateien, d.h. Key und Zertifikat kopiert:


Die Dateinamen sind hier willkürlich, sie lassen sich beim Start des Docker-Containers als Parameter übergeben.

Zuletzt wird das Registry-Image genutzt:


Damit ist die erste, einfachste Variante der privaten Registry bereits lauffähig. Aber Vorsicht – noch ist hier keine Authentifizierung vonnöten, so dass, vorausgesetzt, der Server-Name und Port-Nummer seien bekannt, jeder der eigentlich als privaten Registry gedachten Instanz bedienen kann. Daher ist dies nur das Beispiel für einen ersten Test, denn hier wird Port 5000 nach außen hin freigegeben, was in der späteren Variante weg fällt. Insbesondere sollte nach den ersten Tests auch das Daten-Verzeichnis /srv/docker/registry/data/ geleert werden.

Die private Registry kann nun zunächst getestet werden, bzw. handelt es sich dabei um die grundlegenden Kommandos. Zunächst wird ein Docker-Image mit einem Tag versehen, der auf den Registry-Server verweist. Im Beispiel liegt ein Image namens geschkenet/nginx vor, was ins Repository gespeichert werden soll. Daher zunächst taggen:


Anschließend per push-Befehl übertragen:


Das Image kann nun von jedem Server aus verwendet werden, es lässt sich holen mittels:


Das sieht doch bereits sehr gut aus! Natürlich lassen sich auch andere Tags vergeben wie „latest“, falls man mehrere Versionen eines Images vorhalten möchte. Das Image liegt nun lokal auf dem Docker-Host vor und kann wie üblich verwendet werden, zur Prüfung einfach docker images aufrufen.

Das war simpel, jedoch liegt die Registry sehr ungeschützt vor, und das soll verhindert werden. Zwar bietet das Registry-Image auch selbst die Möglichkeit, Basic-Authentication einzurichten, aber die bessere Variante erschien mir die Verwendung eines Nginx-Proxies. Diese Art der Konfiguration eröffnet weitere Möglichkeiten, so dient Nginx als Authentifizierungsschicht mit all seinen Modulen, etwa könnte eine Single-Sign-On-Lösung (SSO) realisiert werden. Ich habe zunächst eine einfache Variante realisiert, bei der die Zugangsdaten in einer Datei vorliegen.

Zunächst wird der Registry-Container gestoppt und gelöscht:


Anschließend neu gestartet, diesmal jedoch ohne die Portfreigabe nach aussen, vielmehr findet die Verknüpfung von Nginx-Proxy und Registry-Container über einen Link statt, denn selbstverständlich wird Nginx auch innerhalb eines Containers betrieben.


Für Nginx nehme ich mein vor kurzem erstelltes und sehr kleines Image, natürlich ist das keine zwingende Voraussetzung, die bereits zitierte Anleitung verwendet das offizielle Nginx-Image dafür.

Auch Nginx muss die Zertifikatsdateien kennen, daher werden sie ihm zugänglich gemacht. Als Speicherort für alles, was der Nginx-Container benötigt, verwende ich /srv/docker/nginx.


Als nächstes werden die Zugänge konfiguriert. Dazu dient klassischerweise das dem Apache-httpd-Server beiliegende Kommando htpasswd. Falls es nicht vorliegt, kann z.B. folgendes Shellskript verwendet werden, siehe dazu auch den FAQ-Eintrag bei Nginx. Das Skript:


Die Erzeugung der Passwort-Datei:


Die erzeugte Datei wird ebenfalls in das conf.d-Verzeichnis kopiert.

Zuletzt die eigentliche Konfiguration für den virtuellen Host bzw. Proxy. Inspiriert von der Konfiguration unter Ubuntu Linux werden die einzelnen Sites im Verzeichnis /srv/docker/nginx/sites-enabled konfiguriert. Beim Start liest Nginx die darin befindlichen Dateien ein. Für die Registry sieht die Nginx-Konfiguration wie folgt us:


Dazu ein paar Anmerkungen:

  • Die Konfiguration des Proxy wird wie bei Nginx üblich durchgeführt. Dabei wird der Registry-Container unter dem Link-Namen „registry“ zugänglich gemacht, so dass der Server einfach „registry:5000“ lautet.
  • Nginx ist so eingerichtet, dass er auf Port 80 und 443 lauscht. Auch auf Port 80 soll SSL eingeschaltet sein, letztlich dient dieser Port nur zur internen Weiterleitung, d.h. von außen wird die Registry wie später ersichtlich auf dem Port 81 erreichbar sein. Dies ist ebenso willkürlich, im Beispiel von Docker wird etwa 5043 verwendet.
  • Das Document Root Verzeichnis ist im Beispiel eingerichtet, so dass der Nutzer, falls er sich mit dem Browser auf den Server verirren sollte, eine minimale HTML-Datei mit dem Hostnamen erhält.
  • Die restlichen Einstellungen sind weitestgehend analog zum Beispiel, so werden die Zertifikatsdateien eingebunden, ebenso wie die Passwort-Datei htpasswd.
  • Zum Schluss wird mittels proxy_pass der unter upstream eingerichtete Proxy angesprochen. Dabei wird ebenso https und nicht nur http verwendet, da der Registry-Container die Zertifikate nutzt und somit https zur Verfügung stellt. Analog zur Konfiguration aus den Docker-Quellen wäre diese Konfiguration auch ohne durchgängige Verschlüsselung möglich, so dass nur zwischen Client und Nginx-Proxy verschlüsselt würde.
  • Die Einstellungen unter proxy_set_header führten bei mir zu Fehlern, daher sind sie einfach auskommentiert.

Nun muss der Nginx-Container nur noch gestartet werden:


Das war es auch schon. Der Port 81 des Hosts wird auf den Port 80 des Nginx-Containers weiter geleitet, von dem wiederum der Registry-Container angesprochen wird. Der Link namens „registry“ sorgt dafür, dass der Registry-Container unter eben diesem Hostnamen erreichbar ist.

Um die neue private Registry zu erreichen, ist zunächst ein Login notwendig:


Anschließend können die bereits geschilderten Kommandos docker push oder docker pull wie gewohnt ausgeführt werden.

Das war eine kurze Übersicht über den Aufbau einer privaten Registry für Docker-Images. Natürlich erhebt diese keinerlei Anspruch auf Vollständigkeit, davon abgesehen ist es auch nur ein Beispiel einer lauffähigen Konfiguration. Themen wie Load-Balancing, Skalierung oder weiter gehende Authentifizierungsmechanismen wurden nicht dargestellt, aber vielleicht beschäftige ich mich später noch damit. Die weiteren Schritte gehen in Richtung automatisiertes Deployment, was zunächst die Automatisierung der Erstellung der Docker-Images und des „Pushens“ auf die Registry voraussetzt. Aber auch das bietet genug Platz für weitere Beiträge…

 

Tags:

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.