记录一次ipv4_forward被修改导致的生产事故

生产集群的节点内核模块被异常修改,导致集群服务与服务之间网络通信异常,产生了较大规模的生产事故。

此次事故涉及到两个主要的内核模块被修改:

  1. net.ipv4.ip_forward: 用于启用 IP 转发,当此模块加载时,Linux 内核会允许将数据包转发到其他网络

尝试复现

在一个集群中启用两个 Pod,通过这两个 Pod 模拟业务
测试连通性的pod

修改 controller-node-2 节点的 /etc/sysctl.d/99-sysctl.conf 文件,并加载(sysctl -p)
修改节点内核配置

1
net.ipv4.ip_forward=0

此时再去测试连通性,已经不通了
修改节点内核参数后测试连通性

尽管是在同一个宿主机上的 Pod,也无法进行通信
同一宿主机的pod进行通信

节点之间能够正常通信
节点之间通信

查看 calico 组网状态,显示正常
修改节点内核参数后calico组网状态

通过正常节点的 Pod 去 ping 异常节点的 Pod,正常节点抓包,发现没有回包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@controller-node-1 ~]# tcpdump -i any host 10.233.74.83 or 10.233.76.142 -nnvvv
tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
16:14:10.762727 IP (tos 0x0, ttl 64, id 37977, offset 0, flags [DF], proto ICMP (1), length 84)
10.233.74.83 > 10.233.76.142: ICMP echo request, id 265, seq 0, length 64
16:14:10.762787 IP (tos 0x0, ttl 63, id 37977, offset 0, flags [DF], proto ICMP (1), length 84)
10.233.74.83 > 10.233.76.142: ICMP echo request, id 265, seq 0, length 64
16:14:11.762953 IP (tos 0x0, ttl 64, id 38291, offset 0, flags [DF], proto ICMP (1), length 84)
10.233.74.83 > 10.233.76.142: ICMP echo request, id 265, seq 1, length 64
16:14:11.763002 IP (tos 0x0, ttl 63, id 38291, offset 0, flags [DF], proto ICMP (1), length 84)
10.233.74.83 > 10.233.76.142: ICMP echo request, id 265, seq 1, length 64
16:14:12.763208 IP (tos 0x0, ttl 64, id 38345, offset 0, flags [DF], proto ICMP (1), length 84)
10.233.74.83 > 10.233.76.142: ICMP echo request, id 265, seq 2, length 64
16:14:12.763253 IP (tos 0x0, ttl 63, id 38345, offset 0, flags [DF], proto ICMP (1), length 84)
10.233.74.83 > 10.233.76.142: ICMP echo request, id 265, seq 2, length 64

异常节点抓包,发现 icmp 包有到达该节点上,但目标地址没有进行响应,说明流量没有抵达目的地 Pod

1
2
3
4
5
6
7
8
[root@controller-node-2 ~]# tcpdump -i any host 10.233.74.83 or 10.233.76.142 -nnvvv
tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
16:15:00.019656 IP (tos 0x0, ttl 63, id 42391, offset 0, flags [DF], proto ICMP (1), length 84)
10.233.74.83 > 10.233.76.142: ICMP echo request, id 271, seq 0, length 64
16:15:01.019960 IP (tos 0x0, ttl 63, id 43082, offset 0, flags [DF], proto ICMP (1), length 84)
10.233.74.83 > 10.233.76.142: ICMP echo request, id 271, seq 1, length 64
16:15:02.020072 IP (tos 0x0, ttl 63, id 43594, offset 0, flags [DF], proto ICMP (1), length 84)
10.233.74.83 > 10.233.76.142: ICMP echo request, id 271, seq 2, length 64

通过异常节点的 Pod 去 ping 正常节点的 Pod,正常节点抓包,发现没有任何包,说明流量没有从异常节点转发出来

1
2
[root@controller-node-1 ~]# tcpdump -i any host 10.233.74.83 or 10.233.76.142 -nnvvv
tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes

异常节点,进入 Pod 对应的网络命名空间进行抓包,可以看到有 icmp 的请求包,但依旧没有收到响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@controller-node-2 ~]# nsenter -n -t 23056
[root@controller-node-2 ~]# ip -4 a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
4: eth0@if12: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default qlen 1000 link-netnsid 0
inet 10.233.76.142/32 scope global eth0
valid_lft forever preferred_lft forever
[root@controller-node-2 ~]# tcpdump -i any host 10.233.74.83 or 10.233.76.142 -nnvvv
tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
16:22:42.911202 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 169.254.1.1 tell 10.233.76.142, length 28
16:22:43.913783 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 169.254.1.1 tell 10.233.76.142, length 28
16:22:44.915789 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 169.254.1.1 tell 10.233.76.142, length 28
16:22:45.917834 IP (tos 0xc0, ttl 64, id 52805, offset 0, flags [none], proto ICMP (1), length 112)
10.233.76.142 > 10.233.76.142: ICMP host 10.233.74.83 unreachable, length 92
IP (tos 0x0, ttl 64, id 26248, offset 0, flags [DF], proto ICMP (1), length 84)
10.233.76.142 > 10.233.74.83: ICMP echo request, id 90, seq 0, length 64
16:22:45.917839 IP (tos 0xc0, ttl 64, id 52806, offset 0, flags [none], proto ICMP (1), length 112)
10.233.76.142 > 10.233.76.142: ICMP host 10.233.74.83 unreachable, length 92
IP (tos 0x0, ttl 64, id 26601, offset 0, flags [DF], proto ICMP (1), length 84)
10.233.76.142 > 10.233.74.83: ICMP echo request, id 90, seq 1, length 64
16:22:45.917841 IP (tos 0xc0, ttl 64, id 52807, offset 0, flags [none], proto ICMP (1), length 112)
10.233.76.142 > 10.233.76.142: ICMP host 10.233.74.83 unreachable, length 92
IP (tos 0x0, ttl 64, id 26809, offset 0, flags [DF], proto ICMP (1), length 84)
10.233.76.142 > 10.233.74.83: ICMP echo request, id 90, seq 2, length 64

尝试重启该节点的 calico-node,内核模块会被 calico-node 修改回来,此时网络恢复,但 /etc/sysctl.d/99-sysctl.conf 中的 net.ipv4.ip_forward 还是 0,所以在下次重新加载(sysctl -p)的时候,仍然会被设置为关闭状态
重启calico-node后测试连通性
重启calico-node后内核参数

事故总结

此次事故的排障思路是:

  1. 通过两个在不同宿主机的 Pod,测试跨节点的连通性,不通
  2. 测试节点之间的连通性,能够正常通信
  3. 在这两个宿主机进行同一宿主机不同 Pod 的连通性测试,一台通,一台不通 – 确定问题节点
  4. 通过 calicoctl node status 查看组网状态,显示正常 – 暂且排除是 calico 的问题
  5. 通过 tcpdump 进行抓包,获取正常节点 Pod 到异常节点 Pod 的数据包 – icmp 数据包能够到达异常节点,但异常节点的 Pod 没有响应
  6. 通过 tcpdump 进行抓包,获取异常节点 Pod 到正常节点 Pod 的数据包 – 异常节点宿主机层面无法获取 icmp 包,通过 nsenter 进入 Pod 的网络命名空间发现,icmp 有发出但无响应,且 icmp 数据包无法到达正常节点,正常节点抓包观察没有任何包
  7. 尝试重启异常节点 calico-node,网络恢复 – calico-node 启动会修改内核参数,但不会持久化到 /etc/sysctl.d/99-sysctl.conf
  8. 查看 /etc/sysctl.d/99-sysctl.conf 发现 net.ipv4.ip_forward 被设置为了 0
  9. 通过 ansible 检查所有节点的 /etc/sysctl.d/99-sysctl.conf 文件

K8s给命名空间设置专享节点

是指在特定命名空间下部署的 Pod 都会被调度到指定标签的节点上,这个功能需要在 kube-apiserver 添加 PodNodeSelector 参数

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Pod
metadata:
labels:
component: kube-apiserver
tier: control-plane
name: kube-apiserver
namespace: kube-system
...
- --enable-admission-plugins=NodeRestriction,PodNodeSelector
...

然后给命名空间添加 annotation

