Kubernetest網(wǎng)絡(luò)
各容器之間的網(wǎng)絡(luò)交互
一個(gè)容器想要與外界做到互通就需要一套網(wǎng)絡(luò)棧也就是它發(fā)出、響應(yīng)網(wǎng)絡(luò)請(qǐng)求的基本環(huán)境,這其中就包括網(wǎng)卡、回環(huán)設(shè)備、路由表和iptables規(guī)則。
交互方式
本機(jī)容器通信
- Docker容器通過在宿主機(jī)上創(chuàng)建一個(gè)名字是docker0的網(wǎng)橋,來使連接到網(wǎng)橋上的容器進(jìn)行通信
- 主要是通過一個(gè)叫Veth Pair的虛擬設(shè)備實(shí)現(xiàn)(這個(gè)虛擬設(shè)備總是成對(duì)出現(xiàn)的,當(dāng)其中一個(gè)網(wǎng)卡發(fā)出請(qǐng)求時(shí),另外一個(gè)就能接受到請(qǐng)求)
- 無論這兩個(gè)設(shè)備是否在同一個(gè)network space,就相當(dāng)于網(wǎng)線連接
- 原理無外乎使用etho網(wǎng)卡做ARP廣播,從而通過IP找到Mac地址
跨主通信
- 本機(jī)交互通信不過是讓宿主機(jī)作媒介連接多個(gè)容器網(wǎng)絡(luò),那跨多個(gè)宿主機(jī)呢?
- 很容易想到的是在容器網(wǎng)絡(luò)的再搭建一層網(wǎng)絡(luò),只要實(shí)現(xiàn)這層網(wǎng)絡(luò)的相互通信也就解決了各個(gè)宿主機(jī)容器間的網(wǎng)絡(luò)通信問題。
跨主通信實(shí)現(xiàn)方式
- 舉個(gè)例子比如當(dāng)前宿主機(jī)
A上有容器C1想要訪問到宿主機(jī)B上的容器C2 - 首先
C1發(fā)出請(qǐng)求,需要訪問IP為1.1.1.2的C2容器 - 請(qǐng)求先是到達(dá)
A,A上的docker0網(wǎng)橋沒有這個(gè)網(wǎng)段,從而把這個(gè)請(qǐng)求的選擇權(quán)交給宿主機(jī)A的iptables - 這個(gè)時(shí)候
我們在iptables中預(yù)先創(chuàng)建好一系列路由規(guī)則然后將這個(gè)請(qǐng)求發(fā)往C2所在的宿主機(jī)就好了 - 那么如何
預(yù)先創(chuàng)建好路由規(guī)則呢?,在K8s中所有容器其實(shí)都是其宿主機(jī)的一個(gè)子網(wǎng),比如A的子網(wǎng)是1.1.2.0/24,C1的IP就是1.1.2.1這樣子,所以就可以通過IP找到宿主機(jī)B,從而找到對(duì)應(yīng)的C2容器 - 而這個(gè)關(guān)系又保存在
Etcd中,我們只需要監(jiān)聽Etcd中IP規(guī)則的變化,實(shí)時(shí)更新iptables就好了
下面是 Flannel UDP模式的跨主通信的原理圖

