K8s 中跨主機(jī) Pod 之間是如何通信的(SDN 使用 Calico)?



K8s 中 Pod 之間是如何通信的(SDN 使用 Calico)?
目前只接觸過(guò) calico,所以默認(rèn) SDN 實(shí)現(xiàn)為 calico,下文不在贅述。

簡(jiǎn)單介紹
在 Kubernetes 中,每個(gè) Pod 都會(huì)被分配一個(gè)唯一的 IP 地址,并且這些 IP 地址將用于在 Pod 之間進(jìn)行通信。

Pod 通信本質(zhì)上是 不同機(jī)器上的兩個(gè) network namespace 通信, network namespace  通過(guò) veth pair 會(huì)在容器內(nèi)部和宿主機(jī)映射一對(duì)虛擬網(wǎng)卡(veth pair)。這對(duì)虛擬網(wǎng)卡類似一個(gè)通道一樣,一端在容器,一端在宿主機(jī),可以直接通信,在部署的好的 K8s 集群中,可以在節(jié)點(diǎn)上看到好多虛擬網(wǎng)卡,這些就是 veth pair  宿主機(jī)的虛擬網(wǎng)卡。

Calico 使用 Linux 內(nèi)核的網(wǎng)絡(luò)堆棧來(lái)實(shí)現(xiàn)網(wǎng)絡(luò)功能(宿主機(jī)的 calico 組件的 Felix 程序會(huì)在內(nèi)核的路由表里面寫入數(shù)據(jù),注明這個(gè)IP出去時(shí)下一跳地址和進(jìn)來(lái)時(shí)的由那個(gè)網(wǎng)卡解析), 同時(shí)路由程序會(huì)獲取ip變換,通過(guò) BPG 路由協(xié)議擴(kuò)散到其他宿主機(jī)上,這里也包括使用代理 ARP 來(lái)處理 Pod 到節(jié)點(diǎn)的 ARP 請(qǐng)求。

宿主機(jī),也就是工作節(jié)點(diǎn),可以看做是一個(gè)路由器。pod 可以看做是連接到路由器上的網(wǎng)絡(luò)終端。

報(bào)文路徑跟蹤
下面的思維導(dǎo)圖為 兩個(gè)不同節(jié)點(diǎn) Pod 報(bào)文的訪問(wèn)路徑


248325bk-1.png


這里創(chuàng)建兩個(gè) Pod ,簡(jiǎn)單分析一下,編寫 YAML 文件通過(guò)拓?fù)浞植技s束調(diào)度在不同的節(jié)點(diǎn)。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-deployment
  labels:
    app: os
spec:
  replicas: 2
  selector:
    matchLabels:
      app: os
  template:
    metadata:
      labels:
        app: os
    spec:
      containers:
      - name: centos
        image: centos:latest
        args:
         - tail
         - -f
         - /dev/null
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchLabels:
              app: os
應(yīng)用之后,查看 Pod 信息

┌──[root@vms100.liruilongs.github.io]-[~/docker]
└─$kubectl get pods -n demo -o wide
NAME                               READY   STATUS    RESTARTS   AGE   IP              NODE                          NOMINATED NODE   READINESS GATES
demo-deployment-6cbdbd86d5-fbt9d   1/1     Running   0          17m   10.244.169.66   vms105.liruilongs.github.io   <none>           <none>
demo-deployment-6cbdbd86d5-nm467   1/1     Running   0          16m   10.244.38.174   vms103.liruilongs.github.io   <none>           <none>
┌──[root@vms100.liruilongs.github.io]-[~/docker]
└─$
分別調(diào)度到了不同節(jié)點(diǎn):

vms105.liruilongs.github.io :demo-deployment-6cbdbd86d5-fbt9d
vms103.liruilongs.github.io :demo-deployment-6cbdbd86d5-nm467
進(jìn)入容器查看 Pod IP 信息

demo-deployment-6cbdbd86d5-fbt9d Pod 對(duì)應(yīng) IP 為 :10.244.169.66, 生成的 veth pair容器側(cè)的虛擬網(wǎng)卡為 eth0@if16

┌──[root@vms100.liruilongs.github.io]-[~/docker]
└─$kubectl exec -it demo-deployment-6cbdbd86d5-fbt9d -n demo -- bash
[root@demo-deployment-6cbdbd86d5-fbt9d /]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
4: eth0@if16: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default
    link/ether 42:17:dd:38:6a:10 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.244.169.66/32 scope global eth0
       valid_lft forever preferred_lft forever
