k8s技術預研9--Kubernetes核心組件運行原理分析


1、Kubernetes API Server原理分析

Kubernetes API Server的核心功能是提供了Kubernetes各類資源對象(如Pod、RC、Service等)的增、刪、改、查及Watch等HTTP Rest接口,成為集群內各個功能模塊之間數據交互和通信的中心樞紐,是整個系統的數據總線和數據中心。除此之外,它還有以下一些功能特性:
  • 是集群管理的API入口。
  • 是資源配額控制的入口。
  • 提供了完備的集群安全機制。
 

1.1 k8s API Server概述

k8s API Server通過一個名為kube-apiserver的進程提供服務,該進程運行在Master節點上。在默認情況下,kube-apiserver進程在本機的8080端口(對應參數--insecure-port)提供REST服務。我們可以同時啟動HTTPS安全端口(--secure-prt=6443)來啟動安全機制,加強REST API訪問的安全性。
(1)方式一:通常我們可以通過命令行工具kubectl 來與k8s API Server交互,它們之間的接口是REST調用。
下面使用curl進行一些測試驗證。
登錄Master節點,查看k8s API的版本信息:
[root@localhost ~]# curl localhost:8080/api
{
  "kind": "APIVersions",
  "versions": [
    "v1"
  ],
  "serverAddressByClientCIDRs": [
    {
      "clientCIDR": "0.0.0.0/0",
      "serverAddress": "10.0.2.5:6443"
    }
  ]
}[root@localhost ~]#
 
運行下面的curl命令,分別返回集群中的Pod列表、Service列表、RC列表:
curl localhost:8080/api/v1/pods
curl localhost:8080/api/v1/services
curl localhost:8080/api/v1/replicationcontrollers
 
(2)方式二:如果我們只想對外暴露部分REST服務,則可以在Master或其他任何節點上通過運行kubectl proxy進程啟動一個內部代理來實現
運行下面的命令,在8001端口啟動代理,並且拒絕客戶端訪問RC的API:
[root@localhost ~]# kubectl proxy --reject-paths="^/api/v1/replicationcontrollers" --port=8001 --v=2
Starting to serve on 127.0.0.1:8001
已經禁止訪問:
[root@localhost ~]# curl localhost:8001/api/v1/replicationcontrollers
<h3>Unauthorized</h3>
 
kubectl proxy具有很多特性,最實用的一個是提供了簡單有效的安全機制,比如采用白名單方式限制非法客戶的訪問時,只要增加下面的參數即可:
--accept-hosts="^localhost,^127\\.0\\.0\\.1$,^\\[::1\\]$"
 
(3)方式三:通過編程的方式調用k8s API Server
通常用來實現分布式集群搭建,或者開發基於k8s的管理平台。
 

1.2 獨特的k8s Proxy API接口

k8s API Server最主要的REST接口是資源對象的增刪改查,另外還有一類特殊的REST接口—k8s Proxy API接口,這類接口的作用是代理REST請求,即kubernetes API Server把收到的REST請求轉發到某個Node上的kubelet守護進程的REST端口上,由該kubelet進程負責響應。
(1)k8s Proxy API關於Node的相關接口
關於Node相關的接口的REST路徑為:/api/v1/proxy/nodes/{name},其中{name}為節點的名稱或IP地址。
包括以下幾個具體的接口:
/api/v1/proxy/nodes/{name}/pods/    #列出指定節點內所有Pod的信息
/api/v1/proxy/nodes/{name}/stats/   #列出指定節點內物理資源的統計信息
/api/v1/prxoy/nodes/{name}/spec/    #列出指定節點的概要信息
 
需要說明的是:這里獲取的Pod信息來自Node而非etcd數據庫,兩者時間點可能存在偏差。
 
此外,如果在kubelet進程啟動時加--enable-debugging-handles=true參數,那么kubernetes Proxy API還會增加以下接口:
/api/v1/proxy/nodes/{name}/run      #在節點上運行某個容器
/api/v1/proxy/nodes/{name}/exec     #在節點上的某個容器中運行某條命令
/api/v1/proxy/nodes/{name}/attach   #在節點上attach某個容器
/api/v1/proxy/nodes/{name}/portForward   #實現節點上的Pod端口轉發
/api/v1/proxy/nodes/{name}/logs     #列出節點的各類日志信息
/api/v1/proxy/nodes/{name}/metrics  #列出和該節點相關的Metrics信息
/api/v1/proxy/nodes/{name}/runningpods  #列出節點內運行中的Pod信息
/api/v1/proxy/nodes/{name}/debug/pprof  #列出節點內當前web服務的狀態,包括CPU和內存的使用情況
 
(2)k8s Proxy API關於Pod的相關接口
通過這些接口,我們可以訪問Pod里某個容器提供的服務。
/api/v1/proxy/namespaces/{namespace}/pods/{name}/{path:*}      #訪問pod的某個服務接口
/api/v1/proxy/namespaces/{namespace}/pods/{name}                    #訪問Pod
/api/v1/proxy/namespaces/{namespace}/services/{name}/{path:*}      #訪問service的某個服務接口
/api/v1/proxy/namespaces/{namespace}/services/{name}                   #訪問service
 
Pod的proxy接口的作用是在kubernetes集群之外訪問某個pod容器的服務(HTTP服務),可以用Proxy API實現,這種場景多用於管理目的,比如逐一排查Service的Pod副本,檢查哪些Pod的服務存在異常問題。
注:k8s Proxy API關於Pod的相關接口在v1.8.8版本的一個實驗環境中驗證測試失敗,問題待解。
 

1.3 集群功能模塊之間的通信

kubernetes API Server作為集群的核心,負責集群各功能模塊之間的通信,集群內各個功能模塊通過API Server將信息存入etcd,當需要獲取和操作這些數據時,通過API Server提供的REST接口(GET\LIST\WATCH方法)來實現,從而實現各模塊之間的信息交互。
 
主要有以下三類通信交互的場景:
1)kubelet與API Server交互
每個Node節點上的kubelet定期就會調用API Server的REST接口報告自身狀態,API Server接收這些信息后,將節點狀態信息更新到etcd中。kubelet也通過API Server的Watch接口監聽Pod信息,如果監聽到新的Pod副本被調度綁定到本節點,則執行Pod對應的容器的創建和啟動邏輯;如果監聽到Pod對象被刪除,則刪除本節點上的相應的Pod容器;如果監聽到修改Pod信息,則kubelet監聽到變化后,會相應的修改本節點的Pod容器。
 
2)kube-controller-manager與API Server交互
kube-controller-manager中的Node Controller模塊通過API Server提供的Watch接口,實時監控Node的信息,並做相應處理。
 
3)kube-scheduler與API Server交互
Scheduler通過API Server的Watch接口監聽到新建Pod副本的信息后,它會檢索所有符合該Pod要求的Node列表,開始執行Pod調度邏輯。調度成功后將Pod綁定到目標節點上。
 
為了緩解各模塊對API Server的訪問壓力,各功能模塊都采用緩存機制來緩存數據,各功能模塊定時從API Server獲取指定的資源對象信息(LIST/WATCH方法),然后將信息保存到本地緩存,功能模塊在某些情況下不直接訪問API Server,而是通過訪問緩存數據來間接訪問API Server。
 

2、Controller Manager原理分析

Controller Manager作為集群內部的管理控制中心,負責集群內的Node、Pod副本、服務端點(Endpoint)、命名空間(Namespace)、服務賬號(ServiceAccount)、資源定額(ResourceQuota)的管理,當某個Node意外宕機時,Controller Manager會及時發現此故障並執行自動化修復流程,確保集群始終處於預期的工作狀態。
Controller Manager內部包含多種Controller,每種Controller都負責一種具體的控制流程,而Controller Manager正是這些Controller的核心
  • Replication Controller
  • Node Controller
  • ResourceQuota Controller
  • Namespace Controller
  • ServiceAccount Controller
  • Token Controller
  • Service Controller
  • Endpoint Controller
每個Controller通過API Server提供的接口實時監控整個集群的每個資源對象的當前狀態,當發生各種故障導致系統狀態發生變化時,會嘗試着將系統狀態從"現有狀態"修正到"期望狀態"。
 

2.1 Replication Controller

這里的Replication Controller所指的是副本控制器,並不是RC資源對象,注意不要混淆。
Replication Controller 副本控制器的核心作用是確保在任何時候集群中一個RC所關聯的Pod副本數始終保持預設值。需要注意的一點是:只有當Pod的重啟策略是Always的時候(RestartPolicy=Always),副本控制器才會管理該Pod的操作(例如創建、銷毀、重啟等)。
Replication Controller 副本控制器在管理Pod時的一些特征:
  • 在通常情況下,Pod對象被成功創建后不會消失,唯一的例外是當Pod處於succeeded或failed狀態的時間過長(超時參數由系統設定)時,該Pod會被系統自動回收。管理該Pod的副本控制器將在其他工作節點上重新創建、運行該Pod副本。
  • RC中的Pod模板就像一個模具,模具制造出來的東西一旦離開模具,它們之間就再沒關系了。一旦Pod被創建,無論模板如何變化,也不會影響到已經創建的Pod。
  • 此外,Pod可以通過修改標簽來脫離RC的管控,該方法可以用於將Pod從集群中遷移,數據修復等調試。
  • 刪除一個RC不會影響它所創建的Pod,如果要刪除Pod需要將RC的副本數屬性設置為0。
最好不要越過RC而直接創建Pod,因為Replication Controller會通過RC管理Pod副本,實現自動創建、補足、替換、刪除Pod副本,這樣能提高系統的容災能力,減少由於節點崩潰等意外狀況造成的損失。即使你的應用程序只用到一個Pod副本,我們也強烈建議使用RC來定義Pod。
 
Replication Controller的職責:
1)確保當前集群中有且僅有N個Pod實例,N是RC中定義的Pod副本數量。
2)通過調整RC中的spec.replicas屬性值來實現系統擴容或縮容。
3)通過改變RC中的Pod模板(主要是鏡像版本)來實現系統的滾動升級。
 
與上述三類職責所對應的是Replication Controller的三類典型使用場景:
1)重新調度,當發生節點故障或Pod被意外終止運行時,可以重新調度保證集群中仍然運行指定的副本數。
2)彈性伸縮,通過手動或自動擴容代理修復副本控制器的spec.replicas屬性,可以實現彈性伸縮。
3)滾動更新,推薦的方式是創建一個新的只有一個副本的RC,若新的RC副本數量加1,則舊的RC的副本數量減1,直到這個舊的RC的副本數量為零,然后刪除該舊的RC。kubectl rolling-update命令就是按該推薦方式所實現的。
 

2.2 Node Controller

kubelet進程在啟動時會通過API Server注冊自身的節點信息,並定時向API Server匯報狀態信息,API Server接收到信息后將信息更新到etcd中。etcd中存儲的節點信息包括節點健康狀況、節點資源、節點名稱、節點地址信息、操作系統版本、Docker版本、kubelet版本等。節點健康狀況包含"就緒"(True)、"未就緒"(False)和"未知"(Unknown)三種。
Node Controller通過API Server實時獲取Node的相關信息,實現管理和監控集群中的各個Node節點的相關控制功能。
 
Node Controller的核心工作流程如下圖所示:
對流程中關鍵點的解釋如下:
(1)Controller Manager在啟動時如果設置了--cluster-cidr參數,那么為每個沒有設置Spec.PodCIDR的Node節點生成一個CIDR地址,並用該CIDR地址設置節點的Spec.PodCIDR屬性,防止不同的節點的CIDR地址發生沖突。
(2)Controller Manager在處理節點的狀態監控管理時的具體流程見以上流程圖。
(3)逐個讀取節點信息,如果節點狀態變成非“就緒”狀態,則將節點加入待刪除隊列,否則將節點從該隊列刪除。如果節點狀態為非"就緒"狀態,則刪除etcd中的節點信息,並刪除和該節點相關的Pod等資源信息。
 

2.3 ResourceQuota Controller

資源配額管理確保指定的資源對象在任何時候都不會超量占用系統物理資源。
目前k8s支持如下三個層次的資源配額管理:
    1)容器級別:對CPU和Memory進行限制
    2)Pod級別:對一個Pod內所有容器的可用資源進行限制
    3)Namespace級別:是Namespace級別(多租戶)的限制,包括:
  • Pod數量
  • Replication Controller數量
  • Service數量
  • ResourceQuota數量
  • Secret數量
  • 可持有的PV(Persistent Volume)數量
 
k8s配額管理是通過Admission Control(准入控制)來控制的。 Admission Control當前提供兩種配額約束的方式,分別是LimitRanger和ResourceQuota。
其中LimitRanger作用於Pod和Container上,而ResourceQuota則作用於Namespace上,限定一個Namespace里的各類資源的使用總額。
 
ResourceQuota Controller流程圖:

2.4 Namespace Controller

用戶通過API Server可以創建新的Namespace並保存在etcd中,Namespace Controller定時通過API Server讀取這些Namespace信息。
如果Namespace被API標記為優雅刪除(即設置刪除期限,DeletionTimestamp),則將該Namespace狀態設置為“Terminating”並保存到etcd中。同時Namespace Controller刪除該Namespace下的ServiceAccount、RC、Pod、Secret、PersistentVolume、ListRange、ResourceQuota和Event等資源對象。
當Namespace狀態設置為“Terminating”后,由Admission Controller的NamespaceLifecycle插件來阻止為該Namespace創建新的資源。同時,在Namespace Controller刪除完該Namespace中的所有資源對象后,Namespace Controller對該Namespace執行finalize操作,刪除Namespace的spec.finalizers域中的信息。
 

2.5 Service Controller與Endpoint Controller

如下圖所示,Endpoints表示一個Service對應的所有Pod副本的訪問地址,而Endpoints Controller就是負責生成和維護所有Endpoints對象的控制器。
Endpoints表示了一個Service對應的所有Pod副本的訪問地址,而Endpoints Controller負責生成和維護所有Endpoints對象的控制器。它負責監聽Service和對應的Pod副本的變化。
  • 如果監測到Service被刪除,則刪除和該Service同名的Endpoints對象;
  • 如果監測到新的Service被創建或修改,則根據該Service信息獲得相關的Pod列表,然后創建或更新Service對應的Endpoints對象。
  • 如果監測到Pod的事件,則更新它對應的Service的Endpoints對象。
kube-proxy進程獲取每個Service的Endpoints,實現Service的負載均衡功能。
 
Service Controller
Service Controller是屬於kubernetes集群與外部的雲平台之間的一個接口控制器。Service Controller監聽Service變化,如果是一個LoadBalancer類型的Service,則確保外部的雲平台上對該Service對應的LoadBalancer實例被相應地創建、刪除及更新路由轉發表。

 

3、Scheduler原理分析

k8s Scheduler在整個系統中承擔了"承上啟下"的重要功能。承上,是指它負責接收Controller Manager創建的新Pod,為其安排一個落腳的“家”, 即目標Node。啟下,是指安置工作完成后,目標Node上的kubelet服務進程接管后繼工作,負責Pod生命周期中的“下半生”。
具體來說,k8s Scheduler的作用是將待調度的Pod(API新創建的Pod、Controller Manager為補足副本而創建的Pod等)按照特定的調度算法和調度策略綁定到集群中的某個合適的Node上,並將綁定信息寫入etcd中。
在整個調度過程中涉及三個對象,分別是:待調度Pod列表、可用Node列表,以及調度算法和策略。
 
隨后,目標節點上的kubelet通過API Server監聽到k8s Scheduler產生的Pod綁定事件,然后獲取對應的Pod清單,下載Image鏡像,並啟動容器。
完整的流程如下所示:
k8s Scheduler當前提供的默認調度流程分為以下兩步:
1)預選調度過程,即遍歷所有目標Node,篩選出符合要求的候選節點,kubernetes內置了多種預選策略(xxx Predicates)供用戶選擇。
2)確定最優節點,在第一步的基礎上采用優選策略(xxx Priority)計算出每個候選節點的積分,最高積分者勝出。
k8s Scheduler的調度流程是通過插件方式加載的“調度算法提供者”(AlgorithmProvider)具體實現的。一個AlgorithmProvider其實就是包括了一組預選策略與一組優選策略的結構體。
注冊AlgorithmProvider的函數如下:
func RegisterAlgorithmProvider(name string, predicateKeys, priorityKeys util.StringSet)
它包含三個參數:
  • name.string,算法名;
  • predicateKeys,為算法用到的預選策略集合;
  • priorityKeys,為算法用到的優選策略的集合;
 
Scheduler中可用的預選策略包含:NoDiskConflict, PodFitsResources, PodSelectorMatches, PodFirstHost, CheckNodeLabelPresence, CheckServiceAffinity和PodFitsPorts策略等。
其默認的AlgorithmProvider加載的預選策略Predicates包括:PodFitsPorts, PodFitsResources, NoDiskConflict, MatchNodeSelector(PodSelectorMatches) 和 HostName(PodFitsHost),即每個節點只有通過前面提及的5個默認預選策略后,才能初步被選中,進入下一個流程。
 

3.1 下面列出的是對所有預選策略的詳細說明。

1)NoDiskConflict
判斷備選Pod的gcePersistentDisk或AWSElasticBlockStore和備選的節點中已存在的Pod是否存在沖突。檢測過程如下:
  • 首先,讀取備選Pod的所有Volume的信息(即pod.Spec.Volumes),對每個Volume執行以下步驟進行沖突檢測。
  • 如果該Volume是gcePersistentDisk,則將Volume和備選節點上的所有Pod的每個Volume進行比較,如果發現相同的gcePersistentDisk,則返回false,表明存在磁盤沖突,檢查結束,反饋給調度器該備選節點不適合作為備選Pod;如果該Volume是AWSElasticBlockStore,則將Volume和備選節點上的所有Pod的每個Volume進行比較,如果發現相同的AWSElasticBlockStore,則返回false,表明存在磁盤沖突,檢查結束,反饋給調度器該備選節點不適合備選Pod。
  • 如果檢查完備選Pod的所有Volume均未發現沖突,則返回true,表明不存在磁盤沖突,反饋給調度器該備選節點適合備選Pod。
2)PodFitsResources
判斷備選節點的資源是否滿足備選Pod的需求,檢測過程如下:
  • 計算備選Pod和節點中已存在Pod的所有容器的需求資源(內存和CPU)的總和。
  • 獲得備選節點的狀態信息,其中包含節點的資源信息。
  • 如果備選Pod和節點中已存在Pod的所有容器的需求資源(內存和CPU)的總和,超出了備選節點擁有的資源,則返回false,表明備選節點不適合備選Pod,否則返回true,表明備選節點適合備選Pod。
3)PodSelectorMatches
判斷備選節點是否包含備選Pod的標簽選擇器指定的標簽。
  • 如果Pod沒有指定spec.nodeSelector標簽選擇器,則返回true。
  • 否則,獲得備選節點的標簽信息,判斷節點是否包含備選Pod的標簽選擇器(spec.nodeSelector)所指定的標簽,如果包含,則返回true,否則返回false。
4)PodFitsHost
判斷備選Pod的spec.nodeName域所指定的節點名稱和備選節點的名稱是否一致,如果一致,則返回true,否則返回false。
5)CheckNodeLabelPresence
如果用戶在配置文件中指定了該策略,則Scheduler會通過RegisterCustomFitPredicate方法注冊該策略。該策略用於判斷策略列出的標簽在備選節點中存在時,是否選擇該備選節點。
  • 讀取備選節點的標簽列表信息。
  • 如果策略配置的標簽列表存在於備選節點的標簽列表中,且策略配置的presence值為false,則返回false,否則返回true;如果策略配置的標簽列表不存在於備選節點的標簽列表中,且策略配置的presence為true,則返回false,否則返回true。
6)CheckServiceAffinity
如果用戶在配置文件中指定了該策略,則Scheduler會通過RegisterCustomFitPredicate方法注冊該策略。該策略用於判斷備選節點是否包含策略指定的標簽,或包含和備選Pod在相同Service和Namespace下的Pod所在節點的標簽列表。如果存在,則返回true,否則返回false。
7)PodFitsPorts
判斷備選Pod所用的端口列表中的端口是否在備選節點中已被占用,如果被占用,則返回false,否則返回true。
 

3.2 Scheduler中的優選策略

Scheduler中的優選策略包含:LeastRequestedPriority、CalculateNodeLabelPriority和BalancedResourceAllocation等。
每個節點通過優選策略時都會算出一個得分,計算各項得分,最終選出得分值最大的節點作為優選的結果(也是調度算法的結果)。
下面是對優選策略的詳細說明:
1) LeastRequestedPriority
優先從備選節點列表中選擇資源消耗最小的節點(CPU+內存)。
 
2) CalculateNodeLabelPriority
如果用戶在配置文件中指定了該策略,則scheduler會通過RegisterCustomPriorityFunction方法注冊該策略。該策略用於判斷策略列出的標簽在備選節點中存在時,是否選擇該備選節點。
如果備選節點的標簽在優先策略的標簽列表中且優選策略的presence為true,或者備選節點的標簽不在優選策略的標簽列表中且優選策略的presence值為false,則備選節點score=10,否則備選節點score=0。
 
3) BalancedResourceAllocation
優先從備選節點列表中選擇各項資源使用率最均衡的節點。
 

4、kubelet運行機制分析

在kubernetes集群中,每個Node節點(又稱Minion)上都會啟動一個kubelet服務進程。該進程用於處理Master節點下發到本節點的任務,管理Pod和其中的容器。每個kubelet進程會在API Server上注冊節點自身信息,定期向Master節點匯報節點資源的使用情況,並通過cAdvisor監控容器和節點資源。
 

4.1 節點管理

節點通過設置kubelet的啟動參數“--register-node”,來決定是否自動主動地向API Server注冊自己,默認為true。
在自注冊時,kubelet啟動時還包含以下參數:
  • --api-servers: API Server的位置
  • --kubeconfig:kubeconfig文件,用於訪問API Server的配置文件
  • --cloud-provider:雲服務商地址,僅用於公有雲環境
當前每個kubelet被授予創建和修改任務節點的權限,但實踐中,它僅僅創建和修改自己。未來將會限制這一權限,僅允許它修改和創建其所在節點的權限。
在集群運行過程中,如果遇到資源不足的情況,則用戶很容易通過添加機器及利用kubelet的自注冊模式來實現擴容。
如果系統管理員希望手動創建節點信息,則可以通過設置--register-node=false即可。
 
通過--node-status-update-frequency參數,可以配置kubelet向API Server報告節點狀態的時間頻率,默認為間隔10s。
 

4.2 Pod管理

kubelet通過以下幾種方式獲取自身Node上所需要運行的Pod清單。
  • 文件:kubelet啟動參數"--config"指定的配置文件目錄下的文件,通過--file-check-frequency設置檢查該文件目錄的時間間隔,默認為20s。
  • HTTP端點(URL):通過"--manifest-url"參數設置,通過--http-check-frequency設置檢查時間間隔,默認為20s。
  • API Server:kubelet通過API Server監聽etcd目錄,同步Pod列表。
所有以非API Server方式創建的Pod都叫做Static Pod。kubelet將Static Pod狀態匯報給API Server,API Server為該Static Pod創建一個Mirror Pod和其相匹配。Mirror Pod的狀態將真實反映Static Pod的狀態。當Static Pod被刪除時,與之相對應的Mirror Pod也會被刪除。
在本文只討論通過API Server獲得Pod清單的方式。kubelet通過API Server Client使用Watch加List的方式監聽etcd中/registry/nodes/${當前節點名稱}和/registry/pods的目錄,將獲取的信息同步到本地緩存中。
 
kubelet監聽etcd,所以針對Pod的操作將會被kubelet監聽到。如果發現有新的綁定到本節點的Pod,則按照Pod清單的要求創建該Pod。
如果發現本地Pod被修改,則kubelet會做出相應的修改,比如刪除Pod中的某個容器時,則通過Docker Client刪除該容器。
如果發現刪除本節點的Pod,則刪除相應的Pod,並通過Docker Client刪除Pod中的容器。
 
kubelet讀取監聽到的信息,如果是創建和修改Pod任務,則做如下處理:
1)為該Pod創建一個數據目錄。
2)從API Server讀取該Pod清單。
3)為該Pod掛載外部卷(External Volume)
4)下載Pod用到的Secret。
5)檢查已運行在節點中的Pod,如果該Pod沒有容器或Pause容器沒有啟動,則先停止Pod里所有容器的進程。如果在Pod中有需要刪除的容器,則刪除這些容器。
6)用"kubernetes/pause"鏡像為每個Pod創建一個容器。該Pause容器用於接管Pod中所有其它容器的網絡。
7)為Pod中的每個容器做如下處理:
  • 為容器計算一個hash值,然后用容器的名字去查詢對應Docker容器的hash值。這里的hash值可以理解為是依據容器的定義文件所生成的。對比兩個hash值,發現有不同則會停止Docker中容器的進程,並停止與之關聯的Pause容器的進程。若二者相同,則不做任何處理。
  • 如果容器被終止了,且沒有指定restartPolicy重啟策略,則不做任何處理。
  • 調用Docker Client下載容器鏡像,調用Docker Client運行容器。
 

4.3 容器健康檢查

Pod的健康狀態由兩類探針來檢查:LivenessProbe和ReadinessProbe。
LivenessProbe
  • 用於判斷容器是否存活(running狀態)。
  • 如果LivenessProbe探針探測到容器非健康,則kubelet將殺掉該容器,並根據容器的重啟策略做相應處理。
  • 如果容器不包含LivenessProbe探針,則kubelet認為該探針的返回值永遠為“success”。
 
kubelet定期執行LivenessProbe探針來判斷容器的健康狀態。
LivenessProbe參數:
  • initialDelaySeconds:啟動容器后首次進行健康檢查的等待時間,單位為秒。
  • timeoutSeconds:健康檢查發送請求后等待響應的時間,如果超時響應kubelet則認為容器非健康,重啟該容器,單位為秒。
LivenessProbe三種實現方式
1)ExecAction
在一個容器內部執行一個命令,如果該命令狀態返回值為0,則表明容器健康。
apiVersion: v1
kind: Pod
metadata:
  name: liveness-exec
spec:
  containers:
  - name: liveness
    image: busybox
    args:
    - /bin/sh
    - -c
    - echo ok > /tmp/health;sleep 10;rm -fr /tmp/health;sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/health
      initialDelaySeconds: 15
      timeoutSeconds: 1
 
2)TCPSocketAction
通過容器IP地址和端口號執行TCP檢查,如果能夠建立TCP連接,則表明容器健康。
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-healthcheck
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    - containnerPort: 80
    livenessProbe:
      tcpSocket:
        port: 80
      initialDelaySeconds: 15
      timeoutSeconds: 1
 
3)HTTPGetAction
通過容器的IP地址、端口號及路徑調用HTTP Get方法,如果響應的狀態碼大於等於200且小於等於400,則認為容器健康。
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-healthcheck
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    - containnerPort: 80
    livenessProbe:
      httpGet:
        path: /_status/healthz
        port: 80
      initialDelaySeconds: 15
      timeoutSeconds: 1
 
ReadinessProbe
  • 用於判斷容器是否啟動完成(read狀態),可以接受請求。
  • 如果ReadnessProbe探針檢測失敗,則Pod的狀態將被修改。Endpoint Controller將從Service的Endpoint中刪除包含該容器所在Pod的Endpoint。

4.4 cAdvisor資源監控

在kubernetes集群中,應用程序的執行情況可以在不同的級別上監測到。這些級別包括:容器、Pod、Service和整個集群。
Heapster項目為k8s提供了一個基本的監控平台,它是集群級別的監控和事件數據集成器(Aggregator)。Heapster作為Pod運行在k8s集群中,通過kubelet發現所有運行在集群中的節點,並查看來自這些節點的資源使用狀況信息。
kubelet通過cAdvisor獲取其所在節點及容器的數據,Heapster通過帶着關聯標簽的Pod分組這些信息,這些數據被推動一個可配置的后端,用於存儲和展示。當前支持的后端包括InfluxDB(with Grafana for Visualization)和Google Cloud Monitoring。
 
cAdvisor是一個開源的分析容器資源使用率和性能特性的代理工具。在k8s項目中,cAdvisor被集成到k8s代碼中,cAdvisor自動查找所有在其所在節點上的容器,自動采集CPU、內存、文件系統和網絡使用的統計信息。
cAdvisor通過它所在節點機的Root容器,采集並分析該節點機的全面使用情況。
在大部分k8s集群中,cAdvisor通過它所在節點機的4194端口暴露一個簡單的UI。
 
小結:
kubelet作為連接k8s Master和各節點機之間的橋梁,管理運行在節點機上的Pod和容器。kubelet將每個Pod轉換成它的成員容器,同時從cAdvisor獲取單獨的容器使用統計信息,然后通過該REST API暴露這些聚合后的Pod資源使用的統計信息。
 

5、kube-proxy運行機制分析

 

