Docker multi fomaggi: Cloud, MRBS, Office, gitlab, vplan, LE über docker

Hi zusammen,

Ich will hier mal berichten, was ich (in mühevoller Kleinarbeit) mit Docker anstelle.
Was man dafür können muss/bzw. lernen kann:

  • docker (siehe geniale Tutorials von Frank)
  • docker-compose um docker-container zusammen zu konfigurieren
  • SSL-Verbindungen testen, z.B. mit curl
  • Sinn und Zweck eines Reverse Proxy verstehen
  • log-Dateien lesen und interpretieren
  • Namensauflösung global und lokal
  • Letsencrypt Zertifikat Handling

Das folgende docker-compose-file hat folgende Voraussetzungen

  • normales docker auf einem ubuntu 18.04
  • ich verwende intern auch die externe domäne (hier: meine-schule.de)
  • ich leite jeglichen port 443 und 80 Trafik von extern auf den Dockerhost um (zwei Regeln im IPFire), auf dem IPFire läuft also kein Reverse Proxy mehr (weder pound noch haproxy)
  • manche Hosts haben Aliase, die im bind des lmn-servers definiert werden müssen. Ich habe bei Belwue gebeten alle diese subdomänen als Aliase (CNAME, denke ich) für meine IP einzutragen.

docker-compose.yml im Detail erklärt

proxy

version: '2'

services:
  proxy:
    restart: always
    image: jwilder/nginx-proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - /srv/docker/nginx/certs:/etc/nginx/certs:ro
      - /srv/docker/nginx/vhost.d:/etc/nginx/vhost.d
      - /srv/docker/nginx/html:/usr/share/nginx/html
#      - /srv/docker/nginx/custom.conf:/etc/nginx/conf.d/my_proxy.conf:ro      # if you need a custom global nginx configuration
      - /srv/docker/nginx/nginx.tmpl:/app/nginx.tmpl                           # if you need a custom (global) nginx template
    labels:
      - "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy"