[root@demo-deployment-6cbdbd86d5-fbt9d /]# exit
exit
demo-deployment-6cbdbd86d5-nm467 Pod 對(duì)應(yīng) IP 為 10.244.38.174 ,生成的 veth pair容器側(cè)的虛擬網(wǎng)卡為 eth0@if34

┌──[root@vms100.liruilongs.github.io]-[~/docker]
└─$kubectl exec -it demo-deployment-6cbdbd86d5-nm467 -n demo -- bash
[root@demo-deployment-6cbdbd86d5-nm467 /]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
4: eth0@if34: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default
    link/ether 36:a2:81:c4:84:f9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.244.38.174/32 scope global eth0
       valid_lft forever preferred_lft forever
[root@demo-deployment-6cbdbd86d5-nm467 /]# exit
exit
進(jìn)入 demo-deployment-6cbdbd86d5-nm467 Pod 簡(jiǎn)單做 ping 測(cè)試,來(lái)看一下這個(gè) ICMP 包是如何出去的。

┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$kubectl exec -it demo-deployment-6cbdbd86d5-nm467 -n demo -- bash
[root@demo-deployment-6cbdbd86d5-nm467 /]# ping -c 3  10.244.169.66
PING 10.244.169.66 (10.244.169.66) 56(84) bytes of data.
64 bytes from 10.244.169.66: icmp_seq=1 ttl=62 time=0.497 ms
64 bytes from 10.244.169.66: icmp_seq=2 ttl=62 time=0.460 ms
64 bytes from 10.244.169.66: icmp_seq=3 ttl=62 time=0.391 ms

--- 10.244.169.66 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2044ms
rtt min/avg/max/mdev = 0.391/0.449/0.497/0.047 ms
當(dāng)前容器 IP 為 10.244.38.174 , ping 側(cè)的容器 IP 為 10.244.169.66,不在同一個(gè)網(wǎng)絡(luò)內(nèi),所以當(dāng)前容器會(huì)在路由表獲取一下跳地址.

查看容器路由信息

[root@demo-deployment-6cbdbd86d5-nm467 /]# ip route
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link
下一跳地址為 169.254.1.1 ,這是預(yù)留的本地 IP 網(wǎng)段,這里的容器里的路由規(guī)則在所有的容器都是一樣的,不需要?jiǎng)討B(tài)更新.

容器會(huì)查詢下一跳 168.254.1.1 的 MAC 地址,這個(gè) ARP 請(qǐng)求(查找目標(biāo)設(shè)備的 MAC 地址)會(huì)如何發(fā)出?

這里通過(guò) veth pair 發(fā)出,容器內(nèi)部的虛擬網(wǎng)卡eth0@if34 發(fā)到宿主節(jié)點(diǎn)的對(duì)應(yīng)的虛擬網(wǎng)卡cali7a4b00317e6

如何確定一對(duì) veth pair 虛擬網(wǎng)卡?

[root@demo-deployment-6cbdbd86d5-nm467 /]# ethtool -S eth0
NIC statistics:
     peer_ifindex: 34
     rx_queue_0_xdp_packets: 0
     rx_queue_0_xdp_bytes: 0
     rx_queue_0_xdp_drops: 0
[root@demo-deployment-6cbdbd86d5-nm467 /]#
在容器內(nèi)部我們通過(guò) ethtool -S eth0 可以查看到網(wǎng)卡索引為 34。

┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$ansible-console -i host.yaml --limit 192.168.26.103
Welcome to the ansible console.
Type help or ? to list commands.

root@all (1)[f:5]# ip a | grep 34
192.168.26.103 | CHANGED | rc=0 >>
34: cali7a4b00317e6@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP
root@all (1)[f:5]# ifconfig cali7a4b00317e6
192.168.26.103 | CHANGED | rc=0 >>
cali7a4b00317e6: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1480
        inet6 fe80::ecee:eeff:feee:eeee  prefixlen 64  scopeid 0x20<link>
        ether ee:ee:ee:ee:ee:ee  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0      
       
在宿主節(jié)點(diǎn)上對(duì)應(yīng)的索引的虛擬網(wǎng)卡即為 veth pair 的另一端,由前面可知,這個(gè)pod 調(diào)度到了  192.168.26.103 節(jié)點(diǎn),進(jìn)入節(jié)點(diǎn)可以獲取到索引對(duì)應(yīng)的虛擬網(wǎng)卡。

