This page looks best with JavaScript enabled

Signalling docker containers

 ·  ☕ 2 min read

When deploying a containerized application to a container management system like AWS Fargate, you tend to run your application from a shell script. Suppose your script looks like this

1
2
3
set -o nounset
set -x
gunicorn --config config/gunicorn/$GUNICORN_CONFIG.py config.wsgi

Here we are executing the gunicorn service with PID 1 when the container is deployed. Suppose we want to terminate the container with a docker stop <container_id>, the command will send a SIGTERM to the container. As the gunicorn process is PID 1, this signal is ignored.

The way to resolve this issue is to use exec before the command to start your application. The last line of the above shell script should be gunicorn --config config/gunicorn/$GUNICORN_CONFIG.py config.wsgi. A simple example is shown below

t.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import signal
import time

got_signal = False

def process_signal(signum, frame):
    global got_signal
    got_signal = True

    signal.signal(signal.SIGINT, process_signal)
    signal.signal(signal.SIGTERM, process_signal)

    while not got_signal:
        time.sleep(1)
        print("looping...")

    print("Ended with signal.")

entry.sh:

1
2
#!/usr/bin/env bash
exec python ./t.py

Dockerfile:

1
2
3
4
5
FROM python:3.7
WORKDIR /usr/src/app
COPY . .

CMD [ "./entry.sh" ]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
With the exec:

$ docker run --rm -t signal
looping...
looping...
looping...
looping...
looping...
looping...
Ended with signal.
$ 
Without the exec:

$ docker run --rm -t signal
looping...
looping...
looping...
looping...
looping...
looping...
$

Both times, you can run docker stop in another window. If the container doesn’t stop within 10 seconds, then it’s killed.

Most apps do not explicitely handle SIGTERM the way t.py does. If you replace t.py with

1
2
3
4
5
6
7
import time

while True:
    time.sleep(1)
    print("looping...")

print("Ended with signal.")

SIGINT will work with keyboard interrupt but docker stop does not because of PID=1 issue.

If we run docker with a --init option to force a non 1 PID, the docker stop works whether we use exec in the script or not.

In order to use this feature with AWS Fargate, make a small change to your AWS::ECS::TaskDefinition in your cloudformation as shown below

1
2
3
4
5
6
ContainerDefinitions:
    - Name: <container_name>
        Image: <container_image>
        Essential: "true"
        linuxParameters:
            initProcessEnabled: "true"
Share on