Ich lade Sie ein, die Zusammenfassung des Vortrags von Alexander Sigachev von Inventos über den "Entwicklungs- und Testprozess mit Docker + GitLab CI" zu lesen.
Diejenigen, die gerade erst beginnen, den Entwicklungs- und Testprozess auf Basis von Docker + GitLab CI einzuführen, stellen häufig grundlegende Fragen. Wo fange ich an? Wie organisiere ich das? Wie teste ich es?
Dieser Vortrag ist besonders, da er strukturiert über den Entwicklungs- und Testprozess unter Verwendung von Docker und GitLab CI berichtet. Der Vortrag stammt aus dem Jahr 2017. Ich denke, dass man aus diesem Vortrag Grundlagen, Methoden, Ideen und Erfahrungen sammeln kann.

Wer interessiert ist, ist herzlich eingeladen, weiterzulesen.
Mein Name ist Alexander Sigachev. Ich arbeite bei Inventos. Ich werde von meinen Erfahrungen mit Docker berichten und wie wir es schrittweise in unsere Projekte im Unternehmen integrieren.
Thema des Vortrags: Entwicklungsprozess mit Docker und GitLab CI.

Das ist mein zweiter Vortrag über Docker. Zum Zeitpunkt des ersten Vortrags nutzten wir Docker nur in der Entwicklungsphase auf den Maschinen der Entwickler. Die Anzahl der Mitarbeiter, die Docker verwendeten, lag bei etwa 2-3 Personen. Nach und nach haben wir Erfahrungen gesammelt und sind ein Stück weitergekommen. Link zu unserem .
Was wird in diesem Bericht behandelt? Wir teilen unsere Erfahrungen darüber, welche Stolpersteine wir gesammelt haben und wie wir Probleme gelöst haben. Es war nicht immer einfach, aber es hat uns ermöglicht, weiter voranzukommen.
Unser Motto: Docker alles, was in unsere Hände kommt.