這里小伙伴會(huì)發(fā)現(xiàn)這個(gè)虛擬網(wǎng)卡沒有隨機(jī) MAC 地址,所有的 MAC 地址為 ee:ee:ee:ee:ee:ee , 也沒有IP地址。






向所在的節(jié)點(diǎn)發(fā)送 ARP 請(qǐng)求后,節(jié)點(diǎn)上的代理 ARP 進(jìn)程將接收到這個(gè)請(qǐng)求,應(yīng)答報(bào)文中MAC地址是自己的MAC地址,容器的后續(xù)報(bào)文 IP 地址還是 目的容器,但是 MAC 地址就變成了主機(jī)上該網(wǎng)卡的地址,也就是說(shuō),所有的報(bào)文都會(huì)發(fā)給主機(jī),主機(jī)根據(jù)IP地址再進(jìn)行轉(zhuǎn)發(fā).(這里不是特別清晰,和書里的有些出入,書的這部分感覺有點(diǎn)問(wèn)題)

主機(jī)上這塊網(wǎng)卡不管 ARP 請(qǐng)求的內(nèi)容,直接用自己的 MAC 地址作為應(yīng)答的行為被稱為 ARP proxy ,可以通過(guò)以下內(nèi)核參數(shù)檢查

┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$ansible-console -i host.yaml --limit 192.168.26.103
Welcome to the ansible console.
Type help or ? to list commands.

root@all (1)[f:5]# cat /proc/sys/net/ipv4/conf/cali7a4b00317e6/proxy_arp
192.168.26.103 | CHANGED | rc=0 >>
1
root@all (1)[f:5]#
可以認(rèn)為 Calico 把主機(jī)作為容器的默認(rèn)網(wǎng)關(guān)使用,所有的報(bào)文發(fā)到主機(jī),主機(jī)根據(jù)路由表進(jìn)行轉(zhuǎn)發(fā)。和經(jīng)典的網(wǎng)絡(luò)架構(gòu)不同的是,Calico 并沒有給默認(rèn)網(wǎng)關(guān)配置一個(gè) IP 地址,而是通過(guò) ARP proxy 和修改容器路由表的機(jī)制實(shí)現(xiàn)。

主機(jī)上的 cali7a4b00317e6 網(wǎng)卡接收到報(bào)文之后,所有的報(bào)文會(huì)根據(jù)路由表轉(zhuǎn)發(fā),查看節(jié)點(diǎn)的路由表

root@all (1)[f:5]# route
192.168.26.103 | CHANGED | rc=0 >>
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         gateway         0.0.0.0         UG    0      0        0 ens32
10.244.31.64    vms106.liruilon 255.255.255.192 UG    0      0        0 tunl0
10.244.38.128   0.0.0.0         255.255.255.192 U     0      0        0 *
10.244.38.151   0.0.0.0         255.255.255.255 UH    0      0        0 cali39ccd735ea3
10.244.38.154   0.0.0.0         255.255.255.255 UH    0      0        0 calif39455d9c24
10.244.38.174   0.0.0.0         255.255.255.255 UH    0      0        0 cali7a4b00317e6
10.244.63.64    vms102.liruilon 255.255.255.192 UG    0      0        0 tunl0
10.244.169.64   vms105.liruilon 255.255.255.192 UG    0      0        0 tunl0
10.244.198.0    vms101.liruilon 255.255.255.192 UG    0      0        0 tunl0
10.244.239.128  vms100.liruilon 255.255.255.192 UG    0      0        0 tunl0
link-local      0.0.0.0         255.255.0.0     U     1002   0        0 ens32
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
192.168.26.0    0.0.0.0         255.255.255.0   U     0      0        0 ens32
訪問(wèn) IP 為 10.244.169.66,可以看到匹配這一條路由

root@all (1)[f:5]# route | grep 169
192.168.26.103 | CHANGED | rc=0 >>
10.244.169.64   vms105.liruilon 255.255.255.192 UG    0      0        0 tunl0
root@all (1)[f:5]# ip route | grep 169
192.168.26.103 | CHANGED | rc=0 >>
10.244.169.64/26 via 192.168.26.105 dev tunl0 proto bird onlink
。。。。
10.244.169.64/26: 表示一個(gè) IP 地址范圍,其中 /26(255.255.255.192) 表示子網(wǎng)掩碼,確定了網(wǎng)絡(luò)的大小。具體來(lái)說(shuō),這個(gè) IP 地址范圍包括從 10.244.169.64 到 10.244.169.127 的所有 IP 地址。

