Was ist Docker?

  • “Docker enables apps to be quickly assembled from components
    […]
    As a result, IT can ship faster and run the same app, unchanged, on laptops, data center VMs, and any cloud.”

    https://www.docker.com/whatisdocker/

  • Docker basiert auf Linux Containers
    • Linux Containers bauen auf cgroups (control groups) im Linux Kernel auf
    • separater Process- und Network-space für Prozess-Gruppen, CPU, Speicher etc. können beschränkt werden
    • Technologie gibt es schon länger (z.B. LXC), Docker hat sie wesentlich benutzerfreundlicher gemacht

Docker vs. VMs

  • Docker Container haben einen sehr geringen Overhead, benötigen kaum Ressourcen, kein langes Booten
  • Verschiedene Docker-Container teilen sich ein Image – keine Virtuellen HDDs wie bei VMs

Vorteile

  • Ein Container kann auf beliebiger Hardware laufen, ob auf dem Notebook des Entwicklers, einem Test- oder Produktivsystem oder in der Cloud
  • Container sind “billig”. Ideal für CI: Container bauen, testen, wegwerfen
  • Docker macht Deployment einfach. Löst das Problem dass Python-Projekte oft umständlich und nur mit Kenntnissen zu installieren sind (virtualenv, pip, libjpeg-dev, etc.)
  • Updates des Host-Systems sind kein Problem (z.B. kann Python oder eine C-Bibliothek geupdated werden, ohne dass C-Extensions davon betroffen sein können)
  • Game-Changer für PAAS (wo Docker läuft, laufen Container)

Docker Registry

https://registry.hub.docker.com/

Docker Registry

  • Docker bietet auf Docker hub sehr viele Images an (momentan über 15000)
  • Es gibt offizielle Images, “Trusted builds” und andere user-contributed Images
  • Alle Images bauen auf einem Base-Image auf, häufig Ubuntu oder Debian → kaum zusätzlicher Platzverbrauch
  • Dockerfiles die zum Bauen der Images verwendet wurden können eingesehen werden
  • Registries können für eigene Images auch selbst gehostet werden
  • docker run führt automatisch download (docker pull) durch
  • /etc/issue gibt Debian 7 aus, aber uname -r Arch Linux (Host-OS)
  • es gibt einen Prozess mit PID 1, andere Prozesse werden von diesem geforked
  • sobald dieser Prozess beendet wird, beendet sich der Container
  • es können daher direkt Befehle ausgeführt werden, danach beendet sich der Container
  • dies kann z.B. für Cron-Jobs genutzt werden, die einen Container starten

docker run

docker run -ti --rm debian:wheezy /bin/bash
  • docker run erstellt einen neuen Container aus einem Image
  • -t öffnet ein Pseudo-Terminal (für Output)
  • -i gibt Zugriff auf stdin
  • --rm sorgt dafür dass Container gelöscht wird nach Beendigung
    • ansonsten würde der Container auf dem System liegen bleiben und könnte mit docker restart neu gestartet werden
  • debian:wheezy ist der Name des Images, hier mit Tag wheezy. Ohne Tag würde automatisch :latest verwendet.
  • /bin/bash ist das Kommando das vom Container ausgeführt werden soll. Images haben in der Regel ein Default-Kommando, das zur Laufzeit überschrieben werden kann.

Docker Images

  • Dockerfiles enthalten Instruktionen für das Bauen neuer Images
  • Alle Images müssen auf einem Base-Image aufbauen (FROM debian:wheezy)
  • Operationen wie RUN apt-get update werden in eigenen Containern durchgeführt, erzeugen Layer → Layer können von mehreren Images verwendet werden, ermöglichen build caching
  • Lokale Dateien und Ordner können dem Image hinzugefügt werden
  • Images können als tar exportiert oder in Registries hochgeladen werden

Dockerfile EXPOSE

  • EXPOSE 80 im Dockerfile gibt Port 80 frei, vorerst nur für andere Container
  • docker run -p 80:80 forwarded Port 80 auf Host
    • docker run -p [interface:]80:80 bindet Port an bestimmtes Interface (z.B. localhost)
    • docker run -p 8000:80 forwarded Container-Port 80 auf 8000 auf dem Host
  • Andere Container können auf EXPOSEte Ports anderer Container zugreifen, wenn docker run mit dem Parameter --link gestartet wird
  • ohne -p 6379:6379 kann vom Host-System nicht auf redis zugegriffen werden
  • andere container können auf den Port zugreifen (wenn EXPOSEd), mit --link

Volumes

  • Volumes umgehen das Docker-Dateisystem, benutzen das Dateisystem des Hosts
  • Durch Volumes können Container auf Daten anderer Container zugreifen
  • Volumes können mit docker run -v angelegt werden, bzw. mit VOLUME im Dockerfile
  • Andere Container können volumes eines Containers mit --volumes-from einbinden
  • Mit docker run -v /lokaler/pfad:/pfad/im/container können Verzeichnisse des Hosts-Systems in den Container gemountet werden
  • python3 -m http.server startet development Server auf Port 8000
  • -v $(pwd):/opt mountet das aktuelle Verzeichnis in das Verzeichnis /opt im Container

