Pod原理以及Pod生命周期


一、Pod的介紹

1、為什么需要Pod?

假設 Kubernetes 中調度的基本單元就是容器,對於一個非常簡單的應用可以直接被調度直接使用,沒有什么問題,但是往往還有很多應用程序是由多個進程組成的,有人可能會說把這些進程都打包到一個容器中去不就可以了嗎?理論上是可以實現的,但是不要忘記了 Docker 管理的進程是 pid=1 的主進程,其他進程死掉了就會成為僵屍進程,沒辦法進行管理了,這種方式本身也不是容器推薦的運行方式,一個容器最好只干一件事情,所以在真實的環境中不會使用這種方式。

那么我們就把這個應用的進程進行拆分,拆分成一個一個的容器總可以了吧?但是不要忘記一個問題,拆分成一個一個的容器后,是不是就有可能出現一個應用下面的某個進程容器被調度到了不同的節點上呀?往往我們應用內部的進程與進程間通信(通過 IPC 或者共享本地文件之類)都是要求在本地進行的,也就是需要在同一個節點上運行。

所以我們需要一個更高級別的結構來將這些容器綁定在一起,並將他們作為一個基本的調度單元進行管理,這樣就可以保證這些容器始終在同一個節點上面,這也就是 Pod 設計的初衷。

2、Pod原理

其實 Pod 也只是一個邏輯概念,真正起作用的還是 Linux 容器的 Namespace 和 Cgroup 這兩個最基本的概念,Pod 被創建出來其實是一組共享了一些資源的容器而已。首先 Pod 里面的所有容器,都是共享的同一個 Network Namespace,但是涉及到文件系統的時候,默認情況下 Pod 里面的容器之間的文件系統是完全隔離的,但是我們可以通過聲明來共享同一個 Volume。

2.1、如何共享Network Namespace?

對於共享一個Network Namespace,這個就和Docker網絡模式中的Container模式比較相似,可以指定新創建的容器可以和一個已經運行的容器共享一個Network Namespace,在運行容器的時候只需要指定--net=container:目標容器名這個參數就可以了,但是這種模式有一個明顯的問題,就是容器啟動的先后順序。那么Pod是怎么解決這個問題的呢? 那就是加入一個中間容器(沒有什么架構是加一個中間件解決不了的?),這個容器叫做 Infra 容器,而且這個容器在 Pod 中永遠都是第一個被創建的容器,這樣是不是其他容器都加入到這個 Infra 容器就可以了,這樣就完全實現了 Pod 中的所有容器都和 Infra 容器共享同一個 Network Namespace 了 。

image

從上面圖中我們可以看出普通的容器加入到了 Infra 容器的 Network Namespace 中,所以這個 Pod 下面的所有容器就是共享同一個 Network Namespace 了,普通容器不會創建自己的網卡,配置自己的 IP,而是和 Infra 容器共享 IP、端口范圍等,而且容器之間的進程可以通過 lo 網卡設備進行通信,也就是說容器之間是可以直接使用localhost進行通信,看到的網絡設備信息都是和Infra容器一樣,也就意味着同一個Pod下面的容器運行的多個進程不能綁定相同的端口,而且Pod的聲明周期只和Infra容器一致,和容器A、B無關。

2.2、文件系統如何實現共享?

默認情況下容器的文件系統是互相隔離的,要實現共享只需要在 Pod 的頂層聲明一個 Volume,然后在需要共享這個 Volume 的容器中聲明掛載即可。

image

二、Pod的資源清單

在Kubernetes中基本所有資源的一級屬性都是一樣的 ,主要包含五個部分:

  • apiVersion : 版本,由Kubernetes內部定義,版本號必須用kubectl api-versions查詢。
  • kind : 類型,由Kubernetes內部定義,類型必須用kubectl api-resources查詢。
  • metadata : 元數據,主要是資源標示和說明,常用的有name,namespace,labels等。
  • spec :描述,這是配置中最重要的一部分,里面是對各種資源配置的詳細描述。
  • status :狀態信息,里面的內容不需要定義,由Kubernetes自動生成。

我們可以通過kubectl explain 資源類型命令進行查看資源的可配置項,比如我們查看Pod的一級配置:

# 查看pod的一級配置
kubectl explain pod

KIND:     Pod
VERSION:  v1

DESCRIPTION:
     Pod is a collection of containers that can run on a host. This resource is
     created by clients and scheduled onto hosts.

FIELDS:
   apiVersion	<string>
     APIVersion defines the versioned schema of this representation of an
     object. Servers should convert recognized schemas to the latest internal
     value, and may reject unrecognized values. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources

   kind	<string>
     Kind is a string value representing the REST resource this object
     represents. Servers may infer this from the endpoint the client submits
     requests to. Cannot be updated. In CamelCase. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

   metadata	<object>
     Standard object's metadata. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata

   spec	<object>
     Specification of the desired behavior of the pod. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

   status	<object>
     Most recently observed status of the pod. This data may not be up to date.
     Populated by the system. Read-only. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

我們看到其中 spec 字段是一個 <Object> 類型的,證明該字段下面是一個對象,我們可以繼續去查看這個字段下面的詳細信息,這也是我們接下來重點要研究的。

pod.sepc常見的屬性如下:

  • containers <[]Object>:容器列表,用於定義容器的詳細信息。
  • nodeName :根據nodeName的值將Pod調度到指定的Node節點上。
  • nodeSelector <map[]> :根據NodeSelector中定義的信息選擇該Pod調度到包含這些Label的Node上。
  • hostNetwork :是否使用主機網絡模式,默認為false,如果設置為true,表示使用宿主機網絡。
  • volumes <[]Object> :存儲卷,用於定義Pod上面掛載的存儲信息。
  • restartPolicy :重啟策略,表示Pod在遇到故障的時候的處理策略。