Welche Probleme lösen wir?
Wenn in einem Unternehmen mehrere Teams existieren, ist der Programmierer eine gefragte Ressource. Es gibt Phasen, in denen Programmierer aus einem Projekt herausgezogen und für eine gewisse Zeit in ein anderes Projekt gegeben werden.
Damit ein Programmierer schnell einsteigen kann, muss er den Quellcode des Projekts herunterladen und so schnell wie möglich eine Umgebung starten, die es ihm ermöglicht, die Aufgaben dieses Projekts zu lösen.
Normalerweise gibt es, wenn man von Grund auf neu anfängt, wenig Dokumentation im Projekt. Die Informationen zur Einrichtung haben nur die alten Hasen. Die Mitarbeiter richten ihren Arbeitsplatz normalerweise in ein oder zwei Tagen selbst ein. Um diesen Prozess zu beschleunigen, haben wir Docker verwendet.
Ein weiterer Grund sind die Standardisierungen der Einstellungen in der Entwicklung. Meiner Erfahrung nach zeigen Entwickler immer Initiative. In jedem fünften Fall wird eine benutzerdefinierte Domain wie vasya.dev eingeführt. Daneben sitzt Nachbar Petja, der die Domain petya.dev hat. Sie entwickeln eine Website oder ein Systemkomponente unter Verwendung dieses Domainnamens.
Wenn das System größer wird und diese Domainnamen in die Konfigurationen gelangen, entstehen Konflikte zwischen den Entwicklungsumgebungen und der Pfad der Website wird überschrieben.
Das Gleiche passiert mit den Datenbankeinstellungen. Manche kümmern sich nicht um die Sicherheit und arbeiten mit einem leeren root-Passwort. Bei anderen wurde während der MySQL-Installation ein Passwort gefordert und das Passwort war einfach 123. Es kommt häufig vor, dass die Datenbankkonfiguration ständig je nach Commit des Entwicklers geändert wurde. Jemand hat die Konfiguration angepasst, jemand anderer nicht. Es gab Tricks, bei denen wir eine Art Testkonfiguration ins .gitignore Jeder Entwickler muss eine Datenbank einrichten, was den Startprozess kompliziert. Man darf nicht vergessen, dass auch die Datenbank berücksichtigt werden muss. Sie muss initialisiert, das Passwort eingegeben, ein Benutzer angelegt und eine Tabelle erstellt werden, und so weiter.
Ein weiteres Problem sind die unterschiedlichen Versionen der Bibliotheken. Häufig arbeitet ein Entwickler an verschiedenen Projekten. Es gibt ein Legacy-Projekt, das vor fünf Jahren (seit 2017 – Anm. d. Red.) begonnen wurde und mit MySQL 5.5 gestartet ist. Daneben gibt es moderne Projekte, in denen wir versuchen, bereits aktuellere Versionen von MySQL einzuführen, etwa 5.7 oder höher (im Jahr 2017 – Anm. d. Red.).
Wer mit MySQL arbeitet, weiß, dass diese Bibliotheken Abhängigkeiten mit sich bringen. Es ist ziemlich problematisch, zwei Datenbanken gleichzeitig zu betreiben. Zumindest ist es schwierig, ältere Clients mit einer neuen Datenbank zu verbinden. Dies führt seinerseits zu mehreren Problemen.
Das nächste Problem ist, dass der Entwickler auf seiner lokalen Maschine arbeitet, wobei er lokale Ressourcen, lokale Dateien und lokalen RAM verwendet. Alle Interaktionen während der Entwicklung der Lösungen finden innerhalb der Rahmenbedingungen statt, dass alles auf einer Maschine funktioniert. Ein Beispiel ist, wenn wir in der Produktion drei Backend-Server haben und der Entwickler Dateien im Stammverzeichnis speichert, von dem aus Nginx die Dateien zur Beantwortung von Anfragen abruft. Wenn solcher Code in die Produktion gelangt, ist die Datei nur auf einem der drei Server vorhanden.
Derzeit entwickelt sich der Bereich der Microservices. Wenn wir unsere großen Anwendungen in kleinere, miteinander kommunizierende Komponenten aufteilen. Dies ermöglicht es, für spezifische Aufgaben den passenden Technologie-Stack auszuwählen. Außerdem erlaubt es, die Arbeit und Verantwortlichkeiten zwischen den Entwicklern aufzuteilen.
Der Frontend-Entwickler, der mit JS arbeitet, hat kaum Einfluss auf das Backend. Der Backend-Entwickler hingegen entwickelt in unserem Fall mit Ruby on Rails, was das Frontend nicht beeinträchtigt. Die Interaktion erfolgt über APIs.
Als Bonus konnten wir mit Docker die Ressourcen in der Staging-Umgebung optimieren. Jedes Projekt erforderte aufgrund seiner Spezifität bestimmte Einstellungen. Physisch mussten entweder separate virtuelle Server bereitgestellt und individuell konfiguriert werden oder wir mussten eine variable Umgebung teilen, was dazu führte, dass die Projekte je nach Version der Bibliotheken Einfluss aufeinander hatten.

Werkzeuge. Was verwenden wir?
- Direkt Docker selbst. Im Dockerfile werden die Abhängigkeiten einer Anwendung beschrieben.
- Docker-Compose ist das Bindeglied, das unsere verschiedenen Docker-Anwendungen vereint.
- GitLab verwenden wir zur Speicherung des Quellcodes.
- GitLab-CI nutzen wir für die systematische Integration.

Der Bericht besteht aus zwei Teilen.
Der erste Teil erläutert, wie wir Docker auf den Maschinen der Entwickler gestartet haben.
Der zweite Teil beschreibt, wie wir mit GitLab interagieren, wie wir Tests starten und wie wir auf Staging deployen.

