一. kube-proxy 和 service node
kube-proxy是Kubernetes的核心組件,部署在每一個Node節點上,它是實現Kubernetes Service的通訊與負載均衡機制的重要組件; kube-proxy負責爲Pod建立代理服務,從apiserver獲取全部server信息,並根據server信息建立代理服務,實現server到Pod的請求路由和轉發,從而實現K8s層級的虛擬轉發網絡。mysql
在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的服務性能。linux
kube-proxy負責爲Service提供cluster內部的服務發現和負載均衡,它運行在每一個Node計算節點上,負責Pod網絡代理, 它會定時從etcd服務獲取到service信息來作相應的策略,維護網絡規則和四層負載均衡工做。在K8s集羣中微服務的負載均衡是由Kube-proxy實現的,它是K8s集羣內部的負載均衡器,也是一個分佈式代理服務器,在K8s的每一個節點上都有一個,這一設計體現了它的伸縮性優點,須要訪問服務的節點越多,提供負載均衡能力的Kube-proxy就越多,高可用節點也隨之增多。redis
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的變化。sql
舉個例子,好比如今有podA,podB,podC和serviceAB。serviceAB是podA,podB的服務抽象(service)。那麼kube-proxy的做用就是能夠將pod(不論是podA,podB或者podC)向serviceAB的請求,進行轉發到service所表明的一個具體pod(podA或者podB)上。請求的分配方法通常分配是採用輪詢方法進行分配。另外,kubernetes還提供了一種在node節點上暴露一個端口,從而提供從外部訪問service的方式。好比這裏使用這樣的一個manifest來建立service數據庫
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端口。vim
Service, Endpoints與Pod的關係後端
Kube-proxy進程獲取每一個Service的Endpoints,實現Service的負載均衡功能api
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,則其對應的環境變量爲:
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轉發進行實現:
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:
$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端口訪問,則會進入到如下鏈:
-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的鏈:
-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端口:
-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端口:
-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
-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)建立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。