1
2
3
4
5
apiVersion: v1
kind: Namespace
metadata:
annotations:
scheduler.alpha.kubernetes.io/node-selector: test.io/app=true

CoreDNS和NodeLocalDNS的域名解析

在 K8s 中,DNS 的解析主要用这两个工具:

  1. CoreDNS: 主要负责集群内部域名解析
  2. NodeLocalDNS: 提供 DNS 缓存

首先看一下集群中节点的 /etc/resolv.conf 配置文件
node-resolv-conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 指定了搜索域,当使用域名解析主机名时,如果主机名没有完全限定,系统会依次尝试在指定的搜索域中追加搜索后缀进行解析
# 例如解析 test,则会尝试解析 test.default.svc.cluster.local 和 test.svc.cluster.local
search default.svc.cluster.local svc.cluster.local

# nodelocaldns 服务器的地址,如果集群中没有 nodelocaldns,那么这个地址就会替换成 CoreDNS 的 SVC ClusterIP
nameserver 169.254.25.10

# 其他 DNS 服务器地址
nameserver 192.168.0.5
nameserver 223.5.5.5

# 解析的域名最多包涵 2 个点,最多超时 2s,最多重试 2 次
# 假设解析a,会尝试追加 default.svc.cluster.local 和 svc.cluster.local
# 假设解析a.b,则不会,除非 ndots > 2
options ndots:2 timeout:2 attempts:2

在 K8s 中,workload 的 dnsPolicy 有四种类型:

  1. ClusterFirst: 与配置的集群域后缀不匹配的任何 DNS 查询(例如 “www.kubernetes.io") 都会由 DNS 服务器转发到上游名称服务器
  2. Default: 从运行所在的节点继承名称解析配置
  3. ClusterFirstWithHostNet: 对于以 hostNetwork 方式运行的 Pod,应将其 DNS 策略显式设置为 ClusterFirstWithHostNet。否则,以 hostNetwork 方式和 ClusterFirst 策略运行的 Pod 将会做出回退至 Default 策略的行为
  4. None: 会使用 dnsConfig 提供的 DNS 配置

CoreDNS

CoreDNS 主要是复杂集群内部域名的解析,保证 Pod 与 Pod 只见可以通过 Service Name 进行通信。当然也可以为它添加配置,使其能够解析静态的一些 Host 👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
kind: ConfigMap
apiVersion: v1
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors {
}
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . /etc/resolv.conf {
prefer_udp
max_concurrent 1000
}
hosts /etc/coredns/Hosts {
ttl 60
reload 1m
fallthrough
}
cache 30
loop
reload
loadbalance
}
Hosts: |+
192.168.0.10 cqm.com

创建一个 dnsPolicy 为 ClusterFirst 的 busybox
busybox

通过这个 busybox 解析集群内部域名,通过 tcpdump 获取 53 端口的包
通过coredns解析集群内部域名

可以看到会根据 search 对 CoreDNS 发起多次请求

