如何在 docker 覆盖网络创建期间避免竞争条件?
How to avoid race condition during docker overlay network creation?
我有两台机器 HostA 和 HostB,正确配置了 consul 和 docker 守护进程,这样我就可以使用 docker network create -d overlay sharednet
我有一个 TestScript.sh
来检查网络是否存在,如果不存在则创建网络。并且这个脚本在 HostA 和 HostB 上都可用。我还有一个仅在 A 上的 MasterScript.sh
,它基本上只是在每台机器上调用 TestScript.sh
。在我 运行 我的 MasterScript.sh
之后,我看到了一个令人惊讶的结果,创建了两个同名的网络!!!这可以说是 docker 守护进程同步问题。
[HostA]# docker network ls
NETWORK ID NAME DRIVER
ad492bba9efa sharednet overlay
ba53d4e7b739 sharednet overlay
[HostB]# docker network ls
NETWORK ID NAME DRIVER
ad492bba9efa sharednet overlay
ba53d4e7b739 sharednet overlay
预期的行为是,当我在 HostA 上创建网络 testnw
时,在 HostB 上我应该会看到类似这样的内容
[HostB]# docker network ls
68994f95cd67 testnw overlay
[HostB]# docker network create -d overlay testnw
Error response from daemon: network with name testnw already exists
由于某些限制,我无法修改 MasterScript.sh
,但我可以修改我的 TestScript.sh
。那么问题来了,我有没有可能在这个限制下解决这个race condition?
此问题已报告给 Docker Github,目前正在 https://github.com/docker/docker/issues/20648
下进行跟踪
这个问题仍然没有解决,但我很容易使用 run-one
命令(而不是 run command
,它变成了 run-one run command
和 return 如果命令仍然是 运行ning).
,则会出错
(您可以验证 run-one
命令是否适用于 which run-one
)
步骤:
- 创建创建网络的脚本(它可以接受网络名称作为参数,如
docker network create ""
)。
- 通过使用
run-one
调用脚本来创建网络(无论应在何处创建)以确保它不会为同一网络执行两次 (run /path/to/script network-name
)。
- ?
- 盈利!
您可以在下面的(演示)脚本中看到这种方法的实际应用:
#!/bin/bash
set -eou pipefail
RED='3[0;31m'
NC='3[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
"clean")
sudo docker network prune -f
;;
"test1")
run-one "$file" "test:concurrent" "test:network"
;;
"test2")
run-one "$file" "test:concurrent" "test:network:unique"
;;
"test:concurrent")
echo "===========before==========="
sudo docker network ls
echo "============================"
cmd=""
pids=()
for i in $(seq 1 3); do
"$file" "$cmd" &
pids["${i}"]=$!
done
idx=0
for pid in "${pids[@]}"; do
wait "$pid" && status="$?" || status="$?"
idx=$((idx + 1))
if [ "$status" != '0' ]; then
echo "error in process $pid (#$idx)"
fi
done
echo "===========after============"
sudo docker network ls
echo "============================"
;;
"test:network:unique")
run-one "$file" "test:network"
;;
"test:network")
sudo docker network create "my-network"
;;
*)
echo -e "${RED}[error] invalid command: $command${NC}"
exit 1
;;
esac
然后:
- 运行
/path/to/script clean
删除未使用的网络(确保在开发环境中 运行 此脚本)。
- 运行
/path/to/script test1
并看到有 3 个网络名为 my-network
.
- 运行
/path/to/script clean
再一次。
- 运行
/path/to/script test2
并看到只有 1 个名为 my-network
的网络(由于 run-one
命令,3 个进程中的 2 个以错误结束,只有一个创建网络)。
脚本添加了另一个抽象层(如果您打算使用网络选项可能会增加复杂性)这一事实,除了您必须创建脚本并引用它之外,使得该解决方案在以下位置进行了描述最好作为解决方法。
也就是说,这很容易实现,我认为这不应该被标记为 hack,尽管正确的解决方案 IMO 应该在 docker 引擎方面(可能在 API).
使用 docker-compose
难度可能不太容易实现,除非您 运行 从可以轻松更改的脚本中实现它并且您事先知道网络的名称。
我有两台机器 HostA 和 HostB,正确配置了 consul 和 docker 守护进程,这样我就可以使用 docker network create -d overlay sharednet
我有一个 TestScript.sh
来检查网络是否存在,如果不存在则创建网络。并且这个脚本在 HostA 和 HostB 上都可用。我还有一个仅在 A 上的 MasterScript.sh
,它基本上只是在每台机器上调用 TestScript.sh
。在我 运行 我的 MasterScript.sh
之后,我看到了一个令人惊讶的结果,创建了两个同名的网络!!!这可以说是 docker 守护进程同步问题。
[HostA]# docker network ls
NETWORK ID NAME DRIVER
ad492bba9efa sharednet overlay
ba53d4e7b739 sharednet overlay
[HostB]# docker network ls
NETWORK ID NAME DRIVER
ad492bba9efa sharednet overlay
ba53d4e7b739 sharednet overlay
预期的行为是,当我在 HostA 上创建网络 testnw
时,在 HostB 上我应该会看到类似这样的内容
[HostB]# docker network ls
68994f95cd67 testnw overlay
[HostB]# docker network create -d overlay testnw
Error response from daemon: network with name testnw already exists
由于某些限制,我无法修改 MasterScript.sh
,但我可以修改我的 TestScript.sh
。那么问题来了,我有没有可能在这个限制下解决这个race condition?
此问题已报告给 Docker Github,目前正在 https://github.com/docker/docker/issues/20648
下进行跟踪这个问题仍然没有解决,但我很容易使用 run-one
命令(而不是 run command
,它变成了 run-one run command
和 return 如果命令仍然是 运行ning).
(您可以验证 run-one
命令是否适用于 which run-one
)
步骤:
- 创建创建网络的脚本(它可以接受网络名称作为参数,如
docker network create ""
)。 - 通过使用
run-one
调用脚本来创建网络(无论应在何处创建)以确保它不会为同一网络执行两次 (run /path/to/script network-name
)。 - ?
- 盈利!
您可以在下面的(演示)脚本中看到这种方法的实际应用:
#!/bin/bash
set -eou pipefail
RED='3[0;31m'
NC='3[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
"clean")
sudo docker network prune -f
;;
"test1")
run-one "$file" "test:concurrent" "test:network"
;;
"test2")
run-one "$file" "test:concurrent" "test:network:unique"
;;
"test:concurrent")
echo "===========before==========="
sudo docker network ls
echo "============================"
cmd=""
pids=()
for i in $(seq 1 3); do
"$file" "$cmd" &
pids["${i}"]=$!
done
idx=0
for pid in "${pids[@]}"; do
wait "$pid" && status="$?" || status="$?"
idx=$((idx + 1))
if [ "$status" != '0' ]; then
echo "error in process $pid (#$idx)"
fi
done
echo "===========after============"
sudo docker network ls
echo "============================"
;;
"test:network:unique")
run-one "$file" "test:network"
;;
"test:network")
sudo docker network create "my-network"
;;
*)
echo -e "${RED}[error] invalid command: $command${NC}"
exit 1
;;
esac
然后:
- 运行
/path/to/script clean
删除未使用的网络(确保在开发环境中 运行 此脚本)。 - 运行
/path/to/script test1
并看到有 3 个网络名为my-network
. - 运行
/path/to/script clean
再一次。 - 运行
/path/to/script test2
并看到只有 1 个名为my-network
的网络(由于run-one
命令,3 个进程中的 2 个以错误结束,只有一个创建网络)。
脚本添加了另一个抽象层(如果您打算使用网络选项可能会增加复杂性)这一事实,除了您必须创建脚本并引用它之外,使得该解决方案在以下位置进行了描述最好作为解决方法。
也就是说,这很容易实现,我认为这不应该被标记为 hack,尽管正确的解决方案 IMO 应该在 docker 引擎方面(可能在 API).
使用 docker-compose
难度可能不太容易实现,除非您 运行 从可以轻松更改的脚本中实现它并且您事先知道网络的名称。