5.1 基本原理

為了支持集群的水平擴展、高可用性,k8s抽象出來Service的概念。Service是一組Pod的抽象,它會根據訪問策略(如負載均衡策略)來訪問這組Pod。
k8s在創建服務時會為服務分配一個虛擬的IP地址,客戶端通過訪問這個虛擬的IP地址來訪問服務,而服務則負責將請求轉發到后端的Pod上。這就相當於是一個反向代理。但是它和普通的反射代理有一些不同之處:首先,它的IP地址是虛擬的;其次是,它的部署和啟停是k8s統一自動管理的。
 
Service在很多情況下只是一個概念,而真正將Service的作用落實的是背后的kube-proxy服務進程。
 
在k8s集群的每個Node上都會運行一個kube-proxy服務進程,這個進程可以看作Service的透明代理兼負載均衡器,其核心功能是將到某個Service的訪問請求轉發到后端的多個Pod實例上。對每一個TCP類型的k8s Service,kube-proxy都會在本地Node上建立一個SocketServer來負責接收請求,然后均勻發送到后端某個Pod的端口上,這個過程默認采用Round Robin負載均衡算法。另外,k8s也提供通過修改Service的service.spec.sessionAffinity參數的值來實現會話保持特性的定向轉發。如果設置的值為"ClientIP",則將來自同一個ClientIP的請求都轉發到同一個后端Pod上。
 
Service的ClusterIP與NodePort等概念是kube-proxy服務通過iptables的NAT轉換實現的,kube-proxy在運行過程中動態創建與Service相關的iptables規則,這些規則實現了Cluster IP及NodePort的請求流量重定向到kube-proxy進程上對應服務的代理端口的功能。
由於iptables機制針對的是本地的kube-proxy端口,所以每個Node上都要運行kube-proxy組件。
 
綜上所述,由於kube-proxy的作用,在Service的調用過程中客戶端無須關心后端有幾個Pod,中間過程的通信、負載均衡及故障恢復都是透明的。
訪問Service的請求,不論是用ClusterIP+TargetPort的方式,還是用節點機IPO+NodePort的方式,都被節點機的iptables規則重定向到kube-proxy監聽Service服務代理端口。如下圖所示。
 

5.2 深入分析kube-proxy的實現細節

kube-proxy通過查詢和監聽API Server中Service與Endpoints的變化,為每個Service都建立了一個"服務代理對象",並自動同步。服務代理對象是kube-proxy程序內部的一種數據結構,它包括一個用於監聽此服務請求的SocketServer,SocketServer的端口是隨機選擇的一個本地空閑端口。
此外,kube-proxy內部也創建了一個負載均衡器——LoadBalancer,LoadBalancer上保存了Service到對應的后端Endpoint列表的動態路由轉發路由表,而具體的路由選擇則取決於RoundRobin負載均衡算法及Service的Session會話保持(SessionAffinity)這兩個特性。
 
1)針對發生變化的Service列表,kube-proxy的具體處理流程如下:
(1)如果該Service沒有設置集群IP(ClusterIP),則不做任何處理,否則,獲取該Service的所有端口定義列表(spec.ports域)。
(2)逐個讀取服務端口定義列表中的端口信息,根據端口名稱、Service名稱和Namespace判斷本地是否已經存在對應的服務代理對象,如果不存在則新建;如果存在並且Service端口被修改過,則先刪除iptables中和該Service端口相關的規則,關閉服務代理對象,然后走新建流程,即為該Service端口分配服務代理對象並為該Service創建相關的iptables規則。
(3)更新負載均衡器組件中對應Service的轉發地址列表,對於新建的Service,確定轉發時的會話保持策略。
(4)對於已刪除的Service則進行清理。
 
2)針對Endpoint的變化,kube-proxy會自動更新負載均衡器中對應Service的轉發地址列表。
 
3)以上兩個步驟,kube-proxy針對iptables所做的一些具體操作如下:
kube-proxy在啟動時和監聽到Service或Endpoint的變化后,會在本機iptables的NAT表中添加4條規則鏈。
(1)KUBE-PORTALS-CONTAINER:從容器中通過Service Cluster IP和端口號訪問Service的請求。
(2)KUBE-PORTALS-HOST:從主機中通過Service Cluster IP和端口號訪問Service的請求。
(3)KUBE-NODEPORT-CONTAINER:從容器中通過Service的NodePort端口號訪問Service的請求。
(4)KUBE-NODEPORT-HOST:從主機中通過Service的NodePort端口號訪問Service的請求。
 
 

5.3 kube-proxy在iptables中為每個Service創建由ClusterIP+Service端口到kube-proxy所在主機IP+Service代理服務所監聽的端口的轉發規則

Kubernetes通過在目標node的iptables中的nat表的PREROUTING和POSTROUTING鏈中創建一系列的自定義鏈 (這些自定義鏈主要是“KUBE-SERVICES”鏈、“KUBE-POSTROUTING”鏈、每個服務所對應的“KUBE-SVC-XXXXXXXXXXXXXXXX”鏈和“KUBE-SEP-XXXXXXXXXXXXXXXX”鏈),然后通過這些自定義鏈對流經到該node的數據包做DNAT和SNAT操作以實現路由、負載均衡和地址轉換。
 
基於iptables的kube-proxy的實現代碼在pkg/proxy/iptables/proxier.go,其主要職責包括兩大塊,一塊是偵聽Service更新事件,並更新Service相關的iptables規則,一塊是偵聽Endpoint更新事件,更新Endpoint相關的iptables規則,將包請求轉入Endpoint對應的Pod,如果某個Service尚沒有Pod創建,那么針對此Service的請求將會被drop掉。
 
kube-proxy對iptables的鏈進行了擴充,自定義了KUBE-SERVICES,KUBE-NODEPORTS,KUBE-POSTROUTING,KUBE-MARK-MASQ和KUBE-MARK-DROP五個鏈,並主要通過為
KUBE-SERVICES chain增加rule來配制Routing Traffic規則。
 
在iptables表中,通過iptables-save可以看到在Nat表中創建好的這些鏈。
:KUBE-MARK-DROP - [0:0] /*對於未能匹配到跳轉規則的traffic set mark 0x8000,有此標記的數據包會在filter表drop掉*/
:KUBE-MARK-MASQ - [0:0] /*對於符合條件的包 set mark 0x4000, 有此標記的數據包會在KUBE-POSTROUTING chain中統一做MASQUERADE*/
:KUBE-NODEPORTS - [0:0] /*針對通過nodeport訪問的package做的操作*/
:KUBE-POSTROUTING - [0:0]
:KUBE-SERVICES - [0:0] /*操作跳轉規則的主要chain*/
 
為默認的PREROUTING,Output和POSTROUTING chain增加規則,跳轉至Kubernetes自定義的新chain。
-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
 
對於KUBE-MARK-MASQ鏈中所有規則設置了Kubernetes獨有MARK標記,在KUBE-POSTROUTING鏈中對NODE節點上匹配Kubernetes獨有MARK標記的數據包,進行SNAT處理。
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
 
Kube-proxy接着對每個服務創建“KUBE-SVC-”鏈,並在Nat表中將KUBE-SERVICES鏈中每個目標地址是Service的數據包導入這個“KUBE-SVC-”鏈,如果Endpoint尚未創建,KUBE-SVC-鏈中沒有規則,任何Incoming Packets在規則匹配失敗后會被KUBE-MARK-DROP。
 
如果一個Service對應的Pod有多個Replicas,在iptables中會有多條記錄,並通過 -m statistic --mode random --probability來控制比率。
 
PREROUTING chain的最終跳轉規則,抽象為下圖:
 
轉發規則的包匹配規則部分如下所示:
-m comment --comment $SERVICESTRING -p $PROTOCOL -m $PROTOCOL --dport $DESTPORT -d $DESTIP
其中:
  • “-m comment --comment”表示匹配規則使用iptables的顯示擴展的注釋功能,$SERVICESTRING為注釋的內容;
  • “-p $PROTOCOL -m $PROTOCOL --dport $DESTPORT -d $DESTIP”表示協議為“$PROTOCOL”且目標地址和端口為“$DESTIP”和“$DESTPORT”的包,其中“$PROTOCOL”可以為TCP或UDP,“$DESTIP”和“$DESTPORT”為Service的ClusterIP和TargetPort。
 
轉發規則的跳轉部分(-j部分):
  • -j REDIRECT --to-ports $proxyPort 。如果請求來自本地容器且Service代理服務監聽的是所有的接口,則跳轉部分實為端口重定向。該規則的功能是實現數據包的端口重定向,重定向到$proxyPort端口(Service代理服務監聽的端口);
  • -j DNAT --to-destination proxyIP:proxyPort。當請求不是來自本地容器時,需要走數據包轉發的規則,數據包的目的地址變為“proxyIP:proxyPort”(即Service代理服務所在的IP地址和端口,這些地址和端口都會被替換成實際的地址和端口)。
 
如果Service類型為NodePort,則kube-proxy在iptables中除了添加上面介紹的規則外,還會為每個Service創建由NodePort端口到kube-proxy所在主機IP+Service代理服務所監聽的端口的轉發規則。
轉發規則的包匹配規則部分如下所示:
-m comment --comment $SERVICESTRING -p $PROTOCOL -m $PROTOCOL --dport $NODEPORT
上面所列的內容用於匹配目的端口為"$NODEPORT"的包。
 
轉發規則的跳轉部分(-j部分)和前面提及的跳轉規則一致。
 

5.4 kube-proxy創建出的iptables規則樣例

 
[root@worknode1 ~]# iptables-save
# Generated by iptables-save v1.4.21 on Fri Apr  6 19:38:41 2018
*filter
:INPUT ACCEPT [137:45852]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [126:11590]
:DOCKER - [0:0]
:DOCKER-ISOLATION - [0:0]
:DOCKER-USER - [0:0]
:KUBE-FIREWALL - [0:0]
:KUBE-FORWARD - [0:0]
:KUBE-SERVICES - [0:0]
-A INPUT -j KUBE-FIREWALL
-A INPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A FORWARD -m comment --comment "kubernetes forward rules" -j KUBE-FORWARD
-A OUTPUT -j KUBE-FIREWALL
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A DOCKER -d 172.17.0.5/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 443 -j ACCEPT
-A DOCKER -d 172.17.0.5/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT
-A DOCKER-ISOLATION -j RETURN
-A DOCKER-USER -j RETURN
-A KUBE-FIREWALL -m comment --comment "kubernetes firewall for dropping marked packets" -m mark --mark 0x8000/0x8000 -j DROP
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT
COMMIT
# Completed on Fri Apr  6 19:38:41 2018
# Generated by iptables-save v1.4.21 on Fri Apr  6 19:38:41 2018
*nat
:PREROUTING ACCEPT [14:966]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [9:540]
:POSTROUTING ACCEPT [9:540]
:DOCKER - [0:0]
:KUBE-MARK-DROP - [0:0]
:KUBE-MARK-MASQ - [0:0]
:KUBE-NODEPORTS - [0:0]
:KUBE-POSTROUTING - [0:0]
:KUBE-SEP-4RATAVQC2G7Y6Z6O - [0:0]
:KUBE-SEP-BHZYIGDTOBFGE25J - [0:0]
:KUBE-SEP-OGWO7LMK6DKAZC2Q - [0:0]
:KUBE-SEP-QVN6FUUI4LG2VPZV - [0:0]
:KUBE-SEP-RXSC7ZQ2D5HSX4JT - [0:0]
:KUBE-SEP-WJGEXG4NESKNJ3Q7 - [0:0]
:KUBE-SERVICES - [0:0]
:KUBE-SVC-2QFLXPI3464HMUTA - [0:0]
:KUBE-SVC-ERIFXISQEP7F7OF4 - [0:0]
:KUBE-SVC-NPX46M4PTMTKRN6Y - [0:0]
:KUBE-SVC-R36H7ZIERA5ZZDML - [0:0]
:KUBE-SVC-TCOU7JCQXEZGVUNU - [0:0]
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A POSTROUTING -s 172.17.0.5/32 -d 172.17.0.5/32 -p tcp -m tcp --dport 443 -j MASQUERADE
-A POSTROUTING -s 172.17.0.5/32 -d 172.17.0.5/32 -p tcp -m tcp --dport 80 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 443 -j DNAT --to-destination 172.17.0.5:443
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.17.0.5:80
-A KUBE-MARK-DROP -j MARK --set-xmark 0x8000/0x8000
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
-A KUBE-SEP-4RATAVQC2G7Y6Z6O -s 172.17.0.6/32 -m comment --comment "kube-system/kube-dns:dns" -j KUBE-MARK-MASQ
-A KUBE-SEP-4RATAVQC2G7Y6Z6O -p udp -m comment --comment "kube-system/kube-dns:dns" -m udp -j DNAT --to-destination 172.17.0.6:53
-A KUBE-SEP-BHZYIGDTOBFGE25J -s 172.17.0.3/32 -m comment --comment "kube-system/default-http-backend:" -j KUBE-MARK-MASQ
-A KUBE-SEP-BHZYIGDTOBFGE25J -p tcp -m comment --comment "kube-system/default-http-backend:" -m tcp -j DNAT --to-destination 172.17.0.3:8080
-A KUBE-SEP-OGWO7LMK6DKAZC2Q -s 172.17.0.6/32 -m comment --comment "kube-system/kube-dns:dns-tcp" -j KUBE-MARK-MASQ
-A KUBE-SEP-OGWO7LMK6DKAZC2Q -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp" -m tcp -j DNAT --to-destination 172.17.0.6:53
-A KUBE-SEP-QVN6FUUI4LG2VPZV -s 10.0.2.5/32 -m comment --comment "default/kubernetes:https" -j KUBE-MARK-MASQ
-A KUBE-SEP-QVN6FUUI4LG2VPZV -p tcp -m comment --comment "default/kubernetes:https" -m recent --set --name KUBE-SEP-QVN6FUUI4LG2VPZV --mask 255.255.255.255 --rsource -m tcp -j DNAT --to-destination 10.0.2.5:6443
-A KUBE-SEP-RXSC7ZQ2D5HSX4JT -s 172.17.0.4/32 -m comment --comment "kube-system/webapp:" -j KUBE-MARK-MASQ
-A KUBE-SEP-RXSC7ZQ2D5HSX4JT -p tcp -m comment --comment "kube-system/webapp:" -m tcp -j DNAT --to-destination 172.17.0.4:8080
-A KUBE-SEP-WJGEXG4NESKNJ3Q7 -s 172.17.0.3/32 -m comment --comment "kube-system/webapp:" -j KUBE-MARK-MASQ
-A KUBE-SEP-WJGEXG4NESKNJ3Q7 -p tcp -m comment --comment "kube-system/webapp:" -m tcp -j DNAT --to-destination 172.17.0.3:8080
-A KUBE-SERVICES -d 10.10.10.2/32 -p udp -m comment --comment "kube-system/kube-dns:dns cluster IP" -m udp --dport 53 -j KUBE-SVC-TCOU7JCQXEZGVUNU
-A KUBE-SERVICES -d 10.10.10.2/32 -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp cluster IP" -m tcp --dport 53 -j KUBE-SVC-ERIFXISQEP7F7OF4
-A KUBE-SERVICES -d 10.10.10.206/32 -p tcp -m comment --comment "kube-system/webapp: cluster IP" -m tcp --dport 8080 -j KUBE-SVC-R36H7ZIERA5ZZDML
-A KUBE-SERVICES -d 10.10.10.1/32 -p tcp -m comment --comment "default/kubernetes:https cluster IP" -m tcp --dport 443 -j KUBE-SVC-NPX46M4PTMTKRN6Y
-A KUBE-SERVICES -d 10.10.10.22/32 -p tcp -m comment --comment "kube-system/default-http-backend: cluster IP" -m tcp --dport 80 -j KUBE-SVC-2QFLXPI3464HMUTA
-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-2QFLXPI3464HMUTA -m comment --comment "kube-system/default-http-backend:" -j KUBE-SEP-BHZYIGDTOBFGE25J
-A KUBE-SVC-ERIFXISQEP7F7OF4 -m comment --comment "kube-system/kube-dns:dns-tcp" -j KUBE-SEP-OGWO7LMK6DKAZC2Q
-A KUBE-SVC-NPX46M4PTMTKRN6Y -m comment --comment "default/kubernetes:https" -m recent --rcheck --seconds 10800 --reap --name KUBE-SEP-QVN6FUUI4LG2VPZV --mask 255.255.255.255 --rsource -j KUBE-SEP-QVN6FUUI4LG2VPZV
-A KUBE-SVC-NPX46M4PTMTKRN6Y -m comment --comment "default/kubernetes:https" -j KUBE-SEP-QVN6FUUI4LG2VPZV
-A KUBE-SVC-R36H7ZIERA5ZZDML -m comment --comment "kube-system/webapp:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-WJGEXG4NESKNJ3Q7
-A KUBE-SVC-R36H7ZIERA5ZZDML -m comment --comment "kube-system/webapp:" -j KUBE-SEP-RXSC7ZQ2D5HSX4JT
-A KUBE-SVC-TCOU7JCQXEZGVUNU -m comment --comment "kube-system/kube-dns:dns" -j KUBE-SEP-4RATAVQC2G7Y6Z6O
COMMIT
# Completed on Fri Apr  6 19:38:41 2018
 
 
參考資料:
《Kubernetes權威指南——從Docker到Kubernetes實踐全接觸》第3章

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM