• 摩傑斯
  • 2026-10-29

Linux 磁碟沒滿,為什麼一個位元組都寫不進去(inode 耗盡排查)

那天的電話很典型:內部管理系統登不上去。頁面能開啟,登入框也正常,輸賬號密碼,轉一圈,回到登入頁。換瀏覽器、清快取、換賬號都試過,使用者的描述很準確——"永遠登入不上"。

最後查出來的原因,和登入、和這套系統、和資料庫都沒有關係:這臺 Linux 伺服器上有一個沒人管的郵件服務,把檔案系統的 inode 用完了。而那時候 df -h 顯示磁碟還有大半空閒。

這類故障在 Linux 上最有迷惑性的地方就在這裡:報錯說"No space left on device",人去看磁碟空間,空間充足;再去找大檔案,找不到;於是開始懷疑應用、懷疑資料庫、懷疑密碼。真正該看的那條命令,往往要繞很久才想起來。

這篇把 Linux 上這個過程和機制完整拆一遍,包括三組可以自己復現的實測,以及一套下次遇到"寫不進去"可以照著走的判斷順序。

Linux 磁碟沒滿,為什麼一個位元組都寫不進去(inode 耗盡排查)

一、故障是從"登不上"開始的

頁面能開啟,說明 Web 服務活著;登入框能顯示,說明應用還能響應請求。這兩點排除了"服務掛了"。接下來輸賬號密碼點登入,轉一圈回到登入頁——這個現象指向會話(session):登入成功後要寫一個會話檔案,之後每次請求憑它認人。會話寫不進去,登入這件事在邏輯上就完成不了:密碼校驗透過了,會話沒建起來,下一次請求仍然認為你沒登入,於是把你送回登入頁。

順著這個思路查下去:重啟應用服務,沒用;看應用日誌,日誌裡沒有報錯;查資料庫連線,正常;直接跑登入相關查詢,能出結果;賬號狀態和密碼都沒問題。

事後回看,有一個細節當時被忽略了——日誌裡不是沒有報錯,是報錯寫不出去。寫不進日誌的系統,看起來特別健康。

二、第一輪誤判:以為磁碟滿了

"寫不進去"最常見的理由是磁碟滿了,於是先看空間:

df -h

結果出乎意料:根分割槽還有幾十個 G 餘量,使用率不到六成。既然空間夠,那就不是磁碟的問題——當時我們就是這麼判斷的,這個判斷讓排查多繞了將近一個小時。

接著做的事是找"誰把空間吃掉了":翻大檔案、看日誌目錄、看上傳目錄、看資料庫檔案。du 一層層掃下去,最大的目錄也就幾個 G,加起來對不上任何異常。而系統那時候還在不停地報"寫不進去"。

真正讓人回頭的是一個很樸素的疑問:如果空間真的夠,為什麼建立不了一個新檔案?

順著這個問題試了另一條命令:

df -i
Filesystem      Inodes   IUsed    IFree IUse% Mounted on
/dev/vda1      6553600 6553600        0  100% /

inode 用完了,100%。而同一時刻 df -h 依然顯示空間充裕。兩條命令,兩個世界。

三、inode 是什麼,為什麼它也會用完

Linux 的 ext4 檔案系統裡,磁碟上有一塊區域專門記錄"檔案清單",每個檔案在清單裡佔一條記錄,這條記錄的編號就是 inode(索引節點)。一個檔案實際佔兩樣東西:

資料塊——檔案內容本身佔的地方,df -h 統計的是這個;inode——檔名、許可權、屬主、大小、時間、資料塊位置等元資訊,df -i 統計的是這個。

所以"空間夠不夠"和"還能不能建新檔案"是兩個獨立的問題。空間夠、inode 用完,結果就是:已有的檔案照常讀,一個字也寫不進去新的,報錯還是那句最容易誤導人的話——No space left on device。這句話裡既沒有 "disk" 也沒有 "inode",它只說"裝置上沒有空間了"。

兩種檔案系統的差別也值得記住:ext4 的 inode 總數在格式化那一刻就定下來,預設大約每 16KB 空間給一個 inode,一塊 100G 的盤大概六百多萬個,這個數不能線上調大;xfs 的 inode 是動態分配的,一般不會因為檔案數耗盡,但刪檔案後空間回收的表現和 ext4 不同(延遲分配與預分配)。

關鍵結論:inode 的消耗速度只跟"檔案個數"有關,跟"檔案大小"無關。一個 0 位元組的空檔案和一個 4G 的影片,佔的 inode 完全一樣。這就解釋了為什麼 du 掃不出異常——吃掉 inode 的那些檔案,加起來可能只有幾百兆,在幾十 G 餘量面前完全不值一提。

四、六十幾萬個小檔案,從哪來的

按目錄統計檔案數(只讀操作,inode 滿的時候照樣能跑):

du --inodes -d 1 /var | sort -rn | head

結果很乾脆:/var/spool/postfix 一個目錄佔了六十幾萬個 inode,其餘所有目錄加起來不到它的零頭。

postfix 是郵件服務。這臺機器上跑著一套內部管理系統,郵件服務是當初裝系統時按"預設全裝"留下的——面板帶了、系統帶了,誰也沒去關它。它平時安安靜靜,不佔 CPU、不佔記憶體,df -h 上看不出任何動靜,所以從來沒人注意過。

它是怎麼把 inode 吃光的,三件事就夠說清:

第一,郵件佇列是一個"每封郵件一個檔案"的地方。投遞不出去就留在 deferred(延遲佇列)裡等重試。postfix 按雜湊把它分成多層子目錄,初衷就是"別讓一個目錄裡堆幾十萬個檔案"——這個設計本身就說明,佇列堆積是被預料到的、需要防的問題。

第二,投不出去的郵件會一直重試。預設策略是隔一段時間重試一次,越往後間隔越長,一封郵件能在佇列裡躺好幾天。

第三,也是最要命的:退信會生退信。系統裡有定時任務,某些任務的輸出會以郵件形式發給本地賬號;如果這個賬號不存在、或本地投遞失敗,郵件系統就生成一封"退信"通知原發件人;這封退信如果也投不出去(比如收件人是 root@主機名,主機名解析不了,或者外發被網路策略擋住),它又進佇列、又重試、又生成新的退信。一封變兩封,兩封變四封。

這是典型的雪崩式增長,而且是無聲的:沒有告警、沒有日誌異常、CPU 記憶體都正常,只有 inode 在一點一點往下掉。等它掉到 0,所有需要"新建檔案"的操作同時停擺——寫不了會話檔案,登入永遠完不成;寫不了日誌,出錯了連原因都留不下來;驗證碼圖片生成不了;上傳附件、匯出報表、臨時檔案全部失敗。

使用者看到的仍然只是"登不上"。一個系統裡最先壞掉的,往往是最不顯眼的那個寫操作。

把佇列目錄認清楚,下次一眼就能判斷:

目錄含義能不能刪
maildrop / incoming剛接收、還沒落盤不要刪
active正在投遞的不要刪
deferred投遞失敗、等重試的可以刪(先刪最老的)
bounce退信處理中謹慎,正在生成的別刪
corrupt / hold損壞或人工掛起的確認後可刪

平時看佇列,三條命令夠用:

mailq                       # 列出佇列
postqueue -p | tail -1      # 只看彙總:-- 632847 Kbytes in 612304 Requests
postsuper -d ALL deferred   # 清空延遲佇列(謹慎,先看清數量)

那次彙總行給出的數字是六十多萬封。這個數量不是一天漲起來的——起初每天幾十封,投不出去的越攢越多,退信又生新的退信,最後每天新增幾千封。它漲了幾個月,而這段時間裡沒有任何一個監控項能看見它。

五、先把寫入恢復,再談根治

inode 滿了只治一件事:刪。但"刪"在佇列目錄上有風險——直接 rm -rf 會把 postfix 自己維護的目錄結構、屬主和許可權一起刪掉,服務重啟後大機率不正常;刪正在投遞的檔案會把佇列狀態弄亂。當時的處置順序是這樣的:

第一步,留證據。先把 df -i、佇列規模、目錄清單各存一份,再動手。刪完再想覆盤,資料就沒了。