通過(guò)設(shè)備 tunl0 使用協(xié)議 bird 和 onlink 選項(xiàng)發(fā)送,下一跳IP地址為  192.168.26.105(vms105.liruilon...)。

這里的 bird 和 onlink 是路由表中的兩個(gè)選項(xiàng):

bird:是一種路由協(xié)議,它可以幫助路由器動(dòng)態(tài)地學(xué)習(xí)和適應(yīng)網(wǎng)絡(luò)拓?fù)浣Y(jié)構(gòu)的變化。Calico 使用 BGP 協(xié)議來(lái)實(shí)現(xiàn)網(wǎng)絡(luò)功能,Bird 可以用于實(shí)現(xiàn) BGP 路由器.
onlink:選項(xiàng)表示下一跳 IP 地址是直接可達(dá)的,也就是說(shuō),它是在同一子網(wǎng)內(nèi)的。如果下一跳 IP 地址不在同一子網(wǎng)內(nèi),則需要使用網(wǎng)關(guān)來(lái)轉(zhuǎn)發(fā)數(shù)據(jù)包。
然后我們來(lái)到下一跳 IP 地址對(duì)應(yīng)的工作節(jié)點(diǎn) 192.168.26.105

┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$kubectl get pods -n demo -o wide
NAME                               READY   STATUS    RESTARTS   AGE     IP              NODE                          NOMINATED NODE   READINESS GATES
demo-deployment-6cbdbd86d5-fbt9d   1/1     Running   0          4h36m   10.244.169.66   vms105.liruilongs.github.io   <none>           <none>
demo-deployment-6cbdbd86d5-nm467   1/1     Running   0          4h36m   10.244.38.174   vms103.liruilongs.github.io   <none>           <none>
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$
這個(gè)地址實(shí)際上是 目標(biāo) Pod 所在節(jié)點(diǎn)的 IP 地址,查看節(jié)點(diǎn)的路由信息。

┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$ansible-console -i host.yaml --limit 192.168.26.105
Welcome to the ansible console.
Type help or ? to list commands.

root@all (1)[f:5]# ip route
192.168.26.105 | CHANGED | rc=0 >>
default via 192.168.26.2 dev ens32
10.244.31.64/26 via 192.168.26.106 dev tunl0 proto bird onlink
10.244.38.128/26 via 192.168.26.103 dev tunl0 proto bird onlink
10.244.63.64/26 via 192.168.26.102 dev tunl0 proto bird onlink
blackhole 10.244.169.64/26 proto bird
10.244.169.65 dev calid5e76ad523e scope link
10.244.169.66 dev cali39888f400bd scope link
10.244.169.70 dev cali0fdeca04347 scope link
10.244.169.73 dev cali7cf9eedbe64 scope link
10.244.169.77 dev calia011d753862 scope link
10.244.169.78 dev caliaa36e67b275 scope link
10.244.169.90 dev califc24aa4e3bd scope link
10.244.169.119 dev calibdca950861e scope link
10.244.169.120 dev cali40813694dd6 scope link
10.244.169.121 dev calie1fd05af50d scope link
10.244.198.0/26 via 192.168.26.101 dev tunl0 proto bird onlink
10.244.239.128/26 via 192.168.26.100 dev tunl0 proto bird onlink
169.254.0.0/16 dev ens32 scope link metric 1002
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
192.168.26.0/24 dev ens32 proto kernel scope link src 192.168.26.105
通過(guò)路由信息 會(huì)匹配10.244.169.66 dev cali39888f400bd scope link 這個(gè)路由規(guī)則

root@all (1)[f:5]# ip route | grep 66
192.168.26.105 | CHANGED | rc=0 >>
10.244.169.66 dev cali39888f400bd scope link
root@all (1)[f:5]#
這個(gè)規(guī)則匹配的是一個(gè)IP地址,而不是網(wǎng)段。也就是說(shuō),主機(jī)上的每個(gè)容器都會(huì)有一個(gè)對(duì)應(yīng)的路由表項(xiàng)。報(bào)文被發(fā)送到 cali39888f400bd 這個(gè) veth pair

root@all (1)[f:5]# ip a | grep -A 4 cali39888f400bd
192.168.26.105 | CHANGED | rc=0 >>
16: cali39888f400bd@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 9
    inet6 fe80::ecee:eeff:feee:eeee/64 scope link
       valid_lft forever preferred_lft forever
root@all (1)[f:5]#
然后從 cali39888f400bd 一端發(fā)送給目標(biāo)容器的 eth0@if16。目標(biāo)容器接收到報(bào)文之后,回復(fù) ICMP 報(bào)文,應(yīng)答報(bào)文原路返回。

┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$kubectl exec -it demo-deployment-6cbdbd86d5-fbt9d -n demo -- bash
[root@demo-deployment-6cbdbd86d5-fbt9d /]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
4: eth0@if16: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default
    link/ether 42:17:dd:38:6a:10 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.244.169.66/32 scope global eth0
       valid_lft forever preferred_lft forever
簡(jiǎn)單來(lái)回顧一下,跨主機(jī)的 Pod 如何通信?

本質(zhì)是 Linux 中兩個(gè)不同機(jī)器網(wǎng)絡(luò)命名空間(network namespace)的通信,通過(guò)當(dāng)前容器(網(wǎng)絡(luò)命名空間)和宿主機(jī)內(nèi)的一對(duì)虛擬網(wǎng)卡 veth pair ,把數(shù)據(jù)包發(fā)送到宿主節(jié)點(diǎn)上(這里涉及到ARP 代理),然后通過(guò)匹配路由表(這里是網(wǎng)段匹配),下一跳到 目標(biāo)Pod 所在宿主節(jié)點(diǎn),到達(dá) 目標(biāo)Pod 宿主節(jié)點(diǎn) 上之后,在通過(guò) 路由匹配(IP 匹配)到宿主節(jié)點(diǎn)上的虛擬網(wǎng)卡(和容器對(duì)應(yīng)的一對(duì)虛擬網(wǎng)卡 veth pair ),然后從這個(gè)虛擬網(wǎng)卡到容器內(nèi)部的虛擬網(wǎng)卡,實(shí)現(xiàn)請(qǐng)求,之后原路返回。

所以從網(wǎng)絡(luò)命名空間角度理解,是容器 network namespace 到節(jié)點(diǎn) root network namespace,然后 節(jié)點(diǎn) root network namespace 到 容器 network namespace

network namespace 簡(jiǎn)單介紹
network namespace 在Linux內(nèi)核 2.6 版本引入,作用是隔離 Linux 系統(tǒng)的網(wǎng)絡(luò)設(shè)備,以及 IP地址、端口、路由表、防火墻規(guī)則等網(wǎng)絡(luò)資源。因此,每個(gè)網(wǎng)絡(luò) namespace 里都有自己的網(wǎng)絡(luò)設(shè)備(如IP地址、路由表、端口范圍、/proc/net目錄等) .

從網(wǎng)絡(luò)的角度看,network namespace 使得容器非常有用,一個(gè)直觀的例子就是:由于每個(gè)容器都有自己的(虛擬)網(wǎng)絡(luò)設(shè)備,并且容器里的進(jìn)程可以放心地綁定在端口上而不必?fù)?dān)心端口沖突,這就使得在一個(gè)主機(jī)上同時(shí)運(yùn)行多個(gè)監(jiān)聽80端口的Web服務(wù)器變?yōu)榭赡?

初識(shí)network namespace
network namespace 可以通過(guò)系統(tǒng)調(diào)用來(lái)創(chuàng)建, 當(dāng)前 network namespace 的增刪改查功能已經(jīng)集成到 Linux的 ip 工具的 netns 子命令中。

創(chuàng)建一個(gè)名為 netns_demo 的network namespace

┌──[root@liruilongs.github.io]-[~]
└─$ip netns add netns_demo
查看當(dāng)前系統(tǒng)的 network namespace

┌──[root@liruilongs.github.io]-[~]
└─$ip netns list
netns_demo
創(chuàng)建了一個(gè) network namespace 時(shí),系統(tǒng)會(huì)在 /var/run/netns 路徑下面生成一個(gè)掛載點(diǎn)

┌──[root@liruilongs.github.io]-[~]
└─$ls /var/run/netns/netns_demo
/var/run/netns/netns_demo
掛載點(diǎn)的作用

方便對(duì) namespace 的管理
使 namespace 即使沒有進(jìn)程運(yùn)行也能繼續(xù)存在
一個(gè) network namespace 被創(chuàng)建出來(lái)后,可以使用 ip netns exec 命令進(jìn)入,做一些網(wǎng)絡(luò) 查詢/配置 的工作。

查看網(wǎng)絡(luò)命名空間信息,沒有任何配置,因此只有一塊系統(tǒng)默認(rèn)的本地回環(huán)設(shè)備lo。

┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec netns_demo ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
對(duì)于刪除 network namespace,可以通過(guò)以下命令實(shí)現(xiàn):

┌──[root@liruilongs.github.io]-[~]
└─$ip netns delete netns_demo
┌──[root@liruilongs.github.io]-[~]
└─$ip netns list
┌──[root@liruilongs.github.io]-[~]
└─$
注意,上面這條命令實(shí)際上并沒有刪除netns_demo這個(gè)network namespace,它只是移除了這個(gè)network  namespace對(duì)應(yīng)的掛載點(diǎn)。只要里面還有進(jìn)程運(yùn)行著,network  namespace便會(huì)一直存在。

配置 network namespace
當(dāng)嘗試訪問(wèn)創(chuàng)建的 網(wǎng)絡(luò) 命名空間的本地回環(huán)地址時(shí),網(wǎng)絡(luò)是不通的

┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec netns_demo ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
這是因?yàn)楸镜鼗丨h(huán)地址對(duì)應(yīng)的網(wǎng)卡設(shè)備連接默認(rèn)是禁用的,需要下面的方式激活。

┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec netns_demo ip  link set dev lo up
激活之后,查看網(wǎng)絡(luò)命名空間網(wǎng)卡的詳細(xì)信息

┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec netns_demo ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
做 ping 測(cè)試

┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec netns_demo ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.047 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.047 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.048 ms
^C
--- 127.0.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2022ms
rtt min/avg/max/mdev = 0.047/0.047/0.048/0.005 ms
┌──[root@liruilongs.github.io]-[~]
└─$
僅有一個(gè)本地回環(huán)設(shè)備是沒法與外界通信的。如果我們想與外界(比如主機(jī)上的網(wǎng)卡)進(jìn)行通信,需要構(gòu)建一個(gè)通道,就需要在 namespace 里再 創(chuàng)建一對(duì)虛擬的以太網(wǎng)卡,即所謂的 veth pair。






顧名思義,veth pair 總是成對(duì)出現(xiàn)且相互連接,它就像 Linux 的雙向管道(pipe),報(bào)文從 veth pair一端進(jìn)去就會(huì)由另一端收到

創(chuàng)建 veth0 和 veth1 這么一對(duì)虛擬以太網(wǎng)卡

