【问题标题】:How to avoid race condition during docker overlay network creation?如何在 docker 覆盖网络创建期间避免竞争条件?
【发布时间】:2021-07-20 07:35:31
【问题描述】:

我有两台机器 HostA 和 HostB,并正确配置了 consul 和 docker 守护程序,以便我可以使用 docker network create -d overlay sharednet

我有一个TestScript.sh 来检查网络是否存在,如果不存在则创建网络。这个脚本在 HostA 和 HostB 上都可用。我在 A 上也有一个 MasterScript.sh,它基本上只是在每台机器上调用 TestScript.sh。在我运行MasterScript.sh 之后,我看到了一个令人惊讶的结果,创建了两个同名的网络!!!这可以说是一个 docker daemon 同步问题。

[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。那么问题来了,我是否有可能在这个限制下解决这个竞态条件?

【问题讨论】:

  • 我完全同意你的期望;这看起来像一个错误。为什么首先检查所有主机上的网络?在一台主机上做这件事还不够吗?
  • @morxa 在每台机器上,我都有一份TestScript.sh 的副本,用于管理本地 docker 容器并将它们附加到适当的网络。如果网络不存在,脚本只会创建网络并将容器附加到网络。由于TestScript.sh 在本质上是完全分布式的,并且设计上无法相互通信,因此 HostA 上的 TestScript.sh 不知道 HostB 上的 TestScript.sh 是否发出了创建命令。因此我必须依靠 docker daemon 来同步我的网络创建命令。
  • 您使用的是哪个 docker 版本?
  • @morxa docker 1.10.2 我安装了get.docker.com 脚本,所以我认为这是最新的稳定版本
  • 有趣,现在我看到了同样的问题。你提交错误报告了吗?

标签: docker docker-networking


【解决方案1】:

此问题已报告给 Docker Github,目前正在https://github.com/docker/docker/issues/20648 下进行跟踪

【讨论】:

    【解决方案2】:

    这个问题仍然没有解决,但我很容易使用run-one 命令避免它(而不是run command,它变成了run-one run command,如果命令仍在运行则返回错误)。

    (您可以通过which run-one 验证run-one 命令是否可用)

    步骤:

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

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

    #!/bin/bash
    set -eou pipefail
    
    RED='\033[0;31m'
    NC='\033[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="$1"
    
            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的网络(3个进程中有2个因run-one命令而报错,只有一个创建了网络)。

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

    也就是说,这很容易实现,我认为这不应该被标记为 hack,尽管 IMO 应该在 docker 引擎端(可能在 API 中)提供适当的解决方案。

    使用docker-compose 很难做到这一点,除非您从脚本运行它,您可以轻松更改并且事先知道网络的名称。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-30
      • 2010-09-25
      • 1970-01-01
      相关资源
      最近更新 更多