Skip to content

Lab 4: Ein gutes Image, automatisch gebaut

Zur Erininnerung: Bisher haben wir Images wie folgt gebaut:

Irgendwo auf der Festplatte des Servers haben wir ein Dockerfile angelegt und dieses per docker build . zu einem Image zusammengebaut. Ein solches Image könnte man mit docker push <id> in den Docker-Hub kopieren. Doch so flüchtig soll unser Produktivsystem nicht aufgebaut werden. In diesem Lab soll einmal systematisch eine Automation eingerichtet werden.

Voraussetzung um die Anleitung nachzuvollziehen, ist ein Account bei Docker und einer bei GitHub (letzteren dürften die meisten Admins und Entwickler haben). Hier geht es zum Anlegen der Accounts:

1. Das Repo für den Code

Als simples Beispiel soll ein Container mit der Open-Source-Dokumentationssoftware "MkDocs" gebaut werden. In diesen Container sollen die Inhalte der Dokumentation (die ebenfalls im Repository liegen) eingebacken werde. Der fertige Container soll also ohne Volumes einfach laufen und die komplette Doku ausliefern. Mit jedem Push ins Repo soll ein aktuelles Image erzeugt werden.

Die Reise beginnt bei einem GitHub-Repository für den Code. Legen Sie ein solches an: https://github.com/new Das Repo darf grundsätzlich public oder private sein. Für das Beispiel nehmen Sie zunächst ein öffentliches Repo.

Das Repo für dieses Beispiel heißt demo-docs. Wenn Sie es anders nennen, müssen Sie später ggf. den Namen ändern.

1.2 Das Dockerfile

Dieses Lab soll auch die Denkweise veranschaulichen, wie man an das Bauen eines Images herangeht:

  • Gibt es ein MkDocs-Image (am besten der Entwickler) -> nein
  • Gibt es Drittanbieter-Images -> eher nein

Also muss ein eigenes Image her. Dazu lohnt ein Blick in die Doku von MkDocs. Was dort steht, muss als Rezept in ein Dockerfile gelangen:

pip install --upgrade pip
pip install mkdocs
mkdocs serve

MkDocs läuft also mit Python und kommt mit dem Python-Paketmanager Pip auf eine Maschine. Als Basis-Image sollten wir uns nach Python umsehen. Die gute Nachricht: Es gibt ein offizielles Image und auch eins auf Alpine-Basis. Damit können wir ein Dockerfile im Repo anlegen:

FROM python:3-alpine
RUN pip install --upgrade pip && pip install mkdocs
EXPOSE 8080
CMD ["mkdocs", "serve"]

Was jetzt folgt, ist Versuch und Irrtum. Versuchen wir, dieses Dockerfile zu bauen:

docker build .

Während Docker baut (etwa 3 Minuten) ist es Zeit für einen Kaffee.

Schichten sparen

Während das läuft, eine Anmerkung zum &&. Damit kann ich unter Linux zwei Befehle aneinander kleben. Ich könnte auch für jeden Befehl eine Zeile mit RUN ins Dockerfile schreiben. Docker erzeugt aber für jede Zeile eine Schicht. Das sollte ich vermeiden, wo ich nur kann. Man könnte auch überlegen, das apk add in die Zeile zu setzen. Das spart beim Bauen später Zeit und beim Herunterladen ebenso.

Docker hat unterdessen gebaut und ein Image erzeugt, das man mal starten könnte.

docker run -p 80:8080 <id>

Der Container fährt hoch, stürzt aber sofort ab. Ab jetzt ist MkDocs aber lauffähig, es fehlt: der Inhalt.

Die Doku soll ja direkt im Repo liegen – dafür schaffen wir im Repo den Ordner mkdocs. Zunächst hätte MkDocs gern eine YAML-Datei mit Metadaten. Legen Sie die mkdocs/mkdocs.yml an:

site_name: Test-Doku
dev_addr: "0.0.0.0:8080"
site_author: "Ihr Name"

markdown_extensions:
  - attr_list
  - admonition
nav:
  - Willkommen: index.md

Außerdem brauchen Sie einen weiteren Unterordner docs (eine Vorgabe von MkDocs) und darin eine index.md mit etwas Markdown-Text:

# Test

Das ist Markdown

* Test
* Test

Die Struktur des Repos sieht jetzt so aus:

Dockerfile
mkdocs
  mkdocs.yml
  docs
    index.md

Der ganze Ordner mkdocs soll beim Bau in den Container kopiert werden. Achtung: Das ist kein Volume! Der Ordner wird einmalig beim Bau kopiert, nicht zur Laufzeit.

FROM python:3-alpine
RUN apk add build-base
COPY ./mkdocs/ /mkdocs/
WORKDIR /mkdocs/
RUN pip install --upgrade pip && pip install mkdocs
EXPOSE 8080
CMD ["mkdocs", "serve"]

3. Die Automation

Zwei Registries bieten sich an: Die öffentliche Registry im Docker-Hub und die Paket-Registry von GitHub. Erstere ist schnell in Betrieb genommen. Klicken Sie im Repo auf "Packages" und wählen Sie dort "Docker":

GitHub bringt seit fast einem Jahr eine mächtige CI/DCD-Lösung mit. Sie reagiert z.B. auf Pushes ins Repo. Um Sie einzurichten, erstellen Sie die Datei .github/workflows/docker.yml. Passen Sie den Namen Ihres Repos an:

name: Build docs

on:
  push:
    branches: [master]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: docker/build-push-action@v1
        with:
          dockerfile: Dockerfile
          path: .
          username: jamct
          password: ${{ secrets.GITHUB_TOKEN }}
          repository: jamct/workshop/docs
          registry: ghcr.io
          tags: latest

Ein paar Anmerkungen zum (groben) Verständnis: Die GitHub-Registry orientiert sich an den Repos. Innerhalb eines Repos kann es mehrere Images geben. Daher ist der Name des Containers auch länger: jamct/workshop/docs:latest.

Innerhalb des Repos gibt es ein Token (${{ secrets.GITHUB_TOKEN }}), mit dem sich der Actions-Worker an der Registry anmeldet.

Wenn Sie die Datei speichern, beginnt sofort die Action. Das dürfen Sie live verfolgen über den Reiter "Actions".

Danach sehen Sie das fertige Paket unter "1 package" im Reiter "Code".

Kopieren Sie den ganzen Pull-Befehl mit dem Namen heraus und fügen ihn im Server ein, zum Beispiel:

docker pull ghcr.io/jamct/workshow/docs:latest

Und anschließend

docker run -p 80:8080 ghcr.io/jamct/workshop/docs:latest

3.1.1 Private Registries

Ein Hinweis, wenn Sie eine private Registry nutzen wollen (zum Beispiel die von GitHub): Zum Anmelden sollten Sie nicht Ihr Kennwort, sondern ein Token nehmen. Das generieren Sie unter der Adresse

https://github.com/settings/tokens

Angehakt sein müssen die Punkte repo und read:packages:

Anmelden an einer Registry können Sie sich mit:

docker login ghcr.io
Username: <Username>
Password: <das Token>

3.2 Image im Docker-Hub

(diesen Abschnitt empfehlen wir für das Selbststudium, das Prinzip ist sehr ähnlich):

  • Melden Sie sich im Docker-Hub an
  • Erstellen Sie mit "Create Repository" ein Repo und geben ihm einen Namen
  • Erzeuen Sie ein Token unter https://hub.docker.com/settings/security
  • Wechseln Sie zu GitHub und im Repo auf "Settings", dort auf "Secrets". Legen Sie das Secret DOCKER_TOKEN an und kopieren Sie das Token aus dem Docker-Hub hier hinein.

  • Öffnen Sie die YAML-Datei .github/workflows/docker.yml und ergänzen Sie einen Block für den Docker-Hub:

name: Build docs

on:
  push:
    branches: [master]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: docker/build-push-action@v1
        with:
          dockerfile: Dockerfile
          path: .
          username: jamct
          password: ${{ secrets.GITHUB_TOKEN }}
          repository: jamct/workshop/presentation
          registry: ghcr.io
          tags: latest
      # neue Action für den Hub:
      - uses: docker/build-push-action@v1
        with:
          dockerfile: Dockerfile
          path: .
          username: jamct
          password: ${{ secrets.DOCKER_TOKEN }}
          repository: jamct/mkdocs
          tags: latest

Die Action läuft sofort an und pusht in den Hub.

4. Entwickeln mit Automationen im Alltag

Auf der eigenen Maschine zu entwickeln, macht Spaß. Einer der Vorteile von Docker: Ich kann mir eine Entwicklungsumgebung zum Mitnehmen bauen. Dafür legt man sich eine docker-compose-dev.yml ins Repo:

version: "3.8"
services:
  docs:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - 8080:8080
    volumes:
      - ./mkdocs/:/mkdocs

Diese Datei baut das Image bei der Ausführung und hängt den "Code", also die Doku als Volume ein (ein Volume überschreibt den Inhalt, der dort schon liegt). Als Entwickler kann man dieses Setup lokal hochfahren, die Doku schreiben und das Ergebnis direkt betrachten. Ist man zufrieden, pusht man den Code zu GitHub und der Container wird mit Änderungen gebaut (auch diese Doku zum Workshop wurde so entwickelt).

Zum Hochfahren:

docker compose -f docker-compose-dev.yml

Sicherheitstipp für Entwicklermaschinen

Wer im Unternehmensnetz Docker auf der eigenen Maschine laufen lässt und solche Entwicklungsumgebungen hochfährt, veröffentlicht sie auch auf der Netzwerkkarte des PCs. Sie ist also ggf. im Unternehmensnetz sichtbar (wenn keine Firewall auf der Maschine dazwischengeht). Das kann nützlich sein, um Kollegen in einem Meeting die Ergebnisse live vorzuführen.

Wenn man zum Experimentieren mit produktiven Daten arbeitet, sollte man den Zugriff aber auf das Loopback-Interface einschränken. Das geht mit:

```
 - 127.0.0.1:80:8080
```