Kubernetes之NetworkPolicy,F(xiàn)lannel和Calico

Pod是Kubernetes調(diào)度的最小單元。一個(gè)Pod可以包含一個(gè)或多個(gè)容器,因此它可以被看作是內(nèi)部容器的邏輯宿主機(jī)。Pod的設(shè)計(jì)理念是為了支持多個(gè)容器在一個(gè)Pod中共享網(wǎng)絡(luò)和文件系統(tǒng)。那么為什么Pod內(nèi)的容器能夠共享網(wǎng)絡(luò),IPC和PID命名空間?

原因:Kubernetes在每個(gè)Pod啟動(dòng)時(shí),會(huì)自動(dòng)創(chuàng)建一個(gè)鏡像為gcr.io/google_containers/pause:version的容器,所有處于該P(yáng)od中的容器在啟動(dòng)時(shí)都會(huì)添加諸如--net=container:pause --ipc=contianer:pause --pid=container:pause的啟動(dòng)參數(shù),因此Pod內(nèi)所有容器共用pause容器的network,IPC和PID命名空間。所有容器共享pause容器的IP地址,也被稱為Pod IP。因此處于同一個(gè)Pod內(nèi)的容器,可以通過localhost進(jìn)行相互訪問。

在講K8s Pod間通信前,我們先復(fù)習(xí)一下Docker容器間的網(wǎng)絡(luò)通信。默認(rèn)情況下,Docker使用一種名為bridge的網(wǎng)絡(luò)模型。如下圖所示:

bridge.png

Docker引擎在啟動(dòng)時(shí),會(huì)在宿主機(jī)上創(chuàng)建一個(gè)名為docker0的虛擬網(wǎng)橋,這個(gè)虛擬網(wǎng)橋負(fù)責(zé)給所有容器分配不重復(fù)的ip地址以及容器間的網(wǎng)絡(luò)通信。首先,Docker在創(chuàng)建一個(gè)容器時(shí),會(huì)執(zhí)行以下操作:

  1. 創(chuàng)建一對veth pair,一端置于容器中,一端置于docker0虛擬網(wǎng)橋中,從而實(shí)現(xiàn)網(wǎng)橋和容器的通信。
  2. 將容器中的veth命名為eth0,docker0網(wǎng)橋中的veth指定一個(gè)唯一的名字,例如veth0ac884e
  3. 從docker0網(wǎng)橋可用的地址段中選取一個(gè)分配給容器,并配置默認(rèn)路由到docker0網(wǎng)橋的veth0ac884e
  4. 這樣,容器就能夠通過虛擬網(wǎng)卡eth0和其他容器進(jìn)行通信了。當(dāng)該容器結(jié)束后,eth0會(huì)隨該容器的網(wǎng)絡(luò)命名空間一起被清除,相對應(yīng)的veth0ac884e也會(huì)被docker0網(wǎng)橋自動(dòng)卸載掉。

通過這個(gè)docker0網(wǎng)橋,同一宿主機(jī)上的容器可以互相通信。然而由于宿主機(jī)的IP地址與容器veth pair的 IP地址均不在同一個(gè)網(wǎng)段,故僅僅依靠veth pair和namespace的技術(shù),還不足以使宿主機(jī)以外的網(wǎng)絡(luò)主動(dòng)發(fā)現(xiàn)容器的存在。為了使外界可以訪問容器中的進(jìn)程,docker采用了端口綁定的方式,也就是通過iptables的NAT,將宿主機(jī)上的端口端口流量轉(zhuǎn)發(fā)到容器內(nèi)的端口上。

K8s 中的網(wǎng)絡(luò)

K8s 默認(rèn)不提供網(wǎng)絡(luò)功能,所有Pod的網(wǎng)絡(luò)功能,都依賴于宿主機(jī)上的Docker。因此,Pod IP即是依靠docker0網(wǎng)橋分配給pause容器的虛擬IP。

同一個(gè)Node上Pod的通信

同一個(gè)Node內(nèi),不同的Pod都有一個(gè)docker0網(wǎng)橋分配的IP,可以直接通過這個(gè)IP進(jìn)行通信。Pod IP和docker0在同一個(gè)網(wǎng)段。因此,當(dāng)同節(jié)點(diǎn)上的Pod-A發(fā)包給Pod-B時(shí),包傳送路線如下:

pod-a的eth0—>pod-a的vethxxx—>bridge0—>pod-b的vethxxx—>pod-b的eth0

不同Node間Pod的通信