┌──[root@liruilongs.github.io]-[~]
└─$ip link add veth0 type veth peer name veth1
在默認(rèn)情況下,它們都在主機(jī)的  root network namespce 中,將其中一塊虛擬網(wǎng)卡 veth1 通過(guò) ip link set 命令移動(dòng)到 之前創(chuàng)建的 network namespace`。

┌──[root@liruilongs.github.io]-[~]
└─$ip link set veth1 netns  netns_demo
查看 創(chuàng)建的網(wǎng)絡(luò)命名空間內(nèi)的網(wǎng)絡(luò)設(shè)備信息,添加了一塊新的網(wǎng)卡  veth1@if4

┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec netns_demo ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
3: veth1@if4: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
    link/ether 5a:af:75:80:a6:ba brd ff:ff:ff:ff:ff:ff link-netnsid 0
主機(jī)網(wǎng)卡信息查看,多了一塊 veth0@if3 的虛擬網(wǎng)卡

┌──[root@liruilongs.github.io]-[~]
└─$ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: ens32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:0c:29:e5:68:68 brd ff:ff:ff:ff:ff:ff
    inet 192.168.26.152/24 brd 192.168.26.255 scope global dynamic ens32
       valid_lft 1377sec preferred_lft 1377sec
    inet6 fe80::20c:29ff:fee5:6868/64 scope link
       valid_lft forever preferred_lft forever
4: veth0@if3: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
    link/ether be:2a:81:c1:e4:24 brd ff:ff:ff:ff:ff:ff link-netnsid 0
┌──[root@liruilongs.github.io]-[~]
└─$
這兩塊網(wǎng)卡剛創(chuàng)建出來(lái)還都是 DOWN 狀態(tài),需要手動(dòng)把狀態(tài)設(shè)置成 UP ,這里同時(shí)設(shè)置 IP 地址

┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec netns_demo ifconfig veth1 10.1.1.1/24 up
┌──[root@liruilongs.github.io]-[~]
└─$ifconfig veth0 10.1.1.2/24 up
ping 測(cè)試。在網(wǎng)絡(luò)命名空間內(nèi) ping 主機(jī)的 veth pair 對(duì)應(yīng)的網(wǎng)卡 IP 。

┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec  netns_demo ping 10.1.1.2 -c 3
PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data.
64 bytes from 10.1.1.2: icmp_seq=1 ttl=64 time=0.058 ms
64 bytes from 10.1.1.2: icmp_seq=2 ttl=64 time=0.060 ms
64 bytes from 10.1.1.2: icmp_seq=3 ttl=64 time=0.060 ms

--- 10.1.1.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2020ms
rtt min/avg/max/mdev = 0.058/0.059/0.060/0.006 ms
┌──[root@liruilongs.github.io]-[~]
└─$
不同 network namespace 之間的 路由表和防火墻規(guī)則 等也是隔離的,因此我們剛剛創(chuàng)建的 network namespace 沒法和主機(jī)共享路由表和防火墻

需要注意的是,用戶可以隨意將虛擬網(wǎng)絡(luò)設(shè)備分配到自定義的 network namespace 里,而連接真實(shí)硬件的物理設(shè)備則只能放在系統(tǒng)的根  network namesapce 中。并且,任何一個(gè)網(wǎng)絡(luò)設(shè)備最多只能存在于一個(gè) network namespace 中。

veth pair 簡(jiǎn)單介紹
在 Docker 或 Kubernetes 的環(huán)境中,如果在主機(jī)上查詢網(wǎng)卡信息的時(shí)候,總會(huì)出來(lái)一大堆虛擬網(wǎng)卡,這些虛擬網(wǎng)卡就是Docker/Kubernetes為容器而創(chuàng)建的。

veth 是虛擬以太網(wǎng)卡(Virtual  Ethernet)的縮寫,veth 設(shè)備總是成對(duì)的,因此我們稱之為 veth pair

veth pair 一端發(fā)送的數(shù)據(jù)會(huì)在另外一端接收,非常像 Linux 的雙向管道。根據(jù)這一特性,veth pair 常被用于跨network namespace 之間的通信,即分別將 veth pair 的兩端放在不同的 namespace 里。

僅有 veth  pair 設(shè)備,容器是無(wú)法訪問(wèn)外部網(wǎng)絡(luò)的。為什么呢?因?yàn)閺娜萜靼l(fā)出的數(shù)據(jù)包,實(shí)際上是直接進(jìn)了 veth pair 設(shè)備的協(xié)議棧。

如果容器需要訪問(wèn)網(wǎng)絡(luò),則需要使用網(wǎng)橋等技術(shù)將 veth pair 設(shè)備接收的數(shù)據(jù)包通過(guò)某種方式轉(zhuǎn)發(fā)出去。在 docker 中,我們通過(guò) 網(wǎng)橋的方式訪問(wèn),docker 在啟動(dòng)時(shí)會(huì)自動(dòng)創(chuàng)建一個(gè) docker0 的 Linux 網(wǎng)橋.

veth pair的創(chuàng)建和使用
通過(guò)下面的命令創(chuàng)建一對(duì) veth pair

┌──[root@liruilongs.github.io]-[~]
└─$ip  link add veth3 type veth peer name veth4
創(chuàng)建的veth pair在主機(jī)上表現(xiàn)為兩塊網(wǎng)卡,我們可以通過(guò)ip link 命令查看:

┌──[root@liruilongs.github.io]-[~]
└─$ip link list
........
5: veth4@veth3: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
    link/ether de:3a:5e:42:2d:ff brd ff:ff:ff:ff:ff:ff
6: veth3@veth4: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
    link/ether 06:8f:b5:e8:e9:f6 brd ff:ff:ff:ff:ff:ff
新創(chuàng)建的 veth pair 設(shè)備的默認(rèn) mtu 是 1500,設(shè)備初始狀態(tài)是 DOWN。我們同樣可以使用 ip link 命令將這兩塊網(wǎng)卡的狀態(tài)設(shè)置為UP。

┌──[root@liruilongs.github.io]-[~]
└─$ip link set dev veth3 up
┌──[root@liruilongs.github.io]-[~]
└─$ip link set dev veth4 up
veth pair 設(shè)備同樣可以配置IP地址,命令如下:

┌──[root@liruilongs.github.io]-[~]
└─$ifconfig veth3 10.20.30.40/24
┌──[root@liruilongs.github.io]-[~]
└─$ifconfig veth4 10.20.30.41/24
┌──[root@liruilongs.github.io]-[~]
└─$
可以將veth pair設(shè)備放到namespace中。把 veth4 放到 網(wǎng)絡(luò)命令空間 netns_demo 中

┌──[root@liruilongs.github.io]-[~]
└─$ip link  set veth4 netns netns_demo
在命令空間中查看

┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec netns_demo ip link list
......
5: veth4@if6: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
    link/ether de:3a:5e:42:2d:ff brd ff:ff:ff:ff:ff:ff link-netnsid 0
主機(jī)環(huán)境中,原來(lái)索引為 6 的 veth4 虛擬網(wǎng)卡已經(jīng)看不到了

┌──[root@liruilongs.github.io]-[~]
└─$ifconfig
...........
veth3: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 10.20.30.40  netmask 255.255.255.0  broadcast 10.20.30.255
        inet6 fe80::48f:b5ff:fee8:e9f6  prefixlen 64  scopeid 0x20<link>
        ether 06:8f:b5:e8:e9:f6  txqueuelen 1000  (Ethernet)
        RX packets 8  bytes 648 (648.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 8  bytes 648 (648.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

┌──[root@liruilongs.github.io]-[~]
└─$
veth pair 設(shè)備的原理較簡(jiǎn)單,就是向veth pair設(shè)備的一端輸入數(shù)據(jù),數(shù)據(jù)通過(guò)內(nèi)核協(xié)議棧后從 veth pair 的另一端出來(lái)。veth pair 的基本工作原理:

veth pair內(nèi)核實(shí)現(xiàn)
在 veth pair 設(shè)備上,任意一端(RX)接收的數(shù)據(jù)都會(huì)在另一端(TX)發(fā)送出去,veth pair 在轉(zhuǎn)發(fā)過(guò)程中不會(huì)篡改數(shù)據(jù)包的內(nèi)容

容器與 host veth pair 的關(guān)系
經(jīng)典容器組網(wǎng)模型就是 veth pair+bridge 的模式。容器中的 eth0 實(shí)際上和外面 host 上的某個(gè) veth 是成對(duì)的(pair)關(guān)系.

可以通過(guò)下面兩種方式來(lái)獲取對(duì)應(yīng)關(guān)系

方法一
容器里面看 /sys/class/net/eth0/iflink

┌──[root@liruilongs.github.io]-[/]
└─$ docker exec -it  6471704fd03a sh
/ # cat /sys/class/net/eth0/if
ifalias  ifindex  iflink
/ # cat /sys/class/net/eth0/iflink
95
/ # exit
然后,在主機(jī)上遍歷 /sys/claas/net 下面的全部目錄,查看子目錄 ifindex 的值和容器里查出來(lái)的 iflink 值相當(dāng)?shù)?veth 名字,這樣就找到了容器和主機(jī)的 veth pair 關(guān)系。

┌──[root@liruilongs.github.io]-[/]
└─$ grep -c 95 /sys/class/net/*/ifindex | grep 1
/sys/class/net/veth2e08884/ifindex:1
方法二
從上面的命令輸出可以看到 116:eth0@if117,其中116是eth0接口的 index,117是和它成對(duì)的veth的index。當(dāng)host執(zhí)行下面的命令時(shí),可以看到對(duì)應(yīng)117的veth網(wǎng)卡是哪一個(gè), 這樣就得到了容器和veth pair的關(guān)系(這整句忽略哈,忘記調(diào)整了 ^_^)

在目標(biāo)容器里執(zhí)行以下命令,獲取網(wǎng)卡索引為 94,其中 94 是 eth0 接口的index,95 是和它成對(duì)的veth的index。

┌──[root@liruilongs.github.io]-[/]
└─$ docker exec -it  6471704fd03a sh
/ # ip link show eth0
94: eth0@if95: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
/ # exit
通過(guò) 95  index 來(lái)定位主機(jī)上對(duì)應(yīng)的虛擬網(wǎng)卡

┌──[root@liruilongs.github.io]-[/]
└─$ ip link show | grep 95
95: veth2e08884@if94: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT
┌──[root@liruilongs.github.io]-[/]
└─$
關(guān)于 K8s 中 Pod 之間是如何通信的(SDN 使用 Calico)?就和小伙伴們分享到這里,生活加油 ^_^

作者:山河已無(wú)恙


歡迎關(guān)注微信公眾號(hào) :山河已無(wú)恙