Use gcloud metadata-from-file shutdown-script to stop docker container gracefully - linux

I have created gcloud compute instance from docker image and configured it to launch shutdown script which should call docker stop in order to shut down the app in the container gracefully.
gcloud beta compute instances create-with-container mycontainername \
--container-image ypapax/trap_exit \
--metadata-from-file shutdown-script=./on_shutdown.sh
And here is my initital on_shutdown.sh:
#!/usr/bin/env bash
docker stop $(docker ps -a -q)
Although, I added more debugging lines to it and now on_shutdown.sh looks like:
#!/usr/bin/env bash
# https://cloud.google.com/compute/docs/shutdownscript
curl -X POST -d "$(date) running containers on $(hostname): $(docker ps)" http://trap_exit.requestcatcher.com/test
docker stop $(docker ps -a -q)
result=$?
curl -X POST -d "$(date) $(hostname) stop status code: $result" http://trap_exit.requestcatcher.com/test
When I reboot the google compute instance:
sudo reboot
The script on_shutdown.sh is launched (I see it checking requrest listener). But when it tries to stop docker container, there is nothing to stop yet, docker ps shows empty line.
So this line:
curl -X POST -d "$(date) running containers on $(hostname): $(docker ps)" http://trap_exit.requestcatcher.com/test
gives me
Thu Jul 12 04:29:48 UTC 2018 running containers on myinstance:
Before calling sudo reboot I checked docker ps and saw my container running:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bbaba30e65ff ypapax/trap_exit "/entrypoint.sh" 7 seconds ago Up 7 seconds myinstance
So looks like docker container is killed between calling reboot and launching on_shutdown.sh. The problem is that killing doesn't call trap cleanup EXIT in my entrypoint. It needs to be stopped in order to call the cleanup.
Here is my entry point:
#!/usr/bin/env bash
set -ex
cleanup(){
echo cleanup is called
curl -X POST -d "cleanup is called on $(hostname) $(date)" http://trap_exit.requestcatcher.com/test
}
trap cleanup EXIT
curl -X POST -d "container is started on $(hostname) $(date)" http://trap_exit.requestcatcher.com/test
sleep 10000
So I would like to run my container's cleanup on gcloud compute instance reboot or shutdown but flag --metadata-from-file shutdown-script=./on_shutdown.sh doesn't help to do it. I also tried other methods to call a script on reboot like this. But my script hadn't been launched at all.
Here is my Dockerfile if it could help.

First, there are limitations coming with this approach:
Create and run shutdown scripts that execute commands right before an instance is terminated or restarted, on a best-effort basis.
Shutdown scripts are especially useful for instances in a managed instance group with an autoscaler.
The script runs during the limited shutdown period before the instance stops
As you have seen, docker might already have stopped by the time the shutdown script run: check with docker ps -a (instead of docker ps) to see the status of all exited containers.
Try adding a supervisor (as in this example) a docker image itself, in order to see if the supervisor, or at least use the docker run --init option: the goal is to check if the containers themselves do use their supervisor scripts.

Related

Why isn't this script killing Docker background process?

I've read How do I kill background processes / jobs when my shell script exits?, but I can't get it to work.
IDK if it's Docker shenanigans or something else.
#!/bin/bash -e
base="$(dirname "$0")"
trap 'kill $(jobs -p)' SIGINT SIGTERM EXIT
docker run --rm -p 5432:5432 -e POSTGRES_PASSWORD=password postgres:12 &
while ! nc -z localhost 5432; do
sleep 0.1
done
# uh-oh, error
false
When I run this, I am left with a running Docker container.
Why? How can stop the process when my script exits?
Docker is a client/server application, consisting of a thin client, docker, and server, dockerd. When you run a container, the client makes a few API calls to the server, one to create the container, another to start it, and since you didn't run it detached, it runs an attach API. When you kill the docker process, it detaches from the container, no longer showing you the logs, and kills that client portion. But the dockerd server is still running the container until process inside the container, running as pid 1 inside the container namespace, exits. You never killed that process since it's spawned from the dockerd daemon, not directly from the docker client.
To fix this, my suggestion is to run a docker stop, with the container name or id, as part of your trap handler. I wouldn't even bother running docker in the background, and instead pass -d to run detached.
Follow up, testing the script locally, it looks like killing the docker client does send a docker stop signal when you run the client attached like that. However, there's a race condition that can cause that stop to happen before the database is running. The command:
nc -z localhost 5432
is always going to succeed even before postgresql starts listening on the port because docker creates a port forward. E.g.:
$ nc -z localhost 5432 && echo it works
$ docker run -itd --rm -p 5432:5432 busybox tail -f /dev/null
c72427053124608fe18c31e5d6f3307d74a5cdce018503e9fff85dbc039b4fff
$ nc -z localhost 5432 && echo it works
it works
$ docker stop c72
c72
$ nc -z localhost 5432 && echo it works
However, if I run a sleep in the script, that forces it to wait long enough for the container to finish starting up, and the attach to finish, the container is stopped.
A better version of the script looks like the following, that waits for the database to completely start by checking the logs, and changing the trap to run a docker stop command:
#!/bin/bash -e
base="$(dirname "$0")"
trap 'kill $(jobs -p)' SIGINT SIGTERM EXIT
cid=$(docker run --rm -d -p 5432:5432 -e POSTGRES_PASSWORD=password postgres:12)
# leaving the kill assuming you have other background processes
trap 'docker stop $cid; kill $(jobs -p)' SIGINT SIGTERM EXIT
# waiting for the db to actually start, assuming later steps need the db to be up
while ! docker logs "$cid" 2>&1 | grep -q "database system is ready to accept connections" ; do
sleep 0.1
done
# uh-oh, error
false
It was Docker shenanigans.
I needed to use the --init option to run tini shim because
A process running as PID 1 inside a container is treated specially by Linux: it ignores any signal with the default action. As a result, the process will not terminate on SIGINT or SIGTERM unless it is coded to do so.
docker run --rm -p 5432:5432 -e POSTGRES_PASSWORD=password postgres:12 &

How to start single process using service script passed to ENTRYPOINT

I am passing the service script to ENTRYPOINT. The service is started but exited.
I have to start a process per container using service script from ENTRYPOINT or CMD. This way, I can reload the configuration inside the container using service script. I tried with CMD statement as well, but it starting the service but immediately exists the container.
ENTRYPOINT ["/etc/init.d/elasticsearch", "start"]
/etc/init.d/elasticsearch script has below code to start the service as daemon.
cd $ES_HOME
echo -n $"Starting $prog: "
daemon --user elasticsearch --pidfile $pidfile $exec -p $pidfile -d
retval=$?
echo
[ $retval -eq 0 ] && touch $lockfile
return $retval
Is it not possible to start the service using startup script and keep the container running?
commands used to create and run the containers.
docker build -f Dockerfile -t="elk/elasticsearch" .
docker run -d elk/elasticsearch
docker run -it elk/elasticsearch bash
The sysv initscripts are of type "forking" speaking in terms of a service manager. So it will detach from the start script. The container then needs some init process on pid 1 that controls the background process(es).
If you do not want to extract the relevant command from the initscript then you could still use the docker-systemctl-replacement to do both things for you. If it is run as CMD then it will start enabled service scripts just as you are used from a normal machine.
In general you do not use service scripts with Docker. Also in general, you never restart the service inside a container; instead, you stop the existing container, delete it, and start a new one.
The standard pattern is to launch whatever service it is you are trying to run, directly, as a foreground process. (No /etc/init.d, service, or systemctl anything.) You can extract the relevant command from the init script you show. I would replace your ENTRYPOINT command with
CMD ["elasticsearch"]
(but also double-check the Elasticsearch documentation just in case there are some other command-line options that matter).
The second part of this is to make sure database data is stored outside the container. Usually you use the docker run -v option to mount some alternate storage into the container. For example:
docker run \
--name elasticsearch \
-p 9200:9200 \
-v ./elasticsearch:/var/data/elasticsearch \
imagename
Once you’ve done this, you are free to stop, delete, and recreate the container, which is the right way to restart the service. (You need to do this if the underlying image ever changes; this happens if there is a bug fix or security issue in the image software or in the underlying Linux distribution.)
docker stop elasticsearch
docker rm elasticsearch
docker run -- name elasticsearch ...
You can write a simple shell script to hold the docker run command, or use an orchestration tool like Docker Compose that lets you declare the container parameters.

Can't attach to bash running the Docker container

Having troubles with attaching to the bash instance keeping the container running.
To be more detailed. I am running container as here:
$ docker run -dt --name test ubuntu bash
Now it should be actually running, not finished.
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED
STATUS PORTS NAMES
f3596c613cfe ubuntu "bash" 4 seconds ago Up 2 seconds test
After this, I am trying to attach to that instance of bash that keeps the container running. Like this:
$ docker attach test
Running this command I am able to write something to stdin, but no result following. I am not sure if bash is getting lines I typed.
Is there some other way to bash that keeps the container running?
I know, that I can run a different instance of bash and use it docker exec -it test bash. But being more general, is there a way to connect to process that's running in Docker container?
Sometimes it can be useful to save the session of a process running inside the container.
SOLUTION
Thanks to user2915097 for pointing out the missing -i flag.
So now we can have persistent bash session. For example, let's set some alias and reuse after stopping and restarting the container.
$ docker run -itd --name test ubuntu bash
To attach to bash instance just run
$ docker attach test
root#3534cbe1e994:/# alias test="Hello, world!"
To detach from container and not to stop the container press Ctrl+p, Ctrl+q
Then we can stop and restart the container
$ docker stop test
$ docker start test
Now we can attach to the same bash instance and check our alias
$ docker attach test
root#3534cbe1e994:/# test
Hello, world!
Everything is working perfectly!
As I have pointed out in my comment use-case for this can be running some interactive shells as bash, octave, ipython in Docker container persisting all the history, imports, variables and temporary settings just
by reattaching to the same instance.
Your container is running, it is not finished, as you can see
it appears in docker ps, so it is a running container
it show up n seconds
you launch it with -dt so you want it
detached (for d)
allocate a tty (for t)
but not interactive, as you do not add -i
Usually, you nearly always provide -it together, it may be -idt
See this thread
When would I use `--interactive` without `--tty` in a Docker container?
as you want bash, I think you should add -i
I am not sure why you use -d
Usually it is
docker run -it --rm --name=mytest ubuntu bash
and you can test
A container's running lifecycle is determined by its root process, which is bash in your example. When your start your ubuntu container with bash as the process, bash is immediately exiting because it has nothing to keep it running. That's why the container immediately exits and there's nothing to attach to.

bash script command "trap" doesn't work in docker container

