k8s pod 詳解

https://www.cnblogs.com/kevingrace/p/11309409.html

一、什麼是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的yaml格式定義配置文件說明

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

apiVersion: v1          # 必選,版本號

kind: Pod               # 必選,Pod

metadata:               # 必選,元數據

  name: String          # 必選,Pod名稱

  namespace: String        # 必選,Pod所屬的命名空間

  labels:                 # 自定義標籤,Map格式

    Key: Value          # 鍵值對

  annotations:          # 自定義註解

    Key: Value           # 鍵值對

spec:                   # 必選,Pod中容器的詳細屬性

  containers:             # 必選,Pod中容器列表

  - name: String          # 必選,容器名稱

    image: String         # 必選,容器的鏡像地址和名稱

    imagePullPolicy: {Always | Never | IfNotPresent}   

                          # 獲取鏡像的策略,Always表示下載鏡像,IfnotPresent 表示優先使用本地鏡像,否則下載鏡像,Never表示僅使用本地鏡像。

    command: [String]         # 容器的啓動命令列表(覆蓋),如不指定,使用打包鏡像時的啓動命令。

    args: [String]            # 容器的啓動命令參數列表

    workingDir: String        # 容器的工作目錄

    volumeMounts:             # 掛載到容器內部的存儲卷配置

    - name: String            # 引用Pod定義的共享存儲卷的名,需用Pod.spec.volumes[]部分定義的卷名

      mountPath: String       # 存儲卷在容器內Mount的絕對路徑,應少於512字符

      readOnly: boolean       # 是否爲只讀模式

  ports:                      # 容器需要暴露的端口庫號列表

  - name: String              # 端口號名稱

    containerPort: Int        # 容器需要監聽的端口號

    hostPort: Int             # 可選,容器所在主機需要監聽的端口號,默認與Container相同

  env:                        # 容器運行前需設置的環境變量列表

  - name: String              # 環境變量名稱

    value: String             # 環境變量的值

  resources:                  # 資源限制和請求的設置

    limits:                   # 資源限制的設置

      cpu: String             # Cpu的限制,單位爲Core數,將用於docker run --cpu-shares參數,如果整數後跟m,表示佔用權重,1Core=1000m

      memory: String          # 內存限制,單位可以爲Mib/Gib,將用於docker run --memory參數

    requests:                 # 資源請求的設置

      cpu: string             # CPU請求,容器啓動的初始可用數量

      memory: string          # 內存請求,容器啓動的初始可用數量

  livenessProbe:   

                # 對Pod內容器健康檢查設置,當探測無響應幾次後將自動重啓該容器,檢查方法有exec、httpGet和tcpSocket,對一個容器只需設置其中一種方法即可。

    exec:                     # 對Pod容器內檢查方式設置爲exec方式

      command: [String]       # exec方式需要制定的命令或腳本

    httpGet:                  # 對Pod容器內檢查方式設置爲HttpGet方式,需要指定path、port

      path: String            # 網址URL路徑(去除對應的域名或IP地址的部分)

      port: Int               # 對應端口

      host: String            # 域名或IP地址

      schema: String          # 對應的檢測協議,如http

      HttpHeaders:            # 指定報文頭部信息

      - name: String

        value: String

    tcpSocket:                # 對Pod容器內檢查方式設置爲tcpSocket方式

      port: Int

    initialDelaySeconds: Int  # 容器啓動完成後首次探測的時間,單位爲秒

    timeoutSeconds: Int       # 對容器健康檢查探測等待響應的超時時間,單位爲秒,默認爲1秒

    periodSeconds: Int        # 對容器監控檢查的定期探測時間設置,單位爲秒,默認10秒一次

    successThreshold: Int     # 探測幾次成功後認爲成功

    failureThreshold: Int     # 探測幾次失敗後認爲失敗

    securityContext:

      privileged: false

  restartPolicy: {Always | Never | OnFailure} 

                # Pod的重啓策略,Always表示一旦不管以何種方式終止運行,kubelet都將重啓,OnFailure表示只有Pod以非0退出碼才重啓,Nerver表示不再重啓該Pod

  nodeSelector:              # 設置NodeSelector表示將該Pod調度到包含這個label的node上,以Key:Value的格式指定

    Key: Value               # 調度到指定的標籤Node上

  imagePullSecrets:          # Pull鏡像時使用的secret名稱,以Key:SecretKey格式指定

  - name: String

  hostNetwork: false         # 是否使用主機網絡模式,默認爲false,如果設置爲true,表示使用宿主機網絡

  volumes:                   # 在該Pod上定義共享存儲卷列表

  - name: String             # 共享存儲卷名稱(Volumes類型有多種)

    emptyDir: { }            # 類型爲emptyDir的存儲卷,與Pod同生命週期的一個臨時目錄,爲空值

    hostPath: String         # 類型爲hostPath的存儲卷,表示掛載Pod所在宿主機的目錄

    path: String             # Pod所在宿主機的目錄,將被用於同期中Mount的目錄

  secret:                    # 類型爲Secret的存儲卷,掛載集羣與定義的Secret對象到容器內部

    secretname: String

    items:                   # 當僅需掛載一個Secret對象中的指定Key時使用

    - key: String

     path: String

  configMap:                 # 類型爲ConfigMap的存儲卷,掛載預定義的ConfigMap對象到容器內部

    name: String

    items:                   # 當僅需掛載一個ConfigMap對象中的指定Key時使用

    - key: String

      path: String

# kubectl get pod podname -n namespace -o wide       # 查看pod分佈在哪些node節點上
# kubectl get pod podname -n namespace -o yaml       # 查看pod的yml配置情況

二、如何使用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 字段,可能的值爲 AlwaysOnFailure 和 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命令
kubelet對非主進程崩潰類的容器錯誤無法感知,需要藉助存活性探測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對象歸類到BestEffortBurstableGuaranteed三個服務質量類別:
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 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 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數了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章