Docker — dies ist eine Technologie, die es ermöglicht, erforderliche Komponenten mithilfe eines deklarativen Ansatzes zu beschreiben. Hier sehen wir ein Beispiel für ein Dockerfile. Wir erklären, dass wir von dem offiziellen Docker-Image Ruby:2.3.0 erben. Es enthält die installierte Ruby-Version 2.3. Wir installieren die erforderlichen Build-Bibliotheken sowie NodeJS. Zudem beschreiben wir, dass wir ein Verzeichnis erstellen. /appWir setzen das Verzeichnis app als Arbeitsverzeichnis. In dieses Verzeichnis legen wir die notwendigen minimalen Dateien Gemfile und Gemfile.lock. Anschließend führen wir den Build von Projekten durch, die die Abhängigkeiten für dieses Image installieren. Wir geben an, dass der Container bereit sein wird, auf dem externen Port 3000 zuzuhören. Der letzte Befehl ist der Befehl, der unsere Anwendung tatsächlich startet. Wenn wir das Projektstartkommando ausführen, versucht die Anwendung, sich auszuführen und den angegebenen Befehl zu starten.

Dies ist ein einfaches Beispiel für eine Docker-Compose-Datei. In diesem Fall zeigen wir, wie zwei Container miteinander verbunden sind. Dies betrifft direkt den Datenbankservice und den Webservice. Unsere Webanwendungen benötigen in den meisten Fällen eine Datenbank als Backend zur Speicherung von Daten. Da wir MySQL verwenden, ist dies ein Beispiel mit MySQL – aber es steht nichts im Wege, eine andere Datenbank wie PostgreSQL oder Redis zu nutzen.
Wir nehmen das offizielle Image MySQL 5.7.14 ohne Änderungen von Docker Hub. Das Image, das für unsere Webanwendung verantwortlich ist, erstellen wir aus dem aktuellen Verzeichnis. Es wird während des ersten Starts für uns gebaut. Danach wird der Befehl ausgeführt, den wir hier angeben. Wenn wir zurückblicken, sehen wir, dass der Startbefehl über Puma definiert wurde. Puma ist ein Service, der in Ruby geschrieben wurde. Im zweiten Fall überschreiben wir diesen Befehl. Dieser kann abhängig von unseren Bedürfnissen oder Aufgaben beliebig sein.
Außerdem beschreiben wir, dass der Port auf unserer Host-Maschine vom Entwickler von 3000 auf den Containerport 3000 weitergeleitet werden muss. Dies geschieht automatisch mithilfe von iptables und dem integrierten Mechanismus von Docker.
Der Entwickler kann wie zuvor auf jede verfügbare IP-Adresse zugreifen, beispielsweise auf die lokale 127.0.0.1 oder die externe IP-Adresse des Servers.
Die letzte Zeile besagt, dass der Web-Container von dem DB-Container abhängt. Wenn wir den Web-Container starten, wird Docker Compose zuerst die Datenbank starten. Nach dem Start der Datenbank (beziehungsweise nach dem Start des Containers! Die Bereitschaft der DB ist damit nicht garantiert) wird unsere Anwendung, unser Backend, gestartet.
Dies hilft, Fehler zu vermeiden, wenn die Datenbank nicht läuft, und spart Ressourcen, wenn wir den Datenbankcontainer stoppen, wodurch Ressourcen für andere Projekte frei werden.

Was uns die Verwendung von Docker für die Datenbank im Projekt bringt. Wir fixieren für alle Entwickler die MySQL-Version. Dies hilft, gewisse Fehler zu vermeiden, die durch abweichende Versionen entstehen können, wenn sich die Syntax, Konfiguration oder Standardwerte ändern. Es ermöglicht uns, gemeinsame Hostnamen für die Datenbank, Login und Passwort anzugeben. Dadurch vermeiden wir die Verwirrung und Konflikte bei den Konfigurationsdateien, die zuvor entstanden sind.
Wir haben die Möglichkeit, eine optimierte Konfiguration für die Entwicklungsumgebung zu verwenden, die sich vom Standard unterscheidet. MySQL ist standardmäßig für schwache Maschinen konfiguriert und seine Leistung ist direkt nach der Installation sehr gering.

Docker ermöglicht die Nutzung der gewünschten Version von Python, Ruby, NodeJS oder PHP. Dadurch entfällt die Notwendigkeit, einen Versionsmanager zu verwenden. Früher haben wir für Ruby ein rpm-Paket verwendet, das es erlaubte, die Version je nach Projekt zu ändern. Außerdem ermöglicht es uns, dank des Docker-Containers den Code reibungslos zu migrieren und zusammen mit seinen Abhängigkeiten versionieren. Wir haben daher kein Problem, die Version sowohl des Interpreters als auch des Codes zu verstehen. Um die Version zu aktualisieren, müssen wir den alten Container löschen und den neuen Container starten. Wenn etwas schiefgeht, können wir den neuen Container löschen und den alten Container wiederherstellen.
Nach dem Erstellen des Abbilds werden die Container sowohl in der Entwicklungs- als auch in der Produktionsumgebung identisch sein. Dies ist besonders wichtig für große Installationen.
Im Frontend verwenden wir JavaScript und NodeJS.
Das aktuelle Projekt basiert auf ReactJS. Der Entwickler hat alle Container gestartet und arbeitete mit Hot-Reload.
Im nächsten Schritt wird die Aufgabe zum Zusammenstellen von JavaScript gestartet, und der als statisch zusammengestellte Code wird über Nginx bereitgestellt, um Ressourcen zu sparen.

Hier ist das Schema unseres letzten Projekts.
Welche Herausforderungen haben wir gelöst? Wir mussten ein System aufbauen, das mit mobilen Geräten interagiert. Diese erhalten Daten. Eine der Möglichkeiten besteht darin, Push-Benachrichtigungen an dieses Gerät zu senden.
Was haben wir dafür getan?
Wir haben die Anwendung in folgende Komponenten unterteilt: das Admin-Panel in JS, das Backend, das über ein REST-Interface unter Ruby on Rails arbeitet. Das Backend interagiert mit der Datenbank. Die Ergebnisse werden an den Client zurückgegeben. Das Admin-Panel kommuniziert mit dem Backend und der Datenbank über das REST-Interface.
Außerdem hatten wir die Notwendigkeit, Push-Benachrichtigungen zu versenden. Zuvor hatten wir ein Projekt, in dem ein Mechanismus umgesetzt wurde, der für die Zustellung von Benachrichtigungen auf mobilen Plattformen verantwortlich ist.
Wir haben folgendes Schema entwickelt: Der Operator im Browser interagiert mit dem Admin-Panel, das Admin-Panel interagiert mit dem Backend, und es wird die Aufgabe gesetzt, Push-Benachrichtigungen zu senden.
Push-Benachrichtigungen interagieren mit einer anderen Komponente, die auf NodeJS implementiert ist.
Warteschlangen werden aufgebaut und die Benachrichtigungen werden entsprechend ihrem Mechanismus weiter gesendet.
Hier sind zwei Datenbanken dargestellt. Aktuell nutzen wir mithilfe von Docker zwei unabhängige Datenbanken, die nicht miteinander verbunden sind. Abgesehen von ihrem gemeinsamen virtuellen Netzwerk werden die physischen Daten in verschiedenen Verzeichnissen auf dem Entwicklungsrechner gespeichert.

