Docker

Docker ist eine Open-Source-Software zur Isolierung von Anwendungen mit Containervirtualisierung.

Docker vereinfacht die Bereitstellung von Anwendungen, weil sich Container, die alle nötigen Pakete enthalten, leicht als Dateien transportieren und installieren lassen. Container gewährleisten die Trennung und Verwaltung der auf einem Rechner genutzten Ressourcen. Das beinhaltet laut Aussage der Entwickler: Code, Laufzeitmodul, Systemwerkzeuge, Systembibliotheken – alles was auf einem Rechner installiert werden kann.

Quelle Wikipedia

Vorteile:

  • Docker zieht eine Abstraktionsebene ein, d.h. die darunterliegende Struktur (LAN, Storage, Distribution und Paketabhängigkeiten) muss bei der Anwendung nicht betrachtet werden. Überall lauffähig, nur die docker-version muss beachtet werden
  • Ressourceneinsatz (Massenhosting)
  • Skalierbarkeit (Lastspitzen abfangen)
  • Deployment kann schnell und oft erfolgen

Nachteile:

  • in Hochsicherheitsumgebungen nicht einsetzbar
    • nicht zertifizierbar, da Isolierung der Anwendungen nur durch den docker-daemon erfolgt (gleicher Kernel für alle etc.)
    • zur Abschottung wird eine VM außendrum benötigt (oder halt kubernets mit seinen pods)
    • Angriffsvektoren durch alpine Distribution
    • extern gebaute Images sind ein Risiko, Einzelfallprüfung vom wem ein image gebaut wird (Signierung wäre nötig…)
    • Rootless Containers oft schwierig
  • monolitische Software (mehrere Dienste in einem Container …) lassen Vorteile ungenutzt
  • Anwendungen die stateful sind (Datenbanken, Storagecluster)
  • umfangreiche Umgebungsabhängigkeiten (Repository, docker registry, CI/CD-Pipeline, Orchestrierung wie docker swarm oder kubernetes)
  • debugging wird komplexer:
    • Zusammenspiel der microservices
    • networking
    • in Container hinein gehen, teilweise stehen dort debugging tools nicht zur Verfügung

Welche Software ist geeignet?

geeignet:

  1. eigenprogrammierte Software mit CI/CD-Deployment
  2. microservice-Architektur
  3. stateless services

eher nicht

  1. Drittanbietersoftware ohne microservices
  2. stateful-software wo Skalierung unerwünscht oder kontraproduktiv ist

Bevorzugte Methode ist die Einbindung via repo, fast immer ist die bei Distributionen mitgelieferte Version zu alt.

Siehe Installationshandbuch, z.B. Ubuntu oder Debian.

docker-compose

docker ohne root ausführen

Aktuellen Benutzer zur Gruppe docker hinzufügen

sudo usermod -aG docker $USER

→ ausloggen und einloggen nötig

Kernel Kompatibilität

Docker braucht mindestens die Kernel Version 3.10. Ob die richtigen Konfiguration aktiviert ist, lässt sich mit dem Skript check-config.sh überprüfen:

curl https://raw.githubusercontent.com/docker/docker/master/contrib/check-config.sh > check-config.sh
bash ./check-config.sh

bei Ubuntu 18.04 nötig:

/etc/default/grub:

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash cgroup_enable=memory swapaccount=1"
sudo update-grub

Optional:

Testen ob cgroup-memory limit wirkt

docker run -m=4096000000 -d  -t busybox sleep 3600

cat /sys/fs/cgroup/memory/docker/$ID/memory.limit_in_bytes

…mit tatsächlichem Prozess (Quelle https://stackoverflow.com/questions/20096632/limit-memory-on-a-docker-container-doesnt-work):

sudo docker -m 512M -it ubuntu /bin/bash
sudo apt-get update && apt-get install -y build-essential vim

→ vim foo.c:

#include <stdlib.h>
#include <stdio.h>

int main(void) {
    int i;
    for (i=0; i<65536; i++) {
        char *q = malloc(65536);
        printf ("Malloced: %ld\n", 65536*i);
    }
    sleep(9999999);
}

Compile the file

gcc -o foo foo.c
./foo

lokale Netze von Docker schützen:

Beispiel: lokal nur noch das Netz 10.254.0.1/16 benutzen:

(als root) echo '{"bip": "10.254.0.1/16"}' > /etc/docker/daemon.json
sudo systemctl restart docker.service
  • ggf. Netzwerke aufräumen:
    docker network prune
  • routen checken
  • docker0 bridge da?
    sudo brctl show

docker Apparmor

docker wendet für Gäste das Profil docker-default an.

Dazu wird beim Starten eines Containers ein docker-default template in tmpfs abgelegt und von dort in den Kernel geladen.

Oder es wird das Profil benutzt, das via security-opt angegeben wurde:

docker run --rm -it --security-opt apparmor=docker-default hello-world

.

Für den docker daemon selbst wird aktuell kein apparmor-Profil geladen.

Doku: AppArmor security profiles for Docker

  • Version anzeigen:
    docker -v

Container Laufzeitbefehle

Container:

  • start: docker start $ID
  • stop Container: docker stop $ID
  • restart: docker restart $ID
  • Befehle in einem Container mit $ID bash ausführen:
    docker exec -ti $ID bash
  • attach: docker attach $ID
  • detach (funktioniert nur wenn -ti angegeben wurde!): STRG-P STRG-Q 1)

