After some crashes with a docker container with a too low mem_limit, how can i check in a container the mem_limit of this container? I want to print an error message on startup and exit if the mem_limit is set to low.
The memory limit is enforced via cgroups. Therefore you need to use cgget to find out the memory limit of the given cgroup.
To test this you can run a container with a memory limit:
docker run --memory 512m --rm -it ubuntu bash
Run this within your container:
apt-get update
apt-get install cgroup-bin
cgget -n --values-only --variable memory.limit_in_bytes /
# will report 536870912
Docker 1.13 mounts the container's cgroup to /sys/fs/cgroup (this could change in future versions). You can check the limit using:
cat /sys/fs/cgroup/memory/memory.limit_in_bytes
On the host you can run docker stats to get a top like monitor of your running containers. The output looks like:
$ docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
729e4e0db0a9 dev 0.30% 2.876GiB / 3.855GiB 74.63% 25.3MB / 4.23MB 287kB / 16.4kB 77
This is how I discovered that docker run --memory 4096m richardbronosky/node_build_box npm run install was not getting 4G of memory because Docker was configured to limit to 2G of memory. (In the example above this has been corrected.) Without that insite I was totally lost as to why my process was ending with simply "killed".
Worked for me in the container, thanks for the ideas Sebastian
#!/bin/sh
function memory_limit
{
awk -F: '/^[0-9]+:memory:/ {
filepath="/sys/fs/cgroup/memory"$3"/memory.limit_in_bytes";
getline line < filepath;
print line
}' /proc/self/cgroup
}
if [[ $(memory_limit) < 419430400 ]]; then
echo "Memory limit was set too small. Minimum 400m."
exit 1
fi
Previously the /sys/fs/cgroup/memory/memory.limit_in_bytes worked for me, but in my ubuntu with kernel 5.8.0-53-generic seems that the correct endpoint now is /sys/fs/cgroup/memory.max to recover the memory limit from inside the container.
You have to check all values from the path defined in /prof/self/cgroup (example: /sys/fs/cgroup/memory/user.slice/user-1501.slice/session-99.scope) up to /sys/fs/cgroup/memory and look for the minimum. Here is the script:
#!/bin/bash
function memory_limit {
[ -r /proc/self/cgroup ] || { echo >&2 "Cannot read /proc/self/cgroup" ; return 1; }
path=$(grep -Poh "memory:\K.*" /proc/self/cgroup)
[ -n "$path" ] || { echo >&2 "Cannot get memory constrains from /proc/self/cgroup" ; return 1; }
full_path="/sys/fs/cgroup/memory${path}"
cd "$full_path" || { echo >&2 "cd $full_path failed" ; return 1; }
[ -r memory.limit_in_bytes ] || { echo >&2 "Cannot read 'memory.limit_in_bytes' at $(pwd)" ; return 1; }
min=$(cat memory.limit_in_bytes)
while [[ $(pwd) != /sys/fs/cgroup/memory ]]; do
cd .. || { echo >&2 "cd .. failed in $(pwd)" ; return 1; }
[ -r memory.limit_in_bytes ] || { echo >&2 "Cannot read 'memory.limit_in_bytes' at $(pwd)" ; return 1; }
val=$(cat memory.limit_in_bytes)
(( val < min )) && min=$val
done
echo "$min"
}
memory_limit
21474836480
In my situation, I have
cat /proc/self/cgroup
3:memory:/user.slice/user-1501.slice/session-99.scope
cat /sys/fs/cgroup/memory/user.slice/user-1501.slice/session-99.scope/memory.limit_in_bytes
9223372036854771712
cat /sys/fs/cgroup/memory/user.slice/user-1501.slice/memory.limit_in_bytes
21474836480 <= actual limit
cat /sys/fs/cgroup/memory/user.slice/memory.limit_in_bytes
9223372036854771712
cat /sys/fs/cgroup/memory/memory.limit_in_bytes
9223372036854771712
Thanks to Mandragor for the original idea.
Related
As for linux, I can get the real-time of a process through /proc (process file system). But how can I get the data of the process on MacOS 12.4 since there is no /proc dir.
When using top command, I want to save the read-time output of a process to a text file (only the process info), but the interval is too large to get accurate result and I cannot strip the header of top via options. Is there any possible way to achieve this? On a phone, I can get the output of the top via the following script:
#!/bin/sh
export LANG="en_US.UTF-8"
if [ "$#" -ne 1 ]; then
echo "Illegal number of parameters" $#
exit 0
fi
while true; do
pid=`pgrep COMMAND`
if [ "$pid" -ne 0 ]; then
echo "${pid}\t`top -q -p $pid -n 1 -d 1`" >> $1
fi
usleep 100
done
I wonder whether it is possible to get the detailed info via /proc-like file system on MacOS?
I have couple of python docker containers, processing data from kafka topics every X seconds.
If the data isn't appearing for i.e. more than 2X time, I should log it somewhere (so I can check which kafka producers to restart - doesn't matter here).
I was thinking about creating some file, where I would keep last message timestamp, and another script/container would check it every X seconds, to find out, if the data should already have appeared. I could check docker logs aswell, if "amount" of logs is increasing (if not, then it means, that no data is appearing).
While those solutions would probably work, I'm not sure, if there isn't already another "ready-made" solution for that issue. Is there something, that I can use to check if the data is being processed?
You can use some sort of health check mechanism by putting logs inside: <filename_1>
then the healthcheck would be:
if [[ `[ -r <filename_1> ] && cat <filename_1> | wc -l` -ge 1 ]]; then > <filename_1> && echo 0; else echo 1; fi
By doing this you check if the file exist then check the number of lines. If greater/equal than 1 you empty it then return 0 .If empty or does not exist you return 1
This will mark your docker as healthy or unheathly.
If you want to restart by itself you can replace echo 1 by kill 1 kill 1 will kill process with pid 1 in container and force container exit. Usually the process started by CMD or ENTRYPOINT has pid 1.
[[ `[ -r <filename_1> ] && cat <filename_1> | wc -l` -ge 1 ]] && > <filename_1> && echo 0 || kill 1
Don't forget --restart always option.
I have a Jenkins script that looks like this
stage ("Build and Deploy") {
steps {
script {
def statusCode = sh(script:"""ssh ${env.SERVER_NAME} << EOF
cd ${env.LOCATION}
git clone -b ${env.GIT_BRANCH} ${env.GIT_URL} ${env.FOLDER}
cd ${env.FOLDER}
... some other stuff goes here but isnt relevant ..
sudo docker-compose up -d --build
if [ ! \$(sudo docker container ls -f "name=config-provider-*" | wc -l ) -eq 4 ]
then
exit 1
fi
""", returnStatus:true).toString().trim()
if (statusCode == "1") {
error("At least one container failed to start")
}
}
}
}
What I want is to exit with error code 1 in the script if the number of running containers is not equal to 3 (wc -l == 4 including the header), but the if statement is evaluating true and exiting with error code 1 even though i know that the containers are successfully running.
I have tried
echo sh(script: """ssh ${env.SERVER_NAME} << EOF
echo \$(sudo docker container ls -f "name=config-provider-*" | wc -l)
""", returnStdout:true).toString()
and
echo sh(script: """ssh ${env.SERVER_NAME} << EOF
echo \$(sudo docker container ls -f "name=config-provider-*")
""", returnStdout:true).toString()
The latter outputted 4 lines within jenkins showing all of the running containers, as expected, but the former which includes "| wc -l" returned and printed out 0 in jenkins.
I have reproduced the steps of this script line by line manually from start to finish and it works as intended when not run from within jenkins.
Additionally, manually running the command:
[ ! $(sudo docker container ls -f "name=config-provider-*" | wc -l ) -eq 4 ] && echo failed
echoes nothing, and the following command returns an output of 4, which is expected.
echo $(sudo docker container ls -f "name=config-provider-*" | wc -l )
I'm building a sh script to be able to run Docker containers all in once. In this way, I no need to run single containers every time.
What I need to add now is a way to run my docker-compose up --build -target=<ENV> the ENV will be DEV or PROD. In this way, I can run the right environment in my Docker setup.
At this time my script looks as follow but when I try to pass $2 = $DEV is giving me an error of [: =: unary operator expected and I don't know what could be the right fix to this
#!/bin/bash
CLEAN="clean"
RUN="run"
STOP="stop"
DEV="dev"
PROD="prod"
if [ "$#" -eq 0 ] || [ $1 = "-h" ] || [ $1 = "--help" ]; then
echo "Usage: ./myapp [OPTIONS] COMMAND [arg...]"
echo " ./myapp [ -h | --help ]"
echo ""
echo "Options:"
echo " -h, --help Prints usage."
echo ""
echo "Commands:"
echo " $CLEAN - Stop and Remove containers."
echo " $RUN - Build and Run containers."
echo " $STOP - Stop containers."
exit
fi
clean() {
stop_existing
remove_stopped_containers
remove_unused_volumes
}
run() {
echo "Cleaning..."
clean
echo "Running docker..."
if [ $2 = $DEV ]; then
echo "$DEV - Running in - $DEV - environment"
docker-compose up --build -target=$DEV
fi
}
stop_existing() {
MYAPP="$(docker ps --all --quiet --filter=name=wetaxitask_api_dev)"
REDIS="$(docker ps --all --quiet --filter=name=wetaxitask_redis)"
MONGO="$(docker ps --all --quiet --filter=name=wetaxitask_mongodb)"
if [ -n "$MYAPP" ]; then
docker stop $MYAPP
fi
if [ -n "$REDIS" ]; then
docker stop $REDIS
fi
if [ -n "$MONGO" ]; then
docker stop $MONGO
fi
}
remove_stopped_containers() {
CONTAINERS="$(docker ps -a -f status=exited -q)"
if [ ${#CONTAINERS} -gt 0 ]; then
echo "Removing all stopped containers."
docker rm $CONTAINERS
else
echo "There are no stopped containers to be removed."
fi
}
remove_unused_volumes() {
CONTAINERS="$(docker volume ls -qf dangling=true)"
if [ ${#CONTAINERS} -gt 0 ]; then
echo "Removing all unused volumes."
docker volume rm $CONTAINERS
else
echo "There are no unused volumes to be removed."
fi
}
if [ $1 = $CLEAN ]; then
echo "Cleaning..."
clean
exit
fi
if [ $1 = $RUN ]; then
run
exit
fi
if [ $1 = $STOP ]; then
stop_existing
exit
fi
What I want to achieve is that possible to run my sh as follow
./script.sh run dev or prod
When you invoke a shell function, it has its own argument list. The POSIX shell spec indicates:
The operands to the command temporarily shall become the positional parameters during the execution of the compound-command
So, if you define and call a shell function
print_two_things() {
echo "dollars one is $1"
echo "dollars two is $2"
}
print_two_things foo bar
print_two_things
$1 and $2 are the arguments to the function, not the script.
In your script, there are two errors in the run function
run() {
if [ $2 = $DEV ]; then :; fi
}
Here, again, $2 is the second argument to the function, not the script; since you invoke this as just run with no arguments it's an empty string. Second, you don't quote either of these variables, so the empty string just gets dropped. This expands to the nonsensical [ = dev ] which produces the error you see.
You need to either capture the positional parameters in variables at the top level, or pass them down to the shell function. An example of the latter approach could be:
run() {
environment="$1" # first parameter _to this function_
clean
if [ "$environment" = "$DEV" ]; then :; fi
}
# at the top level; parameters _to the script_
if [ "$1" = "$RUN" ]; then
run "$2"
exit 0
fi
In a Docker context you might find it easier just to pass this in as an environment variable. Anything you set with a docker run -e option or similar settings will be directly available as environment variables in shell scripts. It's also usually considered a best practice to run identical images in dev, test, and prod if at all possible.
In an alpine container only: When running a setuid binary which starts an other executable (execve(2)), the kernel[1] BusyBox seems to drop privileges acquired by setuid. I think this might be by design because of security implications.
Question: I would like to understand why this is happening and what is responsible for this?
I am working on a one-shot setuid runner called kamikaze written in rust. kamikaze is a very simple binary that unlink(2) itself and then starts a new process using fork(2) and execve(2).
The main components are:
src/main.rs [a47dedc]: Implements the unlink(2) and process spawning.
use std::env;
use std::fs;
use std::process::{Command, exit};
fn usage() {
println!("usage: kamikaze <command> <arguments>");
exit(1);
}
fn main() {
// Kill myself
fs::remove_file(
env::current_exe().expect("failed to get path to executable")
).expect("kamikaze failed");
let mut args: Vec<String> = env::args().collect();
match args.len() {
0 => usage(),
1 => usage(),
_ => {
args.remove(0);
let mut child = Command::new(args.remove(0))
.args(&args)
.spawn()
.expect("failed to execute process");
exit(
child
.wait()
.expect("wait failed")
.code().unwrap()
);
},
}
}
install.sh [a47dedc]: A simple installer which downloads kamikaze, changes ownership to root and sets the setuid bit.
#!/usr/bin/env sh
set -euo pipefail
REPO="Enteee/kamikaze"
INSTALL="install -m 755 -o root kamikaze-download kamikaze && chmod u+s kamikaze"
curl -s "https://api.github.com/repos/${REPO}/releases/latest" \
| grep "browser_download_url" \
| cut -d '"' -f 4 \
| xargs -n1 curl -s -L --output kamikaze-download
trap 'rm kamikaze-download' EXIT
if [[ $(id -u) -ne 0 ]]; then
sudo sh -c "${INSTALL}"
else
eval "${INSTALL}"
fi
When I run kamikaze outside a container[2]:
$ curl https://raw.githubusercontent.com/Enteee/kamikaze/master/install.sh | sh
$ ./kamikaze ps -f
UID PID PPID C STIME TTY TIME CMD
root 3223 9587 0 08:17 pts/0 00:00:00 ./kamikaze ps -f
root 3224 3223 0 08:17 pts/0 00:00:00 ps -f
I get the expected behavior. The child process (PID=3224) runs as root. On the other hand, inside a container[2]:
$ docker build -t kamikaze - <<EOF
FROM alpine
RUN set -exuo pipefail \
&& apk add curl \
&& curl https://raw.githubusercontent.com/Enteee/kamikaze/master/install.sh | sh
USER nobody
CMD ["/kamikaze", "ps"]
EOF
$ docker run kamikaze
PID USER TIME COMMAND
1 root 0:00 /kamikaze ps
6 nobody 0:00 ps
ps runs as nobody.
[1] I first thought that this was because of some security mechanism implemented by docker and the Linux kernel. But after a deep dive into Docker Security, NO_NEW_PRIVILEGES and seccomp(2) I finally realized that BusyBox is simply dropping privileges.
[2] kamikaze [1.0.0] fixed and changed this behavior. Therefore this example does no longer work. For reproducing the example use the kamikaze [0.0.0] release.
BusyBox, which implements the ps command in alpine, drops privileges acquired by setuid by setting the effective user id to the real user id.
libbb/appletlib.c [b097a84]:
} else if (APPLET_SUID(applet_no) == BB_SUID_DROP) {
/*
* Drop all privileges.
*
* Don't check for errors: in normal use, they are impossible,
* and in special cases, exiting is harmful. Example:
* 'unshare --user' when user's shell is also from busybox.
*
* 'unshare --user' creates a new user namespace without any
* uid mappings. Thus, busybox binary is setuid nobody:nogroup
* within the namespace, as that is the only user. However,
* since no uids are mapped, calls to setgid/setuid
* fail (even though they would do nothing).
*/
setgid(rgid);
setuid(ruid);
}
procps/ps.c [b097a84]: Defines BB_SUID_DROP.
// APPLET_NOEXEC:name main location suid_type help
//applet:IF_PS( APPLET_NOEXEC(ps, ps, BB_DIR_BIN, BB_SUID_DROP, ps))
//applet:IF_MINIPS(APPLET_NOEXEC(minips, ps, BB_DIR_BIN, BB_SUID_DROP, ps))
The fix for this was simple. kamikaze just has to set the real user id to the effective user id before execve(2).
src/main.rs [f4c5501]:
extern crate exec;
extern crate users;
use std::env;
use std::fs;
use std::process::exit;
use users::{get_effective_uid, get_effective_gid};
use users::switch::{set_current_uid, set_current_gid};
fn usage() {
println!("usage: kamikaze <command> <arguments>");
}
fn main() {
// Kill myself
fs::remove_file(
env::current_exe().expect("failed to get path to executable")
).expect("kamikaze failed");
set_current_uid(
get_effective_uid()
).expect("failed setting current uid");
set_current_gid(
get_effective_gid()
).expect("failed setting current gid");
let mut args: Vec<String> = env::args().collect();
match args.len() {
0 => usage(),
1 => usage(),
_ => {
args.remove(0);
let err = exec::Command::new(args.remove(0))
.args(&args)
.exec();
println!("Error: {}", err);
},
}
// Should never get here
exit(1);
}
with the newly released kamikaze [1.0.0] we now get:
$ docker build -t kamikaze - <<EOF
FROM alpine
RUN set -exuo pipefail \
&& apk add curl \
&& curl https://raw.githubusercontent.com/Enteee/kamikaze/master/install.sh | sh
USER nobody
CMD ["/kamikaze", "ps"]
EOF
$ docker run kamikaze
PID USER TIME COMMAND
1 root 0:00 ps