Pod -> CoreDNS -> Pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@controller-node-1 ~]# tcpdump -i any host 10.233.0.3 and 10.233.74.83 and port 53 -nnvvv
tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
22:52:20.447820 IP (tos 0x0, ttl 64, id 37856, offset 0, flags [DF], proto UDP (17), length 82)
10.233.74.83.58663 > 10.233.0.3.53: [bad udp cksum 0x6077 -> 0x753d!] 33482+ A? kubernetes.default.svc.cluster.local. (54)
22:52:20.447950 IP (tos 0x0, ttl 64, id 37857, offset 0, flags [DF], proto UDP (17), length 74)
10.233.74.83.58663 > 10.233.0.3.53: [bad udp cksum 0x606f -> 0xdd2e!] 53027+ A? kubernetes.svc.cluster.local. (46)
22:52:20.447978 IP (tos 0x0, ttl 64, id 37858, offset 0, flags [DF], proto UDP (17), length 70)
10.233.74.83.58663 > 10.233.0.3.53: [bad udp cksum 0x606b -> 0x0aa3!] 30769+ A? kubernetes.cluster.local. (42)
22:52:20.447999 IP (tos 0x0, ttl 64, id 37859, offset 0, flags [DF], proto UDP (17), length 82)
10.233.74.83.58663 > 10.233.0.3.53: [bad udp cksum 0x6077 -> 0x2dde!] 51726+ AAAA? kubernetes.default.svc.cluster.local. (54)
22:52:20.448017 IP (tos 0x0, ttl 64, id 37860, offset 0, flags [DF], proto UDP (17), length 74)
10.233.74.83.58663 > 10.233.0.3.53: [bad udp cksum 0x606f -> 0xba24!] 61970+ AAAA? kubernetes.svc.cluster.local. (46)
22:52:20.448038 IP (tos 0x0, ttl 64, id 37861, offset 0, flags [DF], proto UDP (17), length 70)
10.233.74.83.58663 > 10.233.0.3.53: [bad udp cksum 0x606b -> 0x7758!] 2913+ AAAA? kubernetes.cluster.local. (42)
22:52:20.448407 IP (tos 0x0, ttl 63, id 32091, offset 0, flags [DF], proto UDP (17), length 163)
10.233.0.3.53 > 10.233.74.83.58663: [bad udp cksum 0x60c8 -> 0xd926!] 2913 NXDomain*- q: AAAA? kubernetes.cluster.local. 0/1/0 ns: cluster.local. [5s] SOA ns.dns.cluster.local. hostmaster.cluster.local. 1711809447 7200 1800 86400 5 (135)
22:52:20.448557 IP (tos 0x0, ttl 63, id 32092, offset 0, flags [DF], proto UDP (17), length 175)
10.233.0.3.53 > 10.233.74.83.58663: [bad udp cksum 0x60d4 -> 0x8faf!] 51726*- q: AAAA? kubernetes.default.svc.cluster.local. 0/1/0 ns: cluster.local. [5s] SOA ns.dns.cluster.local. hostmaster.cluster.local. 1711809447 7200 1800 86400 5 (147)
22:52:20.448654 IP (tos 0x0, ttl 63, id 32093, offset 0, flags [DF], proto UDP (17), length 163)
10.233.0.3.53 > 10.233.74.83.58663: [bad udp cksum 0x60c8 -> 0x6c71!] 30769 NXDomain*- q: A? kubernetes.cluster.local. 0/1/0 ns: cluster.local. [5s] SOA ns.dns.cluster.local. hostmaster.cluster.local. 1711809447 7200 1800 86400 5 (135)
22:52:20.448699 IP (tos 0x0, ttl 63, id 32094, offset 0, flags [DF], proto UDP (17), length 167)
10.233.0.3.53 > 10.233.74.83.58663: [bad udp cksum 0x60cc -> 0x1bf3!] 61970 NXDomain*- q: AAAA? kubernetes.svc.cluster.local. 0/1/0 ns: cluster.local. [5s] SOA ns.dns.cluster.local. hostmaster.cluster.local. 1711809447 7200 1800 86400 5 (139)
22:52:20.448761 IP (tos 0x0, ttl 63, id 32095, offset 0, flags [DF], proto UDP (17), length 134)
10.233.0.3.53 > 10.233.74.83.58663: [bad udp cksum 0x60ab -> 0x24fc!] 33482*- q: A? kubernetes.default.svc.cluster.local. 1/0/0 kubernetes.default.svc.cluster.local. [5s] A 10.233.0.1 (106)
22:52:20.448811 IP (tos 0x0, ttl 63, id 32096, offset 0, flags [DF], proto UDP (17), length 167)
10.233.0.3.53 > 10.233.74.83.58663: [bad udp cksum 0x60cc -> 0x3efd!] 53027 NXDomain*- q: A? kubernetes.svc.cluster.local. 0/1/0 ns: cluster.local. [5s] SOA ns.dns.cluster.local. hostmaster.cluster.local. 1711809447 7200 1800 86400 5 (139)

通过这个 busybox 解析集群公网域名
通过coredns解析集群公网域名

可以看到对 www.baidu.com 域名的解析返回结果,是通过 192.168.0.5 获取到的