Das Gleiche, aber in Zahlen. Hier ist die Wiederverwendung von Code wichtig.
Wenn wir zuvor über die Wiederverwendung von Code in Form von Bibliotheken gesprochen haben, wird in diesem Beispiel unser Service für Push-Benachrichtigungen als vollständiger Server wiederverwendet. Er stellt eine API zur Verfügung, mit der unsere neue Entwicklung interagiert.
Zu diesem Zeitpunkt verwendeten wir NodeJS Version 4. Jetzt (im Jahr 2017 – Anmerkung des Herausgebers) verwenden wir in neuen Entwicklungen Version 7 von NodeJS. Es gibt keine Probleme, in neuen Komponenten neue Bibliotheksversionen einzubinden.
Falls nötig, kann ein Refactoring durchgeführt und die NodeJS-Version im Push-Benachrichtigungsdienst aktualisiert werden.
Wenn wir die API-Kompatibilität gewährleisten können, wäre es möglich, ihn in anderen Projekten zu ersetzen, die zuvor verwendet wurden.

Was benötigt man, um Docker hinzuzufügen? Wir fügen unseren Docker-Repository eine Dockerfile hinzu, die die notwendigen Abhängigkeiten beschreibt. In diesem Beispiel sind die Komponenten nach Logik aufgeteilt. Dies ist das Minimalset für einen Backend-Entwickler.
Beim Erstellen eines neuen Projekts erstellen wir eine Dockerfile und beschreiben das benötigte Ökosystem (Python, Ruby, NodeJS). In docker-compose wird die nötige Abhängigkeit — die Datenbank — beschrieben. Wir geben an, dass eine Datenbank dieser Version erforderlich ist, um Daten dort zu speichern.
Wir nutzen einen separaten dritten Container mit nginx, um statische Inhalte zu bedienen. Es besteht die Möglichkeit, Bilder hochzuladen. Der Backend-Server speichert diese in einem bereits vorbereiteten Volume, welches ebenfalls in den Container mit nginx eingebunden ist, der die statischen Inhalte bereitstellt.
Um die Konfiguration von nginx und MySQL zu speichern, haben wir einen Docker-Ordner hinzugefügt, in dem wir die notwendigen Konfigurationen aufbewahren. Wenn ein Entwickler das Repository mit git klont, erhält er bereits ein Projekt, das für die lokale Entwicklung bereit ist. Es stellt sich nicht die Frage, welcher Port oder welche Einstellungen anzuwenden sind.

Darüber hinaus haben wir mehrere Komponenten: Admin, Info-API, Push-Benachrichtigungen.
Um das Ganze zum Laufen zu bringen, haben wir ein weiteres Repository erstellt, das wir dockerized-app genannt haben. Momentan nutzen wir mehrere Repositories für jede Komponente. Diese unterscheiden sich lediglich logisch — in GitLab sieht das aus wie ein Ordner, während es auf dem Rechner des Entwicklers als Ordner für ein spezielles Projekt erscheint. Eine Stufe darunter liegen die Komponenten, die kombiniert werden.

Das ist ein Beispiel für den Inhalt von dockerized-app. Hier bringen wir auch das Docker-Verzeichnis unter, in dem wir die Konfigurationen bereitstellen, die für das Zusammenspiel aller Komponenten erforderlich sind. Es gibt eine README.md, in der kurz erklärt wird, wie man das Projekt startet.
Hier haben wir zwei docker-compose-Dateien eingesetzt. Das wurde gemacht, um die Möglichkeit zu haben, schrittweise zu starten. Wenn der Entwickler mit dem Kern arbeitet und keine Push-Benachrichtigungen benötigt, startet er einfach die docker-compose-Datei, wodurch Ressourcen gespart werden.
Bei Bedarf einer Integration mit Push-Benachrichtigungen wird die docker-compose.yaml und die docker-compose-push.yaml gestartet.
Da docker-compose.yaml und docker-compose-push.yaml im Ordner liegen, wird automatisch ein einheitliches virtuelles Netzwerk erstellt.

