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.
Ansible ist schon sehr nice. Benutzt man es mit Windows, ist es auch noch nice aber manchmal fühlt man sich dabei wie ein Ritt auf einem Vulkan, durch die Hölle… oder sowas. Meist funktioniert es aber was im Hintergrund passiert gleicht Feen-Magie.
Da wir es wagen einen Proxy zu benutzen scheint es völlig unmöglich zu sein an Windows Updates zu kommen. Und in letzter Zeit scheinen auch meine Workarounds nicht zu fruchten.
Ansible ist schon sehr nice. Benutzt man es mit Windows, ist es auch noch nice aber manchmal fühlt man sich dabei wie ein Ritt auf einem Vulkan, durch die Hölle… oder sowas. Meist funktioniert es aber was im Hintergrund passiert gleicht Feen-Magie.
Da wir es wagen einen Proxy zu benutzen scheint es völlig unmöglich zu sein an Windows Updates zu kommen. Und in letzter Zeit scheinen auch meine Workarounds nicht zu fruchten. Immer wieder hängen die Updates tagelang in der Pipeline und können nicht runtergeladen oder installiert werden. Also schaute ich mir mal wieder einen alten Bekannten an: WSUS Offline Update. Ein Paket aus Scripten das Updates zentral runterlädt und dann offline installiert werden kann. Das schöne daran: Das Download-Script liegt auch in Shell vor und kann so direkt auf dem Linux Samba Server ausgeführt werden. Nur wie kommen die Updates auf die Clients? Wie immer lautet die Antwort: Per Ansible Playbook:
---
- name: mount updates share and run update
raw: "net use U: \\\\meinsambaserver\\updates /persistent:no; cmd /c U:\\wsusoffline\\client\\cmd\\DoUpdate.cmd"
register: command_result
changed_when: "'Installation successful' in command_result.stdout"
failed_when:
- "'Nothing to do!' not in command_result.stdout"
- "'Installation successful.' not in command_result.stdout"
WSUS Offline liegt dabei auf einem Updates-Share. Die zwei schwierigsten Sachen war das Escaping der Orte für den Kommandoaufruf und den richtigen Status erkennen. Aus irgendeinem Grund hat WSUS Offline immer den Return Code 1 raus wenn er was installiert hat. Mit der Hilfe von changed_when und failed_when suche ich in dem STDOUT-Output nach bestimmten Stichworten um den richtigen Status zu bekommen. Bestimmt decke ich nicht alles ab, aber in meinen ersten Tests funktioniert es.
Das Ansible Modul win_package beruht auf die Annahme das die product_id in der Registry vorhanden ist oder eben nicht. Davon macht es abhängig ob ein Paket installiert werden soll oder ob es schon vorhanden ist. Nun kann es ja auch vorkommen das man ein Paket, obwohl es laut Registry schon installiert ist, es noch einmal installieren möchte. Quasi ein Upgrade machen. Schön wäre es wenn er nicht nur schaut ob das Paket installiert ist, sondern auch die installierte Version.
Das Ansible Modul win_package beruht auf die Annahme das die product_id in der Registry vorhanden ist oder eben nicht. Davon macht es abhängig ob ein Paket installiert werden soll oder ob es schon vorhanden ist. Nun kann es ja auch vorkommen das man ein Paket, obwohl es laut Registry schon installiert ist, es noch einmal installieren möchte. Quasi ein Upgrade machen. Schön wäre es wenn er nicht nur schaut ob das Paket installiert ist, sondern auch die installierte Version. Daran könnte man Task Entscheidungen treffen. Dies mache ich nun manuell. Ein Beispiel für VLC:
---
# Der ganz normale Install-Task. Es wird nach der product_id gesucht gegebenenfalls installiert
- name: install
win_package:
product_id="VLC media player"
path="//myserver/updates/software/vlc/vlc-2.2.2-win32.exe"
arguments="/L=1031 /S"
# Ein Powershell Snippet das die Version des installierten Pakets in der Variabel "version" speichert
- name: check version
raw: (Get-ItemProperty "HKLM:\SOFTWARE\wow6432node\Microsoft\Windows\CurrentVersion\Uninstall\VLC media player").DisplayVersion
register: version
- name: upgrade
win_package:
product_id="VLC media player upgrade" # Muss anders sein damit das Paket nochmal installiert wird
path="//myserver/updates/software/vlc/vlc-2.2.2-win32.exe"
arguments="/L=1031 /S"
register: upgrade_results # Speichert das Ergebnis des Tasks in die Variabel "upgrade_results"
changed_when: '"was installed" in upgrade_results.msg' # Ändert den Status auf "changed" wenn der String "was installed" im Ergebnis ist
failed_when: '"was installed" not in upgrade_results.msg' # Status "failed" wenn "was installed" nicht im Ergebnis ist
ignore_errors: True # Sonst bricht er ab bevor der Task überhaupt die Variabel "upgrade_results" befüllt
when: '"2.2.2" not in version.stdout_lines' # Den Task nur ausführen wenn die Version eine andere ist
Gerade läuft das letzte Windows 7 auf Windows 10 Update. Diese Möglichkeit habe ich genutzt um für die Administration der Clients von SaltStack zu Ansible zu wechseln. Windows Administration ist für mich eher so ein leidiges Thema, was mich ziemlich oft auf die Palme bringt. Ansible benutze ich auf den Linux Servern für fast alles. Mal schnell ein paar Update einspielen oder auch für das ausrollen meines batcaves.
Master vorbereiten Neben Ansible sollte man noch das Modul pywinrm installieren:
Gerade läuft das letzte Windows 7 auf Windows 10 Update. Diese Möglichkeit habe ich genutzt um für die Administration der Clients von SaltStack zu Ansible zu wechseln. Windows Administration ist für mich eher so ein leidiges Thema, was mich ziemlich oft auf die Palme bringt. Ansible benutze ich auf den Linux Servern für fast alles. Mal schnell ein paar Update einspielen oder auch für das ausrollen meines batcaves.
Master vorbereiten
Neben Ansible sollte man noch das Modul pywinrm installieren:
pip install "pywinrm>=0.1.1"
Clients vorbereiten
Um Ansible unter Windows zu benutzen muss ein PowerShell Script ausgeführt werden. Damit dies funktioniert muss erst eine PowerShell Policy angepasst werden. Dies tut man mit set-executionpolicy remotesigned. Danach Script ausführen und alles sollte eingerichtet sein.
Inventory
Auch die hosts-Datei muss ein wenig angepasst werden. Es werden ein paar zusätzliche Daten benötigt. Wenn das hosts-File so aussieht:
[windows]
192.168.1.5
192.168.1.6
192.168.1.7
habe ich eine group_vars/windows.yml angelegt die so aussieht:
Dieses File kann man mit ansible-vault encrypt group_vars/windows.yml verschlüßeln.
Der erste Test
ansible windows -m win_ping --ask-vault-pass sollte für einen ersten Versuch reichen.
Pakete installieren
Hier ein einfaches Beispiel um ein ein Paket zu installieren:
---
- name: Install the vc thingy
win_package:
name="Microsoft Visual C thingy"
path="http://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x64.exe"
Product_Id="{CF2BEA3C-26EA-32F8-AA9B-331F7E34BA97}"
Arguments="/install /passive /norestart"
Ich hatte mit fast allen Paketen Probleme die product_id herauszufinden. Erst dachte ich es handelt sich hierbei um den Titel der Software in den Systemeinstellungen unter Windows. Pustekuchen. Man muss sich diese ID aus den Registry popeln. Diese befindet sich unter HKLM:\Software\microsoft\windows\currentversion\uninstall. Aber die sah bei mir ziemlich dünn aus und war bei weiten nicht mit allen Sachen gefüllt die ich installiert hatte. Stellt sich herraus das ein Teil der Registry sich unter HKLM:\Software\wow6432node\microsoft\windows\currentversion\uninstall versteckt. Und zwar wenn es sich um 32bit Pakete handelt.
Ich benutze Ansible für viele Sachen. Unter anderem habe ich ein Set an Playbooks und Roles um meine Server so anzupassen das ich mich auf ihnen wohl fühle. Bis jetzt habe ich immer Ansible auch auf den Servern installiert und es dann lokal ausgeführt. Aber dabei nutze ich nicht das eigentlich Ansible Feature: das Ganze Remote über SSH auszurollen. Problem: In den Playbooks muss es die Zeile - hosts: geben. Aber eigentlich will ich das alles Variabel ausführen können.
Ich benutze Ansible für viele Sachen. Unter anderem habe ich ein Set an Playbooks und Roles um meine Server so anzupassen das ich mich auf ihnen wohl fühle. Bis jetzt habe ich immer Ansible auch auf den Servern installiert und es dann lokal ausgeführt. Aber dabei nutze ich nicht das eigentlich Ansible Feature: das Ganze Remote über SSH auszurollen. Problem: In den Playbooks muss es die Zeile - hosts: geben. Aber eigentlich will ich das alles Variabel ausführen können. Zum Beispiel nutze ich die gleichen Files auch um meine Vagrant Container einzurichten. Wieso also beim Aufruf die Hosts dem Playbook nicht einfach übergeben? Die Lösung ist dann doch wieder einmal einfacher als man denkt. Man benutzt die Möglichkeit in Playbooks und Roles Variabeln einzusetzen.
Ab und zu kommt es dann doch einmal vor das man seinen Fileserver umziehen muss. Diese Migration kann man gut benutzen um zum Beispiel mal ein paar Permissions gerade zu ziehen oder andere Kleinigkeiten anzupassen. Nun hatte ich mich dran gemacht ein Script zu schreiben mit allen Befehlen die ich dazu brauche. Wir kennen alle die Nachteile von schnell gehackten Bash-Scripten. Irgendwo ein Dreher drin und die Daten müssen aus dem Backup geholt werden oder man vergisst User oder Gruppen.
Ab und zu kommt es dann doch einmal vor das man seinen Fileserver umziehen muss. Diese Migration kann man gut benutzen um zum Beispiel mal ein paar Permissions gerade zu ziehen oder andere Kleinigkeiten anzupassen. Nun hatte ich mich dran gemacht ein Script zu schreiben mit allen Befehlen die ich dazu brauche. Wir kennen alle die Nachteile von schnell gehackten Bash-Scripten. Irgendwo ein Dreher drin und die Daten müssen aus dem Backup geholt werden oder man vergisst User oder Gruppen.
Ich werde immer mehr zum Ansible Fan. Vor allem das strukturiere definieren von Tasks hilft mir sehr meine Aufgabe darzustellen. Ein weiterer Vorteil ist das Ansible sich um so Sachen wie “Errorhandling” kümmert und es auch möglich ist Results aus dem einem Task im anderen wieder aufzugreifen.
Hier passiert nichts wildes. Wir bereiten den neuen Fileserver vor mit allen Benutzern, Gruppen und den Files die wir benötigen. Dazu gibt es ein paar Tasks die die Permissions gerade ziehen während wir migrieren. Hier das Playbook was ich dafür geschrieben habe. Ich habe es versucht zu kommentieren.
Update: Ich habe einen kapitalen Fehler begangen. Ich habe zweimal eine Variabel mit dem Namen task registriert. Da die notifizierten Tasks erst am Ende ausgeführt werden, wird task einfach überschrieben. Jetzt haben beide Tasks zwei verschiedene Variabeln.
---
# Ich führe das ganze nicht remote aus sondern lokal.
#
- hosts: localhost
sudo: yes
tasks:
- name: install rsync
apt: name=rsync
state=present
# Hier legen wir zwei Gruppen an.
#
- name: add groups
group: name={{ item }}
state=present
with_items:
- intern
- extern
# Wir legen zwei Benutzer an. Die im Dict angegebenen Gruppen werden der
# Gruppe "sambashare" hinzugefügt. Nichts besonderes. Die Benutzer
# haben "/bin/false" als Shell und ein Homeverzeichnis in "/home"
#
- name: add users
user: name={{ item.user }}
state=present
shell=/bin/false
home=/home/{{ item.user }}
groups=sambashare,{{ item.groups }}
append=yes
with_items:
- { user: 'user1', groups: 'intern' }
- { user: 'user2', groups: 'intern,extern' }
# Ansible hat ein "synchronize"-Modul was ein Wrapper für Rsync ist.
# Wirklich schön. So muss man nicht ein Script zusammen hacken sondern
# kann die kompletten Möglichkeiten von Ansible zu nutzen.
#
# Wir speichern die Task-Results in der Variabel "task" damit wir sie in
# den Handlern benutzen können. Ein Möglichkeit um zum Beispiel nur
# die Permissions anzuwenden an das Item welches sich verändert hat.
# Im Normalfall würde er bei nur einem geänderten Item die Handler laufen
# lassen. Diese würden dann über alle Verzeichnisse rüber laufen und
# die Permissions ändern. Da es sich um große Verzeichnisse handelt,
# musste es eine bessere Lösung geben. Die Logik der Handler werden
# später erklärt.
#
- name: sync freigaben
synchronize: src=root@192.168.1.1:/srv/samba/{{ item.src }}/
dest=/srv/samba/{{ item.dest }}
perms=no
group=no
delete=yes
with_items:
- { src: 'Erstefreigabe', dest: 'erstefreigabe' }
- { src: 'Zweitefreigabe', dest: 'zweitefreigabe' }
register: freigaben
notify:
- set freigaben group
- set freigaben file permissions
- set freigaben group permissions
- set freigaben permissions
- name: sync homes
synchronize: src=root@192.168.1.1:/home/{{ item }}/
dest=/home/{{ item }}
group=no
delete=yes
with_items:
- user1
- user2
register: homes
notify:
- set home group
handlers:
# Hier haben wir so einen Handler. Erstmal iteriert er über alle
# Task-Results. Und nun kommt "when" ins Spiel: Er führt das Kommando
# nur aus wenn ein Item als geändert gilt. So gibt es wenig Overhead
# beim Ausführen der Handler. Sie werden nur ausgeführt wenn es auch
# wirklich nötig ist.
#
- name: set freigaben group
command: chgrp -R {{ item.item.dest }} /srv/samba/{{ item.item.dest }}
with_items: freigaben.results
when: item.changed == True
- name : set freigaben file permissions
command: find /srv/samba/{{ item.item.dest }} -type f -exec chmod 0664 {} \;
with_items: freigaben.results
when: item.changed == True
- name : set freigaben group permissions
command: find /srv/samba/{{ item.item.dest }} -type d -exec chmod 2775 {} \;
with_items: freigaben.results
when: item.changed == True
- name: set freigaben permissions
file: path=/srv/samba/{{ item.item.dest }}
state=directory
mode=2775
owner=root
group={{ item.item.dest }}
with_items: freigaben.results
when: item.changed == True
- name: set home group
command: chgrp -R {{ item.item }} /home/{{ item.item }}
with_items: homes.results
when: item.changed == True
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.