Container via netzwerk anbinden

Container exposen via Netzwerk:

docker run -d -p host_ip:host_port:container_port --name NAME image_name

Beispiel (offizielles redis-image):

docker run -d -p 6379:6379 --name redis redis

Netzwerke

  • anzeigen:
    docker network ls
  • anlegen:
    docker network create my_network
  • informationen:
    docker network inspect my_network
  • container in das Netzwerk integrieren: Parameter
    --net my_network

    oder über yml-Datei

Container via Firewall absichern

Wenn Container nach Außen exposed werden, legt Docker entsprechende Iptables-Regeln an. Diese lassen sich leider nicht anpassen, nur das anlegen von iptables-Regeln abschalten mit –iptables=false.

Docker (ab Version 17.06) hat aber für iptables die chain DOCKER-USER erdacht. Dort hinterlegte Regeln werden zuerst durchlaufen und von docker in Ruhe gelassen. Dort können also Regel hinterlegt und permanent gemacht werden (mittels mit iptables-persistent).

Beispiel für Port 9200 (Elasticsearch):

iptables -I DOCKER-USER 1 -i eth0 -s 91.213.91.0/24 -p TCP --dport 9200 -j ACCEPT
iptables -I DOCKER-USER 2 -i eth0 -p TCP --dport 9200 -j LOG --log-prefix "Port9200ExtBlock:" --log-level 6
iptables -I DOCKER-USER 3 -i eth0 -p TCP --dport 9200 -j REJECT

Löschen wäre (hier Regel nr.1):

iptables -D DOCKER-USER 1 usw.

Auflistung aktuell gültiger Regeln:

iptables -L -n --line-numbers

Diagnose /Statistiken/ Informationen

Container auflisten:

  • laufende: docker container ls
  • alle: docker container ls –all
  • ID anzeigen:
    docker ps --no-trunc
    • ID des zuletzt gestarteten containers anzeigen:
      docker ps -l -q

      2)

  • ausführliche Informationen anzeigen: docker inspect $ID
  • Ressourcenverbrauch laufende Container: docker stats
  • Prozesse eines Containers anzeigen: docker top $ID
Logging

Logging ist bei docker vielfältig konfigurierbar.

docker logs $ID

„docker logs“ funktioniert abwer nicht mit anderen loggin driver als json-file und journald.

Image startet nicht

überschreibt den entrypoint aus dem image, einloggen mit bash ist möglich:

docker-compose.yml:

entrypoint:
  - "/usr/bin/tail"
  - "-f"
  - "/dev/null"

Image-handling

Docker images anzeigen:

docker image ls

image erstellen

Dockerfile ↔ Docker Compose file image bauen ↔ image deployen

Beispiele:

  1. Image „ubuntu“ holen + starten (-d im Hintergrund):
    docker run -d -ti ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"
  2. docker run -d ubuntu /bin/sh -c "apt-get install -y hello"
  1. docker run hello/world

image erstellen (mit Dockerfile):

image aus docker-compose-file erstellen:

docker-compose -f $COMPOSE-FILE.yaml up

image aus Dockerfile erstellen:

  1. Dockerfile anlegen und ins Verzeichnis wechseln (tag latest wird angehängt)
    FROM base
    RUN apt-get install hello
    CMD hello
    docker build $USER/$IMAGENAME .
  2. oder Pfad zu meinDockerfile angeben: → docker build -t meineHalloApp -f meinDockerfile
  3. docker build -t meineHalloApp .

image aus laufendem container erstellen

  • docker run -d ubuntu /bin/sh -c "apt-get install -y hello"
  • docker commit -m "1st container" $ID blindguardian/hallo

best practises image-bau

  1. allgemein bei docker images: Prozess sollte als PID 1 laufen damit es die Signale bekommt (SIGTERM, …)
  2. möglichst wenige RUNs: (neue files, neue layers)
  3. tini als init-system damit die bash nicht uid 1 wird (und damit nicht loggt weil sie denkt init zu sein):
    1. NOTE: If you are using Docker 1.13 or greater, Tini is included in Docker itself. This includes all versions of Docker CE. To enable Tini, just pass the –init flag to docker run.
  4. ReadMe.md anlegen im git-Repo

Schlecht:

  1. config-file ins image und dann kein vollwertiges base-linux drinhaben (nur per export auslesbar)
  2. config-file extern als volume einhängen (liegt auf host rum…)

gut:

  1. config als extra layer einbauen das das install-Image aus einer registry benutzt und dann schnell neu gebaut werden kann
  2. jedes image kommt aus einem eigenen repository (erleichert automatisierten image neubau, was wurde wann zuletzt benutzt, gebaut?)

Registries benutzen