Pod -> CoreDNS -> 192.168.0.5 -> CoreDNS -> Pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@controller-node-1 ~]#  tcpdump -i any host 10.233.74.83 and 10.233.0.3 or 192.168.0.5 and port 53 -nnvvv
tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
22:58:29.405332 IP (tos 0x0, ttl 64, id 48447, offset 0, flags [DF], proto UDP (17), length 59)
10.233.74.83.38430 > 10.233.0.3.53: [bad udp cksum 0x6060 -> 0xdcc6!] 28765+ A? www.baidu.com. (31)
22:58:29.405423 IP (tos 0x0, ttl 64, id 48448, offset 0, flags [DF], proto UDP (17), length 59)
10.233.74.83.38430 > 10.233.0.3.53: [bad udp cksum 0x6060 -> 0xcdcd!] 25686+ AAAA? www.baidu.com. (31)
22:58:29.405881 IP (tos 0x0, ttl 64, id 42712, offset 0, flags [DF], proto UDP (17), length 59)
10.233.74.66.56555 > 192.168.0.5.53: [bad udp cksum 0x1611 -> 0x2f90!] 1558+ AAAA? www.baidu.com. (31)
22:58:29.405950 IP (tos 0x0, ttl 63, id 42712, offset 0, flags [DF], proto UDP (17), length 59)
192.168.159.41.56555 > 192.168.0.5.53: [bad udp cksum 0x20b8 -> 0x24e9!] 1558+ AAAA? www.baidu.com. (31)
22:58:29.406110 IP (tos 0x0, ttl 64, id 42713, offset 0, flags [DF], proto UDP (17), length 59)
10.233.74.66.58403 > 192.168.0.5.53: [bad udp cksum 0x1611 -> 0xf518!] 21589+ A? www.baidu.com. (31)
22:58:29.406191 IP (tos 0x0, ttl 63, id 42713, offset 0, flags [DF], proto UDP (17), length 59)
192.168.159.41.58403 > 192.168.0.5.53: [bad udp cksum 0x20b8 -> 0xea71!] 21589+ A? www.baidu.com. (31)
22:58:29.410592 IP (tos 0x0, ttl 63, id 39983, offset 0, flags [DF], proto UDP (17), length 118)
192.168.0.5.53 > 192.168.159.41.58403: [udp sum ok] 21589 q: A? www.baidu.com. 3/0/0 www.baidu.com. [2m9s] CNAME www.a.shifen.com., www.a.shifen.com. [2m9s] A 183.2.172.185, www.a.shifen.com. [2m9s] A 183.2.172.42 (90)
22:58:29.410637 IP (tos 0x0, ttl 62, id 39983, offset 0, flags [DF], proto UDP (17), length 118)
192.168.0.5.53 > 10.233.74.66.58403: [udp sum ok] 21589 q: A? www.baidu.com. 3/0/0 www.baidu.com. [2m9s] CNAME www.a.shifen.com., www.a.shifen.com. [2m9s] A 183.2.172.185, www.a.shifen.com. [2m9s] A 183.2.172.42 (90)
22:58:29.410972 IP (tos 0x0, ttl 63, id 64831, offset 0, flags [DF], proto UDP (17), length 166)
10.233.0.3.53 > 10.233.74.83.38430: [bad udp cksum 0x60cb -> 0xe43e!] 28765 q: A? www.baidu.com. 3/0/0 www.baidu.com. [30s] CNAME www.a.shifen.com., www.a.shifen.com. [30s] A 183.2.172.42, www.a.shifen.com. [30s] A 183.2.172.185 (138)
22:58:29.413159 IP (tos 0x0, ttl 63, id 39984, offset 0, flags [DF], proto UDP (17), length 142)
192.168.0.5.53 > 192.168.159.41.56555: [udp sum ok] 1558 q: AAAA? www.baidu.com. 3/0/0 www.baidu.com. [3m29s] CNAME www.a.shifen.com., www.a.shifen.com. [3m29s] AAAA 240e:ff:e020:9ae:0:ff:b014:8e8b, www.a.shifen.com. [3m29s] AAAA 240e:ff:e020:966:0:ff:b042:f296 (114)
22:58:29.413203 IP (tos 0x0, ttl 62, id 39984, offset 0, flags [DF], proto UDP (17), length 142)
192.168.0.5.53 > 10.233.74.66.56555: [udp sum ok] 1558 q: AAAA? www.baidu.com. 3/0/0 www.baidu.com. [3m29s] CNAME www.a.shifen.com., www.a.shifen.com. [3m29s] AAAA 240e:ff:e020:9ae:0:ff:b014:8e8b, www.a.shifen.com. [3m29s] AAAA 240e:ff:e020:966:0:ff:b042:f296 (114)
22:58:29.413922 IP (tos 0x0, ttl 63, id 64833, offset 0, flags [DF], proto UDP (17), length 190)
10.233.0.3.53 > 10.233.74.83.38430: [bad udp cksum 0x60e3 -> 0x9ac9!] 25686 q: AAAA? www.baidu.com. 3/0/0 www.baidu.com. [30s] CNAME www.a.shifen.com., www.a.shifen.com. [30s] AAAA 240e:ff:e020:966:0:ff:b042:f296, www.a.shifen.com. [30s] AAAA 240e:ff:e020:9ae:0:ff:b014:8e8b (162)

