態(tài)存儲(chǔ)供給實(shí)戰(zhàn):從部署到生產(chǎn)避坑)
1. 為什么要在K8S里折騰NFS動(dòng)態(tài)供給很多團(tuán)隊(duì)在K8S里跑有狀態(tài)服務(wù)時(shí)第一個(gè)繞不開(kāi)的問(wèn)題就是存儲(chǔ)。容器本身是無(wú)狀態(tài)的Pod一重啟寫在容器里的數(shù)據(jù)就沒(méi)了。早期大家最常用的辦法是hostPath或者emptyDir簡(jiǎn)單粗暴但問(wèn)題也很明顯Pod漂移到別的節(jié)點(diǎn)數(shù)據(jù)就找不到了多個(gè)副本想共享同一份數(shù)據(jù)更是無(wú)從談起。再往后一點(diǎn)有人開(kāi)始用PVPersistentVolume加PVCPersistentVolumeClaim的靜態(tài)綁定方式。管理員提前創(chuàng)建好一堆PV用戶寫PVC去申請(qǐng)。這個(gè)方案在小規(guī)模場(chǎng)景下能用但一旦集群里跑了幾十個(gè)需要存儲(chǔ)的服務(wù)管理員就得手動(dòng)創(chuàng)建幾十個(gè)PV還得盯著哪個(gè)被用了、哪個(gè)空著維護(hù)成本高得離譜。更麻煩的是用戶提交PVC時(shí)如果容量、訪問(wèn)模式跟現(xiàn)有PV對(duì)不上PVC就會(huì)一直卡在Pending狀態(tài)排查起來(lái)也費(fèi)勁。NFS動(dòng)態(tài)供給就是為了解決這個(gè)痛點(diǎn)出現(xiàn)的。它的核心思路是集群里部署一個(gè)Provisioner供給器用戶只需要提交PVCProvisioner會(huì)自動(dòng)在NFS服務(wù)器上創(chuàng)建對(duì)應(yīng)的目錄并生成匹配的PV然后自動(dòng)跟PVC綁定。整個(gè)過(guò)程不需要管理員介入PVC提交后幾秒鐘就能變成Bound狀態(tài)Pod直接掛載使用。這套方案特別適合以下幾類場(chǎng)景中小規(guī)模集群里跑有狀態(tài)應(yīng)用比如數(shù)據(jù)庫(kù)、日志收集、文件共享服務(wù)團(tuán)隊(duì)內(nèi)部測(cè)試環(huán)境需要快速給不同項(xiàng)目分配獨(dú)立存儲(chǔ)空間以及那些對(duì)存儲(chǔ)性能要求不是極致、但需要數(shù)據(jù)持久化和多副本共享的場(chǎng)景。NFS本身是網(wǎng)絡(luò)文件系統(tǒng)天然支持多節(jié)點(diǎn)同時(shí)掛載讀寫權(quán)限控制也相對(duì)成熟配合K8S的動(dòng)態(tài)供給機(jī)制能省掉大量手工運(yùn)維工作。不過(guò)我得先說(shuō)清楚NFS動(dòng)態(tài)供給不是銀彈。它的性能受網(wǎng)絡(luò)和NFS服務(wù)器磁盤IO限制高并發(fā)寫入場(chǎng)景下可能成為瓶頸另外NFS本身是單點(diǎn)服務(wù)器掛了所有掛載它的Pod都會(huì)受影響。所以生產(chǎn)環(huán)境用這套方案NFS服務(wù)端的高可用和備份策略必須提前規(guī)劃好。這篇文章我會(huì)從零開(kāi)始把NFS服務(wù)端配置、Provisioner部署、StorageClass定義、PVC動(dòng)態(tài)掛載、常見(jiàn)坑排查整個(gè)鏈路講透代碼和配置都可以直接抄作業(yè)。2. NFS服務(wù)端的搭建與導(dǎo)出目錄規(guī)劃2.1 選一臺(tái)合適的機(jī)器做NFS ServerNFS服務(wù)端不一定非要獨(dú)立物理機(jī)用一臺(tái)配置還行的虛擬機(jī)或者閑置服務(wù)器都行。我的經(jīng)驗(yàn)是磁盤IO盡量好一點(diǎn)因?yàn)樗蠵od的讀寫最終都落到這臺(tái)機(jī)器的磁盤上。如果預(yù)算有限至少保證數(shù)據(jù)盤是SSD系統(tǒng)盤用機(jī)械盤無(wú)所謂。內(nèi)存和CPU要求不高2核4G就能跑得很穩(wěn)但如果掛載的Pod數(shù)量多、并發(fā)讀寫頻繁建議4核8G起步。操作系統(tǒng)層面主流的Linux發(fā)行版都自帶NFS服務(wù)端軟件包。我習(xí)慣用較新的穩(wěn)定版系統(tǒng)內(nèi)核里NFS模塊支持比較完善。安裝之前先確認(rèn)一下系統(tǒng)是否已經(jīng)裝了nfs-utils或者nfs-kernel-server不同發(fā)行版包名不一樣。用rpm -qa | grep nfs或者dpkg -l | grep nfs查一下有的話就不用重復(fù)裝。網(wǎng)絡(luò)方面NFS服務(wù)端和K8S集群節(jié)點(diǎn)最好在同一個(gè)內(nèi)網(wǎng)網(wǎng)段延遲低、帶寬足。如果跨網(wǎng)段記得防火墻要放行NFS相關(guān)端口。NFS默認(rèn)用2049端口做數(shù)據(jù)傳輸?shù)o助服務(wù)mountd、rpcbind會(huì)隨機(jī)用一些端口所以生產(chǎn)環(huán)境建議在配置文件里把端口固定下來(lái)方便防火墻規(guī)則管理。2.2 安裝NFS服務(wù)端并配置導(dǎo)出目錄以常見(jiàn)的Linux發(fā)行版為例安裝命令大概是這樣的# Debian/Ubuntu系 apt-get update apt-get install -y nfs-kernel-server # RHEL/CentOS系 yum install -y nfs-utils裝完之后創(chuàng)建專門用于K8S存儲(chǔ)的目錄。我一般會(huì)單獨(dú)掛一塊數(shù)據(jù)盤掛載到/data/nfs這樣的路徑下然后在這個(gè)目錄里再分門別類。比如mkdir -p /data/nfs/k8s chmod 777 /data/nfs/k8s這里權(quán)限給777是為了省事讓Provisioner能直接創(chuàng)建子目錄。但生產(chǎn)環(huán)境更穩(wěn)妥的做法是創(chuàng)建一個(gè)專用用戶把目錄屬主改掉然后在NFS導(dǎo)出配置里做用戶映射。不過(guò)K8S的NFS Provisioner默認(rèn)以root身份操作如果服務(wù)端做了root_squash創(chuàng)建目錄時(shí)可能會(huì)權(quán)限不足。所以測(cè)試環(huán)境我通常先關(guān)掉root_squash生產(chǎn)環(huán)境再根據(jù)安全要求調(diào)整。接下來(lái)編輯/etc/exports文件配置導(dǎo)出規(guī)則/data/nfs/k8s *(rw,sync,no_subtree_check,no_root_squash)這行配置的意思是把/data/nfs/k8s目錄導(dǎo)出給所有網(wǎng)段讀寫權(quán)限同步寫入不檢查子目錄并且不壓縮root用戶權(quán)限。sync選項(xiàng)保證數(shù)據(jù)寫入磁盤后才返回成功雖然性能比async差一點(diǎn)但數(shù)據(jù)安全性高很多。no_root_squash讓客戶端的root用戶在服務(wù)端也保持root權(quán)限這樣Provisioner創(chuàng)建目錄時(shí)不會(huì)因?yàn)闄?quán)限問(wèn)題失敗。配置改完后讓導(dǎo)出規(guī)則生效exportfs -arv然后重啟NFS服務(wù)systemctl restart nfs-kernel-server systemctl enable nfs-kernel-server在K8S節(jié)點(diǎn)上驗(yàn)證一下能不能掛載showmount -e NFS_SERVER_IP mount -t nfs NFS_SERVER_IP:/data/nfs/k8s /mnt能掛上并且能讀寫文件說(shuō)明服務(wù)端配置沒(méi)問(wèn)題。這一步很關(guān)鍵很多后續(xù)問(wèn)題都是因?yàn)榉?wù)端導(dǎo)出配置不對(duì)導(dǎo)致的所以務(wù)必先手工驗(yàn)證通過(guò)再往下走。2.3 固定NFS輔助服務(wù)端口前面提到NFS除了2049端口還會(huì)用到mountd和rpcbind的隨機(jī)端口。如果K8S節(jié)點(diǎn)和NFS服務(wù)端之間有防火墻隨機(jī)端口會(huì)被攔掉導(dǎo)致掛載超時(shí)。解決辦法是在NFS服務(wù)端配置文件里把端口固定。編輯/etc/nfs.conf或者/etc/sysconfig/nfs不同發(fā)行版路徑不同設(shè)置[lockd] port32803 udp-port32769 [mountd] port20048 [statd] port32765 outgoing-port32766改完后重啟服務(wù)然后用rpcinfo -p確認(rèn)端口都固定了。防火墻放行2049、20048、32803、32765這些端口。這一步做完跨網(wǎng)段掛載基本就不會(huì)因?yàn)槎丝趩?wèn)題卡住了。3. 部署NFS Subdir External Provisioner3.1 Provisioner的工作原理與選型K8S官方并沒(méi)有內(nèi)置NFS動(dòng)態(tài)供給器社區(qū)里用得最多的是nfs-subdir-external-provisioner這個(gè)項(xiàng)目。它的工作邏輯很清晰監(jiān)聽(tīng)集群里新創(chuàng)建的PVC一旦發(fā)現(xiàn)PVC的StorageClass匹配自己就在NFS服務(wù)端的導(dǎo)出目錄下創(chuàng)建一個(gè)以${namespace}-${pvcName}-${pvName}命名的子目錄然后生成對(duì)應(yīng)的PV對(duì)象最后把PV和PVC綁定。這個(gè)Provisioner以Deployment形式跑在集群里通常單副本就夠了。它內(nèi)部用了一個(gè)小的控制器循環(huán)通過(guò)K8S的API監(jiān)聽(tīng)PVC事件。因?yàn)镹FS本身不支持配額管理所以PVC里申請(qǐng)的容量大小實(shí)際上只是個(gè)標(biāo)簽并不會(huì)真正限制目錄使用空間。這一點(diǎn)要特別注意別以為申請(qǐng)了10G就只能用10G實(shí)際能寫多少取決于NFS服務(wù)端磁盤剩余空間。選型方面社區(qū)還有基于CSI的NFS驅(qū)動(dòng)功能更豐富支持快照、擴(kuò)容等高級(jí)特性。但如果只是基礎(chǔ)的文件共享和持久化需求nfs-subdir-external-provisioner足夠用部署簡(jiǎn)單維護(hù)成本低。我下面就以這個(gè)方案為例來(lái)展開(kāi)。3.2 用Helm快速部署Provisioner手工寫一堆YAML比較繁瑣推薦用Helm Chart來(lái)部署。先添加倉(cāng)庫(kù)helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/ helm repo update然后創(chuàng)建一個(gè)values文件把NFS服務(wù)端地址和導(dǎo)出路徑填進(jìn)去nfs: server: NFS_SERVER_IP path: /data/nfs/k8s storageClass: name: nfs-client defaultClass: false reclaimPolicy: Delete archiveOnDelete: true這里幾個(gè)參數(shù)值得說(shuō)明一下。reclaimPolicy: Delete表示PVC刪除后對(duì)應(yīng)的PV和NFS目錄也會(huì)被清理掉。archiveOnDelete: true是個(gè)很實(shí)用的選項(xiàng)它不會(huì)直接刪目錄而是把目錄重命名成archived-xxx相當(dāng)于留了個(gè)后悔藥。萬(wàn)一誤刪了PVC數(shù)據(jù)還在NFS服務(wù)端能找到。生產(chǎn)環(huán)境我強(qiáng)烈建議開(kāi)啟這個(gè)選項(xiàng)。執(zhí)行安裝helm install nfs-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \ -f values.yaml \ -n kube-system裝完后檢查Pod狀態(tài)kubectl get pods -n kube-system | grep nfs看到Running就說(shuō)明Provisioner起來(lái)了。再看一下StorageClasskubectl get sc應(yīng)該能看到名為nfs-client的StorageClass。如果沒(méi)看到多半是RBAC權(quán)限或者鏡像拉取有問(wèn)題用kubectl logs看Provisioner的日志排查。3.3 手工部署方式與關(guān)鍵配置解讀有些環(huán)境不方便用Helm那就手工部署。核心資源包括ServiceAccount、ClusterRole、ClusterRoleBinding、Deployment、StorageClass。我重點(diǎn)說(shuō)Deployment里的幾個(gè)關(guān)鍵配置。Provisioner容器啟動(dòng)參數(shù)里--provisioner指定供給器名稱必須和StorageClass里的provisioner字段一致否則PVC不會(huì)觸發(fā)動(dòng)態(tài)供給。--nfs-server和--nfs-path指定NFS服務(wù)端信息。環(huán)境變量POD_NAMESPACE用fieldRef注入讓Provisioner知道自己跑在哪個(gè)命名空間。Deployment的volume部分要掛載一個(gè)NFS卷指向服務(wù)端的導(dǎo)出目錄。這樣Provisioner容器內(nèi)部才能在該目錄下創(chuàng)建子目錄。掛載配置大概長(zhǎng)這樣volumes: - name: nfs-client-root nfs: server: NFS_SERVER_IP path: /data/nfs/k8s容器里的掛載點(diǎn)通常是/persistentvolumesProvisioner就在這個(gè)路徑下創(chuàng)建子目錄。StorageClass的YAML里provisioner字段填k8s-sigs.io/nfs-subdir-external-provisionerreclaimPolicy按需選Delete或RetainvolumeBindingMode用Immediate就行NFS不涉及拓?fù)涓兄{(diào)度。手工部署的好處是每個(gè)字段都看得見(jiàn)出問(wèn)題容易定位。Helm部署省事但排查時(shí)得先helm get manifest把渲染后的YAML導(dǎo)出來(lái)看。兩種方式我都用過(guò)建議新手先手工部署一遍理解每個(gè)組件的作用之后再上Helm提效。4. PVC動(dòng)態(tài)掛載的完整實(shí)操鏈路4.1 創(chuàng)建一個(gè)測(cè)試PVC并觀察綁定過(guò)程StorageClass就緒后創(chuàng)建一個(gè)PVC試試apiVersion: v1 kind: PersistentVolumeClaim metadata: name: test-pvc namespace: default spec: accessModes: - ReadWriteMany storageClassName: nfs-client resources: requests: storage: 1Gi注意accessModes這里寫的是ReadWriteMany因?yàn)镹FS天然支持多節(jié)點(diǎn)讀寫。如果寫ReadWriteOnce雖然也能綁定但語(yǔ)義上不太匹配NFS的能力。storageClassName必須和前面創(chuàng)建的StorageClass名字一致。提交之后用kubectl get pvc -w盯著看。正常流程是PVC先變成PendingProvisioner監(jiān)聽(tīng)到事件在NFS目錄下創(chuàng)建子目錄生成PV然后PVC變成Bound。整個(gè)過(guò)程通常幾秒鐘。如果超過(guò)一分鐘還是Pending就要查Provisioner日志了。綁定成功后kubectl get pv能看到自動(dòng)生成的PV名字一般是pvc-uuid格式。PV的claimRef指向剛才的PVCnfs字段里的path就是Provisioner創(chuàng)建的子目錄路徑。到NFS服務(wù)端上ls /data/nfs/k8s應(yīng)該能看到一個(gè)類似default-test-pvc-pvc-xxxx的目錄。4.2 用Deployment掛載PVC驗(yàn)證讀寫光綁定成功還不夠得實(shí)際掛載進(jìn)Pod驗(yàn)證讀寫。寫一個(gè)簡(jiǎn)單的DeploymentapiVersion: apps/v1 kind: Deployment metadata: name: nfs-test-app spec: replicas: 2 selector: matchLabels: app: nfs-test template: metadata: labels: app: nfs-test spec: containers: - name: app image: busybox command: [/bin/sh, -c, while true; do echo $(date) /data/out.txt; sleep 5; done] volumeMounts: - name: nfs-vol mountPath: /data volumes: - name: nfs-vol persistentVolumeClaim: claimName: test-pvc這個(gè)Deployment跑兩個(gè)副本每個(gè)副本每5秒往/data/out.txt追加一行時(shí)間戳。因?yàn)閮蓚€(gè)副本掛的是同一個(gè)NFS目錄所以它們會(huì)往同一個(gè)文件里寫。等一會(huì)兒進(jìn)Pod里看kubectl exec -it pod-name -- cat /data/out.txt應(yīng)該能看到時(shí)間戳在不斷增加而且兩個(gè)副本寫入的內(nèi)容混在一起。這就驗(yàn)證了NFS的ReadWriteMany能力。如果只有一個(gè)副本能寫、另一個(gè)報(bào)權(quán)限錯(cuò)誤那多半是NFS服務(wù)端導(dǎo)出配置里root_squash沒(méi)關(guān)或者目錄權(quán)限不對(duì)。4.3 刪除PVC時(shí)數(shù)據(jù)到底去哪了這是很多人容易踩坑的地方。前面StorageClass里配了reclaimPolicy: Delete和archiveOnDelete: true。當(dāng)你刪除PVC時(shí)PV會(huì)被刪除但NFS目錄不會(huì)被真刪而是被重命名為archived-原目錄名。你可以到NFS服務(wù)端確認(rèn)ls /data/nfs/k8s/ # 會(huì)看到 archived-default-test-pvc-pvc-xxxx 這樣的目錄如果archiveOnDelete設(shè)為false那目錄就直接被刪了數(shù)據(jù)找不回來(lái)。所以生產(chǎn)環(huán)境務(wù)必開(kāi)啟歸檔。另外如果reclaimPolicy設(shè)為RetainPV會(huì)變成Released狀態(tài)目錄保留原名但PV不能被新PVC復(fù)用需要管理員手工清理。三種組合的行為差異挺大建議在測(cè)試環(huán)境都試一遍心里有數(shù)。還有一個(gè)細(xì)節(jié)刪除PVC時(shí)如果還有Pod在掛載使用PVC會(huì)卡在Terminating狀態(tài)直到Pod被刪掉。這是K8S的保護(hù)機(jī)制防止數(shù)據(jù)正在寫入時(shí)被強(qiáng)制卸載。所以下線服務(wù)時(shí)先刪Pod再刪PVC順序別搞反。5. 那些讓我熬夜排查的坑與應(yīng)對(duì)方案5.1 PVC一直Pending的幾種典型原因PVC卡在Pending是最常見(jiàn)的故障。排查思路要按順序來(lái)先看StorageClass名字對(duì)不對(duì)kubectl get pvc name -o yaml看storageClassName字段如果為空或者拼錯(cuò)Provisioner根本不會(huì)理它。再看Provisioner Pod是否正常運(yùn)行kubectl logs看有沒(méi)有報(bào)錯(cuò)。常見(jiàn)錯(cuò)誤包括連不上NFS服務(wù)端、創(chuàng)建目錄權(quán)限不足、RBAC權(quán)限不夠。連不上NFS服務(wù)端的情況日志里會(huì)顯示mount failed或者connection timed out。這時(shí)候先在Provisioner Pod里手動(dòng)掛載測(cè)試kubectl exec進(jìn)去mount -t nfs server:/path /mnt看報(bào)什么錯(cuò)。如果是端口不通檢查防火墻和端口固定配置如果是權(quán)限拒絕檢查/etc/exports里的客戶端網(wǎng)段限制。權(quán)限不足的報(bào)錯(cuò)通常是mkdir permission denied。這是因?yàn)镹FS服務(wù)端做了root_squash而Provisioner以root身份創(chuàng)建目錄被映射成了nobody。解決辦法是在exports里加no_root_squash或者把導(dǎo)出目錄權(quán)限放開(kāi)。我一般測(cè)試環(huán)境直接chmod 777加no_root_squash生產(chǎn)環(huán)境再收緊。5.2 掛載成功但寫入報(bào)Read-only file system這個(gè)坑我踩過(guò)好幾次。Pod掛載PVC成功但一寫文件就報(bào)只讀。原因通常是NFS服務(wù)端導(dǎo)出目錄的權(quán)限問(wèn)題或者exports配置里寫成了ro。檢查/etc/exports確認(rèn)是rw不是ro。另外如果NFS服務(wù)端磁盤滿了或者文件系統(tǒng)變成只讀比如磁盤故障也會(huì)導(dǎo)致客戶端寫入失敗。用df -h和mount命令確認(rèn)服務(wù)端磁盤狀態(tài)。還有一種情況是SELinux在搗亂。某些發(fā)行版默認(rèn)開(kāi)啟SELinux會(huì)阻止NFS客戶端寫入。臨時(shí)用setenforce 0測(cè)試如果好了就說(shuō)明是SELinux策略問(wèn)題需要配置相應(yīng)的布爾值或者策略規(guī)則。生產(chǎn)環(huán)境不建議直接關(guān)SELinux而是加規(guī)則放行。5.3 Provisioner Pod反復(fù)重啟的排查Provisioner Pod如果一直CrashLoopBackOff先看日志。常見(jiàn)原因是鏡像拉取失敗國(guó)內(nèi)環(huán)境拉registry.k8s.io的鏡像可能超時(shí)需要提前把鏡像同步到私有倉(cāng)庫(kù)或者用鏡像加速。另一個(gè)原因是RBAC配置不全Provisioner沒(méi)有權(quán)限監(jiān)聽(tīng)PVC和創(chuàng)建PV日志里會(huì)報(bào)forbidden。檢查ClusterRole里是否包含了persistentvolumes的create、delete、list、watch權(quán)限以及persistentvolumeclaims的get、list、watch、update權(quán)限。還有一個(gè)隱蔽的問(wèn)題Provisioner的Deployment里掛載NFS卷時(shí)如果NFS服務(wù)端地址寫錯(cuò)或者網(wǎng)絡(luò)不通Pod會(huì)一直卡在ContainerCreating。用kubectl describe pod看Events會(huì)顯示FailedMount。這時(shí)候回到NFS服務(wù)端確認(rèn)服務(wù)是否正常網(wǎng)絡(luò)是否可達(dá)。5.4 多副本并發(fā)寫入的數(shù)據(jù)一致性問(wèn)題NFS本身提供的是最終一致性不是強(qiáng)一致性。多個(gè)Pod同時(shí)往同一個(gè)文件追加內(nèi)容在低并發(fā)下一般沒(méi)問(wèn)題但高并發(fā)時(shí)可能出現(xiàn)內(nèi)容交錯(cuò)或者部分丟失。如果業(yè)務(wù)對(duì)數(shù)據(jù)一致性要求高建議每個(gè)Pod寫自己的獨(dú)立文件而不是共享同一個(gè)文件。比如用Pod名字或者IP作為文件名后綴避免寫沖突。另外NFS的鎖機(jī)制在某些場(chǎng)景下不太可靠尤其是跨多個(gè)NFS客戶端做文件鎖時(shí)。如果應(yīng)用依賴文件鎖做互斥最好換成基于數(shù)據(jù)庫(kù)或者Redis的分布式鎖別依賴NFS鎖。這個(gè)坑在跑一些老式應(yīng)用時(shí)特別容易遇到表現(xiàn)是偶爾出現(xiàn)兩個(gè)進(jìn)程同時(shí)寫同一份數(shù)據(jù)導(dǎo)致文件損壞。6. 生產(chǎn)環(huán)境落地時(shí)的容量與性能考量6.1 NFS服務(wù)端磁盤容量規(guī)劃與監(jiān)控因?yàn)镹FS動(dòng)態(tài)供給不限制單個(gè)PVC的實(shí)際使用量所以服務(wù)端磁盤很容易被某個(gè)失控的應(yīng)用寫滿。一旦磁盤滿所有掛載該NFS的Pod都會(huì)寫入失敗影響面很大。我的做法是給NFS數(shù)據(jù)盤單獨(dú)分區(qū)并且設(shè)置監(jiān)控告警使用率超過(guò)80%就通知。監(jiān)控可以用Node Exporter采集磁盤指標(biāo)配合Prometheus和Alertmanager做告警規(guī)則。容量規(guī)劃上先估算所有有狀態(tài)服務(wù)的總存儲(chǔ)需求然后留至少30%的余量。比如預(yù)計(jì)總共需要500G那就準(zhǔn)備至少700G的磁盤。如果增長(zhǎng)快可以考慮用LVM做動(dòng)態(tài)擴(kuò)容或者直接上分布式存儲(chǔ)方案替代NFS。但分布式存儲(chǔ)運(yùn)維復(fù)雜度高小團(tuán)隊(duì)用NFS加監(jiān)控就夠了。6.2 提升NFS讀寫性能的幾個(gè)實(shí)用參數(shù)NFS掛載參數(shù)對(duì)性能影響很大。K8S的NFS Volume默認(rèn)掛載參數(shù)比較保守可以在StorageClass的mountOptions里調(diào)整mountOptions: - nfsvers4.1 - rsize1048576 - wsize1048576 - hard - timeo600 - retrans2 - noresvportrsize和wsize設(shè)成1M可以提升大文件傳輸吞吐。nfsvers4.1比3版本在并發(fā)和安全性上更好。hard表示掛載操作會(huì)一直重試而不是報(bào)錯(cuò)返回避免網(wǎng)絡(luò)抖動(dòng)導(dǎo)致應(yīng)用看到IO錯(cuò)誤。noresvport讓客戶端使用非保留端口某些NFS服務(wù)端配置下必須加這個(gè)才能掛載成功。不過(guò)這些參數(shù)不是萬(wàn)能的得根據(jù)實(shí)際網(wǎng)絡(luò)和負(fù)載調(diào)。我一般先用默認(rèn)參數(shù)跑一輪基準(zhǔn)測(cè)試記錄IOPS和吞吐然后逐項(xiàng)調(diào)整對(duì)比。用fio或者dd做簡(jiǎn)單測(cè)試就行別搞太復(fù)雜。6.3 NFS服務(wù)端高可用與數(shù)據(jù)備份思路NFS服務(wù)端是單點(diǎn)掛了整個(gè)存儲(chǔ)層就不可用。提升可用性有兩個(gè)方向一是用DRBD加Keepalived做雙機(jī)熱備主備切換時(shí)VIP漂移客戶端重新掛載二是直接用云廠商的NAS服務(wù)底層做了高可用省心但成本高。小團(tuán)隊(duì)如果預(yù)算有限雙機(jī)熱備方案比較現(xiàn)實(shí)但切換過(guò)程中會(huì)有短暫中斷應(yīng)用要能容忍。數(shù)據(jù)備份方面NFS服務(wù)端上的數(shù)據(jù)目錄要定期備份到另一臺(tái)機(jī)器或者對(duì)象存儲(chǔ)??梢杂胷sync做增量同步或者用restic、borg這類工具做去重加密備份。備份策略根據(jù)數(shù)據(jù)重要程度定核心數(shù)據(jù)每天全備加每小時(shí)增量非核心數(shù)據(jù)每天一次就行。備份完一定要做恢復(fù)演練不然真出事的時(shí)候發(fā)現(xiàn)備份不能用就尷尬了。7. 從靜態(tài)PV到動(dòng)態(tài)供給的遷移經(jīng)驗(yàn)7.1 存量靜態(tài)PV如何平滑過(guò)渡如果集群里已經(jīng)有一批靜態(tài)PV在用想遷移到動(dòng)態(tài)供給不能直接刪了重建得考慮數(shù)據(jù)遷移。我的做法是先部署好Provisioner和StorageClass新業(yè)務(wù)一律用動(dòng)態(tài)PVC。存量靜態(tài)PV繼續(xù)保留等對(duì)應(yīng)服務(wù)下次發(fā)版或者維護(hù)窗口時(shí)再逐步遷移。遷移單個(gè)服務(wù)時(shí)先把老PV里的數(shù)據(jù)用rsync或者cp拷到新創(chuàng)建的動(dòng)態(tài)PVC掛載目錄里然后改Deployment的PVC引用重啟Pod驗(yàn)證數(shù)據(jù)完整。確認(rèn)沒(méi)問(wèn)題后再刪掉老PVC和PV。整個(gè)過(guò)程可以在線做但最好選業(yè)務(wù)低峰期避免數(shù)據(jù)拷貝影響性能。7.2 多命名空間下的存儲(chǔ)隔離動(dòng)態(tài)供給默認(rèn)所有命名空間共用一個(gè)StorageClassPVC名字在不同命名空間可以重復(fù)Provisioner創(chuàng)建的目錄會(huì)帶上命名空間前綴所以不會(huì)沖突。但如果想給不同團(tuán)隊(duì)分配不同的NFS導(dǎo)出目錄可以創(chuàng)建多個(gè)StorageClass每個(gè)指向不同的NFS路徑。比如給開(kāi)發(fā)團(tuán)隊(duì)用/data/nfs/dev給測(cè)試團(tuán)隊(duì)用/data/nfs/test通過(guò)StorageClass名字區(qū)分。RBAC層面也可以做限制讓某個(gè)命名空間只能使用特定的StorageClass。不過(guò)這需要額外配置K8S原生不直接支持StorageClass級(jí)別的命名空間隔離得靠準(zhǔn)入控制器或者策略引擎來(lái)實(shí)現(xiàn)。小規(guī)模場(chǎng)景下靠命名規(guī)范和人工約定就夠了不必過(guò)度設(shè)計(jì)。7.3 日常運(yùn)維中值得固化的幾個(gè)檢查項(xiàng)跑了一段時(shí)間后我總結(jié)了幾條日常檢查清單每周花幾分鐘過(guò)一遍能避免大部分突發(fā)故障。第一確認(rèn)Provisioner Pod狀態(tài)正常沒(méi)有重啟記錄。第二檢查NFS服務(wù)端磁盤使用率超過(guò)閾值就清理歸檔目錄。第三看有沒(méi)有長(zhǎng)期處于Pending或者Terminating狀態(tài)的PVC及時(shí)處理。第四驗(yàn)證備份任務(wù)是否按時(shí)完成備份文件大小是否正常。這些檢查項(xiàng)可以寫成腳本定時(shí)跑輸出結(jié)果發(fā)到運(yùn)維群或者郵件。自動(dòng)化程度不用太高關(guān)鍵是形成習(xí)慣。存儲(chǔ)這東西平時(shí)不出事一出事就是大事提前花點(diǎn)時(shí)間巡檢比事后救火劃算得多。8. 寫在最后的一點(diǎn)個(gè)人體會(huì)這套NFS動(dòng)態(tài)供給方案我在好幾個(gè)集群里用過(guò)從測(cè)試環(huán)境到中小規(guī)模生產(chǎn)環(huán)境都跑得挺穩(wěn)。它最大的價(jià)值是把存儲(chǔ)分配這件事從手工操作變成了自助服務(wù)開(kāi)發(fā)同學(xué)提交PVC就能用運(yùn)維不用再當(dāng)人肉PV工廠。但前提是NFS服務(wù)端要配好、監(jiān)控要到位、備份要可靠這三樣缺一個(gè)都可能埋雷。另外提醒一句NFS終究是網(wǎng)絡(luò)存儲(chǔ)延遲比本地盤高一個(gè)數(shù)量級(jí)。對(duì)IO延遲敏感的應(yīng)用比如高頻交易或者實(shí)時(shí)數(shù)據(jù)庫(kù)還是老老實(shí)實(shí)用本地SSD或者專業(yè)塊存儲(chǔ)。NFS適合的是那些對(duì)延遲不敏感、但需要共享和持久化的場(chǎng)景。選型的時(shí)候想清楚業(yè)務(wù)需求別為了圖省事把不合適的負(fù)載硬塞進(jìn)來(lái)后面性能問(wèn)題會(huì)讓你更頭疼。