Lange wurde hier im Blog nichts mehr geschrieben, aber nun m├Âchte ich das Schreiben wieder aufnehmen. Bitte entschuldigt, wenn der Satzbau noch etwas holprig ist. Das wird sich mit den kommenden Artikel alles wieder finden.

Ich m├Âchte heute ein paar Worte ├╝ber die Einrichtung eines Servers mit Docker, also einem Docker-Host verlieren.

F├╝r den schnellen Going-Live einer Applikation bietet sich noch immer ein Root-Server, egal ob Bare Metal oder VM, an. VMs gibt es mittlerweile wie Sand am Meer und man ist noch nicht mal gezwungen zu einem der drei Gro├čen (AWS, Azure, Google Cloud) zu gehen. Empfehlen kann ich beispielsweise die VMs in der Hetzner Cloud.

Ich gehe in diesem Beispiel von einer Maschine mit Debian Buster aus, zu der ihr Root-Zugang erhalten haben solltet. Debian ist bei mir das Mittel der Wahl, weil es im Verh├Ąltnis zu einigen anderen Distributionen, wie beispielsweise Ubuntu noch mal deutlich schlanker daherkommt. Weniger Pakete reduzieren den Update-Aufwand und die m├Âglichen Angriffsvektoren. Das Host-System muss ja auch eigentlich gar nicht viel k├Ânnen. Im Gegenteil: Ich m├Âchte es sauber halten, um mich vom Wirt nicht zu stark abh├Ąngig zu machen und ihn im Zweifel auch schnell wieder ersetzen zu k├Ânnen - parasite mode on!

Docker Engine - der Stoff aus dem die Tr├Ąume gemacht sind ­čÉ│

Die offizielle Dokumentation beschreibt ziemlich gut, wie man Docker auf der Maschine installiert bekommt. Ein paar Gedanken dazu:

  1. Ich nutze grunds├Ątzlich die Installation mittels Repository, worauf in diesem Artikel auch der Fokus liegt.
  2. Eine Ausnahme stellt nur die Installation von Docker auf einem RaspberryPi dar. Da dies ├╝ber das Repository nicht funktioniert. Dann greife ich auf das Convenience Script zur├╝ck.
  3. Das Anpinnen einer bestimmten Docker-Version ist sinnvoll, allerdings versuche ich immer mit der aktuellen stabilen Version zu arbeiten. Derzeit ist das 19.03.5. Ich pinne die Version also in der Regel nicht an.

Auf der Debian-Maschine wird durch den Root-Login kein sudo genutzt. Das k├Ânnen wir also weglassen.

Zun├Ąchst wird der Paket-Index aktualisiert:

apt-get update

Als n├Ąchstes werden die Abh├Ąngigkeiten installiert:

apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg2 \
    software-properties-common

Jetzt l├Ądt man den GPG-Schl├╝ssel von Docker, um die Signierung des Pakets zu ├╝berpr├╝fen:

curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -

Anschlie├čend kann man den Fingerprint des Paketes ├╝berpr├╝fen:

apt-key fingerprint 0EBFCD88

Nun wird dem Paket-Manager apt eine neue Quelle hinzugef├╝gt, ├╝ber die die Docker-Engine heruntergeladen werden kann:

add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/debian \
   $(lsb_release -cs) \
   stable"

Jetzt wird der Paket-Index erneut aktualisiert und Docker installiert:

apt-get update
apt-get install docker-ce docker-ce-cli containerd.io

Anschlie├čend kann man einen ersten Hallo-Welt-Container starten, um die Funktionsf├Ąhigkeit von Docker zu ├╝berpr├╝fen.

docker run hello-world

Erh├Ąlt man eine passende Ausgabe, haben wir Docker erfolgreich installiert.

N├╝tzliches

Ein paar n├╝tzliche Helfer d├╝rfen es nat├╝rlich doch sein:

apt install fail2ban git htop nano mc rsync sudo unzip
  • fail2ban blockt ungew├╝nschte SSH-Verbindungen
  • git kann man f├╝r die Arbeit mit Git-Repos immer gebrauchen
  • htop f├╝r die Anzeige der Auslastung in einer moderneren Darstellung als es top erm├Âglicht.
  • nano: Ja, ich nutze nano und nicht vi oder vim
  • mc: Manchmal finde ich die Darstellung des Midnight Commanders auch ganz praktisch.
  • rsync ist ideal, um Daten auch ├╝ber die Grenzen von Maschinen hinweg zu bewegen
  • sudo macht f├╝r das Herabsetzen von Nutzerprivilegien Sinn.
  • Um mit gezippten Daten zu arbeiten, macht auch unzip Sinn.

Hierbei bringt jeder sicherlich noch seine pers├Ânlichen Pr├Ąferenzen ein, aber diese Liste soll ein erster Anhaltspunkt sein.

Post-Installation Steps

Wie in der offiziellen Doku erw├Ąhnt, kann man nun auch noch weitere Ma├čnahmen ergreifen, sobald die Docker Engine installiert ist. Interessant finde ich die Einrichtung eines weiteren Systemnutzers, damit man nicht immer auf den Root-Nutzer zur├╝ckgreifen muss, wenngleich der zus├Ątzliche Systemnutzer Zugriff auf den Docker-Socket bekommt und dadurch Root-├Ąhnliche Rechte erh├Ąlt. Docker rootless zu betreiben ist seit L├Ąngerem ein Thema. Hier kann man dazu Weiteres lesen.

