如何在 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

步骤:

  1. 创建创建网络的脚本(它可以接受网络名称作为参数,如 docker network create "")。
  2. 通过使用 run-one 调用脚本来创建网络(无论应在何处创建)以确保它不会为同一网络执行两次 (run /path/to/script network-name)。
  3. ?
  4. 盈利!

您可以在下面的(演示)脚本中看到这种方法的实际应用:

#!/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

然后:

  1. 运行 /path/to/script clean 删除未使用的网络(确保在开发环境中 运行 此脚本)。
  2. 运行 /path/to/script test1 并看到有 3 个网络名为 my-network.
  3. 运行 /path/to/script clean 再一次。
  4. 运行 /path/to/script test2 并看到只有 1 个名为 my-network 的网络(由于 run-one 命令,3 个进程中的 2 个以错误结束,只有一个创建网络)。

脚本添加了另一个抽象层(如果您打算使用网络选项可能会增加复杂性)这一事实,除了您必须创建脚本并引用它之外,使得该解决方案在以下位置进行了描述最好作为解决方法。

也就是说,这很容易实现,我认为这不应该被标记为 hack,尽管正确的解决方案 IMO 应该在 docker 引擎方面(可能在 API).

使用 docker-compose 难度可能不太容易实现,除非您 运行 从可以轻松更改的脚本中实现它并且您事先知道网络的名称。