本篇博客我们介绍 Kube-OVN 的 Subnet 的使用,如果你还没有搭建 Kubernetes + Kube-OVN 的环境,可以参考我之前的文章:使用Kubeadm搭建Kubernetes集群
初始 VPC 和 Subnet
Kube-OVN 是开箱即用的,在官网下载一键安装脚本install.sh
后,无需进行任何修改,直接按默认配置安装,已经能满足我们常见场景的网络需求。
在安装完成后,我们可以看到 Kube-OVN 给我们创建了默认的 VPC ovn-cluster
,以及两个初始的子网join
和ovn-default
,这里我们重点关注ovn-default
这个子网。
$ kubectl get vpc
NAME ENABLEEXTERNAL ENABLEBFD STANDBY SUBNETS NAMESPACES
ovn-cluster false false true ["join","ovn-default"]
$ kubectl get subnet
NAME PROVIDER VPC PROTOCOL CIDR PRIVATE NAT DEFAULT GATEWAYTYPE V4USED V4AVAILABLE V6USED V6AVAILABLE EXCLUDEIPS U2OINTERCONNECTIONIP
join ovn ovn-cluster IPv4 100.64.0.0/16 false false false distributed 2 65531 0 0 ["100.64.0.1"]
ovn-default ovn ovn-cluster IPv4 10.16.0.0/16 false true true distributed 6 65527 0 0 ["10.16.0.1"]
ovn-default 默认子网
对ovn-default
子网,我们主要验证三个场景:
- 调度到相同 node 节点上的 两个 pod 通信是否正常;
- 调度到不同 node 节点上的 两个 pod 通信是否正常;
- pod 出网是否正常;
相同节点上的 pod 间通信
我们先创建两个 pod:ovn-default-node1-pod1
和ovn-default-node1-pod2
,这两个 pod 的 nodeSelector 都是指向 node1
。
$ cat ovn-default-node1-pod1.yaml
apiVersion: v1
kind: Pod
metadata:
name: ovn-default-node1-pod1
spec:
nodeSelector:
kubernetes.io/hostname: node1
containers:
- name: nginx
image: docker.io/library/nginx:alpine
$ cat ovn-default-node1-pod2.yaml
apiVersion: v1
kind: Pod
metadata:
name: ovn-default-node1-pod2
spec:
nodeSelector:
kubernetes.io/hostname: node1
containers:
- name: nginx
image: docker.io/library/nginx:alpine
在创建完 pod 后,可以看到ovn-default-node1-pod1
分配到10.16.0.8
的 IP,而ovn-default-node1-pod2
分配到10.16.0.9
。对应查看 Kube-OVN 的 CRD,也可以看到对应的IP 资源分配情况。
$ kubectl apply -f pod1.yaml -f pod2.yaml
$ kubectl get po -A -o wide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
default ovn-default-node1-pod1 1/1 Running 0 10s 10.16.0.8 node1 <none> <none>
default ovn-default-node1-pod2 1/1 Running 0 10s 10.16.0.9 node1 <none> <none>
$ kubectl get ip | grep ovn-default
...
ovn-default-node1-pod1.default 10.16.0.8 00:00:00:AA:0D:31 node1 ovn-default
ovn-default-node1-pod2.default 10.16.0.9 00:00:00:28:19:46 node1 ovn-default
这时候我们通过ovn-default-node1-pod1
去 ping ovn-default-node1-pod2
的 ip 10.16.0.9
,也可以确定是通的。
$ kubectl exec -it ovn-default-node1-pod1 -- ping -c 4 10.16.0.9
PING 10.16.0.9 (10.16.0.9): 56 data bytes
64 bytes from 10.16.0.9: seq=0 ttl=64 time=0.128 ms
64 bytes from 10.16.0.9: seq=1 ttl=64 time=0.106 ms
64 bytes from 10.16.0.9: seq=2 ttl=64 time=0.118 ms
64 bytes from 10.16.0.9: seq=3 ttl=64 time=0.120 ms
--- 10.16.0.9 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 0.106/0.118/0.128 ms
我们可以简单分析下为什么能通,先查看ovn-default-node1-pod1
上的网卡信息:
$ kubectl exec -it ovn-default-node1-pod1 -- ip a
...
26: eth0@if27: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1400 qdisc noqueue state UP
link/ether 00:00:00:aa:0d:31 brd ff:ff:ff:ff:ff:ff
inet 10.16.0.8/16 brd 10.16.255.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::200:ff:feaa:d31/64 scope link
valid_lft forever preferred_lft forever
通过网卡名eth0@if27
,我们可以察觉到这其实是 veth pair 的一端,在node1
的默认的 namespace 下我们可以找到另一端ee7cde2f0a44_h@if26
。
ip link show type veth
...
27: ee7cde2f0a44_h@if26: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1400 qdisc noqueue master ovs-system state UP mode DEFAULT group default qlen 1000
link/ether 7a:a1:3d:fd:d3:24 brd ff:ff:ff:ff:ff:ff link-netns cni-1904a3c4-4777-aeda-95af-2cc9b49d178e
使用相同的方法,我们也能找到ovn-default-node1-pod1
在node1
的默认 namespace 下的 veth 08a37516ea14_h@if29
。
我们再在 Kube-OVN 中查看 ovs 的概略信息,可以看到ee7cde2f0a44_h@if26
和08a37516ea14_h@if29
都插在 Bridge br-int
上,这也就解释了为什么相同节点上的 pod 间是能正常通信的。
kubectl ko vsctl node1 show
fb310a4a-6c6d-4d62-8b08-6826d3b79ca8
Bridge br-int
fail_mode: secure
datapath_type: system
...
Port ee7cde2f0a44_h
Interface ee7cde2f0a44_h
Port "08a37516ea14_h"
Interface "08a37516ea14_h"
...
ovs_version: "3.1.3"
不同节点上的 pod 间通信
我们先创建一个调度到node2
上的 ovn-default-node2-pod3
:
$ cat ovn-default-node2-pod3.yaml
apiVersion: v1
kind: Pod
metadata:
name: ovn-default-node2-pod3
spec:
nodeSelector:
kubernetes.io/hostname: node2
containers:
- name: nginx
image: docker.io/library/nginx:alpine
$ kubectl get po -A -owide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
default ovn-default-node1-pod1 1/1 Running 0 99m 10.16.0.8 node1 <none> <none>
default ovn-default-node2-pod3 1/1 Running 0 5s 10.16.0.12 node2 <none> <none>
...
验证ovn-default-node1-pod1
和ovn-default-node1-pod3
的连通性:
$ kubectl exec -it ovn-default-node1-pod1 -- ping -c 4 10.16.0.12
PING 10.16.0.12 (10.16.0.12): 56 data bytes
64 bytes from 10.16.0.12: seq=0 ttl=64 time=2.875 ms
64 bytes from 10.16.0.12: seq=1 ttl=64 time=0.763 ms
64 bytes from 10.16.0.12: seq=2 ttl=64 time=0.668 ms
64 bytes from 10.16.0.12: seq=3 ttl=64 time=0.618 ms
--- 10.16.0.12 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 0.618/1.231/2.875 ms
因为ovn-default-node1-pod1
和ovn-default-node1-pod3
已经调度到不同的节点,所以通过本地的 Bridge 已经无法将它们连接起来,需要通过虚拟化隧道通信技术才行,在 Kube-OVN 中对应就是 GENEVE。
我们继续通过ovn-default-node1-pod1
ping ovn-default-node1-pod3
,然后在node2
上抓包,观察一下包的结构。
# node1
$ kubectl exec -it ovn-default-node1-pod1 -- ping -c 1 10.16.0.12
PING 10.16.0.12 (10.16.0.12): 56 data bytes
64 bytes from 10.16.0.12: seq=0 ttl=64 time=2.875 ms
# node2
$ tcpdump -i enp1s0 geneve
...
10:41:25.322179 IP 192.168.31.29.36694 > node2.6081: Geneve, Flags [C], vni 0x3, options [8 bytes]: IP 10.16.0.8 > 10.16.0.11: ICMP echo request, id 156, seq 0, length 64
10:41:25.323089 IP node2.25197 > 192.168.31.29.6081: Geneve, Flags [C], vni 0x3, options [8 bytes]: IP 10.16.0.11 > 10.16.0.8: ICMP echo reply, id 156, seq 0, length 64
...
在抓到的网络包中,源 IP 和目的 IP 都是宿主机节点的 IP,而通过 GENEVE 封装的内部的 IP 数据包中,对应的才是 pod 的 IP,可以看到 pod 在跨节点通信时,是通过 GENEVE 实现的(后续文章再详细分析)。
pod 出网验证
我们使用ovn-default-node1-pod1
来验证出网,可以看到在默认子网下新建的 pod,在没有经过任何配置的前提下,也是可以正常访问外网的。
kubectl exec -it ovn-default-node1-pod1 -- ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=1 ttl=106 time=165.620 ms
64 bytes from 8.8.8.8: seq=4 ttl=106 time=164.629 ms
首先我们在宿主机上抓取ovn-default-node1-pod1
关联的 veth 接口的数据包,可以看到源 IP 是 10.16.0.8
:
tcpdump -i ee7cde2f0a44_h icmp
11:07:58.695344 IP 10.16.0.8 > dns.google: ICMP echo request, id 174, seq 0, length 64
11:07:58.861406 IP dns.google > 10.16.0.8: ICMP echo reply, id 174, seq 0, length 64
...
再在宿主机上对物理网卡设备抓包,可以看到此时源 IP 已经从10.16.0.8
替换成了node1
,也就是在这里已经经过了 NAT 的转换。
$ tcpdump -i enp1s0 icmp
11:08:28.451918 IP node1 > dns.google: ICMP echo request, id 12504, seq 3, length 64
11:08:28.616974 IP dns.google > node1: ICMP echo reply, id 12504, seq 3, length 64
...
我们在宿主机上查看下 iptables 的规则,可以找到对应的 nat 规则的配置,默认的 VPC ovn-cluster
下的子网,在出公网时都是通过 NAT 来实现的。
$ iptables -t nat -L
OVN-MASQUERADE all -- anywhere anywhere match-set ovn40subnets-nat src ! match-set ovn40subnets dst
$ ipset list ovn40subnets-nat
Name: ovn40subnets-nat
Type: hash:net
Revision: 7
Header: family inet hashsize 1024 maxelem 1048576 bucketsize 12 initval 0xaa9c4022
Size in memory: 504
References: 2
Number of entries: 1
Members:
10.16.0.0/16
$ ipset list ovn40subnets
Name: ovn40subnets
Type: hash:net
Revision: 7
Header: family inet hashsize 1024 maxelem 1048576 bucketsize 12 initval 0x7d6b4e88
Size in memory: 552
References: 11
Number of entries: 2
Members:
100.64.0.0/16
10.16.0.0/16
自定义 subnet
我们刚才一直使用的是ovn-default
这个默认子网,我们尝试新建一个子网。
$ kubectl create namespace ns1
$ cat subnet.yaml
apiVersion: kubeovn.io/v1
kind: Subnet
metadata:
name: subnet1
spec:
protocol: IPv4
cidrBlock: 10.66.0.0/16
excludeIps:
- 10.66.0.1..10.66.0.10
gateway: 10.66.0.1
gatewayType: distributed
natOutgoing: true
routeTable: ""
namespaces:
- ns1 # 指定namespace
$ kubectl apply -f subnet.yaml
$ kubectl get subnet
NAME PROVIDER VPC PROTOCOL CIDR PRIVATE NAT DEFAULT GATEWAYTYPE V4USED V4AVAILABLE V6USED V6AVAILABLE EXCLUDEIPS U2OINTERCONNECTIONIP
join ovn ovn-cluster IPv4 100.64.0.0/16 false false false distributed 2 65531 0 0 ["100.64.0.1"]
ovn-default ovn ovn-cluster IPv4 10.16.0.0/16 false true true distributed 7 65526 0 0 ["10.16.0.1"]
subnet1 ovn ovn-cluster IPv4 10.66.0.0/16 false true false distributed 0 65524 0 0 ["10.66.0.1..10.66.0.10"]
新建的subnet1
子网关联了ns1
这个 namespace,我们再在ns1
内创建一个 pod:
$ cat subnet1-node1-pod1.yaml
apiVersion: v1
kind: Pod
metadata:
name: subnet1-node1-pod1
namespace: ns1
spec:
nodeSelector:
kubernetes.io/hostname: node1
containers:
- name: nginx
image: docker.io/library/nginx:alpine
$ kubectl apply -f subnet1-node1-pod1.yaml
可以看到在ns1
namespace 下创建的 pod,其 ip 是由subnet1
网段分配的。
$ kubectl get po -A -owide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
...
ns1 subnet1-node1-pod1 1/1 Running 0 5s 10.66.0.11 node1 <none> <none>
相应的,由于subnet1
的natOutgoing
的配置项为 true,所以在subnet1-node1-pod1
中也是可以访问公网的。
$ kubectl exec -it subnet1-node1-pod1 -n ns1 -- ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=106 time=167.119 ms
64 bytes from 8.8.8.8: seq=2 ttl=106 time=164.704 ms
对应查看 iptables 中的 nat 的规则,可以看到发生如下变化,这也是新的subnet1
子网能通过 nat 出公网的原因。
$ ipset list ovn40subnets-nat
Name: ovn40subnets-nat
Type: hash:net
Revision: 7
Header: family inet hashsize 1024 maxelem 1048576 bucketsize 12 initval 0x4a8ca9d3
Size in memory: 552
References: 2
Number of entries: 2
Members:
10.66.0.0/16
10.16.0.0/16
$ ipset list ovn40subnets
Name: ovn40subnets
Type: hash:net
Revision: 7
Header: family inet hashsize 1024 maxelem 1048576 bucketsize 12 initval 0x58e8fa3a
Size in memory: 600
References: 11
Number of entries: 3
Members:
10.66.0.0/16
10.16.0.0/16
100.64.0.0/16
对于 Kube-OVN 子网的常见用法我们介绍到此处,下一篇会介绍 VPC 的使用。
如果觉得对大家有帮助,可以关注我的微信公众号:李若年