最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

K8s生產(chǎn)環(huán)境那些文檔不會(huì)告訴你的坑及解決辦法

 更新時(shí)間:2026年05月09日 11:34:21   作者:神奇小湯圓  
K8s作為當(dāng)前最流行的容器編排平臺(tái),在企業(yè)生產(chǎn)環(huán)境中的部署需要綜合考慮高可用性、安全性、性能優(yōu)化等多方面因素,這篇文章主要介紹了K8s生產(chǎn)環(huán)境那些文檔不會(huì)告訴你的坑及解決辦法的相關(guān)資料,需要的朋友可以參考下

寫在前面

用 K8s 好幾年了,從最開始的”照著文檔搭集群”,到現(xiàn)在管理幾十個(gè)節(jié)點(diǎn)的生產(chǎn)集群,踩過的坑已經(jīng)夠?qū)懸槐緯恕?/p>

官方文檔當(dāng)然很重要,但文檔告訴你的是”怎么用”,不會(huì)告訴你 "用了之后會(huì)出什么問題"。很多坑,只有你真正在生產(chǎn)上跑過、出過事之后才知道。

這篇文章整理了我在生產(chǎn)環(huán)境遇到過的 7 個(gè)坑,每一個(gè)都是真金白銀的教訓(xùn)。有些坑讓我半夜爬起來(lái)處理,有些坑讓我在復(fù)盤會(huì)上被問得啞口無(wú)言。

希望你看完之后,能少走一些我走過的彎路。

坑一:etcd 磁盤滿了,整個(gè)集群變成只讀

發(fā)生了什么

某天上午 10 點(diǎn),開發(fā)同學(xué)跑來(lái)說:”K8s 集群不能創(chuàng)建新 Pod 了。”

我上去一看:

$ kubectl run test --image=nginx
Error from server (Forbidden): error when creating "test": pods "test" is forbidden: 
etcdserver: request timed out

不只是創(chuàng)建 Pod,kubectl get nodes 都開始超時(shí)了。趕緊 ssh 到 master 節(jié)點(diǎn):

$ journalctl -u etcd --no-pager -n 50
etcdserver: mvcc: database space exceeded

etcd 磁盤空間超限了。

etcd 有一個(gè)默認(rèn)的存儲(chǔ)配額(2GB),當(dāng)數(shù)據(jù)量超過配額時(shí),etcd 會(huì)變成只讀模式,拒絕所有寫操作。而 K8s 的幾乎所有操作(創(chuàng)建 Pod、更新 Deployment、甚至心跳上報(bào))都依賴 etcd 寫入,所以整個(gè)集群就”癱瘓”了。

為什么會(huì)滿?

查了一下,罪魁禍?zhǔn)资?nbsp;事件(Event)記錄。

K8s 會(huì)為每個(gè) Pod 的每次事件(調(diào)度、拉取鏡像、啟動(dòng)、重啟、OOM 等)創(chuàng)建一個(gè) Event 對(duì)象,存在 etcd 里。我們有個(gè)服務(wù)因?yàn)榕渲脝栴}一直在 CrashLoopBackOff,每次重啟都會(huì)產(chǎn)生一條 Event。跑了三天,產(chǎn)生了 100 多萬(wàn)條 Event,把 etcd 撐爆了。

# 查看事件數(shù)量
$ kubectl get events --all-namespaces | wc -l
1234567

怎么解決

緊急止血

# 臨時(shí)擴(kuò)大 etcd 配額(從 2GB 擴(kuò)到 8GB)
$ etcdctl --endpoints=https://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 \
  put quota/bytes -- '8589934592'

擴(kuò)容后 etcd 恢復(fù)寫入,集群恢復(fù)正常。

清理事件

# 刪除所有 namespace 下的舊事件(保留最近 1 小時(shí)的)
$ kubectl get events --all-namespaces --field-selector eventTimestamp<2026-04-27T09:00:00Z \
  | awk '{print $1}' | sort -u | xargs -I {} kubectl delete events --field-selector eventTimestamp<2026-04-27T09:00:00Z -n {}

永久預(yù)防

部署 eventrouter 或使用 kube-controller-manager 自帶的事件清理功能:

# 在 kube-controller-manager 配置中設(shè)置事件 TTL
$ vi /etc/kubernetes/manifests/kube-controller-manager.yaml
spec:
  containers:
  - command:
    - kube-controller-manager
    - --terminated-pod-gc-threshold=1000
    - --event-ttl=1h    # 事件保留 1 小時(shí)后自動(dòng)清理