Beschreibung der Komponenten. Dies ist eine ausführlichere Datei, die für das Zusammenstellen von Komponenten verantwortlich ist. Was ist hier bemerkenswert? Hier führen wir den Load-Balancer-Komponenten ein.
Dies ist ein fertiges Docker-Image, in dem Nginx und eine Anwendung ausgeführt werden, die den Docker-Socket überwacht. Dynamisch wird die Nginx-Konfiguration bei Ein- und Ausschalten der Container neu generiert. Der Umgang mit den Komponenten wird über Subdomain-Namen verteilt.
Für die Entwicklungsumgebung verwenden wir die Domain .dev — api.informer.dev. Anwendungen mit der Domain .dev sind auf der lokalen Maschine des Entwicklers verfügbar.
Danach werden die Konfigurationen an jedes Projekt übergeben und alle Projekte werden gleichzeitig gestartet.

Grafisch dargestellt, ist der Client unser Browser oder ein anderes Tool, mit dem wir Anfragen an den Load-Balancer stellen.
Der Load-Balancer bestimmt anhand des Domainnamens, auf welchen Container zugegriffen werden muss.
Das kann Nginx sein, das die JS-Admin-Oberfläche bereitstellt. Es kann Nginx sein, das API oder statische Dateien bereitstellt, die von Nginx zum Hochladen von Bildern ausgeliefert werden.
Im Diagramm ist zu sehen, dass die Container in einem virtuellen Netzwerk verbunden sind und hinter einem Proxy versteckt werden.
Auf dem Entwicklerrechner kann man den Container über die IP-Adresse ansprechen, aber wir wenden das grundsätzlich nicht an. Ein direkter Zugriff ist nahezu nie erforderlich.

Welches Beispiel sollte man sich ansehen, um seine Anwendung zu dockerisieren? Meiner Meinung nach ist das offizielle Docker-Image für MySQL ein gutes Beispiel.
Es ist ziemlich komplex. Es gibt viele Versionen. Aber die Funktionalität deckt zahlreiche Anforderungen ab, die während der weiteren Entwicklung auftreten können. Wenn Sie sich die Zeit nehmen und verstehen, wie alles zusammenspielt, denke ich, dass Sie in der eigenständigen Implementierung keine Probleme haben werden.
Auf hub.docker.com gibt es normalerweise Links zu github.com, wo die Rohdaten direkt zur Verfügung stehen, aus denen Sie das Image selbst zusammenstellen können.
In diesem Repository gibt es außerdem das Skript docker-endpoint.sh, das für die initiale Einrichtung und die weitere Verarbeitung des Anwendungsstarts verantwortlich ist.
In diesem Beispiel besteht auch die Möglichkeit, Umgebungsvariablen zu konfigurieren. Durch die Definition von Umgebungsvariablen beim Start eines einzelnen Containers oder über docker-compose können wir angeben, dass wir ein leeres Passwort für Docker für root in MySQL festlegen müssen oder eines, das wir wünschen.
Es besteht die Möglichkeit, ein zufälliges Passwort zu erstellen. Wir geben an, dass wir einen Benutzer benötigen, ein Passwort für den Benutzer festgelegt werden muss und eine Datenbank erstellt werden muss.
In unseren Projekten haben wir das Dockerfile, das für die Initialisierung zuständig ist, etwas vereinheitlicht. Dabei haben wir es auf unsere Bedürfnisse angepasst, um einfach die Benutzerrechte zu erweitern, die von der Anwendung verwendet werden. Dies ermöglichte es uns, die Datenbank direkt über die Konsolenanwendung zu erstellen. In Ruby-Anwendungen gibt es Befehle zum Erstellen, Ändern und Löschen von Datenbanken.

Dieses Beispiel zeigt, wie eine bestimmte Version von MySQL auf github.com aussieht. Das Dockerfile kann geöffnet werden, um zu sehen, wie dort die Installation abläuft.
Das Skript docker-endpoint.sh dient als Einstiegspunkt. Bei der ersten Initialisierung sind einige Vorbereitungen erforderlich, die alle im Initialisierungsskript zusammengefasst sind.