NodeLocalDNS

来自官方文档: NodeLocal DNSCache 通过在集群节点上作为 DaemonSet 运行 DNS 缓存代理来提高集群 DNS 性能。

NodeLocalDNS Pod 中的 /etc/resolv.conf 与宿主机是相同的
nodelocaldns的resolv配置

节点上也会有对应的网卡,这个地址取决于启动参数
nodelocaldns网卡

在集群有 NodeLocalDNS 的情况下,在对集群内部域名进行解析,可以看到 DNS 请求会先经过 NodeLocalDNS,然后直接返回请求

Pod -> NodeLocalDNS -> Pod

1
2
3
4
5
6
7
8
9
10
[root@controller-node-1 ~]# tcpdump -i any host 10.233.74.83 and 169.254.25.10 or 10.233.0.3 and port 53 -nnvvv
tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
23:19:46.273718 IP (tos 0x0, ttl 64, id 61227, offset 0, flags [DF], proto UDP (17), length 82)
10.233.74.83.36711 > 169.254.25.10.53: [bad udp cksum 0x1894 -> 0x3f9b!] 22032+ A? kubernetes.default.svc.cluster.local. (54)
23:19:46.273793 IP (tos 0x0, ttl 64, id 61228, offset 0, flags [DF], proto UDP (17), length 82)
10.233.74.83.36711 > 169.254.25.10.53: [bad udp cksum 0x1894 -> 0xb26b!] 58148+ AAAA? kubernetes.default.svc.cluster.local. (54)
23:19:46.275678 IP (tos 0x0, ttl 64, id 60479, offset 0, flags [DF], proto UDP (17), length 134)
169.254.25.10.53 > 10.233.74.83.36711: [bad udp cksum 0x18c8 -> 0xef59!] 22032*- q: A? kubernetes.default.svc.cluster.local. 1/0/0 kubernetes.default.svc.cluster.local. [5s] A 10.233.0.1 (106)
23:19:46.275946 IP (tos 0x0, ttl 64, id 60480, offset 0, flags [DF], proto UDP (17), length 175)
169.254.25.10.53 > 10.233.74.83.36711: [bad udp cksum 0x18f1 -> 0x5d58!] 58148*- q: AAAA? kubernetes.default.svc.cluster.local. 0/1/0 ns: cluster.local. [5s] SOA ns.dns.cluster.local. hostmaster.cluster.local. 1711802462 7200 1800 86400 5 (147)

然后在对集群外部域名进行解析

Pod -> NodeLocalDNS -> CoreDNS -> 192.168.0.5 -> CoreDNS -> NodeLocalDNS -> Pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
23:27:25.569362 IP (tos 0x0, ttl 64, id 36019, offset 0, flags [DF], proto UDP (17), length 60)
10.233.74.83.42419 > 169.254.25.10.53: [bad udp cksum 0x187e -> 0x94cd!] 7676+ A? www.google.com. (32)
23:27:25.569423 IP (tos 0x0, ttl 64, id 36020, offset 0, flags [DF], proto UDP (17), length 60)
10.233.74.83.42419 > 169.254.25.10.53: [bad udp cksum 0x187e -> 0xb5c9!] 64740+ AAAA? www.google.com. (32)
23:27:25.569823 IP (tos 0x0, ttl 64, id 65347, offset 0, flags [DF], proto UDP (17), length 71)
192.168.159.11.57457 > 192.168.0.5.53: [bad udp cksum 0x20a6 -> 0x7971!] 52418+ [1au] AAAA? www.google.com. ar: . OPT UDPsize=2048 DO (43)
23:27:25.569841 IP (tos 0x0, ttl 64, id 65348, offset 0, flags [DF], proto UDP (17), length 71)
192.168.159.11.33304 > 192.168.0.5.53: [bad udp cksum 0x20a6 -> 0xa430!] 120+ [1au] A? www.google.com. ar: . OPT UDPsize=2048 DO (43)
23:27:25.571423 IP (tos 0x0, ttl 63, id 18081, offset 0, flags [DF], proto UDP (17), length 152)
192.168.0.5.53 > 192.168.159.11.57457: [udp sum ok] 52418 q: AAAA? www.google.com. 0/1/1 ns: www.google.com. [10s] SOA fake-for-negative-caching.adguard.com. hostmaster.www.google.com. 100500 1800 60 604800 86400 ar: . OPT UDPsize=2048 DO (124)
23:27:25.571763 IP (tos 0x0, ttl 64, id 40563, offset 0, flags [DF], proto UDP (17), length 172)
169.254.25.10.53 > 10.233.74.83.42419: [bad udp cksum 0x18ee -> 0x3576!] 64740 q: AAAA? www.google.com. 0/1/0 ns: www.google.com. [10s] SOA fake-for-negative-caching.adguard.com. hostmaster.www.google.com. 100500 1800 60 604800 86400 (144)
23:27:25.573701 IP (tos 0x0, ttl 63, id 18082, offset 0, flags [DF], proto UDP (17), length 87)
192.168.0.5.53 > 192.168.159.11.33304: [udp sum ok] 120 q: A? www.google.com. 1/0/1 www.google.com. [1m30s] A 216.58.203.68 ar: . OPT UDPsize=2048 DO (59)
23:27:25.573927 IP (tos 0x0, ttl 64, id 40564, offset 0, flags [DF], proto UDP (17), length 90)
169.254.25.10.53 > 10.233.74.83.42419: [bad udp cksum 0x189c -> 0xe2c7!] 7676 q: A? www.google.com. 1/0/0 www.google.com. [30s] A 216.58.203.68 (62)

kube-proxy工作原理

kube-proxy 以 DaemonSet 的形式运行在集群的所有节点中,负责管理集群外到 Service,以及 Service 到 Pod 的流量转发。

kube-proxy 有三种模式:

  1. userspace
  2. iptables
  3. ipvs

iptables

通过 kube-proxy 的 cm 可以查看 mode
iptables-mode

我们都知道 SVC 有多种类型,常用的有 ClusterIP、NodePort、Headless、LoadBalancer 等等,这里我们通过创建类型的 SVC,查看 kube-proxy 下发的iptables 规则

ClusterIP
clusterip-svc

通过 iptables-save 命令打印这个 SVC 的规则
clusterip-iptables

PodIP: 10.233.74.106
ClusterIP: 10.233.54.175

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 将来自 Pod IP 10.233.74.106 的流量标记为服务 default/nginx,并转发到 KUBE-MARK-MASQ 链中
-A KUBE-SEP-EUB75IW55XDW6EHN -s 10.233.74.106/32 -m comment --comment "default/nginx:tcp-80" -j KUBE-MARK-MASQ

# 将匹配的 TCP 流量进行 DNAT,转发到后端 Pod 10.233.74.106:80
-A KUBE-SEP-EUB75IW55XDW6EHN -p tcp -m comment --comment "default/nginx:tcp-80" -m tcp -j DNAT --to-destination 10.233.74.106:80

# 匹配目标地址为服务 default/nginx 的 cluster IP 10.233.54.175 的 TCP 80 端口流量
-A KUBE-SERVICES -d 10.233.54.175/32 -p tcp -m comment --comment "default/nginx:tcp-80 cluster IP" -m tcp --dport 80 -j KUBE-SVC-XX3NNXKPRC7N6OJH

