1. 程式人生 > 其它 >Kubernetes實戰(第二版)--第五章 在Pods中執行應用程式

Kubernetes實戰(第二版)--第五章 在Pods中執行應用程式

本章涵蓋了

  • 理解如何以及何時對容器進行分組
  • 通過從YAML檔案建立Pod物件來執行應用程式
  • 與應用程式通訊,檢視它的日誌,探索它的環境
  • 新增一個sidecar容器來擴充套件pod的主容器
  • 通過在pod啟動時執行初始化容器命令來初始化pods

讓我用一個圖表來重新整理您的記憶,它顯示了您在第3章中建立的用於在Kubernetes上部署最小應用程式的三種類型的物件。圖5.1顯示了它們之間的關係以及它們在系統中具有哪些功能。

圖5.1組成已部署應用程式的三種基本物件型別

現在您對如何通過Kubernetes API公開這些物件有了基本的瞭解。在本章和接下來的章節中,您將瞭解每一種方法的具體細節,以及通常用於部署完整應用程式的許多其他方法。讓我們從Pod物件開始,因為它代表了Kubernetes中最重要的核心概念——應用程式的執行例項。

5.1 理解pods

您已經瞭解到,pod是一組位於同一位置的容器,是Kubernetes中的基本構建塊。

不是單獨部署容器,而是將一組容器作為單個單元(即pod)部署和管理。雖然pod可能包含幾個容器,但一個pod只包含一個容器並不罕見。當一個pod有多個容器時,它們都在同一個工作節點上執行——單個pod例項永遠不會跨越多個節點。圖5.2將幫助您視覺化此資訊。

圖5.2 一個pod的所有容器都執行在同一個節點上。一個pod從不跨越多個節點。

5.1.1 理解為什麼我們需要pods

讓我們討論一下為什麼需要同時執行多個容器,而不是在同一個容器中執行多個程序。

理解為什麼一個容器不應該包含多個程序

設想一個由幾個程序組成的應用程式,這些程序通過IPC(程序間通訊)或共享檔案相互通訊,這要求它們在同一臺計算機上執行。在第2章中,您瞭解到每個容器就像一個獨立的計算機或虛擬機器。計算機通常執行多個程序;容器也可以做到這一點。您可以在一個容器中執行組成應用程式的所有程序,但這使得該容器非常難以管理。
容器被設計為只執行單個程序,不計算它產生的任何子程序。容器工具和Kubernetes都是圍繞這一事實開發的。例如,在容器中執行的程序應該將其日誌寫入標準輸出。用於顯示日誌的Docker和Kubernetes命令只顯示從輸出中捕獲的內容。如果一個程序在容器中執行,它是唯一的寫入器,但是如果在容器中執行多個程序,它們都寫入相同的輸出。因此,它們的日誌是相互交織的,很難區分每一行日誌屬於哪個程序。

容器應該只執行單個程序的另一個指示是,容器執行時僅在容器的根程序死亡時重新啟動容器。它不關心這個根程序建立的任何子程序。如果它產生子程序,則它單獨負責保持所有這些程序的執行。

要充分利用容器執行時提供的特性,應該考慮在每個容器中只執行一個程序。

理解一個pod如何組合多個容器

由於不應該在單個容器中執行多個程序,因此顯然需要另一種更高階的構造,即使在劃分為多個容器時也可以一起執行相關的程序。這些程序必須能夠像普通計算機中的程序一樣彼此通訊。這就是pods被引入的原因。

使用pod,您可以一起執行緊密相關的程序,為它們(幾乎)提供相同的環境,就像它們都執行在一個容器中一樣。這些程序在某種程度上是孤立的,但不是完全的——它們共享一些資源。這給了你兩全其美。您可以使用容器提供的所有特性,但也允許程序一起工作。pod使這些相互連線的容器作為一個單元進行管理。

在第二章中,您瞭解到容器使用自己的一組Linux名稱空間,但是它也可以與其他容器共享一些名稱空間。這種名稱空間的共享正是Kubernetes和容器執行時將容器組合到pods中的方式。

如圖5.3所示,一個pod中的所有容器共享相同的網路名稱空間,從而共享屬於它的網路介面、IP地址和埠空間。

圖5.3 pod中的容器共享相同的網路介面

由於共享埠空間,在相同pod的容器中執行的程序不能繫結到相同的埠號,而其他pod中的程序有自己的網路介面和埠空間,從而消除了不同pod之間的埠衝突。

pod中的所有容器也看到相同的系統主機名,因為它們共享UTS名稱空間,並且可以通過通常的IPC機制進行通訊,因為它們共享IPC名稱空間。pod還可以配置為對其所有容器使用單個PID名稱空間,這使得它們共享單個程序樹,但是必須為每個pod分別顯式地啟用此功能。

請注意

當相同pod的容器使用單獨的PID名稱空間時,它們不能相互看到,也不能在它們之間傳送SIGTERM或SIGINT之類的程序訊號。

正是這種名稱空間的共享讓在pod中執行的程序感覺像是在一起執行,儘管它們執行在不同的容器中。

相反,每個容器總是有自己的掛載名稱空間,為其提供自己的檔案系統,但是當兩個容器必須共享檔案系統的一部分時,可以向pod新增一個volume(卷),並將其掛載到兩個容器中。這兩個容器仍然使用兩個獨立的掛載名稱空間,但是共享卷被掛載到這兩個名稱空間中。你將在第7章瞭解更多關於卷的知識。

5.1.2 將容器組織到pods

你可以把每個pod看作是一臺獨立的計算機。與虛擬機器不同,虛擬機器通常承載多個應用程式,您通常在每個pod中只執行一個應用程式。您不需要在單個pod中組合多個應用程式,因為pod幾乎沒有資源開銷。您可以有您需要的任意數量的pod,所以您應該將應用程式劃分為單個pod,以便每個pod只執行密切相關的應用程式程序。

讓我用一個具體的例子來說明。

將多層應用程式棧拆分為多個pods

設想一個由前端web伺服器和後端資料庫組成的簡單系統。我已經解釋過前端伺服器和資料庫不應該執行在同一個容器中,因為容器中內建的所有特性都是按照容器中執行的程序不超過一個的預期設計的。如果不是在單個容器中,那麼是否應該在所有在同一pod中的獨立容器中執行它們?

儘管沒有什麼可以阻止您在單個pod中同時執行前端伺服器和資料庫,但這不是最好的方法。我已經解釋了pod的所有容器總是在同一位置執行,但是web伺服器和資料庫必須在同一臺計算機上執行嗎?答案顯然是否定的,因為它們可以很容易地通過網路進行通訊。因此,您不應該在同一個pod中執行它們。
如果前端和後端都在同一個pod中,那麼它們都執行在同一個叢集節點上。如果您有一個兩節點叢集,並且只建立這個pod,那麼您只使用了一個工作節點,並且沒有利用第二個節點上可用的計算資源。這意味著浪費了CPU、記憶體、磁碟儲存和頻寬。將容器分成兩個pod允許Kubernetes將前端pod放在一個節點上,後端pod放在另一個節點上,從而提高硬體的利用率。

分解成多個pods,以支援獨立擴充套件

不使用單個pod的另一個原因與水平伸縮有關。pod不僅是部署的基本單元,也是擴充套件的基本單元。在第2章中,您擴充套件了Deployment物件,Kubernetes建立了額外的pods——應用程式的額外副本。Kubernetes不會在pod中複製容器。它複製整個pod。

前端元件通常與後端元件有不同的伸縮需求,因此我們通常分別對它們進行伸縮。當您的pod同時包含前端和後端容器並且Kubernetes複製它時,您將得到前端和後端容器的多個例項,這並不總是您想要的。有狀態後端(如資料庫)通常無法伸縮。至少沒有無狀態前端那麼容易。如果容器必須與其他元件分開進行伸縮,這就清楚地表明它必須部署在單獨的pod中。

下圖說明了剛才的解釋。

圖5.4 將應用程式棧拆分為pods

5.2.2 從YAML檔案中建立Pod物件

在為pod準備好清單檔案之後,現在可以通過將檔案釋出到Kubernetes API來建立物件。

通過將清單檔案應用到叢集來建立物件

當您將清單釋出到API時,您將引導Kubernetes將清單應用到叢集。這就是為什麼執行此操作的kubectl子命令稱為apply。讓我們使用它來建立pod:

$ kubectl apply -f kubia.yamlpod“kubia”created

通過修改清單檔案來更新物件並重新應用

kubectl apply命令用於建立物件以及對現有物件進行更改。如果您決定更改pod物件,可以簡單地編輯kubia.yaml檔案,並再次執行apply命令。pod的一些欄位是不可變的,因此更新可能會失敗,但您總是可以刪除pod,然後再次建立它。在本章的最後,你將學習如何刪除pods和其他物件。

檢索執行POD的完整清單

pod物件現在是叢集配置的一部分。可以從API中讀取它,使用以下命令檢視完整的物件清單:

$kubectlgetpokubia-oyaml

如果執行這個命令,您將注意到清單與kubia.yaml中的清單相比增加了很多內容。您將看到metadata 部分現在要大得多,物件現在有一個status部分。相關欄位中的spec 部分也增加了內容。您可以使用kubectl explain來檢視更多關於這些新欄位的內容,但它們中的大部分將在本章和後續章節中進行解釋。

5.2.3 檢查新建立的pod

在開始與內部執行的應用程式互動之前,讓我們使用基本的kubectl命令來看看pod是如何執行的。

快速檢查pod狀態

Pod物件已經建立,但是如何知道Pod中的容器是否正在實際執行呢?您可以使用kubectl get命令檢視pod的摘要:

$ kubectl get pod kubiaNAME     READY   STATUS    RESTARTS   AGEkubia1/1Running032s

你可以看到pod在執行中,但除此之外就沒什麼了。要了解更多資訊,您可以嘗試在前一章中學習的kubectl get pod -o wide或kubectl describe命令。

使用 kubectl describe 檢視pod細節

要顯示pod的更詳細檢視,使用kubectl describe命令:

#清單5.2使用 kubectl describe pod 來檢查pod$ kubectl describe pod kubiaName:         kubiaNamespace:    defaultPriority:     0Node:         worker2/172.18.0.4Start Time:   Mon, 27 Jan 2020 12:53:28 +0100...

清單沒有顯示整個輸出,但是如果您自己執行這個命令,您將看到使用kubectl get -o yaml命令列印完整物件清單時所看到的幾乎所有資訊。

觀察事件,看看底層發生了什麼

正如在前一章中使用description node命令檢查節點物件一樣,description pod命令應該在輸出的底部顯示幾個與pod相關的事件。

如果你還記得,這些事件不是物件本身的一部分,而是獨立的物件。讓我們列印它們,以瞭解建立pod物件時發生的更多事情。下面的清單顯示了建立pod後記錄的所有事件。

#清單5.3部署Pod物件後記錄的事件$ kubectl get eventsLAST SEEN   TYPE     REASON      OBJECT      MESSAGE<unknown>   Normal   Scheduled   pod/kubia   Successfully assigned default/                                             kubia to worker25m          Normal   Pulling     pod/kubia   Pulling image luksa/kubia:1.05m          Normal   Pulled      pod/kubia   Successfully pulled image5m          Normal   Created     pod/kubia   Created container kubia5mNormalStartedpod/kubiaStartedcontainerkubia

這些事件是按時間順序列印的。最近的事件在底部。您可以看到,首先將pod分配給一個工作節點,然後提取容器映象,然後建立容器並最終啟動。

沒有顯示任何警告事件,所以似乎一切正常。如果在您的叢集中不是這樣,那麼您應該閱讀第5.4節來學習如何對pod故障進行故障排除。

5.3 與應用和pod互動

您的容器現在正在執行。在本節中,您將學習如何與應用程式通訊,檢查其日誌,並在容器中執行命令以探索應用程式的環境。讓我們確認在容器中執行的應用程式是否響應您的請求。

5.3.1 向pod中的應用傳送請求

在第2章中,您使用kubectl expose命令建立了一個服務,該服務提供了一個負載均衡器,這樣您就可以與在pod中執行的應用程式進行通訊。現在您將採用一種不同的方法。為了開發、測試和除錯目的,您可能希望直接與特定的pod通訊,而不是使用將連線轉發到隨機選擇的pod的服務。

您已經瞭解到,每個pod都被分配了自己的IP地址,叢集中的每個其他pod都可以訪問它。這個IP地址通常是叢集內部的。您不能從本地計算機訪問它,除非Kubernetes以一種特定的方式部署——例如,在使用kind或Minikube而沒有VM來建立叢集時。

通常,要訪問pods,您必須使用以下部分中描述的方法之一。首先,讓我們確定pod的IP地址。

獲取pod的IP地址

通過檢索pod的完整YAML並在status部分中搜索podIP欄位,可以獲得pod的IP地址。或者,您可以使用kubectl description來顯示IP,但最簡單的方法是使用kubectl get命令,搭配wide 輸出選項:

$ kubectl get pod kubia -o wideNAME    READY   STATUS    RESTARTS   AGE   IP           NODE     ...kubia   1/1     Running   0          35m   10.244.2.4   worker2  ...

如IP欄所示,我pod的IP是10.244.2.4。現在我需要確定應用程式正在監聽的埠號。

獲取應用程式使用的埠號

如果我不是應用程式的作者,就很難找出應用程式監聽的是哪個埠。我可以檢查它的原始碼或容器映象的Dockerfile,因為這裡通常指定了埠,但我可能無法訪問它們。如果其他人建立了這個POD,我如何知道它正在監聽哪個埠?

幸運的是,您可以在pod定義本身中指定一個埠列表。沒有必要指定任何埠,這樣做是一個好主意。

為什麼在POD定義中指定容器埠

在pod定義中指定埠純粹是提供資訊的。它們的省略對客戶端是否可以連線到pod的埠沒有影響。如果容器通過繫結到其IP地址的埠接受連線,那麼任何人都可以連線到它,即使pod規範中沒有明確指定埠,或者指定了錯誤的埠號。

儘管如此,指定埠還是一個好主意,這樣任何訪問叢集的人都可以看到每個pod暴露了哪些埠。通過顯式定義埠,還可以為每個埠分配一個名稱,這在通過服務公開pods時非常有用。

