Docker 网状网络和 DNSRR 的群网络延迟

Docker swarm network latency with mesh and DNSRR

我有一个 3 节点 docker 群。

部署的一个堆栈是一个具有 3 个副本的数据库集群。 (MariaDB 加莱拉)

部署的另一个堆栈是具有 2 个副本的 Web 应用程序。

Web 应用程序如下所示:

version: '3'

networks:
  web:
    external: true
  galera_network:
    external: true

services:
  application:
    image: webapp:latest
    networks:
      - galera_network
      - web
    environment:
      DB_HOST: galera_node
    deploy:
      replicas: 2

FWIW,web 网络是 traefik 所连接的。

这里的问题是 galera_node(用于 webapp 的数据库主机)解决了一个 VIP,它最终利用了 swarm 的网状路由(据我所知)并且在网状路由时增加了额外的延迟最终通过 WAN 而不是解析到部署在同一物理主机上的 galera_node 容器。

我发现的另一个选项是使用 tasks.galera_node,但这似乎对 3 个 galera 集群容器使用 DNSRR。所以 33% 的时间,事情又好又快……但其余时间,我添加了不必要的延迟。

这两种行为看起来被记录为我们对不同 endpoint_mode 选项的期望。参考:Docker endpoint_mode

为了说明延迟,请注意从 webapp 容器中进行 ping 操作时: 注意为每个 ping 解析的 IP 地址以及响应时间。

### hitting VIP that "masks" the fact that there is extra latency 
### behind it depending on where the mesh routing sends the traffic.

root@294114cb14e6:/var/www/html# ping galera_node
PING galera_node (10.0.4.16): 56 data bytes
64 bytes from 10.0.4.16: icmp_seq=0 ttl=64 time=0.520 ms
64 bytes from 10.0.4.16: icmp_seq=1 ttl=64 time=0.201 ms
64 bytes from 10.0.4.16: icmp_seq=2 ttl=64 time=0.153 ms
--- galera_node ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.153/0.291/0.520/0.163 ms

### hitting DNSRR that resolves to worst latency server

root@294114cb14e6:/var/www/html# ping tasks.galera_node
PING tasks.galera_node (10.0.4.241): 56 data bytes
64 bytes from 10.0.4.241: icmp_seq=0 ttl=64 time=60.736 ms
64 bytes from 10.0.4.241: icmp_seq=1 ttl=64 time=60.573 ms
--- tasks.galera_node ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max/stddev = 60.573/60.654/60.736/0.082 ms

### hitting DNSRR that resolves to local galera_node container

root@294114cb14e6:/var/www/html# ping tasks.galera_node
PING tasks.galera_node (10.0.4.242): 56 data bytes
64 bytes from 10.0.4.242: icmp_seq=0 ttl=64 time=0.133 ms
64 bytes from 10.0.4.242: icmp_seq=1 ttl=64 time=0.117 ms
--- tasks.galera_node ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.117/0.125/0.133/0.000 ms

### hitting DNSRR that resolves to other "still too much" latency server

root@294114cb14e6:/var/www/html# ping tasks.galera_node
PING tasks.galera_node (10.0.4.152): 56 data bytes
64 bytes from 10.0.4.152: icmp_seq=0 ttl=64 time=28.218 ms
64 bytes from 10.0.4.152: icmp_seq=1 ttl=64 time=40.912 ms
64 bytes from 10.0.4.152: icmp_seq=2 ttl=64 time=26.293 ms
--- tasks.galera_node ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max/stddev = 26.293/31.808/40.912/6.486 ms

我能够获得绕过延迟的良好性能的唯一方法是对本地容器的 IP 地址进行硬编码,但这显然不是一个长期的解决方案,因为容器应该被视为短暂的东西。

我完全明白,由于这种延迟,我可能需要重新考虑我的地理节点位置,并且我可能还可以做一些其他性能调整的事情。不过,似乎应该有一种方法可以强制执行我想要的行为。

当本地容器可用于为给定请求提供服务时,我基本上想绕过 DNSRR 和 VIP/mesh 路由行为。

所以问题是:

How can I have each replica of my webapp only hit the local swarm host's galera container without hard coding that container's IP address?

如果其他人正在与此类问题作斗争,我想 post 一个解决方案(尽管我不一定将其称为实际问题的 "answer")更多比我真正满意的解决方法。

在我的 webapp 中,我可以使用 galera_node 作为我的数据库主机,它解析为我上面提到的网状路由 VIP。无论如何,这都为我提供了功能,所以如果我的解决方法被绊倒,我知道我的连接仍然完好无损。

我编写了一个小 bash 脚本,我可以将其作为 cron 作业调用并提供我想要的结果。它可用于源于同一问题的其他用例。

它接受三个参数:

  • </code> = 数据库容器名称</li> <li><code> = 数据库网络名称
  • </code> = webapp 容器名称</li> </ul> <p>脚本查找容器名称,找到其指定网络的 IP 地址,然后将该容器名称和 IP 地址添加到 webapp 容器的 <code>/etc/hosts 文件中。

    这是有效的,因为容器名称也是 galera_node(在我的例子中),所以将它添加到主机文件只是覆盖 docker 解析为 VIP 的主机名。

    如前所述,我不喜欢这个,但它似乎确实适合我的目的,它避免了我必须对 IP 地址进行硬编码和手动维护它们。我确信有一些场景需要对脚本进行调整,但这是一个功能性的起点。

    我的脚本:update_container_hosts.sh

    #!/bin/bash
    HOST_NAME=
    HOST_NETWORK=
    CONTAINER_NAME=
    
    FMT="{{(index (index .NetworkSettings.Networks \"$HOST_NETWORK\") ).IPAddress}}"
    CONTAINERS=`docker ps  | grep $CONTAINER_NAME | cut -d" " -f1`
    HOST_ID=`docker ps | grep $HOST_NAME | cut -d" " -f1`
    HOST_IP=$(docker inspect $HOST_ID --format="$FMT")
    
    echo --- containers ---
    echo $CONTAINERS
    echo ------------------
    echo host: $HOST_NAME
    echo network: $HOST_NETWORK
    echo ip: $HOST_IP
    echo ------------------
    
    for c in $CONTAINERS;
    do
        if [ "$HOST_IP" != "" ]
        then
            docker cp $c:/etc/hosts /tmp/hosts.tmp
            IP_COUNT=`cat /tmp/hosts.tmp | grep $HOST_IP | wc -l`
            rm /tmp/hosts.tmp
            if [ "$IP_COUNT" = "0" ]
            then
                    docker exec  $c /bin/sh -c "echo $HOST_IP $HOST_NAME >> /etc/hosts"
                    echo "$c: Added entry to container hosts file."
            else
                    echo "$c: Entry already exists in container hosts file.  Skipping."
            fi
        fi
    done