为了支持Kubernetes集群的Pod的水平扩展和高可用性,Kubernetes抽象出Service的概念。Service是对一组Pod的抽象,它会根据访问策略(如负载均衡策略)来访问这组Pod。
我们这里将重点介绍Kubernetes的Service的其中ClusterIP类型。
Service在很多情况下只是一个概念,而真正将Service的作用落实的是背后Kube-proxy的服务进程,只有理解了Kube-proxy的原理和机制,我们才能真正理解Service背后实现的逻辑。
Service的ClusterIP的概念是Kube-proxy通过Linux本身的Iptables的NAT转换实现,Kuber-proxy的运行过程中动态创建与Service相关的Iptables规则。
这里我们展开一小部分的内容进行展开,目标是实现Service负载均衡的Iptables的规则,我们就一个简单的Service为例子:
apiVersion: v1
kind: Service
metadata:
name: http-server
spec:
clusterIP: 10.100.100.100
selector:
component: http-server
ports:
- protocol: TCP
port: 8080
targetPort: 8080
Kubernetes为每个Pod创建一个Namespace,在Linux的Namespace内可以有自己独立的路由表及独立的Iptables/Netfilter设置来提供包转发、NAT及IP包过滤等功能。
我们为了简化模型,在独立的namespace里运行 go语言写的http-server来模拟Kubernetes中的”Pod”
Linux系统具有路由转发功能,需要配置一个Linux的内核参数net.ipv4.ip_forward
。这个参数指定了Linux系统当前对路由转发功能的支持情况;其值为0时表示禁止进行IP转发;如果是1,则说明IP转发功能已经打开。
sudo sysctl --write net.ipv4.ip_forward=1
现在我们需要
创建两个namespace
: netns_011
和netns_021
br1
并分配ip 10.0.0.1
8.8.8.8
veth_ns_011
和veth_ns_021
绑定连接网桥br1
10.0.0.11
给 veth_011
10.0.0.21
给 veth_021
namespace
中设置默认路由sudo ip netns add netns_011
sudo ip netns add netns_0212
sudo ip link add dev br1 type bridge
sudo ip address add 10.0.0.1/24 dev br1
sudo ip link set br1 up
echo '8.8.8.8' | sudo tee -a /etc/netns/netns_011/resolv.conf
echo '8.8.8.8' | sudo tee -a /etc/netns/netns_021/resolv.conf
#新生成的namespace默认只有回环lo设备(默认是down状态)
sudo ip netns exec netns_011 ip link set dev lo up
sudo ip netns exec netns_021 ip link set dev lo up
sudo ip link add dev veth_011 type veth peer name veth_ns_011
sudo ip link add dev veth_021 type veth peer name veth_ns_021
sudo ip link set dev veth_011 master br1
sudo ip link set dev veth_021 master br1
sudo ip link set dev veth_ns_011 netns netns_011
sudo ip link set dev veth_ns_021 netns netns_021
sudo ip nents exec netns_011 ip link set veth_ns_011 name eth0
sudo ip nents exec netns_021 ip link set veth_ns_021 name eth0
sudo ip netns exec netns_011 ip link set dev eth0 up
sudo ip netns exec netns_021 ip link set dev eth0 up
sudo ip netns exec netns_011 ip address add 10.0.0.11/24 dev eth0
sudo ip netns exec netns_021 ip address add 10.0.0.21/24 dev eth0
sudo ip netns exec netns_011 ip route add default via 10.0.0.1
sudo ip netns exec netns_021 ip route add default via 10.0.0.1
创建Iptables规则以允许流量进出br1
设备
如果本身的Iptables规则比较严格,需要新增以下两条规则
sudo iptables --table filter --append FORWARD --in-interface br1 --jump ACCEPT
sudo iptables --table filter --append FORWARD --out-interface br1 --jump ACCEPT
创建另一条Iptables的nat表的POSTROUTING规则来伪装源地址来自10.0.0.0/24的请求:
sudo iptables --table nat --append POSTROUTING --source 10.0.0.0/24 --jump MASQUERADE
接下来就是分别在netns_011
和netns_021
的namespace下启动HTTP Server
sudo ip netns exec netns_011 ./http-server
sudo ip netns exec netns_021 ./http-server
环境如下图:
检查HTTP Server服务是否可以 正常访问
#host network namespace
curl 10.0.0.11:8080
curl 10.0.0.21:8080
# netns_011 network namespace
sudo ip netns exec netns_011 curl 10.0.0.21:8080
# netns_021 network namespace
sudo ip netns exec netns_021 curl 10.0.0.11:8080
结果:
⚡ root@raspberrypi ~ curl 10.0.0.11:8080
2020-09-02 10:47:01, you're running http-server on 10.0.0.11
⚡ root@raspberrypi ~ curl 10.0.0.21:8080
2020-09-02 10:47:09, you're running http-server on 10.0.0.21
⚡ root@raspberrypi ~ ip netns exec netns_011 curl 10.0.0.21:8080
2020-09-02 10:47:46, you're running http-server on 10.0.0.21
⚡ root@raspberrypi ~ ip netns exec netns_021 curl 10.0.0.11:8080
2020-09-02 10:47:54, you're running http-server on 10.0.0.11
当Kubernetes在创建Service时会同时默认创建ClusterIP给Service。从概念上讲,ClusterIP就是虚拟IP,在kube-proxy启用Iptables模式时,kube-proxy负责创建Iptables规则来处理来自Pod、外部请求。
通过以下命令在iptables的nat表新增一条新链HTTP-SERVICE
sudo iptables --table nat --new HTTP-SERVICE
接下来,我们需要在PREROUTING
和OUTPUT
链中添加规则跳转HTTP-SERVICE
链:
sudo iptables --table nat --append PREROUTING --jump HTTP-SERVICE
sudo iptables --table nat --append OUTPUT --jump HTTP-SERVICE
此时,我们可以在HTTP-SERVICE
链中创建一个规则来处理虚拟IP,我们的虚拟IP(模拟ClusterIP),10.100.100.100
,同时创建一个规则:
sudo iptables \
--tables nat \
--append HTTP-SERVICE \
--destination 10.100.100.100 \
--protocol tcp \
--match tcp \
--dport 8080 \
--jump DNAT \
--to-destination 10.0.0.11:8080
通过执行以下请求 虚拟IP:
⚡ root@raspberrypi ~ curl 10.100.100.100:8080
2020-09-03 03:38:48, you're running http-server on 10.0.0.11
⚡ root@raspberrypi ~ ip netns exec netns_011 curl 10.100.100.100:8080
^C
✘ ⚡ root@raspberrypi ~ ip netns exec netns_021 curl 10.100.100.100:8080
2020-09-03 04:01:06, you're running http-server on 10.0.0.11
可以看出来在netns_011
的namespace访问虚拟IP失败,这个是由于我们的请求离开netns_011
,其源地址10.0.0.11
,请求将发送给10.100.100.100
,这个期间iptables规则会进行DNAT转换10.100.100.100
至10.0.0.11
, 这个请求最终被定向到请求来源本身。
如果需要namespace的网络设备通过虚拟IP来访问自身,需要在桥接设备(网桥)上针对每个端口启用
hairpin
模式,有一种方式:可以在网桥上启用混杂模式,它将所有连接端口视为已启用hairpin
模式
# br1 桥接设备启用混杂模式
⚡ root@raspberrypi ~ ip link set br1 promisc on
⚡ root@raspberrypi ~ ip netns exec netns_011 curl 10.100.100.100:8080
2020-09-03 04:14:45, you're running http-server on 10.0.0.11
Kube-proxy会给每个Service使用哈希创建“KUBE-SVC-
KUBE_FIREWALL
内容如下,就是直接丢弃所有报文:
Chain KUBE-FIREWALL (2 references)
target prot opt source destination
DROP all -- anywhere anywhere /* kubernetes firewall for dropping marked packets */ mark match 0x8000/0x8000
ClusterIP类型的service链转发流程:
KUBE-SERVICE ----- KUBE-SVC-<hash> ----- KUBE-SEP-<hash>
sudo iptables --table nat --append PREROUTING --jump KUBE-SERVICE
sudo iptables --table nat --append OUTPUT --jump KUBE-SERVICE
sudo iptables \
--table nat \
--new KUBE-SERVICE
sudo iptables \
--table nat \
--append KUBE-SERVICE \
--destination 10.100.100.100 \
--protocol tcp \
--match comment \
--comment "default/http-server: cluster IP" \
--match tcp \
--dport 8080 \
--jump KUBE-SVC-HVYO5BEGEF5HC7MD7
sudo iptables \
--table nat \
--new KUBE-SVC-HVYO5BEGEF5HC7MD7
sudo iptables \
--table nat \
--append KUBE-SVC-HVYO5BEGEF5HC7MD7 \
--match comment \
--comment "default/http-server:" \
--jump KUBE-SEP-ESZGVIJET5GN2KKU
sudo iptables \
--table nat \
--new KUBE-SEP-ESZGVIJET5GN2KKU
sudo iptables \
--table nat \
--append KUBE-SEP-ESZGVIJET5GN2KKU \
--protocol tcp \
--match comment \
--comment "default/http-server:" \
--jump DNAT \
--to-destination 10.0.0.11:8080
我们再尝试请求虚拟IP 10.100.100.100
⚡ root@raspberrypi ~ curl 10.100.100.100:8080
2020-09-03 06:21:27, you're running http-server on 10.0.0.11
⚡ root@raspberrypi ~ ip netns exec netns_021 curl 10.100.100.100:8080
2020-09-03 06:24:11, you're running http-server on 10.0.0.11
基于以上的iptables规则,我们需要针对netns_021
的http-server添加新的链和规则:
sudo iptables \
--table nat \
--new KUBE-SEP-KJLGOIJET5GN2EKJ
sudo iptables \
--table nat \
--append KUBE-SEP-KJLGOIJET5GN2EKJ \
--protocol tcp \
--match comment \
--comment "default/http-server:" \
--jump DNAT \
--to-destination 10.0.0.21:8080
然后我们还需要在KUBE-SVC-HVYO5BEGEF5HC7MD7
链中添加另一条规则,以随机跳转到KUBE-SEP-KJLGOIJET5GN2EKJ
sudo iptables \
--table nat \
--insert KUBE-SVC-HVYO5BEGEF5HC7MD7 1 \
--match comment \
--comment "default/http-server:" \
--match statistic \
--mode random \
--probability 0.5 \
--jump KUBE-SEP-KJLGOIJET5GN2EKJ
需要注意的是 这条规则是insert插入到
KUBE-SVC-HVYO5BEGEF5HC7MD7
的第一条的位置。
我们知道iptables的匹配是顺序匹配,匹配到之后就不会执行后面的规则,因此,首先有了这条50%的机率会匹配规则,如果成功,iptables会跳至KUBE-SEP-KJLGOIJET5GN2EKJ
,如果失败,则匹配下一条规则,跳至KUBE-SEP-ESZGVIJET5GN2KKU
。
常见的误区就是给这2条KUBE-SEP规则都加上--mode random --probability 0.5
的配置,这样会出现一种情况:
--jump KUBE-SEP-KJLGOIJET5GN2EKJ
),有50%概率失败--jump KUBE-SEP-ESZGVIJET5GN2KKU
),有50%概率失败基于以上这种情况:虚拟IP将不会DNAT转发到任何一个Pod,这个就不是我们想要的负载均衡的效果。
我们运行一些命令,将看到iptables将我们对虚拟IP10.100.100.100
的请求随机转发到10.0.0.11
和10.0.0.21
⚡ root@raspberrypi ~ curl 10.100.100.100:8080
2020-09-03 06:37:41, you're running http-server on 10.0.0.21
⚡ root@raspberrypi ~ curl 10.100.100.100:8080
2020-09-03 06:37:42, you're running http-server on 10.0.0.11
⚡ root@raspberrypi ~ curl 10.100.100.100:8080
2020-09-03 06:37:44, you're running http-server on 10.0.0.21
⚡ root@raspberrypi ~ curl 10.100.100.100:8080
2020-09-03 06:37:45, you're running http-server on 10.0.0.21
⚡ root@raspberrypi ~ curl 10.100.100.100:8080
2020-09-03 06:37:46, you're running http-server on 10.0.0.11
以上就是我对iptables在Kube-proxy组件上对基于ClusterIP类型的Service是如果进行数据包转发及负载均衡的简单的探索与学习。