Kube-OVN系列 - Subnet的使用

本篇博客我们介绍 Kube-OVN 的 Subnet 的使用,如果你还没有搭建 Kubernetes + Kube-OVN 的环境,可以参考我之前的文章:使用Kubeadm搭建Kubernetes集群

初始 VPC 和 Subnet

Kube-OVN 是开箱即用的,在官网下载一键安装脚本install.sh后,无需进行任何修改,直接按默认配置安装,已经能满足我们常见场景的网络需求。
在安装完成后,我们可以看到 Kube-OVN 给我们创建了默认的 VPC ovn-cluster,以及两个初始的子网joinovn-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-pod1ovn-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-pod1node1的默认 namespace 下的 veth 08a37516ea14_h@if29
我们再在 Kube-OVN 中查看 ovs 的概略信息,可以看到ee7cde2f0a44_h@if2608a37516ea14_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-pod1ovn-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-pod1ovn-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>

相应的,由于subnet1natOutgoing的配置项为 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 的使用。

如果觉得对大家有帮助,可以关注我的微信公众号:李若年

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值