在 Centos 7.4 上,我正在设置一个集群,我想在其中运行多个路由器,这些路由器都可通过端口 80/443 访问。
目的是在单个集群上对称地托管多个环境(测试/暂存……)。
我正在使用 Docker 17.12.0-ce 和 Traefik v1.4.6 作为路由器。
基本思路是每个环境都有一个虚拟 IP 地址,并仅在该地址上发布 Traefik 端口。这在 Docker Swarm 中是无法实现的,因此我不得不让 Traefik 实例监听端口 81/82 等,并以某种方式将流量从 VIP:80 引至 :81/:82。
整个集群管理器中所有环境的虚拟 IP 地址均由 Keepalived 处理。
Traefik 的相关 docker 服务配置:
"Ports": [
{
"Protocol": "tcp",
"TargetPort": 80,
"PublishedPort": 81,
"PublishMode": "ingress"
},
# netstat -anp|grep 81
tcp6 7 0 :::81 :::* LISTEN 4578/dockerd
设置firewalld以允许到端口80、81、82等的流量
直接通过 VIP 上的 81 端口访问 Traefik 公开的后端服务即可。
如果 VIP 上未正确配置任何内容,则访问其 80 端口会导致连接被拒绝
Traefik docker 实例正在与我用于以下测试的同一主机上运行。
我首先尝试了基本的 DNAT:
firewall-cmd --add-forward-port=port=80:proto=tcp:toport=81:toaddr=127.0.0.1
这导致超时,服务器上似乎没有建立连接,并且 tcpdump 告诉我 SYN 被忽略
接下来我尝试使用更具体的 DNAT:
firewall-cmd --add-rich-rule='rule family=ipv4 forward-port port=80 protocol=tcp to-port=81 to-addr=127.0.0.1'
结果相同。
我发现戈登它似乎是为我的用例量身定制的,并且配备了
服务:
{
"host": "<VIP>",
"port": 80,
"protocol": "tcp",
"method": "rr",
"persistent": true,
"flags": "sh-port"
}
该服务的后端:
{
"host": "<VIP>",
"port": 81,
"method": "nat",
"weight": 100,
"pulse": {
"type": "tcp",
"interval": "30s",
"args": null
}
}
我使用 ipvsadm 验证了设置并且它看起来是正确的:
# ipvsadm -l -n
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP <VIP>:80 rr (flag-2)
-> <VIP>:81 Masq 100 0 0
在这种情况下,虽然服务器上没有出现任何连接,但 tcpdump 显示正在交换 SYN、SYNACK 和 ACK,然后是 HTTP 请求及其 ACK。
没有其他流量通过,请求最终在客户端超时。ipvsadm
将连接注册为活动连接。
如果我设置 HAProxy 来监听 VIP:80 并通过 HTTP 将请求代理到 127.0.0.1:81,那么一切就都可以正常工作,但我想避免这样做,因为它要求所有数据都通过 HAProxy,浪费资源并需要本地配置。
我没有什么主意,也不知道如何进一步排除故障。
编辑以澄清。我的问题是:
是否可以在不使用 HAProxy 或其他仅将数据泵送到真实路由器(Traefik)的进程的情况下将流量从 VIP:80 路由到 :81/:82 等?
答案1
我们需要在相同的端口上发布单独的 docker swarm 服务,但使用不同的特定 IP 地址。以下是我们的实现方式。
Docker 会将规则添加到 nat 表的 DOCKER-INGRESS 链中,用于每个已发布的端口。它添加的规则不针对特定 IP,因此通常任何已发布的端口都可以在所有主机 IP 地址上访问。以下是 Docker 将为在端口 80 上发布的服务添加的规则示例:
iptables -t nat -A DOCKER-INGRESS -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.18.0.2:80
(您可以通过运行来查看这些内容iptables-save -t nat | grep DOCKER-INGRESS
)。
我们的解决方案是在不同的端口上发布我们的服务,并使用拦截 dockerd 的 iptables 命令的脚本来重写它们,以便它们匹配正确的 IP 地址和公共端口对。
例如:
- 服务 #1 发布在端口 1080 上,但应该监听 1.2.3.4:80
- 服务 #2 发布在端口 1180 上,但应该监听 1.2.3.5:80
然后我们相应地配置我们的脚本:
# cat /usr/local/sbin/iptables
#!/bin/bash
REGEX_INGRESS="^(.*DOCKER-INGRESS -p tcp) (--dport [0-9]+) (-j DNAT --to-destination .*)"
IPTABLES=/usr/sbin/iptables
SRV_1_IP=1.2.3.4
SRV_2_IP=1.2.3.5
ipt() {
echo "EXECUTING: $@" >>/tmp/iptables.log
$IPTABLES "$@"
}
if [[ "$*" =~ $REGEX_INGRESS ]]; then
START=${BASH_REMATCH[1]}
PORT=${BASH_REMATCH[2]}
END=${BASH_REMATCH[3]}
echo "REQUESTED: $@" >>/tmp/iptables.log
case "$PORT" in
'--dport 1080') ipt $START --dport 80 -d $SRV_1_IP $END; exit $?; ;;
'--dport 2080') ipt $START --dport 80 -d $SRV_2_IP $END; exit $?; ;;
*) ipt "$@"; exit $?; ;;
esac
fi
echo "PASSING-THROUGH: $@" >>/tmp/iptables.log
$IPTABLES "$@"
注意:脚本必须安装在发行版的 iptables 命令之前的 dockerd PATH 中。在 Debian Buster 上,iptables 安装到/usr/sbin/iptables
,而 dockerd 的 PATH 位于/usr/local/sbin
之前/usr/sbin
,因此将脚本安装在 是合理的/usr/local/sbin/iptables
。(您可以通过运行 来检查 dockerd 的 PATH cat /proc/$(pgrep dockerd)/environ | tr '\0' '\012' | grep ^PATH
)。
现在,当这些docker服务启动的时候,iptables规则会被重写如下:
iptables -t nat -A DOCKER-INGRESS -d 1.2.3.4/32 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.18.0.2:1080
iptables -t nat -A DOCKER-INGRESS -d 1.2.3.5/32 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.18.0.2:2080
结果是http://1.2.3.4/转到服务 #1,而请求http://1.2.3.5/前往服务 #2。
该脚本可以根据您的需要进行定制和扩展,并且必须安装在您将定向请求的所有节点上,并根据该节点的公共 IP 地址进行定制。
答案2
第一,你能如果你有能力在真实网络中添加 IP,则在主机上使用多个 IP。这做在 Linux 上的 Swarm 中工作。请参阅macvlan 文档并在 Google 上搜索“macvlan swarm”。
第二,您正在使用覆盖和 Swarm 的入口网络,对吗?
第三,大多数人只使用 Traefik(或者我最喜欢的http://proxy.dockerflow.com) 监听 80/443,并根据主机头路由到 Swarm 中的正确服务/堆栈。就像 Florin 问的那样,你为什么不尝试一下呢?