neben der öffentluchen Standard-registry von hub.docker.com können weitere registries verwendet werden. Insbesondere wenn die images privat sein sollen und um zu verhindern das durch externe Fehler Applikationen ihren Betrieb einstellen.

  1. offizielle registry (öffentlich): https://hub.docker.com/explore/
  2. privates Repo auf docker hub mit Anmeldung:
    1. eigene Registry:
      docker login https://meine.registry.de
    2. mehr als ein privates Repo erfordert Geldeinsatz
  3. Docker Trusted Registry enthalten in Docker Enterprise Edition
  4. Docker Registry als Dienst
  5. Registry 2.0 (Roadmap) soll der Nachfolger werden
  6. weitere Software
unsichere registry

unsichere registries sollten nicht benutzt werden, aber falls weder verschlüsselung noch Login gewünscht ist:

Datei /etc/docker/daemon.json:

{    "insecure-registries": ["unsicher.registry.de:80"] }

Restart Docker:

sudo systemctl restart docker.service

image in registry committen

  • docker commit -m "Beschreibung" $ID user/repo1
  • image auf private registry hochladen:
    • Login:
      docker login $URL
    • push:
      docker push Benutzer/projekt

image löschen

docker rm $short_id

Image exportieren

Methode 1 (docker save):

docker save --output="registry-cleanup.tar" $IMAGE

$IMAGE z.B. hello/world, :!: das tar-archiv enthält aber „nur“ die einzelnen layer (in jeweils tar-Archiven)

Methode 2 (docker export) (laufendes image exportieren):

  • in tar-Archiv:
    docker export --output="latest.tar" $NAME
  • in tar.gzip-Archiv:
    docker export $NAME | gzip > NAME.tar.gz

Image importieren

aus tar.gz-Archiv:

zcat NAME.tar.gz | docker import - NAME

Beispiele für images

Volume-handling

Im Gegensatz zu den flüchtigen Containern sind volumes geeignet permanente Daten aufzunehmen („persistent storage“).

-v /tmp/data:/data

  • erzeugen:
    docker volume create --name vol1
  • mounten:
    docker run -d -v vol1:/container/pfad/für/das/volume container_image my_Befehl
  • auflisten:
    docker volume ls
  • details:
    docker volume inspect volume_name
  • unbenutzte volumes anzeigen:
    docker volume ls -f dangling=true
  • unbenutzte volumes löschen 3):
    docker volume prune
  • bestimmtes Volume löschen:
    docker volume rm <volume name>

docker swarm

docker swarm ist eine einfache (in docker bereits enthaltenen) Funktionalität Container über mehrere Nodes verteilt laufen zu lassen.

Initialiseren:

docker swarm init
"Swarm initialized: current node (n352jdg9eoml9nh5ak574izs5) is now a manager.

To add a worker to this swarm, run the following command:

<code bash>docker swarm join --token SWMTKN-1-4a8ilhha4g652ll28ecg7z4pyxdvglnicufxlj2mh5l5ly5nm3-7s7yvbki7m9pfi2yzafter6l6 10.10.0.23:2377</code>

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions."

In diesem Beispiel reicht zum Beitritt

  1. als node:
    docker swarm join --token SWMTKN-1-4a8ilhha4g652ll28ecg7z4pyxdvglnicufxlj2mh5l5ly5nm3-7s7yvbki7m9pfi2yzafter6l6 10.10.0.23:2377
  2. oder als weiteren manager die folgende Ausgabe auszuführen:
    docker swarm join-token manager

Aus dem docker swarm austreten:

  • docker swarm leave
  • wenn manager:
    docker swarm leave --force

*Informationen über sich selbst anzeigen:

docker node inspect self

Nodes

Nodes sind

  1. Active (neue tasks können zugewiesen werden)
  2. Pause (keine neuen, aber bestehende tasks bleiben)
  3. Drain (keine neuen, und existiere task werden heruntergefahren.
    docker node update --availability drain node1

Nodes managen:

  • Nodes auflisten:
    docker node ls

Manager

Manager sind

  1. Reachable
  2. Unavailable
  3. Leader
docker stack ls
NAME                SERVICES
content-sync        2

Services

  • global: 1x pro Node
  • replicated: x Instanzen

Labels

Labels: (wo fährt was hoch?), Beispiel: nur hochfahren wenn label db1 auf der node vorhanden ist:

   deploy:
      placement:
        constraints:
          - node.labels.db1 == TRUE
  1. Label hinzufügen:
    docker node update --label-add LABEL1
  2. Label updaten:
    docker node update --label-add LABEL2=TEST node1

Zusatztools

docker-machine

docker-machine ist ein tool um docker-Hosts zu provisionieren (auf denen dann docker container laufen können).


2)
Alias in der Shell anlegen:
alias dl='docker ps -l -q'
3)
oder bzw. bei älteren Versionen:
docker volume rm $(docker volume ls -f dangling=true -q)
siehe auch: https://stackoverflow.com/questions/18496940/how-to-deal-with-persistent-storage-e-g-databases-in-docker?rq=1