pod清單表明,容器使用埠8080,因此您現在擁有了與應用程式對話所需的一切。

從工作節點連線到pod

Kubernetes網路模型規定,每個pod都可以從任何其他pod訪問,每個節點都可以到達叢集中任何節點上的任何pod。

因此,與pod通訊的一種方法是登入到一個工作節點並從那裡與pod通訊。您已經瞭解到,登入到節點的方式取決於部署叢集時使用的工具。如果你用的是kind,執行docker exec -it kind-worker bash,如果你用的是minikube ssh。在GKE上使用gcloud compute ssh命令。對於其他叢集,請參考它們的文件。

登入到節點後,使用curl命令和pod的IP和埠訪問應用程式。我的pod的IP是10.244.2.4,埠是8080,所以我執行以下命令:

$ curl 10.244.2.4:8080Heythere,thisiskubia.YourIPis::ffff:10.244.2.1.

通常不使用此方法與pods通訊,但如果存在通訊問題,並且希望首先嚐試儘可能短的通訊路徑來查詢原因,則可能需要使用此方法。在這種情況下,最好登入到pod所在的節點並從那裡執行curl。它和pod之間的通訊發生在本地,因此這種方法總是有最大的成功機會。

從臨時(一次性)客戶端pod連線

測試應用程式連線性的第二種方法是在另一個專門為此任務建立的pod中執行curl。使用此方法測試其他pod是否能夠訪問您的pod。即使網路執行良好,情況也可能並非如此。在第24章中,您將學習如何通過隔離pods來鎖定網路。在這樣的系統中,pod只能與它允許的pod通訊。

要在一個臨時pod中執行curl,使用以下命令:

$ kubectl run --image=tutum/curl -it --restart=Never --rm client-pod[CA] curl 10.244.2.4:8080Hey there, this is kubia. Your IP is ::ffff:10.244.2.5.pod"client-pod"deleted

這個命令執行一個pod,這個pod中包含一個從tutum/curl映象建立的容器。也可以使用提供curl二進位制可執行檔案的任何其他映象。-it選項將控制檯附加到容器的標準輸入和輸出,--restart=Never選項確保當curl命令及其容器終止時pod被認為已經完成,--rm選項在結束時刪除pod。pod的名稱為client-pod,在其容器中執行的命令為curl 10.244.24:8080。

請注意

您還可以修改該命令,以便在客戶機pod中執行bash shell,然後在shell中執行curl。

當您專門測試pod到pod的連線時,建立一個pod只是為了看看它是否可以訪問另一個pod是很有用的。如果您只想知道您的pod是否正在響應請求,可以使用下一節中介紹的方法。

通過kubectl port-forward連線到pods

在開發過程中,與執行在pod中的應用程式通訊的最簡單方法是使用kubectl port-forward命令,它允許您通過繫結到本地計算機上的網路埠的代理與特定的pod通訊,如下圖所示。

圖5.8 通過kubectl port-forward代理連線pod

要開啟與pod的通訊路徑,您甚至不需要查詢pod的IP,因為您只需要指定它的名稱和埠。下面的命令啟動一個代理,將您計算機的本地埠8080轉發到kubia pod的埠8080:

$ kubectl port-forward kubia 8080... Forwarding from 127.0.0.1:8080 -> 8080...Forwardingfrom[::1]:8080->8080

代理現在等待傳入的連線。在另一個終端上執行curl命令:

$ curl localhost:8080Heythere,thisiskubia.YourIPis::ffff:127.0.0.1.

如您所見,curl已經連線到本地代理並從pod接收響應。雖然port-forward命令是在開發和故障排除過程中與特定pod通訊的最簡單方法,但就底層發生的事情而言,它也是最複雜的方法。通訊通過幾個元件,因此,如果通訊路徑中出現任何問題,您將無法與pod進行通訊,即使pod本身可以通過常規通訊通道訪問。

請注意

kubectl port-forward命令還可以將連線轉發到服務,而不是pods,它還有其他一些有用的特性。執行kubectl port-forward --help瞭解更多資訊。

圖5.9顯示了網路資料包如何從curl程序流向您的應用程式並返回。

圖5.9使用埠轉發時curl和container之間的長通訊路徑

如圖,curl程序連線到代理,即連線到API伺服器,然後連線到pod所在節點的Kubelet,然後Kubelet通過pod的回送裝置(換句話說,通過本地主機地址)連線到容器。我相信您會同意通訊路徑非常長。

請注意

容器中的應用程式必須繫結到環回裝置(loopback device)上的埠,以便Kubelet到達它。如果它只監聽pod的eth0網路介面,那麼您將無法使用kubectl port-forward命令訪問它。

5.3.2 檢視應用日誌

Node.js應用程式將其日誌寫入標準輸出流。容器化的應用程式通常不會將日誌寫入檔案,而是記錄到標準輸出(stdout)和標準錯誤流(stderr)。這允許容器執行時環境攔截輸出,將其儲存在一致的位置(通常是/var/log/containers),並提供對日誌的訪問,而不必知道每個應用程式將其日誌檔案儲存在何處。

當你使用Docker在容器中執行一個應用程式時,你可以通過Docker logs <container-id>來顯示它的日誌。當您在Kubernetes中執行應用程式時,您可以登入到承載pod的節點,並使用docker logs顯示它的日誌,但是Kubernetes使用kubectl logs命令提供了一種更簡單的方法來實現這一點。

使用kubectl logs檢索pod的日誌

要檢視pod的日誌(更確切地說,是容器的日誌),在本地計算機上執行如下清單所示的命令:

#清單5.4顯示一個pod的日誌$ kubectl logs kubiaKubia server starting...Local hostname is kubiaListening on port 8080Received request for / from ::ffff:10.244.2.1#一個從節點內部發送的請求Received request for / from ::ffff:10.244.2.5#來自一次性客戶端pod的請求Receivedrequestfor/from::ffff:127.0.0.1#通過埠轉發傳送的請求

使用kubectl logs -f檢視日誌流

如果您想要實時地檢視應用程式日誌,以檢視每個傳入的請求,您可以使用--follow選項(或更短的版本-f)執行命令:

$kubectllogskubia-f

現在嚮應用程式傳送一些額外的請求,並檢視日誌。完成後,按ctrl-C停止傳輸日誌。

顯示每個記錄行的時間戳

您可能已經注意到,我們忘記在日誌語句中包含時間戳。沒有時間戳的日誌可用性有限。幸運的是,容器執行時將當前時間戳附加到應用程式生成的每一行。可以使用--timestamps=true選項來顯示這些時間戳,如下面的清單所示。

Listing 5.5 Displaying the timestamp of each log line$ kubectl logs kubia --timestamps=true2020-02-01T09:44:40.954641934Z Kubia server starting...2020-02-01T09:44:40.955123432Z Local hostname is kubia2020-02-01T09:44:40.956435431Z Listening on port 80802020-02-01T09:50:04.978043089Z Received request for / from ...2020-02-01T09:50:33.640897378Z Received request for / from ...2020-02-01T09:50:44.781473256ZReceivedrequestfor/from...

提示

您可以通過只輸入--timestamp而不輸入值來顯示時間戳。對於布林型選項,僅僅指定選項名就會將該選項設定為true。只輸入--timestamp適用於所有接受布林值並預設為false的kubectl選項。

