Ich lade Sie ein, sich mit der Präsentation von Alexander Sigachev von Inventos «Entwicklungs- und Testprozess mit Docker + Gitlab CI» vertraut zu machen.
Diejenigen, die gerade erst damit beginnen, den Entwicklungs- und Testprozess auf Basis von Docker + Gitlab CI einzuführen, stellen oft grundlegende Fragen. Wo soll man anfangen? Wie soll man organisieren? Wie testen?
Dieser Bericht ist gut, weil er strukturiert über den Entwicklungs- und Testprozess mit Docker und Gitlab CI spricht. Der Bericht stammt aus dem Jahr 2017. Ich denke, dass man aus diesem Bericht Grundlagen, Methoden, Ideen und Erfahrungen sammeln kann.

Für Interessierte bitte weiterlesen.
Ich heiße Alexander Sigachev. Ich arbeite bei der Firma Inventos. Ich werde über meine Erfahrungen mit Docker und darüber berichten, wie wir es schrittweise in Projekten im Unternehmen implementieren.
Thema des Berichts: Entwicklungsprozess mit Docker und Gitlab CI.

Das ist mein zweiter Bericht über Docker. Zum Zeitpunkt des ersten Berichts nutzten wir Docker nur in der Entwicklung auf den Maschinen der Entwickler. Die Anzahl der Mitarbeiter, die Docker nutzten, lag bei etwa 2-3 Personen. Nach und nach haben wir Erfahrung gesammelt und sind etwas weitergekommen. Hier ist der Link zu unserem .
Was wird in diesem Bericht behandelt? Wir werden unsere Erfahrungen teilen, welche Stolpersteine wir aufgesammelt haben und wie wir bestimmte Probleme gelöst haben. Es war nicht überall schön, aber es hat uns ermöglicht, voranzukommen.
Unser Motto: Dockerisiere alles, was in unsere Hände kommt.