三、配置Pod

Pod中最關鍵的配置項是什么呢?肯定是containers了。

我們同樣使用命令進行查看pod.spec.containers需要哪些配置項。

# 查看pod.spec.containers的可選配置項
kubectl explain pod.spec.containers

KIND:     Pod
VERSION:  v1
RESOURCE: containers <[]Object>   # 數組,代表可以有多個容器FIELDS:
  name  <string>     # 容器名稱
  image <string>     # 容器需要的鏡像地址
  imagePullPolicy  <string> # 鏡像拉取策略 
  command  <[]string> # 容器的啟動命令列表,如不指定,使用打包時使用的啟動命令
  args   <[]string> # 容器的啟動命令需要的參數列表 
  env    <[]Object> # 容器環境變量的配置
  ports  <[]Object>  # 容器需要暴露的端口號列表
  resources <object> # 資源限制和資源請求的設置

1、基本配置

創建pod_base.yaml文件

apiVersion: v1
kind: Pod
metadata:
  name: pod-base  # 此處不能使用_
  namespace: dev  # 指定namespace
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名稱
      image: nginx:1.17.1  # 容器需要的鏡像地址
    - name: busybox # 容器名稱
      image: busybox:1.30  # 容器需要的鏡像地址          

上面定義了一個比較簡單的Pod的配置,里面有兩個容器:

nginx:用的是1.17.1版本的nginx鏡像創建(nginx是一個輕量級的web容器)。

busybox:用的是1.30版本的busybox鏡像創建(busybox是一個小巧的linux命令集合)。

接下來我們可以創建Pod以及查看pod狀態:

# 創建pod
kubectl apply -f pod_base.yaml

# 查看Pod
kubectl get pods pod-base -n dev
NAME       READY   STATUS             RESTARTS   AGE
pod-base   1/2     ImagePullBackOff   1          2m2s

# 我們發現,當前Pod中有兩個容器,但是准備就緒只有一個。
# 再次查看,發現重試了3次
kubectl get pods pod-base -n dev
NAME       READY   STATUS             RESTARTS   AGE
pod-base   1/2     CrashLoopBackOff   3          3m2s

# 查看內部詳情
kubectl describe pods pod-base -n dev

# 我們只關心Events部分,
Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  <unknown>          default-scheduler  Successfully assigned dev/pod-base to node2
  Normal   Pulled     75s                kubelet, node2     Container image "nginx:1.17.1" already present on machine
  Normal   Created    73s                kubelet, node2     Created container nginx
  Normal   Started    73s                kubelet, node2     Started container nginx
  Normal   Pulling    73s                kubelet, node2     Pulling image "busybox:1.30"
  Normal   Pulled     49s                kubelet, node2     Successfully pulled image "busybox:1.30"
  Normal   Pulled     25s (x2 over 47s)  kubelet, node2     Container image "busybox:1.30" already present on machine
  Normal   Created    24s (x3 over 49s)  kubelet, node2     Created container busybox
  Normal   Started    24s (x3 over 48s)  kubelet, node2     Started container busybox
  Warning  BackOff    11s (x4 over 41s)  kubelet, node2     Back-off restarting failed container
# 發現啟動busybox失敗了。

2.、command命令

在前面的案例中,busybox容器沒有運行成功,那么到底是什么原因導致這個容器的故障呢?

原來busybox並不是一個程序,而是類似於一個工具類的集合,Kubernetes集群啟動管理后,它會自動關閉,解決方法就是讓其一直在運行,這就用到了command的配置。

創建pod_command.yaml文件:

apiVersion: v1
kind: Pod
metadata:
  name: pod-command
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名稱
      image: nginx:1.17.1  # 容器需要的鏡像地址
    - name: busybox # 容器名稱
      image: busybox:1.30  # 容器需要的鏡像地址
      command: ["/bin/sh","-c","touch /tmp/hello.txt;while true; do /bin/echo $(date +%T) >> /tmp/hello.txt;sleep 3;done;"]
      
# 說明:command用於在Pod中的容器初始化完畢之后執行一個命令
# "/bin/sh","-c":使用sh執行命令
# touch /tmp/hello.txt:創建一個/tmp/hello.txt的文件
# while ture;do /bin/echo $(date +%T) >> /tmp/hello.txt:sleep 3;done:每隔三秒,向文件寫入當前時間

使用kubectl apply -f pod_command.yaml命令進行創建pod,並確保Pod創建成功后,我們可以使用kubectl exec命令進入容器內部。

kubectl exec -it pod名稱 -n 命名空間 -c 容器名稱 /bin/sh

kubectl exec -it pod-command -n dev -c busybox /bin/sh

kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead.
/ # tail -f tmp/hello.txt
10:02:15
10:02:18
10:02:21
10:02:24
10:02:27
10:02:30
10:02:33
10:02:36
...

特別說明

​ 通過上面發現command已經可以完成啟動命令和傳遞參數的功能,為什么還要提供一個args選項,用於傳遞參數?其實和Docker有點關系,Kubernetes中的command和args兩個參數其實是為了實現覆蓋Dockerfile中的ENTRYPOINT的功能:

1 、如果command和args均沒有寫,那么用Dockerfile的配置。

2 、如果command寫了,但是args沒有寫,那么Dockerfile默認的配置會被忽略,執行注入的command。

​ 3、如果command沒有寫,但是args寫了,那么Dockerfile中配置的ENTRYPOINT命令會被執行,使用當前args的參數。

​ 4、如果command和args都寫了,那么Dockerfile中的配置會被忽略,執行command並追加上args參數。

3、鏡像拉取策略以及環境變量設置

上面的兩個案例都使用了Nginx鏡像,我們不經回想,第一次,本地沒有Nginx,肯定是遠程倉庫拉取的,那么第二次是從本地拉取呢還是從遠程倉庫拉取的呢?

Kubernetes支持配置三種拉取策略,使用imagePullPolicy設置鏡像拉取的策略,:

  • Always: 總是從遠程倉庫拉取鏡像(一直遠程下載)
  • IfNotPresent: 本地有則使用本地鏡像,本地沒有則從遠程倉庫拉取鏡像(本地有就用本地,本地沒有就使用遠程下載)。
  • Never:只使用本地鏡像,從不去遠程倉庫拉取,本地沒有就報錯(一直使用本地,沒有就報錯)

默認值說明:

如果鏡像tag為具體版本號,默認策略是IfNotPresent;

如果鏡像tag為latest,默認策略是Always。

當然我們也可以給Pod中的容器設置環境變量

env:環境變量,用於在Pod中的容器設置環境變量。

下面我們創建pod_image_env.yaml文件進行測試Kubernetes的鏡像拉取策略和環境變量的設置:

apiVersion: v1
kind: Pod
metadata:
  name: pod-image-env
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名稱
      image: nginx:1.17.1  # 容器需要的鏡像地址
      imagePullPolicy: Always  # 設置鏡像的拉取策略
    - name: busybox # 容器名稱
      image: busybox:1.30  # 容器需要的鏡像地址
      command: ["/bin/sh","-c","touch /tmp/hello.txt;while true; do /bin/echo $(date +%T) >> /tmp/hello.txt;sleep 3;done;"]
      env:
        - name: "username"
          value: "admin"
        - name: "password"
          value: "123456"

Pod創建成功后,我們進入容器內部驗證環境變量:

kubectl exec -it pod-image-env -n dev -c busybox -it /bin/sh

kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead.
/ # echo $username
admin
/ # echo $password
12345

4、端口設置

到此我們已經可以自行創建pod了,但是我們創建的pod如何對外提供服務呢?或者准確的說,運行在Pod上的容器怎么對外提供服務呢?在啟動Docker的時候我們可以通過-p參數來設置端口,那么Kubernetes是否也可以指定端口呢?答案是肯定的。

接下來我們用kubectl explain pod.spec.containers.ports命令來查看posts支持的子選項:

kubectl explain pod.spec.containers.ports

KIND:     Pod
VERSION:  v1
RESOURCE: ports <[]Object>
FIELDS:
  name <string> # 端口名稱,如果指定,必須保證name在pod中是唯一的
  containerPort <integer> # 容器要監聽的端口(0<x<65536) hostport="" <integer=""> # 容器要在主機上公開的端口,如果設置,主機上只能運行容器的一個副本(一般省略)
  hostIP <string>  # 要將外部端口綁定到的主機IP(一般省略)
  protocol <string>  # 端口協議。必須是UDP、TCP或SCTP。默認為“TCP”

#----------------------------------------------------------------
KIND:     Pod
VERSION:  v1

RESOURCE: ports <[]Object>

DESCRIPTION:
     List of ports to expose from the container. Exposing a port here gives the
     system additional information about the network connections a container
     uses, but is primarily informational. Not specifying a port here DOES NOT
     prevent that port from being exposed. Any port which is listening on the
     default "0.0.0.0" address inside a container will be accessible from the
     network. Cannot be updated.

     ContainerPort represents a network port in a single container.

FIELDS:
   containerPort	<integer> -required-
     Number of port to expose on the pod's IP address. This must be a valid port
     number, 0 < x < 65536.

   hostIP	<string>
     What host IP to bind the external port to.

   hostPort	<integer>
     Number of port to expose on the host. If specified, this must be a valid
     port number, 0 < x < 65536. If HostNetwork is specified, this must match
     ContainerPort. Most containers do not need this.

   name	<string>
     If specified, this must be an IANA_SVC_NAME and unique within the pod. Each
     named port in a pod must have a unique name. Name for the port that can be
     referred to by services.

   protocol	<string>
     Protocol for port. Must be UDP, TCP, or SCTP. Defaults to "TCP".

接下來我們創建pod_ports.yaml文件,內容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-ports
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名稱
      image: nginx:1.17.1  # 容器需要的鏡像地址
      imagePullPolicy: IfNotPresent  # 設置鏡像的拉取策略
      ports: 
        - name: nginx-port  # 端口名稱,如果執行,必須保證name在Pod中是唯一的
          containerPort: 80 # 容器要監聽的端口
          protocol: TCP # 端口協議

訪問Pod中的容器用PodIP:ContainerPort的方式進行訪問。

5、資源配額

容器中的程序要運行,肯定會占用一定的資源,比如CPU和內存等,如果不對某個容器的資源做限制,那么它就可能吃掉大量的資源,導致其他的容器無法運行。針對這種情況,Kubernetes提供了對內存和CPU的資源進行配額的機制,這種機制主要通過resources選項實現,它有兩個子選項:

​ 1 、limits:用於限制運行的容器的最大占用資源,當容器占用資源超過limits時會被終止,並進行重啟。

​ 2、requests:用於設置容器需要的最小資源,如果環境資源不夠,容器將無法啟動。

可以通過上面的兩個選項設置資源的上下限。

下面我們創建pod_resoures.yaml文件,測試資源配額。

apiVersion: v1
kind: Pod
metadata:
  name:  pod-resoures
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名稱
      image: nginx:1.17.1  # 容器需要的鏡像地址
      imagePullPolicy: IfNotPresent  # 設置鏡像的拉取策略
      ports:
        - name: nginx-port  # 端口名稱,如果執行,必須保證name在Pod中是唯一的
          containerPort: 80 # 容器要監聽的端口
          protocol: TCP # 端口協議
      resources: # 資源配額
        limits: # 限制資源的上限
          cpu: "2"  # CPU限制,單位是core數
          memory: "10Gi"  # 內存限制
        requests: # 限制資源的下限
          cpu: "1"  
          memory: "10Mi"

這里對cpu和memory的單位做一個說明:

  • CPU:core數,可以為整數或小數
  • memory:內存大小,可以使用Gi,Mi,G,M等形式

啟動Pod並查看狀態:

kubectl get pods pod-resoures -n dev

NAME           READY   STATUS    RESTARTS   AGE
pod-resoures   1/1     Running   0          20s

發現正常啟動,接下來我們修改“requests”中的memory的值為10Gi。

apiVersion: v1
kind: Pod
metadata:
  name: pod-resoures
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名稱
      image: nginx:1.17.1  # 容器需要的鏡像地址
      imagePullPolicy: IfNotPresent  # 設置鏡像的拉取策略
      ports:
        - name: nginx-port  # 端口名稱,如果執行,必須保證name在Pod中是唯一的
          containerPort: 80 # 容器要監聽的端口
          protocol: TCP # 端口協議
      resources: # 資源配額
        limits: # 限制資源的上限
          cpu: "2"  # CPU限制,單位是core數
          memory: "10Gi"  # 內存限制
        requests: # 限制資源的下限
          cpu: "1"  
          memory: "10Gi"

重新啟動並查看

# 刪除原來的pod
kubectl delete -f pod_resoures.yaml
# 啟動
kubectl apply -f pod_resoures.yaml 

# 查看
kubectl get pods pod-resoures -n dev
NAME           READY   STATUS    RESTARTS   AGE
pod-resoures   0/1     Pending   0          18s

# 查看詳情
kubectl describe pods pod-resoures -n dev

# 我們發現提示內存不足
Events:
  Type     Reason            Age        From               Message
  ----     ------            ----       ----               -------
  Warning  FailedScheduling  <unknown>  default-scheduler  0/3 nodes are available: 3 Insufficient memory.

四、Pod的生命周期

我們一般將Pod對象從創建到終止的這段時間范圍稱為Pod的生命周期,它主要包含下面的過程:

​ 1)Pod創建過程;

​ 2)運行初始化容器(init container)過程;

​ 3)運行主容器(main container):

​ ① 容器啟動后鈎子(post start)、容器終止前鈎子(pre stop)

​ ②容器存活性探測(liveness probe)、就緒性探測(readiness probe)

​ 4) Pod終止過程。

在開始之前,我們首先了解下Pod的狀態值,我們可以通過kubectl explain pod.status命令來了解關於Pod的一些信息,Pod的狀態定義在PodStatus對象中,其中有一個phase字段,下面是phase的可能取值

  • 掛起(Pending):Pod信息已經提交給了集群,但它尚未被調度完成或仍處於下載鏡像的過程中。
  • 運行中(Running):Pod已經被調度到某節點,並且所有容器已經被Kubectl創建完成。
  • 成功(Succeeded):Pod中的所有容器都已經成功終止並且不會被重啟。
  • 失敗(Failed):所有容器都已經終止,並且至少有一個容器是因為失敗終止,即容器返回了非0值的退出狀態或被系統終止。
  • 未知(Unknown):API Server無法正常獲取到Pod對象的狀態信息,通常由於網絡通信失敗所導致。

除此之外,PodStatus對象中還包含一個PodCondition的數組,里面包含的屬性有:

  • lastProbeTime:最后一次探測 Pod Condition 的時間戳。
  • lastTransitionTime:上次 Condition 從一種狀態轉換到另一種狀態的時間。
  • message:上次 Condition 狀態轉換的詳細描述。
  • message:上次 Condition 狀態轉換的詳細描述。
  • reason:Condition 最后一次轉換的原因。
  • status:Condition 狀態類型,可以為 “True”, “False”, and “Unknown”.
  • type:Condition 類型,包括以下方面:
    • PodScheduled(Pod 已經被調度到其他 node 里)
    • Ready(Pod 能夠提供服務請求,可以被添加到所有可匹配服務的負載平衡池中)
    • Initialized(所有的init containers已經啟動成功)
    • Unschedulable(調度程序現在無法調度 Pod,例如由於缺乏資源或其他限制)
    • ContainersReady(Pod 里的所有容器都是 ready 狀態)

1、Pod的創建過程

​ 1)、用戶通過kubectl或其他的api客戶端提交需要創建的Pod信息給API Server

​ 2)、API Server開始生成Pod對象的信息,並將信息存入ETCD,然后返回確認信息到客戶端。

​ 3)、API Server開始反映ETCD中Pod對象的變化,其他組件使用watch機制來跟蹤檢查API Server上的變動。

​ 4)、Scheduler發現有新的Pod對象要創建,開始為Pod分配主機並將結果信息更新至API Server

​ 5)、Node節點上的kubelet發現有Pod調度過來,嘗試調度Docker啟動容器,並將結果返回給API Server

​ 6)、API Server將接收到的Pod狀態信息存入到ETCD中。

2、Pod的終止過程

​ 1)、用戶向API Server發送刪除Pod對象的命令。

​ 2)、API Server中的Pod對象信息會隨着時間的推移而更新,在寬限期內(30s),Pod被視為dead。

​ 3)、將Pod標記為terminating狀態。

​ 4)、kubelet在監控到Pod對象轉為terminating狀態的同時啟動Pod關閉過程。

​ 5)、端點控制器監控到Pod對象的關閉行為時,將其從所有匹配到此端點的service資源的端點列表中移除。

​ 6)、如果當前Pod對象定義了Pre Stop鈎子處理器,則在其標記為terminating后會以同步的方式啟動執行。

​ 7)、Pod對象中的容器進程收到停止信號。

​ 8)、寬限期結束后,如果Pod中還存在運行的進程,那么Pod對象會收到立即終止的信號。

​ 9)、kubelet請求API Server將此Pod資源的寬限期設置為0從而完成刪除操作,此時Pod對於用戶來說已經不可用了。

3、初始化容器

初始化容器Init Container是在Pod的主容器啟動之前要運行的容器,主要是做一些主容器的前置工作,可以是一個 它具有兩大特征:

  • 初始化容器必須運行完成直至結束,如果某個初始化容器運行失敗,那么Kubernetes需要重啟它直至成功完成。

  • 初始化容器必須按照定義的順序執行,當且僅當前一個成功之后,后面一個才能運行。

初始化容器有很多應用場景,下面列出的是最常見的幾種:

  • 提供主容器鏡像中不具備的工具程序或自定義代碼。
  • 初始化容器要先於應用容器串行啟動並運行完成;因此可用於延后應用容器的啟動直至其依賴的條件得到滿足。

案例

假設要以主容器來運行Nginx,但是要求在運行Nginx之前要能夠連接上MySQL和Redis所在的服務器。

為了簡化測試,實現規定好MySQL和Redis所在的IP地址分別為192.168.209.120,192.168.209.121(注意這兩個IP都不能ping通)

第一步,創建pod_initcontainer.yaml文件

apiVersion: v1
kind: Pod
metadata:
  name: pod-initcontainers
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名稱
      image: nginx:1.17.1  # 容器需要的鏡像地址
      imagePullPolicy: IfNotPresent  # 設置鏡像的拉取策略
      ports:
        - name: nginx-port  # 端口名稱,如果執行,必須保證name在Pod中是唯一的
          containerPort: 80 # 容器要監聽的端口
          protocol: TCP # 端口協議
      resources: # 資源配額
        limits: # 限制資源的上限
          cpu: "2"  # CPU限制,單位是core數
          memory: "10Gi"  # 內存限制
        requests: # 限制資源的下限
          cpu: "1"
          memory: "10Mi"
  initContainers: # 初始化容器配置
    - name: test-mysql
      image: busybox:1.30
      type: NodePort # Service類型為NodePort
      command: ["sh","-c","until ping 192.168.209.120 -c 1;do echo waiting for mysql ...;sleep 2;done;"]
      securityContext:
        privileged: true # 使用特權模式運行
    - name: test-redis
      image: busybox:1.30
      type: NodePort # Service類型為NodePort
      command: ["sh","-c","until ping 192.168.209.121 -c 1;do echo waiting for redis ...;sleep 2; done;"]

接下來啟動並動態查看狀態

# 啟動
kubectl create -f pod_initcontainers.yaml

# 查看狀態,發現沒有准備就緒
kubectl get pods pod-initcontainers -n dev -w
NAME                 READY   STATUS     RESTARTS   AGE
pod-initcontainers   0/1     Init:0/2   0          74s

我們發現,Pod沒有准備就緒,狀態是Init。

我們新開一個窗口,給網卡添加IP

# 新開一個shell窗口,執行給master主機添加ip
ifconfig ens32:1 192.168.209.120 netmask 255.255.255.0 up
ifconfig ens32:2 192.168.209.121 netmask 255.255.255.0 up

回到我們原來的窗口,會發現神奇的事情發生了

NAME                 READY   STATUS     RESTARTS   AGE
pod-initcontainers   0/1     Init:0/2   0          4m30s
pod-initcontainers   0/1     Init:1/2   0          6m40s
pod-initcontainers   0/1     Init:1/2   0          6m41s
pod-initcontainers   0/1     PodInitializing   0          6m53s
pod-initcontainers   1/1     Running           0          6m54s

4、Pod Hook

我們知道 Pod 是 Kubernetes 集群中的最小單元,而 Pod 是由容器組成的,所以在討論 Pod 的生命周期的時候我們可以先來討論下容器的生命周期。實際上 Kubernetes 為我們的容器提供了生命周期的鈎子,就是我們說的Pod Hook,Pod Hook 是由 kubelet 發起的,當容器中的進程啟動前或者容器中的進程終止之前運行,這是包含在容器的生命周期之中。我們可以同時為 Pod 中的所有容器都配置 hook。

Kubernetes 為我們提供了兩種鈎子函數:

  • PostStart:容器創建之后執行,但是,並不能保證鈎子將在容器 ENTRYPOINT 之前運行,因為沒有參數傳遞給處理程序。主要用於資源部署、環境准備等。不過需要注意的是如果鈎子花費太長時間以至於不能運行或者掛起,容器將不能達到 running 狀態。

  • PreStop:容器終止之前執行,它是阻塞的,意味着它是同步的,所以它必須在刪除容器的調用發出之前完成。主要用於優雅關閉應用程序、通知其他系統等。如果鈎子在執行期間掛起,Pod 階段將停留在 running 狀態並且永不會達到 failed 狀態。

如果 PostStart 或者 PreStop 鈎子失敗, 它會殺死容器。所以我們應該讓鈎子函數盡可能的輕量。當然有些情況下,長時間運行命令是合理的, 比如在停止容器之前預先保存狀態。

另外我們有三種方式來實現上面的鈎子函數:

1、exec - 用於執行一特定的命令,不過要注意的是該命令消耗的資源會被計入容器。

......
    lifecycle:
        postStart:
            exec:
                command:
                    - cat
                    - /tmp/healthy
......

2、tcpSocket - 在當前容器嘗試訪問指定的socket

......
    lifecycle:
        postStart:
            tcpSocket:
                port:8080
......

3、httpGet - 在當前容器中向某url發起HTTP請求

......
    lifecycle:
        postStart:
            httpGet:
                path:/ # url地址
                port:80  # 端口號
                host:192.168.209.128  # 主機地址
                scheme: HTTP  # 支持的協議,http或https
......

以下示例中,定義了一個Nginx Pod,其中設置了PostStart鈎子函數,即在容器創建成功后,寫入一句話到/usr/share/message文件中:

apiVersion: v1
kind: Pod
metadata:
  name: hook-demo1
  namespace: dev
spec:
  containers:
    - name: hook-demo1
      image: nginx:1.17.1
      imagePullPolicy: IfNotPresent
      ports:
        - name: nginx-port  
          containerPort: 80 
          protocol: TCP 
      lifecycle:
        postStart:
          exec:
            command: ["/bin/sh","-c","echo Hello from postStart handler > /usr/share/message"]

直接創建上面的pod:

kubectl apply -f pod-postart.yaml
kubectl get pods -n dev

NAME                            READY   STATUS    RESTARTS   AGE
hook-demo1                      1/1     Running   0          43s

創建成功后,可以查看容器中/usr/share/message文件內容是否正確:

# kubectl exec -it hook-demo1 cat /usr/share/message -n dev
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead.
Hello from postStart handler

下面的示例與上面的示例功能基本類似,只是增加了容器終止前的鈎子:

apiVersion: v1
kind: Pod
metadata:
  name: pod-hook-exec
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名稱
      image: nginx:1.17.1  # 容器需要的鏡像地址
      imagePullPolicy: IfNotPresent  # 設置鏡像的拉取策略
      ports:
        - name: nginx-port  # 端口名稱,如果執行,必須保證name在Pod中是唯一的
          containerPort: 80 # 容器要監聽的端口
          protocol: TCP # 端口協議
      resources: # 資源配額
        limits: # 限制資源的上限
          cpu: "2"  # CPU限制,單位是core數
          memory: "10Gi"  # 內存限制
        requests: # 限制資源的下限
          cpu: "1"
          memory: "10Mi"
      lifecycle: # 聲明周期配置
        postStart: # 容器創建之后執行,如果失敗就會重啟容器
          exec: # 在容器啟動之后,執行一條命令,修改Nginx的首頁內容
            command: ["/bin/sh","-c","echo postStart ... > /usr/share/nginx/html/index.html"]
        preStop: # 容器終止之前執行,執行完畢之后,容器將終止,在其完成之前會阻塞刪除容器操作
          exec: # 在容器終止前,停止Nginx
            command: ["/usr/sbin/nginx", "-s", "quit"]

創建Pod,查看pod,以及訪問Pod

# 創建
kubectl create -f pod_hook_exec.yaml 
> pod/pod-hook-exec created

# 查看
kubectl get pods pod-hook-exec -n dev -o wide

NAME            READY   STATUS    RESTARTS   AGE   IP              NODE    NOMINATED NODE   READINESS GATES
pod-hook-exec   1/1     Running   0          48s   192.168.104.4   node2   <none>           <none>

# 訪問
curl 192.168.104.4
postStart ...

5、Pod健康檢查

現在在 Pod 的整個生命周期中,能影響到 Pod 的就只剩下健康檢查這一部分了。在 Kubernetes 集群當中,我們可以通過配置liveness probe(存活探針)和readiness probe(可讀性探針)來影響容器的生命周期:

  • kubelet 通過使用 liveness probe 來確定你的應用程序是否正在運行,通俗點將就是是否還活着。一般來說,如果你的程序一旦崩潰了, Kubernetes 就會立刻知道這個程序已經終止了,然后就會重啟這個程序。而我們的 liveness probe 的目的就是來捕獲到當前應用程序還沒有終止,還沒有崩潰,如果出現了這些情況,那么就重啟處於該狀態下的容器,使應用程序在存在 bug 的情況下依然能夠繼續運行下去。
  • kubelet 使用 readiness probe 來確定容器是否已經就緒可以接收流量過來了。這個探針通俗點講就是說是否准備好了,現在可以開始工作了。只有當 Pod 中的容器都處於就緒狀態的時候 kubelet 才會認定該 Pod 處於就緒狀態,因為一個 Pod 下面可能會有多個容器。當然 Pod 如果處於非就緒狀態,那么我們就會將它從 Service 的 Endpoints 列表中移除出來,這樣我們的流量就不會被路由到這個 Pod 里面來了。

說明:

livenessProbe:存活性探測,決定是否重啟容器。

readinessProbe:就緒性探測,決定是否將請求轉發給容器。

k8s在1.16版本之后新增了startupProbe探針,用於判斷容器內應用程序是否已經啟動。如果配置了startupProbe探針,就會先禁止其他的探針,直到startupProbe探針成功為止,一旦成功將不再進行探測。

上面的兩種探針目前均支持三種探測方式:

1)、exec

在容器內執行一次命令,如果命令執行的退出碼為0,則認為程序正常,否則不正常。

......
    livenessProbe:
        postStart:
            exec:
                command:
                    - cat
                    - /tmp/healthy
......

案例

下面案例是創建一個pod,然后對這個pod進行健康檢查,執行一個不可能完成的任務,然后容器會不斷的重啟。

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-exec
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名稱
      image: nginx:1.17.1  # 容器需要的鏡像地址
      imagePullPolicy: IfNotPresent  # 設置鏡像的拉取策略
      ports:
        - name: nginx-port  # 端口名稱,如果執行,必須保證name在Pod中是唯一的
          containerPort: 80 # 容器要監聽的端口
          protocol: TCP # 端口協議
      livenessProbe: # 生命周期配置
        exec: # 在容器啟動之后,執行一條命令,修改Nginx的首頁內容
          command: ["/bin/cat","/tmp/hello.txt"] # 執行一個查看文件的命令,必須失敗,因為根本沒有這個文件

接着我們創建上面聲明的pod,並查看其狀態:

# kubectl apply -f pod_liveness-exec.yaml 

# kubectl get pods -n dev
NAME                 READY   STATUS     RESTARTS   AGE
pod-liveness-exec    1/1     Running    2          62s

發現RESTARTS不為0,而是2,說明Pod重啟了兩次,那么我們通過describe查看詳情。

kubectl describe pods pod-liveness-exec -n dev

Events:
  Type     Reason     Age                   From               Message
  ----     ------     ----                  ----               -------
  Normal   Scheduled  2m47s                 default-scheduler  Successfully assigned dev/pod-liveness-exec to node2
  Normal   Pulled     78s (x4 over 2m45s)   kubelet, node2     Container image "nginx:1.17.1" already present on machine
  Normal   Created    78s (x4 over 2m45s)   kubelet, node2     Created container nginx
  Normal   Killing    78s (x3 over 2m17s)   kubelet, node2     Container nginx failed liveness probe, will be restarted
  Normal   Started    77s (x4 over 2m44s)   kubelet, node2     Started container nginx
  Warning  Unhealthy  68s (x10 over 2m38s)  kubelet, node2     Liveness probe failed: /bin/cat: /tmp/hello.txt: No such file or directory

觀察上面的信息就會發現nginx容器啟動之后就進行了健康檢查,檢查失敗之后,容器被kill掉,然后嘗試進行重啟,這是重啟策略的作用。

稍等一會之后,再觀察Pod的信息,就會看到RESTARTS不再是2,而是一直增長。

kubectl get pods -n dev

NAME                 READY   STATUS     RESTARTS   AGE
pod-liveness-exec    1/1     Running    6          4m18s

接下來我們嘗試修改探測指針執行的任務,使其能成功探測。

首先刪除已經運行的pod:

kubectl delete -f pod_liveness_exec.yaml

修改command命令:

command: ["/bin/ls","/tmp"] # 查看tmp目錄

創建並查看,發現存活性探測指針已經能正常探測了。

kubectl apply -f pod_liveness-exec.yaml 

kubectl get pods -n dev
NAME                 READY   STATUS     RESTARTS   AGE
pod-liveness-exec    1/1     Running    0          5s

2)、tcpSocket

將會嘗試訪問一個用戶容器的端口,如果能夠建立這條連接,則認為程序正常,否則不正常。

......
   livenessProbe:
      tcpSocket:
         port: 8080
......

案例

下面案例主要是通過tcpSocket監聽8080端口,來判斷Pod中的容器是否正常。

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-socket
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名稱
      image: nginx:1.17.1  # 容器需要的鏡像地址
      imagePullPolicy: IfNotPresent  # 設置鏡像的拉取策略
      ports:
        - name: nginx-port  # 端口名稱,如果執行,必須保證name在Pod中是唯一的
          containerPort: 80 # 容器要監聽的端口
          protocol: TCP # 端口協議
      livenessProbe: # 生命周期配置
        tcpSocket:
          port: 8080 # 嘗試訪問8080端口,失敗,Pod中只有一個Nginx,監聽80

創建后查看Pod狀態:

kubectl get pod pod-liveness-socket -n dev
NAME                  READY   STATUS    RESTARTS   AGE
pod-liveness-socket   1/1     Running   1          58s

kubectl describe pod pod-liveness-socket -n dev

Events:
  Type     Reason     Age   From               Message
  ----     ------     ----  ----               -------
  Normal   Scheduled  36s   default-scheduler  Successfully assigned dev/pod-liveness-socket to node1
  Normal   Pulled     10s   kubelet, node1     Container image "nginx:1.17.1" already present on machine
  Normal   Created    10s   kubelet, node1     Created container nginx
  Normal   Started    10s   kubelet, node1     Started container nginx
  Warning  Unhealthy  7s    kubelet, node1     Liveness probe failed: dial tcp 192.168.166.131:8080: connect: connection refused

通過上面的Events信息,我們很容易發現,Liveness探針失敗了。

3)、httpGet

調用容器內web應用的URL,如果返回的狀態碼在200和399之前,則認為程序正常,否則不正常。

......
   livenessProbe:
      httpGet:
         path: / #URI地址
         port: 80 #端口號
         host: 127.0.0.1 #主機地址
         scheme: HTTP #支持的協議,http或者https
......

案例

下面案例是通過httpGet方式,在容器內部訪問http://127.0.0.1/htalth

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-httpGet
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名稱
      image: nginx:1.17.1  # 容器需要的鏡像地址
      imagePullPolicy: IfNotPresent  # 設置鏡像的拉取策略
      ports:
        - name: nginx-port  # 端口名稱,如果執行,必須保證name在Pod中是唯一的
          containerPort: 80 # 容器要監聽的端口
          protocol: TCP # 端口協議
      livenessProbe: # 生命周期配置
        httpGet: 
          port: 80
          scheme: HTTP
          path: /hello
          host: 127.0.0.1

4)、容器探測的補充

上面已經使用了livenessProbe演示了三種探測方式,但是查看livenessProbe的子屬性,會發現除了這三種方式,還有一些其他的配置。

kubectl explain pod.spec.containers.livenessProbe

KIND:     Pod
VERSION:  v1

RESOURCE: livenessProbe <object>

DESCRIPTION:
     Periodic probe of container liveness. Container will be restarted if the
     probe fails. Cannot be updated. More info:
     https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

     Probe describes a health check to be performed against a container to
     determine whether it is alive or ready to receive traffic.

FIELDS:
   exec	<object>
     One and only one of the following should be specified. Exec specifies the
     action to take.

   failureThreshold	<integer> 
   # 連續探測失敗多少次才被認定為失敗。默認是3。最小值是1
     Minimum consecutive failures for the probe to be considered failed after
     having succeeded. Defaults to 3. Minimum value is 1.

   httpGet	<object>
     HTTPGet specifies the http request to perform.

   initialDelaySeconds	<integer>
     Number of seconds after the container has started before liveness probes
     are initiated. More info:
     https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

   periodSeconds	<integer>
     How often (in seconds) to perform the probe. Default to 10 seconds. Minimum
     value is 1.

   successThreshold	<integer>
     Minimum consecutive successes for the probe to be considered successful
     after having failed. Defaults to 1. Must be 1 for liveness and startup.
     Minimum value is 1.

   tcpSocket	<object>
     TCPSocket specifies an action involving a TCP port. TCP hooks not yet
     supported

   timeoutSeconds	<integer>
     Number of seconds after which the probe times out. Defaults to 1 second.
     Minimum value is 1. More info:
     https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes


initialDelaySeconds    	# 容器啟動后等待多少秒執行第一次探測
timeoutSeconds      	# 探測超時時間。默認1秒,最小1秒
periodSeconds       	# 執行探測的頻率。默認是10秒,最小1秒
failureThreshold   	 	# 連續探測失敗多少次才被認定為失敗。默認是3。最小值是1
successThreshold    	# 連續探測成功多少次才被認定為成功。默認是1

initialDelaySeconds可以用來配置第一次執行探針的等待時間,對於啟動非常慢的應用這個參數非常有用,比如 JenkinsGitlab 這類應用,但是如何設置一個合適的初始延遲時間呢?這個就和應用具體的環境有關系了,所以這個值往往不是通用的,這樣的話可能就會導致一個問題,我們的資源清單在別的環境下可能就會健康檢查失敗了,為解決這個問題,在 Kubernetes v1.16 版本官方特地新增了一個 startupProbe(啟動探針),該探針將推遲所有其他探針,直到 Pod 完成啟動為止,使用方法和存活探針一樣:

......
    startupProbe:
      httpGet:
        path: /healthz
        port: 8080
      failureThreshold: 30  # 盡量設置大點
      periodSeconds: 10
......

有的時候,應用程序可能暫時無法對外提供服務,例如,應用程序可能需要在啟動期間加載大量數據或配置文件。在這種情況下,你不想殺死應用程序,也不想對外提供服務。那么這個時候我們就可以使用readiness probe來檢測和減輕這些情況。 Pod 中的容器可以報告自己還沒有准備,不能處理 Kubernetes 服務發送過來的流量。readiness probe的配置跟liveness probe基本上一致的。唯一的不同是使用readinessProbe而不是livenessProbe。兩者如果同時使用的話就可以確保流量不會到達還未准備好的容器,准備好過后,如果應用程序出現了錯誤,則會重新啟動容器。

6、重啟策略

在容器探測中,一旦容器探測出現了問題,Kubernetes就會對容器所在的Pod進行重啟,其實這是由Pod的重啟策略決定的,Pod的重啟策略有3種,分別如下:

​ 1) 、Always:容器失效時,自動重啟該容器,默認值。

​ 2)、OnFailure:容器終止運行且退出碼不為0時重啟。

​ 3)、Never:不論狀態如何,都不重啟該容器。

重啟策略適用於Pod對象中的所有容器,首次需要重啟的容器,將在其需要的時候立即進行重啟,隨后再次重啟的操作將由kubelet延遲一段時間后進行,且反復的重啟操作的延遲時長以此為10s、20s、40s、80s、160s和300s,300s是最大的延遲時長。

案例

apiVersion: v1
kind: Pod
metadata:
  name: pod-restart-policy
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名稱
      image: nginx:1.17.1  # 容器需要的鏡像地址
      imagePullPolicy: IfNotPresent  # 設置鏡像的拉取策略
      
      ports:
        - name: nginx-port  # 端口名稱,如果執行,必須保證name在Pod中是唯一的
          containerPort: 80 # 容器要監聽的端口
          protocol: TCP # 端口協議
      livenessProbe: # 聲明周期配置
        httpGet:
          port: 80
          scheme: HTTP
          path: /hello
          host: 127.0.0.1
  restartPolicy: Never # 重啟策略

接着我們創建,並查看狀態

kubectl create -f pod_restart_policy.yaml 

kubectl describe pod pod-restart-policy -n dev

Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  <unknown>          default-scheduler  Successfully assigned dev/pod-restart-policy to node2
  Normal   Pulled     73s                kubelet, node2     Container image "nginx:1.17.1" already present on machine
  Normal   Created    73s                kubelet, node2     Created container nginx
  Normal   Started    73s                kubelet, node2     Started container nginx
  Warning  Unhealthy  50s (x3 over 70s)  kubelet, node2     Liveness probe failed: Get http://127.0.0.1:80/hello: dial tcp 127.0.0.1:80: connect: connection refused
  Normal   Killing    50s                kubelet, node2     Stopping container nginx 
  # 我們發現容器探測失敗后,直接停止容器,並沒有選擇重啟


免責聲明!

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



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