設計理念
- Pod 是一個邏輯單位
- Kubernetes 真正處理的其實是一組共享了某些資源的的容器
- 共享network,volume, IPC, UTS等namespace
- 如果說容器是進程,那麼pod就是進程組
- 在一個真正的操作系統裏,進程並不是獨自運行的,而是以進程組的方式組織在一起
- 例如負責日誌處理的rsyslogd程序,它的主程序和它要用到的內核日誌模塊 imklog 等,同屬於一個進程組
- 這些進程相互協作,共同完成 rsyslogd 程序的職責
- 進程組中的進程有着密切的關係,必須部署在同一個機器上
- 例如需要基於 Socket 的通信或者涉及文件交換
- 在一個真正的操作系統裏,進程並不是獨自運行的,而是以進程組的方式組織在一起
- 爲了解決 進程組的調度, Mesos 中使用了資源囤積的機制
- 在所有資源都囤積完畢後都滿足時,纔開始進行調度
- 缺點在於調度效率損失和死鎖以及飢餓的可能性
- 由於資源搶佔還不支持
- k8s則是把一個pod當做一個整體來調度
- 因此如果兩個容器不必要在同一個機器上,則應該分爲兩個pod
- 例如一個後端應用和mysql數據庫,最好分成兩個pod,還方面水平擴容
pause容器
- 通過業務無關的
pause
容器來判斷pod的生存狀態- 最先啓動,除了等待終止信號退出外什麼都不幹
k8s.gcr.io/pause
鏡像是一個用匯編語言編寫,大小隻有 100~200 KB 左右
- 管理所有namespace並允許業務容器共享它們
- 凡是調度、網絡、存儲,以及安全相關的屬性,基本上是 Pod 級別的
CNI
接口給pause
容器配置相關的網絡,然後 Pod 中其他的容器都使用 pause 容器的網絡- 所有的網絡流量都經過
pause
容器轉發 - pod內容器共享一個IP,通過 localhost 相互通信
- 所有的網絡流量都經過
initcontainer
- 主要負責初始化工作
- 準備一些變化的配置文件
- 做一些前置條件的校驗,如網絡是否聯通
- 當所有的 initContainers運行完成後,Kubernetes纔會開始初始化應用Pod
- 多個initContainer會按順序一次運行一個,每個initContainers必須運行成功,下一個才能夠運行
- 普通業務容器時併發啓動的
- 通過啓動前的檢查可以規定普通容器的啓動順序
- InitContainer 執行成功後就退出並刪除,而普通容器可能會一直在執行
- initContainers不支持Readiness Probe(就緒探針),因爲它們必須在Pod就緒之前運行完成
pod啓動全過程
kubectl
首先會執行一些客戶端驗證操作,以確保不合法的請求將會快速失敗,不會發送給apiserver
- 例如創建不支持的資源或使用格式錯誤的鏡像名稱
- 通過減少不必要的負載來提高系統性能
kubectl
查詢用戶信息以發送給apiserver
以供身份認證- 用戶憑證保存在
kubeconfig
文件中,kubectl
通過以下順序來找到該文件的位置- 如果提供了
--kubeconfig
參數, 就使用該參數提供的 kubeconfig 文件 - 使用
$KUBECONFIG
環境變量提供的 kubeconfig 文件 - 就使用默認的 kubeconfig 文件
$HOME/.kube/config
- 如果提供了
kubectl
會把這些信息附加到請求頭中
- 用戶憑證保存在
apiserver
不僅會驗證用戶身份,還會進行鑑權,即用戶是否有該操作的權限kubectl
向apiserver
發送HTTP請求- Kubernetes 支持多個 API 版本,每個版本都在不同的 API 路徑下
- 例如
/api/v1
或者/apis/extensions/v1beta1
- 不同的 API 版本表明不同的穩定性和支持級別
- 例如
- 一旦請求發送之後獲得成功的響應,
kubectl
將會根據所需的輸出格式打印 success message
- Kubernetes 支持多個 API 版本,每個版本都在不同的 API 路徑下
apiserver
把請求根據YAML文件裏的apiVersion
,kind
等字段通過routes匹配指定Handler- 然後Handler構造對應的API對象,並儲存到
etcd
中
- 然後Handler構造對應的API對象,並儲存到
controller manager
會一直watchapiserver
,如果有更新就會自動執行相應控制器scheduler
監聽pod和node信息,選擇適合的node,並將該信息寫入etcd
- 過濾+打分的策略
kubelet
每隔一定時間向apiserver
通過NodeName
獲取自身 Node 上所要運行的 Pod 清單- 會通過與自己的內部緩存進行比較來檢測新增加的 Pod,如果有差異,就開始同步 Pod 列表
CRI
調用dockershim
,dockershim
把K8S指令轉換爲具體容器API發送給Docker Daemon- Docker Daemon首先創建
pause
容器, 然後啓動initcontainer,最後再啓動業務容器- 首先拉取容器的鏡像
- 再把容器信息發送給 Dockerd 守護進程啓動容器實例
- 如果 Pod 中配置了鉤子(Hook),容器啓動之後就會運行這些 Hook
pod設計模式:sidecar
-
在 Pod 裏面,可以定義一些專門的容器,來執行主業務容器所需要的一些輔助工作
- 例如日誌收集,debug,監控等
-
這種做法一個明顯的優勢就是在於其實將輔助功能從我的業務容器解耦了,能夠獨立發佈 Sidecar 容器
- 很多與 Pod 網絡相關的配置和管理,也都可以交給 sidecar 完成,而完全無須干涉用戶容器
- 熔斷、路由、服務發現、計量、流控、監視、重試、冪等、鑑權等控制面上的功能,以及其相關的配置更新,本質來上來說,和服務的關係並不大
- 但是傳統的工程做法是在開發層面完成這些功能,這就會導致各種維護上的問題,而且還會受到特定語言和編程框架的約束和限制
-
更重要的是這個能力是可以重用的,即同樣的一個監控 Sidecar 或者日誌 Sidecar,可以被全公司的人共用的
- 最典型的例子莫過於 Istio 這個微服務治理項目了
-
進程間通訊機制是這個設計模式的重點,千萬不要使用任何對應用服務有侵入的方式
- 可以通過信號的方式,或是通過共享內存的方式實現
- 最好的方式就是網絡遠程調用的方式,因爲都在127.0.0.1上通訊,所以開銷並不明顯
-
服務協議
- 這裏有兩層協議,一個是 Sidecar 到 service 的內部協議,另一個是 Sidecar 到遠端 Sidecar 或 service 的外部協議
- 對於內部協議,需要儘量靠近和兼容本地 service 的協議;對於外部協議,需要儘量使用更爲開放更爲標準的協議。但無論是哪種,都不應該使用與語言相關的協議
-
sidecar本質是把控制代碼與邏輯(業務)代碼分離
- Sidecar 中所實現的功能應該是控制面上的東西,而不是業務邏輯上的東西,所以不要把業務邏輯設計到 Sidecar 中
-
三種典型的sidecar用法:日誌收集、代理容器、適配器
service mesh
-
微服務框架的問題
- 對於傳統的微服務框架,如果框架本身出現了致命的bug或者框架新功能的實現需要整個依賴該框架調用鏈的服務全部升級,由於業務方衆多,這種升級的推動是十分困難的
- 各個微服務使用的語言衆多(go,java,c++,python),需要針對每個語言實現對應的微服務框架
- RPC通信協議多樣化(thrift, grpc, http),微服務框架需要針對每種協議實現流量治理等功能
- 框架服務發現和治理等內容都位於業務進程內,與業務耦合嚴重,難以進行性能調優和問題排查
-
service mesh的優勢:
- 容易兼容語言差異
- 基礎設施可以獨立於業務進行升級
- 無感知老服務改造
- 統一控制和治理,業務程序可以專注業務
-
Service Mesh 就像是網絡七層模型中的第四層 TCP 協議。其把底層的那些非常難控制的網絡通訊方面的控制面的東西都管了(比如:丟包重傳、擁塞控制、流量控制),而更爲上面的應用層的協議,只需要關心自己業務應用層上的事了。如 HTTP 的 HTML 協議
pod狀態
- 對於包含多個容器的 Pod,只有它裏面所有的容器都進入異常狀態後,Pod 纔會進入
Failed
狀態。在此之前,Pod 都是Running
狀態- Pod 的
READY
字段會顯示正常容器的個數
- Pod 的
- pod狀態(
pod.status.phase
)pending
: API對象已經被創建並保存在ETCD中,但容器還沒全部被創建成功- condition字段表述具體狀態,比如
unschedulabed
- condition字段表述具體狀態,比如
running
: pod已經和節點綁定,容器都創建成功且至少有一個在運行- 未必能提供服務,可能在不斷重啓
- condition處於ready狀態才能提供服務
succeeded
: pod內所有容器東運行完畢- 多見於job中
failed
: 至少有一個容器以非0返回碼退出unknown
: pod狀態未被kubelet彙報給kube-apiserver,可能是主從節點間的通信問題
健康檢查
-
Readiness Probe
- 用來判斷一個 pod 是否處在就緒狀態(啓動完成),當這個 pod 不處在就緒狀態的時候,接入層會把相應的流量從這個 pod 上面移除
- Readiness 主要應對的是啓動之後無法立即對外提供服務的這些應用
- 比如加載緩存數據,連接數據庫等
- 多用於擴容和升級時
-
Liveness Probe
-
用來判斷一個pod是否存活,如果不存活則直接殺掉pod,再根據重啓策略判斷是否重啓
- 如果關心這個容器退出後的上下文環境,比如容器退出後的日誌、文件和目錄,請將 restartPolicy 設置爲
Never
- 如果關心這個容器退出後的上下文環境,比如容器退出後的日誌、文件和目錄,請將 restartPolicy 設置爲
-
因爲一旦容器被自動重新創建,這些內容就有可能丟失掉了(被垃圾回收了)
-
Liveness 適用場景是支持那些可以重新拉起的應用(重啓以實現自愈)
-
- 如果使用 tcpSocket 方式進行判斷的時候,如果遇到了 TLS 的服務,那可能會造成後邊 TLS 裏面有很多這種未健全的 tcp connection,那這個時候需要自己對業務場景上來判斷,這種的鏈接是否會對業務造成影響
資源保障和限制
-
可以設定的資源有:CPU,Mem, 儲存, GPU等
-
通過
spec.container.reousrces
中的requests
和limits
字段設定- 最小單位爲整數
- 在調度的時候,kube-scheduler 只會按照 requests 的值進行計算
- 設置 Cgroups 限制的時候,kubelet 則會按照 limits 的值來進行設置
-
QoS
- 最高優先級:
guaranteed
, 中等:burstable
, 最低:besteffort
- 用戶本身無法指定優先級,只能通過request和limit的組合確定
- 當 Pod 裏的每一個 Container 都同時設置了 requests 和 limits,並且 requests 和 limits 值相等的時候,這個 Pod 就屬於
Guaranteed
類別- 當 Pod 僅設置了 limits 沒有設置 requests 的時候,Kubernetes 會自動爲它設置與 limits 相同的 requests 值
- 而當 Pod 不滿足 Guaranteed 的條件,但至少有一個 Container 設置了 requests,那麼這個 Pod 就會被劃分到
Burstable
類別 - 如果一個 Pod 既沒有設置 requests,也沒有設置 limits,那麼它的 QoS 類別就是
BestEffort
- 當 Pod 裏的每一個 Container 都同時設置了 requests 和 limits,並且 requests 和 limits 值相等的時候,這個 Pod 就屬於
- 最高優先級:
-
獨佔CPU的核
- 類似於在使用容器的時候通過設置
cpuset
把容器綁定到某個 CPU 的核上,而不是像 cpushare 那樣共享 CPU 的計算能力 - 此時由於操作系統在 CPU 之間進行上下文切換的次數大大減少,容器裏應用的性能會得到大幅提升
- 設置方式
- Pod 必須是
Guaranteed
的 QoS 類型 - 將 Pod 的 CPU 資源的 requests 和 limits 設置爲同一個相等的整數值即可
- Pod 必須是
- 類似於在使用容器的時候通過設置