-
一個Pod一個IP
-
所有的 Pod 可以與任何其他 Pod 直接通信,無需使用 NAT 映射
-
所有節點可以與所有 Pod 直接通信,無需使用 NAT 映射
-
-
網絡的命名空間:Linux在網絡棧中引入網絡命名空間,將獨立的網絡協議棧隔離到不同的命令空間中,彼此間無法通信;Docker利用這一特性,實現不同容器間的網絡隔離。
-
Veth設備對:Veth設備對的引入是為了實現在不同網絡命名空間的通信。
-
Iptables/Netfilter:Docker使用Netfilter實現容器網絡轉發。
-
網橋:網橋是一個二層網絡設備,通過網橋可以將Linux支持的不同的端口連接起來,並實現類似交換機那樣的多對多的通信。
-
路由:Linux系統包含一個完整的路由功能,當IP層在處理數據發送或轉發的時候,會使用路由表來決定發往哪里。
實現:
Pod之間通信會有兩種情況:
-
兩個Pod在同一個Node上
-
兩個Pod在不同Node上
-
-
網橋 cbr0 中為 veth0 配置了一個網段。一旦數據包到達網橋,網橋使用ARP 協議解析出其正確的目標網段 veth1;
-
網橋 cbr0 將數據包發送到 veth1;
-
# ls /opt/cni/bin/
當你在宿主機上部署Flanneld后,flanneld 啟動后會在每台宿主機上生成它對應的CNI 配置文件(它其實是一個 ConfigMap),從而告訴Kubernetes,這個集群要使用 Flannel 作為容器網絡方案。
/etc/cni/net.d/10-flannel.conflist
--network-plugin=cni \ --cni-conf-dir=/etc/cni/net.d \ --cni-bin-dir=/opt/cni/bin
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
-
UDP:最早支持的一種方式,由於性能最差,目前已經棄用。
-
-
Host-GW:Flannel通過在各個節點上的Agent進程,將容器網絡的路由信息刷到主機的路由表上,這樣一來所有的主機都有整個容器網絡的路由數據了。
-
# kubeadm部署指定Pod網段 kubeadm init --pod-network-cidr=10.244.0.0/16 # 二進制部署指定 cat /opt/kubernetes/cfg/kube-controller-manager.conf --allocate-node-cidrs=true \ --cluster-cidr=10.244.0.0/16 \ #配置文件 kube-flannel.yml net-conf.json: | { "Network": "10.244.0.0/16", "Backend": { "Type": "vxlan" } }
為了能夠在二層網絡上打通“隧道”,VXLAN 會在宿主機上設置一個特殊的網絡設備作為“隧道”的兩端。這個設備就叫作 VTEP,即:VXLAN Tunnel End Point(虛擬隧道端點)。
如果Pod 1訪問Pod 2,源地址10.244.2.250,目的地址10.244.1.33 ,數據包傳輸流程如下:
1、容器路由:容器根據路由表從eth0發出 # kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES busybox-6cd57fd969-4n6fn 1/1 Running 0 61s 10.244.2.50 k8s-master1 <none> <none> web-5675686b8-rc9bf 1/1 Running 0 7m12s 10.244.1.33 k8s-node01 <none> <none> web-5675686b8-rrc6f 1/1 Running 0 7m12s 10.244.2.49 k8s-master1 <none> <none> # kubectl exec -it busybox-6cd57fd969-4n6fn sh / # ip route default via 10.244.2.1 dev eth0 10.244.0.0/16 via 10.244.2.1 dev eth0 10.244.2.0/24 dev eth0 scope link src 10.244.2.51 2、主機路由:數據包進入到宿主機虛擬網卡cni0,根據路由表轉發到flannel.1虛擬網卡,也就是,來到了隧道的入口。 # ip route default via 192.168.0.1 dev ens32 proto static metric 100 10.244.0.0/24 via 10.244.0.0 dev flannel.1 onlink 10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink 3、VXLAN封裝:而這些VTEP設備(二層)之間組成二層網絡必須要知道目的MAC地址。這個MAC地址從哪獲取到呢?其實在flanneld進程啟動后,就會自動添加其他節點ARP記錄,可以通過ip命令查看,如下所示: # ip neigh show dev flannel.1 10.244.0.0 lladdr 06:6a:9d:15:ac:e0 PERMANENT 10.244.1.0 lladdr 36:68:64:fb:4f:9a PERMANENT 4、二次封包:知道了目的MAC地址,封裝二層數據幀(容器源IP和目的IP)后,對於宿主機網絡來說這個幀並沒有什么實際意義。接下來,Linux內核還要把這個數據幀進一步封裝成為宿主機網絡的一個普通數據幀,好讓它載着內部數據幀,通過宿主機的eth0網卡進行傳輸。 5、封裝到UDP包發出去:現在能直接發UDP包嘛?到目前為止,我們只知道另一端的flannel.1設備的MAC地址,卻不知道對應的宿主機地址是什么。 flanneld進程也維護着一個叫做FDB的轉發數據庫,可以通過bridge fdb命令查看: # bridge fdb show dev flannel.1 06:6a:9d:15:ac:e0 dst 192.168.0.134 self permanent 36:68:64:fb:4f:9a dst 192.168.0.133 self permanent 可以看到,上面用的對方flannel.1的MAC地址對應宿主機IP,也就是UDP要發往的目的地。使用這個目的IP進行封裝。 6、數據包到達目的宿主機:Node1的eth0網卡發出去,發現是VXLAN數據包,把它交給flannel.1設備。flannel.1設備則會進一步拆包,取出原始二層數據幀包,發送ARP請求,經由cni0網橋轉發給container。
# kube-flannel.yml net-conf.json: | { "Network": "10.244.0.0/16", "Backend": { "Type": "host-gw" } }
當你設置flannel使用host-gw模式,flanneld會在宿主機上創建節點的路由表:
# ip route
default via 192.168.0.1 dev ens32 proto static metric 100
10.244.0.0/24 via 192.168.0.134 dev ens32
10.244.1.0/24 via 192.168.0.133 dev ens32
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
192.168.0.0/24 dev ens32 proto kernel scope link src 192.168.0.132 metric 100
一旦配置了下一跳地址,那么接下來,當 IP 包從網絡層進入鏈路層封裝成幀的時候,eth0 設備就會使用下一跳地址對應的 MAC 地址,作為該數據幀的目的 MAC 地址。
# kubectl edit cm kube-flannel-cfg -n kube-system net-conf.json: | { "Network": "10.244.0.0/16", "Backend": { "Type": "vxlan" "Directrouting": true } } # kubectl get cm kube-flannel-cfg -o json -n kube-system "net-conf.json": "{\n \"Network\": \"10.244.0.0/16\",\n \"Backend\": {\n \"Type\": \"vxlan\"\n \"Directrouting\": true\n }\n}\n"
小結:
1、vxlan 不受網絡環境限制,只要三層可達就行
2、vxlan 需要二層解封包,降低工作效率
3、hostgw 基於路由表轉發,效率更高
4、hostgw 只適用於二層網絡(本身網絡架構受限,節點數量也受限)
Calico 在每一個計算節點利用 Linux Kernel 實現了一個高效的虛擬路由器( vRouter) 來負責數據轉發,而每個 vRouter 通過 BGP 協議負責把自己上運行的 workload 的路由信息向整個 Calico 網絡內傳播。
BGP英文全稱是Border Gateway Protocol,即邊界網關協議,它是一種自治系統間的動態路由發現協議,與其他 BGP 系統交換網絡可達信息。
-
-
BGP Client(BIRD):主要負責把 Felix 寫入 Kernel 的路由信息分發到集群 Calico 網絡。
-
Etcd:分布式鍵值存儲,保存Calico的策略和網絡配置狀態。
-
curl https://docs.projectcalico.org/v3.9/manifests/calico-etcd.yaml -o calico.yaml
具體步驟如下:
-
配置連接etcd地址,如果使用https,還需要配置證書。(ConfigMap,Secret)
-
根據實際網絡規划修改Pod CIDR(CALICO_IPV4POOL_CIDR)
-
選擇工作模式(CALICO_IPV4POOL_IPIP),支持BGP(Never)、IPIP(Always)、CrossSubnet
# kubectl delete -f kube-flannel.yaml # ip link delete flannel.1 # ip link delete cni0 # ip route del 10.244.2.0/24 via 192.168.0.132 dev ens32 # ip route del 10.244.1.0/24 via 192.168.0.133 dev ens32
應用清單:
# kubectl apply -f calico.yaml
# kubectl get pods -n kube-system
# wget -O /usr/local/bin/calicoctl https://github.com/projectcalico/calicoctl/releases/download/v3.9.1/calicoctl # chmod +x /usr/local/bin/calicoctl # mkdir /etc/calico # vim /etc/calico/calicoctl.cfg apiVersion: projectcalico.org/v3 kind: CalicoAPIConfig metadata: spec: datastoreType: "etcdv3" etcdEndpoints: "https://192.168.0.132:2379,https://192.168.0.133:2379,https://192.168.0.134:2379" etcdKeyFile: "/opt/etcd/ssl/server-key.pem" etcdCertFile: "/opt/etcd/ssl/server.pem" etcdCACertFile: "/opt/etcd/ssl/ca.pem"
使用calicoctl查看服務狀態:
# calicoctl get node NAME k8s-master k8s-node01 k8s-node02 # calicoctl node status IPv4 BGP status +---------------+-------------------+-------+----------+-------------+ | PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO | +---------------+-------------------+-------+----------+-------------+ | 192.168.0.133 | node-to-node mesh | up | 07:24:45 | Established | | 192.168.0.134 | node-to-node mesh | up | 07:25:00 | Established | +---------------+-------------------+-------+----------+-------------+ 查看 IPAM的IP地址池: # calicoctl get ippool -o wide NAME CIDR NAT IPIPMODE VXLANMODE DISABLED SELECTOR default-ipv4-ippool 10.244.0.0/16 true Never Never false all()
-
-
宿主機根據路由規則,將數據包轉發給下一跳(網關);
-
到達Node2,根據路由規則將數據包轉發給cali設備,從而到達容器2。
# node1 default via 192.168.0.1 dev ens32 proto static metric 100 10.244.0.0/24 via 192.168.0.134 dev ens32 10.244.58.192/26 via 192.168.0.134 dev ens32 proto bird 10.244.85.192 dev calic32fa4483b3 scope link blackhole 10.244.85.192/26 proto bird 10.244.235.192/26 via 192.168.0.132 dev ens32 proto bird # node2 default via 192.168.0.1 dev ens32 proto static metric 100 10.244.58.192 dev calib29fd680177 scope link blackhole 10.244.58.192/26 proto bird 10.244.85.192/26 via 192.168.0.133 dev ens32 proto bird 10.244.235.192/26 via 192.168.0.132 dev ens32 proto bird
# netstat -antp |grep bird tcp 0 0 0.0.0.0:179 0.0.0.0:* LISTEN 23124/bird tcp 0 0 192.168.0.134:44366 192.168.0.132:179 ESTABLISHED 23124/bird tcp 0 0 192.168.0.134:1215 192.168.0.133:179 ESTABLISHED 23124/bird
這時就需要使用 Route Reflector(路由器反射)模式解決這個問題。
確定一個或多個Calico節點充當路由反射器,讓其他節點從這個RR節點獲取路由信息。
1、關閉 node-to-node BGP網格
添加 default BGP配置,調整 nodeToNodeMeshEnabled和asNumber:
cat << EOF | calicoctl create -f - apiVersion: projectcalico.org/v3 kind: BGPConfiguration metadata: name: default spec: logSeverityScreen: Info nodeToNodeMeshEnabled: false asNumber: 63400 EOF
ASN號可以通過獲取 # calicoctl get nodes --output=wide
為方便讓BGPPeer輕松選擇節點,通過標簽選擇器匹配。
kubectl label node k8s-node01 route-reflector=true
然后配置路由器反射器節點routeReflectorClusterID:
# calicoctl get node k8s-node01 -o yaml > rr-node.yaml # vim rr-node.yaml apiVersion: projectcalico.org/v3 kind: Node apiVersion: projectcalico.org/v3 kind: Node metadata: annotations: projectcalico.org/kube-labels: '{"beta.kubernetes.io/arch":"amd64","beta.kubernetes.io/os":"linux","kubernetes.io/arch":"amd64","kubernetes.io/hostname":"k8s-node01","kubernetes.io/os":"linux"}' creationTimestamp: 2020-06-04T07:24:40Z labels: beta.kubernetes.io/arch: amd64 beta.kubernetes.io/os: linux kubernetes.io/arch: amd64 kubernetes.io/hostname: k8s-node01 kubernetes.io/os: linux name: k8s-node01 resourceVersion: "50638" uid: 16452357-d399-4247-bc2b-ab7e7eb52dbc spec: bgp: ipv4Address: 192.168.0.133/24 routeReflectorClusterID: 244.0.0.1 orchRefs: - nodeName: k8s-node01 orchestrator: k8s
# calicoctl apply -f bgppeer.yaml
Successfully applied 1 'BGPPeer' resource(s)
現在,很容易使用標簽選擇器將路由反射器節點與其他非路由反射器節點配置為對等:
# vi bgppeer.yaml apiVersion: projectcalico.org/v3 kind: BGPPeer metadata: name: peer-with-route-reflectors spec: nodeSelector: all() peerSelector: route-reflector == 'true'
# calicoctl apply -f bgppeer.yaml
Successfully applied 1 'BGPPeer' resource(s)
查看節點的BGP連接狀態:
# calicoctl node status Calico process is running. IPv4 BGP status +---------------+---------------+-------+----------+-------------+ | PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO | +---------------+---------------+-------+----------+-------------+ | 192.168.0.133 | node specific | up | 09:54:05 | Established | +---------------+---------------+-------+----------+-------------+
# calicoctl get ipPool -o yaml > ipip.yaml # vim ipip.yaml apiVersion: projectcalico.org/v3 items: - apiVersion: projectcalico.org/v3 kind: IPPool metadata: creationTimestamp: 2020-06-04T07:24:39Z name: default-ipv4-ippool resourceVersion: "50630" uid: e6515b21-44eb-4de9-8dc2-42f291da4273 spec: blockSize: 26 cidr: 10.244.0.0/16 ipipMode: Always natOutgoing: true nodeSelector: all() vxlanMode: Never kind: IPPoolList metadata: resourceVersion: "76218" # calicoctl apply -f ipip.yaml Successfully applied 1 'IPPool' resource(s) # calicoctl get ippool -o wide NAME CIDR NAT IPIPMODE VXLANMODE DISABLED SELECTOR default-ipv4-ippool 10.244.0.0/16 true Always Never false all()
-
數據包從容器1出到達Veth Pair另一端(宿主機上,以cali前綴開頭);
-
進入IP隧道設備(tunl0)由Linux內核IPIP驅動封裝在宿主機網絡的IP包中(新的IP包目的地之是原IP包的下一跳地址),這樣就成了Node1 到Node2的數據包;
-
數據包經過路由器三層轉發到Node2;
-
Node2收到數據包后,網絡協議棧會使用IPIP驅動進行解包,從中拿到原始IP包;
-
# node01 10.244.58.192/26 via 192.168.0.134 dev tunl0 proto bird onlink 10.244.85.192 dev calic32fa4483b3 scope link # node02 10.244.58.192 dev calib29fd680177 scope link 10.244.85.192/26 via 192.168.0.133 dev tunl0 proto bird onlink
不難看到,當 Calico 使用 IPIP 模式的時候,集群的網絡性能會因為額外的封包和解包工作而下降。所以建議你將所有宿主機節點放在一個子網里,避免使用 IPIP。
-
需要細粒度網絡訪問控制?
-
追求網絡性能?
-
服務器之前是否可以跑BGP協議?
-
集群規模多大?
-
-
應用程序間的訪問控制。例如微服務A允許訪問微服務B,微服務C不能訪問微服務A
-
開發環境命名空間不能訪問測試環境命名空間Pod
-
當Pod暴露到外部時,需要做Pod白名單
-
多租戶網絡環境隔離
所以,我們需要使用network policy對Pod網絡進行隔離。支持對Pod級別和Namespace級別網絡訪問控制。
Pod網絡入口方向隔離
-
基於Pod級網絡隔離:只允許特定對象訪問Pod(使用標簽定義),允許白名單上的IP地址或者IP段訪問Pod
-
基於Namespace級網絡隔離:多個命名空間,A和B命名空間Pod完全隔離。
Pod網絡出口方向隔離
-
-
基於目的IP的網絡隔離:只允許Pod訪問白名單上的IP地址或者IP段
-
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: test-network-policy namespace: default spec: podSelector: matchLabels: role: db policyTypes: - Ingress - Egress ingress: - from: - ipBlock: cidr: 172.17.0.0/16 except: - 172.17.1.0/24 - namespaceSelector: matchLabels: project: myproject - podSelector: matchLabels: role: frontend ports: - protocol: TCP port: 6379 egress: - to: - ipBlock: cidr: 10.0.0.0/24 ports: - protocol: TCP port: 5978
-
podSelector:用於選擇策略應用到的Pod組。
-
-
Ingress:from是可以訪問的白名單,可以來自於IP段、命名空間、Pod標簽等,ports是可以訪問的端口。
-
# kubectl create deployment web --image=nginx # kubectl run client1 --generator=run-pod/v1 --image=busybox --command -- sleep 36000 # kubectl run client2 --generator=run-pod/v1 --image=busybox --command -- sleep 36000 # kubectl get pods --show-labels NAME READY STATUS RESTARTS AGE LABELS client1 1/1 Running 0 49s run=client1 client2 1/1 Running 0 49s run=client2 web-5886dfbb96-8mkg9 1/1 Running 0 136m app=web,pod-template-hash=5886dfbb96 web-5886dfbb96-hps6t 1/1 Running 0 136m app=web,pod-template-hash=5886dfbb96 web-5886dfbb96-lckfs 1/1 Running 0 136m app=web,pod-template-hash=5886dfbb96
需求:將default命名空間攜帶run=web標簽的Pod隔離,只允許default命名空間攜帶run=client1標簽的Pod訪問80端口
# vim pod-acl.yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: test-network-policy namespace: default spec: podSelector: matchLabels: app: web policyTypes: - Ingress ingress: - from: - namespaceSelector: matchLabels: project: default - podSelector: matchLabels: run: client1 ports: - protocol: TCP port: 80 # kubectl apply -f pod-acl.yaml
測試
# kubectl exec -it client1 sh / # wget 10.244.85.192 Connecting to 10.244.85.192 (10.244.85.192:80) saving to 'index.html' index.html 100% |**************************************************************************************************************| 612 0:00:00 ETA 'index.html' saved # kubectl exec -it client2 sh / # wget 10.244.85.192 Connecting to 10.244.85.192 (10.244.85.192:80) wget: can't connect to remote host (10.244.85.192): Connection timed out
Pod對象:default命名空間攜帶run=web標簽的Pod
允許訪問端口:80
允許訪問對象:default命名空間攜帶run=client1標簽的Pod
# kubectl create ns test namespace/test created # kubectl create deployment web --image=nginx -n test deployment.apps/web created # kubectl run client -n test --generator=run-pod/v1 --image=busybox --command -- sleep 36000 # vim ns-acl.yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: deny-from-other-namespaces namespace: default spec: podSelector: {} policyTypes: - Ingress ingress: - from: - podSelector: {} # kubectl apply -f ns-acl.yaml # kubectl get pod -n test -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES client 1/1 Running 0 119s 10.244.85.195 k8s-node01 <none> <none> web-d86c95cc9-gr88v 1/1 Running 0 10m 10.244.235.198 k8s-master1 <none> <none> [root@k8s-master ~]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES web-5886dfbb96-8mkg9 1/1 Running 0 158m 10.244.235.193 k8s-master1 <none> <none> web-5886dfbb96-hps6t 1/1 Running 0 158m 10.244.85.192 k8s-node01 <none> <none> web-5886dfbb96-lckfs 1/1 Running 0 158m 10.244.58.192 k8s-node02 <none> <none> # kubectl exec -it client -n test sh / # wget 10.244.58.192 Connecting to 10.244.58.192 (10.244.58.192:80) wget: can't connect to remote host (10.244.58.192): Connection timed out / # wget 10.244.235.198 Connecting to 10.244.235.198 (10.244.235.198:80) saving to 'index.html' index.html 100% |**************************************************************************************************************| 612 0:00:00 ETA 'index.html' saved