Nach Anleitung vom Ersteller ist das die Beschreibung für einen Dockercontainer, der nginx als Proxy startet und auf den Ports für http (80) und https (443) lauscht. Den Port 80 brauche ich nur, damit ich den einfachsten Letsencrypt-modus über http verwenden kann. Wer es anders macht (dns-01 challenge) braucht den nicht.

  • Ein Reverse Proxy ist ein Verteiler von Anfragen. Klassisch z.B. frage ich bei cloud.meine-schule.de nach Dateien und bei moodle.meine-schule.de nach der Lernumgebung. Beide stehen in der Schule und die Schule hat nur eine weltweite IP-Adresse. Mit dem Proxy geht das trotzdem.
  • Die Zeile /srv/docker/nginx/certs sagt, dass im Proxy-container dieser Ordner für SSL-Zertifikate verwendet wird (gemanaged werden di Zertifikate im nächsten Container, daher sind sie hier ro=read-only)
  • Die nächste Zeile erlaubt pro gestartetem Dockercontainer eine eigene nginx-config. Eigentlich für Pro-user, aber ich brauche das schon, damit ich bei nextcloud mehr als 2MB hochladen kann: /srv/docker/nginx/vhost.d/cloud.meine-schule.de:
       client_max_body_size 500m;
  • Ähnliches gilt für die weiteren Zeilen, z.B. Ich habe eine eigene nginx.tmpl eingefügt, weil ich http/2 ausschalten musste/wollte und weil ich noch dafür sorgen musste, dass Anfragen an Adressen, die nicht im Dockercontainer laufen auch funktionieren (meine Cloud z.B.)
    /srv/docker/nginx/nginx.tmpl
      snip...
      server {
              server_name {{ $host }};
              listen 443 ssl {{ $default_server }};
      snip...
  • Die Labelzeile ist für die Aktualisierung der Letsencrypt-Zertifikate nötig.

Proxy Compagnon für LE-Zertifikate

  proxy-le-companion:
    restart: always
    image: jrcs/letsencrypt-nginx-proxy-companion
    volumes:
      - /srv/docker/nginx/certs:/etc/nginx/certs:rw
      - /var/run/docker.sock:/var/run/docker.sock:ro
    volumes_from:
      - proxy

Das beschreibt den Container, der die Zertifikate von Letsencrypt holt und abspeichert (Doku beginnt auch bei jwilder, aber dann liest man hier weiter.)

Bsp: Ein eigener Server im Dockercontainer

  api:
    image: hgkvplan/linuxmuster-vplan
    restart: always
    volumes:
      - /root/vplan/linuxmuster-vplan-fetch-hgk.sh:/app/linuxmuster-vplan-fetch.sh
      - /root/vplan/configuration-hgk.py:/app/configuration.py
      - /root/vplan/dsbservice.rsa:/app/dsbservice.rsa
      - /etc/hosts:/etc/hosts:ro
      - ./parser.log:/app/parser.log
    environment:
      - "VIRTUAL_HOST=api.meine-schule.de, docker.meine-schule.de"
      - "LETSENCRYPT_HOST=api.meine-schule.de, docker.meine-schule.de"
      - "LETSENCRYPT_EMAIL=t.kuechel@humboldt-ka.de"
#      - "LETSENCRYPT_TEST=true"

Ein beliebiger Server, der als Dockercontainer läuft lässt sich seeehr einfach mit SSL-Zertifikat ausstatten und von außen erreichen. In diesem Fall ist das ein eigener API-Server, der meinen Vertretungsplan zurückliefert.

  • Er läuft als Dockercontainer und braucht ein paar Dateien, wie einen RSA-Schlüssel und log-dateien und eine /etc/hosts-Datei.
  • Am VIRTUAL_HOST erkennt man, wie der API-Server von außen angesprochen wird. Aus historischen Gründen hieß der host “docker.meine-schule.de”, jetzt soll er von außen aber mit “api.meine-schule.de” angesprochen werden. Beides geht gleichzeitig.
  • am LETSENCRYPT_HOST erkennt man für welche Namen bei LE Zertifikate geholt werden sollen.

Nextcloud: Ein Server, der nicht im Docker liegt

  cloud:
    image: python:3-slim
    restart: always
    command: tail -f /dev/null
    environment:
      - "VIRTUAL_HOST=cloud.meine-schule.de"
      - "VIRTUAL_PORT=443"
      - "VIRTUAL_PROTO=https"      
      - "UPSTREAM_NAME=172.16.17.2"
      - "LETSENCRYPT_HOST=cloud.meine-schule.de, cleese.meine-schule.de"
###  need to copy the certificate
      - "LETSENCRYPT_EMAIL=t.kuechel@humboldt-ka.de"
#      - "LETSENCRYPT_TEST=true"

Jetzt mal ein Server, der nicht als Dockercontainer läuft: Meine Cloud (auf einem virtuell und physikalisch anderen Rechner)

  • Dieser Containerdienst ist ein Dummy: das Image “python:3-slim” ist arbitrary, denn das Kommando tail -f /dev/null macht gar nix. Interessant ist was dann kommt:
  • VIRTUAL_HOST siehe oben,
  • VIRTUAL_PORT und _PROTO sind klar: das sind die Daten um mit SSL auf meine Cloud zuzugreifen, die aber nicht im Dockercontainer läuft,
  • stattdessen ist UPSTREAM_NAME die IP-Adresse der Cloud-instanz.
  • Alles weitere kennt man, wobei Kommentar daraufhin deutet, dass hier im Dummy-Container das LE-Zertifikat hergestellt wird, ich es aber noch irgendwie auf den Cloud-Server bekommen muss.

Dieser Teil war der schwierigste von allen. Ohne die Änderungen am nginx.tmpl von CWempe würde diese Weiterleitung an einen externen Server nicht funktionieren, zumindest nicht inklusive Bereitstellung der LE-Zertifikate.

lmn-server: Nicht im Docker

  server:
    image: python:3-slim
    restart: always
    command: tail -f /dev/null
    environment:
      - "VIRTUAL_HOST=server.meine-schule.de"
      - "VIRTUAL_PORT=443"
      - "VIRTUAL_PROTO=https"      
      - "UPSTREAM_NAME=10.16.1.1"
      - "LETSENCRYPT_HOST=server.meine-schule.de, meine-schule.de"
###  need to copy the certificate
      - "LETSENCRYPT_EMAIL=t.kuechel@humboldt-ka.de"
#      - "LETSENCRYPT_TEST=true"

Weiteres Beispiel für einen externen Service. Den gleichen Kram mache ich nochmal, weil ich auf meinem v6.2-Server ein MRBS laufen habe, dass erreichbar sein soll. Theoretisch kann man hier auch die Schulkonsole. Praktisch wird bei mir auch mit dem MRBS auch der horde-dienst exponiert (!)

Office für Nextcloud: als Dockercontainer

  office:
    image: collabora/code
    restart: always
    ports:
      - 9980:9980
    cap_add:
      - MKNOD
    environment:
      - "domain=cloud.meine-schule.de"
      - "VIRTUAL_HOST=office.meine-schule.de"
      - "VIRTUAL_PORT=9980"
      - "VIRTUAL_PROTO=https"            
      - "LETSENCRYPT_HOST=office.meine-schule.de"
      - "LETSENCRYPT_EMAIL=t.kuechel@humboldt-ka.de"
#      - "LETSENCRYPT_TEST=true"

Das hier beschreibt wieder einen ordentlichen Container: in dem läuft collabora online (aka libreoffice für Nextcloud) und

  • man erkennt die Änderungen, die man laut der Anleitung (unten bei Getting started in 3 steps) machen muss,
  • nur dass der ganze proxy-quark wegfällt, weil das der nginx-container von oben schon alles automatisch (magisch, ich weiß nicht genau, warum es tut) macht.
  • Am LETSENCRYPT-Teil sieht man, dass hier ordentliche Zertifikate für office.meine-schule.de erzeugt werden.

Obwohl also meine Cloud nicht im Docker liegt, liegt im Docker wiederum der Zugriff auf die Officebearbeitung.

gitlab: Ein externer HTTP-only Server

  git:
    image: python:3-slim
    restart: always    
    command: tail -f /dev/null
    environment:
      - "VIRTUAL_HOST=git.meine-schule.de, idle.meine-schule.de"
      - "VIRTUAL_PORT=80"
      - "VIRTUAL_PROTO=http"      
      - "UPSTREAM_NAME=172.16.17.1"
      - "LETSENCRYPT_HOST=git.meine-schule.de, idle.meine-schule.de"
      - "LETSENCRYPT_EMAIL=t.kuechel@humboldt-ka.de"
#      - "LETSENCRYPT_TEST=true"

Jetzt noch ein Beispiel für einen externen Webserver (hier ein Server, der ein gitlab enthält), der selbst kein SSL beherrscht (weil ich ihn faulerweise so eingerichtet habe). Das Prozedere wie oben:

  • ein dummy-Container,
  • die Zertifikate werde für ihn dennoch korrekt erstellt und
  • hinter dem nginx wird per http/port 80 unverschlüsselt kommuniziert.

kleiner cron-job für die Zertifikate

Bei mir ist immernoch der linuxmuster-Server die Zentrale. Von dort komme ich passwortlos auch zum docker oder bei mir zur cloud, daher kann man folgenden cron-job leicht verstehen und anpassen:

#!/bin/sh                                                                                                                                            
#exit 0                                                                                                                                              

function copy_cert_if_different {
    chain=${le_hostpath}/${maindomain}/fullchain.pem
    key=$(dirname $chain)/key.pem
    copied=0

    tmp=$(mktemp) #empty tmp-files                                                                                                                   
    tmp2=$(mktemp)
    scp ${le_host}:$key $tmp
    scp ${le_host}:$chain $tmp2

    if [ -s $tmp -a -s $tmp2 ]; then   # exists and not empty                                                                                        
        echo >> $tmp  # since they do not end with a newline                                                                                         
        cat $tmp2 >> $tmp
        if ! diff $TARGETDIR/$cert $tmp >/dev/null; then

            echo "   + Sichere altes Zertifikat"
            cp -a $TARGETDIR/$cert $TARGETDIR/${cert}_$TIMESTAMP
            echo "   + Kopiere neues Zertifikat nach $TARGETDIR"
            cp $tmp $TARGETDIR/$cert

            chmod 0640 $TARGETDIR/$cert
            chown root:ssl-cert $TARGETDIR/$cert

            copied=1
        else
            echo "  -- keine Differenz zwischen Zertifikaten"
        fi
    else
        echo "either $chain and $key were not copied from $le_host or it is empty!!"
    fi
    rm -f $tmp $tmp2
    return $copied
}

# TARGET in the standard linuxmuster.net-installation                                                                                                
TARGETDIR=/etc/ssl/private
TIMESTAMP=$(date +%s)

le_host=docker
le_hostpath=/srv/docker/nginx/certs

maindomain=server.meine-schule.de
cert=server.pem
copy_cert_if_different
if [ $? -eq 1 ] ; then
    echo "   + Restart services"
    service apache2 reload
    service slapd restart
    service cyrus-imapd reload       
fi

maindomain=cloud.meine-schule.de
cert=cloud.pem
copy_cert_if_different
if [ $? -eq 1 ] ; then
    echo "   + Copy to cleese, restart apache2 there"
    rsync -avP $TARGETDIR/$cert cleese:/etc/ssl/private/server.pem
    ssh cleese service apache2 restart
fi

Für jedes Zertifikat, dass ich kopieren muss, definiere ich die erste Domäne und den Namen und definiere was ich mit dem Zertifikat anfangen will, z.B. auf den entsprechenden Server kopieren und dort die Dienste neu starten. Diese Datei liegt bei mir in /etc/cron.daily/. Die LE-Zertifikate auf dem Dockerhost werden selbstverständlich automatisch neu erzeugt.

Kritikpunkte

  • office.meine-schule.de ist von außen erreichbar. Das ist vollkommen unnötig und sogar DOS-gefährlich, denke ich.
  • in der Doku zu nginx-proxy sollte man noch den docker-gen container extra laufen lassen.
  • MRBS könnt man auch im container laufen lassen
  • komplexes setup?

kommentare, meinungen, kritik willkommen.

VG, Tobias

1 „Gefällt mir“

Hi. Die Tutorials hatte ich mir vor längerer Zeit auch angesehen. Es ist cool, was damit alles geht. Ich weiß nur nicht, ob dein Setup noch dem KISS-Prinzip entspricht? :slight_smile:

Bei uns ist es auch so, dass docker auf dem Nextcloud-Host läuft. Im Container läuft da collabora-Office.
Vielleicht ist ja docker sogar der Weg, wie auch der linuxmuster-Server bzw sein ganze Peripherie verteilt werden kann?? Die Komplexität nimmt aber ohne Zweifel nochmal ordentlich zu … das ist ein Nachteil …

Schöne Grüße,
Michael

Das Verfahren jetzt ist definitiv simpler als es vorher war. Vorher hatte ich 1. einen proxy auf dem IPFire (zuerst pound, dann haproxy), 2. einen LE-generator-und-zusätzlichen-proxy auf dem docker (zuerst traefik, jetzt nginx) und 3. einen weiteren dockerhost für collabora. Was es noch einheitlicher machen würde, wäre MRBS und die nextcloud im container. Gitlab braucht man selbstverständlich nicht, aber ist ein beispiel für einen HTTP-host hinter dem proxy.

Man kann gerne zur Diskussion stellen, was das simpelste Verfahren ist, so dass man LE-Zertifikate für die Server bekommt, für die man sie auch dringend braucht (z.B. cloud, server, was noch?).

  • Eine Möglichkeit wäre Port 80 für einen letsencrypt server offenzuhalten und 443 auf einen proxy zu leiten, der dann je nach Bedarf weiterleitet.
  • Moodle und Mailserver habe ich z.B. gar nicht angesprochen, wären aber schnell hinzugefügt, entweder als externe server oder interne dockercontainer. Wie viel Dienst will man bereitstellen und wie simpel kann es überhaupt sein?

was ich übrigens vergessen habe als Vorteil: Alles obige läuft auch unverändert unter der lmn v7 weiter…

VG, Tobias

Hi.
Diese Diskussion kommt mir bekannt vor – das hatten wir schon mal. Ich hatte vor längerer Zeit mal versucht, moodle und NC zu verheiraten, damit man innerhalb von moodle gleich auf die hochgeladenen Daten der Cloud zugreifen kann. Der docker-Container für moodle lief hier zu Testzwecken sehr gut; aber es gab da ein Problem mit dem Upgrade! Wenn man z.B. innerhalb des Containers von Version 3.3 auf 3.4 wechseln will, musste man imho wieder bei 0 anfangen. Das ist ja gerade nicht das, was man mit docker erreichen will.

Was ich weiterhin sehr gut finde: die Installation ist völlig modular und perfekt skalierbar. In Sachen Komplexität muss man dennoch bedenken, dass die Fehlersuche schwieriger und aufwändiger wird.
Aber die Sache hat Zukunft :slight_smile: :slight_smile:

Eine Frage habe ich doch noch:
Warum nimmst du nginx anstelle von HAProxy (gibt’s doch auch als docker-container)??

@zefanja hat übrigens kürzlich ein ähnliches Setup in seinem Blog beschrieben. Es gibt da viele Gemeinsamkeiten - auch wenn das dort mit lxd aufgezogen wird.

Schöne Grüße,
Michael

Es gibt keinen speziellen Grund. jwilder/nginx-proxy ist ziemlich beliebt und ist momentan im docker-container für v7 vorinstalliert, außerdem hab ich haproxy so gut wie nicht verstanden. Die Doku ist irgendwie grauselig.
Allerdings gibt es auch Gründe gegen jwilder/nginx-proxy: 1. ich habe schon selbst ein abgewandeltes Template nehmen müssen um externe Quellen anzusprechen und jwilder scheint das nicht zu interessieren. 2. grundsätzlich ist es eine schlechte Idee von docker-containern abhängig zu sein, deren Maintainer jederzeit keine Zeit haben können…

Aber vielleicht gibt es einen einfachen, generischen Weg das mit haproxy zu machen und einen guten Container dafür zu finden. Nächste Weihnachten hab ich wieder Zeit …:slight_smile:

Ja, ich meine auch, dass man sich nicht von einzelnen Maintainern abhängig machen sollte.

Gefunden hatte ich noch das hier
http://www.loadbalancer.org/blog/nginx-vs-haproxy/ :slight_smile:

… und natürlich (leider schon älter)

Hi Michael,

erster link: lässt mich nicht kalt. Würde gerne zu haproxy wechseln.

zweiter link: und darin verlinkte Beispiele welche docker-images es gibt und wie die konfiguriert werden sollen: da bin ich schon wieder reservierter. Ich will nicht noch in “etcd” einarbeiten oder whathaveyou. Die, die haproxy verwenden haben vermutlich auch den Anspruch an HA = high availability und potentiell machen die es “richtig” und so mit komplex. nur ne vermutung von einem, dem haproxy eben zu komplex erscheint. :slight_smile:

Vielleicht der hier?

Solange übrigens HAProxy gar nicht als echter Load Balancer genutzt wird sondern nur den Traffic je nach Port verteilt, ist es wahrscheinlich aber fast egal, wer diese Aufgabe übernimmt, oder??

1 „Gefällt mir“

Danke, k.A. Die Performance eines Verteilers ist scheinbar schon ziemlich wichtig. Meine Cloud ist fühlbar langsamer, seit jede Anfrage durch den docker geht (aber da läuft ja auch eine Menge drauf).

vG, Tobias

Hi.
Der Blogger hat auf seiner Seite noch weitere nette Dinge parat … wie z.B.:

Da steckt offensichtlich noch jede Menge Potential … :wink:
Michael