教訓(xùn):etcd 磁盤空間是 K8s 集群的”生命線”,一旦滿了整個(gè)集群就廢了。一定要設(shè)置事件 TTL,并且監(jiān)控 etcd 的存儲(chǔ)使用量。建議在監(jiān)控里加一條規(guī)則:etcd 存儲(chǔ)使用率超過 70% 就告警。

坑二:CoreDNS 的 ndots:5 讓 DNS 解析慢了 5 倍

發(fā)生了什么

上線了一個(gè)新服務(wù),調(diào)用方反饋”第一次請(qǐng)求特別慢,要 2-3 秒,之后就正常了”。

第一反應(yīng)是”連接池冷啟動(dòng)”,但查了應(yīng)用日志,連接池是預(yù)熱過的。用 dig 在 Pod 里測(cè)試 DNS 解析:

$ kubectl exec -it dev-app-pod -- dig dev-service.default.svc.cluster.local

;; Query time: 12 msec

12ms,看起來(lái)正常。但測(cè)試一個(gè)不存在的域名:

$ kubectl exec -it dev-app-pod -- dig dev-service.default.svc.cluster.local.xxx

;; Query time: 3125 msec

3 秒! 這就是第一次請(qǐng)求慢的原因。

為什么會(huì)這樣?

K8s Pod 里的 DNS 配置默認(rèn)是這樣的:

$ kubectl exec -it dev-app-pod -- cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

關(guān)鍵在 ndots:5。DNS 解析的規(guī)則是:如果域名中”.“的數(shù)量小于 ndots(這里是 5),會(huì)先按照 search 列表中的后綴依次拼接嘗試解析,全部失敗后才用原始域名解析。

比如解析 dev-service(1 個(gè)點(diǎn),小于 5),DNS 會(huì)按這個(gè)順序嘗試:

1. dev-service.default.svc.cluster.local   → NXDOMAIN
2. dev-service.svc.cluster.local           → NXDOMAIN
3. dev-service.cluster.local               → NXDOMAIN
4. dev-service(原始域名)                 → 成功

每次 NXDOMAIN 都要等超時(shí)(默認(rèn) 5 秒),所以最壞情況下要等 15 秒以上。

我們的應(yīng)用在連接數(shù)據(jù)庫(kù)時(shí)用的是短域名 mysql,每次解析都要走完整個(gè) search 列表,所以第一次連接特別慢。

怎么解決

方案一:用完整域名(最簡(jiǎn)單)

代碼里把 mysql 改成 mysql.default.svc.cluster.local,域名中的點(diǎn)數(shù)超過 ndots,直接解析,不走 search。

方案二:降低 ndots(推薦)

在 CoreDNS 的 ConfigMap 中把 ndots 改成 2:

$ kubectl edit configmap coredns -n kube-system
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health
        kubernetes cluster.local in-addr.arpa ip6.arpa {
            pods insecure
            fallthrough in-addr.arpa ip6.arpa
            ttl 30
        }
        prometheus :9153
        forward . /etc/resolv.conf {
            max_concurrent 1000
        }
        cache 30
        loop
        reload
        loadbalance
    }

然后在 Pod 的 DNS 配置中覆蓋 ndots:

spec:
  dnsConfig:
    options:
      - name: ndots
        value: "2"

或者通過 Deployment 的 dnsPolicy 設(shè)置:

spec:
  template:
    spec:
      dnsConfig:
        options:
          - name: ndots
            value: "2"

方案三:給內(nèi)部服務(wù)加 headless Service 的 A 記錄

如果服務(wù)名是 mysql,可以創(chuàng)建一個(gè)同名的 headless Service,讓 DNS 直接解析到對(duì)應(yīng)的 IP,不需要走 search 列表。

教訓(xùn):如果你的應(yīng)用里用了短域名連接其他服務(wù),一定要關(guān)注 DNS 解析耗時(shí)。建議把 ndots 從 5 降到 2,對(duì)絕大多數(shù)場(chǎng)景都?jí)蛴?,而且能顯著減少 DNS 查詢次數(shù)。

坑三:Secret更新了,Pod 里還是舊的

發(fā)生了什么

我們有個(gè)服務(wù)連接第三方 API,用的密鑰存在 K8s Secret 里。密鑰到期了,運(yùn)維同學(xué)更新了 Secret:

$ kubectl create secret generic api-secret \
  --from-literal=api-key=new-key-12345 \
  --dry-run=client -o yaml | kubectl apply -f -

