Docker Container Loading - Teil 3

10 kreative Wege, Docker Images zu bauen: Ansible Container

Roland Huß

© Shutterstock.com / Good_24

Ist der klassische Weg, Docker Images über docker build zu bauen, wirklich in jedem Fall die beste Art und Weise? In seiner Artikelserie geht Roland Huß, Software Engineer bei Red Hat, dieser Frage auf den Grund und stellt zehn alternative und kreative Wege vor, Docker Images zu erstellen.

Im vorherigen Artikel haben wir gelernt, wie man mithilfe von Templates einige der Einschränkungen von Dockerfiles umschiffen kann. Heute werden wir auf Dockerfiles komplett verzichten und uns auf die Erstellung von Docker Images aus Containern mit docker commit konzentrieren. „Aber das ist doch Blödsinn, solche Images sind ja nicht reproduzierbar“, mag man sich da denken. Nun ja, das stimmt schon. Aber nur, wenn der Container von Hand „bestückt“ wurde. Was wäre denn, wenn wir einen laufenden Container mit einem Configuration-Management-System (CMS) wie Chef, Puppet or Ansible genauso konfigurieren würden wie „bare metal“? Genau: es wäre mindestens genauso reproduzierbar wie ein Dockerfile. Am Ende der Provisionierung machen wir dann einfach ein docker commit und haben unser Docker Image, das wir jederzeit wieder exakt gleich via CMS erstellen können. Genau diesen Ansatz verfolgt Ansible Container.

Das Prinzip ist wie gerade beschrieben:

  • Zu Beginn wird ein sogenannter Conductor als Container gestartet, in dem Python und Ansible installiert sind.
  • Danach werden ein (oder auch mehrere) Basis-Container gestartet, die in einer zentralen, docker compose ähnlichen Datei definiert sind. Diese enthält auch die Ansible Roles, die auf die Container angewendet werden sollen.
  • Dann wird vom Conductor aus ein Ansible Playbook ausgeführt, das dynamisch aus den konfigurierten Rollen erstellt wird. Dieses verbindet sich mit den laufenden Containern und führt dort die Ansible Tasks aus, die innerhalb der Rollen definiert sind.
  • Am Ende werden die Container heruntergefahren und mit docker commit entsprechende Docker Images erstellt.

Eine spannende Frage ist natürlich, wie Ansible im Container Prozesse ausführen kann und Dateien in den Container bekommt. Normalerweise verwenden CM-Systeme SSH als das Protokoll der Wahl, was wiederum voraussetzen würde, dass ein sshd auf dem jeweiligen Container laufen müsste. Aus verschiedenen Gründen ist ein sshd im Container unerwünscht, da dieser ja auch in dem finalen Image enthalten sein würde. Daher geht Ansible Container einen anderen Weg: Es verwendet einen eigenes Docker Connection Plug-in, das intern docker cp und docker exec verwendet, sodass auf den Ziel-Containern nichts weiter installiert sein muss. Nun, ganz stimmt das nicht: Die Minimalvoraussetzung ist, das Python auf dem Zielsystem vorhanden ist, damit Ansible seine Tasks ausführen kann. Da jedoch viele Basis-Images von vornherein Python mitbringen und, falls es wirklich notwendig ist, man ja vor dem docker commit Python wieder deinstallieren könnte, ist das wohl für die meisten Anwendungsfälle keine grosse Einschränkung.

DevOpsCon Whitepaper 2018

Free: 40+ pages of DevOps expert knowledge

Learn about Containers,Continuous Delivery, DevOps Culture, Cloud Platforms & Security with articles by experts like Kai Tödter (Siemens), Nicki Watt (OpenCredo), Tobias Gesellchen (Europace AG) and many more.

Beispiel

Schauen wir uns ein einfaches Beispiel an, bei dem wir ein Image mit einem Nginx-Server und einer eigenen statischen Seite erstellen. Zunächst einmal müssen wir Ansible Container installieren. Das geht am besten mit dem Python Installer pip:

$ sudo pip install ansible-container[docker,k8s]
....

Aktuell gibt es ein Problem mit der neuesten Python-Docker-Bibliothek (betrifft nicht nur Ansible Container), sodass ein Downgrade erforderlich ist:

