一. kube-proxy 和 service
kube-proxy是Kubernetes的核心組件,部署在每個Node節點上,它是實現Kubernetes Service的通信與負載均衡機制的重要組件; kube-proxy負責為Pod創建代理服務,從apiserver獲取所有server信息,並根據server信息創建代理服務,實現server到Pod的請求路由和轉發,從而實現K8s層級的虛擬轉發網絡。
在k8s中,提供相同服務的一組pod可以抽象成一個service,通過service提供的統一入口對外提供服務,每個service都有一個虛擬IP地址(VIP)和端口號供客戶端訪問。kube-proxy存在於各個node節點上,主要用於Service功能的實現,具體來說,就是實現集群內的客戶端pod訪問service,或者是集群外的主機通過NodePort等方式訪問service。在當前版本的k8s中,kube-proxy默認使用的是iptables模式,通過各個node節點上的iptables規則來實現service的負載均衡,但是隨着service數量的增大,iptables模式由於線性查找匹配、全量更新等特點,其性能會顯著下降。從k8s的1.8版本開始,kube-proxy引入了IPVS模式,IPVS模式與iptables同樣基於Netfilter,但是采用的hash表,因此當service數量達到一定規模時,hash查表的速度優勢就會顯現出來,從而提高service的服務性能。
kube-proxy負責為Service提供cluster內部的服務發現和負載均衡,它運行在每個Node計算節點上,負責Pod網絡代理, 它會定時從etcd服務獲取到service信息來做相應的策略,維護網絡規則和四層負載均衡工作。在K8s集群中微服務的負載均衡是由Kube-proxy實現的,它是K8s集群內部的負載均衡器,也是一個分布式代理服務器,在K8s的每個節點上都有一個,這一設計體現了它的伸縮性優勢,需要訪問服務的節點越多,提供負載均衡能力的Kube-proxy就越多,高可用節點也隨之增多。
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。
-> service是通過Selector選擇的一組Pods的服務抽象,其實就是一個微服務,提供了服務的LB和反向代理的能力,而kube-proxy的主要作用就是負責service的實現。
-> service另外一個重要作用是,一個服務后端的Pods可能會隨着生存滅亡而發生IP的改變,service的出現,給服務提供了一個固定的IP,而無視后端Endpoint的變化。
舉個例子,比如現在有podA,podB,podC和serviceAB。serviceAB是podA,podB的服務抽象(service)。那么kube-proxy的作用就是可以將pod(不管是podA,podB或者podC)向serviceAB的請求,進行轉發到service所代表的一個具體pod(podA或者podB)上。請求的分配方法一般分配是采用輪詢方法進行分配。另外,kubernetes還提供了一種在node節點上暴露一個端口,從而提供從外部訪問service的方式。比如這里使用這樣的一個manifest來創建service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
apiVersion: v1
kind: Service
metadata:
labels:
name: mysql
role: service
name: mysql-service
spec:
ports:
- port: 3306
targetPort: 3306
nodePort: 30964
type
: NodePort
selector:
mysql-service:
"true"
|
上面配置的含義是在node上暴露出30964端口。當訪問node上的30964端口時,其請求會轉發到service對應的cluster IP的3306端口,並進一步轉發到pod的3306端口。
Service, Endpoints與Pod的關系
Kube-proxy進程獲取每個Service的Endpoints,實現Service的負載均衡功能
Service的負載均衡轉發規則
訪問Service的請求,不論是Cluster IP+TargetPort的方式;還是用Node節點IP+NodePort的方式,都被Node節點的Iptables規則重定向到Kube-proxy監聽Service服務代理端口。kube-proxy接收到Service的訪問請求后,根據負載策略,轉發到后端的Pod。
二. kubernetes服務發現
Kubernetes提供了兩種方式進行服務發現, 即環境變量和DNS, 簡單說明如下:
1) 環境變量: 當你創建一個Pod的時候,kubelet會在該Pod中注入集群內所有Service的相關環境變量。需要注意: 要想一個Pod中注入某個Service的環境變量,則必須Service要先比該Pod創建。這一點,幾乎使得這種方式進行服務發現不可用。比如,一個ServiceName為redis-master的Service,對應的ClusterIP:Port為172.16.50.11:6379,則其對應的環境變量為:
1
2
3
4
5
6
7
|
REDIS_MASTER_SERVICE_HOST=172.16.50.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp:
//172
.16.50.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp:
//172
.16.50.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=172.16.50.11
|
2) DNS:這是k8s官方強烈推薦的方式!!! 可以通過cluster add-on方式輕松的創建KubeDNS來對集群內的Service進行服務發現。
三. kubernetes發布(暴露)服務
kubernetes原生的,一個Service的ServiceType決定了其發布服務的方式。
-> ClusterIP:這是k8s默認的ServiceType。通過集群內的ClusterIP在內部發布服務。
-> NodePort:這種方式是常用的,用來對集群外暴露Service,你可以通過訪問集群內的每個NodeIP:NodePort的方式,訪問到對應Service后端的Endpoint。
-> LoadBalancer: 這也是用來對集群外暴露服務的,不同的是這需要Cloud Provider的支持,比如AWS等。
-> ExternalName:這個也是在集群內發布服務用的,需要借助KubeDNS(version >= 1.7)的支持,就是用KubeDNS將該service和ExternalName做一個Map,KubeDNS返回一個CNAME記錄。
四. kube-proxy 工作原理 (userspace, iptables, ipvs)
kube-proxy當前實現了三種代理模式:userspace, iptables, ipvs。其中userspace mode是v1.0及之前版本的默認模式,從v1.1版本中開始增加了iptables mode,在v1.2版本中正式替代userspace模式成為默認模式。也就是說kubernetes在v1.2版本之前是默認模式, v1.2版本之后默認模式是iptables。
1) userspace mode: userspace是在用戶空間,通過kube-proxy來實現service的代理服務, 其原理如下:
可見,userspace這種mode最大的問題是,service的請求會先從用戶空間進入內核iptables,然后再回到用戶空間,由kube-proxy完成后端Endpoints的選擇和代理工作,這樣流量從用戶空間進出內核帶來的性能損耗是不可接受的。這也是k8s v1.0及之前版本中對kube-proxy質疑最大的一點,因此社區就開始研究iptables mode.
userspace這種模式下,kube-proxy 持續監聽 Service 以及 Endpoints 對象的變化;對每個 Service,它都為其在本地節點開放一個端口,作為其服務代理端口;發往該端口的請求會采用一定的策略轉發給與該服務對應的后端 Pod 實體。kube-proxy 同時會在本地節點設置 iptables 規則,配置一個 Virtual IP,把發往 Virtual IP 的請求重定向到與該 Virtual IP 對應的服務代理端口上。其工作流程大體如下:
由此分析: 該模式請求在到達 iptables 進行處理時就會進入內核,而 kube-proxy 監聽則是在用戶態, 請求就形成了從用戶態到內核態再返回到用戶態的傳遞過程, 一定程度降低了服務性能。
2) iptables mode, 該模式完全利用內核iptables來實現service的代理和LB, 這是K8s在v1.2及之后版本默認模式. 工作原理如下:
iptables mode因為使用iptable NAT來完成轉發,也存在不可忽視的性能損耗。另外,如果集群中存在上萬的Service/Endpoint,那么Node上的iptables rules將會非常龐大,性能還會再打折扣。這也導致目前大部分企業用k8s上生產時,都不會直接用kube-proxy作為服務代理,而是通過自己開發或者通過Ingress Controller來集成HAProxy, Nginx來代替kube-proxy。
iptables 模式與 userspace 相同,kube-proxy 持續監聽 Service 以及 Endpoints 對象的變化;但它並不在本地節點開啟反向代理服務,而是把反向代理全部交給 iptables 來實現;即 iptables 直接將對 VIP 的請求轉發給后端 Pod,通過 iptables 設置轉發策略。其工作流程大體如下:
由此分析: 該模式相比 userspace 模式,克服了請求在用戶態-內核態反復傳遞的問題,性能上有所提升,但使用 iptables NAT 來完成轉發,存在不可忽視的性能損耗,而且在大規模場景下,iptables 規則的條目會十分巨大,性能上還要再打折扣。
iptables的方式則是利用了linux的iptables的nat轉發進行實現:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
apiVersion: v1
kind: Service
metadata:
labels:
name: mysql
role: service
name: mysql-service
spec:
ports:
- port: 3306
targetPort: 3306
nodePort: 30964
type
: NodePort
selector:
mysql-service:
"true"
|
mysql-service對應的nodePort暴露出來的端口為30964,對應的cluster IP(10.254.162.44)的端口為3306,進一步對應於后端的pod的端口為3306。 mysql-service后端代理了兩個pod,ip分別是192.168.125.129和192.168.125.131, 這里先來看一下iptables:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
$iptables -S -t nat
...
-A PREROUTING -m comment --comment
"kubernetes service portals"
-j KUBE-SERVICES
-A OUTPUT -m comment --comment
"kubernetes service portals"
-j KUBE-SERVICES
-A POSTROUTING -m comment --comment
"kubernetes postrouting rules"
-j KUBE-POSTROUTING
-A KUBE-MARK-MASQ -j MARK --
set
-xmark 0x4000
/0x4000
-A KUBE-NODEPORTS -p tcp -m comment --comment
"default/mysql-service:"
-m tcp --dport 30964 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment
"default/mysql-service:"
-m tcp --dport 30964 -j KUBE-SVC-67RL4FN6JRUPOJYM
-A KUBE-SEP-ID6YWIT3F6WNZ47P -s 192.168.125.129
/32
-m comment --comment
"default/mysql-service:"
-j KUBE-MARK-MASQ
-A KUBE-SEP-ID6YWIT3F6WNZ47P -p tcp -m comment --comment
"default/mysql-service:"
-m tcp -j DNAT --to-destination 192.168.125.129:3306
-A KUBE-SEP-IN2YML2VIFH5RO2T -s 192.168.125.131
/32
-m comment --comment
"default/mysql-service:"
-j KUBE-MARK-MASQ
-A KUBE-SEP-IN2YML2VIFH5RO2T -p tcp -m comment --comment
"default/mysql-service:"
-m tcp -j DNAT --to-destination 192.168.125.131:3306
-A KUBE-SERVICES -d 10.254.162.44
/32
-p tcp -m comment --comment
"default/mysql-service: cluster IP"
-m tcp --dport 3306 -j KUBE-SVC-67RL4FN6JRUPOJYM
-A KUBE-SERVICES -m comment --comment
"kubernetes service nodeports; NOTE: this must be the last rule in this chain"
-m addrtype --dst-
type
LOCAL -j KUBE-NODEPORTS
-A KUBE-SVC-67RL4FN6JRUPOJYM -m comment --comment
"default/mysql-service:"
-m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-ID6YWIT3F6WNZ47P
-A KUBE-SVC-67RL4FN6JRUPOJYM -m comment --comment
"default/mysql-service:"
-j KUBE-SEP-IN2YML2VIFH5RO2T
|
首先如果是通過node的30964端口訪問,則會進入到以下鏈:
1
2
|
-A KUBE-NODEPORTS -p tcp -m comment --comment
"default/mysql-service:"
-m tcp --dport 30964 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment
"default/mysql-service:"
-m tcp --dport 30964 -j KUBE-SVC-67RL4FN6JRUPOJYM
|
然后進一步跳轉到KUBE-SVC-67RL4FN6JRUPOJYM的鏈:
1
2
|
-A KUBE-SVC-67RL4FN6JRUPOJYM -m comment --comment
"default/mysql-service:"
-m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-ID6YWIT3F6WNZ47P
-A KUBE-SVC-67RL4FN6JRUPOJYM -m comment --comment
"default/mysql-service:"
-j KUBE-SEP-IN2YML2VIFH5RO2T
|
這里利用了iptables的–probability的特性,使連接有50%的概率進入到KUBE-SEP-ID6YWIT3F6WNZ47P鏈,50%的概率進入到KUBE-SEP-IN2YML2VIFH5RO2T鏈。 KUBE-SEP-ID6YWIT3F6WNZ47P的鏈的具體作用就是將請求通過DNAT發送到192.168.125.129的3306端口:
1
2
|
-A KUBE-SEP-ID6YWIT3F6WNZ47P -s 192.168.125.129
/32
-m comment --comment
"default/mysql-service:"
-j KUBE-MARK-MASQ
-A KUBE-SEP-ID6YWIT3F6WNZ47P -p tcp -m comment --comment
"default/mysql-service:"
-m tcp -j DNAT --to-destination 192.168.125.129:3306
|
同理KUBE-SEP-IN2YML2VIFH5RO2T的作用是通過DNAT發送到192.168.125.131的3306端口:
1
2
|
-A KUBE-SEP-IN2YML2VIFH5RO2T -s 192.168.125.131
/32
-m comment --comment
"default/mysql-service:"
-j KUBE-MARK-MASQ
-A KUBE-SEP-IN2YML2VIFH5RO2T -p tcp -m comment --comment
"default/mysql-service:"
-m tcp -j DNAT --to-destination 192.168.125.131:3306
|
分析完nodePort的工作方式,接下里說一下clusterIP的訪問方式。 對於直接訪問cluster IP(10.254.162.44)的3306端口會直接跳轉到KUBE-SVC-67RL4FN6JRUPOJYM
1
|
-A KUBE-SERVICES -d 10.254.162.44
/32
-p tcp -m comment --comment
"default/mysql-service: cluster IP"
-m tcp --dport 3306 -j KUBE-SVC-67RL4FN6JRUPOJYM
|
接下來的跳轉方式同NodePort方式。
3) ipvs mode. 在kubernetes 1.8以上的版本中,對於kube-proxy組件增加了除iptables模式和用戶模式之外還支持ipvs模式。kube-proxy ipvs 是基於 NAT 實現的,通過ipvs的NAT模式,對訪問k8s service的請求進行虛IP到POD IP的轉發。當創建一個 service 后,kubernetes 會在每個節點上創建一個網卡,同時幫你將 Service IP(VIP) 綁定上,此時相當於每個 Node 都是一個 ds,而其他任何 Node 上的 Pod,甚至是宿主機服務(比如 kube-apiserver 的 6443)都可能成為 rs;
與iptables、userspace 模式一樣,kube-proxy 依然監聽Service以及Endpoints對象的變化, 不過它並不創建反向代理, 也不創建大量的 iptables 規則, 而是通過netlink 創建ipvs規則,並使用k8s Service與Endpoints信息,對所在節點的ipvs規則進行定期同步; netlink 與 iptables 底層都是基於 netfilter 鈎子,但是 netlink 由於采用了 hash table 而且直接工作在內核態,在性能上比 iptables 更優。其工作流程大體如下:
由此分析:ipvs 是目前 kube-proxy 所支持的最新代理模式,相比使用 iptables,使用 ipvs 具有更高的性能。
Endpoint訪問外部服務
k8s訪問集群外獨立的服務最好的方式是采用Endpoint方式,以mysql服務為例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
1)創建mysql-service.yaml
[root@kevin~]
# vim mysql-service.yaml
apiVersion: v1
kind: Service
metadata:
name: mysql-kevin
spec:
ports:
- port: 3306
2) 創建mysql-endpoints.yaml
[root@kevin~]
# vim mysql-endpoints.yaml
kind: Endpoints
apiVersion: v1
metadata:
name: mysql-kevin
namespace: default
subsets:
- addresses:
- ip: 172.16.60.55
ports:
- port: 3306
3) 測試連接數據庫
[root@kevin~]
# kubectl exec -it mysql-client-h7jk8 bash
bash
-4.1
# mysql -hmysql-kevin -u user -p
Enter password:
.........
mysql>
4) 查看這個service
[root@kevin~]
# kubectl describe svc mysql-kevin
Name: mysql-kevin
Namespace: default
Labels: <none>
Annotations: <none>
Selector: <none>
Type: ClusterIP
IP: 10.254.125.157
Port: <
unset
> 3306
/TCP
Endpoints: 172.16.60.55:3306
Session Affinity: None
Events: <none>
|
下面簡單說kube-proxy是如何實現一個請求經過層層轉發最后落到某個pod上的整個過程,這個請求可能來自pod也可能來自外部。
-> kube-proxy為集群提供service功能,相同功能的pods對外抽象為service,service可以實現反向代理和服務發現。可以分為iptables模式和userspace模式。具體有iptables實現
-> 在反向代理方面,kube-proxy默認使用rr算法實現客戶端流量分發到后端的pod
k8s的service和endpoine是如何關聯和相互影響的?
-> api-server創建service對象,與service綁定的pod地址:稱之為endpoints
-> 服務發現方面:kube-proxy監控service后端endpoint的動態變化,並且維護service和endpoint的映射關系
一個經典pod的完整生命周期
-> Pending
-> Running
-> Succeeded
-> Failed
關系流程圖如下:
K8S Endpoint一會消失一會出現的問題
在使用K8s集群時遇到的問題:發現某個service的后端endpoint一會顯示有后端,一會顯示沒有。顯示沒有后端,意味着后端的address被判定為notready。
經過排查確定原因:
kubelet在准備上報信息時,需要收集容器、鏡像等的信息。雖然kubelet默認是10秒上報一次,但是實際的上報周期約為20~50秒。而kube-controller-manager判斷node上報心跳超時的時間為40秒。所以會有一定概率超時。一旦超時,kube-controller會將該node上的所有pod的conditions中type是Ready的字典中的status置為False。
解決辦法:
較為簡單的方案是在kube-controller上配置這個超時時間node-monitor-grace-period長一些。建議配置為60 ~ 120s。