Marvin Preuss xsteadfastx photo

github twitter mastodon flickr

docker

Jitsi-Meet in Corana Zeiten

/// d9d5107 /// jitsi communication voip linux docker ansible golang covid19 prometheus raspberrypi

Social Distancing gehört eigentlich zu meinen Stärken. Soziale Kontakte kostet mich einen riesigen Aufwand. Auf einmal wurden wir alle Zuhausebleiber zu Helden der Corona-Zeit. Ich hätte es nicht gedacht, diese Distanz tat mir nicht gut. Ich scrollte durch viele “witzige” Screenshots von Gruppenvideochats. Vor ein paar Jahren probiere ich mal Jitsi-Meet aus. Ein kleiner Testlauf für die Firma, mit mäßigen Erfolg. Ab drei Teilnehmern ging der Server in die Knie. Nachdem ich diesen Bugreport sah, wurde mich auch klar wieso: Es gibt ein Firefox Bug, der die Verbindungen stark einschränkt, und das für alle Beteiligen.

Social Distancing gehört eigentlich zu meinen Stärken. Soziale Kontakte kostet mich einen riesigen Aufwand. Auf einmal wurden wir alle Zuhausebleiber zu Helden der Corona-Zeit. Ich hätte es nicht gedacht, diese Distanz tat mir nicht gut. Ich scrollte durch viele “witzige” Screenshots von Gruppenvideochats.

Vor ein paar Jahren probiere ich mal Jitsi-Meet aus. Ein kleiner Testlauf für die Firma, mit mäßigen Erfolg. Ab drei Teilnehmern ging der Server in die Knie. Nachdem ich diesen Bugreport sah, wurde mich auch klar wieso: Es gibt ein Firefox Bug, der die Verbindungen stark einschränkt, und das für alle Beteiligen. Egal. Ich kannte Jitsi schon aus meinen XMPP-Hype Jahren. Damals noch als Java Client der auch schon Audio und Video kannte. Er nutze dabei XMPP Jingle für das Aushandeln der Verbindungen. Da ich keine Lust darauf habe kommerzielle, closed Source Geschichten zu benutzen (aus Überzeugung), wollte ich etwas eigenes Aufsetzen. Jitsi musste wieder her halten.

In meiner Fantasie als Mega Super Admin rolle ich gleich mehrere Instanzen aus. Aus diesem Grund muss es eine Ansible Rolle sein. Diese wäre Teil meiner persöhnlichen Infrastruktausrollung. Ich schlug mich durch bereits vorhandene Ansible Rollen. Ein scharzer Tag innerhalb schwarzer Tage. Ich setze immer wieder frische virtualle Hetzner Server auf. Egal ob Debian oder Ubuntu, immer bekam ich andere Fehler. Anscheinend baut Jitsi intern viel um und will nun NGINX als neuen als Reverseproxy nutzen. Zumindestens nehme ich diese Aussage als Ausrede meines Nichtkönnens. Es gab aber noch eine andere Alternative: das docker-compose Setup. Aufgesetzt, funktioniert. Wieso also der Hassle?

Meine Ansible Rolle setzt ein funktionierendes Docker Setup voraus. Dann als Ansible Variabel den Host setzen:

    jitsi_meet__host: foo.bar.tld

Die meiste Arbeit steckt in dem offiziellen docker-compose File. Es kümmert sich sogar um letsencrypt SSL Zertifikate. Ich klickte mir erstmal den billigsten vServer in der Hetzner-Cloud. Natürlich muss ich auch mitbekommen wie sich die Zahlen der Konferenzen und User auf den Ressourcen-Verbrauch auswirkt. Ich brauchte einen Prometheus Exporter. Auch hier gab es was, dies beinahaltet Gefummel am XMPP Server. Keine Lust darauf wenn ich sowas automatisiert ausrollen möchte. Und da ich gerade eh mit go rumspiele musste ich es einfach selber machen. Stellt sich raus, die Videobridge Komponente in Jitsi kann Statistiken. Ich nehme die angebotenen Werte und baue daraus Prometheus Metriken. Ein Hack. Aber es macht was es soll. Der jitsiexporter!

Nun treffen wir uns einmal Morgends, frühstücken zusammen. Ich habe soviel soziale Kontakte wie noch nie. Diese Zeiten erzeugen eine Sensucht nach Nähe. Dies ist priviligiertes Gejammer aus der Quarantäne, das ist mir bewusst.



Scannen in Docker, in klicki und bunti

/// d794cd8 /// docker photography vuescan

Schon seit der Entdeckung der analogen Fotografie für mich, entwickel ich die schwarz-weiß Negative selber und scanne sie dann mit meinem Epson 4490. Den hatte ich mir damals gekauft, da er einerseits bei Media-Markt verfügbar bar und auch 120er Rollfilm scannen konnte. Zu der Zeit entdeckte ich meine Liebe für die Holga Kamera. Die Halter für die Filme sind eher ein wenig wackelig und ich hatte am Anfang Probleme das das Negativ das Glas des Scanner berührte und es zu Newton Ringen kam.

Schon seit der Entdeckung der analogen Fotografie für mich, entwickel ich die schwarz-weiß Negative selber und scanne sie dann mit meinem Epson 4490. Den hatte ich mir damals gekauft, da er einerseits bei Media-Markt verfügbar bar und auch 120er Rollfilm scannen konnte. Zu der Zeit entdeckte ich meine Liebe für die Holga Kamera. Die Halter für die Filme sind eher ein wenig wackelig und ich hatte am Anfang Probleme das das Negativ das Glas des Scanner berührte und es zu Newton Ringen kam. Alles nicht so professionell aber machbar. Zu der Zeit nutze ich einen Mac Mini und war froh das es die Epson Software auch für diesen gab. Später war ich wieder zurück auf Linux und nutze Virtualbox und ein altes Windows XP um die Epson Windows Software zum laufen zu bringen. Letztes Jahr, nach dem Umzug, war mein Setup noch nicht ganz aufgebaut und ich musste ein paar Negative scannen. Ich kaufte mir VueScan. Dies soll sowas wie der heilige Gral des Scannens sein. Die Software ist unter anderem von einem NASA Mitarbeiter programmiert und kommt mit vielen Treibern. Gerade wenn es für aktuelle Betriebssysteme keine mehr gibt, ein Segen. In meinem Fall, Epson 4490 mit VueScan unter Linux, musste ich noch einen Epson Treiber installieren. Ich weiß nicht wie ihr das seht, aber mir dreht sich alles um wenn ich als root eine install.sh ausführen soll die irgendwelchen Kram in mein System kopiert, welches ich im schlechtesten Fall per Hand wieder rauspopeln muss. Sowas wollte ich nicht mehr. Docker und X11 hatte ich bis jetzt nicht wirklich benutzt, sah darin aber den perfekten Usecase.

FROM ubuntu:xenial

ENV GOSU_VERSION 1.10