Wir kommen zum zweiten Teil.
Für die Speicherung des Quellcodes haben wir auf GitLab umgestellt. Dies ist ein leistungsstarkes System, das über eine visuelle Benutzeroberfläche verfügt.
Ein Bestandteil von GitLab ist GitLab CI. Damit können wir Abläufe definieren, die später verwendet werden, um ein System zur Bereitstellung von Code oder zur Ausführung automatischer Tests zu organisieren.
Präsentation zu GitLab CI 2 — Präsentation vom Ruby Russia Club — sie ist recht detailliert und könnte für Sie von Interesse sein.

Jetzt schauen wir uns an, was erforderlich ist, um GitLab CI zu aktivieren. Um GitLab CI zu starten, genügt es, eine Datei mit dem Namen .gitlab-ci.yml in das Stammverzeichnis des Projekts zu legen.
Hier beschreiben wir, was wir in einer Folge von Zuständen wie Tests und Deployments ausführen möchten.
Wir führen Skripte aus, die direkt den Docker-Compose-Bau unserer Anwendung aufrufen. Dies ist ein Beispiel für das Backend.
Anschließend geben wir an, dass die Migrationen zur Änderung der Datenbank durchgeführt und die Tests ausgeführt werden müssen.
Wenn die Skripte korrekt ausgeführt werden und keinen Fehlercode zurückgeben, wechselt das System zur zweiten Phase des Deployments.
Die Deployment-Phase ist derzeit für staging implementiert. Wir haben keinen unterbrechungsfreien Neustart organisiert.
Wir beenden alle Container gezielt und starten dann alle Container neu, die in der ersten Phase während der Tests erstellt wurden.
Wir führen Migrationen der Datenbanken für die aktuelle Umgebungsvariablen aus, die von den Entwicklern geschrieben wurden.
Es gibt einen Hinweis, dass dies nur für den Master-Branch angewendet werden sollte.
Bei Änderungen in anderen Branches wird dies nicht ausgeführt.
Es besteht die Möglichkeit, Rollouts nach Branches zu organisieren.

Um dies weiter zu organisieren, müssen wir den Gitlab Runner installieren.
Dieses Tool ist in Golang geschrieben. Es ist eine Einzeldatei, wie es in der Golang-Welt üblich ist, die keine Abhängigkeiten benötigt.
Bei der Ausführung registrieren wir den Gitlab Runner.
Wir erhalten den Schlüssel im Web-Interface von Gitlab.
Dann rufen wir den Initialisierungsbefehl in der Kommandozeile auf.
Wir konfigurieren den Gitlab Runner im Dialogmodus (Shell, Docker, VirtualBox, SSH).
Der Code auf dem Gitlab Runner wird bei jedem Commit ausgeführt, abhängig von der Konfiguration in .gitlab-ci.yml.

So sieht es visuell in GitLab im Web-Interface aus. Sobald wir GitLab CI verbunden haben, erscheint eine Flagge, die den aktuellen Status des Builds anzeigt.
Wir sehen, dass vor 4 Minuten ein Commit durchgeführt wurde, der alle Tests bestanden hat und keine Probleme verursacht hat.

Wir können die Builds genauer betrachten. Hier sehen wir, dass bereits zwei Zustände durchlaufen wurden: der Testzustand und der Zustand des Deployments auf Staging.
Wenn wir auf einen bestimmten Build klicken, wird der Konsolenausgabe der Befehle angezeigt, die während des Prozesses gemäß .gitlab-ci.yml ausgeführt wurden.

So sieht die Historie unseres Produkts aus. Wir sehen, dass erfolgreiche Versuche vorhanden sind. Wenn Tests fehlschlagen, wird der nächste Schritt nicht ausgeführt und der Code auf Staging wird nicht aktualisiert.

