Docker Health Checks

TLDR;


Docker Container Health Checks

Anwendungen zu deployen ist einfach, und Docker Container zu starten ist noch einfacher. Ein einfaches docker run und schon sind wir live. Das funktioniert auch eine ganze Zeit bis zum ersten Problem - sei es nun eine Lastspitze, ein Bug, ein Amoklaufender Prozess oder einfach ein temporäres Netzwerkproblem. Die Anwendung ist nicht erreichbar, unsere E-Commerce-Plattform ist für die Kunden nichts weiter als eine 500 - Wir arbeiten an einer Lösung und bis wir diese gefunden haben ist die Bestellung meistens schon bei der Konkurrenz gelandet.

Was machen wir also? Wir designen unser System so sicher und gut, dass einfach keine Fehler auftreten? Das klingt verlockend, ist aber schlichtweg nicht möglich und sogar gefährlich, da wir uns dann in falscher Sicherheit wiegen. Wenn dann doch ein Problem auftritt sind wir nicht dafür gewappnet.

Tipp
Besser ist es, die Anwendung gleich von Anfang so zu bauen dass Fehler akzeptiert werden und die Anwendung entsprechend darauf reagiert. Resilient Software ist hier das wichtige Stichwort.

Beispielanwendung

Die Anwendung ist eine Spring Boot Anwendung mit Actuator.

Wichtig
Spring Boot Actuator bietet eine ganze Menge Endpoints um den Zustand der Anwendung zu überwachen. Die Anwendung gibt beispielsweise über den Endpunkt "/blog/health" den Status zurück, ob sie healthy ist. Healthy ist natürlich abhängig davon ob die verwendeten Upstream Services und die Infrastruktur-Komponenten verfügbar sind. Beispielsweise sollte die Anwendung nur healthy sein, wenn eine verwendete Datenbank vorhanden ist.

Als nächstes starten wir die Anwendung.

docker run -d --rm -p 8080:8080 effectivetrainings/docker-health

Nach kurzer Zeit können wir über einen cURL die Healthyness der Anwendung abfragen.

{
"status":"UP",
"static":{"status":"UP"},
"diskSpace": {"status":"UP","total":67371577344,"free":61355114496,"threshold":10485760}
}

Hier sehen wir die HealthChecks der Anwendung. Selbst geschrieben ist der Static-Health-Check, mit dem die Anwendung einfach statisch in einen Healthy- oder Unhealthy-Zustand gesetzt werden kann.

curl "http://localhost:8080/environment/health?status=false"

curl "http://localhost:8080/health"
{
    "status": "DOWN",
    "static":{"status":"DOWN"},
    "diskSpace":{"status":"UP","total":67371577344,"free":61354893312,threshold":10485760}
}

Das Problem ist, der Container ist per Definition bisher immer noch gesund, obwohl die Anwendung vielleicht nicht mal antwortet. Die Docker Engine weiß nichts vom Health-Status der Anwendung. Der Container selbst hat bis jetzt noch keinen Health-State.

Damit die Engine auf eine Änderung im Health-Status reagieren kann muss der Container selbst auch wissen, ob er Healthy ist oder nicht. Und genau hier kommen die neuen Health-Checks ins Spiel.


Docker Health Check

Health Checks sind essentiell für die Anwendung - sowohl für das Monitoring, den Betrieb als auch für beispielsweise Service Discovery in verteilten Umgebungen. Dabei kann sich ein Health-Check ganz unterschiedlich gestalten, beispielsweise kann es für eine Oracle ein einfaches SQL Statement sein wie select count from dual, ein HTTP-Status Code eines Actuator-Endpoints oder ein bash-script das nur prüft, ob ein bestimmter Prozess noch lebt.

Für jeden Container kann separat definiert werden, wie ein Health-Check ausgeführt werden kann. Für die Überwachung der Spring-Boot Anwendung beispielsweise bietet sich die Abfrage des Health-Endpoint mittels cURL an.

Für den Health-Check sind folgende Parameter für docker run hinzugekommen. Analog könnten die Health-Checks im Dockerfile definiert werden.

--health-cmd string   (1)
--health-interval duration  (default 0s) (2)
--health-retries int (3)
--health-timeout duration (default 0s) (4)
  1. Kommando für den Health-Check (Bash, Python..)

  2. Zeit zwischen Health-Checks, standardmäßig alle 30 sekunden.

  3. Retries bevor ein Container als unhealthy deklariert wird

  4. Timeout für den Health-Check

Ein einfacher Health-Check für die Spring-Boot Anwendung wäre beispielsweise folgender cURL-Aufruf.

Tipp
Ein Health-Check liefert einen boolschen Wert - 0 oder 1. 0 ⇒ healthy, 1 ⇒ unhealthy.
--health-cmd curl -f http://localhost:8080/health | exit 1 (1)
  1. Mit curl -f zwingt curl, den Fehlercode 1 im Falle eines HTTP-Errors zurückzuliefern, andernfalls den Code 0.

Starten wir den Container mit aktiviertem Health-Check.

docker run -p 8080:8080 -d --name health-check --rm --health-cmd "curl -f http://localhost:8080/health || exit 1" effectivetrainings/docker-health

Die Instruktion im Dockerfile wäre analog.

HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1;
Achtung
Achtung - der Health-Check wird im Container ausgeführt, nur der interne Container ist also wichtig - nicht der gemappte.

Mit docker inspect healthcheck sehen wir jetzt einen Health-Status für den Container.

"Health": {
    "Status": "starting",
    "FailingStreak": 0,
    "Log": []
}

Parallel ist es recht spannend, sich den Docker Event Stream ausgeben zu lassen.

docker events

2017-01-03T22:10:18.137182990+01:00 container exec_start: /bin/sh -c curl -f http://localhost:8080/health 1a2bfc354a3eb75c41e8822620c85b7920ba0ebb9103aa481b090da6ce137037 (image=effectivetrainings/docker-health, name=health-check)

2017-01-03T22:10:18.527115972+01:00 container health_status: healthy 1a2bfc354a3eb75c41e8822620c85b7920ba0ebb9103aa481b090da6ce137037 (image=effectivetrainings/docker-health, name=health-check)

Was aber passiert jetzt, wenn wir den Container auf unhealthy setzen? Je nach eingestelltem Interval dauert es jetzt kurz, bis die Engine das Problem entdeckt.

curl "localhost:8080/environment/health?status=false"

docker ps

CONTAINER ID        IMAGE                              COMMAND                CREATED             STATUS                     PORTS                    NAMES
1a2bfc354a3e        effectivetrainings/docker-health   "java -jar /app.jar"   3 minutes ago       Up 3 minutes (unhealthy)   0.0.0.0:8080->8080/tcp   health-check

Ein Scheduler wie Docker Swarm könnte jetzt beispielsweise den Container einfach neustarten, in der Hoffnung, dass das hilft.


Fazit

Mit Container-Health Checks können relativ einfach Checks implementiert werden, die einen Scheduler unterstützen können die richtigen Entscheidungen zu treffen.

  • Welcher Container wird jetzt neugestartet?

  • Wohin soll deployt werden

Docker Swarm beispielsweise macht sich den Health-Check zu Nutze und routet nur Requests zu Containern, die healthy sind. Außerdem versucht Swarm, Container neu zu deployen, wenn Sie in den Status unhealthy wechseln.


Schöner NewRelic Post zum Thema


Docker Training

Wollen Sie mehr erfahren? Ich biete Consulting / Training für Docker. Schauen Sie doch mal vorbei!