Skip to content

Instantly share code, notes, and snippets.

@MatrixManAtYrService
Last active April 29, 2023 00:27
Show Gist options
  • Save MatrixManAtYrService/4ba2bd77cd5c56e0f5f3ec152673f668 to your computer and use it in GitHub Desktop.
Save MatrixManAtYrService/4ba2bd77cd5c56e0f5f3ec152673f668 to your computer and use it in GitHub Desktop.
tini vs dumb init

I test images. Sometimes they use tini, sometimes they use dumb-init. I have noticed a case where the tini doesn't forward SIGTERM all the way to the app, but dumb-init does.

I want to understand what's different about how these tools are handling signals.

The problematic case arises when I insert a shim which runs the actual app as a subprocess. The goal is to add a custom signal handler to the shim so that I don't have to modify the app. Ideally this will work without me having to think about whether the image uses tini or dumb-init.

In that handler I intend to export telemetry data, but for now it just does echo after so I can see if it ran.

The problem is that when I do docker stop, the underlying app doesn't get the signal. Instead, docker just waits 10 seconds and kills the app--which means that my handler doesn't run.

FROM ubuntu
RUN apt update
RUN apt install -y tini dumb-init python3-pip
RUN pip install apache-airflow
# place the entrypoint script
COPY entrypoint.sh /entrypoint
RUN chmod +x /entrypoint
# a script that calls airflow (and does test-relevant things too)
COPY shim.sh /root/shim
# rename airflow and put the shim in its place
RUN DIR=$(dirname $(which airflow)) \
&& mv $DIR/airflow $DIR/actual-airflow \
&& mv /root/shim $DIR/airflow \
&& chmod +x $DIR/airflow
# works
ENV INITIALIZER=dumb-init
# breaks
#ENV INITIALIZER=tini
ENTRYPOINT [ "/entrypoint" ]
#!/usr/bin/env bash
set -ex
# We can't call the initializer in the entrypoint because
# of something to do with wanting to run it as a different user.
# I have simplified that part away in this case because the problem persists
# even without different users involved.
if [[ -z "$ALREADYINIT" ]]; then
ALREADYINIT=1 exec $INITIALIZER -- "$0" "$@"
fi
exec "$@"
❯ docker run experiment bash -c 'airflow db init && airflow scheduler'
+ [[ -z '' ]]
+ ALREADYINIT=1
+ exec dumb-init -- /entrypoint bash -c 'airflow db init && airflow scheduler'
+ [[ -z 1 ]]
+ exec bash -c 'airflow db init && airflow scheduler'
before (PID 8)
DB: sqlite:////root/airflow/airflow.db
...
Initialization done
after <------!!!!! that's my handler for `airflow db init`
before (PID 7)
____________ _____________
____ |__( )_________ __/__ /________ __
____ /| |_ /__ ___/_ /_ __ /_ __ \_ | /| / /
___ ___ | / _ / _ __/ _ / / /_/ /_ |/ |/ /
_/_/ |_/_/ /_/ /_/ /_/ \____/____/|__/
...
[2022-07-08 20:41:47 +0000] [15] [INFO] Booting worker with pid: 15
# `docker stop` executed here
[2022-07-08 20:41:53 +0000] [11] [INFO] Handling signal: int
...
[2022-07-08 20:41:55,139] {scheduler_job.py:768} INFO - Exited execute loop
after <------!!!!! that's my handler for `airflow scheduler`
❯ docker run experiment bash -c 'airflow db init && airflow scheduler'
+ [[ -z '' ]]
+ ALREADYINIT=1
+ exec tini -- /entrypoint bash -c 'airflow db init && airflow scheduler'
+ [[ -z 1 ]]
+ exec bash -c 'airflow db init && airflow scheduler'
before (PID 8)
DB: sqlite:////root/airflow/airflow.db
...
Initialization done
after <------!!!!! that's my handler for `airflow db init`
before (PID 7)
____________ _____________
____ |__( )_________ __/__ /________ __
____ /| |_ /__ ___/_ /_ __ /_ __ \_ | /| / /
___ ___ | / _ / _ __/ _ / / /_/ /_ |/ |/ /
_/_/ |_/_/ /_/ /_/ /_/ \____/____/|__/...
[2022-07-08 20:38:29 +0000] [15] [INFO] Booting worker with pid: 15
# `docker stop` killed the container, but no more logs were shown, and the handler was not called
#!/bin/bash
echo "before (PID $$)"
# run this on sigterm
function finished()
{
echo after
exit $1
}
trap finished SIGTERM SIGINT
actual-airflow $@
# also run it when not interupted
finished $?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment