Sidecar 是怎么把应用的流量接过去的?iptables 的 REDIRECT 规则做了什么?sidecar 怎么知道应用原本要连哪里?为什么必须放行 sidecar 自己的流量?排除端口怎么做?入站流量怎么劫持?这篇在 Linux 容器里用真实的 iptables 和一个 50 行的 sidecar 逐项演示。
(a)没有规则:{'ok': False, 'err': 'URLError', 'ms': 2018}
(b)装上的 nat 表规则(iptables -t nat -S):
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N MESH_OUT
-A OUTPUT -p tcp -j MESH_OUT
-A MESH_OUT -m owner --uid-owner 1337 -j RETURN
-A MESH_OUT -d 127.0.0.1/32 -j RETURN
-A MESH_OUT -p tcp -j REDIRECT --to-ports 15001
应用请求同一个地址:{'ok': True, 'body': 'hello from backend 9000', 'ms': 3}
sidecar 的日志:
accept #1 from 172.17.0.2:33650 orig_dst=10.99.0.5:8080 -> 127.0.0.1:9000
MESH_OUT 链的计数器(pkts bytes target … 一行一条规则,iptables -v -n -x):
pkts bytes target prot opt in out source destination
1 60 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0 owner UID match 1337
0 0 RETURN all -- * * 0.0.0.0/0 127.0.0.1
1 60 REDIRECT tcp -- * * 0.0.0.0/0 0.0.0.0/0 redir ports 15001
OUTPUT 链上所有 TCP 先跳进 MESH_OUT;以 uid 1337(sidecar 自己)发出的和目的地是 127.0.0.1 的先放行(RETURN),其余的 REDIRECT 到 15001;应用连的明明是 10.99.0.5:8080,sidecar 看到的连接来自应用,靠 SO_ORIGINAL_DST 取回应用想连的地址。每条规则的计数都只有 1 个包、60 字节(正好是一个 TCP SYN 包),而不是整个请求的包数:nat 表只处理一条连接的第一个包,同一条连接后面的包按已经建立的转换记录走(这一点是我对 netfilter 的了解,计数器的数字与它一致,没有另外验证)OUTPUT 链上的 REDIRECT 规则把应用发出的 TCP 连接送到 sidecar,sidecar 用 SO_ORIGINAL_DST 取回原始目的地址。
应用的结果:{'ok': False, 'err': 'RemoteDisconnected', 'ms': 20}
sidecar 在 3 s 内接受的连接数:31(上限 30);它们的原始目的地址:['10.99.0.5:8080', '127.0.0.1:9000']
日志开头:
accept #1 from 172.17.0.2:33666 orig_dst=10.99.0.5:8080 -> 127.0.0.1:9000
accept #2 from 127.0.0.1:46012 orig_dst=127.0.0.1:9000 -> 127.0.0.1:9000
accept #3 from 127.0.0.1:46024 orig_dst=127.0.0.1:9000 -> 127.0.0.1:9000
accept #4 from 127.0.0.1:46038 orig_dst=127.0.0.1:9000 -> 127.0.0.1:9000
sidecar 转发时自己发出的连接也被 REDIRECT 回了 15001,变成新的一条连接又交给自己,循环往复;所以出站规则里必须让 sidecar 的流量(按 uid 或 gid 识别)绕过自己sidecar 自己发出的连接必须按 uid 放行,否则被重定向回自己形成循环。
(a)有排除规则:应用的结果 {'ok': True, 'body': 'hello from backend 3306', 'ms': 1},sidecar 接受的连接数 0
(b)去掉排除规则:应用的结果 {'ok': True, 'body': 'hello from backend 3306', 'ms': 9},sidecar 接受的连接数 1;日志 ['accept #1 from 172.17.0.2:59876 orig_dst=172.17.0.2:3306 -> 172.17.0.2:3306']
被排除的端口的连接不经过 sidecar(sidecar 的计数不变);数据库、监控等不想经过代理的流量用这个办法绕开在 REDIRECT 之前对指定端口 RETURN,这部分连接就不经过 sidecar。
访问 172.17.0.2:8080 的结果:'hello from backend 8080';访问 172.17.0.2:3306 的结果:'hello from backend 3306'
入站 sidecar 的日志:
accept #1 from 172.17.0.3:49512 orig_dst=172.17.0.2:8080 -> 127.0.0.1:8080
MESH_IN 链的计数器:
1 60 REDIRECT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 redir ports 15006
8080 的请求先到入站 sidecar,sidecar 用 SO_ORIGINAL_DST 知道它本来要去哪个地址,再转发给应用(这里转发到 127.0.0.1:8080);3306 没有入站规则,直接到应用
ERRORS {}PREROUTING 链把进入的连接重定向到入站 sidecar,再转发给应用。