運(yùn)維人的日常之一次 K8s 磁盤故障的驚魂夜
事件起因
23年3月的某個(gè)該死的一天,而且是該死的凌晨?jī)牲c(diǎn),值班人員忽然來電話,公司主力產(chǎn)品的某大型在線對(duì)戰(zhàn)游戲部分玩家在拍賣行交易時(shí),界面一直停在“提交中”,頁面卡死,整個(gè)流程卡住了?!焙?知道了~“,迷迷糊糊中拿起手機(jī),眼皮還沒完全睜開,就看到滿屏的告警,Prometheus那邊已經(jīng)炸了——trade-service的錯(cuò)誤率飆到了85%以上。沒辦法拿人錢財(cái)與人消災(zāi),操練起來吧。
廢話少說直接來干貨!先聲明這個(gè)K8s集群的版本是v1.24.8。
排查過程
先從集群角度看看都什么情況!
kubectl get nodes -o wide 顯示 worker-node-7 狀態(tài)為 DiskPressure該節(jié)點(diǎn)運(yùn)行著交易服務(wù)的核心事務(wù)處理的Pod。
執(zhí)行驅(qū)逐檢查:
kubectl describe node worker-node-7 | grep -i evicted # 發(fā)現(xiàn)12個(gè)Pod因磁盤壓力被驅(qū)逐
Elastic Stack 日志分析發(fā)現(xiàn)連續(xù)錯(cuò)誤:[FATAL] Transaction commit failed: Input/output error
再從問題節(jié)點(diǎn)上看看什么情況。
SSH登錄worker-node-7隨手就是一個(gè)技能:
df -hT /var/lib/kubelet # 顯示使用率100%
嚯!~接下來必須看看是哪個(gè)或者哪些大文件導(dǎo)致的?:
ncdu -x /var/lib/docker/overlay2 # 發(fā)現(xiàn)某容器日志文件占用52GB
那我刪?別忙,別動(dòng)!老運(yùn)維的直覺,感覺有埋伏.那我必須再一個(gè)技能
sudo smartctl -a /dev/nvme0n1 | grep -e 'Media_Wearout_Indicator' -e 'Reallocated_Sector_Ct'
Reallocated_Sector_Ct 0x0033 086 086 000 Pre-fail Always FAILING_NOW 142
我勒個(gè)去.迷糊不?子母雷呀!磁盤有142個(gè)壞道。
這時(shí)候不能處理奧!這時(shí)候就想起了IT運(yùn)維技術(shù)圈的博主老楊對(duì)我的諄諄教誨:“不要看到一個(gè)毛病就下手,一定要盡量的查,全查明白了,然后再反饋,然后再按照領(lǐng)導(dǎo)的決定去執(zhí)行”記得前一陣子剛升級(jí)了k8s集群版本,咋就這么巧出了問題呢?我再仔細(xì)摸一下集群的配置.那邊總監(jiān)已經(jīng)開始在群里各種哀嚎了.不過一定要穩(wěn)住,穩(wěn)住!
我再確認(rèn)一下集群的配置:
ps aux | grep kubelet | grep -o 'eviction-hard=.*' # 輸出'imagefs.available<10%,nodefs.available<5%'
第三顆雷被我找到了.查了一下K8s v1.24.8的新特性,默認(rèn)是禁用SMART的
journalctl -u kubelet | grep 'eviction_manager'
顯示日志輪轉(zhuǎn)失效警告
到這可以做階段總結(jié)了:
- 硬件老化失效:NVMe SSD出現(xiàn)嚴(yán)重壞道,I/O故障直接影響服務(wù)。
- K8s存儲(chǔ)感知缺失:集群未啟用本地存儲(chǔ)容量隔離特性,無法及時(shí)預(yù)警磁盤異常。
- 資源限額與日志治理缺陷:Pod未設(shè)定存儲(chǔ)限額,日志文件無輪轉(zhuǎn),導(dǎo)致磁盤空間被單一容器異常占用。
行了,該總監(jiān)上場(chǎng)了,把自己看到的和認(rèn)為合理的處理辦法告訴它,得到了“汪汪“的回復(fù),并且給出了“汪汪”的建議,以及“汪汪”的指導(dǎo)后,開始操作。
一頓反搖拳
干掉worker-node-7節(jié)點(diǎn),但一定要優(yōu)雅。
標(biāo)記節(jié)點(diǎn)不可調(diào)度:
kubectl taint nodes worker-node-7 diskfailure=true:NoSchedule
驅(qū)逐Pod:
kubectl drain worker-node-7 --ignore-daemonsets --grace-period=300
臨時(shí)用badblocks和e2fsck標(biāo)記壞塊,強(qiáng)行讓文件系統(tǒng)跳過這些坑bash badblocks -sv /dev/nvme0n1 # 標(biāo)記壞塊 e2fsck -c /dev/nvme0n1 # 強(qiáng)制文件系統(tǒng)跳過壞道。
日志全清空:
truncate -s 0 /var/lib/docker/containers/*/*-json.log
直接用ArgoCD把trade-service重新部署了一遍。
使用 ArgoCD 觸發(fā)自動(dòng)重部署:
argocd app actions run trade-service --kind Rollout --action restart
數(shù)據(jù)庫那邊也沒敢大意,特地跑了個(gè)一致性校驗(yàn):
kubectl exec trade-db-0 -- pg_checkconsistency -v
等到凌晨四點(diǎn)半,監(jiān)控大屏上的交易成功率終于回升,玩家投訴也慢慢消停了。第二天直接休了一天!~
復(fù)盤
復(fù)盤會(huì)上沒什么好撕的,根因就上面那些!做了下面的幾個(gè)方向的改進(jìn)。
寫了個(gè)磁盤檢查腳本(羅列一下大概意思,不喜勿噴)disk_health_monitor.sh:
#!/bin/bash
# 設(shè)備自動(dòng)發(fā)現(xiàn)(兼容NVMe/SATA)
DEVICES=$(lsblk -d -o NAME,TYPE | grep disk | awk '{print "/dev/"$1}')
ALERT_FLAG=0
for DEV in$DEVICES; do
# SMART健康檢查(帶重試機(jī)制)
for i in {1..3}; do
SMART_REPORT=$(smartctl -H $DEV 2>/dev/null)
[ $? -eq 0 ] && break || sleep 5
done
# 關(guān)鍵參數(shù)解析
REALLOC=$(smartctl -A $DEV | grep 'Reallocated_Sector_Ct' | awk '{print $10}')
WEAR_LEVEL=$(smartctl -A $DEV | grep 'Wear_Leveling_Count' | awk '{print $4}' | tr -d '%')
# 多維度健康評(píng)估
ifecho$SMART_REPORT | grep -q "FAILED"; then
logger "[DISK CRITICAL] $DEV SMART failed!"
ALERT_FLAG=1
elif [ $REALLOC -gt 50 ]; then
logger "[DISK WARNING] $DEV Realloc sectors: $REALLOC"
ALERT_FLAG=1
elif [ $WEAR_LEVEL -lt 10 ]; then
logger "[DISK WARNING] $DEV Wear level: $WEAR_LEVEL%"
ALERT_FLAG=1
fi
done
# 聯(lián)動(dòng)K8s節(jié)點(diǎn)標(biāo)記
if [ $ALERT_FLAG -eq 1 ]; then
NODE_NAME=$(hostname)
kubectl label nodes $NODE_NAME disk-status=critical --overwrite
curl -X POST -H "Content-Type: application/json" -d '{"text":"磁盤健康告警!"}'$WEBHOOK_URL
fi
(1) 修改k8s集群配置
啟用特性門控(/etc/kubernetes/kubelet.conf):
featureGates:
LocalStorageCapacityIsolation:true
部署存儲(chǔ)限額策略:
apiVersion:scheduling.k8s.io/v1
kind:PriorityClass
metadata:
name:high-storage
value:1000000
ephemeral-storage-limit:5Gi # 每個(gè)Pod限制5GB臨時(shí)存儲(chǔ)
(2) 日志生態(tài)系統(tǒng)重構(gòu)
部署 Loki+Promtail 替代Docker原生日志:
helm upgrade --install loki grafana/loki-stack --set promtail.enabled=true
添加日志自動(dòng)清理CronJob:
apiVersion:batch/v1
kind:CronJob
spec:
schedule:"0 */4 * * *"
jobTemplate:
spec:
containers:
-name:log-cleaner
image:alpine:3.14
command: ["find", "/var/log/containers", "-size +500M", "-delete"]
最終狀態(tài)
后續(xù)研發(fā)通過 Redis事務(wù)補(bǔ)償機(jī)制 自動(dòng)修復(fù)2,317筆中斷交易。叉會(huì)腰,這次可給我牛批壞了!