$ sudo pip install 'docker>=2.4.0,<3.0'
....

Die aktuelle Version von Ansible Container ist 0.9.2 und es sollte auch keine ältere installiert werden, da sich das Konfigurationsformat über die Zeit hinweg geändert hat und wir uns hier auf die letzte Version 2 des Formats stützen.

Nun können wir ein leeres Projekt initialisieren:

$ ansible-container init

Ansible Container initialized from Galaxy container app 'ansible.django-gulp-nginx'

Es werden mehrere Beispieldateien angelegt. Die zentrale Datei hier ist container.yml. Sie ist sehr ähnlich zu einer docker-compose.yml-Spezifikation aufgebaut und bestimmt, wie Docker Images gebaut werden, aber auch, wie sie zur Laufzeit gestartet und verlinkt oder welche Images zu einer Registry gepusht werden. Auch kann aus der container.yml ein Ansible Playbook generiert werden, das gleich beschreibt, wie die Images in der Cloud (z.B. auf Kubernetes oder OpenShift) deployt werden sollen.

Wir beschränken uns aber in diesem Artikel ausschließlich auf das Bauen von Images und fokussieren uns daher nur auf den Teil in container.yml, der für die Erstellung von Images relevant ist.

In der container.yml gibt es eine Sektion services:, die wir für einen einfachen Webserver zum Ausliefern einer statischen Webseite konfigurieren wollen.

Um den Webserver zu installieren, holen wir uns eine Nginx-Rolle von Ansible Galaxy:

$ ansible-container install ansible.nginx-container
Parsing conductor CLI args.
- downloading role 'nginx-container', owned by ansible
- downloading role from https://github.com/ansible/nginx-container/archive/master.tar.gz
- extracting ansible.nginx-container to /tmp/tmpBFIZj5/ansible.nginx-container
- ansible.nginx-container (master) was installed successfully
Conductor terminated. Cleaning up.	command_rc=0 conductor_id=365197... save_container=False

Hierbei wird automatisch die container.yml angepasst:

services:
  ansible.nginx-container:
    roles:
    - ansible.nginx-container

Die hinzugefügte Rolle ansible.nginx-container wird ebenfalls in requirements.xml definiert und als externe Rolle referenziert.

Nun wollen wir eine eigene Rolle hinzufügen, die einfach eine statische HTML-Seite installiert. Dazu erzeugen wir eine YAML-Datei roles/hello-world/tasks/main.yml:

- name: Create "hello world" HTML page
  copy:
    content: "<html><body><h1>Hello Ansible Container !</h1></body></html>"
    dest: /static/index.html

Das Zielverzeichnis /static ist dabei von ansible.nginx-container vorgegeben, sodass statische Dateien aus diesem Verzeichnis ausgeliefert werden. Diese Ansible-Rolle tragen wir in container.yml ein und passen dabei noch ein paar Sachen an:

services:
  web:
    from: centos:7
    roles:
    - ansible.nginx-container
    - hello-world
    ports:
    - "8000:8000"

Neben dem Hinzufügen von hello-world zu der Liste der auszuführenden Rollen haben wir den Service noch in web umbenannt und für das spätere Testen ein einfaches Portmapping für den Nginx Port eingetragen.

Image bauen & starten

Nun ist es an der Zeit, das Image zu bauen:

$ ansible-container build

Building Docker Engine context...
Starting Docker build of Ansible Container Conductor image (please be patient)...
Parsing conductor CLI args.
Docker™ daemon integration engine loaded. Build starting.	project=demo
Building service...	project=demo service=web

PLAY [web] *********************************************************************

TASK [Gathering Facts] *********************************************************
ok: [web]

TASK [ansible.nginx-container : Install epel-release] **************************
changed: [web]

TASK [ansible.nginx-container : Install nginx] *********************************
changed: [web] => (item=[u'nginx', u'rsync'])

.....
.....

PLAY RECAP *********************************************************************
web                        : ok=18   changed=14   unreachable=0    failed=0

Applied role to service	role=ansible.nginx-container service=web
Committed layer as image	image=sha256:0f3581... service=web

PLAY [web] *********************************************************************

TASK [Gathering Facts] *********************************************************
ok: [web]

TASK [hello-world : Create "hello world" HTML page] ****************************
changed: [web]

