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
我有一个 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