# 标记来自外部网络(除了 PodCIDR 10.233.64.0/18)的、目标地址为服务 default/nginx 的 cluster IP 10.233.54.175 的 TCP 80 端口的流量
-A KUBE-SVC-XX3NNXKPRC7N6OJH ! -s 10.233.64.0/18 -d 10.233.54.175/32 -p tcp -m comment --comment "default/nginx:tcp-80 cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ

# 将匹配的流量转发到 KUBE-SEP-EUB75IW55XDW6EHN 链中,进行 DNAT 地址转换
-A KUBE-SVC-XX3NNXKPRC7N6OJH -m comment --comment "default/nginx:tcp-80 -> 10.233.74.106:80" -j KUBE-SEP-EUB75IW55XDW6EHN

NodePort

把这个 SVC 类型改成 NodePort
nodeport-svc

通过 iptables-save 命令打印这个 SVC 的规则,多了两条规则
nodeport-iptables

1
2
3
4
5
# 用于标记来自外部的、目标地址为服务 default/nginx 的 NodePort 流量
-A KUBE-EXT-XX3NNXKPRC7N6OJH -m comment --comment "masquerade traffic for default/nginx:tcp-80 external destinations" -j KUBE-MARK-MASQ

# 用于匹配目标端口为 32594 的 TCP 流量,并将该流量转发到上面的规则进行 MASQ 标记
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/nginx:tcp-80" -m tcp --dport 32594 -j KUBE-EXT-XX3NNXKPRC7N6OJH

IPVS

通过 kube-proxy 的 cm 可以查看 mode
ipvs-mode

来自 GPT 的回答: IPVS 是 Linux 内核中实现的虚拟服务器技术,它可以将网络流量负载均衡到多台服务器上,从而提高服务性能和可靠性。

ClusterIP

PodIP: 10.233.76.138
ClusterIP: 10.233.27.36

通过 ipvsadm 命令查看这个虚拟服务器
clusterip-ipvs

1
2
3
4
5
6
7
8
9
10
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn

# 将目标地址为 nginx.default.svc.cluster.lo:80 的 TCP 流量转发到后端 Pod 10.233.76.138:80
TCP nginx.default.svc.cluster.lo rr
-> 10-233-76-138.nginx.default. Masq 1 0 0

# 将目标地址为 10.233.27.36:80 的 TCP 流量转发到后端 Pod 10.233.76.138:80
TCP 10.233.27.36:80 rr
-> 10.233.76.138:80 Masq 1 0 0

NodePort

通过 ipvsadm 命令查看这个虚拟服务器,多了三条规则
nodeport-ipvs

1
2
3
4
5
6
7
8
9
10
11
# 10.233.74.64 为 nodelocaldns 网卡的 IP,由于 NodePort 是全监听,所以也有对应的规则
TCP 169.254.25.10:30229 rr
-> 10.233.76.138:80 Masq 1 0 0

# 节点IP
TCP 192.168.159.11:30229 rr
-> 10.233.76.138:80 Masq 1 0 0

# 10.233.74.64 为 tun0 网卡的 IP,由于 NodePort 是全监听,所以也有对应的规则
TCP 10.233.74.64:30229 rr
-> 10.233.76.138:80 Masq 1 0

CNI工作流程

CNI 即容器网络接口,通过 CNI 能够使 K8s 支持不同的网络模式。

CNI 常见的实现模式大致分为两种:

  1. overlay: 通过隧道打通网络,不依赖底层网络,如 calico
  2. underlay: 通过底层打通网络,强依赖底层网络,如 macvlan

CNI 如何使用

CNI 的实现通常需要两个部分:

  1. CNI 二进制文件去配置 Pod 的网卡和 IP

  2. DaemonSet 进程去管理 Pod 之间的网络打通

  3. 配置 CNI 配置文件(/etc/cni/net.d/*.conf)

  4. 安装 CNI 二进制文件(/opt/cni/bin/*)

  5. 节点上创建 Pod

  6. kubelet 根据 CNI 配置文件执行 CNI 二进制文件

  7. Pod 网络配置完成