
用了這么多年 Kubernetes每次有新同事問我的第一個問題基本都集中在“Pod到底是什么”上。我太理解這種困惑了因為 Docker 時代大家腦子里已經(jīng)形成了“容器 運行單元”的思維定式結果一到 k8s 里發(fā)現(xiàn)跑起來的最小單元不是容器而是 Pod而且很多時候一個 Pod 里還能塞好幾個容器這跟以前的習慣完全不一樣。這篇文章我就把自己對 Pod 的理解、底層的運行機制、實際部署中的操作心得以及這幾年排查 Pod 問題總結的經(jīng)驗一次說清楚。內容從基礎概念一路講到 LNMP 這樣的多容器場景同時覆蓋到了二進制部署、Rancher、離線環(huán)境這些大家常問的落地方式適合正在學習 k8s 的運維和開發(fā)同學也適合已經(jīng)被 Pod 各種異常狀態(tài)折磨過的實戰(zhàn)派。1. 為什么會有 Pod 這個抽象層1.1 從容器到 Podk8s 為什么不做“容器級調度”先理清一個經(jīng)常被人搞混的點Docker 是容器運行時它負責的是“在單臺機器上把容器跑起來”而 k8s 是集群編排系統(tǒng)它關心的是“在多臺機器上怎么調度、怎么保證服務不掛”。這兩者的抽象粒度天然就不一樣。Docker 時代我們部署一個 web 服務直接把代碼打進去、端口映射出來、然后 docker run 一下就算完事。但到了真實的生產(chǎn)環(huán)境一個服務往往不是單獨一個進程就能搞定的。常見的場景是nginx 要轉發(fā)請求給后端 PHP-FPM同時需要一個日志采集進程在旁邊收日志或者業(yè)務主進程旁邊帶一個監(jiān)控上報進程定時把指標推給 Prometheus。如果用 Docker Compose這些配套進程就得各自啟動一個容器然后再通過 Compose 的網(wǎng)絡把它們連起來它們之間的依賴關系、共享存儲、通信方式都要你手工管理。k8s 的做法是直接在調度層面引入了 Pod 這個中間層。Pod 是一組“必須部署在同一臺宿主機上、資源聯(lián)合調度、生命周期完全一致”的容器集合。它不是把 Docker 容器包一層殼那么簡單而是改變了你組織應用的方式以前你在 Compose 里用“服務”來組織現(xiàn)在你在 k8s 里用“Pod”來組織。二者的區(qū)別在于Compose 的服務之間是“通過網(wǎng)絡調用”的關系而 Pod 里的多個容器是“共享同一個運行環(huán)境”的關系。k8s 之所以不下沉到直接調度容器核心原因是它需要一種機制來表達“這些進程必須緊緊地耦合在一起”的訴求。如果沒有 Pod 這一層兩個容器想要共享 localhost 通信、共享一個數(shù)據(jù)卷、保持同生共死就只能靠外部編排服務去強行管理復雜度會高得離譜。有了 Pod這些問題全部從“應用層的約定”變成了“基礎設施層的內置能力”。1.2 Pod 真正解決的兩個實際問題第一個問題是網(wǎng)絡。Pod 內的所有容器共享同一個網(wǎng)絡命名空間這意味著它們共享同一個 IP、同一個端口空間。舉例來說nginx 容器里只要配置 fastcgi_pass 127.0.0.1:9000就能直接訪問到同一個 Pod 里 PHP-FPM 容器監(jiān)聽的 9000 端口。這在實際部署 LNMP 的時候非常好用你不需要去拿 Service 或負載均衡去連同一個 Pod 內部的進程。第二個問題是存儲。Pod 內的容器可以共享同一個 Volume。最常見的是 emptyDir——一個隨 Pod 創(chuàng)建而創(chuàng)建、隨 Pod 銷毀而消失的臨時目錄。nginx 和 PHP-FPM 容器都掛載同一個代碼目錄代碼發(fā)布的時候只要更新一次掛載的卷兩個容器立刻就能同時看到新代碼。這在容器化的 PHP/Java 應用里非常典型。我見過不少從 Compose 遷到 k8s 的人一開始習慣把 nginx、PHP-FPM、MySQL 分別做成三個獨立 Deployment然后靠 Service 互相訪問。這種思路不是不行但你會發(fā)現(xiàn) nginx 和 PHP-FPM 之間的通信要經(jīng)過 Service 的轉發(fā)、要經(jīng)過 kube-proxy延遲和復雜度都上去了。事實上 nginx 和 PHP-FPM 是典型的“同生命周期應用”它們版本一起升級、部署一起變化更應該放進同一個 Pod。而 MySQL 這種有狀態(tài)的數(shù)據(jù)存儲則完全不同它需要獨立的數(shù)據(jù)持久化、獨立的擴縮容策略絕對不應該和 Web 容器擠在一個 Pod 里。理解了這一點你對 Pod 的編排邊界就有了基本的判斷力。2. Pod 的底層運行機制2.1 一次創(chuàng)建 Pod 時kubelet 在節(jié)點上做了什么很多教程上來就寫 yaml 文件但從來不解釋“你執(zhí)行 kubectl apply 之后發(fā)生了什么”。我建議每個想深入研究 k8s 的人都先把這個鏈路走一遍否則后面排障會非常痛苦。當你在控制平面執(zhí)行 kubectl apply 提交了一個 Pod 定義后請求會打到 API Server。API Server 把 Pod 對象寫入 etcd然后調度器kube-scheduler會 watch 到這個新 Pod根據(jù)它的資源請求、節(jié)點親和性、污點容忍等約束選一個最合適的節(jié)點并把調度結果寫回 API Server。接著目標節(jié)點上的 kubelet 會 watch 到這個 Pod開始在本地執(zhí)行容器創(chuàng)建流程。kubelet 創(chuàng)建 Pod 并不是直接拉起業(yè)務容器而是先啟動一個基礎設施容器在 containerd 里叫 sandbox在 Docker 時代叫 pause 容器。這個容器極其輕量它不跑任何業(yè)務邏輯唯一的職責是持有一組 Linux namespace——比如網(wǎng)絡命名空間、IPC 命名空間、UTS 命名空間。后續(xù)創(chuàng)建的業(yè)務容器全部通過 --networkcontainer:sandbox 這種方式加入同一個命名空間這樣就實現(xiàn)了 Pod 內容器共享網(wǎng)絡和通信域的效果。這也就解釋了為什么你在節(jié)點上執(zhí)行 docker ps或用 crictl 查看容器時會看到一堆“pause”開頭的容器。很多人第一次看到會覺得是殘留垃圾進程其實那是 Pod 存在的基礎。那個 pause 容器是整個 Pod 生命周期里最先啟動、最后刪除的容器它一掛整個 Pod 里的所有容器都會跟著重建。業(yè)務容器的創(chuàng)建流程也不止是 pull image 和 run container 兩步。kubelet 會依次處理初始化容器initContainer如果有、掛載 Volume、設置環(huán)境變量、設置資源 cgroup 限制、配置探針探針啟動后會周期性調用然后依次啟動普通容器。任何一個步驟失敗Pod 都會停在對應的狀態(tài)上這也是我們排障時觀察 Pod 事件Events的直接依據(jù)。2.2 探針、就緒、重啟策略與 Pod 狀態(tài)流轉搞明白了創(chuàng)建鏈路接著看 Pod 運行時的幾個關鍵機制。首先要分清 Pod 狀態(tài)和容器狀態(tài)——Pod 的狀態(tài)是對內容器狀態(tài)的聚合判斷常見的有 Pending、Running、Succeeded、Failed、Unknown但在實際開發(fā)環(huán)境中我們天天打交道的其實是 ContainerCreating、CrashLoopBackOff、ImagePullBackOff、Terminating 這些 kubectl 列表里展示的狀態(tài)。探針Probe是保證 Pod 可靠性的重要組件。livenessProbe 決定“容器活著嗎”如果一直失敗kubelet 會按 restartPolicy 殺掉容器并重啟readinessProbe 決定“容器可以對外提供服務了嗎”如果失敗kubelet 會把該 Pod 從 Service 的 Endpoints 里摘掉流量就不會打到它。startupProbe 是后來加的專門解決“啟動很慢的老 Java 應用”場景——它先于 liveness 執(zhí)行在 startup 成功之前l(fā)iveness 不會介入這樣可以避免應用啟動耗時太長被誤殺。這里要特別強調 restartPolicy 的一個坑Deployment 管理的 PodrestartPolicy 必須是 Always因為 Deployment 本身就是靠滾動重啟來實現(xiàn)發(fā)布和自愈的。如果你把 restartPolicy 改成 OnFailure 或 Never很多人會在這時發(fā)現(xiàn) Pod 時不時變成 Succeeded 或 Failed 狀態(tài)而不是被拉起然后一臉懵。這個限制從 API 校驗層面就會直接攔下你所以寫 yaml 的時候提前注意就好。還有一個容易被忽略的機制是容器的日志處理。Pod 里的容器如果崩潰頻繁kubelet 會按照一定周期做退避重啟拉起的間隔從 10s 開始20s、40s、80s……最長 5 分鐘之后恢復正常探測頻率。這個退避機制就是 CrashLoopBackOff 狀態(tài)的來源。很多新手一看到 CrashLoopBackOff 就以為是死循環(huán)了其實這只是“崩潰后退避等待中”的正常表現(xiàn)重點要看容器為什么崩潰也就是去查日志。3. 實際部署從最簡單到 LNMP3.1 單容器 Pod 的 yaml 長什么樣理論講完必須動手。先寫一個最簡單但完整的單容器 Pod 示例我有一個習慣是永遠不裸寫裸 Pod這里是為了教學先展示 Pod 最小定義生產(chǎn)環(huán)境后面我會強烈建議你換成 Deployment。apiVersion: v1 kind: Pod metadata: name: nginx-single namespace: default labels: app: nginx-single spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80 protocol: TCP resources: requests: cpu: 100m memory: 128Mi limits: cpu: 250m memory: 512Mi readinessProbe: httpGet: path: /nginx_status port: 80 initialDelaySeconds: 3 periodSeconds: 5 livenessProbe: httpGet: path: /nginx_status port: 80 initialDelaySeconds: 15 periodSeconds: 10執(zhí)行 kubectl apply -f 之后用 kubectl get pods -o wide 就能看到 Pod 被調度到哪臺節(jié)點拿到它的 Cluster IP。然后可以用 kubectl exec -it nginx-single -- curl 127.0.0.1 驗證一下。注意 resources 那一節(jié)里 cpu 的單位是 m毫核100m 代表 0.1 個 CPU 核心。內存單位是 Mi這是二進制兆。新手最常見的錯誤就是把 cpu 寫成 1表示 1 個完整核心內存寫成 1024表示 1024 字節(jié)然后發(fā)現(xiàn)調度和 limit 行為完全不符合預期。關于 readinessProbe 和 livenessProbe 的 pathnginx:1.25 官方鏡像里默認不帶 /nginx_status 這個模塊路徑如果你照抄這個配置會發(fā)現(xiàn)就緒探針一直失敗。平時用的話直接探 / 或者 /index.html 更保險。這種細節(jié)問題在測試環(huán)境驗證一下就能發(fā)現(xiàn)我列出來是提醒你不要被教程里的示例坑到。3.2 多容器協(xié)作用 Pod 內兩個容器搭建 LNMP 示例平時被問得特別多的問題就是“k8s 里怎么部署 LNMP”。網(wǎng)上能搜到一些“k8s lnmp 架構實驗”之類的教程但很多講得很含糊。這里我給出一套最經(jīng)典的 Pod 內雙容器方案nginx 容器和 PHP-FPM 容器放同一個 Pod共享代碼目錄nginx 把 PHP 請求轉發(fā)到本機 9000 端口PHP-FPM 處理完后把結果返回給 nginx。先看 Pod 的定義再把關鍵點拆開講。apiVersion: v1 kind: Pod metadata: name: lnmp-pod labels: app: lnmp spec: containers: - name: nginx image: nginx:1.25 volumeMounts: - name: web-code mountPath: /usr/share/nginx/html ports: - containerPort: 80 readinessProbe: httpGet: path: /index.php port: 80 initialDelaySeconds: 5 periodSeconds: 5 - name: php-fpm image: php:8.2-fpm volumeMounts: - name: web-code mountPath: /var/www/html ports: - containerPort: 9000 volumes: - name: web-code emptyDir: {}這段配置只用了幾個核心字段但表達了一個非常重要的概念nginx 和 php-fpm 兩個容器通過 emptyDir 共享了同一份代碼目錄這正是前面我講的 Pod 內容器共享 Volume 的實際應用。emptyDir 的生命周期等于 Pod 的生命周期Pod 刪了它就沒了所以這個方案適合驗證、實驗和臨時負載生產(chǎn)環(huán)境一般會用 PVC 把代碼卷換成持久化的。nginx 配置里要讓 PHP 請求轉到 php-fpm你可以用 ConfigMap 掛一個自定義的 nginx 配置核心是這一句location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name; include fastcgi_params; }關鍵點來了fastcgi_pass 寫的是 127.0.0.1:9000因為兩個容器共享網(wǎng)絡命名空間所以 nginx 容器里訪問 localhost:9000 可以直接到達 php-fpm 容器。這一點如果你拆成兩個 Deployment 就很難做到——你只能通過 Service 或 ClusterIP 連接配置和維護成本都會高不少。而 MySQL 在 LNMP 架構里我傾向于不放進同一個 Pod。它是典型的有狀態(tài)組件數(shù)據(jù)要持久化、要主從同步、要單獨的存儲和備份策略。一般做法是用 StatefulSet 獨立部署或者直接使用托管的數(shù)據(jù)庫服務。把 MySQL 硬塞進和 nginx、php 同一個 Pod短期實驗沒問題上了生產(chǎn)一定會因為數(shù)據(jù)卷生命周期和調度策略的問題吃大虧。3.3 Deployment 還是裸 Pod什么時候不該直接建 Pod上面兩個例子我為了講解概念都用的裸 Podkind: Pod。但生產(chǎn)環(huán)境我強烈建議你不要直接創(chuàng)建裸 Pod而是通過 Deployment、StatefulSet、DaemonSet 這些控制器來管理。為什么裸 Pod 如果所在的節(jié)點宕機了k8s 不會自動幫你在別的節(jié)點重建。但 Deployment 創(chuàng)建的 Pod 是有 ReplicaSet 這樣的控制器盯著Pod 突然掛掉它會開一個新的補齊節(jié)點掛了調度器也會在健康節(jié)點上重新創(chuàng)建。Deployment 還自帶滾動更新、回滾、擴縮容能力這些是裸 Pod 完全沒有的。簡單說Deployment 與 Pod 的關系就像系統(tǒng)進程和守護進程的關系你自己的代碼是那個 Pod而 supervisor 是那個 Deployment。沒有 supervisor 的話進程死了沒人管有 supervisor 的話它保證你需要的副本數(shù)永遠在線。所以實際生產(chǎn)中應用部署的規(guī)格應該長這樣apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80可以看到 Pod 的定義被挪到了 template 里這就是“Pod 模板”??刂破髫撠煿芾磉@份模板產(chǎn)出的所有 Pod 實例。剛開始用 k8s 時我喜歡用 kubectl run nginx --imagenginx --replicas3 試試手但后來發(fā)現(xiàn)這樣的自動化可維護性不強所以現(xiàn)在一律推薦用 yaml 文件管理方便進 Git方便審閱方便回滾。4. Pod 的資源模型、調度與網(wǎng)絡4.1 資源請求與限制背后的調度和驅逐邏輯Pod 里每個容器都可以聲明 resources.requests 和 resources.limits。requests 是調度依據(jù)limit 是運行限制。調度器只看 requests它要保證一臺節(jié)點上所有 Pod 的 requests 總和不超過節(jié)點可分配資源。也就是說哪怕節(jié)點內存其實有 64G但上面已存在的 Pod 請求了 50G再來一個新的 Pod 請求 20G那它就會 Pending直到有節(jié)點騰出空間或你擴容節(jié)點。limits 則由 kubelet 在啟動容器時配置成 cgroup 的上限。這里有個常見的認知誤區(qū)CPU 的限制是限流throttling容器超過了會被降速但內存的限制是硬限制容器只要嘗試分配超過 limit 的內存內核 OOM Killer 就會把進程殺掉kubelet 檢測到容器異常退出后再按策略重啟從而表現(xiàn)為 OOMKilled 狀態(tài)。根據(jù) request 和 limit 的設置方式Pod 會被劃分成三種 QoS 級別Guaranteed、Burstable、BestEffort。三者都設了 request 且等于 limit是 Guaranteed只有部分容器設或 request 小于 limit是 Burstable完全不設 resources是 BestEffort。當節(jié)點內存壓力過大時kubelet 會進入驅逐流程驅逐順序是 BestEffort 先被干掉然后是 Burstable最后才是 Guaranteed。這也是生產(chǎn)環(huán)境我給核心業(yè)務全設 requestlimit 的原因——我不想讓它成為資源緊張時第一個被犧牲的對象。這個資源模型也解釋了為什么二進制方式搭建 k8s 集群或離線部署時經(jīng)常有人遇到 Pod 調度失敗的問題節(jié)點容量明明看起來夠但可用資源被系統(tǒng)預留system-reserved、kube-reserved 這些參數(shù)扣了一部分然后 requests 就會被拒絕所以計算節(jié)點容量時要把預留資源考慮進去。4.2 調度約束從節(jié)點選擇到親和性默認情況下調度器根據(jù)資源 requests 選擇節(jié)點但很多時候我們需要主動控制 Pod 的去向。nodeSelector 是最簡單的方案比如給帶有 gputrue 標簽的節(jié)點專門調度 GPU 任務spec: nodeSelector: disktype: ssd更復雜一點的是節(jié)點親和性和 Pod 親和性。節(jié)點親和性支持硬性要求requiredDuringScheduling和軟性偏好preferredDuringScheduling軟性偏好會給節(jié)點打分得分高的優(yōu)先被選中但如果沒有滿足的節(jié)點也不會調度失敗。Pod 親和性解決的場景是“我想讓這些 Pod 盡量待在同一臺機器上”或者“絕對不要把有沖突的服務放一起”比如把 Web 和緩存放在同節(jié)點減少延遲而把兩個副本分散到不同可用區(qū)保證高可用。污點Taint和容忍Toleration是一個很容易被忽略但生產(chǎn)環(huán)境一定要明白的機制。節(jié)點有了污點默認所有 Pod 都不能調度上去除非 Pod 顯式容忍了這個污點。典型用途是給專用節(jié)點打污點只允許特定 Pod 進去或者用 NoExecute 污點把故障節(jié)點上的 Pod 全部驅逐出去。有時候 Pod 一直 Pending你用 kubectl describe 能看到類似 0/3 nodes are available: 3 node(s) had untolerated taint 的事件排查方法也就很清晰了。4.3 Pod 網(wǎng)絡IP 從哪來端口怎么通每個 Pod 在集群內部都有一個獨立的 IP這個 IP 由 CNI 插件分配。大部分默認安裝的集群用的是 Calico、Cilium 或 Flannel 這類方案Pod 會被分配一個與宿主機不同網(wǎng)段的地址。比如節(jié)點是 192.168.1.xPod 可能是 10.244.x.x各節(jié)點上的 Pod 可以通過 Overlay 網(wǎng)絡跨主機通信。Pod 內的容器共享同一個 IP 和網(wǎng)絡命名空間這就是前面講的 127.0.0.1:9000 能直達兄弟容器的原因。在 Pod 外部訪問 Pod 時通常有兩種方式一是同集群內通過 Service 的 ClusterIP二是調試時用 kubectl port-forward 把本地端口映射到 Pod 端口。還有一種是 hostNetwork: true讓 Pod 直接用節(jié)點網(wǎng)絡不走 CNI這類用法常見于對網(wǎng)絡性能極其敏感的組件或需要固定端口的系統(tǒng)組件。順帶提一個比較進階的方向如果需要給 Pod 配置多個網(wǎng)絡接口比如同時接入業(yè)務網(wǎng)和管理網(wǎng)就會用到 Multus 這種“多網(wǎng)絡插件”方案。Multus 本身不實現(xiàn)網(wǎng)絡它把多個 CNI 插件比如搭配 macvlan 或 ipvlan組合起來給 Pod 創(chuàng)建多個網(wǎng)卡并附加不同網(wǎng)絡的 IP。在一些需要 VLAN 隔離的部署場景比如 k8s multus 網(wǎng)絡 vlan 配置就是通過這種方案把 Pod 接入到不同的二層網(wǎng)絡中。這是個加分技能日常單網(wǎng)絡的集群用不上但遇到多網(wǎng)卡、VLAN 需求時你會非常感激這個設計。5. 排查實錄Pod 起不來的 10 種典型情況5.1 先學會看狀態(tài)、事件和日志排查 Pod 問題最忌諱的就是上來就刪除重建那樣你既看不到根因也可能把現(xiàn)場環(huán)境破壞了。正確順序應該是先 kubectl get pods 看整體狀態(tài)再 kubectl describe pod 看事件Events和容器狀態(tài)最后針對有問題的容器 kubectl logs 看日志。如果容器已經(jīng)崩潰重啟了加 --previous 參數(shù)看上一次啟動的日志很多問題就藏在那里。kubectl describe 輸出里面最重要的部分是 Events 字段它按時間順序記錄了 kubelet 對 Pod 做的所有動作和失敗原因。比如 FailedScheduling、Failed to pull image、Back-off restarting failed container 等每個關鍵事件后面一般都有原因和涉及的對象這足夠我們定位 80% 的問題。5.2 常見錯誤和排查建議速查表下面的表格是我根據(jù)這幾年實操整理出來的高頻 Pod 異常問題幾乎每個集群都用得上現(xiàn)象根本原因最常見排查方向Pending資源不足、節(jié)點有污點、調度約束不滿足describe 看 FailedScheduling 事件檢查 requests 是否超出節(jié)點可分配ImagePullBackOff鏡像拉取失敗檢查鏡像名、tag 是否正確私有倉庫認證是否正確離線環(huán)境是否有鏡像倉庫ErrImagePull鏡像不存在或倉庫無權限查看 describe 事件中的具體報錯not found / denied / timeoutCrashLoopBackOff應用啟動即崩潰或 liveness 探針失敗logs --previous 看上一次日志檢查啟動命令和探針配置Running 但沒 ReadyreadinessProbe 一直失敗檢查探針訪問的路徑、端口是否真的可訪問Running 但無法訪問Service 沒匹配到標簽、端口不一致檢查 Service selector 和 Pod labels 是否匹配檢查 targetPortContainerCreating 卡住存儲掛載不成功、CNI 網(wǎng)絡插件異常describe 看事件常見是 volume 掛載超時或 sandbox 創(chuàng)建失敗Terminating 卡住Pod 內有進程不響應 SIGTERM、finalizer 未完成檢查容器主進程是否處理了優(yōu)雅退出必要時 kubectl delete --forceOOMKilled容器內存超過 limit 被 OOM Killer 殺調大內存 limit 或優(yōu)化應用內存檢查 QoS 級別Unknown節(jié)點失聯(lián)kubelet 心跳中斷登錄節(jié)點查 kubelet 服務狀態(tài)檢查節(jié)點網(wǎng)絡和磁盤這些異常里我最想單獨說一下 OOMKilled它是“重啟后容器可以起來跑一會兒又掛”的常見元兇如果沒看日志大概率會被誤解成“應用代碼問題”。用 kubectl describe pod 看容器狀態(tài)里的 Last State如果顯示 Reason: OOMKilled那就是內存不夠不是業(yè)務代碼崩了。5.3 我實際踩過的坑和幾個現(xiàn)場經(jīng)驗第一個坑是離線部署時鏡像拉不動。內網(wǎng)環(huán)境里 IfNotPresent 這個鏡像拉取策略本來沒問題但如果你先手動 ctr -n k8s.io images import 導入了鏡像卻沒有把 tag 改成和 yaml 里完全一致kubelet 還是會去遠端拉。這個問題的排查時間往往特別長因為一切看起來都正常但 imagePullPolicy 不會自動糾錯。我的建議是離線環(huán)境一律顯式寫 imagePullPolicy: IfNotPresent并且部署前用 crictl images 核對節(jié)點上的鏡像 tag。第二個坑是探針的 initialDelaySeconds 設得太小。應用啟動需要 30 秒但 readinessProbe 的第 3 秒就開始探測結果連續(xù)失敗 3 次Pod 被標記未就緒流量就進不來。看起來像服務雪崩其實只是探針配置問題。如果你部署的是個啟動慢的 Java 應用我建議配合 startupProbe 一起用把 startupProbe 的 failureThreshold 調大等它啟動完畢后再接管后續(xù)探測。第三個坑是日志一直在刷但沒有關鍵信息。遇到 CrashLoopBackOff第一條命令我一般是 kubectl logs --previous --tail200如果還是沒有啟動報錯我會進容器手動執(zhí)行啟動命令看進程能否前臺運行。很多基礎鏡像默認通過 shell 腳本啟動shell 腳本里 cd 不存在的目錄或引用未注入的環(huán)境變量都會導致啟動分鐘級崩潰而這種問題看容器日志往往只是一個泛泛的退出碼非??简災托?。還有一個我覺得特別值得說的經(jīng)驗用 kubectl port-forward 臨時驗證 Pod 內部服務。比如我只想確認 nginx Pod 內部能否正常訪問 PHP寫 Service 之前可以先 port-forward 到本地直接 curl 一下。這比構建完整 Service 后再測試快很多也方便區(qū)分問題出在 Pod 自身還是出在 Service 層。6. 從 Pod 到集群常用命令與學習路徑建議6.1 Pod 相關命令必須滾瓜爛熟學 k8s 最忌諱是只會看 kubectl get pods完整排查一套流程下來以下命令基本缺一不可# 查看 Pod 列表和簡要狀態(tài) kubectl get pods -o wide # 查看 Pod 詳細信息重點是 Events 和容器狀態(tài) kubectl describe pod pod-name # 實時查看 Pod 日志 kubectl logs -f pod-name # 查看崩潰容器的上一次日志 kubectl logs pod-name --previous # 進入 Pod 內部容器 kubectl exec -it pod-name -- /bin/sh # 本地端口轉發(fā)到 Pod kubectl port-forward pod/pod-name 8080:80 # 查看節(jié)點資源占用排查調度失敗 kubectl top nodes # 查看所有命名空間下的 Pod kubectl get pods -A用 kubectl get pods 時我習慣加 -o wide因為它會顯示出 Pod IP 和所在的節(jié)點一眼就能看出調度分布是否合理。describe 和 top 是排查資源問題的兩大法寶不要省。6.2 學習閉環(huán)Pod 是入口但不要停在入口很多人問怎么快速上手 k8s我的建議是一致的先造一個小集群然后用 Pod 把你的第一個服務跑起來再把 Deployment、Service、Ingress 串起來打通“從 Pod 到對外訪問”這條鏈路然后逐步加探針、加資源限制、加自動擴縮容。Pod 是這個閉環(huán)的核心起點但學習不應止步于此。很多網(wǎng)上流傳的“k8s 經(jīng)典版”教程其實講的就是把單機 Docker Compose 的應用比如 LNMP搬進集群的過程這確實是最適合實戰(zhàn)的教學路徑。你從 docker compose 升級到 k8s 時第一件要適應的就是思維方式的變化Compose 里的 service 在 k8s 里可能是一個 Deployment Service或者一個 Pod 里的多容器Compose 里的 depends_on 在 k8s 里變成了探針配合優(yōu)雅退出Compose 里配置的端口映射在 k8s 里要讓位給 Service 和 Ingress。這些變化本質上都發(fā)生在 Pod 這一抽象層上。如果是用 Rancher 這類圖形界面管理集群也是一樣的道理——界面上減少了你敲命令的頻率但 Pod 的狀態(tài)、事件、日志這些核心信息不會變理解底層機制才能正確操作那些按鈕。二進制部署 k8s 則更鍛煉你對組件和網(wǎng)絡的理解至少你親手搭過一遍集群之后再遇到“Pod 無法跨節(jié)點通信”“kubelet 沒起來導致 Pod 一直 Pending”這類問題定位速度會快得多。7. 最后的幾個小建議回到標題“k8s 中的 Pod”寫了這么多最后說幾個實操層面的個人感悟。一個是我每次教學和排查時都會強調遇到問題先 kubectl describe pod這比任何調試工具都值得依賴。因為 describe 里的 Events 記錄了 kubelet 對 Pod 做過的每一個關鍵動作和失敗原因很多時候問題根因已經(jīng)寫在里面只是你沒看。另一個是寫 yaml 時盡量把標簽labels寫規(guī)范。Pod 的標簽是 Service、Deployment、監(jiān)控告警相互關聯(lián)的橋梁標簽設計混亂會直接導致 Service 選不上 Pod、監(jiān)控抓不到目標、滾動更新誤傷其他工作負載。哪怕你用的是 Rancher 這種圖形化工具標簽和 selector 的匹配邏輯也不會變。最后一點是千萬別怕實驗時把 Pod 搞掛。k8s 的聲明式設計讓你可以隨便刪除、重建、滾動更新試錯的成本很低。真正值得投入時間的不是記住每個命令參數(shù)而是理解 Pod 在整個調度、網(wǎng)絡、存儲模型里的位置——把這個抽象層吃透了后面學 StatefulSet、DaemonSet、Operator、自定義控制器你會發(fā)現(xiàn)全都是同一個底層邏輯在延伸。我從 Docker 單機時代走到現(xiàn)在最深刻的體會就是Pod 不是容器之上多套了一個概念它是理解 k8s 一切編排能力的地基。