Support for docker images that don't have a shell
mapx opened this issue · comments
It's possible that a docker image is based on SCRATCH, and contains nothing but the executable. In that case, the command
part of this image should be somthing like:
command: ["/the_executable", "--foo", "bar"]
There won't be &&
available as well without a shell. Could such a thin image have the favor of wait
?
Hi @mapx
honestly, I don't know how to make it work without the &&
shell support.
My only advice would be to try a minimal docker image like the Busybox one instead of scratch.
Do you have any proposal about it?
When a command chain begins with wait
and no strings like &&
, the rest part of this command becomes env::args()
.
If this arguments list is not empty, we know wait
might be running with such paramenters. At the end of wait
process, it could run the paramenters as a command.
The first item in env::args()
would be the "main" command, and the rest be its parameters.
If env::args()
is empty, wait
exits like what it is now.
Could it work?
Hi @mapx
I am not so knowledgeable on Rust and I am primarily just here to learn.
If I understand this correctly, what you propose is, to let wait
handle the scheduling instead of the shell. Eliminating the use of the shell? but keeping backwards compatibility due to the handling of &&
in Rust.
Meaning wait
on shell-less base images, like scratch could be supported, but also supported on base images with shells, with &&
usage however being optional (at the users convenience).
I report here a proposal of @SamMousa:
It would be nice to support syntax that uses exec
instead of a subshell.
The syntax for usage would be simple:
entrypoint: [
"wait",
"--",
"myApp"
]
That way no shell is needed and signal handling becomes easier for the app since there's no longer a shell in between.
I think that what you all are proposing could effectively work; anyway, I don't know how it should be implemented.
When docker starts the wait
executable, it assigns a PID to it (first doubt, is it always PID 1? is it the same if there's no shell?); based on your proposal, the wait
executable itself should launch the application executable; so, my question is, how?
If it launches a child process, then what happens when the wait
process ends? Is the child process killed too? If not, what is the PID of the second process?
Are there issues with docker signal handling if the surviving process does not have PID 1?
There as a system call called exec
that loads a new process that replaces the current process.
I think it's this: https://docs.rs/exec/0.3.1/exec/
FYI, I do not know Rust.
Hi @mapx
I am not so knowledgeable on Rust and I am primarily just here to learn.
If I understand this correctly, what you propose is, to let
wait
handle the scheduling instead of the shell. Eliminating the use of the shell? but keeping backwards compatibility due to the handling of&&
in Rust.Meaning
wait
on shell-less base images, like scratch could be supported, but also supported on base images with shells, with&&
usage however being optional (at the users convenience).
A parent process which calls wait
desides how many parameters wait
gets. If the parent process is a shell, it understands &&
, cuts off the right part starting from &&
, and passes the left part to wait
. If the parent process does not know about &&
, it passes everything to wait
.
In the latter case, it's the user's responsibility not to put &&
in the middle, unless she/he wants that.
After some investigation, I think it could effectively work as you propose. Anyway, I don't have enough knowledge to implement it in a way that I can guarantee it works. In fact, there are too low-level details that I miss and I would not be able to provide support where needed.
So, I keep the task open with a "PR welcome" tag in case someone is willing to attempt a fix; but, honestly, considering that the BusyBox docker image size is only 750Kb, I don't think it is worth investing much time in this feature.
I think this video might help you guys: https://www.youtube.com/watch?v=C1GE07UEFDo
I work around this issue by copying the busybox sh
into the container at a less-common location (to reduce potental exploits). this works in google's distroless images. here's an example multi-stage Dockerfile;
FROM busybox:1.31.1-uclibc as builder
RUN wget -O /wait https://github.com/ufoscout/docker-compose-wait/releases/download/2.7.3/wait &&\
chmod +x /wait
...
FROM gcr.io/distroless/java:11 as base
COPY --from=builder /bin/sh /sh
USER nonroot
COPY --from=builder --chown=nonroot:nonroot /wait /wait
...
ENTRYPOINT ["/sh", "-c", "/wait && java -jar application.jar"]
I made it with alpine os image, and alpine itself had /bin/bash.
Also the wait shell script I downloaded and added it to the current source file folder.
I made it with alpine os image, and alpine itself had /bin/bash.
Also the wait shell script I downloaded and added it to the current source file folder.
@jimmyyem Could you share your code solution?
FYI guys, I have a PR up here that fixes this issue. I ran into the issue recently using distroless
and did some research and got a fix in place. One thing that I wanted to prevent was having multiple processes running in the container and wait
basically having to act like an init system. I've prevented that by making a syscall out to exec
so the wait
process gets completely replaced by the configured command, whether that's tini
or another static binary. For configuration I added a single new env var, WAIT_COMMAND
. The arguments to the command to invoke get properly parsed like they would if they were being processed by a shell using the shell_words
crate so any command to run should be copy/pasteable from CMD
to WAIT_COMMAND
. Hope you have a chance to get 👀 on it @ufoscout and let me know your thoughts
SamMuosa proposal (/wait -- command arg1 arg2...
), by using exec
and replacing the current process, seems the most straightforward. https://github.com/Eficode/wait-for does the same.