df -i > /root/inode-before.txt
mailq | tail -3 > /root/mailq-before.txt
du --inodes -d 1 /var/spool/postfix | sort -rn > /root/postfix-inodes.txt

第二步,指令碼分批清,不一把梭。不用 postsuper -d ALL,原因是它會把 active 和 incoming 裡本來能正常送達的郵件一起刪掉;幾十萬個檔案一次刪,IO 會把已經不健康的機器壓到假死。做法是先刪最沒價值的:deferred 裡最早的那批(躺了幾天、早就沒意義的退信),以及 corrupt(損壞佇列)。

#!/bin/bash
Q=/var/spool/postfix/deferred
BATCH=2000
DRY=1          # 1=只統計不刪;確認後改 0

echo "清理前:" && df -i / | tail -1

for d in "$Q"/*/; do
  cnt=$(find "$d" -type f -mtime +2 | wc -l)
  [ "$cnt" -eq 0 ] && continue
  echo "$d 待清理 $cnt 個"
  [ "$DRY" -eq 1 ] && continue
  find "$d" -type f -mtime +2 -print0 | xargs -0 -n "$BATCH" rm -f
done

echo "清理後:" && df -i / | tail -1

幾個細節:-mtime +2 只刪兩天前的,剛進來還在重試的不動;-print0 加 xargs -0 處理檔名裡的特殊字元;-n 2000 分批,避免單次刪除把 IO 打滿;DRY=1 先跑一遍看清單,確認目錄和數量合理再改 0 真刪。執行過程中用 df -i 盯著,看到使用率往下走就說明有效。

第三步,立刻驗證寫入恢復。不用等使用者反饋,自己造一個檔案:touch /tmp/inode-check,能建立就說明寫入鏈路通了,然後才去看登入。

第四步,停掉郵件服務,這才是根治。清佇列只是把水位降下來,服務還在跑,退信還會繼續生。新系統用 systemctl disable --now postfix;沒有 systemd 的老系統用 chkconfig postfix off 加 service postfix stop。停完隔十幾分鍾再看 df -i,不應再有量級變化。

為什麼這一步算根治:問題的源頭不是"佇列滿了",而是"這臺機器根本不需要一個郵件服務"。它不承擔業務功能,只是安裝時的預設產物,唯一作用是替定時任務發一些沒人看的郵件,發不出去就攢著。停掉它,等於關掉一條沒人使用、卻在持續生產垃圾的流水線。

反面情況也要說清楚:如果業務確實要發信(告警通知、找回密碼),就不能一停了之,而要做三件事——配一個能真正投遞出去的中繼、給佇列設上限和清理策略、把佇列長度納入監控。能發出去的信不會堆佇列,所有佇列堆積本質都是"發不出去還在發"。

六、三組實測:把這件事復現出來

把關鍵機制單獨復現一遍,三組都在容器裡做,不動生產環境。

實驗一:空間還剩一半,inode 用光,寫入失敗。用 50M、只給 800 個 inode 的臨時檔案系統(tmpfs 的 nr_inodes 正好能限定 inode 數):

mkdir -p /mnt/a
mount -t tmpfs -o size=50m,nr_inodes=800 tmpfs /mnt/a
for i in $(seq 1 900); do touch /mnt/a/f_$i 2>/dev/null || break; done
df -h /mnt/a ; df -i /mnt/a ; touch /mnt/a/zzz
建成的檔案數: 799
Filesystem      Size  Used Avail Use% Mounted on
tmpfs           50.0M    0  50.0M   0% /mnt/a
Filesystem    Inodes  Used  Avail Use% Mounted on
tmpfs            800   800      0 100% /mnt/a
touch: /mnt/a/zzz: No space left on device

空間使用率 0%、inode 使用率 100%、報錯 No space left on device。三行放在一起,就是整篇文章要講的事。799 個檔案全是 0 位元組,但它們把 800 個 inode 用掉了 799 個。

實驗二:檔案刪了,空間沒回來。這條和 inode 是兩種不同的"刪了也沒用",排查時特別容易混:

mkdir -p /mnt/b
mount -t tmpfs -o size=10m tmpfs /mnt/b
dd if=/dev/zero of=/mnt/b/big.bin bs=1M count=5 2>/dev/null
sh -c 'exec 3</mnt/b/big.bin; sleep 30' &
sleep 1 ; rm -f /mnt/b/big.bin
df -h /mnt/b ; ls -l /proc/$!/fd | grep big
--- rm 之後(空間沒回來)---
tmpfs    10.0M  5.0M  5.0M  50% /mnt/b
--- 誰還佔著這個已刪檔案 ---
lr-x------ 1 root root 64 Oct 29 00:55 3 -> /mnt/b/big.bin (deleted)

檔案被程序開啟著,rm 掉,目錄裡看不到,但空間不釋放,直到程序退出或關閉控制代碼。這正是"df 說滿了、du 卻找不到大檔案"的最常見原因(lsof +L1 或 /proc/<pid>/fd 能看到 (deleted))。而 inode 耗盡時,刪檔案是立刻有效的,刪一個回來一個——所以處置方式完全不同:前者要重啟那個程序,後者只能刪。

實驗三:分批刪,立即恢復。刪掉一半後,使用率從 100% 掉到 50%,touch 退出碼立刻變成 0。

--- 清理前 ---
tmpfs   800  800    0  100% /mnt/a
本輪刪除 400 個檔案
--- 清理後 ---
tmpfs   800  400  400   50% /mnt/a
--- 再寫一個檔案 ---
touch 退出碼: 0

生產環境上就是這個效果:不是"等它自己好",是刪下去就見效。

七、怎麼提前發現,而不是等使用者來報

這次故障之所以拖到"使用者打來電話"才被發現,是因為監控裡只有 CPU、記憶體、磁碟空間,沒有 inode。三件事補上:

一,df -i 進監控,和 df -h 並列,使用率到 80% 告警。inode 耗盡的過程通常是緩慢爬升然後突然見底,80% 給的是處置視窗。

二,佇列長度進監控。用郵件服務的機器,看 mailq | tail -1 的數量,或直接數 deferred 下的檔案。這個數持續上漲就是"發不出去還在發"的訊號,比 inode 更早暴露問題。

三,按目錄建 inode 基線。記一次正常情況下各主要目錄有多少檔案,出問題時直接對比。/var/spool、/tmp、會話目錄、快取目錄、上傳臨時目錄是高發區。

還有一條容易被忽略:inode 告警不能用"空間夠不夠"來替代。一塊盤空間用了 90% 但檔案很少,inode 可能只用 5%;另一塊盤空間用了 30%、inode 已經 95%。這兩種狀態在 df -h 上一個看著更危險、一個看著更安全,實際正好相反。

八、幾個錯誤動作,和為什麼不能那麼做

rm -rf /var/spool/postfix。佇列目錄的結構、屬主、許可權都是 postfix 自己維護的。刪掉整棵樹,服務重啟後大機率起不來,或者起得來但佇列狀態錯亂。要清就清裡面的檔案,保留目錄樹。

直接刪 active 和 incoming。這兩個是正在投遞和剛進來的佇列,刪這裡會丟掉本來能正常送達的郵件。先刪 deferred 和 corrupt。

一條 rm 刪六十幾萬個檔案。引數過長直接報 Argument list too long;就算用 find -delete 跑,刪除動作本身要更新目錄和日誌,IO 被打滿,本來 inode 滿了就已經很脆,很容易拖到假死。

只想擴容。加盤、擴分割槽解決的是空間,不解決 inode。ext4 擴容會按固定比例補 inode,缺的是幾百萬個,擴一倍空間才多一倍,通常不夠;而且擴容期間業務還在持續產生垃圾,等於一邊加水一邊漏水。

用 tune2fs 調 inode 引數。ext4 的 inode 總數在格式化時就定了,不能線上增加,tune2fs 能調的項解決不了"數不夠"。要大幅提高只能重建檔案系統,那是帶停機的遷移,不是應急手段。

把服務留著、只掛個定時清理指令碼。這是我們一開始差點做的選擇:既然清一次能好,就每天清一次。這等於承認一條沒人用的流水線繼續生產垃圾,然後用掃地機器人應對。清理指令碼該有,作為兜底,但根治是停服務。

九、這件事和"最小化安裝"是同一件事

我們在應用環境搭建那篇裡寫過裝系統的原則:最小化安裝,不需要的服務不裝;裝完還要回頭做減法,把預設帶上來又用不上的東西關掉。這次故障就是那條原則的反面教材,而且它暴露了一個平時看不見的成本——多裝一個服務,成本不止是記憶體和 CPU,還有它在無人看管時的行為。

郵件服務在 df -h 上完全隱形,在 top 上幾乎看不見,但它每天在往佇列裡寫檔案。等到它把 inode 用光,表現出來的是"業務系統登入不上"——一個和郵件毫無關係的症狀。

同一邏輯也適用於安全側:機器上多一個監聽埠、多一個能發信的服務,就是多一個可被利用的入口。安全加固裡講關閉不需要的服務、收斂攻擊面,和這件事其實是同一個動作的兩面——關掉不需要的服務,同時解決"資源被悄悄消耗"和"攻擊面偏大"。

判斷標準一句話:這個服務如果今天掛掉,業務會不會受影響?不會,就該考慮停掉;不確定,先查它最近有沒有實際工作(郵件看佇列和日誌,其他服務看連線數和日誌),確認沒在幹活再停。

十、下次遇到"寫不進去",按這個順序走

把這次的路徑壓成一張判據表。順序很重要,因為前兩步會把人帶偏,第三步才是這次的真兇:

順序看什麼命令結果說明什麼
1空間df -h滿了 → 逐層找大檔案
2誰佔著已刪檔案lsof +L1有 (deleted) → 重啟對應程序即釋放
3inodedf -i100% → 本文這種情況,只能刪
4誰在造檔案du --inodes -d1 /var定位目錄,再往下鑽
5寫入通了嗎touch /tmp/x能建 → 鏈路已通,再查應用
6會不會復發10 分鐘後再看 df -i繼續漲 → 源頭沒停,回第 4 步

兩個提醒:第 1 步和第 3 步要一起做,別因為 df -h 好看就跳過 df -i;應用日誌裡"沒有報錯"要當成線索,不當成結論。

這類故障單看每一環都不復雜,難的是它出現在錯誤的位置上——所有表面現象都在指向應用,而真兇在系統層、在一個沒人記得裝過的服務裡。我們摩傑斯做這類應急處理時,第一步永遠是先把"寫入能不能成功"這件事確認清楚,再往上查,因為一個連日誌都寫不進去的系統,它自己說什麼都不能信。這類問題通常一次現場處置加一輪服務收斂就能根治,剩下的功夫在監控上——把 inode、佇列長度、目錄檔案數這三項看見,同類事故就不會等到使用者打電話。

專業模具,自動化零件加工製造行業管理軟體
中小企業ERP,MES,MRP解決方案
貿易庫存Saas管理系統,Linux郵件伺服器方案

聯絡電話:18625220688

解決方案

模具製造業資訊化
專注模具廠生產管理,顯著縮短交付週期
鈑金行業ERP系統
鐳射切割、折彎、焊接全流程管理
客製化自動化設備管理
報價單、生產進度、成本核算一體化
刀具/治具管理系統
檢具生產全流程數字化管控

資源下載

MES白皮書

模具製造執行系統深度解析

產品介紹文件

摩傑斯PMS系統功能詳解

常見問題FAQ

解答您的所有疑問

聯絡我們

馬上
諮詢我們

14年行業經驗,專注模具、鈑金、客製化自動化設備數字化轉型

186 2522 0688

崑山總部,周邊2小時上門服務

聯絡方式
產品服務詢價

產品及解決方案線上免費諮詢

獲取詢價

常見問題

Q:磁碟明明還有空間,為什麼會寫不進去?
A:因為檔案系統除了"空間",還有一項資源叫 inode——每個檔案佔一條記錄,記錄用完就建不了新檔案,報錯仍然是 No space left on device。這種情況用 df -h 看不出異常,必須用 df -i 才看得見。

Q:df -h 顯示還有幾十 G,df -i 卻是 100%,這說明什麼?
A:說明檔案個數把 inode 用光了,而不是空間被佔滿。此時已有檔案照常讀取,但任何新建檔案的操作都會失敗:寫會話、寫日誌、生成驗證碼、上傳附件、匯出報表全部停擺。

Q:怎麼快速找出是哪個目錄佔的 inode?
A:du --inodes -d1 /var | sort -rn 先看一級目錄,再往下鑽兩三層基本就能定位。老版本 coreutils 沒有 --inodes,可以退一步用 find <目錄> -xdev -type f -printf '%h\n' | sort | uniq -c | sort -rn | head。這兩個命令都只讀,inode 滿了也照樣能跑。

Q:為什麼最先表現出來的是"登入不上"?
A:因為登入依賴會話檔案。密碼校驗透過了,但會話檔案寫不進去,登入這件事在邏輯上就完成不了,下一次請求仍然認為你沒登入,於是把你送回登入頁。同理,日誌也寫不進去——所以排查時看到的是"沒有報錯",實際是報錯留不下來。

Q:df 說滿了,du 卻找不到大檔案,是同一回事嗎?
A:不是。那是另一種情況:檔案已經被 rm 掉了,但仍有程序開啟著它,空間要等程序退出才釋放。用 lsof +L1 或看 /proc//fd 裡的 (deleted) 能確認。區別在於處置方式——控制代碼問題重啟程序就解決,inode 問題只能刪檔案。

Q:inode 用完了,擴容能解決嗎?
A:只能按比例緩解。ext4 擴容時新增空間會按格式化時的比例補 inode,缺的是幾百萬個這種量級,擴容通常補不上;而且擴容期間業務還在持續產生垃圾,等於一邊加水一邊漏水。真要大幅提高 inode 密度只能重建檔案系統,那是帶停機的遷移。

Q:xfs 會不會遇到同樣的問題?
A:一般不會,xfs 的 inode 是動態分配的。但它有別的表現要注意:刪了檔案之後,df 上的可用空間可能不會立刻變化(延遲分配與預分配),容易出現"刪了沒釋放"的錯覺。

Q:郵件佇列能不能直接把目錄刪掉?
A:不要。佇列目錄的結構、屬主、許可權由 postfix 自己維護,刪整棵樹會讓服務重啟後異常或佇列狀態錯亂。正確做法是先刪 deferred 裡最早的、以及 corrupt 目錄裡的檔案,保留目錄結構;active 和 incoming 裡是正在投遞的,不要動。

Q:停掉郵件服務會不會影響業務?
A:要看這臺機器有沒有真的在發信。判斷方法是看佇列有沒有長期積壓、日誌裡是不是全是重試和退信——如果一封都沒投遞成功過,它就沒有存在價值。反之如果業務依賴發信(找回密碼、告警通知),不能一停了之,要配可用的中繼、給佇列設上限,並把佇列長度納入監控。

Q:怎麼提前發現 inode 要滿了?
A:三件事:df -i 和 df -h 並列進監控,使用率 80% 告警;用郵件服務的機器把佇列長度(mailq | tail -1)納入監控;給主要目錄建一次 inode 基線,出問題時直接對比。/var/spool、/tmp、會話目錄、快取目錄、上傳臨時目錄是高發區。

Q:這件事和"最小化安裝"有什麼關係?
A:直接關係——那臺機器上的郵件服務是裝系統時按預設全裝留下的,從來沒人用過。多裝一個服務的成本不只是記憶體和 CPU,還有它在無人看管時的行為。裝完做減法,把不需要的服務關掉,同時解決資源被悄悄消耗和攻擊面偏大兩個問題。

Q:這類故障一次能處理完嗎?
A:通常可以。清佇列恢復寫入,再停掉不需要的服務根治,一次現場處置加一輪服務收斂就夠。剩下的功夫在監控上:把 inode、佇列長度、目錄檔案數三項看見,同類事故就不會等到使用者打電話。我們摩傑斯的應急處理也是按"先恢復業務、再定位源頭、最後補監控"這個順序做的。

最新訊息

線上訂閱