2. Pod

2. Pod

2.1 Pod中的基本概念

 Pod 是K8S中的重要組成部分,也是K8S對象模型中最小、最簡單的可部署對象,Pod代表集羣中運行的進程,一個Pod中包含了一個或多個容器、存儲資源、唯一的網絡IP以及容器運行運行方式的選項(Pod中的內容總是共同定位和安排的)。一個 Pod 是一個部署單元:K8S中一個一應用程序的單個實例(這可能由單個容器或少量緊密耦合並共享資源的容器組成)。Docker 是在Pod中最常見的運行時容器(但K8S不只支持docker),集羣中的Pod可以有2中使用方式:

  1. 運行單個容器的Pod:“每個Pod運行一個容器”是K8S中最常見的使用模型,此時Pod就像是該容器的一個外層包裹,但K8S管理的還是Pod而不是直接去管理容器;
  2. 運行多個容器的Pod:Pod可以封裝由多個緊密耦合且需要共享資源的位於同一位置的容器組成的應用程序,這些位於同一位置的容器可能形成一個統一的服務單元——一個容器將文件從共享卷提供給公衆,而一個單獨的“sidecar”容器刷新或更新這些文件,Pod將這些容器和存儲資源作爲一個單一的可管理實體包裹在一起;

 每個Pod 意味着運行所給應用的單個實例,如果需要對應用進行水平擴容(如運行多個實例),應該使用多個Pod,秉持“一個Pod一個實例”的思想,這種情況其實就是複製,複製的pods通常由一個稱爲控制器(Controller)的抽象東西來創建和管理,並作爲一個組進行管理。Pod支持耦合性較強的多個進程(表現爲一個Pod中多個容器)協作,同一個物理機/虛擬機上的Pod中的容器是自動定位和編排的,容器之間可以共享資源和依賴、通信、協作。一個Pod中對多個容器進行共同定位和管理是一個相對比較高端的用例,使用場景是容器之間高耦合,比如有一個容器充當共享卷中文件的Web服務器,還有一個單獨的“sidecar”容器從遠程源更新這些文件。

Pod圖,Pod 爲它內部的容器提供了2類共享資源——網絡和存儲(networking and storage):

  • 網絡(networking):每個Pod都被分配了一個唯一IP地址,Pod中的每個容器共享網絡命名空間(network namespace),包括IP地址和網絡端口。Pod內部的容器可以使用localhost相互通信(也可以使用標準進程通信,像SystemV semaphores和POSIX共享內存),但如果Pod內的容器想和Pod外部的實體對象通信,那它們必須協調好如何使用共享的網絡資源(比如端口),不同Pod中的容器擁有不同IP地址,沒有特別配置的話是不能通過IPC進行通信的,這種場景下的容器通常是通過Pod的IP地址進行通信。;
  • 存儲(storage):Pod可以指定一組共享存儲卷(storage volumes),被定義爲Pod中的一部分且被安裝在每個應用的文件系統中。Pod中的所有容器都可以使用這些存儲卷、分享數據,如果其中一些容器需要被重啓,那存儲卷允許持久化Pod中的數據是依然存活。

 在隨後的過程中,Pod 共享的上下文(context)是一系列的Linux的namespace、cgroups以及其他隔離的東西(就是隔離Docker容器的東西),在Pod的上下文中,獨立的應用可能進一步運用了子隔離。

2.2 Pod中的管理

  • 聲明週期短;
  • Controller 創建;

 Pod是多進程協作的模式,這些進程形成一個相對黏合的服務單元。和Pod的工作,一般情況很少會在K8S中直接創建獨立的Pod(即使是"1個Pod1個容器"的模式),因爲Pod的生命週期是相對較短的、即用即廢的實體。當Pod被創建後(無論是你自己直接創建的、還是通過Controller間接創建),會被分配到一個唯一的ID(UID),然後將會被安排運行在集羣中的某個節點上,Pod 將保持存活在對應的節點上,直到進程被終止、Pod被刪除、由於資源缺乏被驅逐或者節點失敗(比如某個節點掛了,那安排到該節點上的Pod將會在超時後被刪除)。一個已經給出的Pod(由UID標識)不能被重新安排到一個新的節點上,但它可以被另一個一模一樣的Pod替換(名字可以一樣,但UID不一樣)。重啓Pod中的容器時應該不需要擔心Pod的重啓,Pod本身並不會運行,它只是容器運行的一個環境。Pod本身是不能自愈的,如果Pod被安排的那個節點失敗了,或者安排(調度)本身的行爲失敗了,那Pod將會被刪除,同樣的,由於缺乏資源或節點維護,POD無法在驅逐中存活。K8S使用了更高維度的抽象(即Controller),簡化了應用的部署和管理,解決了管理相對可釋放的Pod實例,因此,即使可以直接去使用Pod,但使用Controller去管理Pod是更加常見的作法。

 Controller 可以創建和管理多個Pod,在集羣中解決副本推出和提供自愈能力,比如:如果節點失敗了,Controller可能會在不同的節點上自動安排一個完全相同的替代品,包含一個或多個Pod的Controller的示例有:Deployment、StatefulSet、DaemonSet,總而言之,Controller 使用你提供的Pod模板去創建它負責的Pod。

 Pod可以用來承載垂直集成的應用堆,但是它們的主要動機是支持共同定位、共同管理的協助程序,比如:

  • 內容管理系統、文件和數據加載程序、本地緩存管理器等;
  • 日誌和檢查點備份、壓縮、旋轉、快照等;
  • 代理、網橋和適配器;
  • 控制器、管理器、配置器和更新程序;

通常來說,單獨的Pod並不會用來運行同一個應用的多個實例。

Pod模板(Pod Templates)是包含在其他對象中的Pod規格,比如 Replication Controllers、Jobs、DaemonSets,Controller 使用Pod Template 去創建實際的 Pod,下面是一個簡單 Pod 清單(manifest):

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
spec:
  containers:
  - name: myapp-container
    image: busybox
    command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600']

Pod Template 就像切割工具,並不是指定所有副本特定的期望狀態,一旦被切割了,那餅乾就和切割工具沒啥關係了,即使之後再去更改 template 甚至轉用另一個新的模板,這些都和已經創建的Pod沒有任何關係。相似的,通過 replication controller 創建的 Pod 可能隨後會直接被更新,這和Pod形成對比,Pod將會指定當前Pod中所有容器的當前期望狀態,這種方式從根本上簡化了系統語義、增加了原始的靈活度。

 Pod代表着集羣中運行的進程,所以允許這些進程被優雅的停止也很重要,用戶可以請求去刪除並且知曉什麼時候這些進程停止、甚至可以確保刪除行爲的最終完成。當請求刪除某個Pod時,系統會記錄允許被強制殺掉Pod所需要的預估時間,然後這個信號將會被髮送至每個容器的主進程,一旦超過之前的預估時間,那Kill信號將被髮送到這些進程中,然後Pod將會從API Server上被刪除,如果在等待進程停止的過程中Kubelet或容器管理者(應該就是抽象的Controller)被重啓,那終止的行爲將會在預估時間內進行重試。下面是一個示例:

  1. 用戶發送命令去刪除Pod,默認的預估時間花費爲30秒;
  2. API Server中的Pod會隨着時間的推移而更新,同時會有寬限期,超過這個時間,pod會被認爲是“死的”;
  3. Pod 在客戶端命令中列出時顯示爲“正在終止”(Terminating);
  4. 和第3步同時進行,當 Kubelet 看到一個Pod被標記爲“Terminating”,因爲時間在2中已經被設置了,它開始停止Pod的進程:
  5. 如果Pod中某個容器已經被定義爲 preStop hook,它將會被容器內被調用,如果 preStop hook 在超時後(grace period)仍然在運行,然後用一個小的(2秒)延長的寬限期調用第2步;
  6. 容器被髮送 TERM 信號,注意並不是Pod中所有容器都會同時收到 TERM 信號,如果關閉的順序很重要,每個都可能需要一個 preStop 鉤子;
  7. 和第3步同時進行,從服務的端節點列表中刪除的Pod將不會被視作複製控制器(replication Controller)的運行Pod集的一部分,緩慢刪除Pod將無法繼續提供流量,因爲負載均衡器(比如Service proxy)將會把Pod從旋轉中移除;
  8. 寬限期超時(grace period expires),任何在Pod中仍然運行的進程將使用 SIGKILL 殺掉;
  9. Kubelet將會把寬限期設置爲0(立即刪除),在API Server完成Pod的刪除,Pod 從 API 消失客戶端將不會再看到;

默認情況下,所有的刪除行爲的寬限期爲30秒, Kubectl delete 命令用戶使用--grace-period=<seconds>參數自定義寬限時間(0表示強制刪除Pod),如果使用0值,必須在設置--grace-period=0時一起添加一個額外的標識--force以便強制刪除。強制刪除Pod被定義爲立即從集羣狀態中和etcd(k8s依賴的數據庫)中刪除該pod,當一個強制刪除行爲執行時,apiserver並不會等待kubelet的確認對應節點上的該Pod是否正在運行或停止,他將會在API中立刻移除Pod以便創建一個新的同名的Pod,在節點上,設置爲立即終止的Pod在被強制殺死之前仍將得到一個小的寬限期,強制刪除有一些潛在危險(謹慎操作)。

Pod容器的特權模式

 在容器的spec中SecurityContext使用privileged標誌可以開啓特權模式,這個功能可以讓容器使用Linux的能力(比如操作網絡堆棧、訪問設備),容器內的進程獲得的權限與容器外的進程幾乎相同,這種模式下,將網絡和卷插件作爲獨立的pods編寫應該更容易,而不需要編譯到kubelet中。

2.3 Pod初始容器(Init Containers)

 一個Pod可以有多個運行着的APP的容器,但也可以有1個或多個初始化容器,初始化容器必須在App容器之前啓動,初始化容器和普通容器完全相同(普通App容器支持的字段和特性它都支持,但不包括就緒探測),特殊的地方主要有2點:

  1. 它們總是跑完全程;
  2. 每個在下一個開始之前必須完全跑完(意味着如果一個Pod有多個初始化容器,那這些容器一定是配置的順序一次運行一個全部運行完畢);

 如果一個初始化容器失敗了,k8s將反覆重啓直到初始化容器成功,但如果Pod有一個Never的restartPolicy,那就不會重啓。如果需要指定一個初始化容器,需要在PodSpec中containers字段中添加一個initContainers對象(一個JSON數組對象,內部元素時 Container 類型),初始化容器的狀態返回到.status.initContainerStatuses字段作爲容器狀態的數組。

初始化容器的應用場景包括:

  • 出於安全考慮,一些不靠包含在App容器鏡像內的公共應用程序,可以放在 init container 中運行;
  • 一些應用程序或者自定義的設置代碼(不包含在App鏡像中),比如:沒必要在設置的過程中包含諸如從另一個鏡像使用sedawkpythondig等工具製造出一個鏡像這樣的步驟;
  • 應用程序映像的構建器和部署器角色可以獨立工作,無需共同構建單個應用程序映像;
  • 它們使用Linux的namespaces,這樣可以讓它們和App容器有不同的文件系統,因此它們可以訪問App容器無法訪問的機密信息;
  • init container 在App容器之前啓動,而App容器是並行運行的,因此初始化容器提供了一種簡便的方式去阻斷或延緩App容器的啓動,直到一些前置的條件工作做完;

示例,下面是一個具備2個初始化容器(第一個myservice,第二個mydb)的Pod(新版語法可能略有不同,以新語法爲準):

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
spec:
  containers:
  - name: myapp-container
    image: busybox:1.28
    command: ['sh', '-c', 'echo The app is running! && sleep 3600']
  initContainers:
  - name: init-myservice
    image: busybox:1.28
    command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;']
  - name: init-mydb
    image: busybox:1.28
    command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']

myservice和mydb服務的yaml文件:

kind: Service
apiVersion: v1
metadata:
  name: myservice
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9376
---
kind: Service
apiVersion: v1
metadata:
  name: mydb
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9377

Pod可以使用下面的命令進行啓動和debug:

kubectl apply -f myapp.yaml

pod/myapp-pod created

kubectl get -f myapp.yaml

NAME        READY     STATUS     RESTARTS   AGE
myapp-pod   0/1       Init:0/2   0          6m

kubectl describe -f myapp.yaml

Name:          myapp-pod
Namespace:     default
[...]
Labels:        app=myapp
Status:        Pending
[...]
Init Containers:
  init-myservice:
[...]
    State:         Running
[...]
  init-mydb:
[...]
    State:         Waiting
      Reason:      PodInitializing
    Ready:         False
[...]
Containers:
  myapp-container:
[...]
    State:         Waiting
      Reason:      PodInitializing
    Ready:         False
[...]
Events:
  FirstSeen    LastSeen    Count    From                      SubObjectPath                           Type          Reason        Message
  ---------    --------    -----    ----                      -------------                           --------      ------        -------
  16s          16s         1        {default-scheduler }                                              Normal        Scheduled     Successfully assigned myapp-pod to 172.17.4.201
  16s          16s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Pulling       pulling image "busybox"
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Pulled        Successfully pulled image "busybox"
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Created       Created container with docker id 5ced34a04634; Security:[seccomp=unconfined]
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Started       Started container with docker id 5ced34a04634

檢查初始化服務:

# 檢查第一個初始化容器
kubectl logs myapp-pod -c init-myservice

# 檢查第二個初始化容器
kubectl logs myapp-pod -c init-mydb

當啓動上述的2個服務後,就可以看到初始化容器完成了並且myapp-pod被創建:

kubectl apply -f services.yaml

service/myservice created
service/mydb created

kubectl get -f myapp.yaml

kubectl get -f myapp.yaml
NAME        READY     STATUS    RESTARTS   AGE
myapp-pod   1/1       Running   0          9m

2.4 Pod的生命週期

 雖然Pod的生命週期很短,但也是一個階段性的過程(Pod中是一個PodStatus對象),主要階段(或者狀態)有:

值 | 說明
Pending | 當前Pod已經被k8s所接受,但至少有一個容器鏡像還未被創建(容器還未完全創建)
Running | Pod已經被綁定到具體節點上且所有容器都已經被創建,此外,且至少有一個容器處於運行或啓動或重啓狀態
Succeeded | Pod中所有容器都已經成功停止且不會被重啓
Failed | Pod中的容器被停止,但至少有一個容器未被成功停止,也就是說,容器要麼以非零狀態退出,要麼被系統終止
Unknown | 即未知狀態,典型原因是由於和主機的通信發生錯誤
Completed | pod已經運行完成,因爲沒有什麼可以讓它繼續運行,例如完成作業
CrashLoopBackOff | Pod中某個容器異常退出

2.5 Pod Preset

 Pod預置對象(Pod Preset)是爲了在創建時向Pod中注入特定的信息,這些信息可以包括機密、卷、卷裝載和環境變量,它是一種API資源,可以使用標籤選擇器來選擇該Pod Preset應用在哪個Pod上。使用pod預設可以允許pod模板作者不必顯式地爲每個pod提供所有信息。這樣,使用特定服務的pod模板的作者就不需要知道該服務的所有細節。

 K8S提供了一個許可控制器,它允許將Pod Preset應用到將要到來的創建Pod的請求中,當Pod創建請求發生時,系統會做下面的一些事情:

  1. 檢索所有可用的PodPresets
  2. 校驗PodPreset的標籤選擇器是否和待創建Pod的標籤匹配;
  3. 嘗試將PodPreset中定義的各種各樣資源合併到將要創建的Pod中;
  4. 如果發生錯誤,拋出在記錄Pod合併出錯、創建Pod但未從PodPreset中注入資源的異常事件;
  5. 對修改後的Pod規約(pod spec)進行註釋,以表明它已被podpreset修改,格式爲:podpreset.admission.kubernetes.io/podpreset-<pod-preset name>: "<resource version>"

 每個Pod可以被0個或多個PodPreset匹配,每個PodPreset可以應用在0個或多個Pod上,當PodPreset應用在1個或多個Pod上,K8S修改Pod規約(Pod Spec)。比如對於修改EnvEnvFromVolumeMounts,K8S修改Pod中所有容器的spec;再如修改Volume,K8S會修改Pod的Spec。

注:Pod Preset是可以修改Pod規約(Pod Spec)中的.spec.containers字段的,但POD Preset中的資源定義不會應用於initcontainers字段。

【要求Pod Preset不應用於某個指定的Pod】

 某些場景下,可能希望某個Pod不應用Pod Preset,即不希望Pod Preset來修改某個Pod的規約,需要在對應的Pod規約(Pod Spec)中指定:podpreset.admission.kubernetes.io/exclude: "true"

【開啓Pod Preset】

 要想在集羣中使用Pod Preset,需要做如下的設置:

  1. 開啓settings.k8s.io/v1alpha1/podpreset類型的API。可以通過在API Server中的--runtime-config添加settings.k8s.io/v1alpha1=true。如果使用的是minikube搭建的集羣,啓動集羣時添加--extra-config=apiserver.runtime-config=settings.k8s.io/v1alpha1=true標識也行。
  2. 開啓PodPreset允許控制器。可以通過在API Server中的--enable-admission-plugins選項包含PodPreset。如果使用的是minikube搭建的集羣,啓動集羣時添加--extra-config=apiserver.enable-admission-plugins=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota,PodPreset也行。
  3. 在對應的命名空間中通過創建PodPreset對象來定義PodPreset。

2.6 中斷(Disruptions)

待續。。。

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