Beispiel-Projekt

  • Flask-SSE mit Redis, gunicorn und nginx
    https://github.com/DazWorrall/flask-sse
  • Bei Docker lässt man in der Regel pro Prozess einen eigenen Container laufen
  • Container kommunizieren untereinander intern, nur nginx wird auf Host geforwarded

Flask-SSE

from flask import Flask, render_template, redirect, url_for, json, request
from flask.ext.sse import sse, send_event

app = Flask(__name__)
app.config['SSE_REDIS_HOST'] = 'redis' #default=='localhost'; 'redis' => --link
app.register_blueprint(sse, url_prefix='/events')

@app.route('/new')
def new():
    return render_template('message.html')

@app.route('/send', methods=['POST'])
def send():
    data = {"message": request.form.get('message', 'Hello, world!')}
    send_event("testevent", json.dumps(data), channel='test')
    return redirect(url_for('new'))

@app.route('/')
def index():
    return render_template('index.html')

gunicorn

# python2 wegen gevent, wheezy basiertes Image da sonst Probleme mit gevent
# https://github.com/gevent/gevent/issues/513
FROM python:2.7.9-wheezy
MAINTAINER Jesaja Everling <jesaja@everling.email>
ENV MODIFIED_AT 201512061738

RUN pip install gunicorn gevent \
"git+https://github.com/DazWorrall/flask-sse.git"

ADD flask-sse/example /opt/flask-sse
WORKDIR /opt/flask-sse

CMD /opt/flask-sse/run.sh
  • Installation von gunicorn, gevent und flask-sse mit pip
  • flask-sse example-Projekt wird in /opt/flask-sse geschoben
    • Image kann dann erstellt werden mit docker build -t flask-sse_gunicorn .
    • Image wird mit flask-sse_gunicorn getaggt, Container kann deshalb mit docker run flask-sse_gunicorn gestartet werden

nginx

FROM nginx:1.7.9
ADD default.conf /etc/nginx/conf.d/default.conf
upstream gunicorn {
    server flask-sse_gunicorn:5000 fail_timeout=0;
}

server {
    listen 80;

    location / {
      proxy_redirect off;
      proxy_set_header Host $http_host;
      # http://stackoverflow.com/a/13673298/204706
      proxy_set_header Connection '';
      proxy_http_version 1.1;
      chunked_transfer_encoding off;
      proxy_buffering off;
      proxy_cache off;

      proxy_pass http://gunicorn;
    }
}
  • In diesem Dockerfile wird nur die default.conf ersetzt
    • Oder: docker run -v$(pwd)/default.conf:/etc/nginx/conf.d/default.conf nginx
  • docker Container kommunizieren über internes Netzwerk
  • auch andere Container können z.B. auf den redis Container zugreifen
  • Flask-SSE benutzt Redis Pub/Sub um Nachrichten in Echtzeit an Clients zu schicken

Container starten

  • docker run -d --name=redis redis:2.8.19
    • --name ist notwendig für --link (docker generiert ansonsten automatisch Namen)
  • docker run -d --link=redis:redis --name=flask-sse_gunicorn flask-sse_gunicorn
    • --link sorgt dafür dass DNS Eintrag für redis auf die IP des Containers verweist
  • docker run -d -p 80:80 --link=flask-sse_gunicorn:flask-sse_gunicorn flask-sse_nginx
    • nur für diesen Container wird der Port auf das Host-System geforwarded

Tipps&Tricks

  • In Dockerfiles:
    • apt-get install -y (sonst Eingabe erforderlich)
    • ENV MODIFIED_AT (deaktiviert den Cache wenn geändert)
  • docker exec erlaubt es in laufenden Containern Prozesse zu starten (z.B. /bin/bash zur Fehlersuche)
  • docker ps zeigt laufende Container an, docker images vorhandene Images
  • docker stop/rm $(docker ps -a -q) stoppt/löscht alle Container
  • docker history zeigt die Layer eines Images an
  • systemd unit files oder fig benutzen um zusammengehörige Container zu starten

Tipps&Tricks

  • Prozesse in Containern laufen per default als root!
    • Docker Container sind nicht so sicher wie eine VM, es ist vorstellbar dass aus einem Container ausgebrochen werden kann
    • Entweder mit USER www-data im Dockerfile einen User für den Prozess festlegen oder den Prozess entsprechend konfigurieren (z.B. user www-data; in nginx.conf)
  • Wenn man mit Container-IDs arbeitet, reicht es die ersten Stellen zu verwenden, z.B. docker rm 3ec
  • Data-only-Container mit VOLUMES können mit --volumes-from genutzt werden, mit docker export umgezogen werden

Links