Ein Blog

Posts mit Tag "docker"

Heute lernte ich die HEALTHCHECK-Instruktion bei Dockerfiles. Damit kann ein Container einen Befehl definieren, mit dem man rausbekommen kann, ob der Container gerade “Healthy” ist. Erinnert ein bisschen an die Probes aus K8s.

HEALTHCHECK --start-period=10s --timeout=10s \
            CMD curl --fail http://localhost:80 || exit 1

Man kann das auch in einer Compose-File definieren.

Letztens hatte ich einen Post zu selten verwendeten Features bei docker-compose. Hier ein guter, aber oberflächlicher Artikel: 6 Docker Compose Best Practices for Dev and Prod

Daraufhin hat sich noch ein weiteres Feature aufgetan: profiles. Beispiel:

version: "3.9"
services:
  frontend:
    image: frontend
    profiles: ["frontend"]

  phpmyadmin:
    image: phpmyadmin
    depends_on:
      - db
    profiles:
      - debug

  backend:
    image: backend

  db:
    image: mysql

Here the services frontend and phpmyadmin are assigned to the profiles frontend and debug respectively and as such are only started when their respective profiles are enabled.

In den Kommentaren auf HN gibt es auch noch weitere Tipps.

Man kennt ja aus Docker-Multistage-Builds das COPY --from=build, um eine Datei aus einer anderen Stage zu kopieren.

Gerade habe ich beim Docker-Image von composer gesehen, dass das nicht nur beschränkt auf Images im selben Multi-stage-Build ist. Man kann dort jedes Image angeben.

Also kann man statt dem hier:

RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
        && php composer-setup.php --install-dir=/usr/local/bin --filename=composer \
        && php -r "unlink('composer-setup.php');" \
        && composer --version

Auch das hier machen und die Binary aus dem offiziellen composer-Image kopieren:

COPY --from=composer /usr/bin/composer /usr/bin/composer

Nachteil dabei ist, dass der Builder dafür das komplette Image runterladen muss. Ist in manchen Situationen vielleicht ganz praktisch.

TIL docker system df:

# docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          5         5         1.318GB   858.6MB (65%)
Containers      5         5         3.416kB   0B (0%)
Local Volumes   5         1         442B      354B (80%)
Build Cache     0         0         0B        0B

Zu Docker hab ich noch was neues gelernt. Man kann jetzt (mit DOCKER_BUILDKIT=1) auch bei einzelnen RUN-Commands Dinge mounten. Das ist ganz praktisch für Lockfiles oder Dinge, die man nur ein Mal braucht. Dazu gibt es neben Bind-Mounts auch Cahce-Mounts, womit man ein Cache-Verzeichnis in den Build-Container mounten kann. Nimmt natürlich etwas Reproduzierbarkeit, aber in gewissen Fällen (npm, rust) kann man damit echt viel Zeit sparen:

Aus

COPY package.json package-lock.json ./
RUN npm ci

COPY ./Cargo.toml ./Cargo.toml
RUN cargo fetch

COPY ./clitool.c /clitool.c
RUN g++ -O3 -o /bin/clitool /clitool.c

Wird:

RUN --mount=type=bind,source=package.json,target=package.json \
	--mount=type=bind,source=package-lock.json,target=package-lock.json \
	--mount=type=cache,target=/root/.npm \ # mit cache des globalen npm-caches
	npm ci


RUN --mount=type=bind,source=Cargo.toml,target=Cargo.toml \
	cargo fetch

RUN --mount=type=bind,source=clitool.c,target=/clitool.c \
	g++ -O3 -o /bin/clitool /clitool.c

Je nach Fall kann man sich damit sogar eine Multi-Stage-Dockerfile ersparen.

Docker kann beim Bauen linten. Vielleicht ist euch mal eine Warning aufgefallen, wenn gebaut wird. Wenn man z. B. FROM x as y schreibt, gibt es eine Warning, die besagt, dass das as in einem anderen Casing als FROM ist.

Das kann man jetzt zu einem Fehler machen. Dazu entweder docker build mit --check aufrufen oder den Anfang der Dockerfile ein bisschen konfigurieren;

# syntax=docker/dockerfile:1
# check=error=true

Diese Checks gibt es aktuell.

Docker kann mehrere Build-Contexte. Damit kann man dann shared-Directorys haben, ohne den Build-Context auf den gemeinsamen Root zu legen.

Heute habe ich gelernt: In docker-compose kann man sich sowas basteln, was ähnlich zu den Init-Containern aus k8s ist. Verwenden tut man dafür die depends_on-Property des Services.

Bisher ging ich davon aus, dass man mit der Property nur festlegen kann, in welcher Reihenfolge die Container gestartet werden. Das bringt einem aber nicht viel, weil die Container üblicherweise unterschiedlich lange brauchen, um zu starten. Das geht mittlerweile besser, nur die Dokumentation erwähnt es (noch) nicht:

Bei den Elementen von depends_on kann man conditions angeben. Dort kann man sagen, in welchem Zustand die Abhängigkeit sein soll, bevor der Container gestartet wird.

services:
  db:
    image: mariadb:10
    ports:
      - "3306:3306"

  init_db:
    image: mariadb:10
    command: /init-db.sh
    volumes:
      - ./init-db.sh:/init-db.sh
    env_file:
      - credentials.env
    depends_on:
      db:
        condition: service_started

  application:
    # ...
    depends_on:
      init_db:
          condition: service_completed_successfully

Durch das service_completed_successfully sagt man, dass application erst startet, wenn init_db mit einem 0-Exit-Code beendet ist. service_started ist offenbar das, was sonst auch immer der Fall war (die Doku nennt es “legacy behaviour”).

Ich hab dann noch ein bisschen Recherche betrieben und rausgefunden: Da geht noch mehr! Definiert man z. B. die Healthchecks, über die ich mal berichtet habe, kann man den Condition service_healthy angeben. Diesen Status erhält ein Container, wenn er hochgefahren und sich durch den Healthcheck als “gesund” erkannt wurde. Definiert man das in der Dockerfile oder in der docker-compose.yml, kann man damit sogar ein ./wait-for-it.sh loswerden. Da hat sich ja echt was getan bei docker-compose.