PLAY RECAP *********************************************************************
web                        : ok=2    changed=1    unreachable=0    failed=0

Applied role to service	role=hello-world service=web
Committed layer as image	image=sha256:be46b1.... service=web
Build complete.	service=web
All images successfully built.
Conductor terminated. Cleaning up.	command_rc=0 conductor_id=589cb00... save_container=False

In der Ausgabe sieht man schön, wie der Conductor gestartet wird und die beiden spezifizierten Rollen von zwei dynamisch erzeugten Playbooks ausgeführt werden. Interessant ist auch, dass nach jeder Rolle eine Image-Schicht comitted wird. Man kann aber auch mit der Option --flatten einstellen, dass am Ende das erzeugte Image aus nur einer Schicht besteht.

Wenn wir jetzt die Images unseres Docker daemons auflisten, sehen wir, dass ein Image mit zwei Tags erzeugt wurde:

docker images | head -3
REPOSITORY   TAG             IMAGE ID      CREATED             SIZE
demo-web     20180407081908  322c2515597b  29 seconds ago      266MB
demo-web     latest          322c2515597b  29 seconds ago      266MB

Zum Schluss können wir nun auch direkt mit Ansible Container das Image starten:

$ ansible-container run
Parsing conductor CLI args.
Engine integration loaded. Preparing run.	engine=Docker™ daemon
Verifying service image	service=web

PLAY [Deploy demo] *************************************************************

TASK [docker_service] **********************************************************
changed: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=1    unreachable=0    failed=0

All services running.	playbook_rc=0
Conductor terminated. Cleaning up.	command_rc=0 conductor_id=969f40... save_container=False

Dank unserer Port-Zuordnung liegt nun unter http://localhost:8000 unsere fantastische Webseite, was wir leicht mit einem Browser überprüfen können (bzw. mit der IP des Docker daemons, falls dieser nicht auf localhost läuft)

Damit sind wir am Ende unseres Beispiels, das auch direkt auf GitHub unter rhuss/ansible-container-demo heruntergeladen werden kann.

Zusammenfassung und Ausblick

Der Ansatz mit einem Configuration-Management-System Docker-Container zu bestücken, hat seinen Charme. Der grösste Vorteil ist sicherlich, dass viele bereits existierende Roles/Recipes/Manifests wiederverwendet werden können. Auch sind die Möglichkeiten, die ein CMS bietet, wesentlich größer als das doch recht eingeschränkte Dockerfile-Vokabular.

Das Schöne ganz speziell bei Ansible Container (im Gegensatz zu anderen CM-Lösungen) ist, dass es dank der flexiblen Architektur des Connector-Plug-ins von Ansible sehr einfach möglich ist, ohne SSH die Container zu provisionieren.

Ein Nachteil des Ansatzes mit Ansible Container ist sicherlich, dass er wesentlich komplexer ist als die einfache Verwendung eines Dockerfiles. Auch sollte man den Laufzeit-Overhead nicht unterschätzen. Beim ersten Lauf fühlt sich Ansible Container recht zäh an, jedoch wird das bei den anschließenden Aufrufen durch aggressives Caching deutlich besser.

Lesen Sie auch: 10 kreative Wege, Docker Images zu bauen: Dockerfile Template

Nicht zuletzt sollte noch erwähnt werden, dass Ansible Container inzwischen ein offizielles Ansible-Projekt und auch ganz hervoragend dokumentiert ist.

Damit sind wir auch schon am Ende für heute. In der nächsten Etappe werden wir dann einen ganz ähnlichen Ansatz mit Packer von HashiCorp kennenlernen.

Mehr zum Thema:

10 kreative Wege, Docker Images zu bauen: Dockerfile & Docker Commit

Geschrieben von
Roland Huß
Roland Huß
Roland Huß ist ein Software Engineer, der für Red Hat an fabric8, einer Microservices-Plattform für Kubernetes und OpenShift arbeitet. Er entwickelt seit fast 20 Jahren, hat aber niemals seine Wurzeln als Systemadministrator vergessen. Roland arbeitete aktiv in Open-Source-Projekten mit und betreut sowohl Jolokia, die JMX-HTTP Bridge als auch das populäre Docker-Maven-Plug-in fabric8io. Und er liebt Chilis.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: