有時候,使用docker images
指令我們可以發現大量的無REPOSITORY、TAG的docker鏡像,這些鏡像究竟是什么?
它們沒有REPOSITORY、TAG名稱,沒有辦法引用,那么它們還有什么用?
[root@cdh-100 data]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
app_worker cuda a7ce8fa8e585 4 weeks ago 7.39GB
<none> <none> 2a2ad8902f37 4 weeks ago 7.39GB
<none> <none> 53d165ed9913 5 weeks ago 7.39GB
<none> <none> a79c59ed7311 6 weeks ago 7.34GB
一、 什么是懸空鏡像?
我們所看到的這些沒有REPOSITORY
和TAG
的Docker鏡像,其實是懸空鏡像(dangling images)。當新的鏡像被構建出來,并且和舊鏡像具有相同的標簽時,舊鏡像就會變成懸空狀態。簡單來說,它們屬于沒有被標記的舊版本鏡像。
一般而言,這類鏡像不會對系統造成危害,不過它們會占用大量的磁盤空間。所以,刪除它們是可行的,而且這也是清理Docker環境的常見操作。
批量刪除懸空鏡像的方法
要刪除這些懸空鏡像,可以借助Docker提供的特定過濾功能來實現批量刪除:
docker rmi $(docker images -f "dangling=true" -q)
上述命令的具體作用如下:
docker images -f "dangling=true" -q
:用于篩選出所有懸空鏡像,并只返回它們的ID。docker rmi
:負責刪除鏡像,這里會刪除前面命令所列出的全部懸空鏡像。
執行此命令時需要留意,它會刪除所有的懸空鏡像。所以在執行之前,要確保我們確實不需要這些鏡像了。
更安全的操作方式
如果想先查看哪些鏡像會被刪除,然后再確認是否刪除,可以分兩步進行操作:
# 查看所有懸空鏡像
docker images -f "dangling=true"# 確認無誤后再刪除
docker rmi $(docker images -f "dangling=true" -q)
要是希望在刪除鏡像之前查看它們占用的空間大小,可以使用以下命令:
docker system df
該命令會顯示Docker各部分占用的磁盤空間,包括懸空鏡像。
自動清理設置
為了避免懸空鏡像不斷積累,在構建新鏡像時,可以添加--no-cache
選項。另外,還可以設置Docker定期自動清理:
docker system prune -a --volumes
這個命令會刪除所有未使用的鏡像、容器、網絡和卷。不過要謹慎使用,因為它可能會刪除我們需要的內容。可以通過添加--filter
選項來更精確地控制刪除范圍。
清理完成后,再次運行docker images
命令,會發現懸空鏡像已經被成功刪除了。
二、 Docker刪除鏡像前,如何找到所有關聯的已停止容器?
在刪除Docker鏡像之前,找到所有關聯的已停止容器是很重要的,因為這些容器可能依賴于想要刪除的鏡像。以下是幾種方法可以幫助我們識別這些關聯的容器:
方法1:通過鏡像ID查找關聯容器
如果知道要刪除的鏡像ID(例如 a7ce8fa8e585
),可以使用以下命令查找所有使用該鏡像的容器(包括已停止的容器):
docker ps -a --filter "ancestor=a7ce8fa8e585" --format "{{.ID}} {{.Image}} {{.Status}} {{.Names}}"
- 參數說明:
docker ps -a
:顯示所有容器(包括已停止的)。--filter "ancestor=鏡像ID"
:篩選出基于指定鏡像創建的容器。--format
:自定義輸出格式,顯示容器ID、鏡像、狀態和名稱。
方法2:查找所有已停止的容器并檢查鏡像
如果想先列出所有已停止的容器,再手動檢查它們使用的鏡像,可以使用:
docker ps -a --filter "status=exited" --format "{{.ID}} {{.Image}} {{.Status}} {{.Names}}"
- 然后,從輸出中找到與要刪除的鏡像ID或名稱匹配的容器。
方法3:批量停止/刪除關聯容器
如果確定要刪除所有關聯的已停止容器,可以結合上述命令批量操作:
# 查找并停止所有使用指定鏡像的容器(如果仍在運行)
docker stop $(docker ps -q --filter "ancestor=a7ce8fa8e585")# 刪除所有使用指定鏡像的已停止容器
docker rm $(docker ps -aq --filter "ancestor=a7ce8fa8e585" --filter "status=exited")
- 注意:執行前請確認這些容器不再需要,避免數據丟失。
方法4:使用腳本自動化檢查
如果需要頻繁清理鏡像,可以編寫一個簡單的腳本,先檢查鏡像關聯的容器,再決定是否刪除:
#!/bin/bash# 檢查鏡像是否有關聯容器
check_image_containers() {local IMAGE=$1local CONTAINERS=$(docker ps -aq --filter "ancestor=$IMAGE")if [ -n "$CONTAINERS" ]; thenecho "警告:鏡像 $IMAGE 有關聯的容器:"docker ps -a --filter "ancestor=$IMAGE" --format "{{.ID}} {{.Image}} {{.Status}} {{.Names}}"read -p "是否刪除這些容器?(y/n) " -n 1 -rechoif [[ $REPLY =~ ^[Yy]$ ]]; thendocker rm -f $CONTAINERSecho "已刪除關聯容器。"elseecho "取消操作。"exit 1fielseecho "鏡像 $IMAGE 沒有關聯容器,可以安全刪除。"fi
}# 使用示例:檢查鏡像并刪除
IMAGE_ID="a7ce8fa8e585"
check_image_containers $IMAGE_ID && docker rmi $IMAGE_ID
總結步驟
- 查找關聯容器:使用
docker ps -a --filter "ancestor=鏡像ID"
。 - 停止并刪除容器(如果需要):
docker stop
和docker rm
。 - 刪除鏡像:確認無關聯容器后,執行
docker rmi 鏡像ID
。
通過這些方法,可以安全地清理不再需要的鏡像和容器,避免因依賴關系導致的刪除失敗。
三、對應的shell 腳本 和Python腳本
下面是兩種實現方式的腳本,它們都能夠在刪除大量Docker鏡像之前,找出所有與之關聯的已停止容器,并且會對容器和鏡像進行相應的處理。
Shell腳本實現
#!/bin/bash# 檢查是否有root權限
if [ "$(id -u)" -ne 0 ]; thenecho "請使用root權限運行此腳本"exit 1
fi# 獲取所有鏡像ID
image_ids=$(docker images -q)# 確認是否繼續
echo "即將處理以下鏡像:"
docker images
read -p "確定要繼續嗎?(y/n): " confirm
if [ "$confirm" != "y" ]; thenecho "操作已取消"exit 0
fi# 處理每個鏡像
for image_id in $image_ids; doecho -e "\n處理鏡像: $image_id"# 查找關聯的已停止容器stopped_containers=$(docker ps -aq --filter "ancestor=$image_id" --filter "status=exited")if [ -n "$stopped_containers" ]; thenecho "找到關聯的已停止容器:"docker ps -a --filter "ancestor=$image_id" --filter "status=exited"# 確認是否刪除容器read -p "是否刪除這些容器?(y/n): " delete_containersif [ "$delete_containers" == "y" ]; thendocker rm $stopped_containersecho "已刪除關聯的已停止容器"elseecho "跳過刪除容器"fielseecho "沒有找到關聯的已停止容器"fi# 確認是否刪除鏡像read -p "是否刪除此鏡像?(y/n): " delete_imageif [ "$delete_image" == "y" ]; then# 嘗試刪除鏡像(可能會失敗,因為有運行中的容器)if docker rmi $image_id 2>/dev/null; thenecho "已刪除鏡像: $image_id"elseecho "無法刪除鏡像: $image_id (可能有運行中的容器依賴它)"fielseecho "跳過刪除鏡像"fi
doneecho -e "\n鏡像處理完成"
Python腳本實現
import subprocess
import sysdef run_command(command):"""執行shell命令并返回輸出"""try:result = subprocess.run(command, shell=True, capture_output=True, text=True, check=True)return result.stdout.strip()except subprocess.CalledProcessError as e:print(f"命令執行失敗: {e.stderr.strip()}")return Nonedef get_all_images():"""獲取所有Docker鏡像ID"""output = run_command("docker images -q")if not output:print("沒有找到Docker鏡像")return []return output.split()def get_stopped_containers(image_id):"""獲取與鏡像關聯的已停止容器"""output = run_command(f'docker ps -aq --filter "ancestor={image_id}" --filter "status=exited"')if not output:return []return output.split()def main():# 檢查root權限if sys.platform != 'win32':import osif os.geteuid() != 0:print("請使用root權限運行此腳本")return# 獲取所有鏡像images = get_all_images()if not images:return# 確認是否繼續print("即將處理以下鏡像:")subprocess.run("docker images", shell=True)confirm = input("確定要繼續嗎?(y/n): ").strip().lower()if confirm != 'y':print("操作已取消")return# 處理每個鏡像for image_id in images:print(f"\n處理鏡像: {image_id}")# 獲取關聯的已停止容器stopped_containers = get_stopped_containers(image_id)if stopped_containers:print("找到關聯的已停止容器:")subprocess.run(f'docker ps -a --filter "ancestor={image_id}" --filter "status=exited"', shell=True)# 確認是否刪除容器delete_containers = input("是否刪除這些容器?(y/n): ").strip().lower()if delete_containers == 'y':container_ids = ' '.join(stopped_containers)if run_command(f"docker rm {container_ids}") is not None:print("已刪除關聯的已停止容器")else:print("刪除容器失敗")else:print("跳過刪除容器")else:print("沒有找到關聯的已停止容器")# 確認是否刪除鏡像delete_image = input("是否刪除此鏡像?(y/n): ").strip().lower()if delete_image == 'y':# 嘗試刪除鏡像if run_command(f"docker rmi {image_id}") is not None:print(f"已刪除鏡像: {image_id}")else:print(f"無法刪除鏡像: {image_id} (可能有運行中的容器依賴它)")else:print("跳過刪除鏡像")print("\n鏡像處理完成")if __name__ == "__main__":main()
使用說明
這兩個腳本都提供了以下功能:
- 先獲取系統中所有的Docker鏡像。
- 針對每個鏡像,查找與之關聯的已停止容器。
- 展示找到的容器信息,并詢問是否要刪除這些容器。
- 詢問是否要刪除當前處理的鏡像。
- 執行相應的刪除操作,并顯示操作結果。
注意事項
- 運行腳本需要有root權限或者適當的Docker權限。
- 腳本在刪除鏡像之前,會先處理已停止的容器,但不會處理正在運行的容器。如果有運行中的容器依賴某個鏡像,那么刪除該鏡像將會失敗。
- 腳本會顯示詳細的操作提示,在進行刪除操作前會多次確認,避免誤刪。
- 對于生產環境,建議先備份重要數據,再運行腳本。
四、刪除懸空鏡像后磁盤占用為什么變化不大?
在使用docker rmi $(docker images -f "dangling=true" -q)
命令刪除懸空鏡像后磁盤空間未減少,可能由以下原因導致,以下是詳細分析及解決方案:
一、原因分析
1. 鏡像層被其他鏡像共享
- Docker采用分層存儲機制,鏡像由多個只讀層(layer)組成。當刪除一個懸空鏡像時,若其底層被其他鏡像共享,則這些層不會被立即刪除,因此磁盤空間不會減少。
- 示例:鏡像A和鏡像B共享底層
layer1
,若刪除鏡像A,layer1
仍被鏡像B使用,不會釋放空間。
2. 未清理其他Docker資源
- 懸空鏡像只是Docker占用空間的一部分,其他資源如:
- 已停止的容器:即使容器停止,其文件系統仍可能占用空間。
- 未使用的卷(Volumes):容器掛載的卷可能存儲大量數據。
- 日志文件:容器運行時產生的日志未被清理。
- 網絡和緩存:未使用的網絡配置或構建緩存。
3. 懸空鏡像未完全刪除
- 可能存在以下情況:
- 命令執行時遺漏了部分懸空鏡像(如鏡像正在被其他進程引用)。
- 新的懸空鏡像在刪除后迅速重建(如自動構建任務)。
4. 未使用Docker系統清理命令
docker rmi
僅刪除鏡像本身,不會清理:- 未被引用的鏡像層(需通過
docker system prune
處理)。 - 其他未使用的資源(如卷、網絡)。
- 未被引用的鏡像層(需通過
5. 文件系統緩存或顯示延遲
- 磁盤空間顯示可能因文件系統緩存未刷新而未更新,需通過系統命令強制刷新。
二、解決方案
1. 檢查懸空鏡像是否完全刪除
# 再次查看懸空鏡像,確認是否有殘留
docker images -f dangling=true
若仍有輸出,嘗試使用-f
參數強制刪除:
docker rmi -f $(docker images -f dangling=true -q)
2. 清理所有未使用的Docker資源
# 清理懸空鏡像、已停止容器、未使用網絡
docker system prune -a# 可選:同時清理未使用的卷(謹慎操作,可能刪除有用數據)
docker system prune -a --volumes
3. 手動清理共享鏡像層
# 查看所有鏡像層的使用情況(需安裝dive工具)
dive list-layers# 或通過存儲驅動查看層引用關系(以Overlay2為例)
ls -la /var/lib/docker/overlay2/layers/
若發現大量共享層,可通過刪除依賴這些層的鏡像釋放空間(需謹慎操作)。
4. 清理容器日志
# 查看容器日志大小
du -sh /var/lib/docker/containers/*/*.log# 清空日志(不刪除文件,保留權限)
find /var/lib/docker/containers/ -name "*.log" -exec truncate -s 0 {} \;
5. 清理未使用的卷
# 查看所有卷及其大小
docker volume ls
docker system df# 清理未使用的卷
docker volume prune
6. 刷新磁盤空間顯示
# 重啟Docker服務
systemctl restart docker# 或使用系統命令強制刷新磁盤統計
sync && echo 3 > /proc/sys/vm/drop_caches
三、預防措施
-
定期自動化清理
創建定時任務,每周執行系統清理:# crontab -e 0 0 * * 0 docker system prune -a --volumes --force
-
鏡像構建時正確打標簽
避免生成懸空鏡像:docker build -t myimage:tag .
-
使用Docker Compose管理資源
通過compose down --remove-orphans
自動清理孤兒資源。
四、關鍵命令總結
操作目的 | 命令 |
---|---|
清理所有未使用資源 | docker system prune -a --volumes |
僅清理懸空鏡像 | docker image prune -a |
清理未使用的卷 | docker volume prune |
查看磁盤使用統計 | docker system df |
強制刪除所有鏡像(危險) | docker rmi $(docker images -q) -f |
通過以上步驟,可全面釋放Docker占用的磁盤空間。若問題仍存在,建議檢查宿主機其他服務(如容器運行時、Kubernetes)是否占用了額外存儲。