Zur├╝ck zum Thema! Bei der Docker-Installation unter Debian wird bereits eine Gruppe "docker" angelegt. Es braucht also nur noch einen eigenen Systemnutzer und anschlie├čend weisen wir dem die Docker-Gruppe zu.

adduser deploy
usermod -aG docker deploy

Anschlie├čend sollte es m├Âglich sein einen Hallo-Welt-Container als Nutzer "deploy" zu starten.

su deploy
docker run hello-world

Wenn man auch nun wieder wieder Ausgabe des Containers sehen kann, hat die Einrichtung funktioniert.

Kernel Tweaks

Docker setzt im Wesentlichen auf Features des Linux-Kernels, wie chroot, Namespaces, etc. Der Kernel ist somit essentiell f├╝r das Betreiben von Containern. ├ťber die Jahre haben sich so einige Probleme mit Limits, die im Kernel gesetzt sind ergeben. Auf manche st├Â├čt man recht schnell. Beispielsweise beim Starten von Redis oder Elasticsearch, wird man darauf hingewiesen gewisse Einstellungen zu t├Ątigen, um die volle Leistung nutzen zu k├Ânnen. Auf andere Probleme st├Â├čt man erst, wenn man die 10. MySQL-Instanz auf der Maschine starten m├Âchte.

Es folgen meine Kernel Tweaks, die sich im Laufe der Jahre durch den langen Betrieb von Containern ergeben haben. Diese lege ich in einer eigenen Datei unter /etc/sysctl.d/80-docker.conf ab.

# Elasticsearch optimization
vm.max_map_count=262144

# MySQL optimization
fs.aio-max-nr=1048576

# Redis optimization
vm.overcommit_memory=1
net.core.somaxconn=65535

# Have a larger connection range available
net.ipv4.ip_local_port_range=1024 65000

# Reuse closed sockets faster
net.ipv4.tcp_tw_reuse=1
net.ipv4.tcp_fin_timeout=15

# The maximum number of "backlogged sockets".  Default is 128.
# net.core.somaxconn=4096
net.core.netdev_max_backlog=4096

# 16 MB per socket - which sounds like a lot, but will virtually never consume that much.
net.core.rmem_max=16777216
net.core.wmem_max=16777216

# Various network tunables
net.ipv4.tcp_max_syn_backlog=20480
net.ipv4.tcp_max_tw_buckets=400000
net.ipv4.tcp_no_metrics_save=1
net.ipv4.tcp_rmem=4096 87380 16777216
net.ipv4.tcp_syn_retries=2
net.ipv4.tcp_synack_retries=2
net.ipv4.tcp_wmem=4096 65536 16777216
#vm.min_free_kbytes=65536

# Connection tracking to prevent dropped connections (usually issue on LBs)
net.netfilter.nf_conntrack_max=262144
# Error by setting the following: cannot stat /proc/sys/net/ipv4/netfilter/ip_conntrack_generic_timeout: No such file or directory
net.ipv4.netfilter.ip_conntrack_generic_timeout=120
net.netfilter.nf_conntrack_tcp_timeout_established=86400

# ARP cache settings for a highly loaded Docker Swarm
net.ipv4.neigh.default.gc_thresh1=8096
net.ipv4.neigh.default.gc_thresh2=12288
net.ipv4.neigh.default.gc_thresh3=16384

System sauber halten

Trotz schlanker Container, wird beim kontinuierlichen Bauen und Ausrollen neuer Versionen das System doch schnell voll. Daf├╝r bietet Docker die Prune-Befehle, die ich auch in diesem Artikel thematisiert habe, allerdings nutze ich die darin beschrieben automatischen M├Âglichkeiten so nicht mehr. Mittlerweile ist daf├╝r ein eigenes Projekt entstanden, welches auf GitHub zu finden ist.

Beim Deployment von Containern setze ich auf den Swarm Mode und so starte ich den Service zum regelm├Ą├čigen Aufr├Ąumen der Maschine als Docker Stack.

Zun├Ąchst lade ich das Compose-File dazu in einen neuen Ordner:

mkdir -p /var/www/srv-cleanup
cd /var/www/srv-cleanup
wget https://raw.githubusercontent.com/servivum/docker-host-cleanup/master/docker-compose.production.yml

Nun initialisiere ich den Swarm Mode und starte den Stack:

docker swarm init
docker stack deploy -c docker-compose.production.yml srv-cleanup

Abschlie├čend kann man noch einen Blick darauf werfen, ob der Dienst erfolgreich gestartet wurde, was nat├╝rlich etwas dauert, da zun├Ąchst noch das Container-Image aus dem Docker Hub geladen werden muss.

docker stack services srv-cleanup

Abschlie├čende Gedanken

Um ein produktives System zu betreiben, reichen diese Ma├čnahmen nat├╝rlich noch nicht aus. Es braucht ein Backup-and-Restore-Konzept, sowie ein Monitoring, welches ├╝ber den Zustand der Maschine berichtet.

Interessant ist zudem, wie man Web-Applikationen schnell und einfach mit HTTPS-Verschl├╝sselung online bringen kann. Diesem Thema widme ich mich in einem der kommenden Beitr├Ąge.