k8s運維筆記


一、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 /1        Running   0          1h
nginx-controller-lf11n    1 /1        Running   0          1h
tomcat-controller-35kzb   1 /1        Running   0          18m
tomcat-controller-lsph4   1 /1        Running   0          18m
    
[root@k8s-master01 ~] # kubectl -s http://k8s-master01:8080 get pod          #將命令中的pod變為pods也是可以的
NAME                      READY     STATUS    RESTARTS   AGE
nginx-controller-d97wj    1 /1        Running   0          1h
nginx-controller-lf11n    1 /1        Running   0          1h
tomcat-controller-35kzb   1 /1        Running   0          18m
tomcat-controller-lsph4   1 /1        Running   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 /TCP           1d
nginx-service-clusterip    172.16.77.193    <none>        8001 /TCP          1h
nginx-service-nodeport     172.16.234.94    <nodes>       8000:32172 /TCP    59m
tomcat-service-clusterip   172.16.144.116   <none>        8801 /TCP          14m
tomcat-service-nodeport    172.16.183.234   <nodes>       8880:31960 /TCP    11m
    
[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 /TCP           1d
nginx-service-clusterip    172.16.77.193    <none>        8001 /TCP          1h
nginx-service-nodeport     172.16.234.94    <nodes>       8000:32172 /TCP    1h
tomcat-service-clusterip   172.16.144.116   <none>        8801 /TCP          17m
tomcat-service-nodeport    172.16.183.234   <nodes>       8880:31960 /TCP    14m
    
或者  (后面的 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-controller     2         2         2         2h
rc /tomcat-controller    2         2         2         1h
    
NAME             STATUS    AGE
ns /default        Active    1d
ns /kube-system    Active    1d
    
6) 查看pod和svc(和service一樣)
[root@k8s-master01 ~] # kubectl get pods,svc
NAME                         READY     STATUS    RESTARTS   AGE
po /nginx-controller-d97wj     1 /1        Running   0          2h
po /nginx-controller-lf11n     1 /1        Running   0          2h
po /tomcat-controller-35kzb    1 /1        Running   0          1h
po /tomcat-controller-lsph4    1 /1        Running   0          1h
    
NAME                           CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
svc /kubernetes                  172.16.0.1       <none>        443 /TCP           1d
svc /nginx-service-clusterip     172.16.77.193    <none>        8001 /TCP          2h
svc /nginx-service-nodeport      172.16.234.94    <nodes>       8000:32172 /TCP    2h
svc /tomcat-service-clusterip    172.16.144.116   <none>        8801 /TCP          1h
svc /tomcat-service-nodeport     172.16.183.234   <nodes>       8880:31960 /TCP    1h
    
7) 以jison格式輸出pod的詳細信息.
[root@k8s-master01 ~] # kubectl get pods
NAME                      READY     STATUS    RESTARTS   AGE
nginx-controller-d97wj    1 /1        Running   0          2h
nginx-controller-lf11n    1 /1        Running   0          2h
tomcat-controller-35kzb   1 /1        Running   0          1h
tomcat-controller-lsph4   1 /1        Running   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 /1        Running   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使用 source  control的方式維護在版本庫中。
每次有更新時,將配置文件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
etcdctl  set  /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 |  grep  dashboard
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           #動態伸縮
kubectl  exec  -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
  
kubectl  set  image deployment /nginx-deployment  nginx=nginx:1.11      #滾動更新鏡像
或者
kubectl edit deployment /nginx-deployment
或者
kubectl apply -f nginx-deployment.yaml                              #也表示對yaml修改后進行更新操作,更新到kubernetes集群配置中
  
kubectl rollout status deployment /nginx-deployment                  #實時觀察發布狀態:
  
kubectl rollout  history  deployment /nginx-deployment                 #查看deployment歷史修訂版本
kubectl rollout  history  deployment /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 /1      Running   0          5h9m    172.30.104.6   m7-autocv-gpu03   <none>
elasticsearch-logging-1                  1 /1      Running   0          4h59m   172.30.232.8   m7-autocv-gpu02   <none>
fluentd-es-v2.2.0-mkkcf                  1 /1      Running   0          5h9m    172.30.104.7   m7-autocv-gpu03   <none>
kibana-logging-f6fc77549-nlxfg           1 /1      Running   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-d47nc  replaced
   
[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 /1        ImagePullBackOff   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 /dne  not 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 /dne  not 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 /1        CrashLoopBackOff   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 /1        RunContainerError   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 /1        ContainerCreating   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 failed  for  volume  "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 /1        Running   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 docker  id  0fb5f1a56ea0; Security:[seccomp=unconfined]
8m        8m      1   {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm}   spec.containers{ test -container} Normal      Started     Started container with docker  id  0fb5f1a56ea0
7m        7m      1   {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm}   spec.containers{ test -container} Normal      Created     Created container with docker  id  3f2392e9ead9; Security:[seccomp=unconfined]
7m        7m      1   {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm}   spec.containers{ test -container} Normal      Killing     Killing container with docker  id  0fb5f1a56ea0: 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 /1  replicas created)
Events:
FirstSeen    LastSeen    Count   From                SubObjectPath   Type        Reason          Message
---------    --------    -----   ----                -------------   --------    ------          -------
4m        4m      1   {deployment-controller }            Normal      ScalingReplicaSet   Scaled up replica  set  gateway-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 -f  test - quota .yaml 創建,然后觀察我們的 Pods:
$ kubectl get pods
NAME                            READY     STATUS    RESTARTS   AGE
gateway- quota -551394438-pix5d   1 /1        Running   0          16s
 
看起來很好,現在讓我們擴展到 3 個副本:kubectl scale deploy /gateway-quota  --replicas=3,然后再次觀察 Pods:
$ kubectl get pods
NAME                            READY     STATUS    RESTARTS   AGE
gateway- quota -551394438-pix5d   1 /1        Running   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 /3  replicas created)
Events:
FirstSeen    LastSeen    Count   From                SubObjectPath   Type        Reason          Message
---------    --------    -----   ----                -------------   --------    ------          -------
9m        9m      1   {deployment-controller }            Normal      ScalingReplicaSet   Scaled up replica  set  gateway- quota -551394438 to 1
5m        5m      1   {deployment-controller }            Normal      ScalingReplicaSet   Scaled up replica  set  gateway- 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: exceeded  quota : 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 /1        Running   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 /1        Pending   0          4m
cpu-scale-908056305-xstti   1 /1        Running   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 fit  in  any 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 /1        ContainerCreating   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 resource  in  Google 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 to  mount  volumes  for  pod  "volume-test-3922807804-33nux_fail(e2180d94-f12e-11e6-bd01-42010af0012c)" : timeout expired waiting  for  volumes to attach /mount  for  pod  "volume-test-3922807804-33nux" / "fail" . list of unattached /unmounted  volumes=[ test -volume]
1m        1m      1   {kubelet gke-ctm-1-sysdig2-35e99c16-qwds}           Warning     FailedSync  Error syncing pod, skipping: timeout expired waiting  for  volumes to attach /mount  for  pod  "volume-test-3922807804-33nux" / "fail" . list of unattached /unmounted  volumes=[ 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 -f  test -application.deploy.yaml
error: error validating  "test-application.deploy.yaml" : error validating data: found invalid field resources  for  v1.PodSpec;  if  you 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 Always  if  :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"
   exit  1
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"
   exit  0
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\""
*************** 當你發現自己的才華撐不起野心時,就請安靜下來學習吧!***************


免責聲明!

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



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