Zombie Processes
Zombie processes, a short definition
The first step is an orphaned process, a process that has lost his parent.
Suppose the parent process terminates, either intentionally (because the program logic has determined that it should exit), or caused by a user action (e.g. the user killed the process). What happens then to its children? They no longer have a parent process, so they become “orphaned” (this is the actual technical term).
— source
The init process is responsible of adopting orphaned processes and to reap them, i.e. to clean it up.
And this is where the init process kicks in. The init process—
PID 1
—has a special task. Its task is to “adopt” orphaned child processes (again, this is the actual technical term). This means that the init process becomes the parent of such processes, even though those processes were never created directly by the init process
If like in standard docker container launching a command, there is no proper init
process, nobody will care about orphaned processes and they will stay here as zombies also called defunct
. The problem is not related to the resources used by these zombies (none) but to the number of processes that will increase until system exhaustion.
As long as a zombie is not removed from the system via a wait, it will consume a slot in the kernel process table, and if this table fills, it will not be possible to create further processes
Producing a zombie process
To generate a zombie process I have used the Python code displayed in this issue.
#!/usr/bin/env python3
import os
import subprocess
pid = os.fork()
if pid == 0: # child
pid2 = os.fork()
if pid2 != 0: # parent
print('The zombie pid will be: {}'.format(pid2))
else: # parent
os.waitpid(pid, 0)
subprocess.check_call(('ps', 'xawuf'))
Then a simple Dockerfile
to package and run it.
FROM python
COPY test.py /root/
RUN chmod +x /root/test.py
CMD ["/root/test.py"]
Ready to test!
# Build
$ docker build --rm -t zombie-init ./zombie-init
# Run
$ docker run --rm zombie-init
# The zombie pid will be: 7
# USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
# root 1 0.0 0.5 13164 10744 ? Ss 09:44 0:00 python3 /root/test.py
# root 7 0.0 0.0 0 0 ? Z 09:44 0:00 [python3] <defunct>
# root 8 0.0 0.1 9392 3068 ? R 09:44 0:00 ps xawuf
We can observe a zombie (defunct
) process that is orphan, it has the PID #7
. This process will remain here since there is no process that will care about reaping (removing) it. In this case it’s not an issue, but imagine if the main process you are running creates a lot of child processes (like in a loop) that become orphans and then zombies.
Killing zombies
How to get rid of zombies?
Use the docker --init
flag
Since the Docker 1.13
version there is a special --init
flag that can be used to tell Docker to use an init system that will reap zombies.
You can use the
--init
flag to indicate that an init process should be used as thePID 1
in the container. Specifying an init process ensures the usual responsibilities of an init system, such as reaping zombie processes, are performed inside the created container.— source
Let’s check if it works.
# With init flag
$ docker run --init --rm zombie-init
# The zombie pid will be: 8
# USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
# root 1 2.0 0.0 1052 4 ? Ss 09:42 0:00 /sbin/docker-init -- /root/test.py
# root 6 0.0 0.5 13164 10808 ? S 09:42 0:00 python3 /root/test.py
# root 9 0.0 0.1 9392 3004 ? R 09:42 0:00 \_ ps xawuf
Yes, it works the defunct
process (#8
) is gone!
Using an init
for containers
There are several init
solutions for containers and mainly
I’ve chosen here to use tini since it’s included in Docker (it’s in fact the --init
flag seen just before).
The default init process used is the first docker-init executable found in the system path of the Docker daemon process. This docker-init binary, included in the default installation, is backed by tini.
— source
To use it several choices are available, one of them is to download and install it directly in the dockerfile
.
# ---- TINI ----
ENV TINI_VERSION v0.18.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini
ENTRYPOINT ["/tini", "--"]
# [...]
CMD ["/root/test.py"]
It is included in some base images and can also be installed through conda
.
$ conda install -c conda-forge tini