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啟動時(shí),會自動創(chuàng)建一個(gè)鏡像為gcr.io/google_containers/pause:version的容器峻呕,所有處于該P(yáng)od中的容器在啟動時(shí)都會添加諸如--net=container:pause --ipc=contianer:pause --pid=container:pause的啟動參數(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ò)模型。如下圖所示:
Docker引擎在啟動時(shí)娘锁,會在宿主機(jī)上創(chuàng)建一個(gè)名為docker0的虛擬網(wǎng)橋牙寞,這個(gè)虛擬網(wǎng)橋負(fù)責(zé)給所有容器分配不重復(fù)的ip地址以及容器間的網(wǎng)絡(luò)通信。首先致盟,Docker在創(chuàng)建一個(gè)容器時(shí)碎税,會執(zhí)行以下操作:
- 創(chuàng)建一對veth pair尤慰,一端置于容器中馏锡,一端置于docker0虛擬網(wǎng)橋中,從而實(shí)現(xiàn)網(wǎng)橋和容器的通信伟端。
- 將容器中的veth命名為eth0杯道,docker0網(wǎng)橋中的veth指定一個(gè)唯一的名字,例如veth0ac884e
- 從docker0網(wǎng)橋可用的地址段中選取一個(gè)分配給容器责蝠,并配置默認(rèn)路由到docker0網(wǎng)橋的veth0ac884e
- 這樣党巾,容器就能夠通過虛擬網(wǎng)卡eth0和其他容器進(jìn)行通信了。當(dāng)該容器結(jié)束后霜医,eth0會隨該容器的網(wǎng)絡(luò)命名空間一起被清除齿拂,相對應(yīng)的veth0ac884e也會被docker0網(wǎng)橋自動卸載掉。
通過這個(gè)docker0網(wǎng)橋肴敛,同一宿主機(jī)上的容器可以互相通信署海。然而由于宿主機(jī)的IP地址與容器veth pair的 IP地址均不在同一個(gè)網(wǎng)段吗购,故僅僅依靠veth pair和namespace的技術(shù),還不足以使宿主機(jī)以外的網(wǎng)絡(luò)主動發(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è)條件:
- 對整個(gè)集群中的Pod-IP分配進(jìn)行規(guī)劃,不能有沖突
- 將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地址劃分不會重復(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ù)會分別負(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):
- 數(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)卡的另外一端。
- Flannel在Etcd服務(wù)維護(hù)了一張節(jié)點(diǎn)間的路由表现斋。
- 源主機(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è)容器會分配一個(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路由表存儲著指向特定網(wǎng)絡(luò)地址的路徑舆驶,即下一跳的地址橱健。ACL其實(shí)是一種報(bào)文過濾器,根據(jù)ACL中的匹配條件對進(jìn)站和出站的報(bào)文進(jìn)行過濾處理沙廉。
- Etcd:負(fù)責(zé)存儲網(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)
Calico 還基于 iptables 還提供了豐富而靈活的網(wǎng)絡(luò) policy, 保證通過各個(gè)節(jié)點(diǎn)上的 ACLs 來提供 workload 的多租戶隔離亮隙、安全組以及其他可達(dá)性限制等功能。
核心問題是垢夹,nodeA怎樣得知下一跳的地址溢吻?答案是node之間通過BGP協(xié)議交換路由信息。
每個(gè)node上運(yùn)行一個(gè)軟路由軟件bird果元,并且被設(shè)置成BGP Speaker促王,與其它node通過BGP協(xié)議交換路由信息。
可以簡單理解為而晒,每一個(gè)node都會向其它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)
/ #