Welche Probleme lösen wir?
Wenn es in einem Unternehmen mehrere Teams gibt, ist der Programmierer eine gefragte Ressource. Es gibt Phasen, in denen ein Programmierer aus einem Projekt herausgerissen wird und für eine gewisse Zeit in ein anderes Projekt gegeben wird.
Damit der Programmierer schnell einsteigen kann, muss er den Quellcode des Projekts herunterladen und möglichst schnell eine Umgebung einrichten, die es ihm ermöglicht, die Aufgaben dieses Projekts weiter voranzutreiben.
Normalerweise, wenn man von Null anfängt, wird in dem Projekt wenig Dokumentation geführt. Die Informationen zur Einrichtung haben nur die alten Hasen. Eigenständig richten die Mitarbeiter ihren Arbeitsplatz in ein oder zwei Tagen ein. Um dies zu beschleunigen, haben wir Docker eingesetzt.
Der nächste Grund sind die Standardisierungen der Einstellungen in der Entwicklung. Nach meiner Erfahrung zeigen die Entwickler immer Initiative. In jedem fünften Fall wird eine benutzerdefinierte Domain eingeführt, zum Beispiel vasya.dev. Nebenan sitzt Nachbar Petja, der die Domain petya.dev hat. Sie entwickeln eine Website oder eine Systemkomponente mit diesem Domainnamen.
Wenn das System wächst und diese Domainnamen in die Konfigurationen gelangen, entsteht ein Konflikt zwischen den Entwicklungsumgebungen und der Weg zur Website wird überschrieben.
Das Gleiche passiert mit den Datenbankeinstellungen. Manche kümmern sich nicht um die Sicherheit und arbeiten mit dem leeren Passwort root. Bei manchen verlangte beim Setup MySQL ein Passwort und es stellte sich heraus, dass es 123 war. Oft kommt es vor, dass die Datenbankkonfiguration ständig geändert wurde, je nach den Commits des Entwicklers. Manche haben die Konfiguration korrigiert, andere nicht. Es gab Tricks, bei denen wir eine Testkonfiguration in die .gitignore und jeder Entwickler musste die Datenbank installieren. Das erschwerte den Startprozess. Man muss neben allem anderen auch an die Datenbank denken. Die Datenbank muss initialisiert, das Passwort muss angegeben, ein Benutzer muss festgelegt und eine Tabelle muss erstellt werden und so weiter.
Ein weiteres Problem sind die unterschiedlichen Versionen von Bibliotheken. Oft ist es so, dass der Entwickler mit verschiedenen Projekten arbeitet. Es gibt ein Legacy-Projekt, das vor fünf Jahren (seit 2017) gestartet wurde. Zu Beginn wurde mit MySQL 5.5 gearbeitet. Es gibt auch moderne Projekte, bei denen wir versuchen, bereits modernere MySQL-Versionen wie 5.7 oder älter (im Jahr 2017) zu implementieren.
Jeder, der mit MySQL arbeitet, weiß, dass diese Bibliotheken Abhängigkeiten mit sich ziehen. Es ist ziemlich problematisch, zwei Datenbanken gleichzeitig zu starten. Zumindest ist es schwierig, alte Clients mit einer neuen Datenbank zu verbinden. Das führt seinerseits zu mehreren Problemen.
Das nächste Problem tritt auf, wenn der Entwickler auf seinem lokalen Rechner arbeitet, wobei er lokale Ressourcen, lokale Dateien und lokalen RAM verwendet. Alle Interaktionen während der Entwicklung der Lösungen erfolgen im Rahmen dessen, dass alles auf einem einzigen Rechner funktioniert. Ein Beispiel wäre, wenn wir in der Produktion drei Backend-Server haben und der Entwickler Dateien im Root-Verzeichnis speichert, von wo aus nginx Dateien für die Anfragen abruft. Wenn dieser Code in die Produktion gelangt, liegt das Problem darin, dass die Datei nur auf einem der drei Server vorhanden ist.
Aktuell entwickelt sich der Bereich der Mikrodienste. Wenn wir unsere großen Anwendungen in kleinere, miteinander interagierende Komponenten aufteilen. Dies ermöglicht es, die Technologien für den jeweiligen Aufgabenstapel auszuwählen. Außerdem erlaubt es eine bessere Aufteilung der Arbeit und der Verantwortungsbereiche unter den Entwicklern.
Frontend-Entwickler, die in JS entwickeln, haben praktisch keinen Einfluss auf das Backend. Der Backend-Entwickler hingegen entwickelt in unserem Fall Ruby on Rails und stört die Frontend-Entwicklung nicht. Die Interaktion erfolgt über APIs.
Als Bonus konnten wir mit Docker die Ressourcen auf Staging optimieren. Jedes Projekt benötigte aufgrund seiner Spezifikationen bestimmte Einstellungen. Physisch mussten entweder separate virtuelle Server bereitgestellt und individuell konfiguriert werden oder es war nötig, eine variable Umgebung zu teilen, sodass die Projekte, je nach Version der Bibliotheken, sich gegenseitig beeinflussen konnten.

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

Der Bericht besteht aus zwei Teilen.
Der erste Teil wird darüber berichten, wie wir Docker auf den Maschinen der Entwickler gestartet haben.
Der zweite Teil wird behandeln, wie wir mit GitLab interagieren, wie wir Tests durchführen und wie wir auf Staging deployen.

Docker ist eine Technologie, die es ermöglicht, die erforderlichen Komponenten (unter Verwendung eines deklarativen Ansatzes) zu beschreiben. Dies ist ein Beispiel für ein Dockerfile. Hier erklären wir, dass wir von dem offiziellen Docker-Image Ruby:2.3.0 erben. Es enthält die installierte Ruby-Version 2.3. Wir installieren die benötigten Build-Bibliotheken und NodeJS. Wir beschreiben, dass wir ein Verzeichnis erstellen. /app. Wir setzen das Verzeichnis app als Arbeitsverzeichnis fest. In dieses Verzeichnis legen wir die erforderlichen minimalen Gemfiles und Gemfile.lock. Anschließend führen wir den Build der Projekte durch, die dieses Abbild der Abhängigkeiten installieren. Wir geben an, dass der Container bereit ist, auf dem externen Port 3000 zu lauschen. Der letzte Befehl ist der Befehl, der unsere Anwendung tatsächlich startet. Wenn wir den Projektstartbefehl ausführen, wird die Anwendung versuchen, sich auszuführen, und den angegebenen Befehl starten.

Dies ist ein minimales Beispiel einer docker-compose Datei. In diesem Fall zeigen wir, was mit der Verbindung von zwei Containern geschieht. Dies bezieht sich direkt auf den Datenbankservice und den Webservice. Unsere Webanwendungen benötigen in den meisten Fällen eine Datenbank als Backend zur Datenspeicherung. Da wir MySQL verwenden, handelt es sich um ein Beispiel mit MySQL – aber es steht nichts im Wege, eine andere Datenbank (PostgreSQL, Redis) zu verwenden.
Wir verwenden das offizielle Image MySQL 5.7.14 von Docker Hub ohne Änderungen. Das Image, das für unsere Webanwendung verantwortlich ist, wird aus dem aktuellen Verzeichnis erstellt. Es erstellt beim ersten Start unser Image. Danach wird der Befehl ausgeführt, den wir hier verwenden. Wenn wir einen Schritt zurückgehen, werden wir sehen, dass der Startbefehl über Puma definiert wurde. Puma ist ein Dienst, der in Ruby geschrieben ist. Im zweiten Fall überschreiben wir. Dieser Befehl kann beliebig sein, je nach unseren Bedürfnissen oder Anforderungen.
Außerdem beschreiben wir, dass der Port auf der Entwicklungsmaschine des Entwicklers von 3000 auf den Port 3000 des Containers weitergeleitet werden muss. Dies erfolgt automatisch mithilfe von iptables und einem Mechanismus, der direkt in Docker eingebaut ist.
Der Entwickler kann sich, wie zuvor, an jede verfügbare IP-Adresse wenden, zum Beispiel 127.0.0.1 lokal oder an die externe IP-Adresse der Maschine.
Die letzte Zeile besagt, dass der Webcontainer von dem DB-Container abhängt. Wenn wir den Webcontainer starten, wird docker-compose zunächst unsere Datenbank starten. Nach dem Start der Datenbank (tatsächlich – nach dem Start des Containers! Die Bereitstellung der Datenbank wird dadurch nicht garantiert) startet unsere Anwendung, unser Backend.
Dies hilft, Fehler zu vermeiden, wenn die Datenbank nicht bereit ist, und ermöglicht es, Ressourcen zu sparen, wenn wir den Datenbankcontainer stoppen und damit Ressourcen für andere Projekte freigeben.

Was uns die Verwendung der Dockerisierung von Datenbanken im Projekt bringt. Wir dokumentieren die MySQL-Version bei allen Entwicklern. Das hilft, einige Fehler zu vermeiden, die auftreten können, wenn die Versionen voneinander abweichen, etwa durch Änderungen in der Syntax, Konfiguration und den Standardeinstellungen. Außerdem ermöglicht es, gemeinsame Hostnamen für die Datenbank, Login und Passwort festzulegen. Damit weichen wir von dem Zoo der Namen und Konflikte in den Konfigurationsdateien ab, die es früher gab.
Wir haben die Möglichkeit, eine optimierte Konfiguration für die Entwicklungsumgebung zu verwenden, die sich vom Standard unterscheidet. MySQL ist standardmäßig auf schwache Maschinen eingestellt und die Leistung ist von Haus aus sehr gering.

Docker ermöglicht die Nutzung des Python-, Ruby-, NodeJS- und PHP-Interpreters in der benötigten Version. Wir benötigen keinen Versionsmanager mehr. Früher verwendeten wir für Ruby ein rpm-Paket, das den Versionswechsel je nach Projekt erlaubte. Dank des Docker-Containers kann der Code sanft migriert und zusammen mit den Abhängigkeiten versioniert werden. Wir haben keine Probleme, die Version sowohl des Interpreters als auch des Codes zu verstehen. Um die Version zu aktualisieren, muss der alte Container abgeschaltet und ein neuer Container hochgefahren werden. Sollte etwas schiefgehen, können wir den neuen Container herunterfahren und den alten Container wieder hochfahren.
Nach dem Erstellen des Images werden die Container sowohl in der Entwicklungsumgebung als auch in der Produktionsumgebung identisch sein. Dies ist besonders relevant für große Installationen.
Im Frontend verwenden wir JavaScript und NodeJS.
Das letzte Projekt, das wir haben, ist auf ReactJS. Der Entwickler hat alle Container gestartet und entwickelte mit Hot-Reload.
Anschließend wird die Aufgabe zur Erstellung von JavaScript gestartet, und der kompilierte Code wird als statische Datei über Nginx bereitgestellt, um Ressourcen zu sparen.

