當前位置: 妍妍網 > 碼農

帶你找回那些被 Docker 吃掉的磁盤空間

2024-05-12碼農

如果你是 Docker/Kubernetes 的重度使用者,應該多多少少會遇到一個問題「no space left on device」。


當然,如果你的硬碟空間很大,也不介意把大量空間用來存放不必要的 Docker 資源,那你可以忽略這篇文章了XD~

在我們討論如何有效避免Docker占用大量磁盤空間之前,讓我們先來談談Docker會占用哪些資源(導致空間占用):

  • Image: Container 的映像檔通常是透過 Dockerfile 結合 docker build 命令生成的

  • Container: 是具有獨立環境的Process(在Linux作業系統中使用namespace實作環境的隔離),通常使用docker create或docker run命令來啟動

  • Volume: 使用者可以透過Docker volume將Container掛載Host環境的數據(檔或整個資料夾)

  • Network: 可以使多個Docker Container(s)的網路環境相互通訊,Docker network的運作方式很大程度上取決於開發者使用何種Network driver,常見的driver有bridge、host、overlay、macvlan、none、external plugin

  • 在了解了 Docker 的常用資源之後,讓我們來看看這些資源分別有哪些狀態:

  • used: 指正在被某個container(s)使用的資源

  • unused: 完全沒有被container使用

  • dangling: 指那些失效的image,永遠不會被使用到

  • 要判斷資源是 used 或是 unused 其實非常簡單,Docker 的判斷標準是該資源目前是否被至少一個 Container 使用,如果不是,該資源就是 unused resource。

    而 dangling 是比較特別的狀態,它只存在於 Docker 映像中,原因是 Docker 映像具有版本概念。一般來說,在構建 Docker 映像時,我們會使用 -t 標誌為映像打上標簽(映像檔名加上版本號)。如果我們不指定版本號,Docker 將預設為最新版本。

    然而,當我們重復編譯同一個 tag name 的 docker image,那麽第一次生成的 docker image 在第二次編譯結束時就會進入 dangling 狀態(用 docker image ls 會觀察到一個 tag name 為 <none> 的 image),而這些 dangling image(s) 永遠不會被使用到,如果非週期性清除便會白白地占據磁盤空間。

    在討論完Docker資源和相關狀態之後,現在讓我們來看看如何清理這些unused/dangling resources吧!

    Docker 提供了一系列的 docker prune 指令,可以幫助我們清理不同型別的資源:

    # Remove all unused images, not just dangling ones$ docker image prune -a# Remove dangling images$ docker image prune$ docker network prune$ docker volume prune$ docker container prune# Remove all unused containers, networks, images (both dangling and unreferenced), and optionally, volumes.$ docker system prune

    其實,Docker的清理功能已經解決了一些場景的問題。比如,對於正在執行生產服務的虛擬機器或CI/CD Runner,我們只需要使用這些命令,並配合系統的定時任務,就可以輕松應對了。

    然而,就開發者個人環境而言,很多時候我們其實不希望刪除那些unused image(s),讓我們考慮以下情景:

    小明用 docker-compose 一次部署了20個容器,這些容器使用了20多個Docker映像。如果小明使用docker compose rm + docker image prune來清空資源,他會發現等到下次要啟用服務時,這些映像可能已經被Docker移除了。

    為了避免這個問題(偷懶),我們通常在本地測試部署環境的時候,都會選擇忽略清空資源的步驟,久而久之這些 dangling image 以及 unused volume 就會把本機的磁盤空間占滿……

    所以,更好的做法實際上是使用 Makefile 或者 shell 指令碼來處理 Docker 映像的編譯工作,同時在編譯完成後移除懸空映像或者指定標簽名稱的映像:

    docker image prune# ordocker image ls | grep "<YOUR_TAG_NAME>" | awk '{print $3}' | xargs docker image rm

    在上面的程式碼中,第二個命令可以一次刪除所有符合 grep 中的條件的image(s),這也是我經常使用的方法(畢竟使用 docker image prune 很容易誤刪無辜的映像……)

    到目前為止,我們已經知道了如何處理不同使用情境下的unused image和dangling image。然而,上述解決方案對於初學者來說並不太友好(也不太方便)。為了節省時間,我們可以將這些步驟編寫成一個shell指令碼,並放在系統的/bin目錄下,這樣就可以直接透過終端執行該指令碼:

    #!/bin/bashforce=falsewhilegetopts":f" opt; docase${opt}in f ) force=true ;; ? )echo"Invalid option: -$OPTARG" 1>&2exit 1 ;; : )echo"Option -$OPTARG requires an argument." 1>&2exit 1 ;;esacdoneshift $((OPTIND -1))if [ $# -ne 2 ]; thenecho"Usage: docker-clean [-f] <resource> <keyword>"exit 1firesource=$1keyword=$2if [ "$force" = true ]; then force_args="-f"elseread -p "Are you sure you want to delete all $resource with $keyword? [y/N] " confirmationif [ "$confirmation" != "y" ] && [ "$confirmation" != "Y" ]; thenecho"Operation cancelled."exit 0fificase$resourcein"image") docker images | grep "$keyword" | awk '{print $3}' | xargs docker rmi $force_args ;;"container") docker ps -a | grep "$keyword" | awk '{print $1}' | xargs docker rm $force_args ;; *)echo"Invalid resource type. Must be either 'image' or 'container'."exit 1 ;;esac

    順便說一句:

    整個 shell 指令碼都是使用 chatGPT 生成的,對於一些簡單的套用案例,chatGPT 真的是非常棒的工具(但也要記得檢查 chatGPT 提供的數據的可靠性,避免發生毀掉重要數據導致連夜訂票跑路……)

    這個指令碼可以移除特定的標簽名稱的影像和容器。使用教程請參考下面的範例:

    # 安裝$ git clone https://github.com/ianchen0119/docker-clean.git$ mv docker-clean/docker-clean /bin/docker-clean$ chmod 777 /bin/docker-clean# 使用$ docker-clean Usage: docker-clean [-f] <resource> <keyword>

    總結

    Docker這類容器化方案對開發者來說是一個非常友善的工具,但使用不當的話其實很容易影響到執行環境的機器。本篇文章簡單地介紹一些可參考的處理方式,如果你也有不錯的管理方式,也歡迎留言共同交流!

    最後補充一下個人不喜歡批次處理unused & dangling image(s)的原因:

  • 在重新編譯時,docker image會根據commit hash使用相應的緩存層(cache layer),以避免重復工作(re-work)。如果每次編譯都固定移除未使用的映像,也會同時清空這些緩存,從而大幅提高映像編譯的速度。

  • 在某些微服務測試場景下,如果一個影像需要額外的3分鐘來重現之前的步驟,那麽啟動整個服務可能需要額外占用30分鐘以上的時間……

  • 要在使用docker的同時最大化磁盤空間利用率,除了定期清理不需要的docker靜態資源,我們還可以從其他方面入手:

  • 使用 精簡化的 Docker 映像 作為基礎;

  • 盡量重用基礎映像: 如果應用程式沒有特殊的依賴關系,應盡量選擇重復使用的基礎映像來進行編譯或作為應用程式的執行環境;

  • 避免過多的 docker commit: 這一點與上述類似,有助於保持映像的簡潔。 但並非強制性要求,因為有時運維人員可能會頻繁更新某些特定的映像,如果 Dockerfile 寫得過於簡潔,可能會導致難以閱讀和難以利用緩存的情況發生;

  • 在安裝完成後,使用rm或apt-get remove等命令 移除安裝包 ,以清除編譯image期間產生的垃圾;

  • 使用distroless映像: 現在圈內也有許多人在呼籲盡量不要使用alpine這類的映像作為基礎,而是建議開發者盡可能地選擇distroless映像,具體原因就是出於容器安全的考慮。

  • 作者丨 睡醒想錢錢

    來源丨 j uejin.cn/post/7259032 711019855930

    dbaplus社群歡迎廣大技術人員投稿,投稿信箱: [email protected]

    活動推薦

    2024 XCOPS智慧運維管理人年會·廣州站將於5月24日舉辦 ,深究大模型、AI Agent等新興技術如何落地於運維領域,賦能企業智慧運維水平提升,構建全面運維自治能力!