Exec not replacing bash shell in Docker entrypoint script
Asked Answered
N

2

6

I'm trying to run a UWSGI server from a Docker container. I've had success, but I'm hitting a wrinkle in that my entrypoint script will still be running as root with PID 1 after container startup, when I'd rather have the initial /bin/bash process be replaced by the UWSGI processes:

bash-4.4# ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 {docker-entrypoi} /bin/bash /usr/local/bin/docker-entrypoint.sh
   19 myuser    0:00 uwsgi --ini /opt/mysite/uwsgi.ini
   21 myuser    0:00 uwsgi --ini /opt/mysite/uwsgi.ini
   22 myuser    0:00 uwsgi --ini /opt/mysite/uwsgi.ini
   24 myuser    0:02 python3 ./manage.py qcluster
   28 myuser    0:00 python3 ./manage.py qcluster
   29 myuser    0:00 python3 ./manage.py qcluster
   30 myuser    0:00 python3 ./manage.py qcluster
   31 myuser    0:00 python3 ./manage.py qcluster
   32 myuser    0:00 python3 ./manage.py qcluster
   33 myuser    0:00 python3 ./manage.py qcluster
   34 myuser    0:00 python3 ./manage.py qcluster

I've tried some variations with exec and su-exec, but I'm still hitting the above. I need to supply my PEM passphrase to UWSGI on startup, so I've been using syntax like so:

echo $PEM_PASSPHRASE | exec uwsgi --ini /opt/mysite/uwsgi.ini

This works fine to get up and running, but I still get the PID 1 /bin/bash process running, with the UWSGI processes as children below. I feel like I'm missing some obvious detail to actually get the bash process to be replaced by the UWSGI processes via exec.

For what it's worth, I'm only using ENTRYPOINT in the Dockerfile, and not CMD:

ENTRYPOINT ["docker-entrypoint.sh"]

Any pointers in the right direction would be greatly appreciated.

Nanceynanchang answered 3/1, 2020 at 22:16 Comment(0)
P
5

Shell commands in a pipeline -- including exec -- run in subshells. Thus, echo ... | exec uwsgi ... creates two subprocesses, one running echo (actually probably a subshell running the shell builtin), and another subshell that promptly replaces itself with uwsgi.

I haven't tested this in docker, but either of the following should work:

exec uwsgi --ini /opt/mysite/uwsgi.ini <<<"$PEM_PASSPHRASE"
exec uwsgi --ini /opt/mysite/uwsgi.ini < <(echo "$PEM_PASSPHRASE")

After writing this, it occurred to me that in bash v4.3 and later, it's actually even easier, because the lastpipe shell option will tell bash to run the last element of a pipe in the current shell rather than a subshell:

shopt -s lastpipe
echo "$PEM_PASSPHRASE" | exec uwsgi --ini /opt/mysite/uwsgi.ini

However, since this is a password we're talking about, there are security considerations about how these might expose the password. The first option (a "here-string") creates a temporary file (on disk!) storing the password, opens it for input, then immediately unlinks it. This means it's not reachable under any normal file path, but it's stored on disk indefinitely (and doesn't get securely deleted). Someone with physical access to the computer (or on some systems, readable directly via /proc). So not too good.

(A here-document would do the same thing.)

The second (redirecting from a "process substitution") and third (lastpipe) might be better... or much worse. In bash, echo is a builtin, so the process substitution (the <( ) part) creates a subshell that runs the echo builtin) into a pipe... and then exits. Which is better. But in a shell without an echo builtin, it'll run a separate echo process, and its argument list (i.e. the password) is effectively public information (e.g. via the ps command). Which is probably worse.

So my recommendation would be to use the second or third, and make sure you're running it under bash.

Padget answered 3/1, 2020 at 23:49 Comment(5)
Hey, this did it! Thank you so much! I replaced my last call with exec uwsgi --ini /opt/mysite/uwsgi.ini < <(echo "$PEM_PASSPHRASE") and that did the trick. Now PID 1 is one of the uwsgi processes, now running under the correct user and not root. Never would I have put that together to figure that out. Thanks so much for the background knowledge; exactly what I was looking for. Cheers!Nanceynanchang
Just so I'm clear: would it be that under my old version, the single echo call was simply holding onto a bash subprocess, so it never gave up the PID to be replaced by the exec call? That was my impression when I was mucking around with different permutations of the Dockerfile/entrypoint script.Nanceynanchang
@AaronBley In the old version, PID 1 is not the echo, it's just a mostly-useless parent shell that forks off two subprocesses (one running echo and the other exec uwsgi. The echo subshell does its bit and exits, but the parent shell waits around until both subprocesses finish. The pipe will force the commands in it to run as subprocesses no matter what they are.Padget
@AaronBley Also, I'm used to macOS, which comes with bash v3.2; you probably have a much newer version, so the lastpipe shell option will be available... and make fixing this trivial. See update.Padget
Thanks a ton for the nitty-gritty details. This helps a lot!Nanceynanchang
S
0

That command you have should be placed as ENTRYPOINT then, since its command will be the first process. (I am assuming you're calling it from inside the docker-entrypoint.sh )

ENTRYPOINT ["echo $PEM_PASSPHRASE | exec uwsgi --ini /opt/mysite/uwsgi.ini"]

Stylopodium answered 3/1, 2020 at 22:23 Comment(4)
That's correct, but I have a bunch of other commands that happen in docker-entrypoint.sh before the exec call, so I would think placing it at the end would suffice? Do I need to rework it along these lines? Dockerfile: ENTRYPOINT ["docker-entrypoint.sh"] CMD "echo $PEM_PASSPHRASE | exec uwsgi --ini /opt/mysite/uwsgi.ini" docker-entrypoint.sh: Other logic, then... exec "$@"Nanceynanchang
The first command is the main process. We don't do miracles. What's the problem of having the entrypoint as pid=1?Stylopodium
I was under the impression that PID 1 bore the responsibility of correctly handling SIGINT/SIGTERM/etc. and making sure those signals went through to child processes. I'd rather have this handled by the UWSGI processes I'm running rather than a one-off bash script meant to run once at container startup. I suppose I can always try attaching Tini to the container. In any case, I tried reworking it without the echo just to see what I'd get, and ps is still showing they entrypoint script as PID 1. I'll take a whack at adding Tini.Nanceynanchang
Ok. But (1) you can handle the signals in the bash script and (2) if you send a signal to terminate your wannabe-main-process, it will exit and the the entrypoint will also exit since that's its last command. And that process will send to child processes its signal as well. So, you get it either way, right?Stylopodium

© 2022 - 2024 — McMap. All rights reserved.