Hier habe ich das Schema unseres letzten Projekts dargestellt.
Welche Aufgaben wurden gelöst? Wir hatten die Notwendigkeit, ein System aufzubauen, das mit mobilen Geräten interagiert. Diese erhalten Daten. Eine der Möglichkeiten ist das Versenden von Push-Benachrichtigungen an dieses Gerät.
Was haben wir dafür getan?
Wir haben die Anwendung in Komponenten unterteilt, darunter: den Administrationsbereich in JS, das Backend, das über eine REST-API unter Ruby on Rails läuft. Das Backend interagiert mit der Datenbank. Die Ergebnisse, die generiert werden, werden an den Client übergeben. Die Admin-Oberfläche kommuniziert mit dem Backend und der Datenbank über die REST-API.
Wir hatten auch die Notwendigkeit, Push-Benachrichtigungen zu senden. Zuvor hatten wir ein Projekt, in dem ein Mechanismus implementiert war, der für die Zustellung von Benachrichtigungen auf mobilen Plattformen zuständig war.
Wir haben folgendes Schema entwickelt: Der Betreiber interagiert über den Browser mit dem Admin-Bereich, der Admin-Bereich interagiert mit dem Backend und es wird die Aufgabe gestellt, Push-Benachrichtigungen zu senden.
Push-Benachrichtigungen interagieren mit einer anderen Komponente, die auf NodeJS implementiert ist.
Es werden Warteschlangen aufgebaut und anschließend erfolgt die Zustellung der Benachrichtigungen nach ihrem Mechanismus.
Hier sind zwei Datenbanken skizziert. Im Moment nutzen wir mithilfe von Docker zwei unabhängige Datenbanken, die nicht miteinander verbunden sind, abgesehen von ihrem gemeinsamen virtuellen Netzwerk, während die physischen Daten in verschiedenen Verzeichnissen auf dem Rechner des Entwicklers gespeichert werden.

Dasselbe, aber in Zahlen. Hier ist die Wiederverwendbarkeit des Codes wichtig.
Während wir zuvor über die Wiederverwendbarkeit von Code in Form von Bibliotheken sprachen, wird unser Service für Push-Benachrichtigungen in diesem Beispiel als vollständiger Server wiederverwendet. Er bietet eine API an. Mit dieser interagiert unsere neue Entwicklung.
Zu diesem Zeitpunkt verwendeten wir Version 4 von NodeJS. Jetzt (im Jahr 2017 – Anm. d. Red.) verwenden wir in neuen Entwicklungen Version 7 von NodeJS. Es gibt keine Probleme, neue Versionen von Bibliotheken in neuen Komponenten zu verwenden.
Bei Bedarf kann eine Refaktorisierung durchgeführt werden, um die Version von NodeJS im Service für Push-Benachrichtigungen zu heben.
Wenn wir die Kompatibilität der API beibehalten können, könnte es möglich sein, sie in anderen Projekten zu ersetzen, die zuvor verwendet wurden.

Was ist nötig, um Docker hinzuzufügen? Wir fügen unserem Repository eine Dockerfile hinzu, die die notwendigen Abhängigkeiten beschreibt. In diesem Beispiel sind die Komponenten nach Logik unterteilt. Dies ist das minimale Set für Backend-Entwickler.
Bei der Erstellung eines neuen Projekts erstellen wir eine Dockerfile und beschreiben das erforderliche Ökosystem (Python, Ruby, NodeJS). In der docker-compose beschreiben wir die notwendige Abhängigkeit – die Datenbank. Wir geben an, dass eine Datenbank dieser Version benötigt wird, um die Daten dort zu speichern.
Wir verwenden einen separaten dritten Container mit nginx für die Bereitstellung von Statischen Inhalten. Es ist die Möglichkeit vorgesehen, Bilder hochzuladen. Der Backend platziert sie in einem zuvor vorbereiteten Volume, das ebenfalls in den Container mit nginx montiert 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 erforderlichen Konfigurationen aufbewahren. Wenn ein Entwickler das Git-Repository auf seinen Computer klont, erhält er ein Projekt, das bereits für die lokale Entwicklung bereit ist. Es stellt sich nicht die Frage, welchen Port oder welche Einstellungen anzuwenden sind.

Darüber hinaus haben wir mehrere Komponenten: Admin, Info-API, Push-Benachrichtigungen.
Um alles zu starten, haben wir ein weiteres Repository erstellt, das wir dockerized-app genannt haben. Im Moment verwenden wir mehrere Repositories für jede Komponente. Sie unterscheiden sich einfach logisch – in GitLab sieht das wie ein Ordner aus, und auf dem Computer des Entwicklers gibt es einen Ordner für ein bestimmtes Projekt. Auf einer tieferen Ebene liegen die Komponenten, die zusammengeführt werden.

Dies ist ein Beispiel für den Inhalt von dockerized-app. Wir nehmen auch das Docker-Verzeichnis hier heraus, in dem wir die Konfigurationen sammeln, die für die Interaktion aller Komponenten erforderlich sind. Es gibt eine README.md, die kurz beschreibt, wie man das Projekt startet.
Hier haben wir zwei Docker-Compose-Dateien verwendet. Dies wurde gemacht, um die Möglichkeit zu haben, schrittweise zu starten. Wenn ein Entwickler am Kern arbeitet und keine Push-Benachrichtigungen benötigt, startet er einfach die Docker-Compose-Datei, und entsprechend werden Ressourcen eingespart.
Wenn es notwendig ist, sich mit Push-Benachrichtigungen zu integrieren, wird die docker-compose.yaml und 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 erweiterte Datei, die für den Zusammenbau der Komponenten verantwortlich ist. Was dabei bemerkenswert ist? Hier führen wir die Lastverteilungskomponente ein.
Dies ist ein fertiges Docker-Image, in dem Nginx und eine Anwendung gestartet werden, die den Docker-Socket überwacht. Dynamisch, je nach Ein- und Ausschalten der Container, wird die Nginx-Konfiguration neu generiert. Der Zugang zu den Komponenten erfolgt über Subdomains.
Für die Entwicklungsumgebung verwenden wir die Domain .dev – api.informer.dev. Anwendungen mit der Domain .dev sind auf dem lokalen Computer des Entwicklers verfügbar.
Dann werden die Konfigurationen für jedes Projekt übergeben, und alle Projekte werden gleichzeitig gestartet.

Grafisch dargestellt ergibt sich, dass der Client unser Browser oder ein Tool ist, mit dem wir Anfragen an den Lastverteiler stellen.
Der Lastverteiler bestimmt anhand des Domainnamens, mit welchem Container er kommunizieren soll.
Das kann nginx sein, der die JS-Administration ausliefert. Das kann nginx sein, der die API oder statische Dateien ausliefert, die von nginx als Bild-Uploads bereitgestellt werden.
In dem Diagramm ist zu sehen, dass die Container in einem virtuellen Netzwerk zusammengefasst und hinter einem Proxy verborgen sind.
Auf dem Entwicklerrechner kann man auf den Container zugreifen, wenn man die IP kennt, aber das nutzen wir grundsätzlich nicht. Die Notwendigkeit für einen direkten Zugriff entsteht praktisch nie.

