概述
起因是开发者在生产环境的harbor中操作时,需要删除测试环境的harbor镜像来清理空间,不小心把生产环境的镜像全部删除。
故而需要探究harbor镜像仓库的镜像恢复思路。
分析
本来很绝望,但是看到清理服务中还能模拟清理(不要点成真的清理了)600G的缓存,我就知道数据还有救。
要第一时间点击清理服务,把里面的定时清理关掉。
然后在harbor的数据文件中可以找到
{harbor数据根目录}/registry/docker/registry/v2
repositories存着镜像的索引信息。
blobs存着镜像索引对应的层文件。
在页面中删除镜像后,不触发清理服务,他们的文件是不会删除的,所以页面只是逻辑删除。
指导文件还在,心里石头落地一半,查阅资料后,发现这些文件的恢复方式如下:
在repositories文件夹里,找对想要恢复的镜像的项目文件夹,可以找到一大堆以镜像名命名的文件夹。
找到想要恢复的镜像名对应的文件夹,里面有_manifests/tags
找到对应标签的文件夹找到link文件
tags/latest/current/link
里面写着的id就是这个镜像的层结构,可以在blobs里找到他,读取出来就是一个可读的json文件,里面记录着镜像有多少层,每层的blobs文件是什么。
这样我们就能通过将这些文件还原成tar包,再通过docker load来加载回来了。
恢复脚本
#!/bin/bash
set -e
# 检查参数
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <image_name:tag>"
exit 1
fi
# 设置镜像名和标签参数
IMAGE_NAME_WITH_TAG=$1
IMAGE_NAME=${IMAGE_NAME_WITH_TAG%:*} # 去掉最后的标签
TAG=${IMAGE_NAME_WITH_TAG##*:} # 获取标签
# 配置工作目录和目标路径
SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
HARBOR_DIR="/data/harbor/registry/docker/registry/v2"
REPO_DIR="$HARBOR_DIR/repositories/$IMAGE_NAME"
BLOB_ROOT_DIR="$HARBOR_DIR/blobs"
WORK_DIR="$SCRIPT_DIR/restore_image"
BACKUP_DIR="$SCRIPT_DIR/backup_harbor"
TAR_FILE="$BACKUP_DIR/restored_image.tar"
# 清理临时目录
rm -rf "${WORK_DIR}"
mkdir -p "${WORK_DIR}"
# 获取最新 manifest link
MANIFEST_LINK=$(cat "${REPO_DIR}/_manifests/tags/$TAG/current/link")
MANIFEST_DIGEST=${MANIFEST_LINK#sha256:}
MANIFEST_FILE="${BLOB_ROOT_DIR}/sha256/${MANIFEST_DIGEST:0:2}/${MANIFEST_DIGEST}/data"
# 提取 manifest 内容
MANIFEST_CONTENT=$(cat "${MANIFEST_FILE}")
echo "${MANIFEST_CONTENT}" > "${WORK_DIR}/manifest.json"
# 提取 config blob
CONFIG_DIGEST=$(echo "${MANIFEST_CONTENT}" | jq -r '.config.digest')
CONFIG_HASH=${CONFIG_DIGEST#sha256:}
CONFIG_FILE="${BLOB_ROOT_DIR}/sha256/${CONFIG_HASH:0:2}/${CONFIG_HASH}/data"
if [ ! -f "${CONFIG_FILE}" ]; then
echo "Error: Cannot find config file: ${CONFIG_FILE}"
exit 1
fi
cp "${CONFIG_FILE}" "${WORK_DIR}/config.json"
# 提取 layers blobs
LAYER_IDX=1
LAYERS=()
for LAYER_DIGEST in $(echo "${MANIFEST_CONTENT}" | jq -r '.layers[].digest'); do
LAYER_HASH=${LAYER_DIGEST#sha256:}
LAYER_FILE="${BLOB_ROOT_DIR}/sha256/${LAYER_HASH:0:2}/${LAYER_HASH}/data"
if [ ! -f "${LAYER_FILE}" ]; then
echo "Error: Cannot find layer file: ${LAYER_FILE}"
exit 1
fi
LAYER_NAME="layer${LAYER_IDX}.tar"
cp "${LAYER_FILE}" "${WORK_DIR}/${LAYER_NAME}"
LAYERS+=("${LAYER_NAME}")
((LAYER_IDX++))
done
# 构造 manifest.json
LAYERS_JSON=$(printf '"%s", ' "${LAYERS[@]}" | sed 's/, $//')
cat <<EOF > "${WORK_DIR}/manifest.json"
[
{
"Config": "config.json",
"RepoTags": ["nm01.registry.meta-blue.com/$IMAGE_NAME:$TAG"],
"Layers": [
${LAYERS_JSON}
]
}
]
EOF
# 打包成 tar 文件
mkdir -p "${BACKUP_DIR}"
tar -cvf "${TAR_FILE}" -C "${WORK_DIR}" .
echo "镜像 tar包已打包完成,路径位置:${TAR_FILE},现在进行 docker load 导入"
# Docker导入镜像
LOADED_IMAGE_ID=$(docker load -i "${TAR_FILE}" | awk '/Loaded image/{print $3}')
docker tag "${LOADED_IMAGE_ID}" "nm01.registry.meta-blue.com/$IMAGE_NAME:$TAG"
docker push "nm01.registry.meta-blue.com/$IMAGE_NAME:$TAG"
# 删除本地镜像
docker rmi "${LOADED_IMAGE_ID}"
# 清理临时文件
rm -rf "${WORK_DIR}"
rm -rf "${BACKUP_DIR}"
echo "镜像成功导入并推送到Harbor,并已清理缓存和本地镜像:${TAR_FILE}"