Harbor镜像存储异常占用清理记录
Harbor镜像存储异常占用清理记录
学院的AI Station算力服务器分配给全体教师和学生使用,大约有140+用户,仅有的2TB空间难以支撑这么多人的镜像存储需求。服务器基本上每隔一段时间就会因为系统盘爆满而宕机。
除去资源配比问题外,主要原因包括:
- 资源使用粗放。启动容器时,系统存储在本地的2TB nvme上,数据挂载盘在管理节点的115TB机械硬盘上,巨大的差速迫使用户将模型权重放在非数据目录下;另一方面,部分用户存在习惯问题,把数据、日志等也打包到镜像中,导致镜像越来越大;
- 镜像构建方式不当。习惯于从别人的已有镜像出发,如果缺少自己要的包,就打包,然后重新构建镜像。这种分层打包的方式让大家的镜像越来越大,服务器运行之初每个人的镜像一般在12G左右,现在运行3年,镜像普遍到了40G,部分账户下生产出了60-100G的大镜像
- K8S版本过旧,且配置存在不当,这是每次大清理都会发现的主要问题
粗糙管理
一直以来的做法都是搞一个统计面板,看看哪个老师的学生使用的镜像太大或者太多了,去跟对方沟通一下,删掉太大的镜像。 惯用的做法是告诉每个老师,让每个学生的账号下最多不超过2个镜像,且每个镜像不超过30G,如果有超过30G的镜像,则只允许保留1个。
Harbor垃圾回收
手动清理悬空镜像
通过AI Station前端平台进行镜像删除后,Harbor中存在一些未被清理掉的悬空docker,需要在管理节点上手动执行清理命令。
docker system prune
必要时增加-a -f参数保证清理干净。
手动调用Harbor的垃圾回收
这是前期主要问题,即使删除了镜像,并调用了docker system prune -a -f,在df -h查看时仍然发现overlay2目录的存储占用极高,核心原因是k8s旧版本的garbage-collect没有自动执行(即使在网页端配置自动执行也无效),必须手动执行一次。
两种路径:1. 通过Harbor网页端手动执行垃圾清理;2. 通过docker exec进入到harbor的管理shell,手动执行garbage collect的指令。
Harbor上传缓存
这个问题是今天新发现的。
解决过程
早期设置120镜像,基本都能正常运转,现在降到60镜像,镜像尺寸也没有大太多,磁盘占用达到99%,基本可以确定是镜像存储中有一些并非镜像本身的冗余。
空间分析
docker system df显示
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 47 47 9.129GB 1.233GB (13%)
Containers 81 79 335.4MB 37.26MB (11%)
Local Volumes 576 11 1.357kB 1.309kB (96%)
Build Cache 0 0 0B 0B
从K8S本身看不出来有多少镜像占用。
但是df -h显示磁盘空间被overlay占满:
Filesystem Size Used Avail Use% Mounted on
# ......
overlay 1.9T 1.8T 21G 99% /var/lib/data/docker/overlay2/e4846fe7cfebc268d113044ce0cac1f3d2d0a42a6ab2db90eb1e57d8bbeea28f/merged
overlay 1.9T 1.8T 21G 99% /var/lib/data/docker/overlay2/5d0963303d7c9b6845dc5d93469442146c02a6fee99cd797ec03cbe56ed89fed/merged
overlay 1.9T 1.8T 21G 99% /var/lib/data/docker/overlay2/cbb3836190072aa0951cbf511e30b542502e75e542c61edbaaf4462c767ff6d9/merged
对具体的目录进行分析,执行du -xh --max-depth=1 /var/lib/data | sort -h得到如下信息,和预期一样,主要问题在harbor存储下。
452K /var/lib/data/ldap
525M /var/lib/data/etcd
1.3G /var/lib/data/etcd_backup
2.1G /var/lib/data/influxdb_backup
11G /var/lib/data/docker
14G /var/lib/data/mariadb_backup
17G /var/lib/data/mysql
26G /var/lib/data/log
221G /var/lib/data/influxdb
1.6T /var/lib/data/harbor
1.8T /var/lib/data
继续深入查看du -xh --max-depth=2 /var/lib/data/harbor/registry/dockerregistry/v2/repositories | sort -h | tail -50找到最大的几个仓库
# ......
19G /var/lib/data/harbor/registry/docker/registry/v2/repositories/pytorch/hongtauo_cv-t13
28G /var/lib/data/harbor/registry/docker/registry/v2/repositories/pytorch/pytorch-t7-t7-t7
29G /var/lib/data/harbor/registry/docker/registry/v2/repositories/pytorch/pytorch_deepspeed_torch_llama_factory-12.10-h1
63G /var/lib/data/harbor/registry/docker/registry/v2/repositories/pytorch/pytorch
68G /var/lib/data/harbor/registry/docker/registry/v2/repositories/pytorch/zrh_docreasoning-stuzrh
99G /var/lib/data/harbor/registry/docker/registry/v2/repositories/other/lerobot-t13-t13
103G /var/lib/data/harbor/registry/docker/registry/v2/repositories/other/zrh_doc-stuzrh
267G /var/lib/data/harbor/registry/docker/registry/v2/repositories/other
296G /var/lib/data/harbor/registry/docker/registry/v2/repositories/pytorch
563G /var/lib/data/harbor/registry/docker/registry/v2/repositories
整个repositories目录占了563G空间,其中pytorch和other两个项目底下占用最多,这都符合预期。但从倒数8个目录之前出现了存储占用的显著不同,60G+还属于正常,但陡增到100+的存储显然不对。
然而,harbor的真实layer数据存储在/var/lib/data/harbor/registry/docker/v2/blobs下,这里的repositories存储的只是一些manifest、layer digest之间的link、上传过程中留下的数据等(此信息由ChatGPT提供)。显然manifest和link不可能有上百G的存储,那问题大概率出在上传过程。
另一方面,通过对比实际看到的镜像列表,并与对应镜像的使用人实际沟通,这个103G的镜像并没有创建成功,这些信息也能确认该目录下存在的是某些缓存。
对比blobs和repositories也能明显发现比例问题:
du -sh /var/lib/data/harbor/registry/docker/registry/v2/blobs /var/lib/data/harbor/registry/docker/registry/v2/repositories
987G /var/lib/data/harbor/registry/docker/registry/v2/blobs
563G /var/lib/data/harbor/registry/docker/registry/v2/repositories
具体对最大的镜像目录进行大小分析,进一步确定是_uploads目录占用,应该是上传过程中服务器崩溃,留存下来的缓存文件。
du -xh --max-depth=3 /var/lib/data/harbor/registry/docker/registry/v2/repositories/other/zrh_doc-stuzrh | sort -h | tail -30
# 大量4.0K的目录
4.0K /var/lib/data/harbor/registry/docker/registry/v2/repositories/other/zrh_doc-stuzrh/_uploads/6d0af006-f4dd-4792-9519-75dca2459ea1/hashstates
4.0K /var/lib/data/harbor/registry/docker/registry/v2/repositories/other/zrh_doc-stuzrh/_uploads/b8c08c77-c085-4769-99f3-9936626c8880/hashstates
88K /var/lib/data/harbor/registry/docker/registry/v2/repositories/other/zrh_doc-stuzrh/_layers
88K /var/lib/data/harbor/registry/docker/registry/v2/repositories/other/zrh_doc-stuzrh/_layers/sha256
131M /var/lib/data/harbor/registry/docker/registry/v2/repositories/other/zrh_doc-stuzrh/_uploads/6d0af006-f4dd-4792-9519-75dca2459ea1
103G /var/lib/data/harbor/registry/docker/registry/v2/repositories/other/zrh_doc-stuzrh
103G /var/lib/data/harbor/registry/docker/registry/v2/repositories/other/zrh_doc-stuzrh/_uploads
103G /var/lib/data/harbor/registry/docker/registry/v2/repositories/other/zrh_doc-stuzrh/_uploads/b8c08c77-c085-4769-99f3-9936626c8880
接下来就要确定这些有没有在日常运行中被正常清理掉,先docker ps | grep registry找到registry容器(本机上为registry),再docker exec registry cat /etc/registry/config.yml,查看其中关于存储的字段,发现uploadpurging字段被disable掉了(即一开始说的厂商配置不当):
maintenance:
uploadpurging:
enabled: false
正常情况下它应该是类似如下的配置比较合理。
storage:
maintenance:
uploadpurging:
enabled: true
age: 168h
interval: 24h
dryrun: false
接下来就是分析占用量了。
find /var/lib/data/harbor/registry/docker/registry/v2/repositories \
-type d -name _uploads \
-exec find {} -mindepth 1 -maxdepth 1 -type d -mtime +7 -exec du -sh {} \; \; \
2>/dev/null | sort -h
至此,问题分析完毕,可清理的空间高达560G。在多次上传中缓存的文件没有得到及时处理。 这个问题在网络上很少能找到类似情况的,看到的大多数清理都是处理日志占用之类的问题,关于上传缓存的清理没见过。
处置
这个处置就相对容易了,先通过Harbor UI将仓库设置成只读,然后直接脚本清理:
find /var/lib/data/harbor/registry/docker/registry/v2/repositories \
-type d -name _uploads \
-print0 |
while IFS= read -r -d '' dir; do
find "$dir" \
-mindepth 1 \
-maxdepth 1 \
-type d \
-mtime +7 \
-exec rm -rf {} +
done
然后设置一个脚本,每天晚上清理一次。
#!/bin/bash
BASE="/var/lib/data/harbor/registry/docker/registry/v2/repositories"
DAYS=7
LOG="/var/log/harbor-clean-uploads.log"
echo "[$(date '+%F %T')] start" >> "$LOG"
find "$BASE" -type d -name _uploads |
while IFS= read -r dir; do
[ -z "$dir" ] && continue
find "$dir" \
-mindepth 1 \
-maxdepth 1 \
-type d \
-mtime +"$DAYS" |
while IFS= read -r upload; do
[ -z "$upload" ] && continue
echo "[$(date '+%F %T')] removing: $upload" >> "$LOG"
# echo "would remove: $upload" # dry run,测试时可以先运行这一句代替下面的rm,确定避免误删
rm -rf -- "$upload"
done
done
df -h /var/lib/data >> "$LOG"
echo "[$(date '+%F %T')] done" >> "$LOG"
用crontab -e配置计划任务
0 3 * * * /usr/local/sbin/harbor-clean-uploads.sh
查看运行日志就tail -100 /var/log/harbor-clean-uploads.log。
关于harbor config
其实可以不自己写脚本,通过docker inspect registry --format '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}'找到registry的映射目录,找到config.yml文件,然后在里面修改maintance字段进行修改,再docker restart registry重启。
但这个可能是厂商的Harbor安装配置自动生成的,这个奇怪的系统有什么暗坑也不知道,为了避免麻烦,这里没有选择改配置。