到Deployment滾動(dòng)更新與Pod配額排障課件)
簡(jiǎn)介面向 Kubernetes 初學(xué)者與云原生運(yùn)維人員的這套課件圍繞集群架構(gòu)、核心對(duì)象和日常管理展開(kāi)幫助建立系統(tǒng)化的 k8s 知識(shí)框架。資源包為 1 個(gè) pptx 文件約 1.25MB篇幅集中便于按章節(jié)瀏覽和課堂演示。內(nèi)容先說(shuō)明 apiserver、kubelet、kube-proxy 等組件職責(zé)再梳理 Pod、Controllers、Service、Label、Namespace 等對(duì)象。結(jié)合 kubectl 命令與 YAML 清單展示集群狀態(tài)查看、資源監(jiān)控、日志排查并串聯(lián) Deployment 的創(chuàng)建、發(fā)布、升級(jí)、擴(kuò)縮容和回滾流程同時(shí)延伸調(diào)度、存儲(chǔ)與集群安全。目前已有 2191 人學(xué)習(xí)適合入門(mén)梳理與面試前復(fù)盤(pán)。1. 一份把 K8s 從概念講到配額邊界的課件適合誰(shuí)啃剛拿到 kubeconfig 的人最常遇到的問(wèn)題不是 YAML 縮進(jìn)寫(xiě)錯(cuò)而是 Pod 起不來(lái)時(shí)只會(huì)反復(fù)kubectl get pods刷新說(shuō)不清 apiserver、etcd、kubelet 各自卡在鏈路哪一段。這份 K8s 課件針對(duì)的正是這個(gè)薄弱點(diǎn)從集群架構(gòu)與組件職責(zé)入手接著是監(jiān)控與日志、應(yīng)用生命周期管理、調(diào)度、存儲(chǔ)、集群安全六塊內(nèi)容按「先知道誰(shuí)在干活再知道怎么讓它干活」的順序排布。它不適合已經(jīng)能順手寫(xiě) Operator 的人但對(duì)剛接手集群、需要獨(dú)立把一個(gè) Deployment 從創(chuàng)建推到升級(jí)再回滾的運(yùn)維和后端更合適。下面我按自己復(fù)盤(pán)的順序拆開(kāi)重心放在命令背后的參數(shù)含義和幾個(gè)容易出錯(cuò)的邊界上。2. 集群架構(gòu)拆開(kāi)看apiserver、etcd、kubelet 各自守哪一段2.1 控制面四個(gè)組件的職責(zé)邊界很多人把控制面當(dāng)成一個(gè)整體其實(shí)它內(nèi)部的分工很明確邊界一旦搞混排查方向就會(huì)偏。kube-apiserver 是集群唯一入口所有組件彼此不直接通信只跟 apiserver 通信apiserver 也是唯一能讀寫(xiě) etcd 的進(jìn)程。一個(gè)請(qǐng)求進(jìn)來(lái)要過(guò)三道關(guān)認(rèn)證證書(shū)、Token 或 ServiceAccount、鑒權(quán)RBAC 規(guī)則、準(zhǔn)入控制各種 Admission 插件全過(guò)了才落盤(pán)。這套設(shè)計(jì)的好處是審計(jì)點(diǎn)集中壞處是 apiserver 一旦不可用kubectl全線報(bào)錯(cuò)但已經(jīng)在跑的 Pod 不受影響因?yàn)?kubelet 還在按本地緩存的期望狀態(tài)干活。kube-controller-manager 是一堆控制器的集合體Deployment、ReplicaSet、Node、Endpoint、ServiceAccount 各有一個(gè)控制器在跑。每個(gè)控制器本質(zhì)是個(gè)死循環(huán)讀期望狀態(tài)spec讀實(shí)際狀態(tài)status算差值發(fā)指令。這里有個(gè)容易被忽略的嵌套關(guān)系Deployment 控制器并不直接管 Pod它管的是 ReplicaSetReplicaSet 再管 Pod。正因?yàn)槔习姹镜?ReplicaSet 會(huì)被保留并縮到 0 而不是刪掉回滾才有歷史可退。kube-scheduler 只做一件事給新創(chuàng)建、還沒(méi)綁定節(jié)點(diǎn)的 Pod 選一個(gè) Node然后寫(xiě)一個(gè) Binding 對(duì)象把spec.nodeName填上。它不負(fù)責(zé)把容器啟動(dòng)起來(lái)啟動(dòng)是 kubelet 的活。調(diào)度分兩階段Filter 先過(guò)濾掉不滿(mǎn)足條件的節(jié)點(diǎn)資源不夠、污點(diǎn)不匹配、端口沖突、親和性不符Score 再對(duì)剩下的節(jié)點(diǎn)打分。所以看到 Pod 處于 Pending 且 Events 里寫(xiě)著FailedScheduling問(wèn)題一定在調(diào)度器這一側(cè)不用去翻容器日志。etcd 是分布式鍵值存儲(chǔ)默認(rèn)監(jiān)聽(tīng) 2379/2380正常配置下只允許 apiserver 訪問(wèn)。集群里所有對(duì)象狀態(tài)都在它里面某種意義上刪掉 etcd 數(shù)據(jù)目錄等于刪掉整個(gè)集群。組件位置核心職責(zé)掛掉后的典型表現(xiàn)kube-apiserver控制面唯一入口認(rèn)證鑒權(quán)準(zhǔn)入讀寫(xiě) etcdkubectl 全部報(bào)錯(cuò)已運(yùn)行 Pod 不受影響kube-controller-manager控制面控制器循環(huán)把實(shí)際狀態(tài)拉向期望狀態(tài)副本數(shù)不收斂Service 沒(méi)有 Endpointskube-scheduler控制面為新 Pod 選擇節(jié)點(diǎn)并綁定新 Pod 一直 Pendingetcd控制面保存全部集群狀態(tài)apiserver 起不來(lái)集群事實(shí)性丟失kubelet節(jié)點(diǎn)管理本機(jī) Pod 生命周期該節(jié)點(diǎn)上的 Pod 狀態(tài)不再更新kube-proxy節(jié)點(diǎn)維護(hù) Service 的四層轉(zhuǎn)發(fā)規(guī)則Service 通但后端訪問(wèn)不到2.2 節(jié)點(diǎn)側(cè)kubelet 和 kube-proxy 不是一回事kubelet 是節(jié)點(diǎn)上的代理它做的是向 apiserver 注冊(cè)節(jié)點(diǎn)、watch 到綁定給自己的 Pod、通過(guò) CRI 調(diào)用容器運(yùn)行時(shí)創(chuàng)建容器、掛載數(shù)據(jù)卷、拉取 Secret 和 ConfigMap、執(zhí)行存活與就緒探針、定時(shí)上報(bào)節(jié)點(diǎn)和 Pod 狀態(tài)。kubelet 把每個(gè) Pod 翻譯成一組容器操作這個(gè)翻譯過(guò)程出錯(cuò)Pod 就卡在 ContainerCreating 或者一直重啟。kube-proxy 完全不參與容器創(chuàng)建它 watch Service 和 Endpoints 的變化在節(jié)點(diǎn)上寫(xiě) iptables 或 ipvs 規(guī)則實(shí)現(xiàn)四層負(fù)載均衡。區(qū)分方法很簡(jiǎn)單Pod 起不來(lái)看 kubelet 和容器運(yùn)行時(shí)Pod 起來(lái)了但 Service 訪問(wèn)不通先看 Endpoints 有沒(méi)有后端再看 kube-proxy 的規(guī)則。2.3 用幾條命令把組件狀態(tài)驗(yàn)證一遍課件里給了kubectl cluster-info但單靠這一條信息量太少。我一般按下面這個(gè)順序過(guò)一遍每條命令看什么、返回什么算正常心里要有數(shù)。# 集群入口信息apiserver 地址和集群 DNS 服務(wù)地址 kubectl cluster-info # 控制面組件是否都是 Running以及各自落在哪個(gè)節(jié)點(diǎn) kubectl get pods -n kube-system -o wide # 直接打 apiserver 的健康端點(diǎn)返回 ok 說(shuō)明 apiserver 自身活著 kubectl get --raw /healthz # 看單個(gè)節(jié)點(diǎn)的容量、已分配資源、污點(diǎn)和最近事件 kubectl describe node node-name第一條命令的返回值里如果 DNS 那行是10.96.0.10:53這類(lèi)地址說(shuō)明集群服務(wù)網(wǎng)段正常。第二條要重點(diǎn)看kube-controller-manager、kube-scheduler、etcd這幾個(gè)靜態(tài) Pod 的 RESTARTS 次數(shù)短時(shí)間反復(fù)重啟通常意味著證書(shū)過(guò)期或者磁盤(pán)壓力。第三條--raw是直接向 apiserver 發(fā)原始請(qǐng)求繞開(kāi) kubectl 的客戶(hù)端邏輯用來(lái)判斷「是 apiserver 掛了還是 kubectl 配置錯(cuò)了」特別快。注意網(wǎng)上很多老教程會(huì)寫(xiě)kubectl get componentstatuses這個(gè)接口在較新版本里已經(jīng)廢棄輸出的警告比內(nèi)容還長(zhǎng)。用kubectl get --raw /healthz配合kubectl get pods -n kube-system替代更靠譜也不用記一堆縮寫(xiě)。2.4 etcd 快照唯一的后悔藥集群層面最該養(yǎng)成的習(xí)慣是給 etcd 做快照。誤刪一個(gè) Namespace 會(huì)連帶刪掉里面所有對(duì)象控制器不會(huì)幫你恢復(fù)因?yàn)閷?duì)它來(lái)說(shuō)那就是期望狀態(tài)變了。# 在控制面節(jié)點(diǎn)執(zhí)行證書(shū)路徑按實(shí)際安裝位置修改 ETCDCTL_API3 etcdctl \ --endpointshttps://127.0.0.1:2379 \ --cacert/etc/kubernetes/pki/etcd/ca.crt \ --cert/etc/kubernetes/pki/etcd/server.crt \ --key/etc/kubernetes/pki/etcd/server.key \ snapshot save /var/backups/etcd-$(date %F-%H%M).db # 校驗(yàn)快照完整性輸出里 hash 和 revision 都要有值 etcdctl snapshot status /var/backups/etcd-20240101-1200.db --write-outtable--endpoints指向本機(jī) etcd 客戶(hù)端端口三個(gè)證書(shū)分別對(duì)應(yīng) CA、客戶(hù)端證書(shū)和私鑰缺一個(gè)都會(huì)報(bào)context deadline exceeded這個(gè)報(bào)錯(cuò)很有迷惑性看起來(lái)像網(wǎng)絡(luò)不通實(shí)際多數(shù)是證書(shū)路徑寫(xiě)錯(cuò)。snapshot save是熱備份不需要停 etcd但恢復(fù)時(shí)必須停掉 apiserver 和 etcd 再執(zhí)行snapshot restore恢復(fù)動(dòng)作不要在業(yè)務(wù)時(shí)段做。備份文件放本地磁盤(pán)意義有限節(jié)點(diǎn)掛了就一起沒(méi)了我一般會(huì)再同步到集群外的存儲(chǔ)。3. 從 kubectl 到 YAML把一個(gè) Deployment 推到回滾的完整閉環(huán)3.1 命令式創(chuàng)建先用兩條命令跑通初學(xué)階段別急著寫(xiě) YAML先用命令式把對(duì)象關(guān)系跑通看到 Deployment、ReplicaSet、Pod 三層是怎么長(zhǎng)出來(lái)的比背字段有用得多。# 創(chuàng)建一個(gè) Deployment鏡像用內(nèi)網(wǎng)倉(cāng)庫(kù)地址 kubectl create deployment web --imageregistry.local/demo/java-web:1.0 # 查看 Deployment 和 Pod-o wide 能看到 Pod 落在哪個(gè)節(jié)點(diǎn) kubectl get deploy,pods -o wide # 暴露服務(wù)Service 80 端口轉(zhuǎn)發(fā)到容器 8080 kubectl expose deployment web \ --port80 \ --target-port8080 \ --typeNodePort \ --nameweb kubectl get svc web--port是 Service 自身監(jiān)聽(tīng)的端口集群內(nèi)其他 Pod 通過(guò)web:80訪問(wèn)--target-port是容器實(shí)際監(jiān)聽(tīng)的端口寫(xiě)錯(cuò)這個(gè)是最常見(jiàn)的 502 來(lái)源--typeNodePort會(huì)在 30000–32767 區(qū)間隨機(jī)分配一個(gè)節(jié)點(diǎn)端口--name不寫(xiě)的話默認(rèn)與 Deployment 同名。創(chuàng)建完立刻kubectl get rs會(huì)看到一個(gè)名字形如web-7d9f8c6b5的 ReplicaSetPod 名字再在后面加隨機(jī)后綴。這個(gè)三層結(jié)構(gòu)不是冗余設(shè)計(jì)Deployment 負(fù)責(zé)版本和滾動(dòng)策略ReplicaSet 負(fù)責(zé)某一版本的副本數(shù)Pod 才是真正被調(diào)度的單元。理解了這一層后面rollout history為什么能列出多條記錄就順理成章了。3.2 YAML 逐字段拆apiVersion 到 containers命令式適合驗(yàn)證生產(chǎn)必須落到 YAML否則改一個(gè)副本數(shù)都要記參數(shù)。下面這份是課件里 Deployment 的加厚版把滾動(dòng)更新策略和資源配額一起帶上。apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: default labels: app: nginx spec: replicas: 3 selector: matchLabels: app: nginx # 必須與 template.metadata.labels 完全匹配 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 # 滾動(dòng)時(shí)最多超出期望副本數(shù) 1 個(gè) maxUnavailable: 0 # 滾動(dòng)時(shí)不允許有副本不可用 revisionHistoryLimit: 10 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 imagePullPolicy: IfNotPresent ports: - containerPort: 80 resources: requests: cpu: 250m memory: 64Mi limits: cpu: 500m memory: 128MiapiVersion和kind是配對(duì)的寫(xiě)錯(cuò)組合會(huì)直接報(bào)no matches for kind不確定時(shí)用kubectl api-resources | grep deployment查當(dāng)前集群支持哪個(gè)版本。selector.matchLabels與template.metadata.labels不匹配時(shí) apply 會(huì)被拒絕報(bào)錯(cuò)信息是selector does not match template labels這條報(bào)錯(cuò)很直白照著改就行。replicas是期望副本數(shù)但控制器實(shí)際收斂需要幾秒剛 apply 完立刻查可能只有 1 個(gè) Pod屬于正?,F(xiàn)象。revisionHistoryLimit默認(rèn)就是 10意味著最多保留 10 個(gè)歷史 ReplicaSet調(diào)小能省 etcd 空間但會(huì)讓可回滾的版本變少。3.3 升級(jí)、擴(kuò)縮容與回滾的操作序列# 升級(jí)鏡像改的是 Pod 模板會(huì)觸發(fā)滾動(dòng)更新 kubectl set image deployment/web nginxnginx:1.25 # 阻塞等待滾動(dòng)完成超時(shí)退出碼非 0適合放進(jìn)腳本 kubectl rollout status deployment/web --timeout120s # 查看歷史版本REVISION 列就是回滾時(shí)要用的編號(hào) kubectl rollout history deployment/web # 回到指定版本 kubectl rollout undo deployment/web --to-revision2 # 手動(dòng)擴(kuò)縮容 kubectl scale deployment web --replicas10 # 配置自動(dòng)擴(kuò)縮容CPU 超過(guò) 80% 時(shí)最多擴(kuò)到 15 個(gè) kubectl autoscale deployment web --min10 --max15 --cpu-percent80rollout status是我最推薦放進(jìn) CI 的一條命令它比sleep 30靠譜得多滾動(dòng)失敗時(shí)會(huì)立刻返回而不是傻等。--to-revision有一個(gè)容易踩的點(diǎn)回滾本身會(huì)產(chǎn)生一個(gè)新的 revision 編號(hào)比如當(dāng)前是 3回滾到 2 之后歷史里會(huì)多出一個(gè) 4內(nèi)容與 2 一致。所以腳本里寫(xiě)死 revision 號(hào)遲早出問(wèn)題更穩(wěn)的做法是記鏡像 tag。scale和autoscale同時(shí)存在會(huì)打架。HPA 接管之后會(huì)周期性改寫(xiě)spec.replicas手動(dòng) scale 上去的數(shù)字過(guò)一會(huì)兒就被覆蓋回來(lái)排查時(shí)看到副本數(shù)自己變先查kubectl get hpa有沒(méi)有對(duì)應(yīng)的規(guī)則。3.4 滾動(dòng)更新參數(shù)maxSurge 與 maxUnavailable 的實(shí)際影響這兩個(gè)參數(shù)決定了滾動(dòng)更新的節(jié)奏也是「升級(jí)時(shí)服務(wù)到底會(huì)不會(huì)抖」的直接原因。maxSurge: 1加maxUnavailable: 0是最保守的組合先起一個(gè)新 Pod等它就緒再停一個(gè)舊的全程副本數(shù)只多不少。代價(jià)是需要集群有富余資源如果節(jié)點(diǎn)資源剛好卡滿(mǎn)新 Pod 調(diào)度不上去滾動(dòng)就會(huì)永遠(yuǎn)停在中間狀態(tài)。maxSurge: 0加maxUnavailable: 1則相反先停舊的再起新的省資源但會(huì)短暫少一個(gè)副本對(duì)流量敏感的服務(wù)不要這么配。兩者不能同時(shí)為 0否則滾動(dòng)無(wú)法推進(jìn)apply 時(shí)會(huì)直接被拒。滾動(dòng)卡住的排查路徑很固定先kubectl rollout status看卡在哪一步再kubectl describe rs 新 RS 名看 Events。如果是FailedScheduling就是資源或親和性問(wèn)題如果是Readiness probe failed就是應(yīng)用起來(lái)了但沒(méi)準(zhǔn)備好接收流量這時(shí)候要去查就緒探針的路徑和端口而不是懷疑鏡像。4. Pod 為什么不是容器的簡(jiǎn)單包裝infra container、拉取策略與配額4.1 共享網(wǎng)絡(luò)是怎么做到的Pod 是 K8s 的最小調(diào)度單元但它本身是個(gè)邏輯概念節(jié)點(diǎn)上并沒(méi)有一個(gè)叫 Pod 的東西。實(shí)現(xiàn)方式是每個(gè) Pod 先起一個(gè)極小的 infra container也叫 pause 容器由它持有網(wǎng)絡(luò)命名空間然后把業(yè)務(wù)容器 join 進(jìn)這個(gè)命名空間。于是同一 Pod 內(nèi)的容器看到的網(wǎng)卡、IP、路由表完全一致可以直接用127.0.0.1通信。這解釋了幾個(gè)日?,F(xiàn)象同 Pod 內(nèi)容器不能監(jiān)聽(tīng)同一個(gè)端口因?yàn)楣蚕硗粡埦W(wǎng)卡nginx 加 php-fpm 這種組合放同一個(gè) Pod 里nginx 直接fastcgi_pass 127.0.0.1:9000就行不用配 ServicePod 重建后 IP 會(huì)變因?yàn)樗玫降氖切碌木W(wǎng)絡(luò)命名空間。存儲(chǔ)共享走的是另一條路靠 volume 掛載而不是命名空間。多個(gè)容器把同一個(gè) emptyDir 掛到各自路徑下就能看到同樣的文件。課件里那個(gè) nginx 日志的例子就是這個(gè)思路把容器內(nèi)的日志目錄掛到宿主機(jī)方便采集。apiVersion: v1 kind: Pod metadata: name: my-pod spec: containers: - name: web image: registry.local/demo/nginx-php:1.0 volumeMounts: - name: logs mountPath: /usr/local/nginx/logs volumes: - name: logs hostPath: path: /tmp/logs type: DirectoryOrCreatetype: DirectoryOrCreate比Directory安全后者要求宿主機(jī)目錄必須已存在不存在時(shí) Pod 會(huì)卡在 ContainerCreating 并報(bào)FailedMount。這一點(diǎn)在批量部署時(shí)格外明顯十臺(tái)節(jié)點(diǎn)里只要有一臺(tái)沒(méi)建目錄那臺(tái)機(jī)器上的 Pod 就永遠(yuǎn)起不來(lái)。4.2 imagePullPolicy 三個(gè)值與 :latest 的隱式規(guī)則三個(gè)取值的含義分別是IfNotPresent本地沒(méi)有才拉Always每次創(chuàng)建都拉Never只用手動(dòng)導(dǎo)入的鏡像。真正需要記住的是默認(rèn)值規(guī)則它跟標(biāo)簽有關(guān)而不是固定的當(dāng)鏡像標(biāo)簽是latest或者干脆不寫(xiě)標(biāo)簽時(shí)默認(rèn)是Always寫(xiě)了具體標(biāo)簽時(shí)默認(rèn)是IfNotPresent。所以生產(chǎn)環(huán)境用latest有兩個(gè)隱患一是每次 Pod 重建都要打倉(cāng)庫(kù)倉(cāng)庫(kù)抖動(dòng)就直接ImagePullBackOff二是同一個(gè)標(biāo)簽對(duì)應(yīng)的鏡像內(nèi)容可能已經(jīng)變了回滾時(shí)你以為回到了舊版本實(shí)際拉的是新內(nèi)容。我的做法是標(biāo)簽寫(xiě)死到小版本重要服務(wù)用 digest 固定形如nginxsha256:...這樣鏡像內(nèi)容不可變回滾才有確定性。4.3 requests 與 limitsCPU 單位、OOMKilled 與 QoS 分類(lèi)這兩個(gè)字段經(jīng)常被混著寫(xiě)其實(shí)職責(zé)完全不同。requests是調(diào)度依據(jù)調(diào)度器按它計(jì)算節(jié)點(diǎn)還剩多少可分配資源limits是運(yùn)行時(shí)上限由容器運(yùn)行時(shí)強(qiáng)制執(zhí)行。CPU 超過(guò) limits 會(huì)被節(jié)流表現(xiàn)為響應(yīng)變慢但進(jìn)程還在內(nèi)存超過(guò) limits 會(huì)直接觸發(fā) OOMKilled容器被殺掉重啟。這個(gè)差異決定了排查方向CPU 問(wèn)題看監(jiān)控里的 throttling 指標(biāo)內(nèi)存問(wèn)題看kubectl describe pod里的Last State: Terminated, Reason: OOMKilled。單位方面CPU 的m是千分之一核250m就是 0.25 核1000m等于 1。內(nèi)存的Mi、Gi是二進(jìn)制單位1024 進(jìn)制M、G是十進(jìn)制兩者差 4.8% 左右配額算得緊的時(shí)候這點(diǎn)差異會(huì)變成事故。# 查看 Pod 的 QoS 等級(jí) kubectl get pod web-7d9f8c6b5-abcde -o jsonpath{.status.qosClass} # 查看容器上次退出的原因OOMKilled 會(huì)在這里體現(xiàn) kubectl describe pod web-7d9f8c6b5-abcde | grep -A3 Last StateQoS 分三檔所有容器都設(shè)置了 requests 且等于 limits 是 Guaranteed部分設(shè)置是 Burstable一個(gè)都沒(méi)設(shè)是 BestEffort。節(jié)點(diǎn)資源緊張觸發(fā)驅(qū)逐時(shí)順序是 BestEffort 優(yōu)先被趕走Burstable 次之Guaranteed 最后。所以核心服務(wù)把 requests 和 limits 設(shè)成相等不只是為了穩(wěn)定也是為了在驅(qū)逐排序里靠后。不設(shè) requests 的后果比想象中嚴(yán)重。調(diào)度器會(huì)認(rèn)為這個(gè) Pod 不需要資源把它塞到已經(jīng)快滿(mǎn)的節(jié)點(diǎn)上實(shí)際跑起來(lái)內(nèi)存一漲節(jié)點(diǎn)整體進(jìn)入壓力狀態(tài)最后被驅(qū)逐的可能是隔壁那個(gè)規(guī)規(guī)矩矩設(shè)了配額的服務(wù)。這就是為什么我堅(jiān)持新服務(wù)上線前先把 requests 填上哪怕估值不準(zhǔn)也比空著強(qiáng)。4.4 私有倉(cāng)庫(kù)拉取imagePullSecrets 的創(chuàng)建與掛載內(nèi)網(wǎng)倉(cāng)庫(kù)基本都要認(rèn)證報(bào)錯(cuò)表現(xiàn)為ErrImagePull或ImagePullBackOffdescribe的 Events 里會(huì)明確寫(xiě)no basic auth credentials或者unauthorized。# 創(chuàng)建 docker-registry 類(lèi)型的 Secret kubectl create secret docker-registry regcred \ --docker-serverregistry.local \ --docker-usernamedevops \ --docker-password密碼 \ --docker-emaildevexample.com # 掛到命名空間的默認(rèn) ServiceAccount 上該空間內(nèi) Pod 自動(dòng)繼承 kubectl patch serviceaccount default \ -p {imagePullSecrets:[{name:regcred}]}掛載位置有兩種選擇。寫(xiě)進(jìn) Pod 的spec.imagePullSecrets只對(duì)當(dāng)前 Pod 生效適合跨團(tuán)隊(duì)共享集群但倉(cāng)庫(kù)權(quán)限不同的場(chǎng)景patch 到 ServiceAccount 上則整個(gè)命名空間生效省事但權(quán)限范圍大。密碼里有特殊字符時(shí)記得用單引號(hào)包住$和!在雙引號(hào)里會(huì)被 shell 展開(kāi)這個(gè)坑我在不同環(huán)境里見(jiàn)過(guò)好幾次。5. 監(jiān)控與日志排查kubectl top 不出數(shù)、日志抓不到的五個(gè)坑5.1 metrics-server 的數(shù)據(jù)從哪來(lái)kubectl top背后依賴(lài)的是 metrics-server它本身不采集數(shù)據(jù)而是定期向每個(gè)節(jié)點(diǎn)的 kubelet 請(qǐng)求資源用量kubelet 再?gòu)?cAdvisor 拿容器級(jí)指標(biāo)。metrics-server 把結(jié)果聚合成 Metrics API通過(guò) APIService 注冊(cè)到 apiserver 上kubectl 和 HPA 都是從這條路讀數(shù)據(jù)。這個(gè)鏈路決定了排查順序先看 APIService 有沒(méi)有注冊(cè)成功再看 metrics-server Pod 是否健康最后看它能不能連通 kubelet 的 10250 端口。# APIService 的 AVAILABLE 必須是 True kubectl get apiservice v1beta1.metrics.k8s.io -o wide # 看節(jié)點(diǎn)和 Pod 的實(shí)時(shí)資源占用按內(nèi)存排序 kubectl top node kubectl top pod -A --sort-bymemory # metrics-server 自身日志證書(shū)和連接錯(cuò)誤都在這里 kubectl logs -n kube-system deploy/metrics-server --tail50需要清楚一點(diǎn)metrics-server 只保留最近幾分鐘的數(shù)據(jù)不做長(zhǎng)期存儲(chǔ)想看過(guò)往趨勢(shì)得另外上 Prometheus 之類(lèi)的方案。它的定位就是給 HPA 和kubectl top供數(shù)不要指望它當(dāng)監(jiān)控系統(tǒng)用。5.2 日志的三條路徑容器日志一般分三層看。第一層是應(yīng)用寫(xiě)到標(biāo)準(zhǔn)輸出的內(nèi)容用kubectl logs讀這是最常用的。容器重啟過(guò)要看上次的日志加--previous多容器 Pod 要加-c指定容器名。第二層是節(jié)點(diǎn)上的日志文件。容器運(yùn)行時(shí)會(huì)把標(biāo)準(zhǔn)輸出落到/var/log/pods/namespace_pod_uid/container/0.logPod 已經(jīng)被刪掉但節(jié)點(diǎn)上還有殘留時(shí)直接去這個(gè)路徑撈。第三層是組件自身的日志。kubelet 通常由 systemd 托管用journalctl -u kubelet看以靜態(tài) Pod 形式運(yùn)行的控制面組件用kubectl logs -n kube-system pod看。# 持續(xù)跟蹤日志只顯示最后 100 行 kubectl logs -f deploy/web --tail100 # 看容器上一次崩潰前的日志 kubectl logs web-7d9f8c6b5-abcde -c nginx --previous # 跟蹤節(jié)點(diǎn)上 kubelet 的日志只看最近 10 分鐘 journalctl -u kubelet -f --since 10 min ago如果應(yīng)用是把日志寫(xiě)文件而不是標(biāo)準(zhǔn)輸出kubectl logs什么都看不到這不是 bug。要么改成輸出到標(biāo)準(zhǔn)輸出要么用 emptyDir 或 hostPath 把日志目錄掛出來(lái)再接采集器。5.3 五條踩坑記錄現(xiàn)象一kubectl top node報(bào)error: Metrics API not available但 Pod 都正常。原因APIService 沒(méi)注冊(cè)成功或者 metrics-server 連不上 kubelet 的 10250 端口。自簽證書(shū)的集群上metrics-server 校驗(yàn) kubelet 證書(shū)失敗是高頻原因。解決先kubectl get apiservice v1beta1.metrics.k8s.io -o yaml看status.conditions里的 message再去翻 metrics-server 日志。確認(rèn)是證書(shū)問(wèn)題的話在它的啟動(dòng)參數(shù)里加--kubelet-insecure-tls這是測(cè)試和生產(chǎn)自簽環(huán)境都常見(jiàn)的做法代價(jià)是放棄對(duì) kubelet 證書(shū)的校驗(yàn)?,F(xiàn)象二Pod 一直 ContainerCreatingdescribe里 Events 顯示FailedMount。原因hostPath 寫(xiě)了type: Directory但宿主機(jī)上目錄不存在或者掛載的 ConfigMap、Secret 還沒(méi)創(chuàng)建。解決type改成DirectoryOrCreate或者提前在節(jié)點(diǎn)上把目錄建好。用 DaemonSet 掛宿主機(jī)目錄時(shí)尤其要注意集群擴(kuò)容進(jìn)來(lái)的新節(jié)點(diǎn)不會(huì)自動(dòng)帶上目錄得靠初始化腳本或 DaemonSet 自己創(chuàng)建?,F(xiàn)象三kubectl logs報(bào)previous terminated container xxx not found。原因容器已經(jīng)重啟過(guò)多次舊日志被運(yùn)行時(shí)輪轉(zhuǎn)清理掉了--previous也拿不回來(lái)。解決短期靠及時(shí)抓長(zhǎng)期靠把日志落盤(pán)到掛載卷或者接采集器。容器日志的輪轉(zhuǎn)策略是運(yùn)行時(shí)管的默認(rèn)單個(gè)文件到 10Mi 左右就會(huì)切業(yè)務(wù)日志寫(xiě)得猛的話幾分鐘就沒(méi)?,F(xiàn)象四Service 訪問(wèn)不通但后端 Pod 狀態(tài)是 Running。原因Endpoints 為空通常是 Service 的 selector 與 Pod 的 labels 不匹配或者targetPort寫(xiě)成了 Service 端口。解決kubectl get endpoints svc-name先看有沒(méi)有后端地址空的話kubectl get svc svc-name -o yaml把 selector 抄出來(lái)跟 Pod 的 labels 逐字比對(duì)。大小寫(xiě)和拼寫(xiě)錯(cuò)誤是主要來(lái)源app: nginx和app: Nginx在 YAML 里是兩個(gè)完全不同的選擇器。現(xiàn)象五出問(wèn)題想查事件kubectl get events是空的。原因事件默認(rèn)只保留 1 小時(shí)apiserver 的--event-ttl參數(shù)排查拖久了就沒(méi)了。解決故障當(dāng)下立刻執(zhí)行kubectl get events -A --sort-by.lastTimestamp把輸出重定向到文件留檔。超過(guò)保留期的只能靠提前部署的事件采集組件這個(gè)能力在事后復(fù)盤(pán)時(shí)價(jià)值很高。6. 調(diào)度、存儲(chǔ)與安全三個(gè)能當(dāng)場(chǎng)跑通的驗(yàn)證動(dòng)作課件目錄里還有調(diào)度、存儲(chǔ)、集群安全三塊篇幅不多但都是容易「看著懂、上手懵」的部分。我的習(xí)慣是每塊至少跑一個(gè)最小驗(yàn)證確認(rèn)自己理解的和集群實(shí)際行為一致。調(diào)度這塊驗(yàn)證方式是把 Pod 釘?shù)街付ü?jié)點(diǎn)再觀察結(jié)果。給 Pod 加nodeSelector或者直接用nodeName創(chuàng)建后kubectl get pod -o wide看 NODE 列是不是預(yù)期的那臺(tái)。用污點(diǎn)和容忍做反向驗(yàn)證更直觀給節(jié)點(diǎn)打上kubectl taint node node envtest:NoSchedule不帶容忍的 Pod 會(huì) Pendingdescribe里的FailedScheduling會(huì)明確寫(xiě)出污點(diǎn)不匹配的原因。這比讀十遍文檔管用。# 給節(jié)點(diǎn)打污點(diǎn)再驗(yàn)證不帶容忍的 Pod 是否被擋住 kubectl taint node node-01 envtest:NoSchedule kubectl run probe --imagenginx:1.25 kubectl describe pod probe | grep -A2 FailedScheduling # 驗(yàn)證某個(gè) ServiceAccount 在命名空間里有哪些權(quán)限 kubectl auth can-i --list \ --assystem:serviceaccount:default:deployer \ -n default存儲(chǔ)這塊PVC 一直 Pending 時(shí)按三步查kubectl get storageclass看有沒(méi)有可用的 StorageClasskubectl describe pvc name看 Events 里是no persistent volumes available還是waiting for a volume to be created再比對(duì) PVC 的accessModes和 PV 支持的是不是一致。ReadWriteOnce和ReadOnlyMany寫(xiě)混了不會(huì)報(bào)語(yǔ)法錯(cuò)只會(huì)靜靜掛起。安全這塊RBAC 的驗(yàn)證靠kubectl auth can-i上面那條命令會(huì)把這個(gè)身份能做的動(dòng)作全列出來(lái)。我在給新同事分配 kubeconfig 之前一定會(huì)跑一次確認(rèn)權(quán)限范圍跟預(yù)期一致避免出現(xiàn)「以為只給了只讀實(shí)際能刪 Deployment」這種事。有個(gè)習(xí)慣我改不掉任何環(huán)境做變更之前先跑一遍kubectl get --raw /healthz加一次 etcd 快照。這個(gè)動(dòng)作花不到一分鐘但在我誤刪過(guò)一整個(gè)命名空間之后它就成了肌肉記憶。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取