在特定 IP 地址上發布 Docker Swarm 服務
在 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。
swarm manager 中所有環境的虛擬 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'
結果相同。
我發現了似乎為我的案例量身定制的GORB ,並為其配置了
服務:
{ "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,浪費資源並且需要本地配置。
我沒有想法,我不知道如何進一步排除故障。
編輯澄清。我的問題是:
是否可以將流量從 VIP:80 路由到 :81/:82 等而不使用 HAProxy 或其他簡單地將數據泵送到真實路由器 (Traefik) 的程序?
我們需要在相同的埠上發佈單獨的 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 的 PATHcat /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 地址進行定制。