Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SIGTERM does not reach node script when docker runs it with `/bin/sh -c`

When my Dockerfile ends with

CMD node .

docker runs that container with the command /bin/sh -c "node ." instead of simply node . (I know, I could do that with CMD ["node", "."]).

I thought that this behavior is actually nice, since it means that inside the container PID1 is /bin/sh and not my humble node script.

If I understand correctly PID1 is responsible for reaping orphaned zombie processes, and I don't really wan't to be responsible for that... So if /bin/sh could do that, that would be nice. (I actually thought that this is the reason why docker does rewrite my CMD).

The problem is that when I send a SIGTERM to the container (started with /bin/sh -c "node ."), either via docker-composer stop or docker-composer kill -s SIGTERM, the signal doesn't reach my node process and therefore it get's forcefully killed everytime with a SIGKILL after the 10 seconds grace period. Not nice.

Is there a way to have someone manage my zombies and have my node instance receive the signals sent by docker?

like image 493
Roman Avatar asked Dec 23 '15 18:12

Roman


People also ask

Does docker send Sigterm?

The docker stop command attempts to stop a running container first by sending a SIGTERM signal to the root process (PID 1) in the container. If the process hasn't exited within the timeout period a SIGKILL signal will be sent.

What is docker Sigterm?

The docker stop commands issue the SIGTERM signal, whereas the docker kill commands sends the SIGKILL signal. The execution of the SIGTERM and SIGKILL. is different. Unlike SIGKILL, the SIGTERM gracefully terminates a process rather than killing it immediately.


3 Answers

I think you have to understand the roles of ENTRYPOINT and CMD, and use the ENTRYPOINT(exec form) way in your Dockerfile.

ENTRYPOINT, which specifies the starting executable of the container, is the core part of a Docker container. Every container MUST have an entrypoint to decide where to start. By default the value is /bin/bash -c. In addition, everything set by CMD would be appended to ENTRYPOINT as arguments.

Therefore, if you failed to specify ENTRYPOINT in your Dockerfile, the actual entrypoint would be /bin/bash -c {your_command_in_CMD}, which unfortunately DOES NOT pass signals.

ENTRYPOINT have two forms: exec form and shell form

  • exec form: ENTRYPOINT ["executable", "param1", "param2"]
  • shell form: command param1 param2

As the Docker reference pointed out: exec form is recommended, and shell form has the disadvantage that command is executed by /bin/bash -c, which might not work well with signals:

The shell form prevents any CMD or run command line arguments from being used, but has the disadvantage that your ENTRYPOINT will be started as a subcommand of /bin/sh -c, which does not pass signals. This means that the executable will not be the container’s PID 1 - and will not receive Unix signals - so your executable will not receive a SIGTERM from docker stop <container>.

like image 143
Haizi Avatar answered Sep 20 '22 20:09

Haizi


There are tools designed to solve this problem:

  • https://github.com/yelp/dumb-init
  • https://github.com/krallin/tini

I think if you only have a single process, all you need to do is explicitly handle the signal with a signal handler, which bash doesn't do for you.

Using the ["node", "."] syntax, you could use https://nodejs.org/api/process.html#process_signal_events and just have it exit on SIGTERM. I believe that would be enough.

Or using a bash script you can use trap "exit 0" TERM

You could also use a process supervisor like http://skarnet.org/software/s6/

like image 22
dnephin Avatar answered Sep 17 '22 20:09

dnephin


My solution for the PID1 problem:

The Dockerfile ends with:

ENTRYPOINT ["/bin/bash", "-c"]

Or leave ENTRYPOINT completely out of your Dockerfile. The default is /bin/sh -c.

Than I run with this shell script, which has the filename of node and have chmod +x:

#!/bin/bash

docker run --rm -it -p 8083:80 -v $HOME/node/work/:/root/node/:rw node_node "echo pid1 > /dev/null && node $@"

The trick is "echo pid1 > /dev/null && node $@" which is the command. $@ is a shell script to accept user input from command line.

echo will trap PID1 and sending output to /dev/null.

For example ./node -v will return the version of Node.js inside the running container.

Running a webserver with ./node /root/node/hello-world.js command, CTRL+C will work again.

Here is my Dockerised Node.js development environment.

EDIT:

Totally crazy idea, but add this node named bash script to the $PATH. So on the host, you can type node -v instead of ./node -v. And you run node in a Docker container which is totally looks like it was installed on the host. :)

like image 30
Lanti Avatar answered Sep 20 '22 20:09

Lanti