Docker - Kommunikation und Cloud-Integration von Containern =========================================================== .. index:: single: Einführung; Docker single: Docker; Virtualisierung single: Setup; Docker single: Dockerfile Einführung ---------- Docker ist eine Plattform, die es ermöglicht, **Anwendungen in Containern zu erstellen**, zu verteilen und auszuführen. Ein Container ist eine Einheit von **Software**, die Code und alle seine **Abhängigkeiten** enthält, so dass die Anwendung schnell und zuverlässig auf jedem Rechner ausgeführt werden kann. Docker hilft außerdem, die Entwicklungs- und Betriebsabläufe zu automatisieren, so dass Anwendungen schneller geliefert werden können. Docker vs Virtualisierung -------------------------- Mit einer virtuellen Maschine können beliebig viele **Betriebssysteme** auf einem Host-System ausgeführt werden. Im Gegensatz zu einer virtuellen Maschine, die ein vollständiges Betriebssystem enthält, enthält ein Docker-Container nur die **Anwendung** und alle ihre Abhängigkeiten. Setup für den Rechner ---------------------- .. _Installationsanleitung: https://docs.docker.com/install/ Für das Installieren von Docker, verweisen wir auf die offizielle Seite von Docker: Installationsanleitung_. Bitte beachten Sie die Systemvoraussetzungen und die Installationsanweisungen für Ihr Betriebssystem. Erstellen eines Docker-Images ----------------------------- Beim Erstellen eines Docker-Images wird eine **Dockerfile** verwendet, um die Anwendung und ihre Abhängigkeiten zu definieren. Ein Dockerfile ist eine Textdatei, die eine Reihe von Schritten enthält, die Docker ausführen muss, um das Docker-Image zu erstellen. Die Kommandos sind fast identisch mit den Befehlen, die in Linux-Shell-Skripten verwendet werden. Eine Dockerfile kann wie folgt aussehen: .. code-block:: Dockerfile # Spezifizieren der Basis FROM python:3.8 # Kommandos werden relativ zu diesem Pfad ausgeführt WORKDIR /app # Kopieren von Dateien aus dem Host Rechner zum Working Directory COPY main.py . COPY requirements.txt . # Ausführen von Kommandos, während dem Build Prozesses RUN pip install --no-cache-dir -r requirements.txt # Falls ein Container von dieser Image ausgeführt wird, dann wird CMD ausgeführt CMD ["python", "main.py"] Damit diese Dockerfile funktioniert, muss sie sich im gleichen Verzeichnis wie die Dateien `main.py` und `requirements.txt` befinden. Die `main.py` Datei ist das Hauptprogramm, das ausgeführt wird, wenn der Container gestartet wird. Die `requirements.txt` Datei enthält alle Abhängigkeiten, die für das Ausführen des Programms erforderlich sind. Ein Beispiel für die `main.py` Datei könnte wie folgt aussehen: .. code-block:: python import requests def main(): try: # Anfragen an Google senden response = requests.get('http://www.google.com') print("Response from Google:", response.status_code) except requests.exceptions.RequestException as e: print("Error:", e) if __name__ == "__main__": main() Dieses Programm sendet eine simple Anfrage an die Google-Homepage und gibt den Statuscode der Antwort aus. Mithilfe vom Befehl .. code-block:: bash docker build -t python-docker . wird das Docker-Image erstellt. Der Parameter `-t` wird verwendet, um dem Image einen Namen zu geben. Der Punkt am Ende des Befehls gibt an, dass das Dockerfile im aktuellen Verzeichnis liegt. Durch den Befehl .. code-block:: bash docker images kann eingesehen werden, ob das Image erfolgreich erstellt wurde. Um den Container zu starten, wird der Befehl .. code-block:: bash docker run python-docker verwendet. Nun sollte die Ausgabe des Programms auf der Konsole erscheinen. Dieses Verfahren wird nun so oft angewendet, bis die gewünschte Anzahl an Containern erstellt wurden. Erstellen eines Docker-Netzwerks --------------------------------- Um ein eigenes Docker Netzwerk zu erstellen, wird der Befehl .. code-block:: bash docker network create docker-network verwendet. Wobei `docker-network` der Name des Netzwerks ist. .. _Repository: https://gitlab.informatik.hs-augsburg.de/dva/berichte-2024/05/-/tree/main/Task_1_Docker/src In diesem Beispiel wird nun der vorher erstellte Container angepasst, ein weiterer Container erstellt und beide in das Netzwerk eingebunden. Da die Dockerfiles immer das Prinzip verfolgen, werden diese in dieser Dokumentation nicht nochmals aufgeführt. Sie können jedoch im Repository_ eingesehen werden. Das gleiche gilt für die Logik der Source-Dateien, da diese als Grundlage angenommen werden. Die angepasste `main.py` Datei sieht wie folgt aus: .. code-block:: python import requests def main(): try: # Anfragen an NodeDocker senden headers = {"X-Container-Name": "PythonDocker"} response = requests.get('http://node-docker-container:3000', headers=headers) print("Response from NodeDocker (http://node-docker-container:3000):", response.text) except requests.exceptions.RequestException as e: print("Error:", e) if __name__ == "__main__": main() Der neu Dazugekommene `NodeDocker` beinhaltet folgende `server.js` Datei: .. code-block:: javascript const http = require("http"); const server = http.createServer((req, res) => { // Dymincally getting the hostname of the container const hostname = process.env.HOSTNAME; // Checking the hostname of the requester const requesterHostname = req.headers['x-container-name']; console.log(`Received request from Docker container: ${requesterHostname}`); // Sending response res.statusCode = 200; res.setHeader("Content-Type", "text/plain"); res.end("Hello from NodeDocker\n"); }); const PORT = 3000; server.listen(PORT, () => { console.log(`Server running at http://localhost:${PORT}/`); }); Da somit das Netzwerk und beide Container strukturen stehen, können nun diese Container erstellt und gestartet werden. Zum erstellen der Container wird der bereits bekannte Befehl .. code-block:: bash docker build -t node-docker . docker build -t python-docker . verwendet. Bitte beachten Sie, dass sie sich im richtigen Verzeichnis befinden. Beim starten der Container, in der richtigen Reihenfolge, wird ebenfalls ein bereits bekannte Befehl verwendet. Jedoch mit 2 weiteren Parametern: .. code-block:: bash docker run --name node-docker-container --network docker-network -d node-docker docker run --name python-docker-container --network docker-network -d python-docker Der Parameter `--name` wird verwendet, um dem Container einen Namen zu geben. Der Parameter `--network` wird verwendet, um den Container in das zuvor erstellte Netzwerk einzubinden. Der Parameter `-d` wird verwendet, um den Container im Hintergrund laufen zu lassen. Da wir mit dem Parameter `-d` den Container im Hintergrund laufen lassen, können wir die Ausgabe des Programms nicht sehen. Um die Ausgabe zu sehen, können wir den Befehl .. code-block:: bash docker logs python-docker-container docker logs node-docker-container verwenden. Nun sollte in den Logs des Pyhton-Dockers der Text "Response from NodeDocker (http://node-docker-container:3000): Hello from NodeDocker" erscheinen. In den Logs von Node-Docker sollte der Text "Received request from Docker container: PythonDocker" erscheinen. Somit haben wir erfolgreich ein Netzwerk und zwei Container erstellt, welche miteinander in 2 verschiedene Sprachen kommunizieren können.  Überlick des Netzwerks ---------------------- Das Netzwerk besteht aus 4 Dockern: * HTTP Server (Node Server) * Python Application * C# Console Application * Cloud Application .. glossary:: HTTP Server Node Server der HTTP Requests von den anderen Docker entgegennimmt und eine Response zurückschickt. Er ist über http://node-docker-container:3000 erreichbar. Python Application Einfache Application die jeweils ein HTTP Request an den Node Server + Cloud Server stellt und auf die responses wartet. C# Console Application Console Application im .NET 7.0 Framework. Diese stellt ebenfalls jeweils ein HTTP Request an den Node Server + Cloud Server und wartet auf die responses. Cloud Application Fast identisch zum HTTP-Node Server der HTTP Requests von den anderen Docker Containern entgegennimmt und eine Response zurückschickt. Der Unterschied ist das dieser Container auf einer Google Cloud Instanz läuft, heißt er ist nicht in dem selben lokalen Netzwerk von den anderen Containern. Er ist über https://nodecloudiiiii-nss7c5v2aa-uc.a.run.app erreichbar. Informationen zu den Dockerfiles + Source Code befinden sich im Repository. In den jeweiligen Files sind die Vorgangsschritte kommentiert. Den Cloud Docker Container einrichten ------------------------------------- 1. Als erstes muss das Image gebuildet werden .. code-block:: bash docker build -t cloud-docker . Zu beachten ist hier, dass man sich im Verzeichnis /src/CloudDocker befinden muss. 2. Als nächstes muss ein Tag für das erstellte Image erstellt werden um es auf DockerHub pushen zu können. .. code-block:: bash docker tag cloud-docker /cloud-docker:latest 3. Nun kann das Image gepusht werden. .. code-block:: bash docker push /cloud-docker:latest 4. Als nächstes wird auf Google Cloud Run ein Projekt angelegt und innerhalb dieses Projekts ein Service konfiguriert. Wichtig: Der CloudDocker muss auf Port 8080 hören, das ist der Standart Port auf Google Cloud. Wenn nicht, dann muss der Standart Port auf Google Cloud noch geändert werden. In der Service Konfiguration muss das hochgepushte Image verlinkt werden: /cloud-docker:latest Nun sollte der Container in der Google Cloud laufen und erreichbar sein. Das Netzwerk aufbauen --------------------- 1. Als erstes müssen alle Images gebuildet werden .. code-block:: bash docker build -t node-docker . docker build -t python-docker . docker build -t csharp-docker . Zu beachten ist hier, dass Sie sich immer im Verzeichnis des jeweiligen Dockerfiles befinden müssen. 2. Als nächstes muss das Netzwerk erstellt werden in dem sich zukünftig die Container befinden. .. code-block:: bash docker network create docker-network 3. Nun müssen die Container erstellt werden die sich dann im zuvor erstellten Netzwerk befinden. .. code-block:: bash docker run --name node-docker-container --network docker-network -d node-docker docker run --name python-docker-container --network docker-network -d python-docker docker run --name csharp-docker-container --network docker-network -d csharp-docker 4. Um zu überprüfen ob alles geklappt hat, einfach die jeweiligen Logs auslesen .. code-block:: bash docker logs python-docker-container docker logs node-docker-container docker logs csharp-docker-container Quellen ------- https://medium.com/@jaydeepvpatil225/containerize-the-net-core-7-console-application-using-docker-86e537321592 https://docs.docker.com/desktop/