Docker

在特定 IP 地址上發布 Docker Swarm 服務

  • June 1, 2021

在 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 的 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 地址進行定制。

引用自:https://serverfault.com/questions/893647