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
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:
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:
#!/usr/bin/env bash
exec python ./t.py
Dockerfile:
FROM python:3.7
WORKDIR /usr/src/app
COPY . .
CMD [ "./entry.sh" ]
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
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
ContainerDefinitions:
- Name: <container_name>
Image: <container_image>
Essential: "true"
linuxParameters:
initProcessEnabled: "true"