顯示最近的日誌

如果您執行的第三方應用程式在其日誌輸出中不包含時間戳,則前面的特性非常好,但是每一行都有時間戳的事實給我們帶來了另一個好處:按時間過濾日誌行。Kubectl提供了兩種按時間過濾日誌的方法。

第一個選項是當您只想顯示過去幾秒、幾分鐘或幾小時的日誌時。例如,要檢視最近兩分鐘內產生的日誌,可以執行以下命令:

$kubectllogskubia--since=2m

另一個選項是使用--since-time選項顯示特定日期和時間之後生成的日誌。使用的時間格式為RFC3339。例如,列印2020年2月1日上午9:50以後的日誌:

$kubectllogskubia--since-time=2020-02-01T09:50:00Z

顯示日誌的最後幾行

不需要使用時間來限制輸出,您還可以指定從日誌末尾開始要顯示多少行。要顯示最後十行,請嘗試:

$kubectllogskubia--tail=10

請注意

接受值的Kubectl選項可以用等號或空格來指定。您也可以輸入--tail 10,或者--tail=10。

瞭解pod日誌的可用性

Kubernetes為每個容器保留一個單獨的日誌檔案。它們通常儲存在執行容器的節點的/var/log/containers中。為每個容器建立一個單獨的檔案。如果重新啟動容器,則將其日誌寫入新檔案。因此,如果在您使用kubectl logs -f跟蹤其日誌時重新啟動容器,則該命令將終止,您需要再次執行該命令來流化新容器的日誌。

kubectl logs命令只顯示當前容器的日誌。要檢視前一個容器中的日誌,請使用--previous(或-p)選項。

請注意

根據您的叢集配置,當日志文件達到一定大小時,也可以旋轉它們。在這種情況下,kubectl logs將只顯示當前的日誌檔案。流化日誌時,必須重新啟動命令,以便在日誌旋轉時切換到新檔案。

當您刪除一個pod時,它的所有日誌檔案也會被刪除。要使pods日誌永久可用,您需要設定一個集中的、叢集範圍的日誌系統。第23章對此作了解釋。

那麼將日誌寫入檔案的應用程式呢?

如果應用程式將其日誌寫入檔案而不是標準輸出,您可能想知道如何訪問該檔案。理想情況下,您應該配置集中式日誌記錄系統來收集日誌,以便可以在集中位置檢視它們,但有時您只是想保持簡單,不介意手動訪問日誌。在接下來的兩個小節中,您將學習如何將日誌和其他檔案從容器複製到您的計算機(以相反的方向),以及如何在執行容器中執行命令。您可以使用任何一種方法來顯示日誌檔案或容器中的任何其他檔案。

5.3.3 在容器之間複製檔案

有時,您可能希望向執行的容器新增檔案或從中檢索檔案。在執行容器中修改檔案不是您通常會做的事情——至少不是在生產環境中——但在開發過程中它可能很有用。

Kubectl提供cp命令,將檔案或目錄從本地計算機複製到任意pod的容器中,或者從容器複製到您的計算機中。例如,將kubia pod容器中的/etc/hosts檔案拷貝到本地檔案系統的/tmp目錄下,執行如下命令:

$kubectlcpkubia:/etc/hosts/tmp/kubia-hosts

要從本地檔案系統複製檔案到容器中,執行以下命令:

$kubectlcp/path/to/local/filekubia:path/in/container

請注意

kubectl cp命令要求在容器中存在tar二進位制檔案,但是這個要求將來可能會改變。

5.3.4 在執行容器中執行命令

當除錯在容器中執行的應用程式時,可能需要從內部檢查容器及其環境。Kubectl也提供了這個功能。可以使用kubectl exec命令執行容器檔案系統中存在的任何二進位制檔案。

在容器中呼叫單個命令

例如,kubia pod中的容器中執行的程序如下所示:

#清單5.6在pod容器中執行的程序$ kubectl exec kubia -- ps auxUSER  PID %CPU %MEM    VSZ   RSS TTY STAT START TIME COMMANDroot    1  0.0  1.3 812860 27356 ?   Ssl  11:54 0:00 node app.js#node.js伺服器root1200.00.1175002128?Rs12:220:00psaux#剛才呼叫的命令

你在第2章中使用Docker命令來研究正在執行的容器中的程序,這是Kubernetes的等價命令。它允許您在任何pod中遠端執行命令,而不必登入到承載該pod的節點。如果使用ssh在遠端系統上執行命令,那麼將看到kubectl exec與ssh沒有太大不同。

在5.3.1節中,你在一個一次性的客戶端pod中執行curl命令來發送請求到你的應用程式,但是你也可以在kubia pod中執行這個命令:

$ kubectl exec kubia -- curl -s localhost:8080Heythere,thisiskubia.YourIPis::1.

為什麼在KUBECTL EXEC命令中使用雙破折號?

命令中的雙破折號(--)將kubectl引數與要在容器中執行的命令分隔開。如果命令沒有以破折號開頭的引數,則沒有必要使用雙破折號。如果在前面的例子中忽略了雙破折號,那麼-s選項將被解釋為kubectl exec的一個選項,並導致以下誤導性錯誤:

$ kubectl exec kubia curl -s localhost:8080Theconnectiontotheserverlocalhost:8080wasrefused–didyouspecifytherighthostorport?

這看起來像是Node.js伺服器拒絕接受連線,但問題出在別處。curl命令永遠不會執行。當kubectl試圖在localhost:8080與Kubernetes API伺服器通訊時,這個錯誤是由kubectl自己報告的,而localhost:8080不是伺服器所在的位置。如果執行kubectl options命令,您將看到-s選項可用於指定Kubernetes API伺服器的地址和埠。kubectl沒有將該選項傳遞給curl,而是將其作為自己的選項。加上雙破折號可以防止這種情況發生。

幸運的是,為了防止出現這種情況,如果忘記使用雙破折號,更新版本的kubectl會返回一個錯誤。

在容器中執行互動式shell

前面的兩個示例展示瞭如何在容器中執行單個命令。當命令完成時,您將返回到shell。如果要在容器中執行多個命令,可以在容器中執行shell,如下所示:

$ kubectl exec -it kubia -- bashroot@kubia:/##在容器中執行的shell的命令提示符

-it是兩個選項的縮寫:-i和-t,這表示您希望通過將標準輸入傳遞給容器並將其標記為終端(TTY)來互動式地執行bash命令。

現在可以通過在shell中執行命令來探索容器的內部。例如,可以使用ls -la命令檢視容器中的檔案,使用ip link命令檢視容器中的網路介面,使用ping命令測試容器的連通性。您可以執行容器中可用的任何工具。

並不是所有的容器都允許執行shell

應用程式的容器映象包含許多重要的除錯工具,但並非每個容器映象都是如此。為了保持映象較小並提高容器中的安全性,生產環境中使用的大多數容器不包含任何二進位制檔案,只包含容器主程序所需的二進位制檔案。這大大減少了攻擊面,但也意味著您不能在生產容器中執行shell或其他工具。幸運的是,Kubernetes的一個新特性稱為臨時容器,它允許您通過將除錯容器附加到正在執行的容器上來除錯這些容器。

MEAP讀者請注意

臨時容器目前是alpha特性,這意味著它們可以隨時更改甚至刪除。這也是為什麼它們目前沒有在本書中解釋。如果在本書投入生產之前,它們已經進入測試階段,那麼將增加一個解釋它們的部分。

5.3.5 attach執行中的容器

kubectl attach命令是與正在執行的容器互動的另一種方式。它將自己附加到在容器中執行的主程序的標準輸入、輸出和錯誤流。通常,您只使用它與讀取標準輸入的應用程式互動。

使用kubectl attach檢視應用程式列印到標準輸出的內容

如果應用程式不從標準輸入讀取資料,那麼kubectl attach命令不過是將應用程式日誌寫入流的一種替代方法,因為這些日誌通常被寫入標準輸出和錯誤流,而attach命令將它們寫入流,就像kubectl logs -f命令那樣。

執行以下命令連線到你的kubia pod:

$ kubectl attach kubiaDefaulting container name to kubia.Use 'kubectl describe pod/kubia -n default' to see all of the containers in this pod.Ifyoudon'tseeacommandprompt,trypressingenter.

現在,當您在另一個終端中使用curl嚮應用程式傳送新的HTTP請求時,您將看到應用程式記錄到標準輸出的行,這些行也列印在執行kubectl attach命令的終端中。

使用kubectl attach寫入應用程式的標準輸入

kubia應用程式並不從標準輸入流中讀取資料,但是您可以在本書的程式碼歸檔中找到另一個版本的應用程式,它可以完成這一操作。您可以通過將其寫入應用程式的標準輸入流來更改應用程式響應HTTP請求的問候語。讓我們將這個版本的應用程式部署到一個新的pod中,並使用kubectl attach命令更改問候語。

您可以在kubia-stdin-image/目錄中找到構建映象所需的工件,或者您可以使用預先構建的映象docker.io/luksa/kubia:1.0-stdin。pod清單在kubia-stdin.yaml檔案中。和您以前部署的kubia的pod清單kubia.yaml只有一點點不同。下面的清單突出顯示了它們之間的區別。

Listing 5.7 Enabling standard input for a containerapiVersion: v1kind: Podmetadata:  name: kubia-stdin #這個pod名為kubia-stdinspec:  containers:  - name: kubia    image: luksa/kubia:1.0-stdin #它使用了kubia應用程式的特殊版本    stdin: true #應用程式需要從標準輸入流中讀取    ports:-containerPort:8080

正如您在清單中看到的,如果在pod中執行的應用程式想要從標準輸入中讀取資料,您必須在pod清單中通過將容器定義中的stdin欄位設定為true來實現這一點。這告訴Kubernetes為標準輸入流分配一個緩衝區,否則當應用程式試圖讀取該流時,它將始終接收到一個EOF。

使用kubectl apply命令從這個清單檔案建立pod:

$ kubectl apply -f kubia-stdin.yamlpod/kubia-stdincreated

要啟用與應用程式的通訊,再次使用kubectl port-forward命令,但是由於前面執行的port-forward命令仍然在使用本地埠8080,所以必須終止它或選擇一個不同的本地埠轉發到新的pod。你可以這樣做:

$ kubectl port-forward kubia-stdin 8888:8080Forwarding from 127.0.0.1:8888 -> 8080Forwardingfrom[::1]:8888->8080

命令列引數8888:8080指示命令將本地埠8888轉發到pod的埠8080。

您現在可以通過http://localhost:8888:訪問該應用程式

$ curl localhost:8888Heythere,thisiskubia-stdin.YourIPis::ffff:127.0.0.1.

讓我們使用kubectl attach將問候從“Hey there”更改為“Howdy”,以寫入應用程式的標準輸入流。執行如下命令:

$kubectlattach-ikubia-stdin

注意命令中附加選項-i的使用。它指示kubectl將其標準輸入傳遞給容器。

請注意

與kubectl exec命令一樣,kubectl attach也支援--tty或-t選項,這表明標準輸入是一個終端(tty),但是必須將容器配置為通過容器定義中的tty欄位分配一個終端。

現在,您可以在終端中輸入新的問候語並按下enter鍵。然後應用程式應該回復新的問候:

Howdy #輸入歡迎語並按<ENTER>Greetingsetto:Howdy#這是應用程式的響應

要檢視應用程式現在是否用新的問候語響應HTTP請求,請重新執行curl命令或在web瀏覽器中重新整理頁面:

$ curl localhost:8888Howdy,thisiskubia-stdin.YourIPis::ffff:127.0.0.1.

這是新的問候方式。您可以通過在終端中使用kubectl attach命令輸入另一行來再次更改它。要退出attach命令,請按Control-C或相應的鍵。

請注意

容器定義中的另一個欄位stdinOnce確定在attach會話結束時標準輸入通道是否關閉。預設情況下,它被設定為false,這允許您在每個kubectl attach 會話中使用標準輸入。如果將其設定為true,則標準輸入僅在第一個會話期間保持開啟狀態。

5.4 在pod中執行多個容器

5.2節中部署的kubia應用程式只支援HTTP。讓我們新增TLS支援,這樣它也可以通過HTTPS為客戶端服務。您可以通過編寫額外的程式碼來實現這一點,但是有一個更簡單的選項,您根本不需要修改程式碼。

您可以在一個sidecar容器中與Node.js應用程式一起執行一個反向代理(見5.1.2節),並讓它代表應用程式處理HTTPS請求。一個非常流行的能夠提供這種功能的軟體包叫做Envoy。Envoy proxy是一款高效能的開源服務代理,最初由Lyft開發,後來為本地雲端計算基金會(Cloud Native Computing Foundation)做了貢獻。把它加到你的pod裡。

5.4.1 使用特使代理擴充套件kubia Node.js應用

讓我簡要解釋一下應用程式的新體系結構將是什麼樣子的。如下圖所示,pod將有兩個容器——Node.js和新的Envoy容器。Node.js容器將繼續直接處理HTTP請求,但HTTPS請求將由Envoy處理。對於每個傳入的HTTPS請求,Envoy將建立一個新的HTTP請求,然後通過本地環回裝置(通過本地主機IP地址)將該請求傳送給Node.js應用程式。

圖5.10 pod容器和網路介面詳細檢視

Envoy還提供了一個基於web的管理介面,在下一章的一些練習中證明它很方便使用。

很明顯,如果您在Node.js應用程式本身內實現TLS支援,應用程式將消耗更少的計算資源和更低的延遲,因為不需要額外的網路跳動,但新增Envoy代理可能是一個更快更容易的解決方案。它還提供了一個很好的起點,您可以在此基礎上新增Envoy提供的許多其他特性,這些特性在應用程式程式碼本身中可能永遠不會實現。請參閱envoyproxy.io上的Envoy代理文件瞭解更多。

5.4.2 新增Envoy代理

您將建立一個帶有兩個容器的新pod。您已經獲得了Node.js容器,但還需要一個將執行Envoy的容器。

建立Envoy容器映象

該代理的作者在Docker Hub上釋出了官方代理容器映象。您可以直接使用此映象,但需要以某種方式向容器中的Envoy程序提供配置、證書和私鑰檔案。你將在第七章學習如何做到這一點。現在,您將使用一個已經包含所有三個檔案的映象。

我已經建立了映象,並在docker.io/luksa/kubia-ssl-proxy:1.0上提供了它,但是如果你想自己構建它,你可以在書的程式碼歸檔中找到kubia-ssl-proxy-image目錄下的檔案。

該目錄包含Dockerfile,以及代理將用於服務HTTPS的私鑰和證書。它還包含了配置檔案envoy.conf。在其中,您將看到代理被配置為偵聽埠8443,終止TLS,並將請求轉發到本地主機上的埠8080,Node.js應用程式正在偵聽埠8080。代理也被配置為在埠9901上提供管理介面,如前所述。

建立pod清單

構建映象之後,您必須為新的pod建立清單,如下面的清單所示。

#清單5.8 pod kubia-ssl (kubia-ssl.yaml)清單apiVersion: v1kind: Podmetadata:  name: kubia-ssl      #A                    spec:  containers:  - name: kubia    image: luksa/kubia:1.0    ports:    - name: http                #B      containerPort: 8080       #B  - name: envoy #C    image: luksa/kubia-ssl-proxy:1.0    ports:    - name: https               #D      containerPort: 8443       #D    - name: admin               #EcontainerPort:9901#E

#A執行Node.js應用的容器

#B Node.js監聽埠8080

#C執行特使代理的容器

#D代理的HTTPS埠

#E代理管理介面埠

這個pod的名稱是kubia-ssl。它有兩個容器:kubia和envoy。清單隻比第5.2.1節中的清單稍微複雜一點。唯一的新欄位是埠名稱,包含這些欄位是為了讓任何讀取清單的人都能理解每個埠號代表什麼。

建立一個pod

使用命令kubectl apply -f kubia-ssl.yaml從清單中建立pod。然後使用kubectl get和kubectl description命令確認pod的容器已成功執行。

5.4.3 與雙pod互動

當pod啟動時,您可以在pod中開始使用應用程式,檢查它的日誌並從內部探索容器。

與應用程式通訊

與前面一樣,您可以使用kubectl port-forward來啟用與pod中的應用程式的通訊。因為它公開了三個不同的埠,所以您可以像下面這樣向所有三個埠轉發:

$ kubectl port-forward kubia-ssl 8080 8443 9901Forwarding from 127.0.0.1:8080 -> 8080Forwarding from [::1]:8080 -> 8080Forwarding from 127.0.0.1:8443 -> 8443Forwarding from [::1]:8443 -> 8443Forwarding from 127.0.0.1:9901 -> 9901Forwardingfrom[::1]:9901->9901

首先,通過在瀏覽器中開啟URL http://localhost:8080或使用curl,確認你可以通過HTTP與應用程式通訊:

$ curl localhost:8080Heythere,thisiskubia-ssl.YourIPis::ffff:127.0.0.1.

如果可以,您還可以在https://localhost:8443上嘗試通過HTTPS訪問應用程式。使用curl,你可以這樣做:

$ curl https://localhost:8443 --insecureHeythere,thisiskubia-ssl.YourIPis::ffff:127.0.0.1.

成功!這位Envoy代理人完美地處理了這項任務。您的應用程式現在使用一個sidecar容器支援HTTPS。

為什麼必須使用--INSECURE選項

在訪問服務時必須使用--insecure選項有兩個原因。說明Envoy代理使用的證書為自簽名證書,且為域名example.com簽發。您通過本地kubectl proxy訪問該服務,並使用localhost作為URL中的域名,這意味著它與伺服器證書中的名稱不匹配。要使它匹配,你必須使用以下命令:

$curlhttps://example.com:8443--resolveexample.com:8443:127.0.0.1

這確保了證書與請求的URL匹配,但是由於證書是自簽名的,curl仍然不能驗證伺服器的合法性。您必須將伺服器的證書替換為由受信任的機構簽名的證書,或者使用--insecure標誌;在本例中,您也不需要費心使用--resolve標誌。

顯示多個容器的pod日誌

kubia-ssl pod包含兩個容器,因此如果要顯示日誌,必須使用--container或-c選項指定容器的名稱。以檢視kubia容器日誌為例,執行如下命令:

$kubectllogskubia-ssl-ckubia

Envoy代理執行在名為envoy的容器中,因此顯示其日誌如下:

$kubectllogskubia-ssl-cenvoy

或者,您可以使用--all-containers選項來顯示這兩個容器的日誌:

$kubectllogskubia-ssl--all-containers

您還可以將這些命令與第5.3.2節中介紹的其他選項組合使用。

在多容器pod的容器中執行命令

如果希望使用kubectl exec命令在pod的容器中執行shell或另一個命令,還可以使用--container或-c選項指定容器名。例如,在envoy容器中執行shell,執行如下命令:

$kubectlexec-itkubia-ssl-cenvoy--bash

請注意

如果不提供名稱,kubectl exec預設使用pod清單中指定的第一個容器。

5.5 在pod啟動時執行額外的容器

當一個pod包含多個容器時,所有的容器都是並行啟動的。Kubernetes還沒有提供一種機制來指定一個容器是否依賴於另一個容器,這將允許您確保一個容器在另一個容器之前啟動。但是,Kubernetes允許您在pod的主容器啟動之前執行一系列容器來初始化pod。本節將解釋這種特殊型別的容器。

5.5.1 init容器(init containers)介紹

pod清單可以指定一個要在pod啟動時和pod的正常容器啟動之前執行的容器列表。這些容器用於初始化pod,並被適當地稱為init容器。如下圖所示,它們一個接一個地執行,並且必須在pod的主容器啟動之前全部成功完成。

5.5.1 init容器(init containers)介紹

pod清單可以指定一個要在pod啟動時和pod的正常容器啟動之前執行的容器列表。這些容器用於初始化pod,並被適當地稱為init容器。如下圖所示,它們一個接一個地執行,並且必須在pod的主容器啟動之前全部成功完成。

圖5.11顯示pod的init容器和常規容器如何啟動的時間序列

Init容器與pod的常規容器類似,但它們不是並行執行的——一次只執行一個Init容器。

理解init容器可以做什麼

init容器通常被新增到pods中以實現以下目的:

  • 初始化pod主容器使用的卷中的檔案。這包括從安全證書儲存中檢索主容器使用的證書和私鑰、生成配置檔案、下載資料等等。
  • 初始化pod的網路系統。由於pod的所有容器共享相同的網路名稱空間,因此網路介面和配置也相同,因此init容器對它所做的任何更改也會影響主容器。
  • 延遲pod的主容器的啟動,直到滿足先決條件。例如,如果主容器在容器啟動之前依賴於另一個可用的服務,則init容器可能會阻塞,直到該服務就緒。
  • 通知外部服務pod即將開始執行。在某些特殊情況下,當應用程式的新例項啟動時,必須通知外部系統,可以使用init容器來傳遞此通知。

您可以在主容器本身中執行這些操作,但使用init容器有時是更好的選擇,並具有其他優點。讓我們看看這是為什麼。

理解什麼時候將初始化程式碼移動到init容器是有意義的

使用init容器來執行初始化任務不需要重新構建主容器映象,並且允許在許多不同的應用程式中重用單個init容器映象。如果希望將相同的特定於基礎設施的初始化程式碼注入到所有pod中,這一點特別有用。使用init容器還可以確保在任何(可能是多個)主容器啟動之前完成初始化。

另一個重要原因是安全性。通過將攻擊者可能使用的工具或資料從主容器轉移到init容器,可以減少pod的攻擊面。

例如,假設pod必須註冊到外部系統。pod需要某種祕密令牌來針對這個系統進行身份驗證。如果註冊過程是由主容器執行的,那麼這個祕密令牌必須存在於其檔案系統中。如果在主容器中執行的應用程式存在漏洞,允許攻擊者讀取檔案系統上的任意檔案,那麼攻擊者可能會獲得這個令牌。通過從init容器執行註冊,令牌必須僅在init容器的檔案系統中可用,這是攻擊者無法輕易破壞的。

5.5.2 向pod中新增init容器

在pod清單中,init容器在spec部分的initContainers欄位中定義,就像常規容器在其containers欄位中定義一樣。

在kubia-ssl pod中新增兩個容器

讓我們看一個向kubia pod新增兩個init容器的例子。第一個init容器模擬初始化過程。它執行5秒,同時將幾行文字列印到標準輸出。

第二個init容器使用ping命令執行網路連通性測試,檢查從pod中是否可以到達特定的IP地址。如果不指定IP地址,則使用地址1.1.1.1。如果你想自己構建它們,你可以在書的程式碼歸檔中找到Dockerfiles和其他工件。或者,您可以使用下面清單中指定的預構建映象。

包含這兩個init容器的pod清單在kubia-init.yaml檔案中。下面的清單顯示瞭如何定義init容器。

#清單5.9在pod清單中定義init容器Listing 5.9 Defining init containers in a pod manifest: kubia-init.yamlapiVersion: v1kind: Podmetadata:  name: kubia-initspec:  initContainers: #A  - name: init-demo #B    image: luksa/init-demo:1.0  - name: network-check #C    image: luksa/network-connectivity-checker:1.0  containers: #D  - name: kubia    image: luksa/kubia:1.0    ports:    - name: http      containerPort: 8080  - name: envoy    image: luksa/kubia-ssl-proxy:1.0    ports:    - name: https      containerPort: 8443    - name: admin      containerPort: 9901

#A init容器在initContainers欄位中指定

#B 首先執行這個容器

#C 在第一個容器完成後執行

#D 這些是pod裡的常規容器。他們同時執行。

如您所見,init容器的定義非常簡單。只指定每個容器的名稱和映象就足夠了。

請注意

容器名在所有init容器和常規容器的聯合中必須是唯一的。

部署帶有init容器的pod

在您從清單檔案中建立pod之前,在一個單獨的終端中執行以下命令,這樣您就可以看到在init和常規容器啟動時pod的狀態是如何變化的:

$kubectlgetpods-w

您還需要使用以下命令在另一個終端中檢視事件:

$kubectlgetevents-w

準備好後,執行apply命令建立pod:

$kubectlapply-fkubia-init.yaml

使用init容器檢查pod的啟動

當pod啟動時,檢查kubectl get events -w命令列印的事件。下面的清單顯示了您應該看到的內容。

#清單5.10 Pod事件顯示瞭如何執行init容器TYPE     REASON      MESSAGENormal   Scheduled   Successfully assigned pod to worker2Normal   Pulling     Pulling image "luksa/init-demo:1.0" #ANormal   Pulled      Successfully pulled imageNormal   Created     Created container init-demoNormal   Started     Started container init-demoNormal   Pulling     Pulling image "luksa/network-connec... #BNormal   Pulled      Successfully pulled imageNormal   Created     Created container network-checkNormal   Started     Started container network-checkNormal   Pulled      Container image "luksa/kubia:1.0" #C                     already present on machineNormal   Created     Created container kubiaNormal   Started     Started container kubiaNormal   Pulled      Container image "luksa/kubia-ssl-                     proxy:1.0" already present on machineNormal   Created     Created container envoyNormalStartedStartedcontainerenvoy

#A 第一個init容器的映象被拉出,容器被啟動

#B 在第一個init容器完成後,啟動第二個init容器

#C pod的兩個主要容器然後並行啟動

清單顯示了容器啟動的順序。首先啟動init-demo容器。當它完成時,網路檢查容器就會啟動,當它完成時,兩個主要容器kubia和envoy就會啟動。

現在檢查另一個終端中pod狀態的轉換。它們顯示在下一個清單中。

#清單5.11在啟動過程中涉及init容器的Pod狀態變化NAME         READY   STATUS            RESTARTS   AGEkubia-init   0/2     Pending           0          0skubia-init   0/2     Pending           0          0skubia-init   0/2     Init:0/2          0          0s #Akubia-init   0/2     Init:0/2          0          1s #Akubia-init   0/2     Init:1/2          0          6s #Bkubia-init   0/2     PodInitializing   0          7s #Ckubia-init   2/2     Running           0          8s #D

#A 第一個init容器正在執行

#B 第一個init容器已經完成,第二個正在執行

#C 所有初始化容器已經成功完成

pod的主要容器正在執行

如清單所示,當init容器執行時,pod的狀態顯示已經完成的init容器的數量和總數。當所有初始化容器都完成時,pod的狀態顯示為PodInitializing。此時,主容器的映象被拉出。當容器啟動時,狀態變為Running。

5.5.3 檢查init容器

與普通容器一樣,您可以使用kubectl exec在正在執行的init容器中執行額外的命令,並使用kubectl logs顯示日誌。

顯示init容器日誌

標準輸出和錯誤輸出(每個init容器都可以寫入其中)的捕獲方式與常規容器完全相同。使用kubectl logs命令可以顯示init容器的日誌,可以在容器執行時或完成後使用-c選項指定容器名稱。使用如下命令檢視kubia-init pod中的network-check容器日誌。

#清單5.12顯示init容器的日誌$ kubectl logs kubia-init -c network-checkChecking network connectivity to 1.1.1.1 ...Host appears to be reachable

日誌顯示network-check init容器執行時沒有錯誤。在下一章中,您將看到如果init容器失敗會發生什麼。

進入正在執行的init容器

您可以使用kubectl exec命令在init容器中執行shell或其他命令,就像使用普通容器一樣,但是隻能在init容器終止之前執行。如果您想自己嘗試一下,可以從kubia-init-slow.yaml檔案建立一個pod,它使init-demo容器執行60秒。當pod啟動時,用以下命令在容器中執行shell:

$ kubectl exec -it kubia-init-slow -c init-demo -- sh

您可以使用shell從內部探索容器,但時間很短。當容器的主程序在60秒後退出時,shell程序也會終止。

通常情況下,只有當init容器未能及時完成時,才需要進入正在執行的init容器,並且需要查詢原因。正常操作時,在執行kubectl exec命令之前,init容器會終止。

5.6 刪除pods和其他物件

如果您已經嘗試了本章和第2章中的練習,那麼您的叢集中現在存在幾個pods和其他物件。為了結束這一章,您將學習各種刪除它們的方法。刪除一個pod將終止其容器並從節點中刪除它們。刪除Deployment物件會導致其pods的刪除,而刪除負載均衡器型別的服務則會解除負載均衡器(如果已經配置了負載均衡器)。

5.6.1 按名稱刪除pod

刪除物件最簡單的方法是通過名稱刪除物件。

刪除單個pod

使用以下命令從叢集中移除kubia pod:

$ kubectl delete po kubiapod "kubia" deleted

通過刪除一個pod,表示您不再需要pod或其容器存在。Kubelet關閉pod的容器,刪除所有相關的資源,如日誌檔案,並在此過程完成後通知API伺服器。然後移除Pod物件。

提示

預設情況下,kubectl delete命令會等待物件不再存在。要跳過等待,可以執行帶有--wait=false選項的命令。

當pod處於關閉狀態時,狀態變為Terminating:

$ kubectl get po kubiaNAME    READY   STATUS        RESTARTS   AGEkubia   1/1     Terminating   0          35m

如果您希望應用程式為其客戶機提供良好的體驗,那麼準確地瞭解容器是如何關閉的是很重要的。這將在下一章中解釋,我們將深入到pod及其容器的生命週期中。

請注意

如果您熟悉Docker,您可能想知道是否可以停止pod,然後再重新啟動它,就像您可以使用Docker容器一樣。答案是否定的。使用Kubernetes,您只能完全刪除一個pod,然後稍後再建立它。

用一個命令刪除多個pod

您還可以用一個命令刪除多個pod。如果你執行kubia-init和kubia-init-slow pod,你可以通過指定它們的名字用空格隔開來刪除它們,如下所示:

$ kubectl delete po kubia-init kubia-init-slowpod "kubia-init" deletedpod "kubia-init-slow" deleted

5.6.2 刪除manifest檔案中定義的物件

每當您從一個檔案中建立物件時,您也可以通過將檔案傳遞給delete命令來刪除它們,而不是指定pod的名稱。

通過指定清單檔案刪除物件

您可以刪除從kubia-ssl.yaml檔案建立的kubia-ssl pod,使用以下命令:

$ kubectl delete -f kubia-ssl.yamlpod "kubia-ssl" deleted

在您的示例中,該檔案只包含一個pod物件,但是您通常會遇到包含多個不同型別的物件的檔案,這些物件代表一個完整的應用程式。這使得部署和刪除應用程式就像分別執行kubectl apply -f app.yaml和kubectl delete -f app.yaml一樣簡單。

從多個清單檔案中刪除物件

有時,應用程式是在幾個清單檔案中定義的。您可以指定多個檔案,用逗號分隔。例如:

$ kubectl delete -f kubia.yaml,kubia-ssl.yaml

請注意

您還可以使用此語法同時應用幾個檔案(例如:kubectl apply -f kubia.yaml,kubia-ssl.yaml)。

在使用Kubernetes的多年中,我實際上從未使用過這種方法,但我經常通過指定目錄名(而不是單個檔案的名稱)來部署檔案目錄中的所有清單檔案。例如,你可以在本書程式碼存檔的基目錄下執行以下命令來部署你在本章中建立的所有pod:

$ kubectl apply -f Chapter05/

這適用於目錄中所有具有正確副檔名(.yaml, .json,和類似的)。然後您可以使用相同的方法刪除pods:

$ kubectl delete -f Chapter05/

請注意

使用--recursive標誌也可以掃描子目錄。

5.6.3 刪除所有pod

現在,您已經刪除了除kubia-stdin和在第3章中使用kubectl create deployment命令建立的pod之外的所有pod。根據您擴充套件部署的方式,其中一些pods應該仍然在執行:

$ kubectl get podsNAME                    READY   STATUS    RESTARTS   AGEkubia-stdin             1/1     Running   0          10mkubia-9d785b578-58vhc   1/1     Running   0          1dkubia-9d785b578-jmnj8   1/1     Running   0          1d

不用通過名字刪除這些pod,我們可以使用--all選項將它們全部刪除:

$ kubectl delete po --allpod "kubia-stdin" deletedpod "kubia-9d785b578-58vhc" deletedpod "kubia-9d785b578-jmnj8" deleted

現在再次執行kubectl get pods命令確認沒有pod存在:

$ kubectl get poNAME                    READY   STATUS    RESTARTS   AGEkubia-9d785b578-cc6tk   1/1     Running   0          13skubia-9d785b578-h4gml   1/1     Running   0          13s

這是意想不到的!兩個pod仍在執行。如果你仔細看他們的名字,你會發現這兩個不是你剛剛刪除的。age欄也表明這些是新的pod。您也可以嘗試刪除它們,但是您將看到,無論您如何頻繁地刪除它們,都會建立新的pods來取代它們。

這些pods不斷彈出的原因是Deployment物件的存在。負責啟用Deployment物件的控制器必須確保pods的數量總是與物件中指定的副本的數量相匹配。當您刪除與部署相關聯的pod時,控制器將立即建立一個替代pod。

要刪除這些pods,您必須將Deployment擴充套件到0,或者完全刪除Deployment物件。這表明您不再希望這個部署或它的pod存在於叢集中。

5.6.4 刪除大多數物件

您可以使用下一個清單中顯示的命令刪除到目前為止所建立的所有內容——包括部署、它的pods和服務。

#清單5.13刪除所有型別的物件$ kubectl delete all --allpod "kubia-9d785b578-cc6tk" deletedpod "kubia-9d785b578-h4gml" deletedservice "kubernetes" deletedservice "kubia" deleteddeployment.apps "kubia" deletedreplicaset.apps "kubia-9d785b578" deleted

命令中的第一個all表示要刪除所有型別的物件。--all選項表示您想要刪除每個物件型別的所有例項。在上一節中嘗試刪除所有pod時使用了這個選項。

當刪除物件時,kubectl列印每個被刪除物件的型別和名稱。在前面的清單中,您應該看到它刪除了pods、部署和服務,還刪除了所謂的複製集物件。您將在第11章中瞭解這是什麼,在該章中,我們將更詳細地瞭解部署。

您將注意到,delete命令還刪除了內建的kubernetes服務。不要擔心這個問題,因為服務會在幾分鐘後自動重新建立。

使用此方法時,某些物件不會被刪除,因為關鍵字all不包括所有物件型別。這是一種預防措施,以防止您不小心刪除包含重要資訊的物件。事件物件型別就是一個例子。

請注意

在delete命令中可以指定多個物件型別。例如,您可以使用kubectl delete events,all --all來刪除事件以及all中包含的所有物件型別。

5.7 總結

在本章中,你學到了:

  • pod作為一個位於同一位置的組執行一個或多個容器。它們是部署和水平擴充套件的單元。一個典型的容器只執行一個程序。Sidecar容器補充pod中的主容器。

  • 如果容器必須一起執行,那麼它們只能是同一個pod的一部分。前端和後端程序應該在單獨的pod中執行。這允許它們單獨縮放。

  • 當pod啟動時,它的init容器一個接一個地執行。當最後一個init容器完成時,pod的主容器就會啟動。您可以使用init容器從內部配置pod,延遲其主容器的啟動,直到滿足先決條件,或通知外部服務pod即將開始執行。

  • kubectl工具用於建立pods、檢視它們的日誌、在容器中複製檔案、在這些容器中執行命令以及在開發過程中與單個pods通訊。