image
- 雖然UDP方案的跨主通信因?yàn)樾阅軉栴}已經(jīng)被拋棄(用戶態(tài)內(nèi)核態(tài)切換過多、需要三次數(shù)據(jù)來回拷貝),但卻值得學(xué)習(xí)
- 除此之外Flannel項(xiàng)目還有兩個(gè)跨主通信的解決方案
VXLAN(virtual Extensible LAN)、host-gw(host-gateway)
- 除此之外Flannel項(xiàng)目還有兩個(gè)跨主通信的解決方案
K8s網(wǎng)絡(luò)模型與CNI
- K8s中使用CNI網(wǎng)橋來替代了docker0網(wǎng)橋,替換的原因有兩點(diǎn):
- K8s沒有使用Docker的網(wǎng)絡(luò)模型,也就沒有docker0網(wǎng)橋
- CNI與K8s配置pod的網(wǎng)絡(luò)環(huán)境密切相關(guān)(即Infra 容器的 network namespace)
- 通過CNI樣式的插件構(gòu)建K8s網(wǎng)絡(luò)能實(shí)現(xiàn)
各個(gè)容器、宿主機(jī)之間直接使用IP通信,實(shí)現(xiàn)的效果是在容器內(nèi)訪問容器的IP與其他容器訪問容器的IP都是一模一樣的,總之實(shí)現(xiàn)的就是實(shí)現(xiàn)互通。
容器之間的網(wǎng)絡(luò)隔離 - NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test-network-policy
namespace: default
spec:
# 定義NetworkPolicy限制范圍 --黑名單,所有role是xxx1的Pod都被拒絕,如果留空就是這個(gè)ns下的所有Pod
podSelector:
matchLabels:
role: xxx1
# 限制類型是流入或流出,下面定義的都是白名單
policyTypes:
- Ingress
- Egress
# 接受下面定義流入的ip比如1.1.0.0/16,拒絕1.1.1.0/24以及其他所有請(qǐng)求
# 流入
ingress:
- from:
- ipBlock:
cidr: 1.1.0.0/16
except:
- 1.1.1.0/24
- namespaceSelector:
matchLabels:
project: testPro
- podSelector:
matchLabels:
role: test
ports:
- protocol: TCP
port: 6379
# 流入
egress:
- to:
- ipBlock:
cidr: 100.0.0.0/24
ports:
- protocol: TCP
port: 5978
NetworkPolicy實(shí)現(xiàn)原理
- 實(shí)際上就是在宿主機(jī)上生成一系列特定iptables規(guī)則。這和安全組非常類似
- 一般來說,我們哪些地方需要NetworkPolicy,比如:job,cronjob這種計(jì)算型Pod不需要也不應(yīng)該對(duì)外提供服務(wù),所以就需要拒絕掉所有訪問它們的流量,從而提升系統(tǒng)安全。
Service實(shí)現(xiàn)原理
kube-proxy + iptables
- 當(dāng)Service被添加到K8s時(shí),kube-proxy就會(huì)在宿主機(jī)上創(chuàng)建一條iptables規(guī)則集合
- 這個(gè)集合中就包含了Service所代理的所有Pod
- 同樣Service的負(fù)載均衡也是在這里實(shí)現(xiàn)的
- 在訪問Service的請(qǐng)求到來后,路由前,K8s會(huì)將流入的IP和端口通過iptables上的信息改成真是的目的IP與端口,也就是真是Pod的IP與Port
ipvs
- iptables維護(hù)路由規(guī)則的方式實(shí)際上有很大的性能開銷,當(dāng)有大量Pod的時(shí)候,需要去維護(hù)很多的iptables規(guī)則,不斷刷新這些規(guī)則
- ipvc則不需要維護(hù)如此多的iptables規(guī)則,而是將規(guī)則的處理移動(dòng)到了內(nèi)核態(tài),從而降低了開銷
- 雖然其也需要iptables來做包過濾等操作,但所需的規(guī)則數(shù)量都是有限的,而不是隨著Pod增加而增加。時(shí)刻保持訪問Pod iptables規(guī)則的最新狀態(tài)
如何訪問Service - NodePort、LoadBalancer service、ExternalName
NodePort
apiVersion: v1
kind: Service
metadata:
name: test
labels:
run: test
spec:
type: NodePort
ports:
# 聲明8080代理Pod的80端口
- nodePort: 8080
targetPort: 80
protocol: TCP
name: http
- nodePort: 443
protocol: TCP
name: https
selector:
run: test-pod
- 這個(gè)時(shí)候使用
宿主機(jī)IP+8080就可以訪問了 - 當(dāng)定義了這樣一個(gè)文件后,kube-proxy同樣也是生成一條iptables規(guī)則,讓它能路由到具體Service上
- 這里需要注意的是在請(qǐng)求結(jié)束后,kube-proxy會(huì)給這個(gè)IP包進(jìn)行一次
SNAT操作,他會(huì)將這個(gè)IP包的源地址替換成當(dāng)前宿主機(jī)的CNI網(wǎng)橋地址 - 這里為什么會(huì)有這一次
SNAT操作呢?比如外部請(qǐng)求訪問了node1,然后這個(gè)請(qǐng)求又被路由到node2上,當(dāng)node2處理完成后開始進(jìn)行回復(fù),那回復(fù)肯定要先回復(fù)到1上,然后1在回復(fù)到客戶端(收到回復(fù)是不同主機(jī),客戶端可能會(huì)報(bào)錯(cuò))
LoadBalancer類型的 Service
kind: Service
apiVersion: v1
metadata:
name: test-service
spec:
ports:
- port: 8765
targetPort: 9376
selector:
app: test-app
type: LoadBalancer
對(duì)于LoadBalancer類型的 Service,Kubernetes 會(huì)調(diào)用 CloudProvider 在公有云上創(chuàng)建一個(gè)負(fù)載均衡服務(wù),并且把被代理的 Pod 的 IP 地址配置給負(fù)載均衡服務(wù)做后端。
ExternalName
kind: Service
apiVersion: v1
metadata:
name: test-service
spec:
type: ExternalName
externalName: my.test.com
這個(gè)時(shí)候訪問test-service.default.svc.cluster.local的時(shí)候返回的就是my.test.com
萬變不離其宗
- Service其實(shí)就是 Kubernetes 為 Pod 分配的、固定的、基于 iptables(或者 IPVS)的訪問入口。而這些訪問入口代理的 Pod 信息,則來自于 Etcd,由 kube-proxy 通過控制循環(huán)來維護(hù)。
- 所以通過分析Service在宿主機(jī)上的
iptables就能解決大多數(shù)問題(比如當(dāng)service沒法通過DNS訪問時(shí),就可以檢查K8smaster節(jié)點(diǎn)和Service DNS是否異常)
訪問Service的Service - Ingress
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: test-ingress
spec:
tls:
- hosts:
- test.com
secretName: test-secret
rules:
- host: test.com
http:
paths:
- path: /path1
backend:
serviceName: svc1
servicePort: 80
- path: /path2
backend:
serviceName: svc2
servicePort: 80
- 其實(shí)Ingress就是對(duì)K8s做的一個(gè)反向代理。
- 上面的Ingress定義的文件就把
svc1與svc2進(jìn)行了代理,當(dāng)訪問path1時(shí),能路由到svc1上 - 比如常用的nginx,官方維護(hù)的nginx-ingress-controller能根據(jù)Ingress與service的變化動(dòng)態(tài)更新Nginx負(fù)載均衡器
- 當(dāng)然請(qǐng)求的時(shí)候,如果沒有任何一個(gè)IngressRule被匹配到,拿nginx-ingress-controller來說就會(huì)返回一個(gè)Nginx的404頁面(也可以自己配置,比如專門啟一個(gè)Pod用來返回404頁面)