Link: http://blog.j2gg0s.com/2023/04/12/Kubernetes-%E7%BD%91%E7%BB%9C-kube-proxy-%E5%92%8C-ipvs.html
Dustin Specker 有个Container Networking 系列的博客, 手动模拟了 kube-proxy 在 iptables/ipvs 下的相关逻辑, 非常有助理解. 我们在这儿也从零走一遍 ipvs 相关的逻辑.
整个流程的大部分命令可以在 Makefile 中找到.
每个 Pod 都拥有自己独立的网络命名空间, 进而实现网络隔离. 即使同一 Node 上的不同 Pod 之间也无法直接访问, Pod 也无法直接访问 Node 的网络.
ip netns add foo
ip netns exec foo ip link set dev lo up
ip link add dev cni0 type bridge
ip addr add 172.22.0.1/24 dev cni0
ip link set dev cni0 up
ip link add dev veth_foo type veth peer name veth_foo_eth0
ip link set dev veth_foo master cni0
ip link set dev veth_foo up
ip link set dev veth_foo_eth0 netns foo
ip netns exec foo ip link set dev veth_foo_eth0 up
ip netns exec foo ip addr add 172.22.0.2/24 dev veth_foo_eth0
为了测试, 我们在 foo 中启动一个 http 服务.
~ ip netns exec foo python3 -m http.server -d foo 8080
Serving HTTP on 0.0.0.0 port 8080 ( http://0.0.0.0:8080/) ...
这时候, 我们就可以直接访问命名空间 foo 中的 http 服务.
~ curl 172.22.0.2:8080 -s | grep im
<li><a href="im-foo">im-foo</a></li>
ip netns add bar
ip netns exec bar ip link set dev lo up
ip link add dev veth_bar type veth peer name veth_bar_eth0
ip link set dev veth_bar master cni0
ip link set dev veth_bar up
ip link set dev veth_bar_eth0 netns bar
ip netns exec bar ip link set dev veth_bar_eth0 up
ip netns exec bar ip addr add 172.22.0.3/24 dev veth_bar_eth0
在测试 foo 和 bar 的互通之间, 我们需要先允许通过 cni0 来转发流量.
iptables -t filter -A FORWARD --in-interface cni0 -j ACCEPT
iptables -t filter -A FORWARD --out-interface cni0 -j ACCEPT
ip netns exec bar curl 172.22.0.2:8080 -s | grep im
<li><a href="im-foo">im-foo</a></li>
我们可以简单将 ipvs 理解为 L4 的负载均衡, 通过 ipvsadm 可以创建一个虚拟的 IP 地址, 并将相关流量转发给 foo.
ipvsadm --add-service --tcp-service 172.23.0.1:8080 --scheduler rr
ipvsadm --add-server --tcp-service 172.23.0.1:8080 --real-server 172.22.0.2:8080 --masquerading
在访问 172.23.0.1:8080 之前, 我们需要将 cni0 指定为 foo 的默认网关.
ip netns exec foo ip route add default via 172.22.0.1
~ curl 172.23.0.1:8080 -s | grep im
<li><a href="im-foo">im-foo</a></li>
如果将 bar 也增加到 172.23.0.1:8080 的后端负载, 则可以免费体验 ipvs 的负载均衡功能.
ipvsadm --add-server --tcp-service 172.23.0.1:8080 --real-server 172.22.0.3:8080 --masquerading
ip netns exec bar ip route ad default via 172.22.0.1
➜ curl 172.23.0.1:8080 -s | grep im
<li><a href="im-foo">im-foo</a></li>
➜ curl 172.23.0.1:8080 -s | grep im
<li><a href="im-bar">im-bar</a></li>
➜ curl 172.23.0.1:8080 -s | grep im
<li><a href="im-foo">im-foo</a></li>
➜ curl 172.23.0.1:8080 -s | grep im
<li><a href="im-bar">im-bar</a></li>
k8s 中一个很经典的场景. 为了便于模拟, 我们先将 bar 中 172.23.0.1 的后端服务中踢出, 仅保留 foo.
ipvsadm --delete-server --tcp-service 172.23.0.1:8080 --real-server 172.22.0.3:8080
为了在 bar 中访问 172.23.0.1, 我们需要虚构对应的设备.
ip link add dev ipvs0 type dummy
ip addr add 172.23.0.1/32 dev ipvs0
ip netns exec bar curl 172.23.0.1:8080 -s | grep im
<li><a href="im-foo">im-foo</a></li>
但是你会发现, 我们无法直接在 foo 通过 ipvs 来访问 foo.
ip netns exec foo curl 172.23.0.1:8080 --connect-timeout 1
curl: (28) Connection timeout after 1001 ms
我们需要做三件事来解决这个问题:
ip link set cni0 promisc on
iptables -t nat -A POSTROUTING -s 172.22.0.0/24 -j MASQUERADE
sysctl net.ipv4.vs.conntrack=1 --write
~ ip netns exec foo curl 172.23.0.1:8080 -s | grep im
<li><a href="im-foo">im-foo</a></li>
MadHatter 在 severfault 上的这个回答非常好的解释了 hairpin 和这次 SNAT 的原因.
有时间的话, 我们后续可以找个实际的集群, 来验证下上述的理解.
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.