一、什么是Pod
kubernetes中的一切都可以理解為是一種資源對象,pod,rc,service,都可以理解是 一種資源對象。pod的組成示意圖如下,由一個叫”pause“的根容器,加上一個或多個用戶自定義的容器構造。pause的狀態帶便了這一組容器的狀態,pod里多個業務容器共享pod的Ip和數據卷。在kubernetes環境下,pod是容器的載體,所有的容器都是在pod中被管理,一個或多個容器放在pod里作為一個單元方便管理。
pod是kubernetes可以部署和管理的最小單元,如果想要運行一個容器,先要為這個容器創建一個pod。同時一個pod也可以包含多個容器,之所以多個容器包含在一個pod里,往往是由於業務上的緊密耦合。【需要注意】這里說的場景都非必須把不同的容器放在同一個pod里,但是這樣往往更便於管理,甚至后面會講到的,緊密耦合的業務容器放置在同一個容器里通信效率更高。具體怎么使用還要看實際情況,綜合權衡。
在Kubrenetes集群中Pod有如下兩種使用方式:
a)一個Pod中運行一個容器。這是最常見用法。在這種方式中,你可以把Pod想象成是單個容器的封裝,kuberentes管理的是Pod而不是直接管理容器。
b)在一個Pod中同時運行多個容器。當多個應用之間是緊耦合的關系時,可以將多個應用一起放在一個Pod中,同個Pod中的多個容器之間互相訪問可以通過localhost來通信(可以把Pod理解成一個虛擬機,共享網絡和存儲卷)。也就是說一個Pod中也可以同時封裝幾個需要緊密耦合互相協作的容器,它們之間共享資源。這些在同一個Pod中的容器可以互相協作成為一個service單位 (即一個容器共享文件),另一個“sidecar”容器來更新這些文件。Pod將這些容器的存儲資源作為一個實體來管理。
就像每個應用容器,pod被認為是臨時實體。在Pod的生命周期中,pod被創建后,被分配一個唯一的ID(UID),調度到節點上,並一致維持期望的狀態直到被終結(根據重啟策略)或者被刪除。如果node死掉了,分配到了這個node上的pod,在經過一個超時時間后會被重新調度到其他node節點上。一個給定的pod(如UID定義的)不會被“重新調度”到新的節點上,而是被一個同樣的pod取代,如果期望的話甚至可以是相同的名字,但是會有一個新的UID(查看replication controller獲取詳情)。
kubernetes為什么使用pod作為最小單元,而不是container
直接部署一個容器看起來更簡單,但是這里也有更好的原因為什么在容器基礎上抽象一層呢?根本原因是為了管理容器,kubernetes需要更多的信息,比如重啟策略,它定義了容器終止后要采取的策略;或者是一個可用性探針,從應用程序的角度去探測是否一個進程還存活着。基於這些原因,kubernetes架構師決定使用一個新的實體,也就是pod,而不是重載容器的信息添加更多屬性,用來在邏輯上包裝一個或者多個容器的管理所需要的信息。
kubernetes為什么允許一個pod里有多個容器
pod里的容器運行在一個邏輯上的"主機"上,它們使用相同的網絡名稱空間 (即同一pod里的容器使用相同的ip和相同的端口段區間) 和相同的IPC名稱空間。它們也可以共享存儲卷。這些特性使它們可以更有效的通信,並且pod可以使你把緊密耦合的應用容器作為一個單元來管理。也就是說當多個應用之間是緊耦合關系時,可以將多個應用一起放在一個Pod中,同個Pod中的多個容器之間互相訪問可以通過localhost來通信(可以把Pod理解成一個虛擬機,共享網絡和存儲卷)。
因此當一個應用如果需要多個運行在同一主機上的容器時,為什么不把它們放在同一個容器里呢?首先,這樣何故違反了一個容器只負責一個應用的原則。這點非常重要,如果我們把多個應用放在同一個容器里,這將使解決問題變得非常麻煩,因為它們的日志記錄混合在了一起,並且它們的生命周期也很難管理。因此一個應用使用多個容器將更簡單,更透明,並且使應用依賴解偶。並且粒度更小的容器更便於不同的開發團隊共享和復用。
【需要注意】這里說到為了解偶把應用分別放在不同容器里,前面我們也強調為了便於管理管緊耦合的應用把它們的容器放在同一個pod里。一會強調耦合,一個強調解偶看似矛盾,實際上普遍存在,高內聚低耦合是我們的追求,然而一個應用的業務邏輯模塊不可能完全完獨立不存在耦合,這就需要我們從實際上來考量,做出決策。
因為,雖然可以使用一個pod來承載一個多層應用,但是更建議使用不同的pod來承載不同的層,因這這樣你可以為每一個層單獨擴容並且把它們分布到集群的不同節點上。
Pod中如何管理多個容器
Pod中可以同時運行多個進程(作為容器運行)協同工作,同一個Pod中的容器會自動的分配到同一個 node 上,同一個Pod中的容器共享資源、網絡環境和依賴,它們總是被同時調度。需要注意:一個Pod中同時運行多個容器是一種比較高級的用法。只有當你的容器需要緊密配合協作的時候才考慮用這種模式。
Pod中共享的環境包括Linux的namespace,cgroup和其他可能的隔絕環境,這一點跟Docker容器一致。在Pod的環境中,每個容器中可能還有更小的子隔離環境。Pod中的容器共享IP地址和端口號,它們之間可以通過localhost互相發現。它們之間可以通過進程間通信,需要明白的是同一個Pod下的容器是通過lo網卡進行通信。例如SystemV信號或者POSIX共享內存。不同Pod之間的容器具有不同的IP地址,不能直接通過IPC通信。Pod中的容器也有訪問共享volume的權限,這些volume會被定義成pod的一部分並掛載到應用容器的文件系統中。
總而言之。Pod中可以共享兩種資源:網絡 和 存儲
1. 網絡:每個Pod都會被分配一個唯一的IP地址。Pod中的所有容器共享網絡空間,包括IP地址和端口。Pod內部的容器可以使用localhost互相通信。Pod中的容器與外界通信時,必須分配共享網絡資源(例如使用宿主機的端口映射)。
2. 存儲:可以Pod指定多個共享的Volume。Pod中的所有容器都可以訪問共享的volume。Volume也可以用來持久化Pod中的存儲資源,以防容器重啟后文件丟失。
容器的依賴關系和啟動順序
當前,同一個pod里的所有容器都是並行啟動並且沒有辦法確定哪一個容器必須早於哪一個容器啟動。如果要想確保第一個容器早於第二個容器啟動,那么就要使用到"init container"了。
同一pod的容器間網絡通信
同一pod下的容器使用相同的網絡名稱空間,這就意味着他們可以通過"localhost"來進行通信,它們共享同一個Ip和相同的端口空間。
同一個pod暴露多個容器
通常pod里的容器監聽不同的端口,想要被外部訪問都需要暴露出去.你可以通過在一個服務里暴露多個端口或者使用不同的服務來暴露不同的端口來實現。
二、如何使用Pod
通常把Pod分為兩類:
- 自主式Pod :這種Pod本身是不能自我修復的,當Pod被創建后(不論是由你直接創建還是被其他Controller),都會被Kuberentes調度到集群的Node上。直到Pod的進程終止、被刪掉、因為缺少資源而被驅逐、或者Node故障之前這個Pod都會一直保持在那個Node上。Pod不會自愈。如果Pod運行的Node故障,或者是調度器本身故障,這個Pod就會被刪除。同樣的,如果Pod所在Node缺少資源或者Pod處於維護狀態,Pod也會被驅逐。
- 控制器管理的Pod:Kubernetes使用更高級的稱為Controller的抽象層,來管理Pod實例。Controller可以創建和管理多個Pod,提供副本管理、滾動升級和集群級別的自愈能力。例如,如果一個Node故障,Controller就能自動將該節點上的Pod調度到其他健康的Node上。雖然可以直接使用Pod,但是在Kubernetes中通常是使用Controller來管理Pod的。如下圖:
每個Pod都有一個特殊的被稱為"根容器"的Pause 容器。 Pause容器對應的鏡像屬於Kubernetes平台的一部分,除了Pause容器,每個Pod還包含一個或者多個緊密相關的用戶業務容器。
Kubernetes設計這樣的Pod概念和特殊組成結構有什么用意呢?
原因一:在一組容器作為一個單元的情況下,難以對整體的容器簡單地進行判斷及有效地進行行動。比如一個容器死亡了,此時是算整體掛了么?那么引入與業務無關的Pause容器作為Pod的根容器,以它的狀態代表着整個容器組的狀態,這樣就可以解決該問題。
原因二:Pod里的多個業務容器共享Pause容器的IP,共享Pause容器掛載的Volume,這樣簡化了業務容器之間的通信問題,也解決了容器之間的文件共享問題。
1. Pod的持久性和終止
- Pod的持久性
Pod在設計上就不是作為持久化實體的。在調度失敗、節點故障、缺少資源或者節點維護的狀態下都會死掉會被驅逐。通常,用戶不需要手動直接創建Pod,而是應該使用controller(例如Deployments),即使是在創建單個Pod的情況下。Controller可以提供集群級別的自愈功能、復制和升級管理。
- Pod的終止
因為Pod作為在集群的節點上運行的進程,所以在不再需要的時候能夠優雅的終止掉是十分必要的(比起使用發送KILL信號這種暴力的方式)。用戶需要能夠放松刪除請求,並且知道它們何時會被終止,是否被正確的刪除。用戶想終止程序時發送刪除pod的請求,在pod可以被強制刪除前會有一個寬限期,會發送一個TERM請求到每個容器的主進程。一旦超時,將向主進程發送KILL信號並從API server中刪除。如果kubelet或者container manager在等待進程終止的過程中重啟,在重啟后仍然會重試完整的寬限期。
示例流程如下:
- 用戶發送刪除pod的命令,默認寬限期是30秒;
- 在Pod超過該寬限期后API server就會更新Pod的狀態為"dead";
- 在客戶端命令行上顯示的Pod狀態為"terminating";
- 跟第三步同時,當kubelet發現pod被標記為"terminating"狀態時,開始停止pod進程:
1. 如果在pod中定義了preStop hook,在停止pod前會被調用。如果在寬限期過后,preStop hook依然在運行,第二步會再增加2秒的寬限期;
2. 向Pod中的進程發送TERM信號;
- 跟第三步同時,該Pod將從該service的端點列表中刪除,不再是replication controller的一部分。關閉的慢的pod將繼續處理load balancer轉發的流量;
- 過了寬限期后,將向Pod中依然運行的進程發送SIGKILL信號而殺掉進程。
- Kublete會在API server中完成Pod的的刪除,通過將優雅周期設置為0(立即刪除)。Pod在API中消失,並且在客戶端也不可見。
刪除寬限期默認是30秒。 kubectl delete命令支持 --grace-period=<seconds> 選項,允許用戶設置自己的寬限期。如果設置為0將強制刪除pod。在kubectl>=1.5版本的命令中,你必須同時使用 --force 和 --grace-period=0 來強制刪除pod。
Pod的強制刪除是通過在集群和etcd中將其定義為刪除狀態。當執行強制刪除命令時,API server不會等待該pod所運行在節點上的kubelet確認,就會立即將該pod從API server中移除,這時就可以創建跟原pod同名的pod了。這時,在節點上的pod會被立即設置為terminating狀態,不過在被強制刪除之前依然有一小段優雅刪除周期。【需要注意:如果刪除一個pod后,再次查看發現pod還在,這是因為在deployment.yaml文件中定義了副本數量!還需要刪除deployment才行。即:"kubectl delete pod pod-name -n namespace" && "kubectl delete deployment deployment-name -n namespace"】
2. Pause容器
Pause容器,又叫Infra容器。我們檢查node節點的時候會發現每個node節點上都運行了很多的pause容器,例如如下:
1
2
3
4
5
6
7
|
[root@k8s-node01 ~]
# docker ps |grep pause
0cbf85d4af9e k8s.gcr.io
/pause
:3.1
"/pause"
7 days ago Up 7 days k8s_POD_myapp-848b5b879b-ksgnv_default_0af41a40-a771-11e8-84d2-000c2972dc1f_0
d6e4d77960a7 k8s.gcr.io
/pause
:3.1
"/pause"
7 days ago Up 7 days k8s_POD_myapp-848b5b879b-5f69p_default_09bc0ba1-a771-11e8-84d2-000c2972dc1f_0
5f7777c55d2a k8s.gcr.io
/pause
:3.1
"/pause"
7 days ago Up 7 days k8s_POD_kube-flannel-ds-pgpr7_kube-system_23dc27e3-a5af-11e8-84d2-000c2972dc1f_1
8e56ef2564c2 k8s.gcr.io
/pause
:3.1
"/pause"
7 days ago Up 7 days k8s_POD_client2_default_17dad486-a769-11e8-84d2-000c2972dc1f_1
7815c0d69e99 k8s.gcr.io
/pause
:3.1
"/pause"
7 days ago Up 7 days k8s_POD_nginx-deploy-5b595999-872c7_default_7e9df9f3-a6b6-11e8-84d2-000c2972dc1f_2
b4e806fa7083 k8s.gcr.io
/pause
:3.1
"/pause"
7 days ago Up 7 days k8s_POD_kube-proxy-vxckf_kube-system_23dc0141-a5af-11e8-84d2-000c2972dc1f_2
|
kubernetes中的pause容器主要為每個業務容器提供以下功能:
- 在pod中擔任Linux命名空間共享的基礎;
- 啟用pid命名空間,開啟init進程;
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
|
示例如下:
[root@k8s-node01 ~]
# docker run -d --name pause -p 8880:80 k8s.gcr.io/pause:3.1
[root@k8s-node01 ~]
# docker run -d --name nginx -v `pwd`/nginx.conf:/etc/nginx/nginx.conf --net=container:pause --ipc=container:pause --pid=container:pause nginx
[root@k8s-node01 ~]
# docker run -d --name ghost --net=container:pause --ipc=container:pause --pid=container:pause ghost
現在訪問http:
//
****:8880/就可以看到ghost博客的界面了。
解析說明:
pause容器將內部的80端口映射到宿主機的8880端口,pause容器在宿主機上設置好了網絡namespace后,nginx容器加入到該網絡namespace中,我們看到nginx容器啟動的時候指定了
--net=container:pause,ghost容器同樣加入到了該網絡namespace中,這樣三個容器就共享了網絡,互相之間就可以使用localhost直接通信,
--ipc=contianer:pause --pid=container:pause就是三個容器處於同一個namespace中,init進程為pause
這時我們進入到ghost容器中查看進程情況。
[root@k8s-node01 ~]
# docker exec -it ghost /bin/bash
root@d3057ceb54bc:
/var/lib/ghost
# ps axu
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 1012 4 ? Ss 03:48 0:00
/pause
root 6 0.0 0.0 32472 780 ? Ss 03:53 0:00 nginx: master process nginx -g daemon off;
systemd+ 11 0.0 0.1 32932 1700 ? S 03:53 0:00 nginx: worker process
node 12 0.4 7.5 1259816 74868 ? Ssl 04:00 0:07 node current
/index
.js
root 77 0.6 0.1 20240 1896 pts
/0
Ss 04:29 0:00
/bin/bash
root 82 0.0 0.1 17496 1156 pts
/0
R+ 04:29 0:00
ps
axu
在ghost容器中同時可以看到pause和nginx容器的進程,並且pause容器的PID是1。而在kubernetes中容器的PID=1的進程即為容器本身的業務進程。
|
3. Init容器
Pod 能夠具有多個容器,應用運行在容器里面,但是它也可能有一個或多個先於應用容器啟動的 Init 容器。init容器是一種專用的容器,在應用容器啟動之前運行,可以包含普通容器映像中不存在的應用程序或安裝腳本。init容器會優先啟動,待里面的任務完成后容器就會退出。 init容器配置示例如下:
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
|
apiVersion: v1
kind: Pod
metadata:
name: init-demo
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: workdir
mountPath:
/usr/share/nginx/html
# These containers are run during pod initialization
initContainers:
- name:
install
image: busybox
command
:
- wget
-
"-O"
-
"/work-dir/index.html"
- http:
//kubernetes
.io
volumeMounts:
- name: workdir
mountPath:
"/work-dir"
dnsPolicy: Default
volumes:
- name: workdir
emptyDir: {}
|
1. 理解init容器
- 它們總是運行到完成。
- 每個都必須在下一個啟動之前成功完成。
- 如果 Pod 的 Init 容器失敗,Kubernetes 會不斷地重啟該 Pod,直到 Init 容器成功為止。然而,如果 Pod 對應的 restartPolicy 為 Never,它不會重新啟動。
- Init 容器支持應用容器的全部字段和特性,但不支持 Readiness Probe,因為它們必須在 Pod 就緒之前運行完成。
- 如果為一個 Pod 指定了多個 Init 容器,那些容器會按順序一次運行一個。 每個 Init 容器必須運行成功,下一個才能夠運行。
- 因為 Init 容器可能會被重啟、重試或者重新執行,所以 Init 容器的代碼應該是冪等的。 特別地,被寫到 EmptyDirs 中文件的代碼,應該對輸出文件可能已經存在做好准備。
- 在 Pod 上使用 activeDeadlineSeconds,在容器上使用 livenessProbe,這樣能夠避免 Init 容器一直失敗。 這就為 Init 容器活躍設置了一個期限。
- 在 Pod 中的每個 app 和 Init 容器的名稱必須唯一;與任何其它容器共享同一個名稱,會在驗證時拋出錯誤。
- 對 Init 容器 spec 的修改,被限制在容器 image 字段中。 更改 Init 容器的 image 字段,等價於重啟該 Pod。
一個pod可以包含多個普通容器和多個init容器,在Pod中所有容器的名稱必須唯一,init容器在普通容器啟動前順序執行,如果init容器失敗,則認為pod失敗,K8S會根據pod的重啟策略來重啟這個容器,直到成功。
Init容器需要在pod.spec中的initContainers數組中定義(與3pod.spec.containers數組相似)。init容器的狀態在.status.initcontainerStatus字段中作為容器狀態的數組返回(與status.containerStatus字段類似)。init容器支持普通容器的所有字段和功能,除了readinessprobe。Init 容器只能修改image 字段,修改image 字段等於重啟 Pod,Pod 重啟所有Init 容器必須重新執行。
如果Pod的Init容器失敗,Kubernetes會不斷地重啟該Pod,直到Init容器成功為止。然而如果Pod對應的restartPolicy為Never,則它不會重新啟動。所以在Pod上使用activeDeadlineSeconds,在容器上使用livenessProbe,相當於為Init容器活躍設置了一個期限,能夠避免Init容器一直失敗。
2. Init容器與普通容器的不同之處
Init 容器與普通的容器非常像,除了如下兩點:
- Init 容器總是運行到成功完成為止。
- 每個 Init 容器都必須在下一個 Init 容器啟動之前成功完成。
Init 容器支持應用容器的全部字段和特性,包括資源限制、數據卷和安全設置。 然而,Init 容器對資源請求和限制的處理稍有不同, 而且 Init 容器不支持 Readiness Probe,因為它們必須在 Pod 就緒之前運行完成。如果為一個 Pod 指定了多個 Init 容器,那些容器會按順序一次運行一個。 每個 Init 容器必須運行成功,下一個才能夠運行。 當所有的 Init 容器運行完成時,Kubernetes 初始化 Pod 並像平常一樣運行應用容器。
3. Init 容器能做什么
因為 Init 容器具有與應用容器分離的單獨鏡像,它們的啟動相關代碼具有如下優勢:
- 它們可以包含並運行實用工具,處於安全考慮,是不建議在應用容器鏡像中包含這些實用工具的。
- 它們可以包含實用工具和定制化代碼來安裝,但不能出現在應用鏡像中。例如創建鏡像沒必要FROM另一個鏡像,只需要在安裝中使用類似sed,awk、 python 或dig這樣的工具。
- 應用鏡像可以分離出創建和部署的角色,而沒有必要聯合它們構建一個單獨的鏡像。
- 它們使用 Linux Namespace,所以對應用容器具有不同的文件系統視圖。因此,它們能夠具有訪問 Secret 的權限,而應用容器不能夠訪問。
- 它們在應用容器啟動之前運行完成,然而應用容器並行運行,所以 Init 容器提供了一種簡單的方式來阻塞或延遲應用容器的啟動,直到滿足了一組先決條件。
4. 靜態pod
靜態Pod是由kubelet進行管理,僅存在於特定Node上的Pod。它們不能通過API Server進行管理,無法與ReplicationController、Deployment或DaemonSet進行關聯,並且kubelet也無法對其健康檢查。靜態Pod總是由kubelet創建,並且總在kubelet所在的Node上運行。創建靜態Pod的方式:使用配置文件方式 或 HTTP方式。一般常使用的是配置文件方式。
- 通過配置文件創建
配置文件只是特定目錄中json或yaml格式的標准pod定義。它通過在kubelet守護進程中添加配置參數--pod-manifest-path=<the directory> 來運行靜態Pod,kubelet經常會它定期掃描目錄;例如,如何將一個簡單web服務作為靜態pod啟動?
選擇運行靜態pod的節點服務器,不一定是node節點,只要有kubelet進程所在的節點都可以運行靜態pod。可以在某個節點上創建一個放置一個Web服務器pod定義的描述文件文件夾,例如/etc/kubelet.d/static-web.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
# mkdir /etc/kubelet.d/
# vim /etc/kubelet.d/static-web.yaml
apiVersion: v1
kind: Pod
metadata:
name: static-web
labels:
role: myrole
spec:
containers:
- name: web
image: nginx
ports:
- name: web
containerPort: 80
protocol: TCP<br>
#ls /etc/kubelet.d/
static-web.yaml
|
通過使用--pod-manifest-path=/etc/kubelet.d/參數運行它,在節點上配置我的kubelet守護程序以使用此目錄。比如這里kubelet啟動參數位/etc/systemd/system/kubelet.service.d/10-kubelet.conf, 修改配置,然后將參數加入到現有參數配置項中(安裝方式不盡相同,但是道理一樣)。
1
2
3
4
5
6
|
# vim /etc/systemd/system/kubelet.service.d/10-kubelet.conf
······
······
Environment=
"KUBELET_EXTRA_ARGS=--cluster-dns=10.96.0.10 --cluster-domain=cluster.local --pod-manifest-path=/etc/kubelet.d/"
······
······
|
保存退出,reload一下systemd daeomon ,重啟kubelet服務進程
1
2
|
#systemctl daemon-reload
# systemctl restart kubelet
|
前面說了,當kubelet啟動時,它會自動啟動在指定的目錄–pod-manifest-path=或–manifest-url=參數中定義的所有pod ,即我們的static-web。接着在該節點上檢查是否創建成功:
1
2
3
|
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
static-web-k8s-m1 1
/1
Running 0 2m 10.244.2.32 k8s-m1
|
上面也提到了,它不歸任何部署方式來管理,即使我們嘗試kubelet命令去刪除
1
2
3
4
5
6
|
# kubectl delete pod static-web-k8s-m1
pod
"static-web-k8s-m1"
deleted
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
static-web-k8s-m1 0
/1
Pending 0 2s <none> k8s-m1 <none>
|
可以看出靜態pod通過這種方式是沒法刪除的
那我如何去刪除或者說是動態的添加一個pod呢?這種機制已經知道,kubelet進程會定期掃描配置的目錄(/etc/kubelet.d在我的示例)以進行更改,並在文件出現/消失在此目錄中時添加/刪除pod。
5. Pod容器共享Volume
同一個Pod中的多個容器可以共享Pod級別的存儲卷Volume,Volume可以定義為各種類型,多個容器各自進行掛載,將Pod的Volume掛載為容器內部需要的目錄。例如:Pod級別的Volume:"app-logs",用於tomcat向其中寫日志文件,busybox讀日志文件。
pod-volumes-applogs.yaml文件的配置內容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
apiVersion: v1
kind: Pod
metadata:
name: volume-pod
spec:
containers:
- name: tomcat
image: tomcat
ports:
- containerPort: 8080
volumeMounts:
- name: app-logs
mountPath:
/usr/local/tomcat/logs
- name: busybox
image: busybox
command
: [
"sh"
,
"-c"
,
"tailf /logs/catalina*.log"
]
volumeMounts:
- name: app-logs
mountPath:
/logs
volumes:
- name: app-logs
emptuDir: {}
|
查看日志
# kubectl logs <pod_name> -c <container_name>
# kubectl exec -it <pod_name> -c <container_name> – tail /usr/local/tomcat/logs/catalina.xx.log
6. Pod的配置管理
Kubernetes v1.2的版本提供統一的集群配置管理方案 – ConfigMap:容器應用的配置管理
ConfigMap使用場景:
- 生成為容器內的環境變量。
- 設置容器啟動命令的啟動參數(需設置為環境變量)。
- 以Volume的形式掛載為容器內部的文件或目錄。
ConfigMap以一個或多個key:value的形式保存在kubernetes系統中供應用使用,既可以表示一個變量的值(例如:apploglevel=info),也可以表示完整配置文件的內容(例如:server.xml=<?xml…>…)。可以通過yaml配置文件或者使用kubectl create configmap命令的方式創建ConfigMap。
3.1)創建ConfigMap
通過yaml文件方式
cm-appvars.yaml
1
2
3
4
5
6
7
|
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-appvars
data:
apploglevel: info
appdatadir:
/var/data
|
常用命令
# kubectl create -f cm-appvars.yaml
# kubectl get configmap
# kubectl describe configmap cm-appvars
# kubectl get configmap cm-appvars -o yaml
通過kubectl命令行方式
通過kubectl create configmap創建,使用參數–from-file或–from-literal指定內容,可以在一行中指定多個參數。
1
2
3
4
5
6
7
8
|
1)通過–from-
file
參數從文件中進行創建,可以指定key的名稱,也可以在一個命令行中創建包含多個key的ConfigMap。
# kubectl create configmap NAME --from-file=[key=]source --from-file=[key=]source
2)通過–from-
file
參數從目錄中進行創建,該目錄下的每個配置文件名被設置為key,文件內容被設置為value。
# kubectl create configmap NAME --from-file=config-files-dir
3)通過–from-literal從文本中進行創建,直接將指定的key=value創建為ConfigMap的內容。
# kubectl create configmap NAME --from-literal=key1=value1 --from-literal=key2=value2
|
容器應用對ConfigMap的使用有兩種方法:
- 通過環境變量獲取ConfigMap中的內容。
- 通過Volume掛載的方式將ConfigMap中的內容掛載為容器內部的文件或目錄。
通過環境變量的方式
ConfigMap的yaml文件: cm-appvars.yaml
1
2
3
4
5
6
7
|
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-appvars
data:
apploglevel: info
appdatadir:
/var/data
|
Pod的yaml文件:cm-test-pod.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
apiVersion: v1
kind: Pod
metadata:
name: cm-
test
-pod
spec:
containers:
- name: cm-
test
image: busybox
command
: [
"/bin/sh"
,
"-c"
,
"env|grep APP"
]
env
:
- name: APPLOGLEVEL
valueFrom:
configMapKeyRef:
name: cm-appvars
key: apploglevel
- name: APPDATADIR
valueFrom:
configMapKeyRef:
name: cm-appvars
key: appdatadir
|
創建命令:
# kubectl create -f cm-test-pod.yaml
# kubectl get pods --show-all
# kubectl logs cm-test-pod
使用ConfigMap的限制條件
- ConfigMap必須在Pod之前創建
- ConfigMap也可以定義為屬於某個Namespace。只有處於相同Namespace中的Pod可以引用它。
- kubelet只支持可以被API Server管理的Pod使用ConfigMap。靜態Pod無法引用。
- 在Pod對ConfigMap進行掛載操作時,容器內只能掛載為“目錄”,無法掛載為文件。
7. Pod的生命周期
- Pod的狀態
pod從創建到最后的創建成功會分別處於不同的階段,下面是Pod的生命周期示意圖,從圖中可以看到Pod狀態的變化:
掛起或等待中 (Pending):API Server創建了Pod資源對象並已經存入了etcd中,但是它並未被調度完成,或者仍然處於從倉庫下載鏡像的過程中。這時候Pod 已被 Kubernetes 系統接受,但有一個或者多個容器鏡像尚未創建。等待時間包括調度 Pod 的時間和通過網絡下載鏡像的時間,這可能需要花點時間。創建pod的請求已經被k8s接受,但是容器並沒有啟動成功,可能處在:寫數據到etcd,調度,pull鏡像,啟動容器這四個階段中的任何一個階段,pending伴隨的事件通常會有:ADDED, Modified這兩個事件的產生。
運行中 (Running):該 Pod 已經被調度到了一個node節點上,Pod 中所有的容器都已被kubelet創建完成。至少有一個容器正在運行,或者正處於啟動或重啟狀態。
正常終止 (Succeeded):pod中的所有的容器已經正常的自行退出,並且k8s永遠不會自動重啟這些容器,一般會是在部署job的時候會出現。
異常停止 (Failed):Pod 中的所有容器都已終止了,並且至少有一個容器是因為失敗終止。也就是說,容器以非0狀態退出或者被系統終止。
未知狀態 (Unkonwn):出於某種原因,無法獲得Pod的狀態,通常是由於與Pod主機通信時出錯。
- Pod的創建過程
Pod是Kubernetes的基礎單元,了解其創建的過程,更有助於理解系統的運作。創建Pod的整個流程的時序圖如下:
① 用戶通過kubectl客戶端提交Pod Spec給API Server。
② API Server嘗試將Pod對象的相關信息存儲到etcd中,等待寫入操作完成,API Server返回確認信息到客戶端。
③ API Server開始反映etcd中的狀態變化。
④ 所有的Kubernetes組件通過"watch"機制跟蹤檢查API Server上的相關信息變動。
⑤ kube-scheduler(調度器)通過其"watcher"檢測到API Server創建了新的Pod對象但是沒有綁定到任何工作節點。
⑥ kube-scheduler為Pod對象挑選一個工作節點並將結果信息更新到API Server。
⑦ 調度結果新消息由API Server更新到etcd,並且API Server也開始反饋該Pod對象的調度結果。
⑧ Pod被調度到目標工作節點上的kubelet嘗試在當前節點上調用docker engine進行啟動容器,並將容器的狀態結果返回到API Server。
⑨ API Server將Pod信息存儲到etcd系統中。
⑩ 在etcd確認寫入操作完成,API Server將確認信息發送到相關的kubelet。
Pod常規的排查:見這里
一個pod的完整創建,通常會伴隨着各種事件的產生,kubernetes事件的種類總共只有4種:
Added EventType = "ADDED"
Modified EventType = "MODIFIED"
Deleted EventType = "DELETED"
Error EventType = "ERROR"
PodStatus 有一組PodConditions。 PodCondition中的ConditionStatus,它代表了當前pod是否處於某一個階段(PodScheduled,Ready,Initialized,Unschedulable),"true" 表示處於,"false"表示不處於。PodCondition數組的每個元素都有一個類型字段和一個狀態字段。
類型字段 PodConditionType 是一個字符串,可能的值是:
PodScheduled:pod正處於調度中,剛開始調度的時候,hostip還沒綁定上,持續調度之后,有合適的節點就會綁定hostip,然后更新etcd數據
Ready: pod 已經可以開始服務,譬如被加到負載均衡里面
Initialized:所有pod 中的初始化容器已經完成了
Unschedulable:限制不能被調度,譬如現在資源不足
狀態字段 ConditionStatus 是一個字符串,可能的值為True,False和Unknown
Pod的ERROR事件的情況大概有:
CrashLoopBackOff: 容器退出,kubelet正在將它重啟
InvalidImageName: 無法解析鏡像名稱
ImageInspectError: 無法校驗鏡像
ErrImageNeverPull: 策略禁止拉取鏡像
ImagePullBackOff: 正在重試拉取
RegistryUnavailable: 連接不到鏡像中心
ErrImagePull: 通用的拉取鏡像出錯
CreateContainerConfigError: 不能創建kubelet使用的容器配置
CreateContainerError: 創建容器失敗
m.internalLifecycle.PreStartContainer 執行hook報錯
RunContainerError: 啟動容器失敗
PostStartHookError: 執行hook報錯
ContainersNotInitialized: 容器沒有初始化完畢
ContainersNotReady: 容器沒有准備完畢
ContainerCreating:容器創建中
PodInitializing:pod 初始化中
DockerDaemonNotReady:docker還沒有完全啟動
NetworkPluginNotReady: 網絡插件還沒有完全啟動
- Pod的重啟策略
PodSpec 中有一個 restartPolicy 字段,可能的值為 Always、OnFailure 和 Never。默認為 Always。 restartPolicy 適用於 Pod 中的所有容器。restartPolicy 僅指通過同一節點上的 kubelet 重新啟動容器。失敗的容器由 kubelet 以五分鍾為上限的指數退避延遲(10秒,20秒,40秒...)重新啟動,並在成功執行十分鍾后重置。pod一旦綁定到一個節點,Pod 將永遠不會重新綁定到另一個節點(除非刪除這個pod,或pod所在的node節點發生故障或該node從集群中退出,則pod才會被調度到其他node節點上)。
說明: 可以管理Pod的控制器有Replication Controller,Job,DaemonSet,及kubelet(靜態Pod)。
- RC和DaemonSet:必須設置為Always,需要保證該容器持續運行。
- Job:OnFailure或Never,確保容器執行完后不再重啟。
- kubelet:在Pod失效的時候重啟它,不論RestartPolicy設置為什么值,並且不會對Pod進行健康檢查。
- 常見的狀態轉換場景
8. Pod健康檢查 (存活性探測)
在pod生命周期中可以做的一些事情。主容器啟動前可以完成初始化容器,初始化容器可以有多個,他們是串行執行的,執行完成后就推出了,在主程序剛剛啟動的時候可以指定一個post start 主程序啟動開始后執行一些操作,在主程序結束前可以指定一個 pre stop 表示主程序結束前執行的一些操作。Pod啟動后的健康狀態可以由兩類探針來檢測:Liveness Probe(存活性探測) 和 Readiness Probe(就緒性探測)。如下圖:
- Liveness Probe
1. 用於判斷容器是否存活(running狀態)。
2. 如果LivenessProbe探針探測到容器非健康,則kubelet將殺掉該容器,並根據容器的重啟策略做相應處理。
3. 如果容器不包含LivenessProbe探針,則kubelet認為該探針的返回值永遠為“success”。
livenessProbe:指示容器是否正在運行。如果存活探測失敗,則 kubelet 會殺死容器,並且容器將受到其 重啟策略 的影響。如果容器不提供存活探針,則默認狀態為 Success。Kubelet使用liveness probe(存活探針)來確定何時重啟容器。例如,當應用程序處於運行狀態但無法做進一步操作,liveness探針將捕獲到deadlock,重啟處於該狀態下的容器,使應用程序在存在bug的情況下依然能夠繼續運行下去(誰的程序還沒幾個bug呢)。
- Readiness Probe
1. 用於判斷容器是否啟動完成(read狀態),可以接受請求。
2. 如果ReadnessProbe探針檢測失敗,則Pod的狀態將被修改。Endpoint Controller將從Service的Endpoint中刪除包含該容器所在Pod的Endpoint。
readinessProbe:指示容器是否准備好服務請求。如果就緒探測失敗,端點控制器將從與 Pod 匹配的所有 Service 的端點中刪除該 Pod 的 IP 地址。初始延遲之前的就緒狀態默認為 Failure。如果容器不提供就緒探針,則默認狀態為 Success。Kubelet使用readiness probe(就緒探針)來確定容器是否已經就緒可以接受流量。只有當Pod中的容器都處於就緒狀態時kubelet才會認定該Pod處於就緒狀態。該信號的作用是控制哪些Pod應該作為service的后端。如果Pod處於非就緒狀態,那么它們將會被從service的load balancer中移除。
Kubelet 可以選擇是否執行在容器上運行的兩種探針執行和做出反應,每次探測都將獲得以下三種結果之一:
成功:容器通過了診斷。
失敗:容器未通過診斷。
未知:診斷失敗,因此不會采取任何行動。
探針是由 kubelet 對容器執行的定期診斷。要執行診斷,kubelet 調用由容器實現的Handler。其存活性探測的方法有以下三種:
- ExecAction:在容器內執行指定命令。如果命令退出時返回碼為 0 則認為診斷成功。
- TCPSocketAction:對指定端口上的容器的 IP 地址進行 TCP 檢查。如果端口打開,則診斷被認為是成功的。
- HTTPGetAction:對指定的端口和路徑上的容器的 IP 地址執行 HTTP Get 請求。如果響應的狀態碼大於等於200 且小於 400,則診斷被認為是成功的。
- 定義LivenessProbe命令
許多長時間運行的應用程序最終會轉換到broken狀態,除非重新啟動,否則無法恢復。Kubernetes提供了Liveness Probe來檢測和補救這種情況。LivenessProbe三種實現方式:
1)ExecAction:在一個容器內部執行一個命令,如果該命令狀態返回值為0,則表明容器健康。(即定義Exec liveness探針)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
apiVersion: v1
kind: Pod
metadata:
labels:
test
: liveness-
exec
name: liveness-
exec
spec:
containers:
- name: liveness-
exec
-demo
image: busybox
args: [
"/bin/sh"
,
"-c"
,
"touch /tmp/healthy;sleep 60;rm -rf /tmp/healthy;"
sleep
600]
livenessProbe:
exec
:
command
: [
"test"
,
"-e"
,
"/tmp/healthy"
]
initialDelaySeconds: 5
periodSeconds: 5
|
上面的資源清單中定義了一個Pod 對象, 基於 busybox 鏡像 啟動 一個 運行“ touch/ tmp/ healthy; sleep 60; rm- rf/ tmp/ healthy; sleep 600” 命令 的 容器, 此 命令 在 容器 啟動 時 創建/ tmp/ healthy 文件, 並於 60 秒 之后 將其 刪除。 periodSeconds 規定kubelet要每隔5秒執行一次liveness probe, initialDelaySeconds 告訴kubelet在第一次執行probe之前要的等待5秒鍾。存活性探針探針檢測命令是在容器中執行 "test -e /tmp/healthy"命令檢查/ tmp/healthy 文件的存在性。如果命令執行成功,將返回0,表示 成功 通過 測試,則kubelet就會認為該容器是活着的並且很健康。如果返回非0值,kubelet就會殺掉這個容器並重啟它。
2)TCPSocketAction:通過容器IP地址和端口號執行TCP檢查,如果能夠建立TCP連接,則表明容器健康。這種方式使用TCP Socket,使用此配置,kubelet將嘗試在指定端口上打開容器的套接字。 如果可以建立連接,容器被認為是健康的,如果不能就認為是失敗的。(即定義TCP liveness探針)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
apiVersion: v1
kind: Pod
metadata:
labels:
test
: liveness-tcp
name: liveness-tcp
spec:
containers:
- name: liveness-tcp-demo
image: nginx:1.12-alpine
ports:
- name: http
containerPort: 80
livenessProbe:
tcpSocket:
port: http
|
上面的資源清單文件,向Pod IP的80/tcp端口發起連接請求,並根據連接建立的狀態判斷Pod存活狀態。
3)HTTPGetAction:通過容器IP地址、端口號及路徑調用HTTP Get方法,如果響應的狀態碼大於等於200且小於等於400,則認為容器健康。(即定義HTTP請求的liveness探針)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
apiVersion: v1
kind: Pod
metadata:
labels:
test
: liveness-http
name: liveness-http
spec:
containers:
- name: liveness-http-demo
image: nginx:1.12-alpine
ports:
- name: http
containerPort: 80
lifecycle:
postStart:
exec
:
command
: [
"/bin/sh"
,
"-c"
,
"echo healthy > /usr/share/nginx/html/healthy"
]
livenessProbe:
httpGet:
path:
/healthy
port: http
scheme: HTTP
initialDelaySeconds: 3
periodSeconds: 3
|
上面 清單 文件 中 定義 的 httpGet 測試 中, 請求 的 資源 路徑 為“/ healthy”, 地址 默認 為 Pod IP, 端口 使用 了 容器 中 定義 的 端口 名稱 HTTP, 這也 是 明確 為 容器 指明 要 暴露 的 端口 的 用途 之一。livenessProbe 指定kubelete需要每隔3秒執行一次liveness probe。initialDelaySeconds 指定kubelet在該執行第一次探測之前需要等待3秒鍾。該探針將向容器中的server的默認http端口發送一個HTTP GET請求。如果server的/healthz路徑的handler返回一個成功的返回碼,kubelet就會認定該容器是活着的並且很健康。如果返回失敗的返回碼,kubelet將殺掉該容器並重啟它。任何大於200小於400的返回碼都會認定是成功的返回碼。其他返回碼都會被認為是失敗的返回碼。
- 定義ReadinessProbe命令
有時,應用程序暫時無法對外部流量提供服務。 例如,應用程序可能需要在啟動期間加載大量數據或配置文件。 在這種情況下,你不想殺死應用程序,但你也不想發送請求。 Kubernetes提供了readiness probe來檢測和減輕這些情況。 Pod中的容器可以報告自己還沒有准備,不能處理Kubernetes服務發送過來的流量。Readiness probe的配置跟liveness probe很像。唯一的不同是使用 readinessProbe而不是livenessProbe。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
apiVersion: v1
kind: Pod
metadata:
labels:
test
: readiness-
exec
name: readiness-
exec
spec:
containers:
- name: readiness-demo
image: busybox
args: [
"/bin/sh"
,
"-c"
,
"touch /tmp/healthy;sleep 60;rm -rf /tmp/healthy;"
sleep
600]
readinessProbe:
exec
:
command
: [
"test"
,
"-e"
,
"/tmp/healthy"
]
initialDelaySeconds: 5
periodSeconds: 5
|
上面定義的是一個exec的Readiness探針,另外Readiness probe的HTTP和TCP的探測器配置跟liveness probe一樣。Readiness和livenss probe可以並行用於同一容器。 使用兩者可以確保流量無法到達未准備好的容器,並且容器在失敗時重新啟動。
- 配置Probe
Probe中有很多精確和詳細的配置,通過它們你能准確的控制liveness和readiness檢查:
initialDelaySeconds:容器啟動后第一次執行探測是需要等待多少秒。即啟動容器后首次進行健康檢查的等待時間,單位為秒。
periodSeconds:執行探測的頻率。默認是10秒,最小1秒。
timeoutSeconds:探測超時時間。默認1秒,最小1秒。即健康檢查發送請求后等待響應的時間,如果超時響應kubelet則認為容器非健康,重啟該容器,單位為秒。
successThreshold:探測失敗后,最少連續探測成功多少次才被認定為成功。默認是1。對於liveness必須是1。最小值是1。
failureThreshold:探測成功后,最少連續探測失敗多少次才被認定為失敗。默認是3。最小值是1。
HTTP probe中可以給 httpGet設置其他配置項:
host:連接的主機名,默認連接到pod的IP。你可能想在http header中設置”Host”而不是使用IP。
scheme:連接使用的schema,默認HTTP。
path: 訪問的HTTP server的path。
httpHeaders:自定義請求的header。HTTP運行重復的header。
port:訪問的容器的端口名字或者端口號。端口號必須介於1和65525之間。
對於HTTP探測器,kubelet向指定的路徑和端口發送HTTP請求以執行檢查。 Kubelet將probe發送到容器的IP地址,除非地址被httpGet中的可選host字段覆蓋。 在大多數情況下,不想設置主機字段。 有一種情況下可以設置它, 假設容器在127.0.0.1上偵聽,並且Pod的hostNetwork字段為true。 然后,在httpGet下的host應該設置為127.0.0.1。 如果你的pod依賴於虛擬主機,這可能是更常見的情況,你不應該是用host,而是應該在httpHeaders中設置Host頭。
- Liveness Probe和Readiness Probe使用場景
- 如果容器中的進程能夠在遇到問題或不健康的情況下自行崩潰,則不一定需要存活探針; kubelet 將根據 Pod 的restartPolicy 自動執行正確的操作。
- 如果希望容器在探測失敗時被殺死並重新啟動,那么請指定一個存活探針,並指定restartPolicy 為 Always 或 OnFailure。
- 如果要僅在探測成功時才開始向 Pod 發送流量,請指定就緒探針。在這種情況下,就緒探針可能與存活探針相同,但是 spec 中的就緒探針的存在意味着 Pod 將在沒有接收到任何流量的情況下啟動,並且只有在探針探測成功后才開始接收流量。
- 如果你希望容器能夠自行維護,您可以指定一個就緒探針,該探針檢查與存活探針不同的端點。
請注意:如果你只想在 Pod 被刪除時能夠排除請求,則不一定需要使用就緒探針;在刪除 Pod 時,Pod 會自動將自身置於未完成狀態,無論就緒探針是否存在。當等待 Pod 中的容器停止時,Pod 仍處於未完成狀態。
9. Pod調度
在kubernetes集群中,Pod(container)是應用的載體,一般通過RC、Deployment、DaemonSet、Job等對象來完成Pod的調度與自愈功能。
0. Pod的生命
一般來說,Pod 不會消失,直到人為銷毀它們。這可能是一個人或控制器。這個規則的唯一例外是成功或失敗的 phase 超過一段時間(由 master 確定)的Pod將過期並被自動銷毀。有三種可用的控制器:
- 使用 Job 運行預期會終止的 Pod,例如批量計算。Job 僅適用於重啟策略為 OnFailure 或 Never 的 Pod。
- 對預期不會終止的 Pod 使用 ReplicationController、ReplicaSet 和 Deployment ,例如 Web 服務器。 ReplicationController 僅適用於具有 restartPolicy 為 Always 的 Pod。
- 提供特定於機器的系統服務,使用 DaemonSet 為每台機器運行一個 Pod 。
所有這三種類型的控制器都包含一個 PodTemplate。建議創建適當的控制器,讓它們來創建 Pod,而不是直接自己創建 Pod。這是因為單獨的 Pod 在機器故障的情況下沒有辦法自動復原,而控制器卻可以。如果節點死亡或與集群的其余部分斷開連接,則 Kubernetes 將應用一個策略將丟失節點上的所有 Pod 的 phase 設置為 Failed。
1. RC、Deployment:全自動調度
RC的功能即保持集群中始終運行着指定個數的Pod。在調度策略上主要有:
- 系統內置調度算法 [最優Node]
- NodeSelector [定向調度]
- NodeAffinity [親和性調度]
- NodeSelector [定向調度]
kubernetes中kube-scheduler負責實現Pod的調度,內部系統通過一系列算法最終計算出最佳的目標節點。如果需要將Pod調度到指定Node上,則可以通過Node的標簽(Label)和Pod的nodeSelector屬性相匹配來達到目的。
1. kubectl label nodes {node-name} {label-key}={label-value}
2. nodeSelector:
{label-key}:{label-value}
如果給多個Node打了相同的標簽,則scheduler會根據調度算法從這組Node中選擇一個可用的Node來調度。
如果Pod的nodeSelector的標簽在Node中沒有對應的標簽,則該Pod無法被調度成功。
Node標簽的使用場景:
對集群中不同類型的Node打上不同的標簽,可控制應用運行Node的范圍。例如:role=frontend;role=backend;role=database。
- NodeAffinity [親和性調度]
NodeAffinity意為Node親和性調度策略,NodeSelector為精確匹配,NodeAffinity為條件范圍匹配,通過In(屬於)、NotIn(不屬於)、Exists(存在一個條件)、DoesNotExist(不存在)、Gt(大於)、Lt(小於)等操作符來選擇Node,使調度更加靈活。
1. RequiredDuringSchedulingRequiredDuringExecution:類似於NodeSelector,但在Node不滿足條件時,系統將從該Node上移除之前調度上的Pod。
2. RequiredDuringSchedulingIgnoredDuringExecution:與上一個類似,區別是在Node不滿足條件時,系統不一定從該Node上移除之前調度上的Pod。
3. PreferredDuringSchedulingIgnoredDuringExecution:指定在滿足調度條件的Node中,哪些Node應更優先地進行調度。同時在Node不滿足條件時,系統不一定從該Node上移除之前調度上的Pod。
如果同時設置了NodeSelector和NodeAffinity,則系統將需要同時滿足兩者的設置才能進行調度。
2. DaemonSet:特定場景調度
DaemonSet是kubernetes1.2版本新增的一種資源對象,用於管理在集群中每個Node上僅運行一份Pod的副本實例。
該用法適用的應用場景:
1. 在每個Node上運行一個GlusterFS存儲或者Ceph存儲的daemon進程。
2. 在每個Node上運行一個日志采集程序:fluentd或logstach。
3. 在每個Node上運行一個健康程序,采集該Node的運行性能數據,例如:Prometheus Node Exportor、collectd、New Relic agent或Ganglia gmond等。
DaemonSet的Pod調度策略與RC類似,除了使用系統內置算法在每台Node上進行調度,也可以通過NodeSelector或NodeAffinity來指定滿足條件的Node范圍進行調度。
3. Job:批處理調度
kubernetes從1.2版本開始支持批處理類型的應用,可以通過kubernetes Job資源對象來定義並啟動一個批處理任務。批處理任務通常並行(或串行)啟動多個計算進程去處理一批工作項(work item),處理完后,整個批處理任務結束。
批處理的三種模式:
批處理按任務實現方式不同分為以下幾種模式:
1. Job Template Expansion模式
一個Job對象對應一個待處理的Work item,有幾個Work item就產生幾個獨立的Job,通過適用於Work item數量少,每個Work item要處理的數據量比較大的場景。例如有10個文件(Work item),每個文件(Work item)為100G。
2. Queue with Pod Per Work Item
采用一個任務隊列存放Work item,一個Job對象作為消費者去完成這些Work item,其中Job會啟動N個Pod,每個Pod對應一個Work item。
3. Queue with Variable Pod Count
采用一個任務隊列存放Work item,一個Job對象作為消費者去完成這些Work item,其中Job會啟動N個Pod,每個Pod對應一個Work item。但Pod的數量是可變的。
Job的三種類型
1. Non-parallel Jobs
通常一個Job只啟動一個Pod,除非Pod異常才會重啟該Pod,一旦此Pod正常結束,Job將結束。
2. Parallel Jobs with a fixed completion count
並行Job會啟動多個Pod,此時需要設定Job的.spec.completions參數為一個正數,當正常結束的Pod數量達到該值則Job結束。
3. Parallel Jobs with a work queue
任務隊列方式的並行Job需要一個獨立的Queue,Work item都在一個Queue中存放,不能設置Job的.spec.completions參數。
此時Job的特性:
- 每個Pod能獨立判斷和決定是否還有任務項需要處理;
- 如果某個Pod正常結束,則Job不會再啟動新的Pod;
- 如果一個Pod成功結束,則此時應該不存在其他Pod還在干活的情況,它們應該都處於即將結束、退出的狀態;
- 如果所有的Pod都結束了,且至少一個Pod成功結束,則整個Job算是成功結束;
10. Pod伸縮
kubernetes中RC是用來保持集群中始終運行指定數目的實例,通過RC的scale機制可以完成Pod的擴容和縮容(伸縮)。
1. 手動伸縮(scale)
1
|
# kubectl scale rc redis-slave --replicas=3
|
2. 自動伸縮(HPA)
Horizontal Pod Autoscaler(HPA)控制器用於實現基於CPU使用率進行自動Pod伸縮的功能。HPA控制器基於Master的kube-controller-manager服務啟動參數--horizontal-pod-autoscaler-sync-period定義是時長(默認30秒),周期性監控目標Pod的CPU使用率,並在滿足條件時對ReplicationController或Deployment中的Pod副本數進行調整,以符合用戶定義的平均Pod CPU使用率。Pod CPU使用率來源於heapster組件,因此需安裝該組件。
HPA可以通過kubectl autoscale命令進行快速創建或者使用yaml配置文件進行創建。創建之前需已存在一個RC或Deployment對象,並且該RC或Deployment中的Pod必須定義resources.requests.cpu的資源請求值,以便heapster采集到該Pod的CPU。
- 通過kubectl autoscale創建。
例如php-apache-rc.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
apiVersion: v1
kind: ReplicationController
metadata:
name: php-apache
spec:
replicas: 1
template:
metadata:
name: php-apache
labels:
app: php-apache
spec:
containers:
- name: php-apache
image: gcr.io
/google_containers/hpa-example
resources:
requests:
cpu: 200m
ports:
- containerPort: 80
|
創建php-apache的RC
1
|
kubectl create -f php-apache-rc.yaml
|
php-apache-svc.yaml
1
2
3
4
5
6
7
8
9
|
apiVersion: v1
kind: Service
metadata:
name: php-apache
spec:
ports:
- port: 80
selector:
app: php-apache
|
創建php-apache的Service
1
|
kubectl create -f php-apache-svc.yaml
|
創建HPA控制器
1
|
kubectl autoscale rc php-apache --min=1 --max=10 --cpu-percent=50
|
- 通過yaml配置文件創建
hpa-php-apache.yaml
1
2
3
4
5
6
7
8
9
10
11
12
|
apiVersion: v1
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
spec:
scaleTargetRef:
apiVersion: v1
kind: ReplicationController
name: php-apache
minReplicas: 1
maxReplicas: 10
targetCPUUtilizationPercentage: 50
|
創建hpa
1
|
kubectl create -f hpa-php-apache.yaml
|
查看hpa
1
|
kubectl get hpa
|
11. Pod滾動升級和回滾
Kubernetes是一個很好的容器應用集群管理工具,尤其是采用ReplicationController這種自動維護應用生命周期事件的對象后,將容器應用管理的技巧發揮得淋漓盡致。在容器應用管理的諸多特性中,有一個特性是最能體現Kubernetes強大的集群應用管理能力的,那就是滾動升級。
滾動升級的精髓在於升級過程中依然能夠保持服務的連續性,使外界對於升級的過程是無感知的。整個過程中會有三個狀態:全部舊實例,新舊實例皆有,全部新實例。舊實例個數逐漸減少,新實例個數逐漸增加,最終達到舊實例個數為0,新實例個數達到理想的目標值。
1. 使用kubectl rolling-update命令完成RC的滾動升級 和 回滾
kubernetes中的RC的滾動升級通過執行 kubectl rolling-update 命令完成,該命令創建一個新的RC(與舊的RC在同一個命名空間中),然后自動控制舊的RC中的Pod副本數逐漸減少為0,同時新的RC中的Pod副本數從0逐漸增加到目標值,來完成Pod的升級。 需要注意的是:新舊RC要再同一個命名空間內。但滾動升級中Pod副本數(包括新Pod和舊Pod)保持原預期值。
1.1 通過配置文件實現
redis-master-controller-v2.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
apiVersion: v1
kind: ReplicationController
metadata:
name: redis-master-v2
labels:
name: redis-master
version: v2
spec:
replicas: 1
selector:
name: redis-master
version: v2
template:
metadata:
labels:
name: redis-master
version: v2
spec:
containers:
- name: master
image: kubeguide
/redis-master
:2.0
ports:
- containerPort: 6379
|
注意事項:
- RC的名字(name)不能與舊RC的名字相同
- 在selector中應至少有一個Label與舊的RC的Label不同,以標識其為新的RC。例如本例中新增了version的Label。
運行kubectl rolling-update
1
|
kubectl rolling-update redis-master -f redis-master-controller-v2.yaml
|
1.2 通過kubectl rolling-update命令實現
1
|
kubectl rolling-update redis-master --image=redis-master:2.0
|
與使用配置文件實現不同在於,該執行結果舊的RC被刪除,新的RC仍使用舊的RC的名字。
1.3 通過kubectl rolling-update加參數--rollback實現回滾操作
1
|
kubectl rolling-update redis-master --image=kubeguide
/redis-master
:2.0 --rollback
|
rollback原理很簡單,kubernetes記錄了各個版本的PodTemplate,把舊的PodTemplate覆蓋新的Template即可。
2. 通過Deployment的滾動升級 和 回滾
采用RS來管理Pod實例。如果當前集群中的Pod實例數少於目標值,RS會拉起新的Pod,反之,則根據策略刪除多余的Pod。Deployment正是利用了這樣的特性,通過控制兩個RS里面的Pod,從而實現升級。滾動升級是一種平滑過渡式的升級,在升級過程中,服務仍然可用,這是kubernetes作為應用服務化管理的關鍵一步!!服務無處不在,並且按需使用。Kubernetes Deployment滾動更新機制不同於ReplicationController rolling update,Deployment rollout還提供了滾動進度查詢,滾動歷史記錄,回滾等能力,無疑是使用Kubernetes進行應用滾動發布的首選。配置示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
apiVersion: apps
/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
images: nginx:1.7.9
ports:
- containerPort: 80
|
2.1 通過kubectl set image命令為Deployment設置新的鏡像名稱
1
|
kubectl
set
image deployment
/nginx-deployment
nginx=nginx:1.9.1
|
2.2 使用kubectl edit命令修改Deployment的配置
將spec.template.spec.containers[0].images 從nginx:1.7.9 更改為1.9.1; 保存退出后,kubernetes會自動升級鏡像。
2.3 通過"kubectl rollout status"可以查看deployment的更新過程
在Deployment的定義中,可以通過spec.strategy指定Pod更新的策略:
- Recreate(重建): 設置spec.strategy.type=Recreate,表示Deployment在更新Pod時,會先殺掉所有正在運行的Pod,然后創建新的Pod.
- RollingUpdate(滾動更新):以滾動更新的方式來逐個更新Pod,可以通過設置spec.strategy.rollingUpdate下的兩個參數(maxUnavailable和maxSurge)來控制滾動更新的過程。
通常來說,不鼓勵更新Deployment的標簽選擇器,因為這樣會導致Deployment選擇的Pod列表發生變化,也可能與其它控制器產生沖突。
Deployment滾動升級的過程大致為:
- 查找新的RS和舊的RS,並計算出新的Revision(這是Revision的最大值);
- 對新的RS進行擴容操作;
- 對舊的RS進行縮容操作;
- 完成之后,刪掉舊的RS;
- 通過Deployment狀態到etcd;
2.4 Deployment的回滾
所有Deployment的發布歷史記錄都保留在系統中,如果要進行回滾:
- 用 kubectl rollout history 命令檢查這個Deployment部署的歷史記錄
- 用 kubectl rollout undo deployment/nginx-deployment 撤銷本次發布回滾到上一個部署版本
- 用 kubectl rollout undo deployment/nginx-deployment --to-revision=2 回滾到指定版本
2.5 暫停和恢復Deployment的部署操作,以完成復雜的修改
對應一次復雜的Deployment配置修改,為了避免頻繁觸發Deployment的更新操作,可以暫停Deployment的更新操作,然后進行配置修改,再回復Deployment.一次性觸發完整的更新操作。使用命令:kubectl rollout pause deployment/nginx-deployment
Kubernetes滾動升級和回滾操作分享 (Deployment的rollout方式)
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
|
產品部署完成上線之后,經常遇到需要升級服務的要求(只考慮更新鏡像),以往比較粗糙的操作流程大致如下:
方式一:找到 master具體調度到的所有目標node,刪除其對應的鏡像文件
方式二:修改
file
.yaml中鏡像拉取策略為Always
刪掉舊pod,並重新創建
# kubectl delete -f /path/file.yaml
# kubectl create -f /path/file.yaml
但是這樣有一個比較棘手的問題,就是如果升級失敗的回滾策略。因此想到利用kubernetes自身的滾動升級的工具,部署及升級流程如下:
1)在初次創建的時候,盡量加入參數--record,這樣k8s會記錄下本次啟動的腳本 。
# kubectl create -f /path/file.yaml --record
2)執行查看發布的歷史記錄,會顯示現在已經記錄的腳本及其序號。
查看歷史記錄
# kubectl rollout history deployment deploy-apigw
3)升級命令執行后會輸出
"xxx image updated"
升級鏡像
# kubectl set image deployment/deploy-name containerName=newIMG:version
# kubectl set image controllerType/controllerInstanceName underInstanceContainerName=image:version
4)查看pod狀態,如果失敗需要回滾操作
回滾到上一個操作版本
# kubectl rollout undo deployment/deploy-name
回滾到指定版本,版本號由第二步查看獲得
# kubectl rollout undo deployment/deploy-name --to-revision=3
需要注意:執行rollout undo操作之后,版本號會移動,需要確認版本號無誤再undo
|
3. 其它管理對象的更新策略
3.1 DaemonSet的更新策略
- OnDelete: 默認配置。只有舊的Pod被用戶手動刪除后,才觸發新建操作。
- RollingUpdate: 舊版本的Pod將被自動殺掉,然后自動創建新版本的DaemonSet Pod.
3.2 StatefulSet的更新策略
StatefulSet的更新策略正逐漸向Deployment和DaemonSet的更新策略看齊。
12. 資源需求和資源限制
在Docker的范疇內,我們知道可以對運行的容器進行請求或消耗的資源進行限制。而在Kubernetes中也有同樣的機制,容器或Pod可以進行申請和消耗的計算資源就是CPU和內存,這也是目前僅有的受支持的兩種類型。相比較而言,CPU屬於可壓縮資源,即資源額度可按需收縮;而內存則是不可壓縮型資源,對其執行收縮操作可能會導致某種程度的問題。
資源的隔離目前是屬於容器級別,CPU和內存資源的配置需要Pod中的容器spec字段下進行定義。其具體字段,可以使用"requests"進行定義請求的確保資源可用量。也就是說容器的運行可能用不到這樣的資源量,但是必須確保有這么多的資源供給。而"limits"是用於限制資源可用的最大值,屬於硬限制。
在Kubernetes中,1個單位的CPU相當於虛擬機的1顆虛擬CPU(vCPU)或者是物理機上一個超線程的CPU,它支持分數計量方式,一個核心(1core)相當於1000個微核心(millicores),因此500m相當於是0.5個核心,即二分之一個核心。內存的計量方式也是一樣的,默認的單位是字節,也可以使用E、P、T、G、M和K作為單位后綴,或者是Ei、Pi、Ti、Gi、Mi、Ki等形式單位后綴。
- 容器的資源需求,資源限制
requests:需求,最低保障;
limits:限制,硬限制;
- CPU
1 顆邏輯 CPU
1=1000,millicores (微核心)
500m=0.5CPU
- 資源需求
自主式pod要求為stress容器確保128M的內存及五分之一個cpu核心資源可用,它運行stress-ng鏡像啟動一個進程進行內存性能壓力測試,滿載測試時它也會盡可能多地占用cpu資源,另外再啟動一個專用的cpu壓力測試進程。stress-ng是一個多功能系統壓力測試工具,master/worker模型,master為主進程,負責生成和控制子進程,worker是負責執行各類特定測試的子進程。
集群中的每個節點都擁有定量的cpu和內存資源,調度pod時,僅那些被請求資源的余量可容納當前調度的pod的請求量的節點才可作為目標節點。也就是說,kubernetes的調度器會根據容器的requests屬性中定義的資源需求量來判定僅哪些節點可接受運行相關的pod資源,而對於一個節點的資源來說,每運行一個pod對象,其requestes中定義的請求量都要被預留,直到被所有pod對象瓜分完畢為止。
資源需求配置示例:
1
2
3
4
5
6
7
8
9
10
11
12
|
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
memory:
"128Mi"
cpu:
"200m"
|
上面的配置清單中,nginx請求的CPU資源大小為200m,這意味着一個CPU核心足以滿足nginx以最快的方式運行,其中對內存的期望可用大小為128Mi,實際運行時不一定會用到這么多的資源。考慮到內存的資源類型,在超出指定大小運行時存在會被OOM killer殺死的可能性,於是該請求值屬於理想中使用的內存上限。
- 資源限制
容器的資源需求僅能達到為其保證可用的最少資源量的目的,它並不會限制容器的可用資源上限,因此對因應用程序自身存在bug等多種原因而導致的系統資源被長期占用的情況則無計可施,這就需要通過limits屬性定義資源的最大可用量。資源分配時,可壓縮型資源cpu的控制閾可自由調節,容器進程無法獲得超出其cpu配額的可用時間。不過,如果進程申請分配超出其limits屬性定義的硬限制的內存資源時,它將被OOM killer殺死。不過,隨后可能會被其控制進程所重啟。例如,容器進程的pod對象會被殺死並重啟(重啟策略為always或onfailure時),或者是容器進程的子進程被其父進程所重啟。也就是說,CPU是屬於可壓縮資源,可進行自由地調節。內存屬於硬限制性資源,當進程申請分配超過limit屬性定義的內存大小時,該Pod將被OOM killer殺死。
與requests不同的是,limits並不會影響pod的調度結果,也就是說,一個節點上的所有pod對象的limits數量之和可以大於節點所擁有的資源量,即支持資源的過載使用。不過,這么一來一旦資源耗盡,尤其是內存資源耗盡,則必然會有容器因OOMKilled而終止。另外,kubernetes僅會確保pod能夠獲得他們請求的cpu時間額度,他們能否獲得額外的cpu時間,則取決於其他正在運行的作業對cpu資源的占用情況。例如,對於總數為1000m的cpu來說,容器a請求使用200m,容器b請求使用500m,在不超出它們各自的最大限額的前提下,余下的300m在雙方都需要時會以2:5的方式進行配置。
資源限制配置示例:
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
|
[root@k8s-master ~]
# vim memleak-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: memleak-pod
labels:
app: memleak
spec:
containers:
- name: simmemleak
image: saadali
/simmemleak
resources:
requests:
memory:
"64Mi"
cpu:
"1"
limits:
memory:
"64Mi"
cpu:
"1"
[root@k8s-master ~]
# kubectl apply -f memleak-pod.yaml
pod
/memleak-pod
created
[root@k8s-master ~]
# kubectl get pods -l app=memleak
NAME READY STATUS RESTARTS AGE
memleak-pod 0
/1
OOMKilled 2 12s
[root@k8s-master ~]
# kubectl get pods -l app=memleak
NAME READY STATUS RESTARTS AGE
memleak-pod 0
/1
CrashLoopBackOff 2 28s
|
Pod資源默認的重啟策略為Always,在上面例子中memleak因為內存限制而終止會立即重啟,此時該Pod會被OOM killer殺死,在多次重復因為內存資源耗盡重啟會觸發Kunernetes系統的重啟延遲,每次重啟的時間會不斷拉長,后面看到的Pod的狀態通常為"CrashLoopBackOff"。
- 容器的可見資源
對於容器中運行top等命令觀察資源可用量信息時,即便定義了requests和limits屬性,雖然其可用資源受限於此兩個屬性的定義,但容器中可見資源量依然是節點級別可用總量。
- Pod的服務質量類別(QoS)
這里還需要明確的是,kubernetes允許節點資源對limits的過載使用,這意味着節點無法同時滿足其上的所有pod對象以資源滿載的方式運行。在一個Kubernetes集群上,運行的Pod眾多,那么當node節點都無法滿足多個Pod對象的資源使用時 (節點內存資源緊缺時),應該按照什么樣的順序去終止這些Pod對象呢?kubernetes無法自行對此做出決策,它需要借助於pod對象的優先級來判定終止Pod的優先問題。根據pod對象的requests和limits屬性,kubernetes將pod對象歸類到BestEffort、Burstable和Guaranteed三個服務質量類別:
Guaranteed:每個容器都為cpu資源設置了具有相同值的requests和limits屬性,以及每個容器都為內存資源設置了具有相同值的requests和limits屬性的pod資源會自動歸屬於此類別,這類pod資源具有最高優先級.
Burstable:至少有一個容器設置了cpu或內存資源的requests屬性,但不滿足Guaranteed類別要求的pod資源將自動歸屬此類別,它們具有中等優先級。
BestEffort:未為任何一個容器設置requests和limits屬性的pod資源將自動歸屬於此類別,它們的優先級為最低級別。
內存資源緊缺時,BestEfford類別的容器將首當其沖地終止,因為系統不為其提供任何級別的資源保證,但換來的好處是:它們能夠在可用時做到盡可能多地占用資源。若已然不存在BestEfford類別的容器,則接下來是有着中等優先級的Burstable類別的pod被終止。Guaranteed類別的容器擁有最高優先級,它們不會被殺死,除非其內存資源需求超限,或者OOM時沒有其他更低優先級的pod資源存在。
每個運行狀態的容器都有其OOM得分,得分越高越會被優先殺死。OOM得分主要根據兩個維度進行計算:由QoS類別繼承而來的默認分值和容器的可用內存資源比例。同等類別的pod資源的默認分值相同。同等級別優先級的pod資源在OOM時,與自身requests屬性相比,其內存占用比例最大的pod對象將被首先殺死。需要特別說明的是,OOM是內存耗盡時的處理機制,它們與可壓縮型資源cpu無關,因此cpu資源的需求無法得到保證時,pod僅僅是暫時獲取不到相應的資源而已。
1
2
3
|
查看 Qos
[root@k8s-master01 ~]
# kubectl describe pod/prometheus-858989bcfb-ml5gk -n kube-system|grep "QoS Class"
QoS Class: Burstable
|
13. Pod持久存儲方式
volume是kubernetes Pod中多個容器訪問的共享目錄。volume被定義在pod上,被這個pod的多個容器掛載到相同或不同的路徑下。volume的生命周期與pod的生命周期相同,pod內的容器停止和重啟時一般不會影響volume中的數據。所以一般volume被用於持久化pod產生的數據。Kubernetes提供了眾多的volume類型,包括emptyDir、hostPath、nfs、glusterfs、cephfs、ceph rbd等。
1. emptyDir
emptyDir類型的volume在pod分配到node上時被創建,kubernetes會在node上自動分配 一個目錄,因此無需指定宿主機node上對應的目錄文件。這個目錄的初始內容為空,當Pod從node上移除時,emptyDir中的數據會被永久刪除。emptyDir Volume主要用於某些應用程序無需永久保存的臨時目錄,多個容器的共享目錄等。下面是pod掛載emptyDir的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
apiVersion: v1
kind: Pod
metadata:
name:
test
-pd
spec:
containers:
- image: gcr.io
/google_containers/test-webserver
name:
test
-container
volumeMounts:
- mountPath:
/cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
|
2. hostPath
hostPath Volume為pod掛載宿主機上的目錄或文件,使得容器可以使用宿主機的高速文件系統進行存儲。缺點是,在k8s中,pod都是動態在各node節點上調度。當一個pod在當前node節點上啟動並通過hostPath存儲了文件到本地以后,下次調度到另一個節點上啟動時,就無法使用在之前節點上存儲的文件。下面是pod掛載hostPath的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
apiVersion: v1
kind: Pod
metadata:
name:
test
-pd
spec:
containers:
- image: gcr.io
/google_containers/test-webserver
name:
test
-container
volumeMounts:
- mountPath:
/test-pd
name:
test
-volume
volumes:
- name:
test
-volume
hostPath:
# directory location on host
path:
/data
|
3. pod持久存儲
方式一: pod直接掛載nfs-server
1
2
3
4
5
|
volumes:
- name: nfs
nfs:
server: 192.168.1.1
path:
"/"
|
靜態提供:管理員手動創建多個PV,供PVC使用。
動態提供:動態創建PVC特定的PV,並綁定。
方式二: 手動創建PV
Persistent Volume(持久化卷)簡稱PV,是一個Kubernetes資源對象,我們可以單獨創建一個PV,它不和Pod直接發生關系,而是通過Persistent Volume Claim,簡稱PVC來實現動態綁定, 我們會在Pod定義里指定創建好的PVC, 然后PVC會根據Pod的要求去自動綁定合適的PV給Pod使用。
持久化卷下PV和PVC概念
Persistent Volume(PV)是由管理員設置的存儲,它是群集的一部分。就像節點是集群中的資源一樣,PV 也是集群中的資源。 PV 是 Volume 之類的卷插件,但具有獨立於使用 PV 的 Pod 的生命周期。此 API 對象包含存儲實現的細節,即 NFS、iSCSI 或特定於雲供應商的存儲系統。
PersistentVolumeClaim(PVC)是用戶存儲的請求。它與 Pod 相似,Pod 消耗節點資源,PVC 消耗 PV 資源。Pod 可以請求特定級別的資源(CPU 和內存)。PVC聲明可以請求特定的大小和訪問模式(例如,可以以讀/寫一次或只讀多次模式掛載)。
它和普通Volume的區別是什么呢?
普通Volume和使用它的Pod之間是一種靜態綁定關系,在定義Pod的文件里,同時定義了它使用的Volume。Volume是Pod的附屬品,我們無法單獨創建一個Volume,因為它不是一個獨立的Kubernetes資源對象。
配置示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv003
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
nfs:
path:
/somepath
server: 192.168.1.1
|
查看PV
1
2
3
4
|
# kubectl get pv
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs-pv-heketi 300Mi ROX Retain Bound default
/nfs-pvc-heketi
7d
pvc-02b8a30d-8e28-11e7-a07a-025622f1d9fa 50Gi RWX Retain Bound kube-public
/jenkins-pvc
heketi-storage 5d
|
PV可以設置三種回收策略:保留(Retain),回收(Recycle)和刪除(Delete)。
保留策略:允許人工處理保留的數據。
刪除策略:將刪除pv和外部關聯的存儲資源,需要插件支持。
回收策略:將執行清除操作,之后可以被新的pvc使用,需要插件支持。
PV的狀態:
Available :資源尚未被claim使用
Bound :已經綁定到某個pvc上
Released : 對應的pvc被刪除,但是資源還沒有被集群回收
Failed : 自動回收失敗
PV訪問權限
ReadWriteOnce : 被單個節點mount為讀寫rw模式
ReadOnlyMany : 被多個節點mount為只讀ro模式
ReadWriteMany : 被多個節點mount為讀寫rw模式
配置示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
pv的配置定義
# pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
pod配置文件中應用pv
# mypod.yaml
volumes:
- name: mypod
persistentVolumeClaim:
claimName: myclaim
|
kubernetes 快速批量創建 PV & PVC 腳本
- 快速批量創建nfs pv
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
for
i
in
{3..6};
do
cat
<<EOF
| kubectl apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv00${i}
spec:
capacity:
storage: 100Gi
accessModes:
- ReadWriteOnce #這里根據需要配置ReadWriteOnce或者ReadWriteMany
persistentVolumeReclaimPolicy: Recycle
nfs:
path: /volume1/harbor/nfs${i}
server: 192.168.2.4
EOF
done
|
- 快速批量創建nfs pvc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
for
i
in
{3..6};
do
cat
<<EOF
| kubectl delete -f -
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc00${i}-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
EOF
done
|
14. Pod水平自動擴展(HPA)
Kubernetes有一個強大的功能,它能在運行的服務上進行編碼並配置彈性伸縮。如果沒有彈性伸縮功能,就很難適應部署的擴展和滿足SLAs。這一功能稱為Horizontal Pod Autoscaler (HPA),這是kubernetes的一個很重要的資源對象。HPA是Kubernetes中彈性伸縮API組下的一個API資源。當前穩定的版本是autoscaling/v1,它只提供了對CPU自動縮放的支持。
Horizontal Pod Autoscaling,即pod的水平自動擴展。自動擴展主要分為兩種,其一為水平擴展,針對於實例數目的增減;其二為垂直擴展,即單個實例可以使用的資源的增減。HPA屬於水平自動擴展。HPA的操作對象是RC、RS或Deployment對應的Pod,根據觀察到的CPU等實際使用量與用戶的期望值進行比對,做出是否需要增減實例數量的決策。
1. 為什么使用HPA
使用HPA,可以根據資源的使用情況或者自定義的指標,實現部署的自動擴展和縮減,讓部署的規模接近於實際服務的負載。HPA可以為您的服務帶來兩個直接的幫助:
- 在需要計算和內存資源時提供資源,在不需要時釋放它們
- 按需增加/降低性能以實現SLA
2. HPA原理
它根據Pod當前系統的負載來自動水平擴容,如果系統負載超過預定值,就開始增加Pod的個數,如果低於某個值,就自動減少Pod的個數。目前Kubernetes的HPA只能根據CPU等資源使用情況去度量系統的負載。HPA會根據監測到的CPU/內存利用率(資源指標),或基於第三方指標應用程序(如Prometheus等)提供的自定義指標,自動調整副本控制器、部署或者副本集合的pods數量(定義最小和最大pods數)。HPA是一種控制回路,它的周期由Kubernetes的controller manager 的--horizontal-pod-autoscaler-sync-period標志控制(默認值是30s)。
在一般情況下HPA是由kubectl來提供支持的。可以使用kubectl進行創建、管理和刪除:
創建HPA
- 帶有manifest: "kubectl create -f <HPA_MANIFEST>"
- 沒有manifest(只支持CPU):"kubectl autoscale deployment hello-world –min=2 --man=5 –-cpu-percent=50"
獲取hpa信息
- 基本信息: "kubectl get hpa hello-world"
- 細節描述: "kubectl describe hpa hello-world"
刪除hpa
# kubectl delete hpa hello-world
下面是一個HPA manifest定義的例子:
這里使用了autoscaling/v2beta1版本,用到了cpu和內存指標
控制hello-world項目部署的自動縮放
定義了副本的最小值1
定義了副本的最大值10
當滿足時調整大小:
- CPU使用率超過50%
- 內存使用超過100Mi
3. HPA條件
HPA通過定期(定期輪詢的時間通過--horizontal-pod-autoscaler-sync-period選項來設置,默認的時間為30秒)通過Status.PodSelector來查詢pods的狀態,獲得pod的CPU使用率。然后,通過現有pods的CPU使用率的平均值(計算方式是最近的pod使用量(最近一分鍾的平均值,從heapster中獲得)除以設定的每個Pod的CPU使用率限額)跟目標使用率進行比較,並且在擴容時,還要遵循預先設定的副本數限制:MinReplicas <= Replicas <= MaxReplicas。
計算擴容后Pod的個數:sum(最近一分鍾內某個Pod的CPU使用率的平均值)/CPU使用上限的整數+1
4. HPA流程
- 創建HPA資源,設定目標CPU使用率限額,以及最大、最小實例數
- 收集一組中(PodSelector)每個Pod最近一分鍾內的CPU使用率,並計算平均值
- 讀取HPA中設定的CPU使用限額
- 計算:平均值之和/限額,求出目標調整的實例個數
- 目標調整的實例數不能超過1中設定的最大、最小實例數,如果沒有超過,則擴容;超過,則擴容至最大的實例個數
- 回到2,不斷循環
5. HPA例外
考慮到自動擴展的決策可能需要一段時間才會生效,甚至在短時間內會引入一些噪聲。例如當pod所需要的CPU負荷過大,從而運行一個新的pod進行分流,在創建過程中,系統的CPU使用量可能會有一個攀升的過程。所以,在每一次作出決策后的一段時間內,將不再進行擴展決策。對於ScaleUp (縱向擴展)而言,這個時間段為3分鍾,Scaledown為5分鍾。
HPA允許一定范圍內的CPU使用量的不穩定,只有 avg(CurrentPodsConsumption) / Target 小於90%或者大於110%時才會觸發擴容或縮容,避免頻繁擴容、縮容造成顛簸。
【擴展】
Scale Up (縱向擴展) :主要是利用現有的存儲系統,通過不斷增加存儲容量來滿足數據增長的需求。但是這種方式只增加了容量,而帶寬和計算能力並沒有相應的增加。所以,整個存儲系統很快就會達到性能瓶頸,需要繼續擴展。
Scale-out (橫向擴展):通常是以節點為單位,每個節點往往將包含容量、處理能力和I / O帶寬。一個節點被添加到存儲系統,系統中的三種資源將同時升級。這種方式容量增長和性能擴展(即增加額外的控制器)是同時進行。而且,Scale-out架構的存儲系統在擴展之后,從用戶的視角看起來仍然是一個單一的系統,這一點與我們將多個相互獨立的存儲系統簡單的疊加在一個機櫃中是完全不同的。所以scale out方式使得存儲系統升級工作大大簡化,用戶能夠真正實現按需購買,降低TCO。
6. 為什么HPA選擇相對比率
為了簡便,選用了相對比率(90%的CPU資源)而不是0.6個CPU core來描述擴容、縮容條件。如果選擇使用絕對度量,用戶需要保證目標(限額)要比請求使用的低,否則,過載的Pod未必能夠消耗那么多,從而自動擴容永遠不會被觸發:假設設置CPU為1個核,那么這個pod只能使用1個核,可能Pod在過載的情況下也不能完全利用這個核,所以擴容不會發生。在修改申請資源時,還有同時調整擴容的條件,比如將1個core變為1.2core,那么擴容條件應該同步改為1.2core,這樣的話,就真是太麻煩了,與自動擴容的目標相悖。
7. 安裝需求
在HPA可以在Kubernetes集群上使用之前,有一些元素需要在系統中安裝和配置。檢查確定Kubernetes集群服務正在運行並且至少包含了這些標志:
kube-api:requestheader-client-ca-file
kubelet:read-only-port 在端口10255
kube-controller:可選,只在需要和默認值不同時使用
horizontal-pod-autoscaler-downscale-delay:”5m0s”
horizontal-pod-autoscaler-upscale-delay:”3m0s”
horizontal-pod-autoscaler-sync-period: “30s”
HPA的實例說明:
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
|
1)創建Deployment
[root@k8s-master01 ~]
# cat << EOF > lykops-hpa-deploy.yaml
apiVersion: extensions
/v1beta1
kind: Deployment
metadata:
name: lykops-hpa-deploy
labels:
software: apache
project: lykops
app: hpa
version: v1
spec:
replicas: 1
selector:
matchLabels:
name: lykops-hpa-deploy
software: apache
project: lykops
app: hpa
version: v1
template:
metadata:
labels:
name: lykops-hpa-deploy
software: apache
project: lykops
app: hpa
version: v1
spec:
containers:
- name: lykops-hpa-deploy
image: web:apache
command
: [
"sh"
,
"/etc/run.sh"
]
ports:
- containerPort: 80
name: http
protocol: TCP
resources:
requests:
cpu: 0.001
memory: 4Mi
limits:
cpu: 0.01
memory: 16Mi
EOF
創建這個實例
[root@k8s-master01 ~]
# kubectl create -f lykops-hpa-deploy.yaml --record
2)創建service
[root@k8s-master01 ~]
#
cat
<< EOF > lykops-hpa-deploy-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: lykops-hpa-svc
labels:
software: apache
project: lykops
app: hpa
version: v1
spec:
selector:
software: apache
project: lykops
app: hpa
version: v1
name: lykops-hpa-deploy
ports:
- name: http
port: 80
protocol: TCP
EOF
創建這個service
[root@k8s-master01 ~]
# kubectl create -f lykops-hpa-deploy-svc.yaml
3)創建HPA
[root@k8s-master01 ~]
# cat << EOF > lykops-hpa.yaml
apiVersion: autoscaling
/v1
kind: HorizontalPodAutoscaler
metadata:
name: lykops-hpa
labels:
software: apache
project: lykops
app: hpa
version: v1
spec:
scaleTargetRef:
apiVersion: v1
kind: Deployment
name: lykops-hpa-deploy
#這里只能為這三項
minReplicas: 1
maxReplicas: 10
targetCPUUtilizationPercentage: 5
EOF
創建這個HPA
[root@k8s-master01 ~]
# kubectl create -f lykops-hpa.yaml
4)測試
多台機器不斷訪問service的clusterIP地址,然后可以看出是否增加pod數了
|