Was ist Docker?

Docker ist einer der beliebtesten und bekanntesten Implementation von einer „containerized software“ welches eine Cross-Platform und skalierbare Umgebung zur Software Entwicklung bieten.

Warte… was ist „containerized software“?

Starten wir mit einem Beispiel. Sagen wir einmal deine Haupt-Entwicklungsmachine läuft auf Ubuntu und du möchtest eine eine simple Webseite erstellen. Natürlich kannst du auf dieser Maschine einen nativen Web-Server installieren aber dieser funktioniert dann nur auf dieser Maschine mit diesem Betriebssystem.

Container sind prinzipiell ein sehr reduziertes Betriebssystem auf dem nur das minimalste installiert ist, um die darin verwendete Software zum laufen zu bringen. Also in unserem Beispiel könnten wir einen NGINX Webserver in unserem container installieren mit dem wir dann unsere Webseite aufbauen.

Der Hauptvorteil hier ist, dass das ganze Docker Eco-System mit unterschiedlichen Betriebssystem kompatibel ist. Das heist deine im Linux entwickelte Webseite kann auch unter MacOS oder in einer Windows Maschine gestartet und weiterentwickelt werden.

Also geht es prinzipiell nur um Container?

Nicht ganz. Wir müssen noch klären was „images“ sind.

Wie vorhin schon beschrieben beinhaltet ein Container die Implementation deiner App mit allem drum herum fertig konfiguriert. Aber was ist wenn du noch eine weitere Webseite aufbauen willst?

Damit du nicht jedes mal immer von 0 anfange musst gibt es vordefinierte „images“ welche ein vor-konfiguriertes System zur Verfügung stellen in dem du dann nur mehr deinen Code hinzufügen müsst.

„Images“ können also sozusagen als Entwürfe für Container gesehen werden.

Alle verfügbaren offiziellen Images können hier gefunden werden: https://hub.docker.com/search?q=&type=image

Ich möchte meinen ersten eigenen Container erstellen!

Zuerst muss sichergestellt sein, dass die aktuellste Version von Docker installiert ist: https://docs.docker.com/get-docker/

Danach kann folgender Befehl in der CLI ausgeführt werden

docker run --name some-nginx -v /Users/kevin/dockertest:/usr/share/nginx/html:ro -d -p 8080:80 nginx
  • docker: Docker CLI Befehl welcher allgemein zur Docker-Verwaltung in der CLI dient
  • run: Erstelle einen neuen Container
  • –name: Gib dem Container einen Namen
  • some-nginx: Der Name
  • -v: Verbinde einen lokalen Order mit einem Ordner im Container
  • /Users/kevin/dockertest:/usr/share/nginx/html:ro
    • /Users/kevin/dockertest: Der absolute Pfad auf dem Host-System der gemounted werden soll
    • /usr/share/nginx/html: Der absolute Pfad im Container
    • ro: read-only mode (Siehe hier)
  • -d: Lass den Container außerhalb der aktuellen Shell laufen (Siehe hier)
  • -p: Verbinde einen Host Port mit einem container internen port
    • 8080: Der Port, der im Host-System verwendet wird
    • 80: Der Port, der im Container-System verwendet wird
  • nginx: Der Name des image auf dem der Container aufbaut

Bitte passe /Users/kevin/dockertest an deinen absoluten Pfad an

Falls du mit Linux oder MacOS arbeitest solltest du nun folgenden CLI Output sehen

-> % docker run --name some-nginx -v /Users/kevin/dockertest:/usr/share/nginx/html:ro -d -p 8080:80 nginx
1d7b787fcbe8d6ee71b9e09908a2027c48d5c4b6cd146eb26314a0dacf763f2a

Nun können wir eine index.html in unserem Ordner erstellen.

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">
  <title>It works!</title>
</head>

<body>
  <h1>Hello!</h1>
</body>
</html>