RUN set -ex \
 && apt-get update \
 && apt-get install -y \
        ca-certificates \
        wget \
 && cd /tmp \
 && wget https://download2.ebz.epson.net/iscan/plugin/gt-x750/deb/x64/iscan-gt-x750-bundle-1.0.0.x64.deb.tar.gz \
 && tar xvfz iscan-gt-x750-bundle-1.0.0.x64.deb.tar.gz \
 && iscan-gt-x750-bundle-1.0.0.x64.deb/install.sh \
 && wget https://www.hamrick.com/files/vuex6495.tgz \
 && tar xvfz vuex6495.tgz \
 && cp VueScan/* /usr/local/bin/ \
 && rm -rf /tmp/* \
 && rm -rf /var/lib/apt/lists/* \
 && cd /

COPY entrypoint.sh /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]
CMD ["vuescan"]

Dieses Dockerfile macht nichts anderes als die Treiber von Epson herunter zu laden und zu installieren. Das gleiche gilt für VueScan. Ich habe mich aus Feigheit für ein Ubuntu Docker Image entschieden. Dies ist am nächsten zum den System auf dem ich es laufen lassen will.

#!/bin/sh

set -e

if [ "$1" = "vuescan" ];then

        echo "---> starting vuescan"
        vuescan

        echo "---> fix permissions"
        chown -R $USERID:$GROUPID /root/Scan
        chown -R $USERID:$GROUPID /root/.vuescan
        chown $USERID:$GROUPID /root/.vuescanrc
        exit

fi

exec "$@"

Hier komme ich schon zu dem einen Problem was ich hatte. Es war mir nicht möglich den USB Scanner mit einem anderen Benutzer im Container zu benutzen. Vielleicht finde ich ja nicht einen richtigen Hinweis. So trickse ich weiter rum und fixe die lokalen Permissions wenn ich VueScan beende.

Ich lege mir immer gerne Makefiles an. So muss ich nicht erstmal schauen wie ich die Sachen richtig schreibe oder mit welchen wilden Optionen ich den Container starte.

PHONY.: build run

USERID=$(shell id -u)
GROUPID=$(shell id -g)

build:
	docker build -t vuescan .

run:
	xhost +local:
	touch /home/$(USER)/.vuescanrc
	docker run --rm -ti --privileged -e USERID=$(USERID) -e GROUPID=$(GROUPID) -e DISPLAY=$(DISPLAY) -e XAUTHORITY=$(HOME)/.Xauthority -v /tmp/.X11-unix:/tmp/.X11-unix -v /home/$(USER)/Nextcloud/Scan:/root/Scan -v /home/$(USER)/.vuescan:/root/.vuescan -v /home/$(USER)/.vuescanrc:/root/.vuescanrc -v /dev/bus/usb:/dev/bus/usb vuescan

Ich übergebe dem Container die User-ID und Gruppen-ID damit ich Permissions fixen kann. Dazu kommt --privileged. Dies ist nötig um direkten Zugriff auf den USB-Bus des Host Systems zu haben. -e DISPLAY=$(DISPLAY), -e XAUTHORITY=$(HOME)/.Xauthority und -v /tmp/.X11-unix:/tmp/.X11-unix sind nötig um auf den Xserver zu zugreifen. Wichtig dafür auch das xhost +local: im Makefile. Alle anderen Volumes sind VueScan config-Verzeichnisse und das super wichtige /dev/bus/usb, der direkte Zugriff auf den USB-Bus.

Ein wenig gefrickel… aber am Ende ging es.



Docker aufs auf einem Ubuntu 16.04 LTS

/// d794cd8 /// ubuntu docker linux mastodon

Ich bin gestern auf was gestossen. Ich betreibe einen kleinen Server mit einer Mastodon Instanz mit nur mir als User. Der Server auf dem dies läuft ist ein Ubuntu 16.04 LTS. Das neue Mastodon Docker Image hat einen Entrypoint der bei jedem Aufruf des Containers erstmal einen Haufen Files nach ihren Benutzerrechten abfragt und diese dann gegebenenfalls ändert. Dies hat beim letzten Update dann einfach Stunden gedauert, da bei jeder Datenbank-Migration oder Assert-Kompilierung erstmal wieder alle Files durchkämmt wurden.

Ich bin gestern auf was gestossen. Ich betreibe einen kleinen Server mit einer Mastodon Instanz mit nur mir als User. Der Server auf dem dies läuft ist ein Ubuntu 16.04 LTS.

Das neue Mastodon Docker Image hat einen Entrypoint der bei jedem Aufruf des Containers erstmal einen Haufen Files nach ihren Benutzerrechten abfragt und diese dann gegebenenfalls ändert. Dies hat beim letzten Update dann einfach Stunden gedauert, da bei jeder Datenbank-Migration oder Assert-Kompilierung erstmal wieder alle Files durchkämmt wurden. Ein Graus. Ein wenig in den Bugreports von Mastodon geschaut und auch was gefunden. Anscheinend liegt das an dem Storagedriver overlay2 und mit aufs sollte dies “viel schneller” gehen. Unter Ubuntu Zesty ist aufs wohl auch der Default. Nun unter 16.04 sieht man davon nichts. Dies kann man mit grep aufs /proc/filesystems überprüfen. Es gibt aber einen Weg…

  1. Mit sudo apt-get install linux-image-extra-$(uname -r) linux-image-extra-virtual die passenden Pakete installieren

  2. Mit sudo modprobe aufs das Kernelmodul laden

  3. grep aufs /proc/filesystems sollte nun was auswerfen

  4. Das File /etc/docker/daemon.conf so anpassen das auch wirklich aufs genutzt wird

     {
         "storage-driver": "aufs"
     }
    
  5. Docker neu starten mit service docker restart

  6. Mit docker info überprüfen ob wirklich aufs genutzt wird

  7. aufs zu /etc/modules hinzufügen damit es auch nach einem Neustart funktioniert

  8. Nun kann /var/lib/docker/overlay2 gelöscht werden. Alle Images müssen nun neu heruntergeladen oder gebaut werden



NGINX im Container und docker-gen

/// d794cd8 /// nginx docker linux

Das hatte ich lange auf meiner Liste. Bei mir lief immer noch der NGINX nicht im Container. Um ehrlich zu sein hatte ich mir alles schwieriger vorgestellt als es dann am Ende war. Ich halte mir immer alles in docker-compose.yml-Files fest. nginx: image: nginx ports: - "80:80" - "443:443" volumes: - /etc/nginx:/etc/nginx - /etc/letsencrypt:/etc/letsencrypt - /var/log/nginx:/var/log/nginx Dies läßt eine funktionierende Ubuntu-NGINX-Config mit dem offiziellen NGINX-Dockerimage laufen. Der NGINX macht fungiert nur als Reverse-Proxy für ein paar Anwendungen.

https://gifsboom.net/post/106865490574/fail-container-video

Das hatte ich lange auf meiner Liste. Bei mir lief immer noch der NGINX nicht im Container. Um ehrlich zu sein hatte ich mir alles schwieriger vorgestellt als es dann am Ende war. Ich halte mir immer alles in docker-compose.yml-Files fest.

nginx:
  image: nginx
  ports:
    - "80:80"
    - "443:443"
  volumes:
    - /etc/nginx:/etc/nginx
    - /etc/letsencrypt:/etc/letsencrypt
    - /var/log/nginx:/var/log/nginx

Dies läßt eine funktionierende Ubuntu-NGINX-Config mit dem offiziellen NGINX-Dockerimage laufen. Der NGINX macht fungiert nur als Reverse-Proxy für ein paar Anwendungen. Also werden Requests an ein Upstream Server weitergeleitet. NGINX macht manchmal Probleme wenn der Upstream Server Hostname nicht auflösbar ist und bricht dann sofort ab. Und dann sitzt man da und wundert sich wieso der Container immer wieder aussteigt. IP-Adressen sind auch auf ihre Art problematisch. Sie können sich unter Docker öfters ändern wenn man nach einem Image-Update den Container neustartet. Also was tun? Ich setze nun docker-gen ein. Der überwacht den Docker-Daemon und erstellt aus einem Template Config-Files. In diesem Fall eine upstream.conf. Die docker-compose.yml sieht so aus:

gen:
  image: jwilder/docker-gen
  volumes:
    - /var/run/docker.sock:/tmp/docker.sock:ro
    - /opt/docker-gen:/data
  volumes_from:
    - nginx_nginx_1
  command: -notify-sighup nginx_nginx_1 -watch -only-exposed /data/nginx.tmpl /etc/nginx/upstream.conf

docker-gen braucht den docker.sock um aus dem Container mit dem Daemon zu kommunizieren. Schließlich muss er mitbekommen wenn es Veränderungen gibt. Sprich Container gestartet werden oder Container nicht mehr da sind. Ich mounte das Arbeitsverzeichnis um auf das Template aus dem Container zugreifen zu können. Dazu binde ich alle Volumes des NGINX Containers ein. Schließlich soll das finale Config-File an die richtige Stelle geschoben werden. -notify-sighup nginx_nginx_1 sagt das er den NGINX Container neustarten soll. Mit -watch beobachtet er den Docker Daemon auf Änderungen. Mit -only-exposed werden nur Container betrachtet die Ports exposen. Dahinter kommt dann das Template welches benutzt werden soll und wohin die Finale Datei geschrieben werden soll. Ich habe mich dafür entschieden nicht die ganze nginx.conf aus dem Template zu generieren. Ich lasse nur die upstream.conf bauen. Dieses inkludiere ich dann in der nginx.conf.

upstream cloud {
                        # ownclouddocker_owncloud_1
                        server 172.17.0.6:80;
}
upstream gogs {
                        # gogs_gogs_1
                        server 172.17.0.5:3000;
}
upstream ttrss {
                        # ttrssdocker_ttrss_1
                        server 172.17.0.8:80;
}

Damit so eine Config erzeugt wird benutze ich dieses Template:

{{ range $host, $containers := groupByMulti $ "Env.VIRTUAL_HOST" "," }}
upstream {{ $host }} {

{{ range $index, $value := $containers }}

	{{ $addrLen := len $value.Addresses }}
	{{ $network := index $value.Networks 0 }}

	{{/* If only 1 port exposed, use that */}}
	{{ if eq $addrLen 1 }}
		{{ with $address := index $value.Addresses 0 }}
			# {{$value.Name}}
			server {{ $network.IP }}:{{ $address.Port }};
		{{ end }}

	{{/* If more than one port exposed, use the one matching VIRTUAL_PORT env var */}}
	{{ else if $value.Env.VIRTUAL_PORT }}
		{{ range $i, $address := $value.Addresses }}
			{{ if eq $address.Port $value.Env.VIRTUAL_PORT }}
			# {{$value.Name}}
			server {{ $network.IP }}:{{ $address.Port }};
			{{ end }}
		{{ end }}

	{{/* Else default to standard web port 80 */}}
	{{ else }}
		{{ range $i, $address := $value.Addresses }}
			{{ if eq $address.Port "80" }}
			# {{$value.Name}}
			server {{ $network.IP }}:{{ $address.Port }};
			{{ end }}
		{{ end }}
	{{ end }}
{{ end }}
}

{{ end }}

Damit docker-gen die passenden Container findet die er betrachten soll, müssen den Containern ein paar Environment-Variabeln mitgegeben werden. Hier zum Beispiel mit Gogs:

gogs:
  image: gogs/gogs
  ports:
    - "3000"
  volumes:
    - "/srv/www/gogs:/data"
  environment:
    - VIRTUAL_HOST=gogs
    - VIRTUAL_PORT=3000

VIRTUAL_HOST gibt dem ganzen einen Namen. Den brauchen wir dann in der eigentlichen vhost-Config. VIRTUAL_PORT wird gebraucht wenn der Port nicht 80 ist. Dann ignoriert docker-gen sonst den Container.

Die NGINX-Config sieht dann so aus:

server {
        listen 443;
        server_name git.foo.bar;

        ssl on;
        ssl_certificate /etc/letsencrypt/live/git.foo.bar/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/git.foo.bar/privkey.pem;

        client_max_body_size 50m;

        location / {
                proxy_pass http://gogs;
                proxy_set_header X-Forwarded-Host $server_name;
                proxy_set_header X-Forwarded-Proto https;
                proxy_set_header X-Forwarded-For $remote_addr;
        }

}

Unter proxy_pass taucht VIRTUAL_HOST wieder auf. In der NGINX Config spiegelt sich das wie folgt wieder:

http {

        ##
        # Basic Settings
        ##

        ...
        ...
        ...

        ##
        # Virtual Host Configs
        ##

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;

        ##
        # Upstream
        ##

        include /etc/nginx/upstream.conf;

}

Nun alles Container starten und genießen oder sowas.



Jenkins und die Dockerception

/// d794cd8 /// jenkins docker linux

Nach meinen ersten Experimenten mit Jenkins und Docker geht es nun wieder einen Schritt weiter. Mein Jenkins läuft im Docker-Container hinter einem NGINX im Docker-Container. Was könnte also noch eine Steigerung sein? Noch schöner wäre es wenn die Tests bzw Builds in eigenen Containern laufen, die dann nach dem Run wieder in sich zusammen fallen. Ich versuche mal alles zusammen zuschreiben. Docker in Docker Der beste Weg ist es den Docker-Socket als Volume an den Container durchzureichen.

Nach meinen ersten Experimenten mit Jenkins und Docker geht es nun wieder einen Schritt weiter. Mein Jenkins läuft im Docker-Container hinter einem NGINX im Docker-Container. Was könnte also noch eine Steigerung sein? Noch schöner wäre es wenn die Tests bzw Builds in eigenen Containern laufen, die dann nach dem Run wieder in sich zusammen fallen. Ich versuche mal alles zusammen zuschreiben.

Docker in Docker

Der beste Weg ist es den Docker-Socket als Volume an den Container durchzureichen. Hier der docker-compose-Eintrag:

jenkins:
  container_name: jenkins
  build: ./jenkins/
  volumes:
    - "/srv/www/jenkins:/var/jenkins_home"
    - "/usr/bin/docker:/bin/docker"
    - "/usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1"
    - "/var/run/docker.sock:/var/run/docker.sock"
  ports:
    - "8080:8080"
    - "50000:50000"

Wir müssen nicht nur den Socket durchreichen, sondern auch das Docker-Binary und eine Library (war in diesem Fall nötig). Sonst nichts besonderes hier. Die Ports sind einmal für die Jenkins Weboberfläche und einmal für die Kommunikation mit den Slaves die sich im Netz befinden. In meinem Fall ein Windows Build Slave.

Docker in Jenkins

Ich benutze das offizielle Jenkins Docker Image mit ein paar Anpassungen. Das Dockerfile sieht wie folgt aus:

FROM jenkins

USER root

