Tail stdout from multiple Docker containers - docker

I have a script that starts 10 containers in background mode (fig up -d option). I want to aggregate the stdout or the log in /var/log from all of them. How can I do this?
The containers are started using different docker-compose files so I can not do docker-compose up target1 target2 target3
docker logs only accepts one container as a parameter.
I was considering creating a volume from the /var/log on all containers, mapping them to a directory outside of docker, making sure the logs do not have colliding name and than using the bash tail -f * . But I would appreciate a more elegant solution

This bash script will do what you want:
docker-logs
#!/bin/bash
if [ $# -eq 0 ]; then
echo "Usage: $(basename "$0") containerid ..."
exit 1
fi
pids=()
cleanup()
{
kill "${pids[#]}"
}
trap cleanup EXIT
while [ $# -ne 0 ]
do
(docker logs -f -t --tail=10 "$1"|sed -e "s/^/$1: /")&
pids+=($!)
shift
done
wait
Usage:
$ docker-logs containerid1 containerid2 ... containeridN
The output of this script has each line from the tracked logs prepended with the container id.
The script works in --follow mode and must be interrupted with Ctrl-C.
Note that the options of docker logs are hardcoded in the script. If you need to be able to control the options of docker logs from the command line then you will need to parse the command line arguments (for example with getopts).

Docker does not support as 1.12 yet. But I have a workaround via bash;
docker ps | grep -w <filter-text> | for i in `awk '{ print $1 }'`; do docker logs -f --tail=30 $i & done
I am using docker swarm modes comes with 1.12 and deploying many replication. So all of my containers contain common text which is same as service name. To tail all of its logs in a docker node , I am using this on each docker node. filter-text will be filtering only my containers.
If you want to stop tailing, this works for me;
pkill -f 'docker logs'

Related

Running a background process versus running a pipeline in the background

I want to output a logfile from a Docker container and stumbled across something that I don't understand. These two lines don't fail, but only the first one works as I would like it to:
tail --follow "/var/log/my-log" &
tail --follow "/var/log/my-log" | sed -e 's/^/prefix:/' &
Checking inside the running container, I see that the processes are running but I only see the output of the first line in the container output.
Dockerfile
FROM debian:buster-slim
COPY boot.sh /
ENTRYPOINT [ "/boot.sh" ]
boot.sh
Must be made executable (chmod +x)!
#!/bin/sh
echo "starting"
echo "start" > "/var/log/my-log"
tail --follow "/var/log/my-log" &
tail --follow "/var/log/my-log" | sed -e 's/^/prefix:/' &
echo "sleeping"
sleep inf
Running
Put the two files above into a folder.
Build the image with docker build --tag pipeline .
Run the image in one terminal with docker run --init --rm --name pipeline pipeline. Here you can also watch the output of the container.
In a second terminal, open a shell with docker exec -it pipeline bash and there, run e.g. date >> /var/log/my-log. You can also run the two tail ... commands here to see how they should work.
To stop the container use docker kill pipeline.
I would expect to find the output of both tail ... commands in the output of the container, but it already fails on the initial "start" entry of the logfile. Further entries to the logfile are also ignored by the tail command that adds a prefix.
BTW: I would welcome a workaround using pipes/FIFOs that would avoid writing a persistent logfile to begin with. I'd still like to understand why this fails. ;)
Based on what I have tested, It seems that sed is causing the issue where the output of this command tail --follow "/var/log/my-log" | sed -e 's/^/prefix:/' & does not appear while running the container. The issue can be solved by passing -u to sed which disables the buffering.
The final working boot.sh will be as follow:
#!/bin/sh
echo "starting"
echo "start" > "/var/log/my-log"
tail --follow "/var/log/my-log" &
tail --follow "/var/log/my-log" | sed -u -e 's/^/prefix:/' &
echo "sleeping"
sleep inf
And the output after running the container will be:
starting
sleeping
start
prefix:start
Appending more data to the log file will be displayed as expected too.
starting
sleeping
start
prefix:start
newlog
prefix:newlog
Also see: why cant I redirect the output from sed to a file

Write to stdin of running docker container

Say I run a docker container as a daemon:
docker run -d foo
is there a way to write to the stdin of that container? Something like:
docker exec -i foo echo 'hi'
last time I checked the -i and -d flags were mutually exclusive when used with the docker run command.
According to another answer on ServerFault, you can use socat to pipe input to a docker container like this:
echo 'hi' | socat EXEC:"docker attach container0",pty STDIN
Note that the echo command includes a newline at the end of the output, so the line above actually sends hi\n. Use echo -n if you don't want a newline.
Let's see how this looks like with the example script from David's answer:
# Create a new empty directory
mkdir x
# Run a container, in the background, that copies its stdin
# to a file in that directory
docker run -itd --rm -v $PWD/x:/x --name cattainer busybox sh -c 'cat >/x/y'
# Send some strings in
echo 'hi' | socat EXEC:"docker attach cattainer",pty STDIN
echo 'still there?' | socat EXEC:"docker attach cattainer",pty STDIN
# Stop container (cleans up itself because of --rm)
docker stop cattainer
# See what we got out
cat x/y
# should output:
# hi
# still there?
You could also wrap it in a shell function:
docker_send() {
container="$1"; shift
echo "$#" | socat EXEC:"docker attach $container",pty STDIN
}
docker_send cattainer "Hello cat!"
docker_send cattainer -n "No newline here:" # flag -n is passed to echo
Trivia: I'm actually using this approach to control a Terraria server running in a docker container, because TerrariaServer.exe only accepts server commands (like save or exit) on stdin.
In principle you can docker attach to it. CTRL+C will stop the container (by sending SIGINT to the process); CTRL+P, CTRL+Q will detach from it and leave it running (if you started the container with docker run -it).
The one trick here is that docker attach expects to be running in a terminal of some sort; you can do something like run it under script to meet this requirement. Here's an example:
# Create a new empty directory
mkdir x
# Run a container, in the background, that copies its stdin
# to a file in that directory
docker run -itd -v $PWD/x:/x --name cat busybox sh -c 'cat >/x/y'
# Send a string in
echo foo | script -q /dev/null docker attach cat
# Note, EOF here stops the container, probably because /bin/cat
# exits normally
# Clean up
docker rm cat
# See what we got out
cat x/y
In practice, if the main way a program communicates is via text on its standard input and standard output, Docker isn't a great packaging mechanism for it. In higher-level environments like Docker Compose or Kubernetes, it becomes progressively harder to send content this way, and there's frequently an assumption that a container can run completely autonomously. Just invoking the program gets complicated quickly (as this question hints at). If you have something like, say, the create-react-app setup tool that asks a bunch of interactive questions then writes things to the host filesystem, it will be vastly easier to run it directly on the host and not in Docker.

how to identify if any applications inside docker container as running as root

We use a lot of 3rd party images [Eg: gitlab , jenkins, centos7 ..] which we run inside our docker containers. I would like to know how to check if any of the applications running in the container is run as root user. Is it the same as checking on a normal server ps -elf|grep root but inside the container.
Running Containers
To get all processes and their UIDs inside your running containers on a host, you can do the following:
for c in $(docker ps -q); do docker inspect $c -f "{{ .Name }}:"; docker top $c | awk '{print $1, $2, $8}'; echo "--------------"; done
This will print something like
/webserver-dockerized_nginx_1:
UID PID CMD
root 13437 nginx:
systemd+ 13522 nginx:
systemd+ 13526 nginx:
systemd+ 13527 nginx:
systemd+ 13528 nginx:
--------------
for all containers you have running.
Images
To get the configured users for all images on a host you can do
docker image inspect $(docker image ls -q) -f "{{ .RepoTags }}: {{ .ContainerConfig.User }} {{ .Config.User }}"
This will output something like
[nginx:mainline-alpine]:
[memcached:alpine]: memcache memcache
[redis:5-alpine]:
As Marvin mentioned: If there is no user in the output, no USER was defined in the Dockerfile, thus the container will run as root (Reference: Docker Documentation)
You can attach the terminal to your running container and once you're inside you can run the ps command:
Attaching to the container
$ docker exec -it <container_id> /bin/bash
You can read more about docker exec in the official docs site: https://docs.docker.com/engine/reference/commandline/exec/
Hope it helps!
You could use docker top command in association with the process id...
Combining "docker ps" and "docker top" could make the thing..
You could do stg like that :
docker ps | perl -ne '#cols = split /\s{2,}/, $_; printf "%15s\n", $cols[0]' > tmp.txt && tail -n $(($(wc -l < tmp.txt)-1)) tmp.txt | xargs -L1 docker top | perl -ne '#cols = split /\s{2,}/, $_; printf "%15s %65s\n", $cols[0], $cols[7]' && rm tmp.txt
That's not a perfect answer ((ould be prettyfied), and also note that it only works for running container. It'd be safer to check this from a image point of view, before you run the container.
Then, every time you get an image, just check this way :
d image inspect <image id> | grep -i user
I might be wrong, but I think no user means root. Otherwise, you will have to analyse the output there.

How to clean Docker container logs?

I read my Docker container log output using
docker logs -f <container_name>
I log lots of data to the log in my node.js app via calls to console.log(). I need to clean the log, because it's gotten too long and the docker logs command first runs through the existing lines of the log before getting to the end. How do I clean it to make it short again? I'd like to see a command like:
docker logs clean <container_name>
But it doesn't seem to exist.
First, if you just need to see less output, you can have docker only show you the more recent lines:
docker logs --since 30s -f <container_name_or_id>
Or you can put a number of lines to limit:
docker logs --tail 20 -f <container_name_or_id>
To delete the logs on a Docker for Linux install, you can run the following for a single container:
echo "" > $(docker inspect --format='{{.LogPath}}' <container_name_or_id>)
Note that this requires root, and I do not recommend this. You could potentially corrupt the logfile if you null the file in the middle of docker writing a log to the same file. Instead you should configure docker to rotate the logs.
Lastly, you can configure docker to automatically rotate logs with the following in an /etc/docker/daemon.json file:
{
"log-driver": "json-file",
"log-opts": {"max-size": "10m", "max-file": "3"}
}
That allows docker to keep up to 3 log files per container, with each file limited to 10 megs (so a limit between 20 and 30 megs of logs per container). You will need to run a systemctl reload docker to apply those changes. And these changes are the defaults for any newly created container, they do not apply to already created containers. You will need to remove and recreate any existing containers to have these settings apply.
The best script I found is
sudo sh -c 'truncate -s 0 /var/lib/docker/containers/*/*-json.log'
It cleans all logs and you don't need to stop the containers.
Credit goes to https://bytefreaks.net/applications/docker/horrible-solution-how-to-delete-all-docker-logs
If you want to remove all log files, not only for a specific container's log, you can use:
docker system prune
But, note that this does not clear logs for running containers.
This is not the ideal solution, but until Docker builds in a command to do it, this is a good workaround.
Create a script file docker-clean-logs.sh with this content:
#!/bin/bash
rm $(docker inspect $1 | grep -G '"LogPath": "*"' | sed -e 's/.*"LogPath": "//g' | sed -e 's/",//g');
Grant the execute permission to it:
chmod +x ./docker-clean-logs.sh
Stop the Docker container that you want to clean:
docker stop <container_name>
Then run the above script:
./docker-clean-logs.sh <container_name>
And finally run your container again:
docker start ...
Credit goes to the user sgarbesi on this page: https://github.com/docker/compose/issues/1083
You can use logrotate as explained in this article
https://sandro-keil.de/blog/2015/03/11/logrotate-for-docker-container/
This needs to be done before launching the container.
Run:
docker inspect {containerId}
Copy LogPath value
truncate -s 0 {LogaPath}
Solution for a docker swarm service:
logging:
options:
max-size: "10m"
max-file: "10"
In order to do this on OSX, you need to get to the virtual machine the Docker containers are running in.
You can use the walkerlee/nsenter image to run commands inside the VM like so:
docker run --rm -it --privileged --pid=host walkerlee/nsenter -t 1 -m -u -i -n sh
Combining that with a simplified version of the accepted answer you get:
#!/bin/sh
docker run --rm -it --privileged --pid=host walkerlee/nsenter -t 1 -m -u -i -n \
cp /dev/null $(docker inspect -f '{{.LogPath}}' $1)
Save it, chmod +x it, run it.
As far as I can tell this doesn't require the container to be stopped. Also, it clears out the log file (instead of deleting it) avoiding errors when doing docker logs right after cleanup.
On Windows 10 none of the solutions worked for me, I kept getting 'No such file or directory'
This worked
Get container ID (inspect the container)
In file explorer open docker-desktop-data (in WSL)
Navigate to version-pack-data\community\docker\containers\CONTAINER_ID
Stop the container
Open the file CONTAINER_ID-json.log file and trim it or just create a blank file with same name
source

How to tell if a docker container run with -d has finished running its CMD

I want to make a simple bash script which runs one docker container with -d and then do something else if and only if the container has finished running its CMD. How can I do this while avoiding timing issues since the docker container can take a while to finish starting up?
My only thought was that the Dockerfile for the container will need to create some sort of state on the container itself when it's done and then the bash script can poll until the state file is there. Is there a better / standard way to do something like this?
Essentially I need a way for the host that ran a docker container with -d to be able to tell when it's ready.
Update
Made it work with the tailing logs method, but it seems a bit hacky:
docker run -d \
--name sauceconnect \
sauceconnect
# Tail logs until 'Sauce Connect is up'
docker logs -f sauceconnect | while read LINE
do
echo "$LINE"
if [[ "$LINE" == *"Sauce Connect is up"* ]]; then
pkill -P $$ docker
fi
done
You should be fine to check the logs via docker logs -f <containter_name_or_ID>
-f : same as tail -f
For example, the CMD is finished, and export a log as JOB ABC is successfully started.
your script can detect and run the rest jobs after get it.

Resources