不同的Node之間,Node的IP相當(dāng)于外網(wǎng)IP,可以直接訪問,而Node內(nèi)的docker0和Pod的IP則是內(nèi)網(wǎng)IP,無法直接跨Node訪問。因此,不同Node上的Pod間需要通信,需要滿足以下兩個(gè)條件:

  1. 對整個(gè)集群中的Pod-IP分配進(jìn)行規(guī)劃,不能有沖突
  2. 將Node-IP與該Node上的Pod-IP關(guān)聯(lián)起來,通過Node-IP再轉(zhuǎn)發(fā)到Pod-IP

因此,為了實(shí)現(xiàn)以上兩個(gè)需求,K8s提供了CNI(Container Network Interface)供第三方實(shí)現(xiàn)從而進(jìn)行網(wǎng)絡(luò)管理。由此出現(xiàn)了一系列開源的Kubernetes中的網(wǎng)絡(luò)插件與方案,包括:flannel,calico,cilium等等。這些網(wǎng)絡(luò)插件集中解決了以下需求:

  • 保證每個(gè)Pod擁有一個(gè)集群內(nèi)唯一的IP地址
  • 保證不同節(jié)點(diǎn)的IP地址劃分不會(huì)重復(fù)
  • 保證跨節(jié)點(diǎn)的Pod可以互相通信
  • 保證不同節(jié)點(diǎn)的Pod可以與跨節(jié)點(diǎn)的主機(jī)互相通信

CNI的介紹請參考這篇文章:https://jimmysong.io/kubernetes-handbook/concepts/cni.html

Flannel介紹

在默認(rèn)的Docker配置中,每個(gè)節(jié)點(diǎn)上的Docker服務(wù)會(huì)分別負(fù)責(zé)所在節(jié)點(diǎn)容器的IP分配。這樣導(dǎo)致的一個(gè)問題是,不同節(jié)點(diǎn)上容器可能獲得相同的內(nèi)外IP地址。

Flannel的設(shè)計(jì)目的就是為集群中的所有節(jié)點(diǎn)重新規(guī)劃IP地址的使用規(guī)則,從而使得不同節(jié)點(diǎn)上的容器能夠獲得“同屬一個(gè)內(nèi)網(wǎng)”且”不重復(fù)的”IP地址,并讓屬于不同節(jié)點(diǎn)上的容器能夠直接通過內(nèi)網(wǎng)IP通信。

Flannel實(shí)質(zhì)上是一種“覆蓋網(wǎng)絡(luò)(overlay network)”,也就是將TCP數(shù)據(jù)包裝在另一種網(wǎng)絡(luò)包里面進(jìn)行路由轉(zhuǎn)發(fā)和通信,目前已經(jīng)支持UDP、VxLAN、AWS VPC和GCE路由等數(shù)據(jù)轉(zhuǎn)發(fā)方式,默認(rèn)的節(jié)點(diǎn)間數(shù)據(jù)通信方式是UDP轉(zhuǎn)發(fā)。下圖展示了數(shù)據(jù)包在flannel中的流轉(zhuǎn):

flannel.png
flannel2.png
  1. 數(shù)據(jù)從源容器中發(fā)出后,經(jīng)由所在主機(jī)的docker0虛擬網(wǎng)卡轉(zhuǎn)發(fā)到flannel0虛擬網(wǎng)卡,這是個(gè)P2P的虛擬網(wǎng)卡,flanneld服務(wù)監(jiān)聽在網(wǎng)卡的另外一端。
  2. Flannel在Etcd服務(wù)維護(hù)了一張節(jié)點(diǎn)間的路由表。
  3. 源主機(jī)的flanneld服務(wù)將原本的數(shù)據(jù)內(nèi)容UDP封裝后根據(jù)自己的路由表投遞給目的節(jié)點(diǎn)的flanneld服務(wù),數(shù)據(jù)到達(dá)以后被解包,然后直接進(jìn)入目的節(jié)點(diǎn)的flannel0虛擬網(wǎng)卡,然后被轉(zhuǎn)發(fā)到目的主機(jī)的docker0虛擬網(wǎng)卡,最后就像本機(jī)容器通信一下的有docker0路由到達(dá)目標(biāo)容器。

更多關(guān)于flannel的解析,請參考:https://jimmysong.io/kubernetes-handbook/concepts/flannel.html

Calico介紹

Flannel是一種典型的Overlay網(wǎng)絡(luò),它將已有的物理網(wǎng)絡(luò)(Underlay網(wǎng)絡(luò))作為基礎(chǔ),在其上建立疊加的邏輯網(wǎng)絡(luò),實(shí)現(xiàn)網(wǎng)絡(luò)資源的虛擬化。Overlay網(wǎng)絡(luò)有一定額外的封包和解包等網(wǎng)絡(luò)開銷,對網(wǎng)絡(luò)通信的性能有一定損耗。