RUN apt-get update \
 && apt-get install -y \
    rsync \
 && rm -rf /var/lib/apt/lists/*

RUN addgroup --gid 116 docker \
 && usermod -a -G docker jenkins

USER jenkins

Ich installiere ein rsync und füge den Benutzer jenkins der docker-Gruppe hinzu damit der die Container starten, beenden und löschen kann.

Jenkins hat viele Plugins. Diese wiederum haben kaum bis garkeine Dokumentation oder klaffende Lücken in ihr. Also habe ich erstmal, ziemlich naiv, das Docker Plugin installiert. Jenkins braucht erstmal eine Connection zu Docker. Dazu gehen wie in die Jenkins System Konfiguration in die Cloud Sektion. Neben einem Namen sollten wir auch die Docker URL eintragen. Die kann auch ein Socket sein. In diesem Fall unix:///var/run/docker.sock. Nun klappt die Kommunikation.

tox-jenkins-slave

Nun brauchen wir ein Docker-Image das als Jenkins-Slave taugt. Ich habe mich ganz an dem Image von evarga orientiert. Mein Image beinhaltet ein paar Änderungen. Zum Beispiel installiere ich nicht das gesammte JDK, sondern nur ein Headless-JRE. Sonst installiert er mir schön diverse X-Libraries. Die braucht man nicht um den Jenkins-Slave-Client auszuführen. Ich bin riesen tox-Fan. Ein Tool um Tests in diversen Python Versionen auszuführen. Alles getrennt durch Virtualenv’s. Dazu braucht tox aber auch die Python Versionen installiert. Dies funktioniert dank dem PPA von fkrull ganz wunderbar. Zum Schluß installiere ich tox selber und fertig ist das Image. Ach ja, ein openssh-server und git wird auch noch gebraucht. Hier das Dockerfile:

FROM ubuntu:trusty

RUN gpg --keyserver keyserver.ubuntu.com --recv-keys DB82666C \
 && gpg --export DB82666C | apt-key add -

RUN echo deb http://ppa.launchpad.net/fkrull/deadsnakes/ubuntu trusty main >> /etc/apt/sources.list \
 && echo deb-src http://ppa.launchpad.net/fkrull/deadsnakes/ubuntu trusty main >> /etc/apt/sources.list

RUN apt-get update \
 && apt-get install -y \
    git \
    openssh-server \
    openjdk-7-jre-headless \
    python-pip \
    python2.3 \
    python2.4 \
    python2.5 \
    python2.6 \
    python3.1 \
    python3.2 \
    python3.3 \
    python3.4 \
    python3.5 \
 && rm -rf /var/lib/apt/lists/*

RUN pip install tox

RUN sed -i 's|session    required     pam_loginuid.so|session    optional     pam_loginuid.so|g' /etc/pam.d/sshd \
 && mkdir -p /var/run/sshd

RUN adduser --quiet jenkins \
 && echo "jenkins:jenkins" | chpasswd

EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]

Es wird ein jenkins Benutzer mit dem Password jenkins angelegt. Wenn sich jemand daran stört soll er sich das Image selber bauen und dies verändern.

Um es zum Einsatz zu bringen müssen wir unter Configure System -> Cloud -> Docker -> Add Docker Template folgendes einstellen:

  1. Docker Image: xsteadfastx/tox-jenkins-slave
  2. Remote Filing System Root: /home/jenkins
  3. Labels: tox-jenkins-slave
  4. Launch method: Docker SSH computer launcher
  • Credentials: Add a new one with username jenkins and password jenkins
  1. Remote FS Root Mapping: /home/jenkins
  2. Remove volumes: True

Mir war nicht klar das das Label ausschlaggebend ist um den Job auch wirklich auf den passenden Container laufen zu lassen. Ohne Label kann dieser nicht getriggert werden.

Nun können wir im Job den Container einsetzen:

  1. Docker Container: True
  2. Restrict where this project can be run: True
  • Label Expression: tox-jenkins-slave

Sachen mit denen ich noch so gekämpft habe

Aus irgendeinem Grund hatte ich riesige Probleme mit den Locales in dem Container. Einige Tests haben Vergleichsdaten in Textfiles gespeichert. Falsche Locales haben die Tests gebrochen. Das Absurde… egal was ich tat, es half einfach nichts. LANG setzen in der .bashrc oder in /etc/profile wurde vollständig ignoriert. Startete ich den Container manuell und loggte mich per SSH ein, kein Problem, die Tests liefen durch. Der Slave gestartet von Jenkins, völlige Ingoranz meiner Config. Also musste ich es direkt in das Python Job Script packen:

import os
import tox

os .environ['LANG'] = 'C.UTF-8'
os.chdir(os.environ['WORKSPACE'])

tox.cmdline()

Ich setze die Environment-Variabel direkt im Script, gehe in den Jenkins-Workspace und führe tox aus. Ein Umweg, aber es funktioniert.

Schenkt den PyPI-Servern eine Auszeit

Noch eine Kleinigkeit: Da die Abhängigkeiten für die Tests bei jedem Durchlauf neu heruntergeladen werden lohnt sich der Einsatz von devpi. Der kann sehr viel mehr, wird aber von mir als einfacher PyPI-Mirror missbraucht. Ich habe dafür auch ein kleines Image. Läuft der Container sieht mein Jenkins Job kompletto so aus:

import os
import tox

os.environ['LANG'] = 'C.UTF-8'

os.makedirs('/home/jenkins/.config/pip')
with open('/home/jenkins/.config/pip/pip.conf', 'w') as f:
  f.write('[install]\ntrusted-host = 192.168.1.8')

os.environ['PIP_INDEX_URL'] = 'http://192.168.1.8:3141/root/pypi/+simple/'

os.chdir(os.environ['WORKSPACE'])

tox.cmdline(['-e py33,py34,py35'])

Von nun an wird der Mirror benutzt.

https://www.tumblr.com



Das Letsencrypt Docker Moped

/// d794cd8 /// docker linux letsencrypt ssl nginx

Darauf haben wir lange gewartet: SSL für alle!!1!!!111! Wie oft musste ich Leuten erzählen das sie keine Angst haben müssen vor dem Polizisten mit dem Schlagstock (zumindestens nicht vor dem im Chrome Browser). Ich hatte persöhnlich nie etwas gegen selbstsignierte Zertifikate, wenn ich nicht gerade Geld über diese Seiten schubsen musste. Aber könnte nun alles der Vergangenheit angehören. Letsencrypt bieten nun einen einfachen Weg an um sich seine Zertifikate unterschreiben zu lassen.

Darauf haben wir lange gewartet: SSL für alle!!1!!!111! Wie oft musste ich Leuten erzählen das sie keine Angst haben müssen vor dem Polizisten mit dem Schlagstock (zumindestens nicht vor dem im Chrome Browser). Ich hatte persöhnlich nie etwas gegen selbstsignierte Zertifikate, wenn ich nicht gerade Geld über diese Seiten schubsen musste. Aber könnte nun alles der Vergangenheit angehören. Letsencrypt bieten nun einen einfachen Weg an um sich seine Zertifikate unterschreiben zu lassen. Und nicht nur das. Sie wollen Tools mitliefern, die ohne viel Vorwissen die Arbeit für einen erledigen. Sprich Keys erzeugen und den Zertfikatsrequest. Dann findet eine überprüfung statt ob die Domain für die ihr das Zertifikat haben wollt, auch wirklich euch gehört. Ein wichtiger Schritt. Nur weil ihr keine Hunderte von Euro für ein Zertifikat ausgibt, sollt ihr trotzdem das Recht haben das der Transport über das Netz verschlüßelt ist. Ohne Schlagstock-Polizisten-Warning. Nun geht das Projekt in die BETA-Phase und die üblich Verdächtigen(WARNUNG: fefe link) ranten rum. Räudiges Python und alles viel zu einfach… und vor allem… jeder weiß doch wie man openssl richtig bedient und wo man richtige Zertifikate bekommt. Ich finde den Ansatz gut. Natürlich ist der Client noch nicht dort angekommen wo er ankommen will. Aber es funktioniert. Der Client bietet mehrere Modi an. Der eine öffent die Ports 80 und 443 und Letsencrypt schaut dann ob sie erreichbar sind und tauschen ein paar Sachen aus. Problem: Auf dem Server läuft meist schon ein Webserver und blockiert die Ports. Ich muss also NGINX stoppen, den Client starten und dann NGINX wieder anschieben. Irgendwie nicht so schön. Eine andere Möglichkeit ist webroot. Dafür legt der Client ein paar Ordner in das Root-Verzeichnis des Webservers und der Dienst schaut dann dort nach. Problem hierbei: Ich habe in meine NGINX keine Files im Root. Ich benutze NGINX nur als Reverse-Proxy. Es gibt Hilfe:

Das NGINX Snippet

location /.well-known/acme-challenge {
    alias /etc/letsencrypt/webrootauth/.well-known/acme-challenge;
    location ~ /.well-known/acme-challenge/(.*) {
        add_header Content-Type application/jose+json;
    }
}

Und wenn wir dieses Snippet nun in den server-Teil der NGINX config “includen” haben wir die passende Location damit Letsencrypt alles findet.

Letsencrypt im Docker-Container

Der Client versucht sich vor jedem Ausführen nochmal selber zu updaten. Keine schlechte Idee in der BETA-Phase. Ich dachte nur ich versuche alles ein wenig aufzubrechen und in ein Docker-Image zu gießen. Dies sollte (Dank Alpine-Linux) schön klein Sein und schon alle wichtigen Optionen im ENTRYPOINT beinhalten. Das man nur noch die Domains, für die man Zertifikate haben will, anhängt. Der Gedanke dahinter: Man kann es einfach in die Crontab packen und alle Abhängigkeiten sind im Container gefangen. Die Repo liegt hier. Für noch mehr Convenience nutze ich auch mein geliebtes docker-compose.

letsencrypt:
  build: letsencrypt/
  volumes:
    - /etc/letsencrypt:/etc/letsencrypt
    - /var/lib/letsencrypt:/var/lib/letsencrypt
  environment:
    EMAIL: marvin@xsteadfastx.org
    WEBROOTPATH: /etc/letsencrypt/webrootauth
  command:
    "-d emby.xsteadfastx.org -d cloud.xsteadfastx.org -d reader.xsteadfastx.org -d git.xsteadfastx.org"

Ich benutze Compose so oft wie es geht. Dort kann ich schön alles definieren wie der Container ausgeführt werden soll. Eine saubere Sache. Erstmal werden die Volumes eingebunden. Diese beiden Verzeichnise braucht der Letsencrypt-Client. Dann werden zwei Environment-Variablen definiert. Email und der Pfad in dem der Client die Files zur Authentifizierung ablegt. Als command werden die Domains jeweils mit der Option -d angehängt. Mit docker-compose up wird erst das Image gebaut und dann ausgeführt.

Ich bin gespannt wie es weitergeht. Natürlich ist mein Container auch nur der erste Entwurf… aber ich werde weiter daran basteln.



Dockercraft - Minecraft Docker client

/// d794cd8 /// docker minecraft

Endlich kann man dem Chef einfach sagen das man sich nur um die Administration der Docker-Container kümmert. Darauf habe ich gewartet… Docker Container aus Minecraft heraus steuern. Zweimal heißen Scheiß kombinieren und voilá: Dockercraft. Man fühlt sich ein wenig in einen Hackerfilm der 90er versetzt. Und alleine deswegen kann man das schon feiern. Erinnert sehr start an den Doom-Prozess-Killer. (via)

Dockercraft

Endlich kann man dem Chef einfach sagen das man sich nur um die Administration der Docker-Container kümmert. Darauf habe ich gewartet… Docker Container aus Minecraft heraus steuern. Zweimal heißen Scheiß kombinieren und voilá: Dockercraft. Man fühlt sich ein wenig in einen Hackerfilm der 90er versetzt. Und alleine deswegen kann man das schon feiern. Erinnert sehr start an den Doom-Prozess-Killer.

(via)



Dockerize all die Sachen

/// 7ab0ec1 /// docker linux

Docker ist manchmal immer noch ein Buch mit sieben Siegeln für mich. In meinem Fall war die Lernnkurve nicht gerade die Beste. Aber was solls. Das meiste lernt man dann doch durch Trial and error. Ein paar kleine selbstgeschriebene Web-Apps laufen bei mir schon in Docker Containern nun wollte ich bestehende Dienste auf meinem Heimserver in Container auslagern. Vor allem die Sachen denen ich immer noch ein wenig kritisch gegenüber stehe.

Docker ist manchmal immer noch ein Buch mit sieben Siegeln für mich. In meinem Fall war die Lernnkurve nicht gerade die Beste. Aber was solls. Das meiste lernt man dann doch durch Trial and error. Ein paar kleine selbstgeschriebene Web-Apps laufen bei mir schon in Docker Containern nun wollte ich bestehende Dienste auf meinem Heimserver in Container auslagern. Vor allem die Sachen denen ich immer noch ein wenig kritisch gegenüber stehe. Da wären zum Beispiel Owncloud, Plex und tiny tiny rss. Was ich gelernt habe: Jede Anwendung fordert individuelle Entscheidungen die manchmal erst auf den zweiten Blick Sinn machen :).

Owncloud

Meine lokale Owncloud Installation hat schon lange keine Liebe mehr gesehen. Oft benutze ich es nicht mehr und um Updates hatte ich mich auch nicht so wirklich gekümmert. Ich musste mir erstmal Gedanken was ich zum Beispiel mit PHP Anwednungen machen. Bei Python starte ich Gunicorn im Container und richte einfach einen Reverse-Proxy (Nginx) drauf. Bei PHP brauche ich einen Layer dazwischen. Ich habe mich dafür entschieden einen minimalen Nginx mit php-fpm im Container laufen zu lassen. Davor kommt dann der Reverse Proxy mit SSL und Soße und Scharf. Und da ich php-fpm und Nginx im Container laufen haben muss, muss ich die Prozesse per Supervisor starten. Normalerweise versuche ich die einzelnen Prozesse in verschiedene Container zu packen. Hier habe ich mich explizit dagegen entschieden. Die Config dazu sieht in diesem Fall so aus:

[supervisord]
nodaemon = true

[program:nginx]
command = nginx
user = root
autostart = true

[program:php]
command = php5-fpm --nodaemonize
user = root
autostart = true

Sehr wichtig ist das Supervisor nicht als daemon läuft. Sonst schließt sich der Docker Container sofort wieder nach dem ausführen. Mein Dockerfile sieht so aus:

FROM nginx

RUN apt-get update && apt-get -y install bzip2 curl supervisor php5-fpm php5-gd php5-json php5-mysql php5-curl php5-intl php5-mcrypt php5-imagick php5-sqlite
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

RUN mkdir -p /var/log/supervisor
RUN mkdir /var/www

RUN curl -k https://download.owncloud.org/community/owncloud-8.0.4.tar.bz2 | tar jx -C /var/www/
RUN chown -Rv www-data:www-data /var/www

COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY php.ini /etc/php5/fpm/
COPY nginx.conf /etc/nginx/nginx.conf

VOLUME ["/var/www/owncloud/data", "/var/www/owncloud/config"]
EXPOSE 80
CMD ["/usr/bin/supervisord"]

Eigentlich auch nichts wildes. Die Verzeichnisse /var/www/owncloud/data und /var/www/owncloud/config sind als Volumes deklariert. Die werde ich dann mit lokalen Verzeichnissen auf dem Host verbinden. Alle anderen Files liegen in dem passenden Repository.

Grundlegend benutze ich docker-compose. Es macht den Workflow für mich so viel besser. Ich schreibe in docker-compose.yml all meine Optionen die ich haben will (zum Beispiel die Volumes oder Ports) und mit docker-compose up wird alles hochgefahren. Ich muss mir mein ursprüngliches docker run-Kommando nicht merken. Am schönsten wird es wenn wir mehrere Container miteinander verkleben. Bei Owncloud hantieren wir mit nur einem Container. Das File sieht dann so aus:

owncloud:
  build: .
  ports:
    - "127.0.0.1:9998:80"
  volumes:
    - "/srv/www/owncloud/config:/var/www/owncloud/config"
    - "/srv/www/owncloud/data:/var/www/owncloud/data"

Wie man sieht steht nicht wirklich viel drin aber es erleichtert das starten und bauen sehr.

tiny tiny rss

tiny tiny rss war dann meine Master-Arbeit. Hier gab es dann gleich mehrere Problemszenarien die zu bewältigen waren. Ich finge also an ein einfaches Dockerfile zusammen zu hacken. Es gab dann aber ein paar Probleme. Zum Beispiel muss die Datenbank bestehen bleiben auch wenn ich den Container lösche. Das einrichten der Datenbank erfolgt aber durch einen Setup Screen von tt-rss bei ersten aufrufen. Das gleiche gilt für das Configfile. Im Dockerfile kann man nicht andere Volumes von anderen Container definieren die zum speichern von Daten genutzt werden (dies ist ein Weg bestimmte Daten vor dem löschen zu bewahren). Also war mir klar das ich ein Script haben muss welches bei jedem erstellen des Containers sicherstellt das das tt-rss Datenbankschema in der Datenbank sich befindet und die richtige Config an der richtigen Stelle liegt. Da kommt ein Liebling von mir ins Spiel: Ansible. Ich definiere einen ENTRYPOINT. Ein einfaches Bash-Script welches Ansible aufruft und danach Supervisor um alles zu starten. Mein Ansible-Playbook sieht so aus:

---
- hosts: localhost
  remote_user: root

  tasks:

    - name: create ttrss config.php
      template: src=templates/config.php.j2
                dest=/var/www/tt-rss/config.php

    - name: pause everything
      pause: seconds=30

    - name: ttrss db
      mysql_db: name=ttrss
                state=present
                login_host={{ lookup('env','DB_PORT_3306_TCP_ADDR') }}
                login_user=root
                login_password={{ lookup('env','DB_ENV_MYSQL_ROOT_PASSWORD') }}
      notify: import ttrss schema

  handlers:

    - name: import ttrss schema
      mysql_db: name=ttrss
                state=import
                target=/var/www/tt-rss/schema/ttrss_schema_mysql.sql
                login_host={{ lookup('env','DB_PORT_3306_TCP_ADDR') }}
                login_user=root
                login_password={{ lookup('env','DB_ENV_MYSQL_ROOT_PASSWORD') }}

Ich erstelle die Config aus einem Template. Dann kommt der “hacky” Teil. Ich muss 30 Sekunden warten. Dies beruht darauf das docker-compose alle Container parallel startet und dadruch bekomme ich Connection Probleme bei einrichten der Datenbank weil diese einfach noch nicht hochgefahren ist. Nicht schön… läuft aber. Dann gehen wir sicher das es eine DB mit dem Namen “ttrss” gibt. Wenn nicht wird der Handler import ttrss schema angestoßen der dann das Schema importiert. Die Supervisor-Config sieht so aus:

[supervisord]
nodaemon = true

[program:nginx]
command = nginx
user = root
autostart = true

[program:php]
command = php5-fpm --nodaemonize
user = root
autostart = true

[program:ttrss-update-daemon]
command = php /var/www/tt-rss/update_daemon2.php
user = www-data
autostart=true

Was dazu kam ist der ttrss-update-daemon. Er wird gestartet um die Feeds, im Hintergrund, zu aktualisieren. Die docker-compose.yml sieht wie folgt aus:

ttrss:
  build: .
  ports:
    - "127.0.0.1:9997:80"
  environment:
    - URL_PATH=https://reader.domain.foo
  links:
    - db
db:
  image: mariadb
  volumes_from:
    - ttrss-data
  environment:
    - MYSQL_ROOT_PASSWORD=mysecretpassword

Hier sieht mand ie ganze docker-compose Magic. Wir definieren alle Container die gebraucht werden und vor allem wie sie verlinkt werden sollen. Dann können wir auch gleich noch ein paar Variabeln mitgeben. URL_PATH wird für die tt-rss Config gebraucht und MYSQL_ROOT_PASSWORD um MariaDB zu initialisieren. Wir benutzen einen Data-Only-Container um die Datenbank zu speichern. Diesen legen wir mit docker run --name ttrss-data mariadb true an. Wir benutzen hier das mariadb-Image damit die Permissions mit dem Server übereinstimmen. docker-compose up und den Reverse Proxy setzen. Zack fertig! Hier gibt es alle benötigten Files.

Plex Media Server

Ich liebe Plex ja. Auch wenn es teilweise closed-source ist komme ich einfach nicht davon weg. Von der Usability habe ich bis jetzt nicht vergleichbares gefunden. Also was liegt näher als Plex auch im Container laufen zu lassen. Komischerweise war Plex die einfachste Aufgabe bis jetzt. Alles ziemlich straight-forward. Deswegen einfach hier alle Files. Bis jetzt rennt Plex. Na mal schauen :)

Hier sind die Links zu meinen Dockerfiles und den dazugehörigen Helper.



Jenkins, Docker und Ansible

/// d794cd8 /// jenkins docker ansible

Und ab in die Docker Hölle. Irgendwie scheint mir Docker nicht zu liegen. Es fängt schon damit an das mir noch nicht so ganz bewusst ist wieso man nicht einfach den Container schön macht und ihn dann immer wieder mit docker start mein-toller-container startet. Ich glaube es geht darum alles so weit wie möglich unabhängig zu machen. So mehr ich es benutze um so mehr wird mir langsam klar wie ich es “richtig” benutzen kann.

Und ab in die Docker Hölle. Irgendwie scheint mir Docker nicht zu liegen. Es fängt schon damit an das mir noch nicht so ganz bewusst ist wieso man nicht einfach den Container schön macht und ihn dann immer wieder mit docker start mein-toller-container startet. Ich glaube es geht darum alles so weit wie möglich unabhängig zu machen. So mehr ich es benutze um so mehr wird mir langsam klar wie ich es “richtig” benutzen kann. Es scheint aber noch ein sehr langer und schmerzhafter Weg zu werden.

Da ich in letzter Zeit ein wenig mit ansible rumgespielt habe, dachte ich ob dies ein Weg wäre alles für mich zu vereinfachen. Eine andere Alternative wäre Docker Compose gewesen. Es geht immer darum seine Container in einem YAML-File zu definieren und anstatt einer Batterie an Kommandos einzugeben, einfach den Composer oder halt Ansible anzuschmeißen. Docker kann schließlich schon ziemlich komplex werden wenn man Data-Container benutzt und auch noch mehrere Container miteinander verlinkt. Ansible nimmt einem dabei nicht nur das Orchestrieren mit Docker ab, es kümmert sich auch gleich noch um das gesammte Setup.

Ich habe mir mal ein praktisches Beispiel genommen. Ich habe ja auf meinem Raspi ein Jenkins laufen. Nicht gerade ein mächtiges Arbeitstier. Also wollte ich Jenkins schon seit einer Weile umziehen und dabei den Server nicht voll müllen. Schließlich braucht Jenkins für meine Tests auch Test-Datenbank usw. Diese sollten sich am besten nicht mit dem Host-System mischen. Dies ist das Ansible-Playbook was ich dafür genutzt habe. Ich versuche die Schritte durch die Kommentare zu erläutern. Das ganze gibt es auch als Repo auf GitHub.

---
# In diesem Fall führe ich alles lokal aus. Dies funktioniert natürlich auch
# perfekt remote.
- hosts: localhost

  # Eine Ubuntu-Box hier. Also wäre sudo nicht schlecht.
  sudo: yes

  vars:
    # Die user-Variabel benutze ich vor allem um meine Images zu benennen.
    - user: xsteadfastx

    # Es wird ein lokales temp-Verzeichnis definiert. Dies wird gebraucht
    # um die Docker Images zu bauen.
    - temp_docker_build_dir: /tmp/docker-jenkins

    # Das offizielle MySQL Image braucht ein definiertes root-Password um
    # zu bauen.
    - mysql_root_password: nicepassword

  tasks:

    # Wir adden den Docker GPG-Key damit wir die offiziellen Pakete ziehen
    # können. Ich habe die ID aus dem Install-Script rausgepoppelt.
    # Keine Garantie ob er in Zukunft auch stimmt.
    - name: APT | add docker repo key
      apt_key: keyserver=hkp://p80.pool.sks-keyservers.net:80
               id=36A1D7869245C8950F966E92D8576A8BA88D21E9
               state=present

    # Wir adden das offizielle Docker Repo für frische Versionen.
    - name: APT | add docker repo
      apt_repository: repo="deb https://get.docker.com/ubuntu docker main"
                      update_cache=yes
                      state=present

    # Docker installieren.
    - name: APT | install docker
      apt: name=lxc-docker
           update_cache=yes
           cache_valid_time=600
           state=present

    # Auf dem Docker-Host benötigt Ansible Python-Bindings. Das Problem mit
    # mit den offiziellen "python-docker" debs: Sie waren schlicht und einfach
    # zu alt. Also müssen wir es von PyPi installieren. Eigentlich nicht die
    # tolle Art... aber ich mache mal eine Ausnahme.
    - name: PIP | install docker-py
      pip: name=docker-py
           version=1.1.0

    # Es soll ein Temp-Verzeichnis zum bauen des jenkins-data Images
    # angelegt werden.
    - name: DIR | create jenkins-data docker build directory
      file: path="{{ temp_docker_build_dir }}/jenkins-data"
            state=directory

    # Kopiere das Dockerfile in des temporäre Build-Verzeichnis
    - name: COPY | transfer jenkins-data Dockerfile
      copy: src=files/Dockerfile-jenkins-data
            dest="{{ temp_docker_build_dir }}/jenkins-data/Dockerfile"

    # Endlich kann es losgehen... und wir können das Image bauen.
    - name: DOCKER | build jenkins data image
      docker_image: path="{{ temp_docker_build_dir }}/jenkins-data"
                    name="{{ user }}/jenkins-data"
                    state=present

    # Das gleiche machen wir jetzt für unser leicht angepasstes Dockerfile.
    - name: DIR | create jenkins docker build directory
      file: path="{{ temp_docker_build_dir }}/jenkins"
            state=directory

    - name: COPY | transfer jenkins Dockerfile
      copy: src=files/Dockerfile-jenkins
            dest="{{ temp_docker_build_dir }}/jenkins/Dockerfile"

    - name: DOCKER | build jenkins image
      docker_image: path="{{ temp_docker_build_dir }}/jenkins"
                    name="{{ user }}/jenkins"
                    state=present

    # Nun löschen wir das temporäre Verzeichnis.
    - name: RM | remove temp docker build directory
      file: path="{{ temp_docker_build_dir }}"
            state=absent

    # Das interessante an data-only Volumes ist, dass sie nicht gestartet
    # werden müssen um benutzt zu werden. Sie sind nur dazu da um von anderen
    # Containern genutzt zu werden. Es werden Volumes definiert die wir dann
    # benutzen zu können. Auch wenn der Jenkins Container gelöscht und wieder
    # neugestartet wird... benutzt man diesen Data-Container bleiben die Daten
    # in "/var/jenkins_home" bestehen.
    - name: DOCKER | jenkins data volume
      docker:
        name: jenkins-data
        image: "{{ user }}/jenkins-data"
        state: present
        volumes:
          - /var/jenkins_home

    # Das gleiche machen wir für den mysql-data Container.
    - name: DOCKER | mysql data volume
      docker:
        name: mysql-data
        image: busybox
        state: present
        volumes:
          - /var/lib/mysql

    # Nun starten wir den mysql Container. Wir benutzen das offizielle
    # MySQL-Image. Der state "reloaded" bedeutet, dass wenn sie die Config
    # ändert... dann wird der Container entfernt und neu erstellt.
    # Wichtig ist hier das "volumes_from". Hier geben wir den Container an
    # von dem die definierten Volumes benutzt werden sollen. Also werden sie
    # in einem eigenes dafür angelegten Container gespeichert.
    # Kapselung und so. Mit "env" können wir auch noch diverese
    # Environment dem Container mitgeben. In diesem Fall ein Passwort für den
    # root-User für den Server. Diese Variabel wird gebraucht damit der
    # erstellte Container auch gestartet werden kann.
    - name: DOCKER | mysql container
      docker:
        name: mysql
        image: mysql:5.5
        state: reloaded
        pull: always
        volumes_from:
          - mysql-data
        env:
          MYSQL_ROOT_PASSWORD: "{{ mysql_root_password }}"

    # now finally the jenkins container. it links to the mysql container
    # because i need some mysql for some tests im running in jenkins.
    # and with the link i can easily use the environment variables in jenkins.
    # i bind the jenkins port only to localhost port 9090. i use nginx for
    # proxying. in "volumes" i define my host directory in which my git repos
    # are. so jenkins think its all in a local directory.
    # and "/var/jenkings_home" is stored in the "jenkins-data" container.
    # voila.

    # Hier ist nun das Herzstück. Hier kommt alles zusammen. Wir erstellen
    # den Jenkins Container und starten ihn. Wir benutzen als Basis unser
    # Jenkins Image. Mit "links" können wir mehrere laufende Container
    # miteinander verbinden. Diese sind dann untereinander über ein virtualles
    # Netz verbunden. Gleichzeitig stellt Docker dann Environment-Variables
    # zu verfügung. Diese können wir ganz prima im System nutzen. In diesem
    # Beispiel brauche ich für Tests in Jenkins ein paar MySQL-Datenbanken.
    # So ist der MySQL-Container im Jenkins-Container sichtbar und vor allem
    # benutzbar. Mit "ports" können wir bestimmte Ports nach Aussen mappen.
    # In diesem Fall mappe ich den standard Jenkins Port "8080" auf localhost
    # Port "9090". Perfekt um ihn per NGINX von Aussen zugänglich zu machen.
    # Mit "volumes" definieren wir lokale Verzeichnisse die an einer
    # bestimmten Stelle im Container zu verfügung gestellt werden. In diesem
    # Fall den lokalen Ordner mit den Git-Repos "/srv/git" in das
    # Container-Filesystem unter "/data". Also kein gefummel mit SSH-Keys
    # um Git im Container zu benutzen. Als letztes "volumes_from".
    # Damit können wir die Volumes aus einem anderen Container, in diesem Fall
    # des data-only Containers "jenkins-data", benutzen. Diese Daten würden
    # so den Tot des Jenkins Container überleben. Genau das was wir wollen.
    - name: DOCKER | jenkins container
      docker:
        name: jenkins
        image: "{{ user }}/jenkins"
        state: reloaded
        links:
          - "mysql:mysql"
        ports:
          - "127.0.0.1:9090:8080"
        volumes:
          - "/srv/git:/data"
        volumes_from:
          - jenkins-data

Hier das jenkins-data Dockerfile:

FROM busybox

RUN mkdir -p /var/jenkins_home \
  && chown -R default /var/jenkins_home

VOLUME /var/jenkins_home

CMD ["true"]

Und hier mein Jenkins Dockerfile. Ich benutze eigentlich das Offizielle noch mit ein paar extra Sachen die ich installiere. Dies sind auch spezielle Packages für meine Tests. Also Python-Kram.

FROM jenkins

USER root

RUN apt-get update && apt-get install -y python-dev python-setuptools python-virtualenv python-pip libjpeg-dev ansible mysql-client && rm -rf /var/lib/apt/lists/* && pip install tox

Also schießen wir mal los:

ansible-playbooks -i hosts site.yml -c local --ask-become-passansible-playbooks -i hosts site.yml -c local --ask-become-pass

Na mal schauen ob irgendwann der Zeitpunkt kommt an dem ich Docker besser verstehe. Ich verlinke hier nochmal den Docker Beitrag von Andrew T. Baker von der PyCon 2015.



MySQL Server mit Docker

/// d794cd8 /// docker mysql

Es ist schon ein Jahr her das ich mir mal Docker angeschaut habe. Bis dato war meine einzige Erfahrung mit “Virtualisierung” VMware im Desktopeinsatz und Virtualbox wenn ich mal eine Windows-Umgebung unter Linux brauche. Nun gibt es diesen Hype um Docker. Vereinfacht gesagt geht es darum einzellne Anwendungen in seperaten Linux-Container laufen zu lassen. Zum Beispiel kann man als Entwickler seine schöne Webapp komplett zusammen kleben, den Container exportieren und dem Admin zum ausrollen geben.

Es ist schon ein Jahr her das ich mir mal Docker angeschaut habe. Bis dato war meine einzige Erfahrung mit “Virtualisierung” VMware im Desktopeinsatz und Virtualbox wenn ich mal eine Windows-Umgebung unter Linux brauche. Nun gibt es diesen Hype um Docker. Vereinfacht gesagt geht es darum einzellne Anwendungen in seperaten Linux-Container laufen zu lassen. Zum Beispiel kann man als Entwickler seine schöne Webapp komplett zusammen kleben, den Container exportieren und dem Admin zum ausrollen geben. Desweitern ist es auch möglich mehrere Container zusammen zu kleben. Zum Beispiel wenn die App in ihrem eignen Container noch eine Datenbank braucht.

Ich wollte es mal ausprobieren. Und zwar einen einfachen MySQL-Server in einem Docker Container laufen lassen. Problem ist nur: Hat man sein Container fertig und darin entstehen Veränderungen, zum Beispiel in dem Sachen in die Datenbank geschrieben werden, müssten diese Änderungen explizit wieder “committed” werden um sie auch nach einem Neustart zu erhalten. Nun gibt es einen anderen Ansatz für dieses Problem. Es gibt unter Docker Volumes. Diese wollen genau das umgehen. Man definiert Verzeichnisse im Docker-Container die erhalten bleiben sollen. Funktioniert auch ganz fein nur sollte man diese Volumes in einem seperaten Container anlegen und den MySQL-Container anweisen die Volumes aus dem DATA-Container zu benutzen. Dies spielt der Modularität Dockers völlig in die Karten. Docker hat mein Gehirn ziemlich zermatscht, aber im Endeffekt hat es geklappt.

Erstmal habe ich Daten-Container mit dem Namen mysql-data angelegt:

docker run --name mysql-data -v /var/lib/mysql -v /var/log/mysql 32bit/ubuntu:14.04 true

Der macht nicht viel. Er definiert die Verzeichnisse /var/lib/mysql und /var/log/mysql als Volumes, startet ein ubuntu Image und führ den Befehl true aus. Mit docker ps -a kann man sich alle Container anschauen:

706e6f2ddc21        32bit/ubuntu:14.04   "true"                 10 days ago         Exited (0) 10 days ago                            mysql-data

Dieser muss garnicht laufen. Er definiert quasi nur die Volumes für uns. Nun habe ich mich an den MySQL-Container gemacht. Dafür habe ich von einem Ubuntu-Image eine Bash-Shell gestartet. Wichtig dabei die Angabe die volumes von unserem Container mit dem Namen mysql-data zu verwenden. Das -p 3306:3306 sagt das er den Port des MySQL-Servers auf den lokalen Port 3306 auf dem Host-Computer durchleiten soll.

   docker run -t -i --name mysql-server --volumes-from mysql-data -p 3306:3306 32bit/ubuntu:14.04 /bin/bash

In der Bash-Shell installieren wir nun den MySQL-Server und alles wird eingerichtet. Ist dies fertig, detached man den Container mit dem Tasterturkürzel CTRL-p CTRL-q. Der Container sollte nun trotzdem weiterlaufen. Dies kann man sich mit docker ps anschauen. Um alle Änderungen zu speichern committed man die Änderungen mit:

docker commit 72 mysql-server

Die 72 ist der Anfang des Hashes mit dem unser Container identifiziert wird. Nun haben wir ein Image mit dem Namen mysql-server. Wir starten nun alles komplett mit folgendem Befehl:

docker run -d --name mysql-server --volumes-from mysql-data -p 3306:3306 mysql-server /usr/bin/mysqld_safe

Dies startet den Container im Daemon-Mode mit dem Namen mysql-server, den Namen für den Container und nicht für das Image, mit dem Volumes von unserem Data-Container mit dem Namen mysql-data, den Port 3306 gemappt auf 3306 am Host, und von dem Image mysql-server. Es wird der Befehl /usr/bin/mysqld_safe gestartet. Läuft alles können wir später den Container starten mit dem Befehl

docker start mysql-server

Das wars eigentlich. Nun sollte man normal connecten können.

mysql -uroot -p -h127.0.0.1

Ich hoffe ich habe alles zusammen bekommen. War ein ganz schöner Ritt und sehr Braintwisting am Anfang für mich.



1 of 1