1.kube-proxy組件介紹
Kubernetes service只是把應用對外提供服務的方式做了抽象,真正的應用跑在Pod中的container里,我們的請求轉到kubernetes nodes對應的nodePort上,那么nodePort上的請求是如何進一步轉到提供后台服務的Pod的呢? 就是通過kube-proxy實現的:
kube-proxy部署在k8s的每一個Node節點上,是Kubernetes的核心組件,我們創建一個 service 的時候,kube-proxy 會在iptables中追加一些規則,為我們實現路由與負載均衡的功能。在k8s1.8之前,kube-proxy默認使用的是iptables模式,通過各個node節點上的iptables規則來實現service的負載均衡,但是隨着service數量的增大,iptables模式由於線性查找匹配、全量更新等特點,其性能會顯著下降。從k8s的1.8版本開始,kube-proxy引入了IPVS模式,IPVS模式與iptables同樣基於Netfilter,但是采用的hash表,因此當service數量達到一定規模時,hash查表的速度優勢就會顯現出來,從而提高service的服務性能。
service是一組pod的服務抽象,相當於一組pod的LB,負責將請求分發給對應的pod。service會為這個LB提供一個IP,一般稱為cluster IP。kube-proxy的作用主要是負責service的實現,具體來說,就是實現了內部從pod到service和外部的從node port向service的訪問。
- kube-proxy其實就是管理service的訪問入口,包括集群內Pod到Service的訪問和集群外訪問service。
- kube-proxy管理sevice的Endpoints,該service對外暴露一個Virtual IP,也可以稱為是Cluster IP, 集群內通過訪問這個Cluster IP:Port就能訪問到集群內對應的serivce下的Pod。
2.kube-proxy三種工作模式
-
Userspace方式:
Client Pod要訪問Server Pod時,它先將請求發給內核空間中的service iptables規則,由它再將請求轉給監聽在指定套接字上的kube-proxy的端口,kube-proxy處理完請求,並分發請求到指定Server Pod后,再將請求轉發給內核空間中的service ip,由service iptables將請求轉給各個節點中的Server Pod。
這個模式有很大的問題,客戶端請求先進入內核空間的,又進去用戶空間訪問kube-proxy,由kube-proxy封裝完成后再進去內核空間的iptables,再根據iptables的規則分發給各節點的用戶空間的pod。由於其需要來回在用戶空間和內核空間交互通信,因此效率很差。在Kubernetes 1.1版本之前,userspace是默認的代理模型。 -
iptables方式:
客戶端IP請求時,直接請求本地內核service ip,根據iptables的規則直接將請求轉發到到各pod上,因為使用iptable NAT來完成轉發,也存在不可忽視的性能損耗。另外,如果集群中存上萬的Service/Endpoint,那么Node上的iptables rules將會非常龐大,性能還會再打折
iptables代理模式由Kubernetes 1.1版本引入,自1.2版本開始成為默認類型。 -
ipvs方式:
Kubernetes自1.9-alpha版本引入了ipvs代理模式,自1.11版本開始成為默認設置。客戶端
請求時到達內核空間時,根據ipvs的規則直接分發到各pod上。kube-proxy會監視Kubernetes Service對象和Endpoints,調用netlink接口以相應地創建ipvs規則並定期與Kubernetes Service對象和Endpoints對象同步ipvs規則,以確保ipvs狀態與期望一致。訪問服務時,流量將被重定向到其中一個后端Pod。與iptables類似,ipvs基於netfilter 的 hook 功能,但使用哈希表作為底層數據結構並在內核空間中工作。這意味着ipvs可以更快地重定向流量,並且在同步代理規則時具有更好的性能。此外,ipvs為負載均衡算法提供了更多選項,例如:- rr:輪詢調度
- lc:最小連接數
- dh:目標哈希
- sh:源哈希
- sed:最短期望延遲
- nq:不排隊調度
如果某個服務后端pod發生變化,標簽選擇器適應的pod又多一個,適應的信息會立即反映到apiserver上,而kube-proxy一定可以watch到etc中的信息變化,而將它立即轉為ipvs或者iptables中的規則,這一切都是動態和實時的,刪除一個pod也是同樣的原理。如圖:
注:
以上不論哪種,kube-proxy都通過watch的方式監控着apiserver寫入etcd中關於Pod的最新狀態信息,它一旦檢查到一個Pod資源被刪除了或新建了,它將立即將這些變化,反應再iptables 或 ipvs規則中,以便iptables和ipvs在調度Clinet Pod請求到Server Pod時,不會出現Server Pod不存在的情況。自k8s1.11以后,service默認使用ipvs規則,若ipvs沒有被激活,則降級使用iptables規則.
3.service的type類型是ClusterIp,iptables規則分析
在k8s創建的service,雖然有ip地址,但是service的ip是虛擬的,不存在物理機上的,是在iptables或者ipvs規則里的。
pod_test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80 #pod中的容器需要暴露的端口
service_test.yaml
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-service
spec:
type: ClusterIP
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
run: my-nginx
[root@master1]# kubectl apply -f pod_test.yaml
[root@master1]# kubectl apply -f service_test.yaml
[root@master1 service]# kubectl get svc -l run=my-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx ClusterIP 10.98.63.235 <none> 80/TCP 75s
[root@master1 service]# kubectl get pods -l run=my-nginx -o wide
NAME READY STATUS RESTARTS AGE IP
my-nginx-69f769d56f-6fn7b 1/1 Running 0 13m 10.244.121.22
my-nginx-69f769d56f-xzj5l 1/1 Running 0 13m 10.244.121.21
[root@master1 service]# iptables -t nat -L | grep 10.98.63.235
KUBE-MARK-MASQ tcp -- !10.244.0.0/16 10.98.63.235 /* default/my-nginx cluster IP */ tcp dpt:http
KUBE-SVC-L65ENXXZWWSAPRCR tcp -- anywhere 10.98.63.235 /* default/my-nginx cluster IP */ tcp dpt:http
[root@master1 service]# iptables -t nat -L | grep KUBE-SVC-L65ENXXZWWSAPRCR
KUBE-SVC-L65ENXXZWWSAPRCR tcp -- anywhere 10.98.63.235 /* default/my-nginx cluster IP */ tcp dpt:http
Chain KUBE-SVC-L65ENXXZWWSAPRCR (1 references)
[root@master1 service]# iptables -t nat -L | grep 10.244.121.22
KUBE-MARK-MASQ all -- 10.244.121.22 anywhere /* default/my-nginx */
DNAT tcp -- anywhere anywhere /* default/my-nginx */ tcp to:10.244.121.22:80
[root@master1 service]# iptables -t nat -L | grep 10.244.121.21
KUBE-MARK-MASQ all -- 10.244.121.21 anywhere /* default/my-nginx */
DNAT tcp -- anywhere anywhere /* default/my-nginx */ tcp to:10.244.121.21:80
通過上面可以看到之前創建的service,會通過kube-proxy在iptables中生成一個規則,來實現流量路由,有一系列目標為 KUBE-SVC-xxx 鏈的規則,每條規則都會匹配某個目標 ip 與端口。也就是說訪問某個 ip:port 的請求會由 KUBE-SVC-xxx 鏈來處理。這個目標 IP 其實就是service ip。
4.service的type類型是nodePort,iptables規則分析
pod_nodeport.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx-nodeport
spec:
selector:
matchLabels:
run: my-nginx-nodeport
replicas: 2
template:
metadata:
labels:
run: my-nginx-nodeport
spec:
containers:
- name: my-nginx-nodeport-container
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
service_nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: my-nginx-nodeport
labels:
run: my-nginx-nodeport
spec:
type: NodePort
ports:
- port: 80
protocol: TCP
targetPort: 80
nodePort: 30380
selector:
run: my-nginx-nodeport
[root@master1]# kubectl apply -f pod_nodeport.yaml
[root@master1]# kubectl apply -f service_nodeport.yaml
[root@master1 service]# kubectl get pods -l run=my-nginx-nodeport
NAME READY STATUS RESTARTS AGE
my-nginx-nodeport-649c945f85-l2hj6 1/1 Running 0 21m
my-nginx-nodeport-649c945f85-zr47r 1/1 Running 0 21m
[root@master1 service]# kubectl get svc -l run=my-nginx-nodeport
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx-nodeport NodePort 10.104.251.190 <none> 80:30380/TCP 22m
[root@master1 service]# iptables -t nat -S | grep 30380
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/my-nginx-nodeport" -m tcp --dport 30380 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/my-nginx-nodeport" -m tcp --dport 30380 -j KUBE-SVC-J5QV2XWG4FEBPH3Q
[root@master1 service]# iptables -t nat -S | grep KUBE-SVC-J5QV2XWG4FEBPH3Q
-N KUBE-SVC-J5QV2XWG4FEBPH3Q
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/my-nginx-nodeport" -m tcp --dport 30380 -j KUBE-SVC-J5QV2XWG4FEBPH3Q
-A KUBE-SERVICES -d 10.104.251.190/32 -p tcp -m comment --comment "default/my-nginx-nodeport cluster IP" -m tcp --dport 80 -j KUBE-SVC-J5QV2XWG4FEBPH3Q
-A KUBE-SVC-J5QV2XWG4FEBPH3Q -m comment --comment "default/my-nginx-nodeport" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-XRUO23GXY67LXLQN
-A KUBE-SVC-J5QV2XWG4FEBPH3Q -m comment --comment "default/my-nginx-nodeport" -j KUBE-SEP-IIBDPNPJZXXASELC
[root@master1 service]# iptables -t nat -S | grep KUBE-SEP-XRUO23GXY67LXLQN
-N KUBE-SEP-XRUO23GXY67LXLQN
-A KUBE-SEP-XRUO23GXY67LXLQN -s 10.244.102.90/32 -m comment --comment "default/my-nginx-nodeport" -j KUBE-MARK-MASQ
-A KUBE-SEP-XRUO23GXY67LXLQN -p tcp -m comment --comment "default/my-nginx-nodeport" -m tcp -j DNAT --to-destination 10.244.102.90:80
-A KUBE-SVC-J5QV2XWG4FEBPH3Q -m comment --comment "default/my-nginx-nodeport" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-XRUO23GXY67LXLQN
[root@master1 service]# iptables -t nat -S | grep KUBE-SEP-IIBDPNPJZXXASELC
-N KUBE-SEP-IIBDPNPJZXXASELC
-A KUBE-SEP-IIBDPNPJZXXASELC -s 10.244.121.23/32 -m comment --comment "default/my-nginx-nodeport" -j KUBE-MARK-MASQ
-A KUBE-SEP-IIBDPNPJZXXASELC -p tcp -m comment --comment "default/my-nginx-nodeport" -m tcp -j DNAT --to-destination 10.244.121.23:80
-A KUBE-SVC-J5QV2XWG4FEBPH3Q -m comment --comment "default/my-nginx-nodeport" -j KUBE-SEP-IIBDPNPJZXXASELC