Welches Beispiel sollte man sich anschauen, um seine Anwendung zu dockerisieren? Meiner Meinung nach ist ein gutes Beispiel das offizielle Docker-Image für MySQL.
Es ist ziemlich komplex. Es gibt viele Versionen. Aber seine Funktionalität ermöglicht es, viele Bedürfnisse abzudecken, die während der weiteren Entwicklung entstehen können. Wenn Sie sich die Zeit nehmen und lernen, wie alles zusammenarbeitet, denke ich, dass Sie bei der eigenständigen Implementierung keine Probleme haben werden.
Auf hub.docker.com sind normalerweise Links zu github.com vorhanden, wo die Rohdaten bereitgestellt werden, aus denen man selbst ein Image erstellen kann.
In diesem Repository befindet sich das Skript docker-endpoint.sh, das für die Erstinitialisierung und die weitere Verarbeitung des Anwendungsstarts verantwortlich ist.
In diesem Beispiel gibt es auch die Möglichkeit der Konfiguration über Umgebungsvariablen. Indem man die Umgebungsvariablen beim Start eines einzelnen Containers oder über docker-compose festlegt, kann man sagen, dass wir ein leeres Passwort für docker für root in MySQL oder ein beliebiges Passwort, das wir möchten, festlegen müssen.
Es besteht die Möglichkeit, ein zufälliges Passwort zu erstellen. Wir sagen, dass wir einen Benutzer benötigen, ein Passwort für den Benutzer festlegen und eine Datenbank erstellen müssen.
In unseren Projekten haben wir das Dockerfile etwas standardisiert, das für die Initialisierung verantwortlich ist. Dort haben wir es nach unseren Bedürfnissen angepasst, um einfach die Benutzerrechte zu erweitern, die die Anwendung benötigt. Das hat es uns ermöglicht, später einfach eine Datenbank über die Anwendungs-Konsole 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 erfolgt.
Das Skript docker-endpoint.sh ist für den Einstiegspunkt verantwortlich. Bei der первичной Initialisierung sind einige Vorbereitungsmaßnahmen erforderlich, und all diese Maßnahmen sind im Initialisierungsskript zusammengefasst.

Wir gehen zum zweiten Teil über.
Für die Speicherung der Quellcodes haben wir auf GitLab umgestellt. Es ist ein leistungsfähiges System mit einer visuellen Benutzeroberfläche.
Einer der Komponenten von GitLab ist GitLab CI. Damit kann man eine Abfolge von Befehlen beschreiben, die anschließend verwendet werden, um das Code-Deployment oder die Durchführung automatischer Tests zu organisieren.
Bericht über GitLab CI 2 — Bericht vom Ruby Russia Club — ist ziemlich detailliert und könnte Sie interessieren.

Jetzt sehen wir uns an, was benötigt wird, um GitLab CI zu aktivieren. Um GitLab CI zu starten, reicht es aus, die Datei .gitlab-ci.yml ins Wurzelverzeichnis des Projekts zu legen.
Hier beschreiben wir, welche Abfolge von Status wie Tests und Deployments wir ausführen möchten.
Wir führen Skripte aus, die die docker-compose-Build unseres Anwendungs-Backends direkt aufrufen. Das ist ein Beispiel für das Backend.
Anschließend geben wir an, dass Migrationen zur Datenbankänderung durchgeführt und 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 einen reibungslosen Neustart nicht organisiert.
Wir stoppen gezielt alle Container und starten dann alle Container neu, die in der ersten Phase während der Tests erstellt wurden.
Wir führen die Migrationen der Datenbanken für die aktuelle variable Umgebung aus, die von den Entwicklern geschrieben wurden.
Es gibt einen Hinweis, dass dies nur für den Master-Branch angewendet werden soll.
Bei Änderungen an anderen Branches erfolgt dies nicht.
Es besteht die Möglichkeit, Rollouts nach Branches zu organisieren.

Um dies weiter zu organisieren, müssen wir GitLab Runner installieren.
Dieses Tool ist in Golang geschrieben. Es handelt sich um eine Einzeldatei, wie es in der Golang-Welt üblich ist, ohne dass Abhängigkeiten erforderlich sind.
Bei der Ausführung registrieren wir den GitLab Runner.
Im Webinterface von GitLab erhalten wir einen Schlüssel.
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 der .gitlab-ci.yml.

So sieht es visuell in Gitlab im Web-Interface aus. Nachdem wir Gitlab CI verbunden haben, erscheint eine Flagge, die anzeigt, in welchem Zustand sich der Build derzeit befindet.
Wir sehen, dass vor 4 Minuten ein Commit gemacht wurde, der alle Tests bestanden hat und keine Probleme verursacht hat.

Wir können uns die Builds detaillierter ansehen. Hier sehen wir, dass bereits zwei Zustände durchlaufen wurden: den Test- und den Deploy-Zustand auf Staging.
Wenn wir auf einen bestimmten Build klicken, wird es eine Konsolenausgabe der Befehle geben, die im Prozess gemäß .gitlab-ci.yml ausgeführt wurden.