Welche Herausforderungen hatten wir im Staging bei der Einführung von Docker? Unser System besteht aus Komponenten, und wir mussten die Möglichkeit schaffen, nur die Teile, die im Repository aktualisiert wurden, neu zu starten und nicht das gesamte System.
Dafür mussten wir alles in separate Ordner aufteilen.
Nachdem wir das eingerichtet hatten, traten Probleme auf, da Docker-Compose für jeden Ordner sein eigenes Netzwerk erstellt und die Nachbarkomponenten nicht erkennt.
Um dies zu umgehen, haben wir manuell ein Netzwerk in Docker erstellt. In Docker-Compose haben wir festgelegt, dass dieses Projekt dieses Netzwerk nutzen soll.
Dadurch kann jede Komponente, die mit diesem Netzwerk gestartet wird, die Komponenten in anderen Teilen des Systems sehen.
Das nächste Problem ist die Trennung von Staging zwischen mehreren Projekten.
Um das ansprechend und so nah wie möglich an der Produktionsumgebung zu gestalten, ist es ratsam, die Ports 80 oder 443 zu verwenden, die überall im Web genutzt werden.

Wie haben wir das gelöst? Wir haben einen GitLab Runner für alle größeren Projekte zugewiesen.
GitLab ermöglicht das Starten mehrerer verteilten GitLab Runner, die einfach abwechselnd in zufälliger Reihenfolge alle Aufgaben übernehmen und ausführen.
Um ein Chaos zu vermeiden, haben wir unsere Projektgruppe auf einen GitLab Runner beschränkt, der mit unserem Volumen problemlos zurechtkommt.
Wir haben den Nginx-Proxy in ein separates Startskript ausgelagert und darin die Netzwerke aller Projekte definiert.
Unser Projekt verfügt über ein Netzwerk, während der Load-Balancer mehrere Netzwerke basierend auf den Projekt-Namen hat. Er kann auch über Domainnamen weiter proxen.
Die Anfragen gelangen über die Domain auf Port 80 und werden an eine Gruppe von Containern weitergeleitet, die diese Domain bedient.

Gab es noch andere Probleme? Standardmäßig laufen alle Container unter dem Benutzer root. Dieser root ist jedoch nicht dasselbe wie der root des Hostsystems.
Wenn man jedoch in den Container einsteigt, ist man root und die Dateien, die wir in diesem Container erstellen, erhalten root-Rechte.
Falls der Entwickler in den Container eingegangen ist und dort Befehle ausgeführt hat, die Dateien erzeugen, hat er beim Verlassen des Containers in seinem Arbeitsverzeichnis Dateien, auf die er keinen Zugriff hat.
Wie kann man das lösen? Man könnte Benutzer hinzufügen, die im Container arbeiten.
Welche Probleme traten auf, als wir einen Benutzer hinzugefügt haben?
Wenn wir einen Benutzer erstellen, stimmen oft die Gruppen-ID (UID) und die Benutzer-ID (GID) nicht überein.
Um dieses Problem im Container zu beheben, verwenden wir Benutzer mit der ID 1000.
In unserem Fall stimmt es, dass nahezu alle Entwickler Ubuntu als Betriebssystem verwenden. Bei Ubuntu hat der erste Benutzer die ID 1000.

Was sind unsere Pläne?
Die Dokumentation zu Docker noch einmal durchlesen. Das Projekt entwickelt sich aktiv und die Dokumentation ändert sich. Die Informationen, die vor zwei oder drei Monaten gesammelt wurden, sind mittlerweile etwas veraltet.
Einige der Probleme, die wir gelöst haben, könnten bereits mit den Standardmethoden behoben worden sein.
Es wäre großartig, weiterzugehen und direkt zur Orchestrierung überzugehen.
Ein Beispiel dafür ist der in Docker integrierte Mechanismus namens Docker Swarm, der standardmäßig verfügbar ist. Ich möchte etwas in der Produktion mit Docker Swarm-Technologie starten.
Das Erstellen von Containern erschwert die Arbeit mit Logs. Momentan sind die Logs isoliert und über die Container verstreut. Eine der Aufgaben besteht darin, einen einfachen Zugriff auf die Logs über eine Web-Oberfläche zu ermöglichen.

Quelle: habr.com