Wenn wir nun folgende URL aufrufen http://localhost:8080 sollten wir folgenden Output sehen.

Du kannst nun ebenso alle laufenden Docker Container im Docker Dashboard (sollte mit Docker mitinstalliert worden sein) sehen oder über folgenden Befehl in der CLI:

-> % docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS                  NAMES
d6ee6e327100   nginx     "/docker-entrypoint.…"   12 minutes ago   Up 12 minutes   0.0.0.0:8080->80/tcp   some-nginx

Hier sehen wir also einen benannten Container some-nginx welcher vor 12 Minuten erstellt wurde und ein Port Mapping vom Host-Port 8080 auf den Container-Port 80 aktiv hat.

Weitere nützliche CLI Befehler

  • docker images: Zeige alle installierten Images
  • docker rmi %image-name%: Lösche ein definiertes Image
  • docker pull %image-name%: Aktualisiere ein Image auf die aktuellste Version
  • docker ps: Zeige alle aktiven Container
  • docker ps -a: Zeige alle Container (auch gestoppte)
  • docker rm %container-name%: Entferne einen speziellen container

Docker NFS Implementation für bessere Performance in MacOS 11 (BigSur)

Allem voran: Docker allgemein ist ein sehr beliebtes und bekanntes Tool und bietet viele Möglichkeiten es zu verwenden und zu konfigurieren. Die folgende Beschreibung zeigt einen Weg der für mich eine funktionierende Entwicklungsumgebung in MacOS möglich gemacht hat.

Es ist nun schon einige Zeit bekannt, dass Docker unter MacOS Probleme macht. Meistens geht es um das Thema Performance wo, im Vergleich zu „native Linux“ und auch Windows MacOS sehr weit hinten liegt.

Das Problem

Die Basis des ganzen Problems liegt darin wie MacOS das Dateisystem von Docker Container mounted.

In Linux wird das sogenannte namespacing verwendet um die unterschiedlichen Prozesse und Ressourcen zu „gruppieren“ und damit nur in dieser Gruppe Zugriff aufeinander erhalten. Dieses namespacing Feature ist nur im Linux Kernel vorhanden und MacOS basiert nicht auf Linux.

Daher laufen Docker Container unter Linux quasi wie „native“ Applikationen da diese nicht wirklich „virtualisiert“ werden. Jedoch funktioniert dies eben unter MacOS nicht wie unter Linux, daher entsteht hier Latenz und Performance Probleme.

Folgendes Video (bzw. die Video Serie) von LiveOverflow kann ich definitiv empfehlen falls jemand mehr zum Thema namespacing und warum Linux docker containers keine VMs sind wissen möchte.

Die Lösung

Die folgende Beschreibung habe ich in diesem Blog-Post gefunden:

https://www.jeffgeerling.com/blog/2020/revisiting-docker-macs-performance-nfs-volumes

Daher danke an Jeff Geerling.

Zusammenfassend:

Bearbeite/Erstelle die Datei /etc/exports und füge folgende Zeile hinzu:

/System/Volumes/Data -alldirs -mapall=501:20 localhost

Diese Datei ist eine Konfigurations-Datei für das NFS System welches unter MacOS zur Verfügung steht. Hier wird also der Pfad /System/Volumes/Data inklusive aller Unterordner über den localhost zur Verfügung gestellt.

Hier kann es sein, dass ein Popup auftaucht, welches bzgl Berechtigungen Zugriff erfragt. Bitte dieses aktzeptieren.


Bearbeite/Erstelle die Datei /etc/nfs.conf und füge folgende Zeile hinzu:

nfs.server.mount.require_resv_port = 0

Diese Zeile wird benötigt damit der NFS Daemon über einen beliebigen Port Verbindungen zulässt. Andernfalls kann der Docker keine Verbindung zum NFS Daemon herstellen.


Nun muss der NFS Daemon neu gestartet werden.

sudo nfsd restart

Final muss Full Disk Access zum NFS Daemon gewährt werden.

Hierfür bitte unter System Preferences ==> Security & Privacy ==> Tab Privacy ==> Full Disk Access

Nun unten links auf das Schloss Symbol klicken um das bearbeiten der Einträge freizuschalten.

Danach das + Icon klicken um einen neuen Eintrag hinzuzufügen.

Nun bitte CMD + Shift + G drücken und den absoluten Pfad /sbin/nfsd eingeben und GO drücken

Der nfsd Daemon sollte nun in der Liste für Full Disk Access auftauchen und die Checkbox aktiviert sein.


Der letzte Schritt ist nun nur mehr die eigene docker-compose.yml anzupassen um NFS anstatt des „normalen volume binding“ zu verwenden.

Hier ein Beispiel für das „normalen volume binding“

version: "3.8"
services:
  web:
    container_name: sl_web
    build:
      context: .
      dockerfile: docker/web/Dockerfile
    ports:
      # 'host:container'
      - '80:80'
    networks:
      - frontend
      - backend
    volumes:
      - ./webvol:/var/www/html:cached

  mysql:
    container_name: sl_db
    image: mysql:5.7
    command: mysqld
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_unicode_ci
      --init-connect='SET NAMES UTF8;'
    ports:
      # 'host:container'
      - '3306:3306'
    networks:
      - backend
    environment:
      - MYSQL_ROOT_PASSWORD=sl_db_root_password
      - MYSQL_DATABASE=sl_db_name
      - MYSQL_USER=sl_db_user
      - MYSQL_PASSWORD=sl_db_password

  phpmyadmin:
    container_name: sl_pma
    image: phpmyadmin/phpmyadmin
    networks:
      - frontend
      - backend
    environment:
      PMA_HOST: mysql
      PMA_PORT: 3306
      PMA_USER: root
      PMA_PASSWORD: root
    ports:
      # 'host:container'
      - '8080:80'

networks:
  frontend:
  backend:

Wie im web container zu sehen wird das „normalen volume binding“ verwendet um den Inhalt des webvol Ordners des Hosts in den /var/www/html Ordner des Containers zu mounten.

Um dies nun zu ändern bitte ./webvol:/var/www/html:cached ändern auf nfsmount:/var/www/html:cached

  web:
    ...
    volumes:
      - nfsmount:/var/www/html:cached

Hiermit wird Docker gesagt, dass das „named volume“ nfsmount in den angegebenen containers Pfad gemounted werden soll.

Nun müssen wir aber das „named volume“ am Ende der docker-compose.yml noch definieren.

volumes:
  nfsmount:
      driver: local
      driver_opts:
          type: nfs
          o: addr=host.docker.internal,rw,nolock,hard,nointr,nfsvers=3
          device: ":$PWD/webvol"

Wie hier zu sehen muss nun der verwendete Document Root Pfad in der device config definiert werden

Und damit sind wir fertig!

Zum testen füge eine nfo.php in den webvol Order, starte den Container und öffne localhost/nfo.php. Dieses sollte folgendes anzeigen

Das gesamte Projekt-Template kann HIER heruntergeladen werden.

Docker-Engine Debug-Mode standardmäßig aktiv

Nachdem ich mit einem Freund im BoltCMS Slack über das Docker Performance Problem geredet habe hat mich dieser darauf hingewiesen, dass Docker standardmäßig debugging aktiv hat in der Docker Engine (danke an Simon vom BoltCMS Slack Channel)

Wie hier zu sehen ist dieses hier auf true gesetzt.

Nachdem dieser Wert auf false gesetzt wurde hat sich die Performance meiner container wesentlich verbessert.

Ich habe keine Ahnung warum Docker diesen Wert standardmäßig setzt bzw. aktiviert (zu mindest am MacOS Client) aber diese Änderung in Kombination mit dem NFS mount hat mir einen wesentlichen Performance Schwung gebracht.