So sieht die Historie unseres Produkts aus. Wir sehen, dass es erfolgreiche Versuche gab. Wenn die Tests fehlerhaft sind, wird nicht zum nächsten Schritt übergegangen und der Code auf Staging wird nicht aktualisiert.

Welche Aufgaben haben wir auf Staging gelöst, als wir Docker implementiert haben? Unser System besteht aus Komponenten und wir hatten die Notwendigkeit, nur die Teile der Komponenten, die im Repository aktualisiert wurden, neu zu starten, nicht das gesamte System.
Dafür mussten wir alles in separate Ordner aufteilen.
Nachdem wir das getan hatten, hatten wir das Problem, dass Docker-Compose für jeden Ordner sein eigenes Netzwerk erstellt, und die Komponenten der Nachbarn nicht sehen kann.
Um dies zu umgehen, haben wir manuell ein Netzwerk in Docker erstellt. In Docker-Compose haben wir festgelegt, dass für dieses Projekt dieses Netzwerk verwendet werden soll.
Auf diese Weise sieht jede Komponente, die mit diesem Netzwerk gestartet wird, die Komponenten in anderen Teilen des Systems.
Das nächste Problem ist die Trennung von Staging zwischen mehreren Projekten.
Damit alles schön aussieht und so nah wie möglich an der Produktion ist, ist es gut, den Port 80 oder 443 zu verwenden, der im Web allgemein genutzt wird.

Wie haben wir das gelöst? Wir haben einen Gitlab Runner für alle großen Projekte zugewiesen.
Gitlab erlaubt es, mehrere verteilte Gitlab Runner zu starten, die einfach der Reihe nach, in zufälliger Reihenfolge, alle Aufgaben abarbeiten.
Um kein Chaos zu verursachen, haben wir die Gruppe unserer Projekte auf einen Gitlab Runner beschränkt, der mit unseren Volumen problemlos zurechtkommt.
Wir haben nginx-proxy in ein separates Startskript ausgelagert und in diesem die Netzwerke aller Projekte festgelegt.
Unser Projekt hat ein Netzwerk, während der Balancer mehrere Netzwerke nach Projektnamen hat. Er kann weiter nach Domainnamen proxy.
Wir erhalten Anfragen über die Domain auf Port 80, die in eine Gruppe von Containern geleitet werden, die diese Domain bedienen.

Welche weiteren Probleme gab es? Standardmäßig werden alle Container als Benutzer root ausgeführt. Dieses root ist nicht gleich dem root des Hosts.
Wenn man jedoch in den Container einloggt, ist man root, und die Datei, die wir in diesem Container erstellen, erhält die Berechtigungen von root.
Wenn der Entwickler in den Container eingeloggt ist und dort Befehle ausführt, die Dateien erzeugen, und dann den Container verlässt, hat er in seinem Arbeitsverzeichnis eine Datei, 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?
Beim Erstellen eines Benutzers stimmen oft die Gruppen-ID (UID) und die Benutzer-ID (GID) nicht überein.
Um dieses Problem zu lösen, verwenden wir im Container Benutzer mit der ID 1000.
In unserem Fall stimmte das damit überein, dass fast alle Entwickler das Betriebssystem Ubuntu verwenden. Bei Ubuntu hat der erste Benutzer die ID 1000.

Was sind unsere Pläne?
Die Dokumentation zu Docker nochmal durchlesen. Das Projekt entwickelt sich aktiv weiter, die Dokumentation ändert sich. Daten, die vor zwei bis drei Monaten erhalten wurden, sind allmählich schon veraltet.
Ein Teil der Probleme, die wir gelöst haben, könnte bereits mit Standardmitteln gelöst worden sein.
Wir möchten gerne weitergehen und direkt zur Orchestrierung übergehen.
Ein Beispiel ist der in Docker integrierte Mechanismus namens Docker Swarm, der standardmäßig verfügbar ist. Wir möchten etwas im Produktionsbetrieb auf der Basis der Docker Swarm-Technologie starten.
Die Erzeugung von Containern macht die Arbeit mit Logs unpraktisch. Momentan sind die Logs isoliert. Sie sind über die Container verstreut. Eine der Aufgaben besteht darin, einen komfortablen Zugang zu den Logs über ein Web-Interface zu schaffen.

Quelle: habr.com
