境的完整方案)
先說點實在話。臨時文件這東西幾乎每個用電腦的人都會碰到但真正把它當(dāng)回事的人不多。我曾經(jīng)在一臺測試服務(wù)器上見過 /tmp 目錄里堆了接近 30GB 的垃圾里面全是各種安裝包殘留、編譯中間產(chǎn)物和半年前的日志切片。更麻煩的是這臺機(jī)器磁盤已經(jīng)告警了可沒人敢手動刪——因為誰都不確定里面有沒有哪個文件正在被某個服務(wù)使用。這種場景相信不少運維和開發(fā)朋友都遇到過。臨時文件自動化方案說白了就是用一套確定的規(guī)則和工具代替“人工不定期清理”這個不靠譜的習(xí)慣。它解決的是三個層面的問題磁盤空間被不知不覺吃光、臨時文件混亂導(dǎo)致排障困難、以及手動刪除時誤刪正在使用的文件造成事故。這篇文章適合運維工程師、開發(fā)者、以及想把自己電腦整理干凈但不想天天操心的朋友。我會從方案選型、腳本設(shè)計、定時任務(wù)配置、誤刪防護(hù)一直講到 CI/CD 和容器環(huán)境里的進(jìn)階玩法全程給可復(fù)現(xiàn)的命令和配置。1. 臨時文件為什么需要一套自動化清理方案1.1 臨時文件是怎么悄悄長大的臨時文件不是只有一個 /tmp 目錄那么簡單。按照來源大致能分成幾類操作系統(tǒng)運行時會產(chǎn)生的套接字文件、PID 文件、軟件安裝包殘骸包管理器緩存比如 apt、pip、npm、yarn 留下的緩存開發(fā)工具的編譯產(chǎn)物像 dist、build、target 目錄以及 node_modules/.cache還有日志切割后的歷史文件。每一類單獨看都不大但架不住積少成多。我見過一個很典型的例子一個 Java 項目的編譯構(gòu)建每次會在 /tmp 里留下幾百 MB 的中間文件。開發(fā)環(huán)境跑了幾周/tmp 就被撐到了 20GB。還有個前端項目node_modules/.cache 里緩存了幾百 MB 的 webpack 持久化緩存磁盤空間莫名其妙就少了。這種問題最大的迷惑性在于你根本不知道是誰寫的也不知道什么時候?qū)懙呐挪槠饋硖貏e費勁。如果是在 Linux 服務(wù)器上inode 耗盡比磁盤滿更隱蔽。一個目錄里塞了上百萬個小文件df -h 看著還有空間但 df -i 已經(jīng) 100% 了這時候任何新文件都創(chuàng)建不了系統(tǒng)行為變得非常詭異。這類問題一旦發(fā)生手動清理難度極大沒有自動化方案幾乎只能靠重建文件系統(tǒng)來恢復(fù)。1.2 手動清理的三個硬傷手動清理臨時文件看起來簡單實際做起來有三個繞不過去的硬傷。第一個是頻率問題。人一定會忘記。磁盤告警才想起來去清理往往已經(jīng)是火燒眉毛的時刻。我見過很多人平時不關(guān)心等到發(fā)現(xiàn)磁盤滿了才臨時抱佛腳地刪文件刪完過兩個星期又滿了陷入無限循環(huán)。第二個是判斷問題。哪些臨時文件可以刪、哪些不能刪需要根據(jù)文件的修改時間、訪問時間、當(dāng)前是否被進(jìn)程占用來判斷。手動操作時沒人愿意逐項分析結(jié)果要么一刀切亂刪要么干脆一個都不敢動。第三個是路徑問題。臨時文件分布在系統(tǒng)的各個角落從 /tmp、/var/tmp 到用戶目錄下的 .cache、項目目錄里的構(gòu)建產(chǎn)物甚至容器運行時的 overlay 層。手動清理很難覆蓋全而且每次執(zhí)行的操作命令可能都不一樣最終效果完全不可控。1.3 自動化方案解決的核心問題自動化清理最大的價值不是“替你點了刪除按鈕”而是把清理這回事從“隨機(jī)事件”變成“確定性行為”。確定性的含義包括定期執(zhí)行、規(guī)則明確、操作可審計、結(jié)果可追蹤。規(guī)則明確指的是你可以精確指定哪些目錄、按什么條件、刪除多老的文件。比如“刪除 /tmp 下超過 7 天沒修改過的文件”這是一條可執(zhí)行、可驗證的規(guī)則。操作可審計指的是每次執(zhí)行都留下日志刪了多少文件、釋放了多少空間都記錄在案出問題能回溯。結(jié)果可追蹤則意味著你能通過日志和告警知道清理動作是否成功、是否有異常。一旦這套東西搭建起來它就能自己運轉(zhuǎn)不再依賴人工的臨場判斷。我自己的習(xí)慣是“先盤家底、再定規(guī)則、后上自動化”下面幾個章節(jié)就是圍繞這個思路展開的。2. 方案選型從“能用”到“好用”2.1 系統(tǒng)自帶方案不用白不用如果只看 Linux 系統(tǒng)本身其實已經(jīng)內(nèi)置了幾套基礎(chǔ)的臨時文件管理機(jī)制其中最值得了解的是 systemd-tmpfiles 和 tmpreaper。systemd-tmpfiles 通過配置文件來聲明哪些目錄要清理、按什么規(guī)則清理。配置文件放在 /etc/tmpfiles.d/ 下語法很直白。舉個例子# /etc/tmpfiles.d/clean-tmp.conf d /tmp 1777 root root 7d D /var/tmp 1777 root root 30d第一行表示 /tmp 目錄保留 7 天超過這個期限的文件會被 systemd-tmpfiles --clean 處理。第二行的 D 表示目錄本身和里面的內(nèi)容一起清理保留周期 30 天。這個方案的好處是零額外依賴、聲明式管理適合規(guī)則不復(fù)雜的場景。tmpreaper 則是傳統(tǒng) tmpwatch 的增強(qiáng)版用起來更靈活。比如這條命令tmpreaper 7d /tmp含義是刪除 /tmp 下 7 天未訪問的文件。tmpreaper 的優(yōu)勢在于它考慮了很多邊界情況比如不會去動正在被進(jìn)程使用的文件、知道怎么處理符號鏈接、還支持排除規(guī)則。不過在不少現(xiàn)代 Linux 發(fā)行版里systemd 已經(jīng)成為默認(rèn) init 系統(tǒng)systemd-tmpfiles 明顯是更“原汁原味”的選擇。還有一個很實用的系統(tǒng)機(jī)制是 logrotate。它管理的是日志文件不是普通臨時文件但日志和臨時文件經(jīng)?;煸谝黄?。給日志配置好按大小或日期切割再配合 rotate 份數(shù)限制能從源頭上避免日志文件無限增長。很多生產(chǎn)事故其實就是日志把磁盤寫滿了。這兩類系統(tǒng)方案的共同問題是“夠用但不夠細(xì)”。如果只有幾條靜態(tài)規(guī)則那直接用 systemd-tmpfiles 就夠了如果你需要按目錄、按條件、按項目自定義復(fù)雜的清理邏輯就得用腳本。2.2 腳本方案最靈活也最危險腳本方案的核心就是兩個工具find 負(fù)責(zé)查找cron 或 systemd timer 負(fù)責(zé)定時執(zhí)行。它靈活在哪里你可以自由組合路徑、時間條件、文件類型、大小條件還可以在刪除前做任何你想做的預(yù)處理。但是腳本方案也是最危險的。原因在于find 命令是一個純粹的“執(zhí)行者”它不會理解文件是否重要。一條命令寫錯可能把不該刪的全刪了。比如很多人剛接觸時會寫 find / -name *.log -exec rm {} ;這行命令會去系統(tǒng)里找所有 .log 文件然后刪掉包括某些應(yīng)用正在寫入的日志后果不堪設(shè)想。因此腳本方案的準(zhǔn)入門檻是你必須嚴(yán)格遵守“先 dry-run、再真實執(zhí)行”的原則。dry-run 就是只打印會刪除哪些文件不真正執(zhí)行刪除。這一步能幫你提前發(fā)現(xiàn)規(guī)則寫錯的問題。下面這行命令就是 dry-run 的典型寫法find /tmp -type f -mtime 7 -print先看輸出確認(rèn)列出來的文件全是該清的再把 -print 換成 -delete或者接上 -exec rm 來真正執(zhí)行。2.3 工具鏈方案處理特定生態(tài)的殘留系統(tǒng)方案和腳本方案解決的是通用臨時文件。但在真實場景里最有價值的是針對特定生態(tài)做清理。我舉例說明幾類項目構(gòu)建產(chǎn)物。node 項目里的 node_modules/.cache、dist、buildJava 項目里的 targetPython 項目里的pycache。這些目錄量大、增長快而且重建成本低。清理它們通常不用猶豫??梢詫懸粋€項目級清理腳本當(dāng)項目不再活躍時自動清掉這些構(gòu)建緩存。包管理器緩存。pip 的 ~/.cache/pipnpm 的 ~/.npmapt 的 /var/cache/apt。這些緩存刪掉之后下次安裝包會重新下載但在磁盤緊張的時候它們是最好的“出血點”。我通常在 CI 鏡像構(gòu)建的最后階段清掉這些緩存能顯著減小鏡像體積。Docker 相關(guān)的殘留。docker system prune -f 能清理懸空鏡像和停止的容器/var/lib/docker/overlay2 里堆積的層文件是磁盤大戶。配合日志輪轉(zhuǎn)和鏡像保留策略能避免 Docker 占據(jù)大量磁盤。每種生態(tài)都有自己的一套清理規(guī)則腳本的靈活性在這里體現(xiàn)得淋漓盡致。但要注意工具鏈方案往往涉及特定業(yè)務(wù)邏輯最好能結(jié)合項目實際情況做保留策略而不是一刀切全刪。2.4 選型建議與對比我直接給一個決策表格方便你對號入座方案優(yōu)勢劣勢適用場景systemd-tmpfiles零依賴、聲明式、系統(tǒng)原生規(guī)則表達(dá)能力有限常規(guī) /tmp 和 /var/tmp 清理tmpreaper安全邊界處理較好、主動檢查占用部分發(fā)行版需額外安裝傳統(tǒng)服務(wù)器、復(fù)雜排除規(guī)則自寫腳本 cron/timer完全可控、可定制業(yè)務(wù)邏輯風(fēng)險高依賴編寫者水平多目錄、混合規(guī)則的項目環(huán)境docker system prune專攻容器殘留、集成度高只處理 Docker 生態(tài)CI 跑機(jī)、容器化部署環(huán)境商業(yè)/平臺級工具集中管理、告警完善引入成本高多機(jī)、多團(tuán)隊的大規(guī)模治理選型的核心原則是盤子里有多少菜決定你用多大鍋。單機(jī)服務(wù)器上幾條規(guī)則別一上來就上重量級平臺方案而真正的大規(guī)模集群環(huán)境光靠幾個 cron 腳本也撐不住這時候才需要集中管理和統(tǒng)一的度量體系。3. 實操搭一套最小可用的自動清理系統(tǒng)這一節(jié)我會完整走一遍實際搭建流程從盤家底、寫規(guī)則到掛定時任務(wù)全部是可復(fù)現(xiàn)的命令和配置。我先說明一下我的環(huán)境假設(shè)一臺 Ubuntu 22.04 服務(wù)器Docker 和 Node 環(huán)境都有目標(biāo)是每天自動清理各類臨時文件。3.1 第一步盤點目錄和設(shè)計保留策略在動任何命令之前先花半小時做一次“家底盤點”。你需要明確知道這臺機(jī)器上哪些目錄是真正需要清理的以及每個目錄的合理保留周期。我推薦的盤點方法是先全盤掃一遍目錄占用定位最大的幾個目錄du -h --max-depth1 /tmp 2/dev/null | sort -hr | head -20 du -h --max-depth1 /var/tmp 2/dev/null | sort -hr | head -20 du -h --max-depth2 ~/.cache 2/dev/null | sort -hr | head -20然后根據(jù)目錄實際內(nèi)容和業(yè)務(wù)要求給每個目錄設(shè)定保留周期。我給一套通用的初始策略你可以直接抄目錄保留周期說明/tmp1~3 天系統(tǒng)臨時文件生命周期極短/var/tmp7~30 天相對持久但也不應(yīng)長期堆積~/.cache/pip、~/.npm30 天重新下載成本低但沒必要天天刪項目 target、dist、node_modules/.cache7 天構(gòu)建產(chǎn)物可重建日志切割后的舊文件7~14 天由 logrotate 管理更佳Docker 懸空鏡像立即清理docker system prune 處理注意保留周期的設(shè)定不是拍腦袋。太短可能導(dǎo)致正在使用的文件被刪太長則清理沒意義。我的經(jīng)驗是“寧保守勿激進(jìn)”初次設(shè)定可以給到雙倍周期運行一兩周觀察后再逐步縮短。3.2 第二步寫核心清理腳本腳本是整套系統(tǒng)的核心。我給出一個我實際在用的版本做了精簡但核心邏輯完整。它支持日志輸出、支持排除規(guī)則、也支持 dry-run 模式。#!/usr/bin/env bash # 臨時文件自動清理腳本 # 用法: # ./clean-tmp.sh --dry-run # 只打印將刪除的文件不實際刪除 # ./clean-tmp.sh # 真實執(zhí)行清理 set -euo pipefail DRY_RUNfalse if [[ ${1:-} --dry-run ]]; then DRY_RUNtrue fi LOG_FILE/var/log/clean-tmp.log # 需要清理的目錄列表空格分隔 CLEAN_DIRS(/tmp /var/tmp /root/.cache/pip /root/.npm) # 各目錄保留天數(shù), 默認(rèn)7天 declare -A KEEP_DAYS KEEP_DAYS[/tmp]3 KEEP_DAYS[/var/tmp]14 # 排除規(guī)則的正則表達(dá)式 EXCLUDE_PATTERN(\.dockerenv|\.X11-unix|\.ICE-unix|systemd-private.*-(systemd.*)/) log() { echo $(date %Y-%m-%d %H:%M:%S) $* $LOG_FILE } clean_dir() { local dir$1 local keep_days${KEEP_DAYS[$dir]:-7} local threshold${keep_days} if [[ ! -d $dir ]]; then log SKIP: $dir not exists return 0 fi log START: $dir keep_days$keep_days if [[ $DRY_RUN true ]]; then # dry-run: 只打印 find $dir -type f -mtime $threshold \ -regextype posix-extended ! -regex $EXCLUDE_PATTERN \ -printf %p %s bytes\n else # 真實刪除刪除前統(tǒng)計空間 local before0 local after0 before$(du -sb $dir 2/dev/null | awk {print $1}) find $dir -type f -mtime $threshold \ -regextype posix-extended ! -regex $EXCLUDE_PATTERN \ -delete after$(du -sb $dir 2/dev/null | awk {print $1}) log DONE: $dir released $((before - after)) bytes fi } for d in ${CLEAN_DIRS[]}; do clean_dir $d done log ALL DONE這個腳本有幾個關(guān)鍵設(shè)計我說一下理由。set -euo pipefail 是必須的。set -e 讓腳本在遇到錯誤時立即退出set -u 直接爆出未定義變量pipefail 保證管道中任一環(huán)節(jié)失敗都會讓整個命令失敗。這三個組合起來能讓你第一時間發(fā)現(xiàn)腳本本身的問題而不是讓它帶病運行。find 命令中 -type f 避免了誤刪目錄。如果直接 -delete目錄也會被遞歸刪除風(fēng)險極大。保留目錄結(jié)構(gòu)只刪除文件是更穩(wěn)妥的路徑。-delete 相比于 -exec rm {} 更安全因為 find 內(nèi)置的 -delete 會在內(nèi)部處理一些邊界情況比如先檢查目錄是否為空避免刪非空目錄時出錯。排除規(guī)則用的是 -regextype posix-extended 和 -regex它的作用是保護(hù)特殊路徑。比如 systemd-private 動態(tài)生成的私有目錄里面可能存有運行時 socket不能亂刪。腳本里的 du 統(tǒng)計可以幫你直觀看到每次釋放了多少空間日志對排查很有幫助。3.3 第三步配置定時任務(wù)腳本寫好后剩下的就是讓它按時跑起來。Linux 下有兩種主流方式cron 和 systemd timer。cron 是最傳統(tǒng)的方式配置很簡單。編輯 /etc/crontab 或使用 crontab -e加入一行0 2 * * * root /opt/scripts/clean-tmp.sh /var/log/clean-tmp.log 21這表示每天凌晨 2 點執(zhí)行一次清理腳本。為什么選這個時間因為臨時文件清理的最佳窗口是業(yè)務(wù)低峰期大多數(shù)服務(wù)在凌晨的負(fù)載最低。如果服務(wù)器上跑著夜間批處理任務(wù)就需要避開那段時間。systemd timer 是更現(xiàn)代的選擇。我需要先創(chuàng)建一個 service 單元和一個 timer 單元# /etc/systemd/system/clean-tmp.service [Unit] DescriptionClean temporary files Afternetwork.target [Service] Typeoneshot ExecStart/opt/scripts/clean-tmp.sh# /etc/systemd/system/clean-tmp.timer [Unit] DescriptionRun clean-tmp daily at 02:00 [Timer] OnCalendar*-*-* 02:00:00 Persistenttrue [Install] WantedBytimers.target然后執(zhí)行systemctl daemon-reload systemctl enable --now clean-tmp.timersystemd timer 相比 cron 有幾個優(yōu)勢可以設(shè)置 Persistenttrue即使機(jī)器當(dāng)時關(guān)機(jī)下次開機(jī)后也會補(bǔ)跑錯過的任務(wù)可以用 systemctl status 查看任務(wù)最近執(zhí)行情況日志集中管理方便排查。我個人的建議是新環(huán)境優(yōu)先用 systemd timer老環(huán)境沿用 cron 也沒問題。如果你用的是 Windows 服務(wù)器最常用的方式是“任務(wù)計劃程序”配合 PowerShell 腳本。PowerShell 里頭找臨時文件同樣簡單$threshold (Get-Date).AddDays(-7) Get-ChildItem -Path C:\Windows\Temp -Recurse -File | Where-Object { $_.LastWriteTime -lt $threshold } | Remove-Item -Force -ErrorAction SilentlyContinue在任務(wù)計劃程序里設(shè)置每天執(zhí)行一次即可。注意 C:\Windows\Temp 有很多文件被系統(tǒng)占用刪除時要帶上 -ErrorAction SilentlyContinue 忽略失敗否則任務(wù)會報錯。3.4 第四步先演練再上崗腳本和定時任務(wù)都配置好了但我不建議直接讓它以真實刪除模式運行。正確做法是先 dry-run 跑幾天。第一輪演練手動執(zhí)行 --dry-run仔細(xì)觀察輸出內(nèi)容。重點看兩個問題是否有不該刪的文件被列出來了是否有應(yīng)該清理的目錄被排除規(guī)則擋掉了。發(fā)現(xiàn)異常就調(diào)整腳本改完再跑。第二輪演練真實刪除但選擇業(yè)務(wù)最低峰期手動執(zhí)行一次。執(zhí)行前確保已經(jīng)備份哪怕只是備份腳本并且開啟了日志。等腳本連續(xù)跑了一周沒有異常再把它交給 cron 或 systemd timer 去自動執(zhí)行。這時候才算真正上崗。我特別想強(qiáng)調(diào)一點自動清理系統(tǒng)的“上線”不是一錘子買賣。它應(yīng)該像監(jiān)控系統(tǒng)一樣需要持續(xù)迭代。磁盤增長趨勢變化了、業(yè)務(wù)新增了臨時文件目錄、某個服務(wù)生命周期變短了這些都要反映到清理規(guī)則里。我基本上每季度會重盤一遍目錄占用同步更新一次腳本。4. 誤刪與安全清理工具的底線工程自動化清理的恐怖之處在于它不會疲倦也不會猶豫它是按照你的規(guī)則去執(zhí)行每一個刪除動作。如果規(guī)則有誤它會毫不猶豫地把所有命中的文件殺掉。所以守住安全底線是這套系統(tǒng)的生死線。4.1 正在被使用的臨時文件會怎樣這個現(xiàn)象值得單獨講。很多人在 Linux 上發(fā)現(xiàn)一個詭異的事情某個文件明明被刪除了但磁盤空間并沒有釋放。這是因為有一個進(jìn)程仍然持有該文件的打開句柄。在 Linux 的文件系統(tǒng)語義里文件刪除只是把目錄項移除但如果文件還被進(jìn)程打開著它的 inode 和磁盤塊并不會立刻釋放要等所有句柄關(guān)閉之后才真正回收。這意味著什么如果服務(wù)在運行時寫了一個日志文件你把它刪了服務(wù)并不會立刻崩潰日志會繼續(xù)寫入這個已經(jīng)“不存在”的文件里但你再也找不到這個文件了。直到服務(wù)重啟文件才被真正刪除期間磁盤空間一直占用著。排障的時候這種情況非常隱蔽。你 df -h 看磁盤空間沒少但目錄里文件又不見了很容易懷疑是被病毒入侵或者有日志清理任務(wù)出了問題。其實根因可能就是有人在 /tmp 里刪了某個正在使用的文件。避免這種問題最直接的辦法是用 lsof 檢查文件是否被占用。生產(chǎn)環(huán)境的清理腳本里我建議加一段# 檢查目錄中是否有被進(jìn)程打開的文件 find /var/tmp -type f -mtime 7 -print0 2/dev/null | xargs -0 -r lsof 2/dev/null如果 lsof 輸出有結(jié)果說明有文件正在使用中應(yīng)該調(diào)整保留策略或者將這些文件排除掉。4.2 權(quán)限、符號鏈接和變量空值三個經(jīng)典大坑清理腳本里最容易翻車的三個點權(quán)限、符號鏈接和變量空值。權(quán)限問題很好理解很多臨時目錄屬于 root普通用戶清理不了這需要用 root 或 sudo 執(zhí)行清理腳本。但問題在于一旦用 root 執(zhí)行腳本里哪怕有一個地方邏輯混亂后果會被無限放大。我的建議是腳本執(zhí)行的用戶權(quán)限要有明確邊界比如用獨立用戶跑清理任務(wù)不給不必要的 root 權(quán)限。符號鏈接坑是最隱蔽的。想象一下 /tmp/link 是一個指向 /etc 的符號鏈接。如果你執(zhí)行 find /tmp -type d -name link -delete危險不大但如果你在腳本里寫了 rm -rf /tmp/*在某個特殊情況下rm 會對符號鏈接指向的目標(biāo)進(jìn)行操作尤其是配合 --no-preserve-root 之類參數(shù)時可能把系統(tǒng)關(guān)鍵目錄刪掉。我見過一次事故有人把 find 的 -exec rm -rf {} ; 誤應(yīng)用于一個包含符號鏈接的目錄結(jié)果把鏈接指向的應(yīng)用目錄整個刪了。所以腳本里涉及目錄刪除時務(wù)必加上 -type d 的限制并且顯式排除符號鏈接find ... ! -type l。變量空值是最容易被新手忽視的經(jīng)典坑。在 bash 腳本中如果某個變量沒有賦值默認(rèn)就是空字符串。假設(shè)你寫了 rm -rf $DIR/而 $DIR 因為某種原因是空的那么命令行就變成 rm -rf /結(jié)果是災(zāi)難性的。防范手段很簡單腳本開頭用 set -u 讓未定義變量直接報錯或者在刪除前斷言變量非空[[ -n $DIR ]] || exit 1。順便說一個和空格相關(guān)的坑。文件名里帶空格是合法的但很多腳本在拼接路徑時沒有加引號find 的輸出在管道傳給 xargs 時會被錯誤分隔。處理辦法是使用 -print0 和 xargs -0或者直接用 -delete。我在 3.2 的腳本里使用 -delete就是考慮到這個。4.3 從“刪”到“移”更穩(wěn)妥的回收站模式對很多場景來說直接物理刪除是不必要的冒險。更穩(wěn)妥的方式是“移動”也就是先設(shè)置一個回收桶目錄把該清理的文件移動進(jìn)去觀察一段時間沒有異常再真正刪除。這在生產(chǎn)環(huán)境里非常實用。比如我可以把臨時文件先移到 /var/tmp/trash保留 14 天后再自動徹底刪除。這樣即使有誤判你還有回旋余地可以手工恢復(fù)到原位置。實現(xiàn)起來也不復(fù)雜在之前的清理腳本基礎(chǔ)上把 -delete 替換成 -exec mv {} /var/tmp/trash/ 即可。我實際用下來這個模式既滿足了清理需求又大幅降低了誤刪造成的不可逆風(fēng)險。唯一要注意的是回收桶目錄自身的清理別讓它變成另一個堆積點。給回收桶設(shè)置一個更長的保留周期比如 30 天然后定期清一遍。4.4 日志、告警與問題速查自動化清理必須留痕。日志是事后排查的唯一依據(jù)。我在腳本里已經(jīng)把每次清理的路徑和釋放空間寫入 /var/log/clean-tmp.log。如果你有集中日志平臺建議把這份日志同步上去如果沒有至少確保日志會按天切割避免單個文件無限增大。告警也很重要。單純把任務(wù)掛到 cron 里有效期是“靜默失敗”如果腳本出錯了可能只會默默寫個日志就完事。我的做法是在腳本末尾加上簡單的健康檢查如果本次釋放空間為 0或者腳本執(zhí)行異常就通過 webhook 發(fā)送告警到值班群。這不需要復(fù)雜的監(jiān)控平臺curl 一條 webhook 就能搞定。梳理一下常見問題方便你快速定位癥狀可能原因排查方法磁盤空間沒減小文件被進(jìn)程占用未釋放lsof 檢查句柄、重啟服務(wù)后觀察日志顯示刪除了但目錄還在find 匹配的是文件目錄未被刪除檢查排除規(guī)則、目錄層級某些目錄從未被清理名稱包含空格/特殊字符路徑拼接失敗用 -print0 驗證開啟 set -u清理任務(wù)不執(zhí)行cron 環(huán)境變量或 systemd timer 狀態(tài)異常systemctl status clean-tmp.timer誤刪了業(yè)務(wù)文件排除規(guī)則不完整、保留周期過短立即停止任務(wù)檢查回收桶恢復(fù)這些坑我基本上都踩過一遍。最有價值的教訓(xùn)是永遠(yuǎn)先 dry-run永遠(yuǎn)先移動后刪除永遠(yuǎn)要留日志。記住這三條自動清理就不會成為災(zāi)難制造機(jī)。5. 進(jìn)階多機(jī)、CI 與容器環(huán)境里的臨時文件治理5.1 給 CI 跑機(jī)做磁盤減負(fù)CI 系統(tǒng)是臨時文件堆積的重災(zāi)區(qū)。尤其是自建 GitLab Runner 或 Jenkins 節(jié)點每跑一次構(gòu)建都會在 workspace 和 /tmp 里留下大量中間產(chǎn)物。如果構(gòu)建頻率高磁盤空間兩三天就能告警。我處理過一臺 GitHub Actions 自建 runner每天跑幾十次構(gòu)建每次構(gòu)建產(chǎn)生的臨時文件從幾十 MB 到數(shù) GB 不等。最初磁盤 100GB半個月就滿了。后來加了雙重機(jī)制解決第一重是定時清理用上面說的腳本每天早上 4 點清一次第二重是構(gòu)建結(jié)束鉤子在每一個 job 收尾時主動清理該構(gòu)建產(chǎn)生的臨時目錄。構(gòu)建結(jié)束鉤子其實就是 CI 配置文件里的一段代碼比如 GitHub Actions 的 post 步驟- name: Cleanup workspace if: always() run: | docker system prune -f sudo rm -rf /tmp/* /var/tmp/* || true sudo rm -rf /home/runner/work/*/node_modules /home/runner/work/*/*/dist || true注意這里用的是 if: always()保證即使構(gòu)建失敗清理也會執(zhí)行。CI 跑機(jī)的治理思路是定時清理兜底構(gòu)建鉤子主動清理雙保險。另外 CI 鏡像構(gòu)建階段清掉包管理器緩存能顯著減小鏡像體積、縮短推送時間。5.2 多機(jī)統(tǒng)一治理從“單機(jī)腳本”到“批量執(zhí)行”當(dāng)服務(wù)器數(shù)量多到一定程度逐臺配置 cron 是不可維護(hù)的。這時候需要把清理規(guī)則集中管理。我常用的方案有兩種。一種是用 Ansible 之類的配置管理工具。把清理腳本和 timer 單元作為基礎(chǔ)設(shè)施代碼推送到所有目標(biāo)機(jī)器。好處是配置變更可以快速批量生效而且可以統(tǒng)一版本管理。另一種是搭建一個簡單的中心化日志和告警管道每臺機(jī)器跑完清理后把結(jié)果匯總到同一處比如收集日志到 Elasticsearch 或用簡單的 Webhook 上報。批量執(zhí)行時有一個重要細(xì)節(jié)不要在同一時間讓所有機(jī)器同時清理否則可能會同時觸發(fā)集中監(jiān)控平臺的告警風(fēng)暴也可能造成集中的 I/O 沖擊。給每臺機(jī)器的清理時間加上隨機(jī)偏移量比如 2 點到 3 點之間隨機(jī)執(zhí)行是一個成熟的做法。在 Kubernetes 集群里情況稍有不同。節(jié)點的臨時目錄和容器的 emptyDir 卷由 kubelet 管理kubelet 會根據(jù) eviction 閾值自動驅(qū)逐容器釋放臨時存儲。但這是“兜底式”的治理主要目的是避免整個節(jié)點磁盤寫滿。業(yè)務(wù)層面仍然需要自己管理鏡像殘留和構(gòu)建緩存。docker system prune 在容器節(jié)點上仍然很有用配合定期執(zhí)行能顯著減少磁盤占用。5.3 容器場景的特殊考量容器里的 /tmp 和宿主機(jī)上的 /tmp 有本質(zhì)不同。容器內(nèi)每個文件系統(tǒng)層都是臨時的容器刪除后層也隨之消失。但也因此產(chǎn)生了一個問題如果你在容器里堆積了大量臨時文件而容器長期不重建這些文件會一直占著內(nèi)存或磁盤。兩個實踐建議第一容器內(nèi)盡量避免使用 /tmp 做持久化存儲應(yīng)該使用掛載卷或者 emptyDir第二對跑長時間任務(wù)的有狀態(tài)容器定期重建是比清理內(nèi)部臨時文件更徹底的辦法。docker system prune 這個命令我在前面提到了展開說幾個參數(shù)docker system prune -f --volumes --filter until72h-f 表示不交互確認(rèn)--volumes 表示同時清理未使用的卷--filter until72h 只清理 72 小時前創(chuàng)建的懸空資源。這個命令特別適合 CI 跑機(jī)清理效果立竿見影。但注意默認(rèn)情況下 docker system prune 不會清理還在使用的鏡像和容器這一點安全性還可以不過加上 --volumes 之后要確認(rèn)沒有重要的持久卷被誤傷。還有一類常見容器殘留是構(gòu)建緩存。BuildKit 的緩存目錄可能非常龐大。對應(yīng)清理命令是 docker builder prune -f建議在 CI 構(gòu)建完成后定期執(zhí)行。總結(jié)一下容器的清理哲學(xué)優(yōu)先重建其次治理鏡像最后才是容器內(nèi)部清理。5.4 建立長效機(jī)制從“清理”到“預(yù)防”臨時文件治理的終極目標(biāo)不是“清得多”而是“產(chǎn)生得少”。當(dāng)自動化方案穩(wěn)定運行之后我會建議你花更多時間從源頭減少臨時文件的產(chǎn)生。具體可以做的事情很多規(guī)范日志切割配置 logrotate 的 maxsize 和 rotate統(tǒng)一包管理器緩存目錄并設(shè)置定期清理在 CI 流水線里對構(gòu)建產(chǎn)物做歸檔并自動刪除舊版本使用 tmpfs 掛載 /tmp讓系統(tǒng)重啟時臨時空間自動清空對需要的臨時數(shù)據(jù)直接寫入內(nèi)存文件系統(tǒng)而不是寫盤再刪。以 tmpfs 為例把 /tmp 掛載到內(nèi)存中讀寫速度快是一個好處更關(guān)鍵的是重啟后內(nèi)容自動清空完全不需要人為干預(yù)。但有個前提內(nèi)存要夠大否則 /tmp 占滿內(nèi)存反而影響系統(tǒng)穩(wěn)定性。在內(nèi)存資源充足的服務(wù)器上這是一個很優(yōu)雅的方案。預(yù)防思維的本質(zhì)是把“清理臨時文件”這件苦差事從“事后補(bǔ)救”變成“設(shè)計時考慮”。你可以在系統(tǒng)設(shè)計階段就把臨時文件的存儲路徑、保留時長、清理責(zé)任劃分清楚。能做到這一步之后維護(hù)的負(fù)擔(dān)會直線下降?;氐轿易约旱捏w會臨時文件自動化治理最被低估的價值其實不是省了多少 GB 磁盤而是讓系統(tǒng)行為變得可預(yù)期。定時任務(wù)每天都在跑日志每天都在記磁盤占用曲線慢慢變得平緩。那種“不知道哪一天磁盤會滿”的焦慮感消失之后你才有余力去做更有價值的事。這套方案本身不復(fù)雜但它需要你一點點打磨從單機(jī)腳本到多機(jī)批量從被動清理到主動預(yù)防每一步都是在為系統(tǒng)的長期穩(wěn)定夯實基礎(chǔ)。最后再分享一條個人經(jīng)驗每次清理腳本變更后至少保留一周的 dry-run 日志不要急著刪除。這些日志是你在排查“是不是清理腳本誤刪了文件”時最有力的證據(jù)。臨時文件清理這種操作寧可慢一點、啰嗦一點也千萬別拿生產(chǎn)環(huán)境做賭注。