1. 程式人生 > 實用技巧 >Kubernetes 運維學習筆記

Kubernetes 運維學習筆記

Kubernetes 運維學習筆記

一、Kubernetes 介紹

Kubernetes是一個全新的基於容器技術的分散式架構領先方案, 它是Google在2014年6月開源的一個容器叢集管理系統,使用Go語言開發,Kubernetes也叫K8S。K8S是Google內部一個叫Borg的容器叢集管理系統衍生出來的,Borg已經在Google大規模生產執行十年之久。K8S主要用於自動化部署、擴充套件和管理容器應用,提供了資源排程、部署管理、服務發現、擴容縮容、監控等一整套功能。2015年7月,Kubernetes v1.0正式釋出,截止到2017年9月29日最新穩定版本是v1.8。Kubernetes目標是讓部署容器化應用簡單高效。

Kubernetes最初源於谷歌內部的Borg,提供了面向應用的容器叢集部署和管理系統。Kubernetes 的目標旨在消除編排物理/虛擬計算,網路和儲存基礎設施的負擔,並使應用程式運營商和開發人員完全將重點放在以容器為中心的原語上進行自助運營。Kubernetes 也提供穩定、相容的基礎(平臺),用於構建定製化的workflows 和更高階的自動化任務。

Kubernetes 具備完善的叢集管理能力,包括多層次的安全防護和准入機制、多租戶應用支撐能力、透明的服務註冊和服務發現機制、內建負載均衡器、故障發現和自我修復能力、服務滾動升級和線上擴容、可擴充套件的資源自動排程機制、多粒度的資源配額管理能力。Kubernetes 還提供完善的管理工具,涵蓋開發、部署測試、運維監控等各個環節。

二、Kubernetes主要功能

Kubernetes是docker容器用來編排和管理的工具,它是基於Docker構建一個容器的排程服務,提供資源排程、均衡容災、服務註冊、動態擴縮容等功能套件。Kubernetes提供應用部署、維護、 擴充套件機制等功能,利用Kubernetes能方便地管理跨機器執行容器化的應用,其主要功能如下:

資料卷: Pod中容器之間共享資料,可以使用資料卷。

應用程式健康檢查: 容器內服務可能程序堵塞無法處理請求,可以設定監控檢查策略保證應用健壯性。

複製應用程式例項: 控制器維護著Pod副本數量,保證一個Pod或一組同類的Pod數量始終可用。

彈性伸縮: 根據設定的指標(CPU利用率)自動縮放Pod副本數。

服務發現: 使用環境變數或DNS服務外掛保證容器中程式發現Pod入口訪問地址。

負載均衡: 一組Pod副本分配一個私有的叢集IP地址,負載均衡轉發請求到後端容器。在叢集內部其他Pod可通過這個ClusterIP訪問應用。

滾動更新: 更新服務不中斷,一次更新一個Pod,而不是同時刪除整個服務。

服務編排: 通過檔案描述部署服務,使得應用程式部署變得更高效。

資源監控: Node節點元件整合cAdvisor資源收集工具,可通過Heapster彙總整個叢集節點資源資料,然後儲存到InfluxDB時序資料庫,再由Grafana展示。

提供認證和授權: 支援屬性訪問控制(ABAC)、角色訪問控制(RBAC)認證授權策略。

除此之外, Kubernetes主要功能還體現在:
-使用Docker對應用程式包裝(package)、例項化(instantiate)、執行(run)。
- 將多臺Docker主機抽象為一個資源,以叢集的方式執行、管理跨機器的容器,包括任務排程、資源管理、彈性伸縮、滾動升級等功能。
-使用編排系統(YAML File)快速構建容器叢集,提供負載均衡,解決容器直接關聯及通訊問題
-解決Docker跨機器容器之間的通訊問題。
- 自動管理和修復容器,簡單說,比如建立一個叢集,裡面有十個容器,如果某個容器異常關閉,那麼,會嘗試重啟或重新分配容器,始終保證會有十個容器在執行,反而殺死多餘的。Kubernetes的自我修復機制使得容器叢集總是執行在使用者期望的狀態.當前Kubernetes支援GCE、vShpere、CoreOS、OpenShift。

kubernetes的叢集至少有兩個主機組成:master + node ,即為master/node架構。master為叢集的控制面板,master主機需要做冗餘,一般建議為3臺;而node主機不需要做冗餘,因為node的主要作用是執行pod,貢獻計算能力和儲存能力,而pod控制器會自動管控pod資源,如果資源少,pod控制器會自動建立pod,即pod控制器會嚴格按照使用者指定的副本來管理pod的數量。客戶端的請求下發給master,即把建立和啟動容器的請求發給master,master中的排程器分析各node現有的資源狀態,把請求呼叫到對應的node啟動容器。

可以理解為kubernetes把容器抽象為pod來管理1到多個彼此間有非常緊密聯絡的容器,但是LAMP的容器主機A,M,P只是有關聯,不能說是非常緊密聯絡,因此A,M,P都要執行在三個不同的pod上。在kubernetes中,要執行幾個pod,是需要定義一個配置檔案,在這個配置檔案裡定義用哪個控制器啟動和控制幾個pod,在每個pod裡要定義那幾臺容器,kubernetes通過這個配置檔案,去建立一個控制器,由此控制器來管控這些pod,如果這些pod的某幾個down掉後,控制器會通過健康監控功能,隨時監控pod,發現pod異常後,根據定義的策略進行操作,即可以進行自愈。

kubernetes內部需要5套證書,手動建立或者自動生成,分別為:
1.etcd內部通訊需要一套ca和對應證書。
2.etcd與外部通訊也要有一套ca和對應證書。
3.APIserver間通訊需要一套證書。
4.apiserver與node間通訊需要一套證書。
5.node和pod間通訊需要一套ca證書。

目前來說還不能實現把所有的業務都遷到kubernetes上,如儲存,因為這個是有狀態應用,出現錯誤排查很麻煩,所以目前kubernetes主要是執行無狀態應用。

所以一般而言,負載均衡器執行在kubernetes之外,nginx或者tomcat這種無狀態的應用運行於kubernetes叢集內部,而資料庫如mysql,zabbix,zoopkeeper等有狀態的,一般運行於kubernetes外部,通過網路連線,實現kubernetes叢集的pod呼叫這些外部的有狀態應用。

三、Kubernetes架構和元件

kubernetes主要由以下幾個核心元件組成:
etcd: 叢集的主資料庫,儲存了整個叢集的狀態; etcd負責節點間的服務發現和配置共享。etcd分散式鍵值儲存系統, 用於保持叢集狀態,比如Pod、Service等物件資訊。
kube-apiserver: 提供了資源操作的唯一入口,並提供認證、授權、訪問控制、API註冊和發現等機制;這是kubernetes API,作為叢集的統一入口,各元件協調者,以HTTPAPI提供介面服務,所有物件資源的增刪改查和監聽操作都交給APIServer處理後再提交給Etcd儲存。
kube-controller-manager: 負責維護叢集的狀態,比如故障檢測、自動擴充套件、滾動更新等;它用來執行整個系統中的後臺任務,包括節點狀態狀況、Pod個數、Pods和Service的關聯等, 一個資源對應一個控制器,而ControllerManager就是負責管理這些控制器的。
kube-scheduler: 資源排程,按照預定的排程策略將Pod排程到相應的機器上;它負責節點資源管理,接受來自kube-apiserver建立Pods任務,並分配到某個節點。它會根據排程演算法為新建立的Pod選擇一個Node節點。
kubectl: 客戶端命令列工具,將接受的命令格式化後傳送給kube-apiserver,作為整個系統的操作入口。
kubelet: 負責維護容器的生命週期,負責管理pods和它們上面的容器,images映象、volumes、etc。同時也負責Volume(CVI)和網路(CNI)的管理;kubelet執行在每個計算節點上,作為agent,接受分配該節點的Pods任務及管理容器,週期性獲取容器狀態,反饋給kube-apiserver; kubelet是Master在Node節點上的Agent,管理本機執行容器的生命週期,比如建立容器、Pod掛載資料卷、下載secret、獲取容器和節點狀態等工作。kubelet將每個Pod轉換成一組容器。
container runtime: 負責映象管理以及Pod和容器的真正執行(CRI);
kube-proxy: 負責為Service提供cluster內部的服務發現和負載均衡;它執行在每個計算節點上,負責Pod網路代理。定時從etcd獲取到service資訊來做相應的策略。它在Node節點上實現Pod網路代理,維護網路規則和四層負載均衡工作。
docker或rocket(rkt): 執行容器。

除了上面的幾個核心組建, 還有一些常用外掛(Add-ons):
kube-dns: 負責為整個叢集提供DNS服務;
Ingress Controller: 為服務提供外網入口;
Heapster: 提供資源監控;
Dashboard: 提供GUI;
Federation: 提供跨可用區的叢集;
Fluentd-elasticsearch: 提供叢集日誌採集、儲存與查詢;

其中:
master元件包括: kube-apiserver, kube-controller-manager, kube-scheduler;
Node元件包括: kubelet, kube-proxy, docker或rocket(rkt);
第三方服務:etcd

Kubernetes Master控制組件,排程管理整個系統(叢集),包含如下元件:
Kubernetes API Server: 作為Kubernetes系統入口,其封裝了核心物件的增刪改查操作,以RESTful API介面方式提供給外部客戶和內部元件呼叫,維護的REST物件持久化到Etcd中儲存。
Kubernetes Scheduler: 為新建立的Pod進行節點(node)選擇(即分配機器),負責叢集的資源排程。元件抽離,可以方便替換成其他排程器。
Kubernetes Controller: 負責執行各種控制器,目前已經提供了很多控制器來保證Kubernetes的正常執行。
Replication Controller: 管理維護Replication Controller,關聯Replication Controller和Pod,保證Replication Controller定義的副本數量與實際執行Pod數量一致。
Node Controller: 管理維護Node,定期檢查Node的健康狀態,標識出(失效|未失效)的Node節點。
Namespace Controller: 管理維護Namespace,定期清理無效的Namespace,包括Namesapce下的API物件,比如Pod、Service等。
Service Controller: 管理維護Service,提供負載以及服務代理。
EndPoints Controller: 管理維護Endpoints,關聯Service和Pod,建立Endpoints為Service的後端,當Pod發生變化時,實時更新Endpoints (即Pod Ip + Container Port)。
Service Account Controller: 管理維護Service Account,為每個Namespace建立預設的Service Account,同時為Service Account建立Service Account Secret。
Persistent Volume Controller: 管理維護Persistent Volume和Persistent Volume Claim,為新的Persistent Volume Claim分配Persistent Volume進行繫結,為釋放的Persistent Volume執行清理回收。
Daemon Set Controller: 管理維護Daemon Set,負責建立Daemon Pod,保證指定的Node上正常的執行Daemon Pod。
Deployment Controller: 管理維護Deployment,關聯Deployment和Replication Controller,保證執行指定數量的Pod。當Deployment更新時,控制實現Replication Controller和 Pod的更新。
Job Controller: 管理維護Job,為Jod建立一次性任務Pod,保證完成Job指定完成的任務數目
Pod Autoscaler Controller: 實現Pod的自動伸縮,定時獲取監控資料,進行策略匹配,當滿足條件時執行Pod的伸縮動作。

Kubernetes Node執行節點,執行管理業務容器,包含如下元件:
Kubelet: 負責管控容器,Kubelet會從Kubernetes API Server接收Pod的建立請求,啟動和停止容器,監控容器執行狀態並彙報給Kubernetes API Server。
Kubernetes Proxy: 負責為Pod建立代理服務,Kubernetes Proxy會從Kubernetes API Server獲取所有的Service資訊,並根據Service的資訊建立代理服務,實現Service到Pod的請求路由和轉發,從而實現Kubernetes層級的虛擬轉發網路。
Docker: Node上需要執行容器服務

Kubernetes的分層設計理念
Kubernetes設計理念和功能類似Linux的分層架構,如下圖:

核心層:Kubernetes最核心的功能,對外提供API構建高層的應用,對內提供外掛式應用執行環境;
應用層:部署(無狀態應用、有狀態應用、批處理任務、叢集應用等)和路由(服務發現、DNS解析等);
管理層:系統度量(如基礎設施、容器和網路的度量),自動化(如自動擴充套件、動態Provision等)以及策略管理(RBAC、Quota、PSP、NetworkPolicy等);
介面層:kubectl命令列工具、客戶端SDK以及叢集聯邦;
生態系統:在介面層之上的龐大容器叢集管理排程的生態系統,可以劃分為兩個範疇:
- Kubernetes外部:日誌、監控、配置管理、CI、CD、Workflow、FaaS、OTS應用、ChatOps等;
- Kubernetes內部:CRI、CNI、CVI、映象倉庫、Cloud Provider、叢集自身的配置和管理等;

四、Kubernetes基本物件概念

Kubernetes中的大部分概念Node、Pod、Replication Controller、Service等都可以看作一種“資源物件”,幾乎所有的資源物件都可以通過kubectl工具(API呼叫)執行增、刪、改、查等操作並將其儲存在etcd中持久化儲存。從這個角度來看,kubernetes其實是一個高度自動化的資源控制系統,通過跟蹤對比etcd庫裡儲存的“資源期望狀態”與當前環境中的“實際資源狀態”的差異來實現自動控制和自動糾錯的高階功能。

基本物件:
Pod: Pod是最小部署單元,一個Pod有一個或多個容器組成,Pod中容器共享儲存和網路,在同一臺Docker主機上執行; Pod 中的容器會作為一個整體被Master排程到一個Node上執行。pod 是一組container,pod裡面的container是共享網路棧和儲存卷等資源,是一個整體. pod 可以認為是容器組的概念,裡面有個infra container 負責pod內所有container 共享 namespace。docker的容器可以類比成OS中的程序,而K8S的pod則更像是OS中的“程序組”概念。
Service: Service一個應用服務抽象,定義了Pod邏輯集合和訪問這個Pod集合的策略。Service代理Pod集合對外表現是為一個訪問入口,分配一個叢集IP地址,來自這個IP的請求將負載均衡轉發後端Pod中的容器。Service通過LableSelector選擇一組Pod提供服務。
Volume: 資料卷,共享Pod中容器使用的資料。
Namespace: 名稱空間將物件邏輯上分配到不同Namespace,可以是不同的專案、使用者等區分管理,並設定控制策略,從而實現多租戶。名稱空間也稱為虛擬叢集。
Lable: 標籤用於區分物件(比如Pod、Service),鍵/值對存在;每個物件可以有多個標籤,通過標籤關聯物件。

基於基本物件更高層次抽象:
ReplicaSet:下一代ReplicationController。確保任何給定時間指定的Pod副本數量,並提供宣告式更新等功能。RC與RS唯一區別就是lableselector支援不同,RS支援新的基於集合的標籤,RC僅支援基於等式的標籤。
Deployment: Deployment是一個更高層次的API物件,它管理ReplicaSets和Pod,並提供宣告式更新等功能。官方建議使用Deployment管理ReplicaSets,而不是直接使用ReplicaSets,這就意味著可能永遠不需要直接操作ReplicaSet物件。負責無狀態應用pod控制,支援二級控制器(HPA,HorizontalPodAutoscaler水平pod自動控制器)。
StatefulSet: StatefulSet適合永續性的應用程式,有唯一的網路識別符號(IP),持久儲存,有序的部署、擴充套件、刪除和滾動更新。負責有狀態應用pod控制。
DaemonSet: DaemonSet確保所有(或一些)節點運行同一個Pod。當節點加入Kubernetes叢集中,Pod會被排程到該節點上執行,當節點從叢集中移除時,DaemonSet的Pod會被刪除。刪除DaemonSet會清理它所有建立的Pod。
Job: 一次性任務,執行完成後Pod銷燬,不再重新啟動新容器。還可以任務定時執行。Kubernetes中的Job 用於執行結束就刪除的應用。


API物件是K8s叢集中管理操作單元。K8s集群系每支援一項新功能,引入一項新技術,一定會新引入對應的API物件,支援對該功能的管理操作。例如副本集Replica Set對應的API物件是RS。Kubernetes中所有的配置都是通過API物件的spec去設定的,也就是使用者通過配置系統的理想狀態來改變系統,這是k8s重要設計理念之一,即所有的操作都是宣告式 (Declarative) 的而不是命令式(Imperative)的。宣告式操作在分散式系統中好處是穩定,不怕丟操作或執行多次,例如設定副本數為3的操作執行多次也還是一個結果, 而給副本數加1的操作就不是宣告式的, 執行多次結果就錯了。

Cluster
Cluster 是計算、儲存和網路資源的集合,Kubernetes 利用這些資源執行各種基於容器的應用

Master
kubernetes叢集的管理節點,負責管理叢集,提供叢集的資源資料訪問入口。擁有Etcd儲存服務(可選),執行Api Server程序,Controller Manager服務程序及Scheduler服務程序,關聯工作節點Node。Kubernetes API server提供HTTP Rest介面的關鍵服務程序,是Kubernetes裡所有資源的增、刪、改、查等操作的唯一入口。也是叢集控制的入口程序;Kubernetes Controller Manager是Kubernetes所有資源物件的自動化控制中心;Kubernetes Schedule是負責資源排程(Pod排程)的程序.

Node
Node是Kubernetes叢集架構中執行Pod的服務節點(亦叫agent或minion)。Node是Kubernetes叢集操作的單元,用來承載被分配Pod的執行,是Pod執行的宿主機。關聯Master管理節點,擁有名稱和IP、系統資源資訊。執行docker eninge服務,守護程序kunelet及負載均衡器kube-proxy. 每個Node節點都執行著以下一組關鍵程序:
-kubelet:負責對Pod對於的容器的建立、啟停等任務
-kube-proxy:實現Kubernetes Service的通訊與負載均衡機制的重要元件
-Docker Engine(Docker):Docker引擎,負責本機容器的建立和管理工作

Node節點可以在執行期間動態增加到Kubernetes叢集中,預設情況下,kubelet會想master註冊自己,這也是Kubernetes推薦的Node管理方式,kubelet程序會定時向Master彙報自身情報,如作業系統、Docker版本、CPU和記憶體,以及有哪些Pod在執行等等,這樣Master可以獲知每個Node節點的資源使用情況,冰實現高效均衡的資源排程策略。、

Pod
運行於Node節點上,若干相關容器的組合。Pod內包含的容器執行在同一宿主機上,使用相同的網路名稱空間、IP地址和埠,能夠通過localhost進行通。Pod是Kurbernetes進行建立、排程和管理的最小單位,它提供了比容器更高層次的抽象,使得部署和管理更加靈活。一個Pod可以包含一個容器或者多個相關容器。

Pod其實有兩種型別:普通Pod和靜態Pod,後者比較特殊,它並不存在Kubernetes的etcd儲存中,而是存放在某個具體的Node上的一個具體檔案中,並且只在此Node上啟動。普通Pod一旦被建立,就會被放入etcd儲存中,隨後會被Kubernetes Master排程到摸個具體的Node上進行繫結,隨後該Pod被對應的Node上的kubelet程序例項化成一組相關的Docker容器並啟動起來。在預設情況下,當Pod裡的某個容器停止時,Kubernetes會自動檢測到這個問起並且重啟這個Pod(重啟Pod裡的所有容器),如果Pod所在的Node宕機,則會將這個Node上的所有Pod重新排程到其他節點上。

Pod是在K8s叢集中執行部署應用或服務的最小單元,它是可以支援多容器的。Pod的設計理念是支援多個容器在一個Pod中共享網路地址和檔案系統,可以通過程序間通訊和檔案共享這種簡單高效的方式組合完成服務.比如你執行一個作業系統發行版的軟體倉庫,一個Nginx容器用來發布軟體,另一個容器專門用來從源倉庫做同步,這兩個容器的映象不太可能是一個團隊開發的,但是他們一塊兒工作才能提供一個微服務;這種情況下,不同的團隊各自開發構建自己的容器映象,在部署的時候組合成一個微服務對外提供服務。

kubernetes的最核心功能就是為了執行pod,其他元件是為了pod能夠正常執行而執行的。pod可以分為兩類:
1.自主式pod
2.控制器管理的pod

一個pod上有兩類元資料,label 和 annotation
label:標籤,對資料型別和程度要求嚴格,
annotation:註解,用於儲存自己定義的複雜元資料,用來描述pod的屬性

外部請求訪問內部的pod經過了三級轉發,第一級先到nodeip(宿主機ip)對應的埠,然後被轉為cluster ip的service 埠,然後轉換為PodIP的containerPort。

Kubernetes 引入 Pod 主要基於下面兩個目的:
-可管理性
有些容器天生就是需要緊密聯絡, 一起工作。Pod 提供了比容器更高層次的抽象,將它們封裝到一個部署單元中。Kubernetes 以 Pod 為最小單位進行排程、擴充套件、共享資源、管理生命週期。

-通訊和資源共享
Pod 中的所有容器使用同一個網路 namespace,即相同的 IP 地址和 Port 空間。它們可以直接用 localhost 通訊。同樣的,這些容器可以共享儲存,當 Kubernetes 掛載 volume 到 Pod,本質上是將 volume 掛載到 Pod 中的每一個容器。

File Puller 會定期從外部的 Content Manager 中拉取最新的檔案,將其存放在共享的 volume 中。Web Server 從 volume 讀取檔案,響應 Consumer 的請求。這兩個容器是緊密協作的,它們一起為 Consumer 提供最新的資料;同時它們也通過 volume 共享資料。所以放到一個 Pod 是合適的。

Controller
Kubernetes 通常不會直接建立 Pod,而是通過 Controller 來管理 Pod 的。Controller 中定義了 Pod 的部署特性,比如有幾個副本,在什麼樣的 Node 上執行等。為了滿足不同的業務場景, Kubernetes 提供了多種 Controller,包括 Deployment、ReplicaSet、DaemonSet、StatefuleSet、Job 等.

Replication Controller (副本集RC)
Replication Controller用來管理Pod的副本,保證叢集中存在指定數量的Pod副本。叢集中副本的數量大於指定數量,則會停止指定數量之外的多餘容器數量,反之,則會啟動少於指定數量個數的容器,保證數量不變。Replication Controller是實現彈性伸縮、動態擴容和滾動升級的核心。

通過監控執行中的Pod來保證叢集中執行指定數目的Pod副本。少於指定數目,RC就會啟動執行新的Pod副本;多於指定數目,RC就會殺死多餘的Pod副本(這是k8s早期技術概念)

Replica Set (副本集RS)
RS是新一代RC,提供同樣的高可用能力,區別主要在於RS後來居上,能支援更多種類的匹配模式。副本集物件一般不單獨使用,而是作為Deployment的理想狀態引數使用.Replica Set 實現了 Pod 的多副本管理。使用 Deployment 時會自動建立 ReplicaSet,也就是說 Deployment 是通過 ReplicaSet 來管理 Pod 的多個副本,我們通常不需要直接使用 ReplicaSet。

Deployment (部署)
Deployment 是最常用的 Controller,Deployment 可以管理 Pod 的多個副本,並確保 Pod 按照期望的狀態執行。Deployment是一個比RS應用模式更廣的API物件,支援動態擴充套件。可以建立一個新的服務,更新一個新的服務,也可以是滾動升級一個服務。滾動升級一個服務,實際是建立一個新的RS,然後逐漸將新RS中副本數增加到理想狀態,將舊RS中的副本數減小到0的複合操作 (逐步升級新得副本,剔除舊的副本).
總結:RC、RS和Deployment只是保證了支撐服務的微服務Pod的數量.

DaemonSet
DaemonSet 用於每個 Node 最多隻執行一個 Pod 副本的場景。正如其名稱所揭示的,DaemonSet 通常用於執行 daemon。

StatefuleSet
StatefuleSet 能夠保證 Pod 的每個副本在整個生命週期中名稱是不變的。而其他 Controller 不提供這個功能,當某個 Pod 發生故障需要刪除並重新啟動時,Pod 的名稱會發生變化。同時 StatefuleSet 會保證副本按照固定的順序啟動、更新或者刪除。

Service

Service定義了Pod邏輯集合和訪問該集合的策略,是真實服務的抽象。Service提供了統一的服務訪問入口以及服務代理和發現機制,關聯多個相同Label的Pod,使用者不需要了解後臺Pod是如何執行。
外部系統訪問Service的問題:
-> 首先需要弄明白Kubernetes的三種IP這個問題
-Node IP:Node節點的IP地址
  - Pod IP: Pod的IP地址
  -Cluster IP:Service的IP地址
-> 首先,Node IP是Kubernetes叢集中節點的物理網絡卡IP地址,所有屬於這個網路的伺服器之間都能通過這個網路直接通訊。這也表明Kubernetes叢集之外的節點訪問Kubernetes叢集之內的某個節點或者TCP/IP服務的時候,必須通過Node IP進行通訊
->其次,Pod IP是每個Pod的IP地址,他是Docker Engine根據docker0網橋的IP地址段進行分配的,通常是一個虛擬的二層網路。

最後Cluster IP是一個虛擬的IP,但更像是一個偽造的IP網路,原因有以下幾點:
-> Cluster IP僅僅作用於Kubernetes Service這個物件,並由Kubernetes管理和分配P地址
-> Cluster IP無法被ping,他沒有一個“實體網路物件”來響應
-> Cluster IP只能結合Service Port組成一個具體的通訊埠,單獨的Cluster IP不具備通訊的基礎,並且他們屬於Kubernetes叢集這樣一個封閉的空間。
->Kubernetes叢集之內,Node IP網、Pod IP網於Cluster IP網之間的通訊,採用的是Kubernetes自己設計的一種程式設計方式的特殊路由規則。

RC、RS和Deployment只是保證了支撐服務的微服務Pod的數量,但是沒有解決如何訪問這些服務的問題。一個Pod只是一個執行服務的例項,隨時可能在一個節點上停止,在另一個節點以一個新的IP啟動一個新的Pod,因此不能以確定的IP和埠號提供服務。要穩定地提供服務需要服務發現和負載均衡能力。服務發現完成的工作,是針對客戶端訪問的服務,找到對應的的後端服務例項。在K8s叢集中,客戶端需要訪問的服務就是Service物件。每個Service會對應一個叢集內部有效的虛擬IP,叢集內部通過虛擬IP訪問一個服務。在K8s叢集中微服務的負載均衡是由Kube-proxy實現的。Kube-proxy是K8s叢集內部的負載均衡器。它是一個分散式代理伺服器,在K8s的每個節點上都有一個;這一設計體現了它的伸縮性優勢,需要訪問服務的節點越多,提供負載均衡能力的Kube-proxy就越多,高可用節點也隨之增多。與之相比,我們平時在伺服器端做個反向代理做負載均衡,還要進一步解決反向代理的負載均衡和高可用問題。

Kubernetes 執行容器(Pod)與訪問容器(Pod)這兩項任務分別由 Controller 和 Service 執行。

Namespace
名字空間為K8s叢集提供虛擬的隔離作用,K8s叢集初始有兩個名字空間,分別是預設名字空間default和系統名字空間kube-system,除此以外,管理員可以可以建立新的名字空間滿足需要。

Label
Kubernetes中任意API物件都是通過Label進行標識,Label的實質是一系列的Key/Value鍵值對,其中key於value由使用者自己指定。Label可以附加在各種資源物件上,如Node、Pod、Service、RC等,一個資源物件可以定義任意數量的Label,同一個Label也可以被新增到任意數量的資源物件上去。Label是Replication Controller和Service執行的基礎,二者通過Label來進行關聯Node上執行的Pod。

我們可以通過給指定的資源物件捆綁一個或者多個不同的Label來實現多維度的資源分組管理功能,以便於靈活、方便的進行資源分配、排程、配置等管理工作。
一些常用的Label如下:
版本標籤:"release":"stable","release":"canary"......
環境標籤:"environment":"dev","environment":"qa","environment":"production"
架構標籤:"tier":"frontend","tier":"backend","tier":"middleware"
分割槽標籤:"partition":"customerA","partition":"customerB"
質量管控標籤:"track":"daily","track":"weekly"

Label相當於我們熟悉的標籤,給某個資源物件定義一個Label就相當於給它大了一個標籤,隨後可以通過Label Selector(標籤選擇器)查詢和篩選擁有某些Label的資源物件,Kubernetes通過這種方式實現了類似SQL的簡單又通用的物件查詢機制。

Label Selector在Kubernetes中重要使用場景如下:
->kube-Controller程序通過資源物件RC上定義Label Selector來篩選要監控的Pod副本的數量,從而實現副本數量始終符合預期設定的全自動控制流程;
->kube-proxy程序通過Service的Label Selector來選擇對應的Pod,自動建立起每個Service島對應Pod的請求轉發路由表,從而實現Service的智慧負載均衡;
->通過對某些Node定義特定的Label,並且在Pod定義檔案中使用Nodeselector這種標籤排程策略,kuber-scheduler程序可以實現Pod”定向排程“的特性;


Master管理節點和Node工作節點的各元件關係:

Kuberneter工作流程:
1)通過kubectl向kubernetes Master發出指令, Master節點主要提供API Server、Scheduler、Controller元件,接收kubectl命令,從Node節點獲取Node資源資訊,併發出調度任務。
2)Node節點提供kubelet、kube-proxy,每個node節點都安裝docker,是實際的執行者。kubernetes不負責網路,所以一般是用flannel或者weave。
3)etcd是一個鍵值儲存倉庫,etcd負責服務發現和node資訊儲存。不過需要注意的是:由於etcd是負責儲存,所以不建議搭建單點叢集,如zookeeper一樣,由於存在選舉策略,所以一般推薦奇數個叢集,如3,5,7。只要叢集半數以上的結點存活,那麼叢集就可以正常執行,否則叢集可能無法正常使用。

Master:叢集控制管理節點,所有的命令都經由master處理。

Node:是kubernetes叢集的工作負載節點。Master為其分配工作,當某個Node宕機時,Master會將其工作負載自動轉移到其他節點。

Node節點可動態增加到kubernetes叢集中,前提是這個節點已經正確安裝、配置和啟動了上述的關鍵程序,預設情況下,kubelet會向Master註冊自己,這也kubernetes推薦的Node管理方式。一旦Node被納入叢集管理範圍,kubelet會定時向Master彙報自身的情況,以及之前有哪些Pod在執行等,這樣Master可以獲知每個Node的資源使用情況,並實現高效均衡的資源排程策略。如果Node沒有按時上報資訊,則會被Master判斷為失聯,Node狀態會被標記為Not Ready,隨後Master會觸發工作負載轉移流程。

Pod:是kubernetes最重要也是最基本的概念。每個Pod都會包含一個 “根容器”,還會包含一個或者多個緊密相連的業務容器。

Kubernetes為每個Pod都分配了唯一IP地址, 稱之為PodIP, 一個Pod裡多個容器共享PodIP地址. 要求底層網路支援叢集內任意兩個Pod之間的直接通訊,通常採用虛擬二層網路技術來實現 (Flannel).

Label:是一個key=value的鍵值對,其中key與value由使用者指定, 可以附加到各種資源物件上, 一個資源物件可以定義任意數量的Label。可以通過LabelSelector(標籤選擇器)查詢和篩選資源物件。

RC:Replication Controller宣告某個Pod的副本數在任意時刻都符合某個預期值。定義包含如下:
- Pod期待的副本數(replicas);
- 用於篩選目標Pod的Label Selector;
- 當Pod副本數小於期望時,用於新的建立Pod的模板template;

需要注意
- 通過改變RC裡的Pod副本數量,可以實現Pod的擴容或縮容功能;
- 通過改變RC裡Pod模板中的映象版本,可以實現Pod的滾動升級功能;

Service:“微服務”,kubernetes中的核心。通過分析、識別並建模系統中的所有服務為微服務,最終系統有多個提供不同業務能力而又彼此獨立的微服務單元所組成,服務之間通過TCP/IP進行通訊。每個Pod都會被分配一個單獨的IP地址,而且每個Pod都提供了一個獨立的Endpoint以被客戶端訪問。

客戶端如何訪問?
部署負載均衡器,為Pod開啟對外服務埠,將Pod的Endpoint列表加入轉發列表中,客戶端通過負載均衡器的對外IP+Port來訪問此服務。每個Service都有一個全域性唯一的虛擬ClusterIP,這樣每個服務就變成了具備唯一IP地址的“通訊節點”,服務呼叫就變成了最基礎的TCP網路通訊問題。

Volume:是Pod中能夠被多個容器訪問的共享目錄。定義在Pod之上,被一個Pod裡的多個容器掛載到具體的檔案目錄之下;Volume與Pod生命週期相同。Volume可以讓一個Pod裡的多個容器共享檔案、讓容器的資料寫到宿主機的磁碟上或者寫檔案到 網路儲存中,具體如下圖所示:

在kubernetes1.2的時候,RC就由Replication Controller升級成Replica Set,“下一代RC”。命令相容適用,Replica Set主要被Deployment這個更高層的資源物件所使用,從而形成一套Pod建立、刪除、更新的編排機制。當我們使用Deployment時,無需關心它是如何建立和維護ReplicaSet的,這一切是自動發生的。

Docker:既然k8s是基於容器的,那麼就不得不提到docker。2013年初,docker橫空出世,孕育著新思想的“容器”,Docker選擇容器作為核心和基礎,以容器為資源分割和排程的基本單位,封裝整個軟體執行時環境,為開發者和系統管理員設計,用於構建、釋出和執行分散式應用的平臺。是一個跨平臺、可移植並且簡單易用的容器解決方案。通過作業系統核心技術(namespaces、cgroups等)為容器提供資源隔離與安全保障。

上圖是一個image的簡單使用。我們可以通過一個dockerfile來build自己的image。可以把image上傳(push)到自己的私有映象倉庫,也可以從私有倉庫pull到本地進行使用。可以單獨使用命令列,直接run container,可以對container進行stop、start、restart操作。也可以對image進行save儲存操作以及載入load操作,大傢俱體可以根據自己的使用,選擇不同的操作即可。

Docker資源隔離技術
Docker選擇容器作為核心和基礎,以容器為資源分割和排程的基本單位,封裝整個軟體執行時環境,為開發者和系統管理員設計,用於構建、釋出和執行分散式應用的平臺。Docker是一個跨平臺、可移植並且簡單易用的容器解決方案, 通過作業系統核心技術(namespaces、cgroups等)為容器提供資源隔離與安全保障。

Docker監控
cAdvisor(Container Advisor)是Google開發的用於分析執行中容器的資源佔用和效能指標的開源工具。cAdvisor是一個執行時的守護程序,負責收集、聚合、處理和輸出執行中容器的資訊。對於每個容器,cAdvisor都有資源隔離引數、資源使用歷史情況以及完整的歷史資源使用和網路統計資訊的柱狀圖。cAdvisor不但可以為使用者提供監控服務,還可以結合其他應用為使用者提供良好的服務移植和定製。包括結合InfluxDB對資料進行儲存,以及結合Grafana提供web控制檯,自定義查詢指標,並進行展示:

當下配合Kubernetes叢集比較成熟的監控方案是:Prometheus +Grafana

五、Kubernetes叢集裡容器之間的通訊方式

Kubernetes叢集裡面容器是存在於pod裡面的,所以容器之間通訊,一般分為三種類型:
->pod內部容器之間
->pod與pod容器之間
->pod訪問service服務

1)pod內部容器之間
這種情況下容器通訊比較簡單,因為k8s pod內部容器是共享網路空間的,所以容器直接可以使用localhost訪問其他容器。k8s在啟動容器的時候會先啟動一個pause容器,這個容器就是實現這個功能的。

2)pod與pod容器之間
這種型別又可以分為兩種情況:
-> 兩個pod在同一臺主機上面
-> 兩個pod分佈在不同主機之上
第一種情況,就比較簡單了,就是docker預設的docker網橋互連容器。
第二種情況需要更為複雜的網路模型了,k8s官方推薦的是使用flannel組建一個大二層扁平網路,pod的ip分配由flannel統一分配,通訊過程也是走flannel的網橋。比如:

1 2 # docker --daemon --bip=172.17.18.1/24 注意,這其中的"--bip=172.17.18.1/24"這個引數,它限制了所在節點容器獲得的IP範圍。

每個node上面都會建立一個flannel0虛擬網絡卡,用於跨node之間通訊。所以容器直接可以直接使用pod id進行通訊。跨節點通訊時,傳送端資料會從docker0路由到flannel0虛擬網絡卡,接收端資料會從flannel0路由到docker0,這是因為flannel會新增一個路由。

1 2 3 4 5 6 7 8 傳送端: # route -n 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 flannel0 172.17.13.0 0.0.0.0 255.255.255.0 U 0 0 0 docker0 接收端: 172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 flannel0 172.17.12.0 0.0.0.0 255.255.255.0 U 0 0 0 docker0

例如現在有一個數據包要從IP為172.17.13.2的容器發到IP為172.17.12.2的容器。根據資料傳送節點的路由表,它只與172.17.0.0/16匹配這條記錄匹配,因此資料從docker0出來以後就被投遞到了flannel0。同理在目標節點,由於投遞的地址是一個容器,因此目的地址一定會落在docker0對於的172.17.12.0/24這個記錄上,自然的被投遞到了docker0網絡卡。

flannel的原理:是將網路包封裝在udp裡面,所以傳送端和接收端需要裝包和解包,對效能有一定的影響。除了flannel,k8s也支援其他的網路模型,比較有名的還有calico。

3)pod 訪問service服務
這裡涉及到k8s裡面一個重要的概念service。它是一個服務的抽象,通過label(k8s會根據service和pod直接的關係建立endpoint,可以通過“kubectl get ep”檢視)關聯到後端的pod容器。Service分配的ip叫cluster ip是一個虛擬ip(相對固定,除非刪除service),這個ip只能在k8s叢集內部使用,如果service需要對外提供,只能使用Nodeport方式對映到主機上,使用主機的ip和埠對外提供服務。(另外還可以使用LoadBalance方式,但這種方式是在gce這樣的雲環境裡面使用的 )。

節點上面有個kube-proxy程序,這個程序從master apiserver獲取資訊,感知service和endpoint的建立,然後做下面兩個事情:
->為每個service 在叢集中每個節點上面建立一個隨機埠,任何該埠上面的連線會代理到相應的pod
->叢集中每個節點安裝iptables規則,用於clusterip + port路由到上一步定義的隨機埠上面,所以叢集中每個node上面都有service的轉發規則:

1 2 3 4 KUBE-PORTALS-CONTAINER 從容器中通過service cluster ip和埠訪問service的請求 KUBE-PORTALS-HOST 從主機中通過service cluster ip和埠訪問service的請求 KUBE-NODEPORT-CONTAINER 從容器中通過service nodeport埠訪問service的請求 KUBE-NODEPORT-HOST 從主機中通過service nodeport埠訪問service的請求。

比如下面是一個測試環境內容:

1 2 3 4 -A KUBE-NODEPORT-CONTAINER -p tcp -m comment --comment"smart/ccdb:port1521"-m tcp --dport 50171 -j REDIRECT --to-ports 52244 -A KUBE-NODEPORT-HOST -p tcp -m comment --comment"smart/ccdb:port1521"-m tcp --dport 50171 -j DNAT --to-destination 10.45.25.227:52244 -A KUBE-PORTALS-CONTAINER -d 10.254.120.169/32-p tcp -m comment --comment"smart/ccdb:port1521"-m tcp --dport 1521 -j REDIRECT --to-ports 52244 -A KUBE-PORTALS-HOST -d 10.254.120.169/32-p tcp -m comment --comment"smart/ccdb:port1521"-m tcp --dport 1521 -j DNAT --to-destination 10.45.25.227:5224452244

這些就是kube-proxy針對service “"smart/ccdb:port1521"” 在節點上面監聽的埠。

六、Kubernetes日常維護命令

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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 一. 檢視叢集資訊 ============================================================================================================= [root@k8s-master01 ~]# kubectl cluster-info [root@k8s-master01 ~]# kubectl cluster-info dump 二. 檢視各元件狀態 ============================================================================================================= [root@k8s-master01 ~]# kubectl -s http://localhost:8080 get componentstatuses NAME STATUS MESSAGE ERROR controller-manager Healthy ok scheduler Healthy ok etcd-0 Healthy {"health":"true"} 或者 [root@k8s-master01 ~]# kubectl -s http://172.16.60.220:8080 get componentstatuses NAME STATUS MESSAGE ERROR scheduler Healthy ok controller-manager Healthy ok etcd-0 Healthy {"health":"true"} 三. GET資訊 ============================================================================================================= 1) 檢視節點 (k8s-master01 對應的是 172.16.60.220的主機名) [root@k8s-master01 ~]# kubectl get node #將命令中的node變為nodes也是可以的 NAME STATUS AGE k8s-node01 Ready 1d k8s-node02 Ready 1d [root@k8s-master01 ~]# kubectl -s http://k8s-master01:8080 get node #將命令中的node變為nodes也是可以的 NAME STATUS AGE k8s-node01 Ready 1d k8s-node02 Ready 1d 2) 檢視pods清單(檢視pod ip地址,下面命令加上"-o wide" [root@k8s-master01 ~]# kubectl get pod #將pod變為pods也可以。如果有namespace,需要跟上"-n namespace名字" 或 "--all-namespaces" NAME READY STATUS RESTARTS AGE nginx-controller-d97wj 1/1Running 0 1h nginx-controller-lf11n 1/1Running 0 1h tomcat-controller-35kzb 1/1Running 0 18m tomcat-controller-lsph4 1/1Running 0 18m [root@k8s-master01 ~]# kubectl -s http://k8s-master01:8080 get pod #將命令中的pod變為pods也是可以的 NAME READY STATUS RESTARTS AGE nginx-controller-d97wj 1/1Running 0 1h nginx-controller-lf11n 1/1Running 0 1h tomcat-controller-35kzb 1/1Running 0 18m tomcat-controller-lsph4 1/1Running 0 18m 3) 檢視service清單 [root@k8s-master01 ~]# kubectl get service #將命令中的service變為services也是可以的 NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes 172.16.0.1 <none> 443/TCP1d nginx-service-clusterip 172.16.77.193 <none> 8001/TCP1h nginx-service-nodeport 172.16.234.94 <nodes> 8000:32172/TCP59m tomcat-service-clusterip 172.16.144.116 <none> 8801/TCP14m tomcat-service-nodeport 172.16.183.234 <nodes> 8880:31960/TCP11m [root@k8s-master01 ~]# kubectl -s http://172.16.60.220:8080 get service #將命令中的service變為services也是可以的 NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes 172.16.0.1 <none> 443/TCP1d nginx-service-clusterip 172.16.77.193 <none> 8001/TCP1h nginx-service-nodeport 172.16.234.94 <nodes> 8000:32172/TCP1h tomcat-service-clusterip 172.16.144.116 <none> 8801/TCP17m tomcat-service-nodeport 172.16.183.234 <nodes> 8880:31960/TCP14m 或者 (後面的sed表示 列印奇數行) [root@k8s-master01 ~]# kubectl get services -o json|grep '"name":'|sed -n '1~2p' "name":"kubernetes", "name":"nginx-service-clusterip", "name":"nginx-service-nodeport", "name":"tomcat-service-clusterip", "name":"tomcat-service-nodeport", 4) 檢視replicationControllers清單 (同理可以將命令中的replicationControllers變為replicationController也是可以的) [root@k8s-master01 ~]# kubectl get replicationControllers NAME DESIRED CURRENT READY AGE nginx-controller 2 2 2 2h tomcat-controller 2 2 2 1h [root@k8s-master01 ~]# kubectl -s http://172.16.60.220:8080 get replicationControllers NAME DESIRED CURRENT READY AGE nginx-controller 2 2 2 2h tomcat-controller 2 2 2 1h 5) 檢視rc和namespace [root@k8s-master01 ~]# kubectl get rc,namespace NAME DESIRED CURRENT READY AGE rc/nginx-controller2 2 2 2h rc/tomcat-controller2 2 2 1h NAME STATUS AGE ns/defaultActive 1d ns/kube-systemActive 1d 6) 檢視pod和svc(和service一樣) [root@k8s-master01 ~]# kubectl get pods,svc NAME READY STATUS RESTARTS AGE po/nginx-controller-d97wj1/1Running 0 2h po/nginx-controller-lf11n1/1Running 0 2h po/tomcat-controller-35kzb1/1Running 0 1h po/tomcat-controller-lsph41/1Running 0 1h NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE svc/kubernetes172.16.0.1 <none> 443/TCP1d svc/nginx-service-clusterip172.16.77.193 <none> 8001/TCP2h svc/nginx-service-nodeport172.16.234.94 <nodes> 8000:32172/TCP2h svc/tomcat-service-clusterip172.16.144.116 <none> 8801/TCP1h svc/tomcat-service-nodeport172.16.183.234 <nodes> 8880:31960/TCP1h 7) 以jison格式輸出pod的詳細資訊. [root@k8s-master01 ~]# kubectl get pods NAME READY STATUS RESTARTS AGE nginx-controller-d97wj 1/1Running 0 2h nginx-controller-lf11n 1/1Running 0 2h tomcat-controller-35kzb 1/1Running 0 1h tomcat-controller-lsph4 1/1Running 0 1h 注意下面命令中的pods的名稱可以通過上面命令檢視 [root@k8s-master01 ~]# kubectl get po nginx-controller-d97wj -o json { "apiVersion":"v1", "kind":"Pod", "metadata": { "annotations": { ................... ................... "hostIP":"172.16.60.222", "phase":"Running", "podIP":"192.168.100.2", "startTime":"2019-03-15T14:40:18Z" } } 還可以輸出其它格式和方法(kubectl get -h檢視幫助) [root@k8s-master01 ~]# kubectl get -h 8) 檢視指定pod跑在哪個node上 [root@k8s-master01 ~]# kubectl get po nginx-controller-d97wj -o wide NAME READY STATUS RESTARTS AGE IP NODE nginx-controller-d97wj 1/1Running 0 2h 192.168.100.2 k8s-node02 9) 獲取指定json或ymal格式的KEY資料,custom-columns=XXXXX(自定義列名):.status.hostIP(以“點開始”,然後寫路徑就可以) 注意: 下面命令中的nginx-controller-d97wj是pod單元名稱 (kubectl get pods 可以檢視pods) [root@k8s-master01 ~]# kubectl get po nginx-controller-d97wj -o custom-columns=HOST-IP:.status.hostIP,POD-IP:.status.podIP HOST-IP POD-IP 172.16.60.222 192.168.100.2 10) describe方法 describe類似於get,同樣用於獲取resource的相關資訊。不同的是,get獲得的是更詳細的resource個性的詳細資訊,describe獲得的是resource叢集相關的資訊。 describe命令同get類似,但是describe不支援-o選項,對於同一型別resource,describe輸出的資訊格式,內容域相同。 需要注意: 如果發現是查詢某個resource的資訊,使用get命令能夠獲取更加詳盡的資訊。但是如果想要查詢某個resource的狀態,如某個pod並不是在running狀態, 這時需要獲取更詳盡的狀態資訊時,就應該使用describe命令。 [root@k8s-master01 ~]# kubectl describe po nginx-controller-d97wj Name: nginx-controller-d97wj Namespace: default Node: k8s-node02/172.16.60.222 Start Time: Fri, 15 Mar 2019 22:40:18 +0800 Labels: name=nginx Status: Running IP: 192.168.100.2 Controllers: ReplicationController/nginx-controller Containers: nginx: Container ID: docker://8ae4502b4e62120322de98aa532e653d3d2e058ffbb0b842e0f265621bebbe61 Image: 172.16.60.220:5000/nginx Image ID: docker-pullable://172.16.60.220:5000/nginx@sha256:7734a210432278817f8097acf2f72d20e2ccc7402a0509810c44b3a8bfe0094a Port: 80/TCP State: Running Started: Fri, 15 Mar 2019 22:40:19 +0800 Ready: True Restart Count: 0 Volume Mounts: <none> Environment Variables: <none> Conditions: Type Status Initialized True Ready True PodScheduled True No volumes. QoS Class: BestEffort Tolerations: <none> No events. 11) create建立 kubectl命令用於根據檔案或輸入建立叢集resource。如果已經定義了相應resource的yaml或son檔案,直接kubectl create -f filename即可建立檔案內定義的 resource。也可以直接只用子命令[namespace/secret/configmap/serviceaccount]等直接建立相應的resource。從追蹤和維護的角度出發,建議使用json或 yaml的方式定義資源。 命令格式: # kubectl create -f 檔名 12) replace更新替換資源 replace命令用於對已有資源進行更新、替換。如前面create中建立的nginx,當我們需要更新resource的一些屬性的時候,如果修改副本數量,增加、修改label, 更改image版本,修改埠等。都可以直接修改原yaml檔案,然後執行replace命令。 需要注意: 名字不能被更更新。另外,如果是更新label,原有標籤的pod將會與更新label後的rc斷開聯絡,有新label的rc將會建立指定副本數的新的pod,但是預設 並不會刪除原來的pod。所以此時如果使用get po將會發現pod數翻倍,進一步check會發現原來的pod已經不會被新rc控制,此處只介紹命令不詳談此問題,好奇者可自行實驗。 命令格式: # kubectl replace -f nginx-rc.yaml 13) patch 如果一個容器已經在執行,這時需要對一些容器屬性進行修改,又不想刪除容器,或不方便通過replace的方式進行更新。kubernetes還提供了一種在容器執行時,直接 對容器進行修改的方式,就是patch命令。 如建立pod的label是app=nginx-2,如果在執行過程中,需要把其label改為app=nginx-3。 這個patch命令如下: [root@k8s-master01 ~]# kubectl patch pod nginx-controller-d97wj -p '{"metadata":{"labels":{"app":"nginx-3"}}}' "nginx-controller-d97wj"patched 14) edit edit提供了另一種更新resource源的操作,通過edit能夠靈活的在一個common的resource基礎上,發展出更過的significant resource。 例如,使用edit直接更新前面建立的pod的命令為: # kubectl edit po nginx-controller-d97wj 上面命令的效果等效於: # kubectl get po nginx-controller-d97wj -o yaml >> /tmp/nginx-tmp.yaml # vim /tmp/nginx-tmp.yaml // 這此檔案裡做一些修改 # kubectl replace -f /tmp/nginx-tmp.yaml 15) Delete 根據resource名或label刪除resource。 # kubectl delete -f nginx-rc.yaml # kubectl delete po nginx-controller-d97wj # kubectl delete po nginx-controller-lf11n 16) apply apply命令提供了比patch,edit等更嚴格的更新resource的方式。通過apply,使用者可以將resource的configuration使用sourcecontrol的方式維護在版本庫中。 每次有更新時,將配置檔案push到server,然後使用kubectl apply將更新應用到resource。kubernetes會在引用更新前將當前配置檔案中的配置同已經應用的配置 做比較,並只更新更改的部分,而不會主動更改任何使用者未指定的部分。 apply命令的使用方式同replace相同,不同的是,apply不會刪除原有resource,然後建立新的。apply直接在原有resource的基礎上進行更新。同時kubectl apply 還會resource中新增一條註釋,標記當前的apply。類似於git操作。 17) logs logs命令用於顯示pod執行中,容器內程式輸出到標準輸出的內容。跟docker的logs命令類似。如果要獲得tail-f 的方式,也可以使用-f選項。 # kubectl logs nginx-controller-d97wj 18) rolling-update rolling-update是一個非常重要的命令,對於已經部署並且正在執行的業務,rolling-update提供了不中斷業務的更新方式。rolling-update每次起一個新的pod, 等新pod完全起來後刪除一箇舊的pod,然後再起一個新的pod替換舊的pod,直到替換掉所有的pod。 rolling-update需要確保新的版本有不同的name,Version和label,否則會報錯 。 # kubectl rolling-update nginx-controller -f nginx-rc.yaml 如果在升級過程中,發現有問題還可以中途停止update,並回滾到前面版本 # kubectl rolling-update nginx-controller --rollback rolling-update還有很多其他選項提供豐富的功能,如--update-period指定間隔週期,使用時可以使用-h檢視help資訊. 19) scale (注意下面的nginx-controller 是在nginx-rc.yaml檔案中定義的name名稱) scale用於程式在負載加重或縮小時副本進行擴容或縮小,如前面建立的nginx有兩個副本,可以輕鬆的使用scale命令對副本數進行擴充套件或縮小。 擴充套件副本數到4: # kubectl scale rc nginx-controller --replicas=4 重新縮減副本數到2: # kubectl scale rc nginx-controller --replicas=2 20) autoscale scale雖然能夠很方便的對副本數進行擴充套件或縮小,但是仍然需要人工介入,不能實時自動的根據系統負載對副本數進行擴、縮。autoscale命令提供了自動根據pod負載 對其副本進行擴縮的功能。 autoscale命令會給一個rc指定一個副本數的範圍,在實際執行中根據pod中執行的程式的負載自動在指定的範圍內對pod進行擴容或縮容。如前面建立的nginx,可以用 如下命令指定副本範圍在1~4 # kubectl autoscale rc nginx-controller --min=1 --max=4 21) attach attach命令類似於docker的attach命令,可以直接檢視容器中以daemon形式執行的程序的輸出,效果類似於logs -f,退出檢視使用ctrl-c。如果一個pod中有多個容器, 要檢視具體的某個容器的的輸出,需要在pod名後使用-c containers name指定執行的容器。如下示例的命令為檢視kube-system namespace中的kube-dns-v9-rcfuk pod 中的skydns容器的輸出。 # kubectl attach kube-dns-v9-rcfuk -c skydns --namespace=kube-system 22)exec exec命令同樣類似於docker的exec命令,為在一個已經執行的容器中執行一條shell命令,如果一個pod容器中,有多個容器,需要使用-c選項指定容器。 23) run 類似於docker的run命令,直接執行一個image。 24) cordon, drain, uncordon 這三個命令是正式release的1.2新加入的命令,三個命令一起介紹,是因為三個命令配合使用可以實現節點的維護。在1.2之前,因為沒有相應的命令支援,如果要維護一個 節點,只能stop該節點上的kubelet將該節點退出叢集,是叢集不在將新的pod排程到該節點上。如果該節點上本生就沒有pod在執行,則不會對業務有任何影響。如果該節 點上有pod正在執行,kubelet停止後,master會發現該節點不可達,而將該節點標記為notReady狀態,不會將新的節點排程到該節點上。同時,會在其他節點上建立新的 pod替換該節點上的pod。這種方式雖然能夠保證叢集的健壯性,但是任然有些暴力,如果業務只有一個副本,而且該副本正好執行在被維護節點上的話,可能仍然會造成業 務的短暫中斷。 1.2中新加入的這3個命令可以保證維護節點時,平滑的將被維護節點上的業務遷移到其他節點上,保證業務不受影響。如下圖所示是一個整個的節點維護的流程(為了方便 demo增加了一些檢視節點資訊的操作): 1- 首先檢視當前叢集所有節點狀態,可以看到共四個節點都處於ready狀態; 2- 檢視當前nginx兩個副本分別執行在d-node1和k-node2兩個節點上; 3- 使用cordon命令將d-node1標記為不可排程; 4- 再使用kubectl get nodes檢視節點狀態,發現d-node1雖然還處於Ready狀態,但是同時還被禁能了排程,這意味著新的pod將不會被排程到d-node1上。 5- 再檢視nginx狀態,沒有任何變化,兩個副本仍執行在d-node1和k-node2上; 6- 執行drain命令,將執行在d-node1上執行的pod平滑的趕到其他節點上; 7- 再檢視nginx的狀態發現,d-node1上的副本已經被遷移到k-node1上;這時候就可以對d-node1進行一些節點維護的操作,如升級核心,升級Docker等; 8- 節點維護完後,使用uncordon命令解鎖d-node1,使其重新變得可排程;8)檢查節點狀態,發現d-node1重新變回Ready狀態 # kubectl get nodes # kubectl get po -o wide # kubectl cordon d-node1 # kubectl get nodes # kubectl get po -o wide # kubectl drain d-node1 # kubectl get po -o wide # kubectl uncordon # kubectl uncordon d-node1 # kubectl get nodes 25) 檢視某個pod重啟次數(這個是參考) # kubectl get pod nginx-controller-d97wj --template="{{range .status.containerStatuses}}{{.name}}:{{.restartCount}}{{end}}" 26) 檢視pod生命週期 [root@k8s-master01 ~]# kubectl get pod nginx-controller-d97wj --template="{{.status.phase}}" Running 四、日常維護命令 ============================================================================================================= kubectl get pods kubectl get rc kubectl get service kubectl get componentstatuses kubectl get endpoints kubectl cluster-info kubectl create -f redis-master-controller.yaml kubectl delete -f redis-master-controller.yaml kubectl delete pod nginx-772ai kubectl logs -f pods/heapster-xxxxx-n kube-system#檢視日誌 kubectl scale rc redis-slave --replicas=3#修改RC的副本數量,來實現Pod的動態縮放 etcdctl cluster-health#檢查網路叢集健康狀態 etcdctl --endpoints=http://172.16.60.220:2379 cluster-health#帶有安全認證檢查網路叢集健康狀態 etcdctl member list etcdctlset/k8s/network/config'{ "Network": "10.1.0.0/16" }' etcdctl get/k8s/network/config 五、基礎進階 ============================================================================================================= kubectl get services kubernetes-dashboard -n kube-system#檢視所有service kubectl get deployment kubernetes-dashboard -n kube-system#檢視所有釋出 kubectl get pods --all-namespaces#檢視所有pod kubectl get pods -o wide --all-namespaces#檢視所有pod的IP及節點 kubectl get pods -n kube-system |grepdashboard kubectl describe service/kubernetes-dashboard--namespace="kube-system" kubectl describe pods/kubernetes-dashboard-349859023-g6q8c--namespace="kube-system"#指定型別檢視 kubectl describe pod nginx-772ai#檢視pod詳細資訊 kubectl scale rc nginx --replicas=5#動態伸縮 kubectl scale deployment redis-slave --replicas=5#動態伸縮 kubectl scale --replicas=2 -f redis-slave-deployment.yaml#動態伸縮 kubectlexec-it tomcat-controller-35kzb/bin/bash#進入容器 kubectl label nodes k8s-node01 zone=north#增加節點lable值 spec.nodeSelector: zone: north, 指定pod在哪個節點 kubectl get nodes -lzone#獲取zone的節點 kubectl label pod tomcat-controller-35kzb role=master#增加lable值 [key]=[value] kubectl label pod tomcat-controller-35kzb role-#刪除lable值 kubectl label pod tomcat-controller-35kzb role=backend --overwrite#修改lable值 kubectl rolling-update redis-master -f redis-master-controller-v2.yaml#配置檔案滾動升級 kubectl rolling-update redis-master --image=redis-master:2.0#命令升級 kubectl rolling-update redis-master --image=redis-master:1.0 --rollback#pod版本回滾 六、yaml使用及命令 ============================================================================================================= kubectl create -f nginx-deployment.yaml#建立deployment資源 kubectl get deploy#檢視deployment kubectl get rs#檢視ReplicaSet kubectl get pods --show-labels#檢視pods所有標籤。可以新增"-all-namespaces" 或者 "-n kube-system"表示檢視所有名稱空間或某一名稱空間裡pods的標籤 kubectl get pods -l app=nginx#根據標籤檢視pods kubectlsetimage deployment/nginx-deploymentnginx=nginx:1.11#滾動更新映象 或者 kubectl edit deployment/nginx-deployment 或者 kubectl apply -f nginx-deployment.yaml#也表示對yaml修改後進行更新操作,更新到kubernetes叢集配置中 kubectl rollout status deployment/nginx-deployment#實時觀察釋出狀態: kubectl rollouthistorydeployment/nginx-deployment#檢視deployment歷史修訂版本 kubectl rollouthistorydeployment/nginx-deployment--revision=3 kubectl rollout undo deployment/nginx-deployment#回滾到以前版本 kubectl rollout undo deployment/nginx-deployment--to-revision=3 kubectl scale deployment nginx-deployment --replicas=10#擴容deployment的Pod副本數量 kubectl autoscale deployment nginx-deployment --min=10 --max=15 --cpu-percent=80#設定啟動擴容/縮容 七、名稱空間 ============================================================================================================= kubectl get namespace#獲取k8s的名稱空間 kubectl get pod --namespace =[命令空間名稱]#獲取對應名稱空間內的pod,"--namespace"可以寫成"-c" kubectl --namespace [命令空間名稱] logs [pod名稱] -c 容器名稱#獲取對應namespace中對應pod的日誌,如果不加"-c 容器名稱",則預設檢視的是該pod下第一個容器的日誌 pod維護示例: 檢視某個命令空間下的pod # kubectl get pods -n namespace 在沒有pod 的yaml檔案時,強制重啟某個pod # kubectl get pod podname -n namespace -o yaml | kubectl replace --force -f - 檢視某個pod重啟次數(這個是參考) # kubectl get pod podname -n namespace --template="{{range .status.containerStatuses}}{{.name}}:{{.restartCount}}{{end}}" 檢視pod生命週期 # kubectl get pod podname --template="{{.status.phase}}" 檢視kube-space命令空間下的pod [root@m7-autocv-gpu01 ~]# kubectl get pods -n kube-system -o wide|grep -E 'elasticsearch|fluentd|kibana' elasticsearch-logging-0 1/1Running 0 5h9m 172.30.104.6 m7-autocv-gpu03 <none> elasticsearch-logging-1 1/1Running 0 4h59m 172.30.232.8 m7-autocv-gpu02 <none> fluentd-es-v2.2.0-mkkcf 1/1Running 0 5h9m 172.30.104.7 m7-autocv-gpu03 <none> kibana-logging-f6fc77549-nlxfg 1/1Running 0 42s 172.30.96.7 m7-autocv-gpu01 <none> [root@m7-autocv-gpu01 ~]# kubectl get pod kibana-logging-f6fc77549-nlxfg -n kube-system -o yaml | kubectl replace --force -f - pod"kibana-logging-f6fc77549-d47nc"deleted pod/kibana-logging-f6fc77549-d47ncreplaced [root@m7-autocv-gpu01 ~]# kubectl get pod kibana-logging-f6fc77549-nlxfg -n kube-system --template="{{range .status.containerStatuses}}{{.name}}:{{.restartCount}}{{end}}" kibana-logging:0 [root@m7-autocv-gpu01 ~]# kubectl get pod kibana-logging-f6fc77549-nlxfg -n kube-system --template="{{.status.phase}}" Running 八、進入pod內的容器 ============================================================================================================= kubernetes中登入pod中的容器,如下,kevintest-f857f78ff-dlp24是pod名稱,webha是名稱空間 # kubectl -n webha exec -it kevintest-f857f78ff-dlp24 -- bash #登入後終端資訊中顯示主機名 # kubectl -n webha exec -it kevintest-f857f78ff-dlp24 sh #登入後終端資訊中不顯示主機名 如果pod中有多個容器,則預設登入到第一個容器中。 也可以通過-c引數制定登入到哪個容器中, 比如進入kevintest-f857f78ff-dlp24的nginx_bo容器 # kubectl -n webha exec -it kevintest-f857f78ff-dlp24 -c nginx_bo -- bash

七、Kubernetes叢集部署失敗的一般原因

1.錯誤的容器映象/非法的倉庫許可權
其中兩個最普遍的問題是:a)指定了錯誤的容器映象;b)使用私有映象卻不提供倉庫認證資訊。這在首次使用 Kubernetes 或者繫結 CI/CD 環境時尤其棘手。看個例子:

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 首先我們建立一個名為 fail 的 deployment,它指向一個不存在的 Docker 映象: $ kubectl run fail --image=rosskukulinski/dne:v1.0.0 然後我們檢視 Pods,可以看到有一個狀態為 ErrImagePull 或者 ImagePullBackOff 的 Pod: $ kubectl get pods NAME READY STATUS RESTARTS AGE fail-1036623984-hxoas 0/1ImagePullBackOff 0 2m 想檢視更多資訊,可以 describe 這個失敗的 Pod: $ kubectl describe pod fail-1036623984-hxoas 檢視 describe 命令的輸出中 Events 這部分,我們可以看到如下內容: Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 5m 5m 1 {default-scheduler } Normal Scheduled Successfully assigned fail-1036623984-hxoas to gke-nrhk-1-default-pool-a101b974-wfp7 5m 2m 5 {kubelet gke-nrhk-1-default-pool-a101b974-wfp7} spec.containers{fail} Normal Pulling pulling image"rosskukulinski/dne:v1.0.0" 5m 2m 5 {kubelet gke-nrhk-1-default-pool-a101b974-wfp7} spec.containers{fail} Warning Failed Failed to pull image"rosskukulinski/dne:v1.0.0": Error: image rosskukulinski/dnenot found 5m 2m 5 {kubelet gke-nrhk-1-default-pool-a101b974-wfp7} Warning FailedSync Error syncing pod, skipping: failed to"StartContainer"for"fail"with ErrImagePull:"Error: image rosskukulinski/dne not found" 5m 11s 19 {kubelet gke-nrhk-1-default-pool-a101b974-wfp7} spec.containers{fail} Normal BackOff Back-off pulling image"rosskukulinski/dne:v1.0.0" 5m 11s 19 {kubelet gke-nrhk-1-default-pool-a101b974-wfp7} Warning FailedSync Error syncing pod, skipping: failed to"StartContainer"for"fail"with ImagePullBackOff:"Back-off pulling image \"rosskukulinski/dne:v1.0.0\"" 顯示錯誤的那句話:Failed to pull image"rosskukulinski/dne:v1.0.0": Error: image rosskukulinski/dnenot found 告訴我們 Kubernetes無法找到映象 rosskukulinski/dne:v1.0.0。 因此問題變成:為什麼 Kubernetes 拉不下來映象? 除了網路連線問題外,還有三個主要元凶: - 映象 tag 不正確 - 映象不存在(或者是在另一個倉庫) - Kubernetes 沒有許可權去拉那個映象 如果你沒有注意到你的映象 tag 的拼寫錯誤,那麼最好就用你本地機器測試一下。 通常我會在本地開發機上,用 docker pull 命令,帶上 完全相同的映象 tag,來跑一下。比如上面的情況,我會執行命令 docker pull rosskukulinski/dne:v1.0.0。 如果這成功了,那麼很可能 Kubernetes 沒有許可權去拉取這個映象。參考映象拉取 Secrets 來解決這個問題。 如果失敗了,那麼我會繼續用不顯式帶 tag 的映象測試 - docker pull rosskukulinski/dne- 這會嘗試拉取 tag 為 latest 的映象。如果這樣成功,表明原來指定的 tag 不存在。這可能是人為原因,拼寫錯誤,或者 CI/CD的配置錯誤。 如果 docker pull rosskukulinski/dne(不指定 tag)也失敗了,那麼我們碰到了一個更大的問題:我們所有的映象倉庫中都沒有這個映象。預設情況下,Kubernetes 使用 Dockerhub 映象倉庫,如果你在使用 Quay.io,AWS ECR,或者 Google Container Registry,你要在映象地址中指定這個倉庫的 URL,比如使用 Quay,映象地址就變成 quay.io/rosskukulinski/dne:v1.0.0。 如果你在使用 Dockerhub,那你應該再次確認你釋出映象到 Dockerhub 的系統,確保名字和 tag 匹配你的 deployment 正在使用的映象。 注意:觀察 Pod 狀態的時候,映象缺失和倉庫許可權不正確是沒法區分的。其它情況下,Kubernetes 將報告一個 ErrImagePull 狀態。

2.應用啟動之後又掛掉
無論你是在 Kubernetes 上啟動新應用,還是遷移應用到已存在的平臺,應用在啟動之後就掛掉都是一個比較常見的現象。看個例子:

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 44 45 46 47 48 49 50 51 52 我們建立一個 deployment,它的應用會在1秒後掛掉: $ kubectl run crasher --image=rosskukulinski/crashing-app 我們看一下 Pods 的狀態: $ kubectl get pods NAME READY STATUS RESTARTS AGE crasher-2443551393-vuehs 0/1CrashLoopBackOff 2 54s CrashLoopBackOff 告訴我們,Kubernetes 正在盡力啟動這個 Pod,但是一個或多個容器已經掛了,或者正被刪除。 讓我們 describe 這個 Pod 去獲取更多資訊: $ kubectl describe pod crasher-2443551393-vuehs Name: crasher-2443551393-vuehs Namespace: fail Node: gke-nrhk-1-default-pool-a101b974-wfp7/10.142.0.2 Start Time: Fri, 10 Feb 2017 14:20:29 -0500 Labels: pod-template-hash=2443551393 run=crasher Status: Running IP: 10.0.0.74 Controllers: ReplicaSet/crasher-2443551393 Containers: crasher: Container ID: docker://51c940ab32016e6d6b5ed28075357661fef3282cb3569117b0f815a199d01c60 Image: rosskukulinski/crashing-app Image ID: docker://sha256:cf7452191b34d7797a07403d47a1ccf5254741d4bb356577b8a5de40864653a5 Port: State: Terminated Reason: Error Exit Code: 1 Started: Fri, 10 Feb 2017 14:22:24 -0500 Finished: Fri, 10 Feb 2017 14:22:26 -0500 Last State: Terminated Reason: Error Exit Code: 1 Started: Fri, 10 Feb 2017 14:21:39 -0500 Finished: Fri, 10 Feb 2017 14:21:40 -0500 Ready: False Restart Count: 4 ... 好可怕,Kubernetes 告訴我們這個 Pod 正被 Terminated,因為容器裡的應用掛了。我們還可以看到應用的 Exit Code 是 1。後面我們可能還會看到一個 OOMKilled 錯誤。 我們的應用正在掛掉?為什麼? 首先我們檢視應用日誌。假定你傳送應用日誌到 stdout(事實上你也應該這麼做),你可以使用 kubectl logs 看到應用日誌: $ kubectl logs crasher-2443551393-vuehs 不幸的是,這個 Pod 沒有任何日誌。這可能是因為我們正在檢視一個新起的應用例項,因此我們應該檢視前一個容器: $ kubectl logs crasher-2443551393-vuehs --previous 什麼!我們的應用仍然不給我們任何東西。這個時候我們應該給應用加點啟動日誌了,以幫助我們定位這個問題。我們也可以本地執行一下這個容器,以確定是否缺失環境變數或者掛載卷。

3.缺失 ConfigMap 或者 Secret
Kubernetes 最佳實踐建議通過 ConfigMaps 或者 Secrets 傳遞應用的執行時配置。這些資料可以包含資料庫認證資訊,API endpoints,或者其它配置資訊。一個常見的錯誤是,建立的 deployment 中引用的 ConfigMaps 或者 Secrets 的屬性不存在,有時候甚至引用的 ConfigMaps 或者 Secrets 本身就不存在。

缺失 ConfigMap
第一個例子,我們將嘗試建立一個 Pod,它載入 ConfigMap 資料作為環境變數:

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 # configmap-pod.yaml apiVersion: v1 kind: Pod metadata: name: configmap-pod spec: containers: - name:test-container image: gcr.io/google_containers/busybox command: ["/bin/sh","-c","env"] env: - name: SPECIAL_LEVEL_KEY valueFrom: configMapKeyRef: name: special-config key: special.how 讓我們建立一個 Pod:kubectl create -f configmap-pod.yaml。在等待幾分鐘之後,我們可以檢視我們的 Pod: $ kubectl get pods NAME READY STATUS RESTARTS AGE configmap-pod 0/1RunContainerError 0 3s Pod 狀態是 RunContainerError 。我們可以使用 kubectl describe 瞭解更多: $ kubectl describe pod configmap-pod [...] Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 20s 20s 1 {default-scheduler } Normal Scheduled Successfully assigned configmap-pod to gke-ctm-1-sysdig2-35e99c16-tgfm 19s 2s 3 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Normal Pulling pulling image"gcr.io/google_containers/busybox" 18s 2s 3 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Normal Pulled Successfully pulled image"gcr.io/google_containers/busybox" 18s 2s 3 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} Warning FailedSync Error syncing pod, skipping: failed to"StartContainer"for"test-container"with RunContainerError:"GenerateRunContainerOptions: configmaps \"special-config\" not found" Events 章節的最後一條告訴我們什麼地方錯了。Pod 嘗試訪問名為 special-config 的 ConfigMap,但是在該 namespace 下找不到。一旦我們建立這個 ConfigMap,Pod 應該重啟並能成功拉取執行時資料。 在 Pod 規格說明中訪問 Secrets 作為環境變數會產生相似的錯誤,就像我們在這裡看到的 ConfigMap錯誤一樣。

但是假如你通過 Volume 來訪問 Secrets 或者 ConfigMap會發生什麼呢?

缺失 Secrets
下面是一個pod規格說明,它引用了名為 myothersecret 的 Secrets,並嘗試把它掛為卷:

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 44 45 46 # missing-secret.yaml apiVersion: v1 kind: Pod metadata: name: secret-pod spec: containers: - name:test-container image: gcr.io/google_containers/busybox command: ["/bin/sh","-c","env"] volumeMounts: - mountPath:/etc/secret/ name: myothersecret restartPolicy: Never volumes: - name: myothersecret secret: secretName: myothersecret 讓我們用 kubectl create -f missing-secret.yaml 來建立一個 Pod。 幾分鐘後,我們 get Pods,可以看到 Pod 仍處於 ContainerCreating 狀態: $ kubectl get pods NAME READY STATUS RESTARTS AGE secret-pod 0/1ContainerCreating 0 4h 這就奇怪了。我們 describe 一下,看看到底發生了什麼: $ kubectl describe pod secret-pod Name: secret-pod Namespace: fail Node: gke-ctm-1-sysdig2-35e99c16-tgfm/10.128.0.2 Start Time: Sat, 11 Feb 2017 14:07:13 -0500 Labels: Status: Pending IP: Controllers: [...] Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 18s 18s 1 {default-scheduler } Normal Scheduled Successfully assigned secret-pod to gke-ctm-1-sysdig2-35e99c16-tgfm 18s 2s 6 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} Warning FailedMount MountVolume.SetUp failedforvolume"kubernetes.io/secret/337281e7-f065-11e6-bd01-42010af0012c-myothersecret"(spec.Name:"myothersecret") pod"337281e7-f065-11e6-bd01-42010af0012c"(UID:"337281e7-f065-11e6-bd01-42010af0012c") with: secrets"myothersecret"not found Events 章節再次解釋了問題的原因。它告訴我們 Kubelet 無法從名為 myothersecret 的 Secret 掛卷。為了解決這個問題,我們可以建立 myothersecret ,它包含必要的安全認證資訊。一旦 myothersecret 建立完成,容器也將正確啟動。

4.活躍度/就緒狀態探測失敗
在 Kubernetes 中處理容器問題時,需要注意的是:你的容器應用是 running 狀態,不代表它在工作!?

Kubernetes 提供了兩個基本特性,稱作活躍度探測就緒狀態探測。本質上來說,活躍度/就緒狀態探測將定期地執行一個操作(例如傳送一個 HTTP 請求,開啟一個 tcp 連線,或者在你的容器內執行一個命令),以確認你的應用和你預想的一樣在工作。

如果活躍度探測失敗,Kubernetes 將殺掉你的容器並重新建立一個。如果就緒狀態探測失敗,這個 Pod 將不會作為一個服務的後端 endpoint,也就是說不會流量導到這個 Pod,直到它變成 Ready。

如果你試圖部署變更你的活躍度/就緒狀態探測失敗的應用,滾動部署將一直懸掛,因為它將等待你的所有 Pod 都變成 Ready。

