From 56e14f506bb875efbf5a06c4efa502653523c6f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=BCffner?= <11882946+mkuf@users.noreply.github.com> Date: Mon, 11 Nov 2024 23:03:40 +0100 Subject: [PATCH] Implement Health checks (#178) * add healthchecks to moonraker, klipper and ustreamer images --- CHANGELOG.md | 1 + docker/klipper/Dockerfile | 3 +++ docker/klipper/README.md | 9 ++++++++- docker/klipper/health.py | 22 ++++++++++++++++++++++ docker/moonraker/Dockerfile | 4 ++++ docker/moonraker/README.md | 12 +++++++++++- docker/moonraker/health.sh | 17 +++++++++++++++++ docker/ustreamer/Dockerfile | 7 ++++++- docker/ustreamer/README.md | 11 ++++++++++- docker/ustreamer/health.sh | 13 +++++++++++++ 10 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 docker/klipper/health.py create mode 100755 docker/moonraker/health.sh create mode 100755 docker/ustreamer/health.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 502a229..b3451c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Added * klipper & moonraker: generate version file during build to correctly display versions +* klipper, moonraker & ustreamer: add healthchecks to container images ### Fixed ### Changed ### Removed diff --git a/docker/klipper/Dockerfile b/docker/klipper/Dockerfile index 12f0967..badb8b3 100644 --- a/docker/klipper/Dockerfile +++ b/docker/klipper/Dockerfile @@ -39,6 +39,9 @@ RUN groupadd klipper --gid 1000 \ RUN mkdir -p printer_data/run printer_data/gcodes printer_data/logs printer_data/config \ && chown -R klipper:klipper /opt/* +COPY --chown=klipper:klipper health.py ./ +HEALTHCHECK --interval=5s CMD ["python3", "/opt/health.py"] + COPY --chown=klipper:klipper --from=build /opt/klipper ./klipper COPY --chown=klipper:klipper --from=build /opt/venv ./venv diff --git a/docker/klipper/README.md b/docker/klipper/README.md index 64727b2..ef39d72 100644 --- a/docker/klipper/README.md +++ b/docker/klipper/README.md @@ -118,4 +118,11 @@ none |`build-hostmcu`|Based on `mcu`: Build the klipper_mcu binary|No| |`run`|Default runtime Image for klippy|Yes| |`tools`|Build Tools for MCU code compilation|Yes| -|`hostmcu`|Runtime Image for the klipper_mcu binary|Yes| \ No newline at end of file +|`hostmcu`|Runtime Image for the klipper_mcu binary|Yes| + +## Healthcheck +`/opt/health.py` gets executed every 5s inside the container. +The script does the following: +* queries klippers `info` endpoint via its unix socket +* Checks if state is `ready` +* If one of the above requirements is not `ready`, the script exits with a failure state to indicate the container is unhealthy \ No newline at end of file diff --git a/docker/klipper/health.py b/docker/klipper/health.py new file mode 100644 index 0000000..a58892c --- /dev/null +++ b/docker/klipper/health.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +import socket, json, sys + +socket_address="/opt/printer_data/run/klipper.sock" +message={"id": 666, "method": "info"} + +# Set up socket connection +sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) +sock.connect(socket_address) + +# Send message and receive response +sock.sendall(json.dumps(message).encode() + b"\x03") +response = sock.recv(4096).decode('utf-8').strip('\x03') +sock.close() + +# Check the result +if json.loads(response)["result"]["state"] == "ready": + # State is ready - healthy + sys.exit(0) +else: + # State is not ready - unhealthy + sys.exit(1) diff --git a/docker/moonraker/Dockerfile b/docker/moonraker/Dockerfile index cb1ca68..6287798 100644 --- a/docker/moonraker/Dockerfile +++ b/docker/moonraker/Dockerfile @@ -39,6 +39,7 @@ RUN apt update \ systemd \ sudo \ git \ + jq \ && apt clean WORKDIR /opt @@ -48,6 +49,9 @@ RUN groupadd moonraker --gid 1000 \ RUN mkdir -p printer_data/run printer_data/gcodes printer_data/logs printer_data/database printer_data/config \ && chown -R moonraker:moonraker /opt/* +COPY --chown=moonraker:moonraker health.sh ./ +HEALTHCHECK --interval=5s CMD ["bash", "/opt/health.sh"] + COPY --chown=moonraker:moonraker --from=build /opt/moonraker ./moonraker COPY --chown=moonraker:moonraker --from=build /opt/venv ./venv diff --git a/docker/moonraker/README.md b/docker/moonraker/README.md index 4987e6a..031236a 100644 --- a/docker/moonraker/README.md +++ b/docker/moonraker/README.md @@ -84,4 +84,14 @@ services: |Target|Description|Pushed| |---|---|---| |`build`|Pull Upstream Codebase and build python venv|No| -|`run`|Default runtime Image|Yes| \ No newline at end of file +|`run`|Default runtime Image|Yes| + +## Healthcheck +`/opt/health.sh` gets executed every 5s inside the container. +The script does the following: +* queries the `/server/info` endpoint of moonraker +* Performs the following checks + * Number of failed moonraker_components = 0 + * klippy_connected is `true` + * klippy_state is `ready` +* If one of the above requirements is not met, the script exits with a failure state to indicate the container is unhealthy \ No newline at end of file diff --git a/docker/moonraker/health.sh b/docker/moonraker/health.sh new file mode 100755 index 0000000..bdc9d08 --- /dev/null +++ b/docker/moonraker/health.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +serverinfo=$(curl -s localhost:7125/server/info) + +klippy_connected=$(echo -n ${serverinfo} | jq -r .result.klippy_connected) +klippy_state=$(echo -n ${serverinfo} | jq -r .result.klippy_state) +failed_components=$(echo -n ${serverinfo} | jq -r .result.failed_components[] | wc -l) + +if [ "$klippy_connected" == "true" ] \ +&& [ "$klippy_state" == "ready" ] \ +&& [ $failed_components -eq 0 ]; then + ## moonraker is up and connected to klippy + exit 0 +else + ## moonraker started w/ failed components and/or is not connected to klippy + exit 1 +fi \ No newline at end of file diff --git a/docker/ustreamer/Dockerfile b/docker/ustreamer/Dockerfile index 38c3fa6..c8e1538 100644 --- a/docker/ustreamer/Dockerfile +++ b/docker/ustreamer/Dockerfile @@ -39,6 +39,8 @@ RUN apt update \ libbsd0 \ libgpiod2 \ v4l-utils \ + curl \ + jq \ && apt clean WORKDIR /opt @@ -46,11 +48,14 @@ RUN groupadd ustreamer --gid 1000 \ && useradd ustreamer --uid 1000 --gid ustreamer \ && usermod ustreamer --append --groups video +COPY --chown=ustreamer:ustreamer health.sh ./ +HEALTHCHECK --interval=5s CMD ["bash", "/opt/health.sh"] + COPY --chown=ustreamer:ustreamer --from=build /opt/ustreamer/src/ustreamer.bin ./ustreamer ## Start ustreamer USER ustreamer EXPOSE 8080 -ENTRYPOINT [ "/opt/ustreamer"] +ENTRYPOINT ["/opt/ustreamer"] CMD ["--host=0.0.0.0", "--port=8080"] diff --git a/docker/ustreamer/README.md b/docker/ustreamer/README.md index 65bf57b..666e0e7 100644 --- a/docker/ustreamer/README.md +++ b/docker/ustreamer/README.md @@ -51,4 +51,13 @@ none |Target|Description|Pushed| |---|---|---| |`build`|Pull Upstream Codebase and build application|No| -|`run`|Default runtime Image|Yes| \ No newline at end of file +|`run`|Default runtime Image|Yes| + +## Healthcheck +`/opt/health.sh` gets executed every 5s inside the container. +The script does the following: +* gets the JSON structure with the state of the server +* Checks the following values + * `.ok` is set to `true`, which indicates ustreamer is working + * `.result.source.online` is set to `true`, which indicates the source (webcam) is returning an image rather than `NO SIGNAL` +* If one of the above requirements is not met, the script exits with a failure state to indicate the container is unhealthy \ No newline at end of file diff --git a/docker/ustreamer/health.sh b/docker/ustreamer/health.sh new file mode 100755 index 0000000..c5ae649 --- /dev/null +++ b/docker/ustreamer/health.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +state=$(curl -s localhost:8080/state) +ok=$(echo $state | jq -r .ok) +online=$(echo $state | jq -r .result.source.online) + +if [ "$ok" == "true" ] && [ "$online" == "true" ]; then + ## ustreamer is ok and source is online + exit 0 +else + ## ustreamer is not ok or source is not online + exit 1 +fi \ No newline at end of file