更新完之后,通知開發(fā)同學(xué)”密鑰已更新”。開發(fā)同學(xué)說”好的”,然后繼續(xù)用。過了半小時(shí),第三方 API 那邊反饋我們的請(qǐng)求還是用的舊密鑰。開發(fā)同學(xué)查了應(yīng)用日志,確認(rèn)應(yīng)用讀到的確實(shí)是舊密鑰。“Secret 不是已經(jīng)更新了嗎?”

為什么會(huì)這樣?

K8s Secret 有兩種掛載方式:

方式一:環(huán)境變量

env:
  - name: API_KEY
    valueFrom:
      secretKeyRef:
        name: api-secret
        key: api-key

方式二:Volume 掛載

volumes:
  - name: secret-volume
    secret:
      secretName: api-secret
volumeMounts:
  - name: secret-volume
    mountPath: /etc/secrets
    readOnly: true

兩種方式的更新行為完全不同:

掛載方式Secret 更新后需要重啟 Pod 嗎?
環(huán)境變量不會(huì)更新,Pod 里還是舊值必須重啟
Volume 掛載會(huì)更新(K8s 會(huì)定期同步,大約 1 分鐘)不需要重啟

我們的服務(wù)用的是環(huán)境變量方式掛載 Secret,所以更新 Secret 后,Pod 里讀到的還是舊值。必須重啟 Pod 才能生效。但運(yùn)維同學(xué)不知道這個(gè)區(qū)別,以為更新了 Secret 就完事了。

怎么解決

短期:重啟 Pod

$ kubectl rollout restart deployment app-service

長(zhǎng)期:如果需要 Secret 熱更新,改用 Volume 掛載方式。應(yīng)用層做文件監(jiān)聽,檢測(cè)到文件變化后重新加載密鑰。

spec:
  containers:
  - name: dev-app
    volumeMounts:
    - name: secret-volume
      mountPath: /etc/secrets
      readOnly: true
    env:
    - name: SECRET_PATH
      value: "/etc/secrets/api-key"
  volumes:
  - name: secret-volume
    secret:
      secretName: api-secret

應(yīng)用代碼里監(jiān)聽文件變化:

// Go 示例:監(jiān)聽 Secret 文件變化
watcher, _ := fsnotify.NewWatcher("/etc/secrets/api-key")
go func() {
    for {
        select {
        case event := <-watcher.Events:
            if event.Op&fsnotify.Write == fsnotify.Write {
                newKey, _ := os.ReadFile("/etc/secrets/api-key")
                reloadAPIKey(string(newKey))
            }
        }
    }
}()

教訓(xùn):K8s Secret 的更新機(jī)制是面試高頻題,但在生產(chǎn)上踩坑的人真的不少。如果你不確定團(tuán)隊(duì)是否清楚這個(gè)區(qū)別,建議在運(yùn)維文檔里明確寫上:更新 Secret 后,必須確認(rèn) Pod 的掛載方式,如果是環(huán)境變量則需要重啟 Pod。

坑四:HPA擴(kuò)容了,但新 Pod 一直 Pending

發(fā)生了什么

晚上 8 點(diǎn),流量高峰來(lái)了。HPA 正常觸發(fā)了擴(kuò)容:

$ kubectl get hpa
NAME         REFERENCE                       TARGETS   MINPODS   MAXPODS   REPLICAS
dev-service   Deployment/dev-service/Scale   85%/80%   3         20        8

副本數(shù)從 3 擴(kuò)到了 8。但業(yè)務(wù)同學(xué)說”還是扛不住,響應(yīng)時(shí)間還是很長(zhǎng)”。我上去一看,8 個(gè) Pod 里只有 3 個(gè)是 Running,剩下 5 個(gè)全是 Pending:

$ kubectl get pods | grep dev-service
dev-service-xxx-abc12   1/1   Running   0          15m
dev-service-xxx-def34   1/1   Running   0          15m
dev-service-xxx-ghi56   1/1   Running   0          15m
dev-service-xxx-jkl78   0/1   Pending   0          5m
dev-service-xxx-mno90   0/1   Pending   0          5m
dev-service-xxx-pqr12   0/1   Pending   0          5m
dev-service-xxx-stu34   0/1   Pending   0          5m
dev-service-xxx-vwx56   0/1   Pending   0          5m

HPA 擴(kuò)了,但新 Pod 調(diào)度不上去。等于 HPA 做了無(wú)用功。

為什么會(huì)這樣?