I'm trying to gracefully shutdown a service that I run in a docker container (on a raspberry pi 3 b running raspbian lite), since just issuing 'docker stop' breaks the service (a lock file does not get cleared)
My internet research has shown people working with the bash command 'trap' to execute a command when the script receives a signal to terminate.
I have tried this with the following:
docker run --rm -it --name trap resin/rpi-raspbian:jessie /bin/bash -c " trap 'echo shutting down ; exit' INT; echo waiting; read"
When I run above command, 'waiting' is being printed to the screen and when i hit CTRL+C, the message 'shutting down' appears. - Exactly as I want it!
The only problem is, that I can't seem to get that signal sent to the bash script from outside the docker container. I've tried (in a parallel ssh session):
docker kill --signal="INT" trap
docker kill --signal="SIGINT" trap
Furthermore, I tried to use the default signal that is being sent by 'docker stop', but that resulted in the console waiting ~10s (default docker stop timeout) and then killing the docker container.
docker stop trap
Neither methods got the message 'shutting down' to print onto the screen.
I'd really appreciate any help.
Edit 1:
It doesn't work with SIGTERM either (when I either of the following commands and then issue a 'docker stop' I still don't get the message 'shutting down').
docker run --rm -it --name trap resin/rpi-raspbian:jessie /bin/bash -c " trap 'echo shutting down ; exit' TERM; echo waiting; read"
docker run --rm -it --name trap resin/rpi-raspbian:jessie /bin/bash -c " trap 'echo shutting down ; exit' SIGTERM; echo waiting; read"
Workaround:
The image seems to be the problem. The entrypoint '/usr/bin/entry.sh' traps the signal. I've tried it with another image and it worked:
$ docker run -it --name trap digitallyseamless/archlinux-armv6h /bin/bash -c " trap 'echo shutting down ; exit 0' SIGTERM; echo waiting; read"
Stop the container using (from another terminal)
$ docker stop trap
Then view the container output
$ docker logs trap
waiting
shutting down
As I have no particular need to use a specific image (it really is just a means to get me to my goal), I'll just swap out images.
PS:
Huge thanks to #tkausl
As I mentioned in the comments, docker stop sends a SIGTERM. According to your edit, this doesn't work for you, however, I just tried it with exactly the same command (changed the image to ubuntu though) and it works. Are you probably detaching from the running box? In this case, you obviously don't see the message, remove the --rm flag and take a look at the docker logs.
How I did it:
In one terminal:
docker run --rm -it --name trap ubuntu /bin/bash -c " trap 'echo shutting down ; exit' SIGTERM; echo waiting; read"
In a second terminal:
$ docker stop f6
f6
What I see in the first terminal after stopping:
$ docker run --rm -it --name trap ubuntu /bin/bash -c " trap 'echo shutting down ; exit' SIGTERM; echo waiting; read"
waiting
shutting down
$
Docker stop and docker kill are sending a signal and not the trap command. You have to make a docker exec container_name trap

docker container started in Detached mode stopped after process execution

I create my docker container in detached mode with the following command:
docker run [OPTIONS] --name="my_image" -d container_name /bin/bash -c "/opt/init.sh"
so I need that "/opt/init.sh" executed at container created. What I saw that the container is stopped after scripts finish executed.
How to keep container started in detached with script/services execution at container creation ?
There are 2 modes of running docker container
Detached mode - This mode you execute a command and will terminate container after the command is done
Foreground mode - This mode you run a bash shell, but will also terminate container after you exit the shell
What you need is Background mode. This is not given in parameters but there are many ways to do this.
Run an infinite command in detached mode so the command never ends and the container never stops. I usually use "tail -f /dev/null" simply because it is quite light weight and /dev/null is present in most linux images
docker run -d --name=name container tail -f /dev/null
Then you can bash in to running container like this:
docker exec -it name /bin/bash -l
If you use -l parameter, it will login as login mode which will execute .bashrc like normal bash login. Otherwise, you need to bash again inside manually
Entrypoint - You can create any sh script such as /entrypoint.sh. in entrypoint.sh you can run any never ending script as well
#!/bin/sh
#/entrypoint.sh
service mysql restart
...
tail -f /dev/null <- this is never ending
After you save this entrypoint.sh, chmod a+x on it, exit docker bash, then start it like this:
docker run --name=name container --entrypoint /entrypoint.sh
This allows each container to have their own start script and you can run them without worrying about attaching the start script each time
A Docker container will exit when its main process ends. In this case, that means when init.sh ends. If you are only trying to start a single application, you can just use exec to launch it at the end, making sure to run it in the foreground. Using exec will effectively turn the called service/application into the main process.
If you have more than one service to start, you are best off using a process manager such as supervisord or runit. You will need to start the process manager daemon in the foreground. The Docker documentation includes an example of using supervisord.

Resources