這個實際是怎樣的情況?以下是一個 Pod 規格說明,它定義了活躍度/就緒狀態探測方法,都是基於8080埠對 /healthy 路由進行健康檢查:

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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 apiVersion: v1 kind: Pod metadata: name: liveness-pod spec: containers: - name:test-container image: rosskukulinski/leaking-app livenessProbe: httpGet: path:/healthz port: 8080 initialDelaySeconds: 3 periodSeconds: 3 readinessProbe: httpGet: path:/healthz port: 8080 initialDelaySeconds: 3 periodSeconds: 3 讓我們建立這個 Pod:kubectl create -f liveness.yaml,過幾分鐘後檢視發生了什麼: $ kubectl get pods NAME READY STATUS RESTARTS AGE liveness-pod 0/1Running 4 2m 2分鐘以後,我們發現 Pod 仍然沒處於 Ready 狀態,並且它已被重啟了4次。讓我們 describe 一下檢視更多資訊: $ kubectl describe pod liveness-pod Name: liveness-pod Namespace: fail Node: gke-ctm-1-sysdig2-35e99c16-tgfm/10.128.0.2 Start Time: Sat, 11 Feb 2017 14:32:36 -0500 Labels: Status: Running IP: 10.108.88.40 Controllers: Containers: test-container: Container ID: docker://8fa6f99e6fda6e56221683249bae322ed864d686965dc44acffda6f7cf186c7b Image: rosskukulinski/leaking-app Image ID: docker://sha256:7bba8c34dad4ea155420f856cd8de37ba9026048bd81f3a25d222fd1d53da8b7 Port: State: Running Started: Sat, 11 Feb 2017 14:40:34 -0500 Last State: Terminated Reason: Error Exit Code: 137 Started: Sat, 11 Feb 2017 14:37:10 -0500 Finished: Sat, 11 Feb 2017 14:37:45 -0500 [...] Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 8m 8m 1 {default-scheduler } Normal Scheduled Successfully assigned liveness-pod to gke-ctm-1-sysdig2-35e99c16-tgfm 8m 8m 1 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Normal Created Created container with dockerid0fb5f1a56ea0; Security:[seccomp=unconfined] 8m 8m 1 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Normal Started Started container with dockerid0fb5f1a56ea0 7m 7m 1 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Normal Created Created container with dockerid3f2392e9ead9; Security:[seccomp=unconfined] 7m 7m 1 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Normal Killing Killing container with dockerid0fb5f1a56ea0: pod"liveness-pod_fail(d75469d8-f090-11e6-bd01-42010af0012c)"container"test-container"is unhealthy, it will be killed and re-created. 8m 16s 10 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Warning Unhealthy Liveness probe failed: Get http://10.108.88.40:8080/healthz: dial tcp 10.108.88.40:8080: getsockopt: connection refused 8m 1s 85 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Warning Unhealthy Readiness probe failed: Get http://10.108.88.40:8080/healthz: dial tcp 10.108.88.40:8080: getsockopt: connection refused Events 章節再次救了我們。我們可以看到活躍度探測和就緒狀態探測都失敗了。關鍵的一句話是 container"test-container"is unhealthy, it will be killed and re-created。這告訴我們 Kubernetes 正在殺這個容器,因為容器的活躍度探測失敗了。 這裡有三種可能性: - 你的探測不正確,健康檢查的 URL 是否改變了? - 你的探測太敏感了, 你的應用是否要過一會才能啟動或者響應? - 你的應用永遠不會對探測做出正確響應,你的資料庫是否配置錯了 檢視 Pod 日誌是一個開始調測的好地方。一旦你解決了這個問題,新的 deployment 應該就能成功了。

5.超出CPU/記憶體的限制
Kubernetes 賦予叢集管理員限制 Pod 和容器的 CPU 或記憶體數量的能力。作為應用開發者,你可能不清楚這個限制,導致 deployment 失敗的時候一臉困惑。我們試圖部署一個未知 CPU/memory 請求限額的 deployment:

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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 # gateway.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: gateway spec: template: metadata: labels: app: gateway spec: containers: - name:test-container image: nginx resources: requests: memory: 5Gi 你會看到我們設了 5Gi 的資源請求。讓我們建立這個 deployment:kubectl create -f gateway.yaml。 現在我們可以看到我們的 Pod: $ kubectl get pods No resources found. 為啥,讓我們用 describe 來觀察一下我們的 deployment: $ kubectl describe deployment/gateway Name: gateway Namespace: fail CreationTimestamp: Sat, 11 Feb 2017 15:03:34 -0500 Labels: app=gateway Selector: app=gateway Replicas: 0 updated | 1 total | 0 available | 1 unavailable StrategyType: RollingUpdate MinReadySeconds: 0 RollingUpdateStrategy: 0 max unavailable, 1 max surge OldReplicaSets: NewReplicaSet: gateway-764140025 (0/1replicas created) Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 4m 4m 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replicasetgateway-764140025 to 1 基於最後一行,我們的 deployment 建立了一個 ReplicaSet(gateway-764140025) 並把它擴充套件到 1。這個是用來管理 Pod 生命週期的實體。我們可以 describe 這個 ReplicaSet: $ kubectl describe rs/gateway-764140025 Name: gateway-764140025 Namespace: fail Image(s): nginx Selector: app=gateway,pod-template-hash=764140025 Labels: app=gateway pod-template-hash=764140025 Replicas: 0 current / 1 desired Pods Status: 0 Running / 0 Waiting / 0 Succeeded / 0 Failed No volumes. Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 6m 28s 15 {replicaset-controller } Warning FailedCreate Error creating: pods"gateway-764140025-"is forbidden: [maximum memory usage per Pod is 100Mi, but request is 5368709120., maximum memory usage per Container is 100Mi, but request is 5Gi.]

上面可知,叢集管理員設定了每個 Pod 的最大記憶體使用量為 100Mi。你可以執行 kubectl describe limitrange 來檢視當前租戶的限制。

那麼現在就有3個選擇:
- 要求你的叢集管理員提升限額;
- 減少 deployment 的請求或者限額設定;
- 直接編輯限額;

6.資源配額
和資源限額類似,Kubernetes 也允許管理員給每個 namespace 設定資源配額。這些配額可以在 Pods,Deployments,PersistentVolumes,CPU,記憶體等資源上設定軟性或者硬性限制。讓我們看看超出資源配額後會發生什麼。以下是我們的 deployment 例子:

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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 # test-quota.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: gateway-quota spec: template: spec: containers: - name:test-container image: nginx 我們可用 kubectl create -ftest-quota.yaml 建立,然後觀察我們的 Pods: $ kubectl get pods NAME READY STATUS RESTARTS AGE gateway-quota-551394438-pix5d 1/1Running 0 16s 看起來很好,現在讓我們擴充套件到 3 個副本:kubectl scale deploy/gateway-quota--replicas=3,然後再次觀察 Pods: $ kubectl get pods NAME READY STATUS RESTARTS AGE gateway-quota-551394438-pix5d 1/1Running 0 9m 啊,我們的pod去哪了?讓我們觀察一下 deployment: $ kubectl describe deploy/gateway-quota Name: gateway-quota Namespace: fail CreationTimestamp: Sat, 11 Feb 2017 16:33:16 -0500 Labels: app=gateway Selector: app=gateway Replicas: 1 updated | 3 total | 1 available | 2 unavailable StrategyType: RollingUpdate MinReadySeconds: 0 RollingUpdateStrategy: 1 max unavailable, 1 max surge OldReplicaSets: NewReplicaSet: gateway-quota-551394438 (1/3replicas created) Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 9m 9m 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replicasetgateway-quota-551394438 to 1 5m 5m 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replicasetgateway-quota-551394438 to 3 在最後一行,我們可以看到 ReplicaSet 被告知擴充套件到 3 。我們用 describe 來觀察一下這個 ReplicaSet 以瞭解更多資訊: kubectl describe replicaset gateway-quota-551394438 Name: gateway-quota-551394438 Namespace: fail Image(s): nginx Selector: app=gateway,pod-template-hash=551394438 Labels: app=gateway pod-template-hash=551394438 Replicas: 1 current / 3 desired Pods Status: 1 Running / 0 Waiting / 0 Succeeded / 0 Failed No volumes. Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 11m 11m 1 {replicaset-controller } Normal SuccessfulCreate Created pod: gateway-quota-551394438-pix5d 11m 30s 33 {replicaset-controller } Warning FailedCreate Error creating: pods"gateway-quota-551394438-"is forbidden: exceededquota: compute-resources, requested: pods=1, used: pods=1, limited: pods=1

上面可以看出,我們的 ReplicaSet 無法建立更多的 pods 了,因為配額限制了:exceeded quota: compute-resources, requested: pods=1, used: pods=1, limited: pods=1。

和資源限額類似,我們現在也有3個選項:
- 要求叢集管理員提升該 namespace 的配額
- 刪除或者收縮該 namespace 下其它的 deployment
- 直接編輯配額

7.叢集資源不足
除非你的叢集開通了叢集自動伸縮功能,否則總有一天你的叢集中 CPU 和記憶體資源會耗盡。這不是說 CPU 和記憶體被完全使用了,而是指它們被 Kubernetes 排程器完全使用了。如同我們在第 5 點看到的,叢集管理員可以限制開發者能夠申請分配給 pod 或者容器的 CPU 或者記憶體的數量。聰明的管理員也會設定一個預設的 CPU/記憶體 申請數量,在開發者未提供申請額度時使用。

如果你所有的工作都在 default 這個 namespace 下工作,你很可能有個預設值 100m 的容器 CPU申請額度,對此你甚至可能都不清楚。執行 kubectl describe ns default 檢查一下是否如此。我們假定你的 Kubernetes 叢集只有一個包含 CPU 的節點。你的 Kubernetes 叢集有 1000m 的可排程 CPU。當前忽略其它的系統 pods(kubectl -n kube-system get pods),你的單節點叢集能部署 10 個 pod(每個 pod 都只有一個包含 100m 的容器)。

10 Pods * (1 Container * 100m) = 1000m == Cluster CPUs

當你擴大到 11 個的時候,會發生什麼?下面是一個申請 1CPU(1000m)的 deployment 例子

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 44 45 46 47 48 49 50 51 52 # cpu-scale.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: cpu-scale spec: template: metadata: labels: app: cpu-scale spec: containers: - name:test-container image: nginx resources: requests: cpu: 1 我把這個應用部署到有 2 個可用 CPU 的叢集。除了我的 cpu-scale 應用,Kubernetes 內部服務也在消耗 CPU 和記憶體。 我們可以用 kubectl create -f cpu-scale.yaml 部署這個應用,並觀察 pods: $ kubectl get pods NAME READY STATUS RESTARTS AGE cpu-scale-908056305-xstti 1/1Running 0 5m 第一個 pod 被排程並運行了。我們看看擴充套件一個會發生什麼: $ kubectl scale deploy/cpu-scale--replicas=2 deployment"cpu-scale"scaled $ kubectl get pods NAME READY STATUS RESTARTS AGE cpu-scale-908056305-phb4j 0/1Pending 0 4m cpu-scale-908056305-xstti 1/1Running 0 5m 我們的第二個pod一直處於 Pending,被阻塞了。我們可以 describe 這第二個 pod 檢視更多的資訊: $ kubectl describe pod cpu-scale-908056305-phb4j Name: cpu-scale-908056305-phb4j Namespace: fail Node: gke-ctm-1-sysdig2-35e99c16-qwds/10.128.0.4 Start Time: Sun, 12 Feb 2017 08:57:51 -0500 Labels: app=cpu-scale pod-template-hash=908056305 Status: Pending IP: Controllers: ReplicaSet/cpu-scale-908056305 [...] Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 3m 3m 1 {default-scheduler } Warning FailedScheduling pod (cpu-scale-908056305-phb4j) failed to fitinany node fit failure on node (gke-ctm-1-sysdig2-35e99c16-wx0s): Insufficient cpu fit failure on node (gke-ctm-1-sysdig2-35e99c16-tgfm): Insufficient cpu fit failure on node (gke-ctm-1-sysdig2-35e99c16-qwds): Insufficient cpu

Events 模組告訴我們 Kubernetes 排程器(default-scheduler)無法排程這個 pod 因為它無法匹配任何節點。它甚至告訴我們每個節點哪個擴充套件點失敗了(Insufficient cpu)。

那麼我們如何解決這個問題?如果你太渴望你申請的 CPU/記憶體 的大小,你可以減少申請的大小並重新部署。當然,你也可以請求你的叢集管理員擴充套件這個叢集(因為很可能你不是唯一一個碰到這個問題的人)。

現在你可能會想:我們的 Kubernetes 節點是在我們的雲提供商的自動伸縮群組裡,為什麼他們沒有生效呢?原因是,你的雲提供商沒有深入理解 Kubernetes 排程器是做啥的。利用 Kubernetes 的叢集自動伸縮能力允許你的叢集根據排程器的需求自動伸縮它自身。如果你在使用 GCE,叢集伸縮能力是一個 beta 特性。

8.持久化卷掛載失敗
另一個常見錯誤是建立了一個引用不存在的持久化卷(PersistentVolumes)的 deployment。不論你是使用 PersistentVolumeClaims(你應該使用這個!),還是直接訪問持久化磁碟,最終結果都是類似的。

下面是我們的測試 deployment,它想使用一個名為 my-data-disk 的 GCE 持久化卷:

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 44 45 46 47 48 49 50 51 52 53 54 55 56 # volume-test.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: volume-test spec: template: metadata: labels: app: volume-test spec: containers: - name:test-container image: nginx volumeMounts: - mountPath:/test name:test-volume volumes: - name:test-volume # This GCE PD must already exist (oops!) gcePersistentDisk: pdName: my-data-disk fsType: ext4 讓我們建立這個 deployment:kubectl create -f volume-test.yaml,過幾分鐘後檢視 pod: kubectl get pods NAME READY STATUS RESTARTS AGE volume-test-3922807804-33nux 0/1ContainerCreating 0 3m 3 分鐘的等待容器建立時間是很長了。讓我們用 describe 來檢視這個 pod,看看到底發生了什麼: $ kubectl describe pod volume-test-3922807804-33nux Name: volume-test-3922807804-33nux Namespace: fail Node: gke-ctm-1-sysdig2-35e99c16-qwds/10.128.0.4 Start Time: Sun, 12 Feb 2017 09:24:50 -0500 Labels: app=volume-test pod-template-hash=3922807804 Status: Pending IP: Controllers: ReplicaSet/volume-test-3922807804 [...] Volumes: test-volume: Type: GCEPersistentDisk (a Persistent Disk resourceinGoogle Compute Engine) PDName: my-data-disk FSType: ext4 Partition: 0 ReadOnly:false [...] Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 4m 4m 1 {default-scheduler } Normal Scheduled Successfully assigned volume-test-3922807804-33nux to gke-ctm-1-sysdig2-35e99c16-qwds 1m 1m 1 {kubelet gke-ctm-1-sysdig2-35e99c16-qwds} Warning FailedMount Unable tomountvolumesforpod"volume-test-3922807804-33nux_fail(e2180d94-f12e-11e6-bd01-42010af0012c)": timeout expired waitingforvolumes to attach/mountforpod"volume-test-3922807804-33nux"/"fail". list of unattached/unmountedvolumes=[test-volume] 1m 1m 1 {kubelet gke-ctm-1-sysdig2-35e99c16-qwds} Warning FailedSync Error syncing pod, skipping: timeout expired waitingforvolumes to attach/mountforpod"volume-test-3922807804-33nux"/"fail". list of unattached/unmountedvolumes=[test-volume] 3m 50s 3 {controller-manager } Warning FailedMount Failed to attach volume"test-volume"on node"gke-ctm-1-sysdig2-35e99c16-qwds"with: GCE persistent disk not found: diskName="my-data-disk"zone="us-central1-a"

Events模組留有我們一直在尋找的線索。我們的 pod 被正確排程到了一個節點(Successfully assigned volume-test-3922807804-33nux to gke-ctm-1-sysdig2-35e99c16-qwds),但是那個節點上的 kubelet 無法掛載期望的卷test-volume。那個卷本應該在持久化磁碟被關聯到這個節點的時候就被建立了,但是,正如我們看到的,controller-manager 失敗了:Failed to attach volume "test-volume" on node "gke-ctm-1-sysdig2-35e99c16-qwds" with: GCE persistent disk not found: diskName="my-data-disk" zone="us-central1-a"。

最後一條資訊相當清楚了:為了解決這個問題,我們需要在 GKE 的us-central1-a區中建立一個名為my-data-disk的持久化卷。一旦這個磁碟建立完成,controller-manager 將掛載這塊磁碟,並啟動容器建立過程。

9.校驗錯誤
看著整個 build-test-deploy 任務到了 deploy 步驟卻失敗了,原因竟是 Kubernetes 物件不合法。還有什麼比這更讓人沮喪的!

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 你可能之前也碰到過這種錯誤: $ kubectl create -ftest-application.deploy.yaml error: error validating"test-application.deploy.yaml": error validating data: found invalid field resourcesforv1.PodSpec;ifyou choose to ignore these errors, turn validation off with --validate=false 在這個例子中,我嘗試建立以下 deployment: # test-application.deploy.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name:test-app spec: template: metadata: labels: app:test-app spec: containers: - image: nginx name: nginx resources: limits: cpu: 100m memory: 200Mi requests: cpu: 100m memory: 100Mi

一眼望去,這個 YAML 檔案是正確的,但錯誤訊息會證明是有用的。錯誤說的是 found invalid field resources for v1.PodSpec,再仔細看一下 v1.PodSpec, 我們可以看到 resource 物件變成了 v1.PodSpec的一個子物件。事實上它應該是 v1.Container 的子物件。在把 resource 物件縮排一層後,這個 deployment 物件就可以正常工作了。

除了查詢縮排錯誤,另一個常見的錯誤是寫錯了物件名(比如 peristentVolumeClaim 寫成了 persistentVolumeClaim),這樣的錯誤有時會很費你的時間!

為了能在早期就發現這些錯誤,我推薦在 pre-commit 鉤子或者構建的測試階段新增一些校驗步驟。例如,你可以:
1.用 python -c 'import yaml,sys;yaml.safe_load(sys.stdin)' < test-application.deployment.yaml 驗證 YAML 格式
2.使用標識 --dry-run 來驗證 Kubernetes API 物件,比如這樣:kubectl create -f test-application.deploy.yaml --dry-run --validate=true

重要提醒:校驗 Kubernetes 物件的機制是在服務端的校驗,這意味著 kubectl 必須有一個在工作的 Kubernetes 叢集與之通訊。不幸的是,當前 kubectl 還沒有客戶端的校驗選項,但是已經有 issue(kubernetes/kubernetes #29410 和 kubernetes/kubernetes #11488)在跟蹤這個缺失的特性了。

10.容器映象沒有更新
可能使用 Kubernetes 的大多數人都碰到過這個問題,它也確實是一個難題。

這個場景就像下面這樣:
1.使用一個映象 tag(比如:rosskulinski/myapplication:v1) 建立一個 deployment
2.注意到 myapplication 映象中存在一個 bug
3.構建了一個新的映象,並推送到了相同的 tag(rosskukulinski/myapplication:v1)
4.刪除了所有 myapplication 的 pods,新的例項被 deployment 創建出了
5.發現 bug 仍然存在
6.重複 3-5 步直到你抓狂為止

這個問題關係到 Kubernetes 在啟動 pod 內的容器時是如何決策是否做 docker pull 動作的。

在 v1.Container 說明中,有一個選項 ImagePullPolicy:

1 Image pull policy. One of Always, Never, IfNotPresent. Defaults to Alwaysif:latest tag is specified, or IfNotPresent otherwise.

因為我們把我們的映象 tag 標記為 :v1,預設的映象拉取策略是 IfNotPresent。Kubelet 在本地已經有一份 rosskukulinski/myapplication:v1 的拷貝了,因此它就不會在做 docker pull 動作了。當新的 pod 出現的時候,它仍然使用了老的有問題的映象。

有三個方法來解決這個問題:
1.切成 :latest tag(千萬不要這麼做!)
2.deployment 中指定 ImagePullPolicy: Always
3.使用唯一的 tag(比如基於你的程式碼版本控制器的 commit id)

在開發階段或者要快速驗證原型的時候,我會指定 ImagePullPolicy: Always 這樣我可以使用相同的 tag 來構建和推送。然而,在我的產品部署階段,我使用基於 Git SHA-1 的唯一 tag。這樣很容易查到產品部署的應用使用的原始碼。

所以說,當使用kubernetes時,我們有這麼多地方要當心,一般來說,大部分常見的部署失敗都可以用下面的命令定位出來:
1.kubectl describe deployment/<deployname>
2.kubectl describe replicaset/<rsname>
3.kubectl get pods
4.kubectl describe pod/<podname>
5.kubectl logs <podname> --previous

下面是一個bash指令碼,它在 CI/CD 的部署過程中任何失敗的時候,都可以跑。在 Jenkins等的構建輸出中,將顯示有用的 Kubernetes 資訊,幫助開發者快速找到任何明顯的問題。

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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 #!/bin/bash if[ -z"$1"] then echo"ERROR: No deployment specified" exit1 fi DEPLOY=${1} NAMESPACE=${2:=default} printf"\n\nOk - Let's figure out why this deployment might have failed" printf"\n\n------------------------------\n\n" printf"> kubectl describe deployment ${DEPLOY} --namespace=${NAMESPACE}\n\n" kubectl describe deployment ${DEPLOY} --namespace=${NAMESPACE} printf"\n\n------------------------------\n\n" CURRENT_GEN=$(kubectl get deployment ${DEPLOY} --namespace=${NAMESPACE} -o jsonpath='{.metadata.generation}') OBS_GEN=$(kubectl get deployment ${DEPLOY} --namespace=${NAMESPACE} -o jsonpath='{.status.observedGeneration}') REPLICAS=$(kubectl get deployment ${DEPLOY} --namespace=${NAMESPACE} -o jsonpath='{.status.replicas}') UPDATED_REPLICAS=$(kubectl get deployment ${DEPLOY} --namespace=${NAMESPACE} -o jsonpath='{.status.updatedReplicas}') AVAILABLE_REPLICAS=$(kubectl get deployment ${DEPLOY} --namespace=${NAMESPACE} -o jsonpath='{.status.availableReplicas}') if["$AVAILABLE_REPLICAS"=="$REPLICAS"] && \ ["$UPDATED_REPLICAS"=="$REPLICAS"] ;then printf"Available Replicas (${AVAILABLE_REPLICAS}) equals Current Replicas (${REPLICAS}) \n" printf"Updated Replicas (${UPDATED_REPLICAS}) equals Current Replicas (${REPLICAS}). \n" printf"Are you sure the deploy failed?\n\n" exit0 fi if["$AVAILABLE_REPLICAS"!="$REPLICAS"] ;then printf"Available Replicas (${AVAILABLE_REPLICAS}) does not equal Current Replicas (${REPLICAS}) \n" fi if["$UPDATED_REPLICAS"!="$REPLICAS"] ;then printf"Updated Replicas (${UPDATED_REPLICAS}) does not equal Current Replicas (${REPLICAS}) \n" fi printf"\n\n------------------------------\n\n" NEW_RS=$(kubectl describe deploy ${DEPLOY} --namespace=${NAMESPACE} |grep"NewReplicaSet"|awk'{print $2}') POD_HASH=$(kubectl get rs ${NEW_RS} --namespace=${NAMESPACE} -o jsonpath='{.metadata.labels.pod-template-hash}') printf"Pods for this deployment:\n\n" printf"> kubectl get pods --namespace=${NAMESPACE} -l pod-template-hash=${POD_HASH}\n\n" kubectl get pods --namespace=${NAMESPACE} -l pod-template-hash=${POD_HASH} printf"\n\n------------------------------\n\n" printf"Detailed pods for this deployment:\n\n" printf"> kubectl describe pods --namespace=${NAMESPACE} -l pod-template-hash=${POD_HASH}\n\n" kubectl describe pods --namespace=${NAMESPACE} -l pod-template-hash=${POD_HASH} printf"\n\n------------------------------\n\n" printf"Containers that are currently 'waiting':\n\n" printf"> kubectl get pods --namespace=${NAMESPACE} -l pod-template-hash=${POD_HASH} -o jsonpath='...'\n" kubectl get pods --namespace=${NAMESPACE} -l pod-template-hash=${POD_HASH} -o jsonpath='{"\n"}{range .items[*]}{@.metadata.name}:{"\n"}{range @.status.conditions[*]}{"\t"}{@.lastTransitionTime}: {@.type}={@.status}{"\n"}{end}{"\n"}{"\tWaiting Containers\n"}{range @.status.containerStatuses[?(@.state.waiting)]}{"\t\tName: "}{@.name}{"\n\t\tImage: "}{@.image}{"\n\t\tState: Waiting"}{"\n\t\tMessage: "}{@.state.waiting.message}{"\n\t\tReason: "}{@.state.waiting.reason}{end}{"\n"}{end}' printf"\n\n------------------------------\n\n" printf"Pods with Terminated state\n\n" printf"> kubectl get pods --namespace=${NAMESPACE} -l pod-template-hash=${POD_HASH} -o jsonpath='...'\n" kubectl get pods --namespace=${NAMESPACE} -l pod-template-hash=${POD_HASH} -o jsonpath='{"\n"}{range .items[*]}{"\n"}{@.metadata.name}:{"\n"}{"\n\tTerminated Containers\n"}{range @.status.containerStatuses[?(@.lastState.terminated)]}{"\t\tName: "}{@.name}{"\n\t\tImage: "}{@.image}{"\n\t\texitCode: "}{@.lastState.terminated.exitCode}{"\n\t\tReason: "}{@.lastState.terminated.reason}{"\n"}{end}{"\n"}{end}' printf"\n\n------------------------------\n\n" printf"Trying to get previous logs from each Terminated pod\n\n" kubectl get pods --namespace=${NAMESPACE} -l pod-template-hash=${POD_HASH} --no-headers |awk'{print $1}'|xargs-I pod sh -c"printf \"pod\n\n\"; kubectl --namespace=${NAMESPACE} logs --previous --tail=100 --timestamps pod; printf \"\n\n\""