$ kubectl describe pod dev-service-xxx-jkl78 | tail -10
Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  5m    default-scheduler   0/5 nodes are available: 3 Insufficient cpu, 2 Insufficient memory.

節(jié)點(diǎn)資源不夠了。問題出在:我們的 Pod 的 resources.requests 設(shè)得太低了:

resources:
  requests:
    cpu: "100m"
    memory: "128Mi"
  limits:
    cpu: "2"
    memory: "4Gi"

request 設(shè)了 100m CPU,但實(shí)際運(yùn)行時(shí)每個(gè) Pod 要用 800m-1500m CPU。調(diào)度器按 request 算,覺得每個(gè)節(jié)點(diǎn)還能塞很多 Pod,就都調(diào)度上去了。結(jié)果 Pod 跑起來(lái)之后瘋狂搶占 CPU,節(jié)點(diǎn)負(fù)載飆升,真正需要擴(kuò)容時(shí)反而沒資源了。

這就是經(jīng)典的 request 和 limit 差距過大 的問題。

怎么解決

緊急:手動(dòng)清理一些低優(yōu)先級(jí)的 Pod,騰出資源

# 查看哪些 Pod 占用資源最多
$ kubectl top pods --all-namespaces | sort -k3 -rn | head -20

# 臨時(shí)縮容非核心服務(wù)
$ kubectl scale deployment non-critical-service --replicas=0

長(zhǎng)期:合理設(shè)置 request 值

request 應(yīng)該反映 Pod 的實(shí)際資源使用量,而不是”最小可能值”。建議用以下方法確定:

# 查看 Pod 歷史資源使用量(需要 metrics-server)
$ kubectl top pod dev-service-xxx-abc12 --no-headers
# 輸出類似:523m 384Mi

# 建議取 P50 或 P70 的值作為 request
# 如果 P50 CPU = 500m,P70 CPU = 800m
# 那 request 設(shè)為 600m-800m 比較合理
resources:
  requests:
    cpu: "800m"      # 接近實(shí)際使用量
    memory: "512Mi"
  limits:
    cpu: "2000m"
    memory: "2Gi"

教訓(xùn):request 設(shè)太低不是”省資源”,而是”騙調(diào)度器”。調(diào)度器按 request 算,你告訴它每個(gè) Pod 只要 100m,它就會(huì)往節(jié)點(diǎn)上塞很多 Pod,結(jié)果實(shí)際運(yùn)行時(shí)資源不夠,真正需要擴(kuò)容時(shí)反而沒地方放。request 應(yīng)該設(shè)成實(shí)際使用量的 70%-80% ,給一些余量,但不要差太多。

坑五:kubectl drain把集群搞崩了

發(fā)生了什么

有次要升級(jí)一批節(jié)點(diǎn)的內(nèi)核版本,需要先把節(jié)點(diǎn)上的 Pod 驅(qū)逐走。我用了 kubectl drain:

$ kubectl drain worker-03 --ignore-daemonsets --delete-emptydir-data
node/worker-03 cordoned
evicting pod default/my-service-xxx-abc12
evicting pod kube-system/calico-node-xxx
...

看起來(lái)正常。然后我對(duì) worker-04 執(zhí)行了同樣的操作。然后對(duì) worker-05,然后監(jiān)控告警炸了:多個(gè)服務(wù)不可用

為什么會(huì)這樣?

kubectl drain 會(huì)驅(qū)逐節(jié)點(diǎn)上的所有 Pod(除了 DaemonSet)。但驅(qū)逐是”優(yōu)雅的”——它會(huì)先發(fā) SIGTERM,等 Pod 完成清理工作后再刪除。

問題出在:我們的 Pod 沒有配置優(yōu)雅終止。

# 很多 Pod 的 terminationGracePeriodSeconds 用的是默認(rèn)值 30 秒
# 但應(yīng)用沒有監(jiān)聽 SIGTERM 信號(hào),不會(huì)主動(dòng)退出

Pod 收到 SIGTERM 后,應(yīng)用不處理,30 秒后 kubelet 發(fā) SIGKILL 強(qiáng)殺。但在這 30 秒內(nèi),Pod 還是 Running 狀態(tài),Service 的 endpoint 里還有它。

當(dāng)我同時(shí) drain 3 個(gè)節(jié)點(diǎn)時(shí),3 個(gè)節(jié)點(diǎn)上的 Pod 都在”等死”狀態(tài)。新 Pod 調(diào)度到其他節(jié)點(diǎn)需要時(shí)間,而舊 Pod 還沒完全退出。Service 的 endpoint 里混著”正在退出的舊 Pod”和”剛啟動(dòng)的新 Pod”,流量分發(fā)混亂,部分請(qǐng)求打到了正在退出的 Pod 上,導(dǎo)致錯(cuò)誤。

更糟糕的是,有個(gè)服務(wù)的 Pod 里有本地緩存(Redis 連接池),優(yōu)雅終止時(shí)需要 30 秒來(lái)關(guān)閉連接。但 terminationGracePeriodSeconds 只有 30 秒,還沒來(lái)得及關(guān)完就被殺了,導(dǎo)致 Redis 連接泄漏。

怎么解決

1. 配置優(yōu)雅終止

spec:
  terminationGracePeriodSeconds: 60   # 給足夠的時(shí)間
  containers:
  - name: dev-app
    lifecycle:
      preStop:
        exec:
          command: ["/bin/sh", "-c", "sleep 10"]  # 預(yù)停止鉤子,先從 Service 摘除
    # 應(yīng)用本身需要監(jiān)聽 SIGTERM 信號(hào)

2. 配置 PodDisruptionBudget

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: dev-service-pdb
spec:
  minAvailable: "50%"
  selector:
    matchLabels:
      app: dev-service

有了 PDB,kubectl drain 在驅(qū)逐 Pod 之前會(huì)檢查:驅(qū)逐后是否還有足夠的可用 Pod。如果不夠,drain 會(huì)拒絕操作,避免服務(wù)不可用。

$ kubectl drain worker-03 --ignore-daemonsets
error when evicting pods/default/my-service-xxx: 
Cannot evict pod as it would violate the pod's disruption budget.

3. 逐個(gè)節(jié)點(diǎn)操作,不要同時(shí) drain 多個(gè)節(jié)點(diǎn)

# 正確做法:一個(gè)一個(gè)來(lái)
$ kubectl drain worker-03 --ignore-daemonsets --delete-emptydir-data
# 等所有 Pod 都遷移完成
$ kubectl get nodes worker-03
# 確認(rèn) Ready 后再操作下一個(gè)
$ kubectl drain worker-04 --ignore-daemonsets --delete-emptydir-data

教訓(xùn):kubectl drain 看起來(lái)是個(gè)簡(jiǎn)單的命令,但它會(huì)觸發(fā)一系列連鎖反應(yīng)。在執(zhí)行之前,確保:① 配置了 PDB;② Pod 有優(yōu)雅終止邏輯;③ 逐個(gè)節(jié)點(diǎn)操作,不要貪快。

坑六:日志采集把節(jié)點(diǎn)打掛了

發(fā)生了什么

某天凌晨 3 點(diǎn),值班同事打電話給我:”好幾臺(tái)機(jī)器 CPU 100%,服務(wù)全掛了。”

我迷迷糊糊爬起來(lái),打開電腦一看,確實(shí)是好幾臺(tái) worker 節(jié)點(diǎn) CPU 飆滿。但不是我們的業(yè)務(wù)應(yīng)用,而是:

$ top -bn1 | head -20
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
 5678 root      20   0  512.4m  128.6m  12.2m S  95.3   1.6  2345:12.78 fluentd
 5679 root      20   0  498.2m  115.3m  10.8m S  92.1   1.4  2198:34.56 fluentd

Fluentd(日志采集組件)占了 90%+ 的 CPU。

為什么會(huì)這樣?

查了一下 Fluentd 的日志,發(fā)現(xiàn)它在瘋狂處理日志。原因是:有個(gè)服務(wù)的日志級(jí)別被開發(fā)同學(xué)調(diào)試時(shí),臨時(shí)改成了 DEBUG,每秒產(chǎn)生上百 MB 的日志。Fluentd 拼命采集、解析、轉(zhuǎn)發(fā),CPU 直接被打滿了。

更慘的是,F(xiàn)luentd 是以 DaemonSet 方式部署的,每個(gè)節(jié)點(diǎn)上都跑著一個(gè)。所以日志暴增的服務(wù)的 Pod 在哪個(gè)節(jié)點(diǎn)上,那個(gè)節(jié)點(diǎn)的 Fluentd 就被打滿,進(jìn)而影響節(jié)點(diǎn)上所有其他 Pod。

怎么解決

緊急:先給 Fluentd 加資源限制,防止它把節(jié)點(diǎn)打掛

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: kube-system
spec:
  template:
    spec:
      containers:
      - name: fluentd
        resources:
          requests:
            cpu: "100m"
            memory: "200Mi"
          limits:
            cpu: "1000m"     # 限制最大 CPU
            memory: "1Gi"

然后:找到產(chǎn)生大量日志的 Pod,把日志級(jí)別改回正常

# 找到日志量最大的 Pod
$ kubectl top pods --all-namespaces | sort -k3 -rn | head -10

# 修改日志級(jí)別
$ kubectl set env deployment dev-service LOG_LEVEL=INFO

長(zhǎng)期

1、給所有日志采集組件設(shè)置資源限制(很多團(tuán)隊(duì)用 DaemonSet 部署 Fluentd/Filebeat 時(shí)不設(shè) limits,這是定時(shí)炸彈)

2、配置日志采集的速率限制:

# Fluentd 配置
<source>
  @type tail
  path /var/log/containers/*.log
  pos_file /var/log/fluentd-containers.log.pos
  tag kubernetes.*
  read_from_head true
  <parse>
    @type json
    time_key time
    time_format %Y-%m-%dT%H:%M:%S.%NZ
  </parse>
</source>
# 限制采集速率
<filter kubernetes.**>
  @type throttle
  group_key log
  group_bucket_limit 100    # 每秒最多采集 100 條
  group_interval 10s
  group_reset_rate 10/m
</filter>

3、在監(jiān)控里加上日志采集組件的資源使用告警

教訓(xùn):DaemonSet 部署的組件(日志采集、監(jiān)控 Agent)一定要設(shè)資源限制。它們跑在每個(gè)節(jié)點(diǎn)上,一旦出問題影響面是全集群級(jí)別的。不要相信”日志采集組件很輕量”這種說法——它輕量是正常的,但不正常的時(shí)候能把你整個(gè)集群搞崩。

坑七:滾動(dòng)更新導(dǎo)致的連接中斷

發(fā)生了什么

某次發(fā)布新版本,用的是正常的滾動(dòng)更新:

$ kubectl set image deployment/dev-service dev-app=evd-app:v2.0.0
deployment.apps/dev-service image updated

發(fā)布過程中,監(jiān)控顯示有大約 1-2% 的請(qǐng)求返回了 502。業(yè)務(wù)同學(xué)問:”不是滾動(dòng)更新嗎?為什么還有請(qǐng)求失敗?”

為什么會(huì)這樣?

滾動(dòng)更新的流程是這樣的:

  1. 創(chuàng)建新 Pod,等待新 Pod Ready
  2. 新 Pod Ready 后,刪除舊 Pod
  3. 重復(fù)直到所有舊 Pod 被替換

問題出在第 1 步和第 2 步之間。

新 Pod 變成 Ready,意味著 readinessProbe 通過了。但 readinessProbe 通過 ≠ 應(yīng)用真正能處理請(qǐng)求。

我們的應(yīng)用啟動(dòng)流程是這樣的:

JVM 啟動(dòng) → Spring 初始化 → 連接數(shù)據(jù)庫(kù) → 加載緩存 → 開始監(jiān)聽端口

readinessProbe 配置的是 TCP 端口檢查:

readinessProbe:
  tcpSocket:
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5

端口打開了,readinessProbe 就通過了。但這時(shí)候 Spring 可能還在初始化數(shù)據(jù)庫(kù)連接池、加載緩存。如果這時(shí)候有請(qǐng)求打過來(lái),應(yīng)用能收到,但處理不了,就會(huì)返回 502。

而舊 Pod 被刪除后,它的連接是直接斷掉的,不會(huì)有優(yōu)雅關(guān)閉的過程。

怎么解決

1. 改用 HTTP 探針,檢查應(yīng)用真正就緒

readinessProbe:
  httpGet:
    path: /health/ready    # 應(yīng)用提供一個(gè)真正的就緒檢查接口
    port: 8080
  initialDelaySeconds: 15
  periodSeconds: 5
  failureThreshold: 3

應(yīng)用的 /health/ready 接口應(yīng)該檢查:

@GetMapping("/health/ready")
public ResponseEntity<String> ready() {
    // 檢查數(shù)據(jù)庫(kù)連接池是否就緒
    if (!dataSource.isRunning()) {
        return ResponseEntity.status(503).body("database not ready");
    }
    // 檢查緩存是否加載完成
    if (!cacheManager.isReady()) {
        return ResponseEntity.status(503).body("cache not ready");
    }
    return ResponseEntity.ok("ok");
}

2. 配置 preStop 鉤子,讓舊 Pod 優(yōu)雅退出

spec:
  terminationGracePeriodSeconds: 60
  containers:
  - name: my-app
    lifecycle:
      preStop:
        exec:
          command: ["/bin/sh", "-c", "sleep 10"]

preStop 的 sleep 10 看起來(lái)很蠢,但它有重要作用:讓 kubelet 在發(fā) SIGTERM 之前先等 10 秒。在這 10 秒內(nèi),endpoint controller 會(huì)把舊 Pod 從 Service 的 endpoint 列表中移除。這樣就不會(huì)有新請(qǐng)求打到正在退出的舊 Pod 上了。

時(shí)序是這樣的:

t=0s    preStop sleep 10 開始執(zhí)行
t=0s    endpoint controller 從 endpoint 列表中移除舊 Pod
t=10s   preStop 執(zhí)行完畢,kubelet 發(fā)送 SIGTERM
t=10s   應(yīng)用收到 SIGTERM,開始優(yōu)雅關(guān)閉
t=40s   應(yīng)用完成清理,主動(dòng)退出
t=60s   terminationGracePeriodSeconds 到期,強(qiáng)制 SIGKILL

3. 配置 maxSurge

spec:
  strategy:
    rollingUpdate:
      maxSurge: 1           # 滾動(dòng)更新時(shí)最多多創(chuàng)建 1 個(gè) Pod
      maxUnavailable: 0     # 滾動(dòng)更新時(shí)不允許有 Pod 不可用

maxUnavailable: 0 確保更新過程中,可用 Pod 數(shù)量不會(huì)減少。maxSurge: 1 允許臨時(shí)多一個(gè) Pod 來(lái)承接流量。

教訓(xùn):滾動(dòng)更新”零中斷”不是 K8s 默認(rèn)就能做到的,需要三個(gè)條件配合:① HTTP 就緒探針(不是 TCP);② preStop 鉤子 + 合理的 terminationGracePeriodSeconds;③ 合理的 maxSurge 和 maxUnavailable。缺一個(gè)都可能出問題。

最后整理一份清單

把上面 7 個(gè)坑的預(yù)防措施整理成一份上線前檢查清單:

#檢查項(xiàng)不做的后果操作
1etcd 存儲(chǔ)監(jiān)控 + 事件 TTLetcd 滿了集群癱瘓設(shè)置 --event-ttl=1h,監(jiān)控 etcd 存儲(chǔ)使用率
2DNS ndots 配置DNS 解析慢,首次請(qǐng)求延遲高ndots 改為 2,內(nèi)部服務(wù)用完整域名
3Secret 掛載方式更新 Secret 后應(yīng)用不生效需要熱更新用 Volume 掛載,否則更新后要重啟 Pod
4resources.request 合理設(shè)置HPA 擴(kuò)容時(shí)調(diào)度不上request 設(shè)為實(shí)際使用量的 70%-80%
5PodDisruptionBudget + 優(yōu)雅終止drain 節(jié)點(diǎn)時(shí)服務(wù)中斷配置 PDB + preStop + terminationGracePeriodSeconds
6DaemonSet 資源限制日志采集/監(jiān)控組件打滿節(jié)點(diǎn) CPU所有 DaemonSet 必須設(shè) requests + limits
7滾動(dòng)更新零中斷配置發(fā)布時(shí)有少量請(qǐng)求失敗HTTP 探針 + preStop + maxSurge/maxUnavailable

如果你也在用 K8s 跑生產(chǎn)環(huán)境,并且踩過其他文檔里沒寫的,歡迎在評(píng)論區(qū)分享。踩過的坑多了,路就平了。

到此這篇關(guān)于K8s生產(chǎn)環(huán)境那些文檔不會(huì)告訴你的坑及解決辦法的文章就介紹到這了,更多相關(guān)K8s生產(chǎn)環(huán)境的坑內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • kubernetes?k8s入門定義一個(gè)Pod

    kubernetes?k8s入門定義一個(gè)Pod

    這篇文章主要為大家介紹了k8s入門定義一個(gè)Pod以及破底的定義內(nèi)容詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多計(jì)步,早日升職加薪
    2022-03-03
  • 虛擬化和云計(jì)算的區(qū)別分析

    虛擬化和云計(jì)算的區(qū)別分析

    這篇文章主要介紹了虛擬化和云計(jì)算的區(qū)別,深入淺出的列舉分析了虛擬化與云計(jì)算的幾點(diǎn)常見區(qū)別,需要的朋友可以參考下
    2016-10-10
  • k8s scc權(quán)限和內(nèi)置的restricted、anyuid、privileged詳解

    k8s scc權(quán)限和內(nèi)置的restricted、anyuid、privileged詳解

    這篇文章主要介紹了k8s scc權(quán)限和內(nèi)置的restricted、anyuid、privileged,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-07-07
  • K8s解決主機(jī)重啟后kubelet無(wú)法自動(dòng)啟動(dòng)問題(推薦)

    K8s解決主機(jī)重啟后kubelet無(wú)法自動(dòng)啟動(dòng)問題(推薦)

    在安裝配置好Kubernetes后,正常情況下服務(wù)器關(guān)機(jī)重啟,kubelet也會(huì)自動(dòng)啟動(dòng)的,如何解決這個(gè)問題呢,下面小編給大家?guī)?lái)了K8s解決主機(jī)重啟后kubelet無(wú)法自動(dòng)啟動(dòng)問題,感興趣的朋友一起看看吧
    2022-08-08
  • KVM虛擬化技術(shù)之virt-manager使用及KVM虛擬化平臺(tái)網(wǎng)絡(luò)模型介紹

    KVM虛擬化技術(shù)之virt-manager使用及KVM虛擬化平臺(tái)網(wǎng)絡(luò)模型介紹

    這篇文章主要介紹了KVM虛擬化技術(shù)之virt-manager使用及KVM虛擬化平臺(tái)網(wǎng)絡(luò)模型介紹,需要的朋友可以參考下
    2016-10-10
  • Kubernetes集群調(diào)度詳解(節(jié)點(diǎn)親和性、Pod親和性、Taint與Toleration)

    Kubernetes集群調(diào)度詳解(節(jié)點(diǎn)親和性、Pod親和性、Taint與Toleration)

    Kubernetes調(diào)度器負(fù)責(zé)將Pod分配到節(jié)點(diǎn),兼顧資源合理分配、調(diào)度效率及用戶策略,通過預(yù)選、優(yōu)選、選擇三階段決策,支持自定義調(diào)度器、節(jié)點(diǎn)親和性(軟硬策略)、Taint/Toleration機(jī)制及直接指定節(jié)點(diǎn),實(shí)現(xiàn)靈活調(diào)度與容錯(cuò)
    2025-09-09
  • 配置Ingress的SSL/TLS證書全過程

    配置Ingress的SSL/TLS證書全過程

    在Kubernetes中配置Ingress的SSL/TLS證書涉及兩個(gè)主要步驟:首先是創(chuàng)建包含證書的Secret,然后是配置Ingress資源以使用該Secret,這要求已部署IngressController如NGINX,并擁有有效的SSL/TLS證書,可以通過Cert-Manager自動(dòng)管理證書的申請(qǐng)和續(xù)期
    2025-10-10
  • k8s Job 執(zhí)行一次性以及批處理任務(wù)使用場(chǎng)景案例

    k8s Job 執(zhí)行一次性以及批處理任務(wù)使用場(chǎng)景案例

    這篇文章主要為大家介紹了k8s Job 執(zhí)行一次性以及批處理任務(wù)使用場(chǎng)景案例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • K8S中若要掛載其他命名空間中的 Secret操作方法

    K8S中若要掛載其他命名空間中的 Secret操作方法

    在Kubernetes中,通過創(chuàng)建ServiceAccount和RoleBinding,可以實(shí)現(xiàn)一個(gè)命名空間中的Pod掛載另一個(gè)命名空間中的Secret,以下是具體步驟和示例代碼,包括創(chuàng)建ServiceAccount、Role和RoleBinding,以及在Pod中使用這些資源掛載Secret,感興趣的朋友一起看看吧
    2025-03-03
  • Kubernetes探針使用介紹

    Kubernetes探針使用介紹

    這篇文章主要為大家介紹了Kubernetes探針使用詳細(xì)介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-03-03

最新評(píng)論

潼南县| 普定县| 新野县| 安远县| 永靖县| 宾川县| 吉安市| 武城县| 建昌县| 安化县| 班戈县| 黔西县| 和顺县| 蓬溪县| 昌黎县| 交城县| 同江市| 中西区| 大悟县| 元谋县| 长兴县| 铅山县| 肃南| 甘孜县| 合水县| 靖边县| 深水埗区| 搜索| 连城县| 衡阳县| 新乡市| 武威市| 洛扎县| 格尔木市| 海门市| 巴青县| 泸水县| 丘北县| 科技| 衡山县| 七台河市|