Calico是純?nèi)龑拥腟DN 實(shí)現(xiàn),它基于BPG 協(xié)議和Linux自身的路由轉(zhuǎn)發(fā)機(jī)制,不依賴特殊硬件,容器通信也不依賴iptables NAT或Tunnel 等技術(shù)。能夠方便的部署在物理服務(wù)器、虛擬機(jī)(如 OpenStack)或者容器環(huán)境下。同時(shí)calico自帶的基于iptables的ACL管理組件非常靈活,能夠滿足比較復(fù)雜的安全隔離需求。

  • Calico創(chuàng)建和管理一個(gè)扁平的三層網(wǎng)絡(luò)(不需要overlay),每個(gè)容器會(huì)分配一個(gè)可路由的IP。由于通信時(shí)不需要解包和封包,網(wǎng)絡(luò)性能損耗小,易于排查,且易于水平擴(kuò)展。
  • 小規(guī)模部署時(shí)可以通過BGP client直接互聯(lián),大規(guī)模下可通過指定的BGP Route Reflector來完成,這樣保證所有的數(shù)據(jù)流量都是通過IP路由的方式完成互聯(lián)的。
  • Calico基于iptables還提供了豐富而靈活的網(wǎng)絡(luò)Policy,保證通過各個(gè)節(jié)點(diǎn)上的ACL來提供Workload的多租戶隔離、安全組以及其他可達(dá)性限制等功能。

iptable是內(nèi)核中的一個(gè)網(wǎng)絡(luò)數(shù)據(jù)包處理模塊,它具有網(wǎng)絡(luò)數(shù)據(jù)包修改,網(wǎng)絡(luò)地址轉(zhuǎn)換(NAT)等功能。而Routes路由表存儲(chǔ)著指向特定網(wǎng)絡(luò)地址的路徑,即下一跳的地址。ACL其實(shí)是一種報(bào)文過濾器,根據(jù)ACL中的匹配條件對進(jìn)站和出站的報(bào)文進(jìn)行過濾處理。

calico.png
  • Etcd:負(fù)責(zé)存儲(chǔ)網(wǎng)絡(luò)信息
  • BGP client:負(fù)責(zé)將Felix配置的路由信息分發(fā)到其他節(jié)點(diǎn)
  • Felix:Calico Agent,每個(gè)節(jié)點(diǎn)都需要運(yùn)行,主要負(fù)責(zé)配置路由、配置ACLs、報(bào)告狀態(tài)
  • BGP Route Reflector:大規(guī)模部署時(shí)需要用到,作為BGP client的中心連接點(diǎn),可以避免每個(gè)節(jié)點(diǎn)互聯(lián)
calico2.png

Calico 還基于 iptables 還提供了豐富而靈活的網(wǎng)絡(luò) policy, 保證通過各個(gè)節(jié)點(diǎn)上的 ACLs 來提供 workload 的多租戶隔離、安全組以及其他可達(dá)性限制等功能。

calico3.png

calico4.png

核心問題是,nodeA怎樣得知下一跳的地址?答案是node之間通過BGP協(xié)議交換路由信息。

每個(gè)node上運(yùn)行一個(gè)軟路由軟件bird,并且被設(shè)置成BGP Speaker,與其它node通過BGP協(xié)議交換路由信息。

可以簡單理解為,每一個(gè)node都會(huì)向其它node通知這樣的信息:

我是X.X.X.X,某個(gè)IP或者網(wǎng)段在我這里,它們的下一跳地址是我。

通過這種方式每個(gè)node知曉了每個(gè)workload-endpoint的下一跳地址。

更多關(guān)于Calico的文章,請參考:
https://jimmysong.io/kubernetes-handbook/concepts/calico.html
https://blog.51cto.com/dengaosky/2069666
https://cloud.tencent.com/developer/article/1482739
https://qiankunli.github.io/2018/02/04/calico.html

K8s NetworkPoclicy

K8s NetworkPoclicy 用于實(shí)現(xiàn)Pod間的網(wǎng)絡(luò)隔離。在使用Network Policy前,必須先安裝支持K8s NetworkPoclicy的網(wǎng)絡(luò)插件,包括:Calico,Romana,Weave Net,Trireme,OpenContrail等。

Pod的網(wǎng)絡(luò)隔離

在未使用NetworkPolicy前,K8s中所有的Pod并不存在網(wǎng)絡(luò)隔離,他們能夠接收任何網(wǎng)絡(luò)流量。一旦使用NetworkPolicy選中某個(gè)namespace下的某些Pod,那么這些Pod只能接收特定來源的流量(由Ingress屬性定義),并且只能向特定出口發(fā)送網(wǎng)絡(luò)請求(由Egress屬性定義)。其他未被這個(gè)NetworkPolicy選中的Pod,依然不具備網(wǎng)絡(luò)隔離。

