使用 run-this-one 运行的 Linux 脚本不适用于 docker



我遇到一个问题,我在cronjob中运行了一个命令,并希望确保它尚未执行。我以run-one [command](手册页(的身份实现了这一点。

如果我想取消已经运行的命令并强制运行新命令,我将以run-this-one [command]的身份运行。

至少这是我所期望的,但如果该命令运行一个docker容器,则另一个进程似乎被终止(但不是(,终端显示Terminated,但继续显示在容器中运行的命令输出(但容器结束运行后的命令不会执行(。在这种情况下,运行run-this-one的命令不会被执行(不是预期的(。

示例:

/path/to/file.sh

#!/bin/bash
set -eou pipefail
echo "sleep started..." >&2
docker run --rm alpine /bin/sh -c 'echo "sleep started inside..." && sleep 5 && echo "sleep ended inside..."'
echo "sleep ended..." >&2

如果我在一个终端窗口sudo run-one /path/to/file.sh中运行,然后在另一个终端(在上一个命令结束运行之前(运行命令sudo run-one /path/to/file.sh,则该命令不会按预期执行,并且该命令成功结束。

终端1:

user@host:/path$ sudo run-one /path/to/file.sh
sleep started...
sleep started inside...
sleep ended inside...
sleep ended...
user@host:/path$

终端2:

user@host:/path$ sudo run-one /path/to/file.sh
user@host:/path$

但是,如果我在终端窗口sudo run-one /path/to/file.sh中运行,然后在另一个终端中运行命令sudo run-this-one /path/to/file.sh(在前一个命令结束运行之前(,则该命令不会执行,这不是预期的,并且该命令显示在终端Terminated中,终端显示user@host:/path$,但是容器中的输出仍然显示(命令仍然在第一个终端中创建的容器中运行(。

终端1:

user@host:/path$ sudo run-one /path/to/file.sh
sleep started...
sleep started inside...
Terminated
user@host:/path$ sleep ended inside...
# terminal doesn't show new input from the keyboard, but I can run commands after

终端2:

user@host:/path$ sudo run-this-one /path/to/file.sh
user@host:/path$

如果文件被更改为:,它就会工作

/path/to/file.sh

#!/bin/bash
set -eou pipefail
echo "sleep started..." >&2
sleep 5
echo "sleep ended..." >&2

上面的带有docker的脚本文件只是一个例子,在我的情况下它是不同的,但问题是相同的,并且与运行带有或不带有-it的容器无关。

有人知道为什么会发生这种情况?这个问题有没有一个(不是很复杂,也不是很棘手(的解决方案我已经在一台VirtualBox机器(带有流浪者(中的Ubuntu 20.04中执行了上述命令。

更新(2021-07-15(

根据@ErikMD评论和@DannyB答案,我设置了一个陷阱和一个清除函数来删除容器,如下脚本所示:

/path/to/test

#!/bin/bash
set -eou pipefail
trap 'echo "[error] ${BASH_SOURCE[0]}:$LINENO" >&2; exit 3;' ERR
RED='33[0;31m'
NC='33[0m' # No Color
function error {
msg="$(date '+%F %T') - ${BASH_SOURCE[0]}:${BASH_LINENO[0]}: ${*}"
>&2 echo -e "${RED}${msg}${NC}"
exit 2
}
file="${BASH_SOURCE[0]}"
command="${1:-}"
if [ -z "$command" ]; then
error "[error] no command entered"
fi
shift;
case "$command" in
"cmd1")
function cleanup {
echo "cleaning $command..."
sudo docker rm --force "test-container"
}
trap 'cleanup; exit 4;' ERR
args=( "$file" "cmd:unique" )
echo "$command: run-one ${args[*]}" >&2
run-one "${args[@]}"
;;
"cmd2")
function cleanup {
echo "cleaning $command..."
sudo docker rm --force "test-container"
}
trap 'cleanup; exit 4;' ERR
args=( "$file" "cmd:unique" )
echo "$command: run-this-one ${args[*]}" >&2
run-this-one "${args[@]}"
;;
"cmd:unique")
"$file" "cmd:container"
;;
"cmd:container")
echo "sleep started..." >&2
sudo docker run --rm --name "test-container" alpine 
/bin/sh -c 'echo "sleep started inside..." && sleep 5 && echo "sleep ended inside..."'
echo "sleep ended..." >&2
;;
*)
echo -e "${RED}[error] invalid command: $command${NC}"
exit 1
;;
esac

如果我在另一个终端中运行/path/to/test cmd1(run-one(和/path/to/test cmd2(run-this-one(,它会按预期工作(cmd1进程停止并移除容器,cmd2进程成功运行(。

如果我在两个终端中运行/path/to/test cmd2,它也能按预期工作(第一个cmd2进程停止并移除容器,第二个cmd2进程成功运行(。

但不太好:在上述两种情况下,有时第二个进程在第一个进程删除容器之前会因错误而停止(这种情况可能会间歇性发生,可能是由于竞争条件(。

情况会变得更糟:如果我在两个终端中运行/path/to/test cmd1,两个命令都会失败,尽管第一个cmd1应该会成功运行(它会失败,因为第二个cmd1会在清理中删除容器(。

我试着把cleanup放在cmd:unique命令中(从其他两个地方删除(,以便只由运行的单个进程调用,以避免上述问题,但奇怪的是,cleanup没有在那里调用,即使陷阱也在那里定义。

为了简化您的问题,我将使用此命令来重现问题:

run-one docker run --rm -it alpine sleep 10

可以看出,无论是run-one还是run-this-one,其行为都绝对不是所期望的。

由于该命令创建了一个由docker管理的进程,我怀疑run-one工具集不是适合该作业的工具,因为docker容器不应该用pkill杀死,而是用docker kill杀死。

一个相对简单的解决方案是接受docker希望您杀死容器的方式,并创建适当处理docker的简短run-one脚本。

run-one-docker.sh

#!/usr/bin/env bash
if [[ "$#" -lt 2 ]]; then
echo "Usage:   ./run-one-docker.sh NAME COMMAND"
echo "Example: ./run-one-docker.sh temp alpine sleep 10"
exit 1
fi
name="$1"
command=("${@:2}")
container_is_running() {
[ "$( docker container inspect -f '{{.State.Running}}' "$1" 2> /dev/null)" == "true" ]
}
if container_is_running "$name"; then
echo "$name is already running, aborting"
exit 1
else
docker run --rm -it --name "$name" "${command[@]}"
fi

run-this-one-docker.sh

#!/usr/bin/env bash
if [[ "$#" -lt 2 ]]; then
echo "Usage:   ./run-this-one-docker.sh NAME COMMAND"
echo "Example: ./run-this-one-docker.sh temp alpine sleep 10"
exit 1
fi
name="$1"
command=("${@:2}")
container_is_running() {
[ "$( docker container inspect -f '{{.State.Running}}' "$1" 2> /dev/null)" == "true" ]
}
if container_is_running "$name"; then
echo "killing old $name"
docker kill "$name" > /dev/null
fi
docker run --rm -it --name "$name" "${command[@]}"

最新更新