天天看點

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

作者:dbaplus社群

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

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

當然,如果你的硬碟空間很大,也不介意把大量空間用來存放不必要的 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
# or
docker 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/bash


force=false


while getopts ":f" opt; do
  case ${opt} in
    f )
      force=true
      ;;
    ? )
      echo "Invalid option: -$OPTARG" 1>&2
      exit 1
      ;;
    : )
      echo "Option -$OPTARG requires an argument." 1>&2
      exit 1
      ;;
  esac
done


shift $((OPTIND -1))


if [ $# -ne 2 ]; then
    echo "Usage: docker-clean [-f] <resource> <keyword>"
    exit 1
fi


resource=$1
keyword=$2


if [ "$force" = true ]; then
  force_args="-f"
else
  read -p "Are you sure you want to delete all $resource with $keyword? [y/N] " confirmation
  if [ "$confirmation" != "y" ] && [ "$confirmation" != "Y" ]; then
    echo "Operation cancelled."
    exit 0
  fi
fi


case $resource in
    "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鏡像,具體原因就是出于容器安全的考慮。

作者丨睡醒想錢錢

來源丨juejin.cn/post/7259032711019855930

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