下面是一個(gè)NetworkPolicy的例子:

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: 用于定義這個(gè)NetworkPolicy適用于哪些Pod。如果其值為空,則表示適用于所有此namespace下的Pod
  • policyTypes: 包含兩個(gè)可選值:用于控制進(jìn)入流量的Ingress和控制出口流量的Egress
  • ingress: 用于定義能夠進(jìn)入Pod的流量的規(guī)則。例子中允許三種流量與podSelector選中的Pod的6379端口進(jìn)行通信,三種流量分別為:ip地址位于172.17.0.0/16網(wǎng)段內(nèi)的流量(但不包括172.17.1.0/24)允許與目標(biāo)Pod通信,任何擁有role=frontend的label的Pod的流量允許進(jìn)入,任何擁有l(wèi)abel "project=myproject"的namespace下的Pod的流量允許進(jìn)入。其他任何進(jìn)入流量將被禁止。
  • egress: 用于定義允許的出口流量。上面的例子中表示podSelector選中的Pod能夠向10.0.0.0/24的5978端口發(fā)送TCP請求。其他任何TCP出流量都將被禁止。

下面的例子表示默認(rèn)禁止所有Pod間的Ingress流量:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny
spec:
  podSelector: {}
  policyTypes:
  - Ingress

默認(rèn)拒絕所有 Pod 之間 Egress 通信的策略為:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny
spec:
  podSelector: {}
  policyTypes:
  - Egress

而默認(rèn)允許所有 Pod 之間 Ingress 通信的策略為:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all
spec:
  podSelector: {}
  ingress:
  - {}

默認(rèn)允許所有 Pod 之間 Egress 通信的策略為:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all
spec:
  podSelector: {}
  egress:
  - {}

calico 實(shí)例

以 calico 為例看一下 Network Policy 的具體用法。首先配置 kubelet 使用 CNI 網(wǎng)絡(luò)插件:

kubelet --network-plugin=cni --cni-conf-dir=/etc/cni/net.d --cni-bin-dir=/opt/cni/bin ...

安裝 calio 網(wǎng)絡(luò)插件:

kubectl apply -f https://docs.projectcalico.org/v3.0/getting-started/kubernetes/installation/hosted/kubeadm/1.7/calico.yaml

首先部署一個(gè) nginx 服務(wù),此時(shí),通過其他 Pod 是可以訪問 nginx 服務(wù)的:

$ kubectl run nginx --image=nginx --replicas=2
deployment "nginx" created
$ kubectl expose deployment nginx --port=80
service "nginx" exposed

開啟 default namespace 的 DefaultDeny Network Policy 后,其他 Pod(包括 namespace 外部)不能訪問 nginx 了:

$ cat default-deny.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny
spec:
  podSelector: {}
  policyTypes:
  - Ingress

$ kubectl create -f default-deny.yaml

$ kubectl run busybox --rm -ti --image=busybox /bin/sh
Waiting for pod default/busybox-472357175-y0m47 to be running, status is Pending, pod ready: false

Hit enter for command prompt

/ # wget --spider --timeout=1 nginx
Connecting to nginx (10.100.0.16:80)
wget: download timed out
/ #

最后再創(chuàng)建一個(gè)運(yùn)行帶有 access=true label的 Pod 訪問的網(wǎng)絡(luò)策略:

$ cat nginx-policy.yaml
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: access-nginx
spec:
  podSelector:
    matchLabels:
      run: nginx
  ingress:
  - from:
    - podSelector:
        matchLabels:
          access: "true"

$ kubectl create -f nginx-policy.yaml
networkpolicy "access-nginx" created

# 不帶 access=true 標(biāo)簽的 Pod 還是無法訪問 nginx 服務(wù)
$ kubectl run busybox --rm -ti --image=busybox /bin/sh
Waiting for pod default/busybox-472357175-y0m47 to be running, status is Pending, pod ready: false

Hit enter for command prompt

/ # wget --spider --timeout=1 nginx
Connecting to nginx (10.100.0.16:80)
wget: download timed out
/ #


# 而帶有 access=true 標(biāo)簽的 Pod 可以訪問 nginx 服務(wù)
$ kubectl run busybox --rm -ti --labels="access=true" --image=busybox /bin/sh
Waiting for pod default/busybox-472357175-y0m47 to be running, status is Pending, pod ready: false

Hit enter for command prompt

/ # wget --spider --timeout=1 nginx
Connecting to nginx (10.100.0.16:80)
/ #

參考文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容