docker清理私有镜像仓registry,python脚本实现
查询和登录私有镜像仓容器
docker ps | grep registry
docker exec -it <container_id> /bin/sh
registry 配置
# 进入容器
docker exec -it d2cfdce99aeb /bin/sh
# 修改registry配置文件
# vi /etc/docker/registry/config.yml
version: 0.1
log:
fields:
service: registry
storage:
delete:
enabled: true
cache:
blobdescriptor: inmemory
filesystem:
rootdirectory: /var/lib/registry
maintenance:
uploadpurging:
enabled: true
age: 168h
interval: 24h
dryrun: false
readonly:
enabled: false
http:
addr: :5000
headers:
X-Content-Type-Options: [nosniff]
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3
registry API
查询镜像列表
curl --location --request GET 'http://192.168.205.129:5000/v2/_catalog' -ik
获取响应中的reposities数组
查询指定repositry的tag
curl --location --request GET 'http://192.168.205.129:5000/v2/redis/tags/list' -ik
URL中的redis是上一步中查询处理的,需要替换。如果当前repository没有tag,则为null
获取dgist签名
curl --location --request GET 'http://192.168.205.129:5000/v2/redis/manifests/latest' \
--header 'Accept: application/vnd.docker.distribution.manifest.v2+json' -ik
获取返回头里面的Docker-Content-Digest值
删除镜像
根据上一步查询处理的digest进行删除
curl --location --request DELETE 'http://192.168.205.129:5000/v2/redis/manifests/sha256:b11fca7b77c198f14a027bfca1981d95c9704fbf97fbf33908cb179c7da3588f' -ik
registry 垃圾回收
# 垃圾回收(容器内执行命令)
docker exec -it <私有库的容器ID或者容器名> sh -c ' registry garbage-collect /etc/docker/registry/config.yml'
或
registry garbage-collect /etc/docker/registry/config.yml
重启registry后,再次执行垃圾回收,发现已经回收完成
python脚本实现
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Usage:执行前修改全局变量,如果某些容器不需要处理
"""
from urllib import request
import json
import logging
import os
# 环境变量配置,执行前请修改此处的内容
REGISTRY_HOST = "192.168.205.129"
REGISTRY_PORT = "5000"
VM_USER = "root"
REGISTRY_URL = f"http://{REGISTRY_HOST}:{REGISTRY_PORT}"
# 镜像白名单,对于某些不需要处理的镜像,可以将其repository写在列表中过滤掉
whiteList = []
logger = logging.getLogger(__name__)
# 设置日志级别
logging.basicConfig(level="INFO")
# 第二步:创建控制台日志处理器+文件日志处理器
console_handler = logging.StreamHandler()
file_handler = logging.FileHandler("./registry-clean-image-running.log", mode="a", encoding="utf-8")
# 第三步:设置控制台日志的输出级别,需要日志器也设置日志级别为info;----根据两个地方的等级进行对比,取日志器的级别
console_handler.setLevel(level="WARNING")
# 第四步:设置控制台日志和文件日志的输出格式
console_fmt = "%(name)s--->%(levelname)s--->%(asctime)s--->%(message)s--->%(lineno)d"
file_fmt = "%(lineno)d--->%(name)s--->%(levelname)s--->%(asctime)s--->%(message)s"
fmt1 = logging.Formatter(fmt=console_fmt)
fmt2 = logging.Formatter(fmt=file_fmt)
console_handler.setFormatter(fmt=fmt1)
file_handler.setFormatter(fmt=fmt2)
# 第五步:将控制台日志器、文件日志器,添加进日志器对象中
logger.addHandler(console_handler)
logger.addHandler(file_handler)
class RegistryError(Exception):
pass
class RegistryAPI:
def __init__(self, registry_url):
self.registry_url = registry_url
def getRepositories(self):
"""获取镜像仓中的所有镜像目录"""
response = request.urlopen(f'{self.registry_url}/v2/_catalog')
code = response.status
content = response.read().decode('utf-8')
if code == 200:
logger.info(code)
logger.info(content)
return json.loads(content)['repositories']
else:
raise RegistryError(content)
def getImageTags(self, repository):
"""获取镜像的标签tag"""
response = request.urlopen(f'{self.registry_url}/v2/{repository}/tags/list')
code = response.status
content = response.read().decode('utf-8')
if code == 200:
logger.info(code)
logger.info(content)
return json.loads(content)['tags']
else:
raise RegistryError(content)
def getImageDigest(self, repository, tag):
"""获取指定tag镜像的digest"""
headers = {"Accept": "application/vnd.docker.distribution.manifest.v2+json"}
req = request.Request(f'{self.registry_url}/v2/{repository}/manifests/{tag}', headers=headers)
response = request.urlopen(req)
code = response.status
responseHeaders = dict(response.getheaders())
content = response.read().decode('utf-8')
if code == 200:
logger.info(code)
logger.info(responseHeaders)
logger.info(content)
return responseHeaders['Docker-Content-Digest']
else:
raise RegistryError(content)
def deleteImageDigest(self, repository, digest):
"""删除tag镜像文件(不是彻底删除,只是把关联的文件释放了,还需要在容器中执行回收程序才会清理磁盘)"""
req = request.Request(f'{self.registry_url}/v2/{repository}/manifests/{digest}', method='DELETE')
response = request.urlopen(req)
code = response.status
content = response.read().decode('utf-8')
if code == 202:
logger.info(code)
logger.info(content)
logger.warning(f"delete success,repository:{repository},digest:{digest}")
else:
raise RegistryError(content)
class RegistryClean:
def __init__(self, whiteList=[]):
self.registryAPI = RegistryAPI(REGISTRY_URL)
self.whiteList = whiteList # 配置过滤的白名单,跳过清理白名单的镜像
def deleteAllImages(self):
"""执行清理镜像的操作"""
repositories = self.registryAPI.getRepositories()
for repository in repositories:
logger.info(f"current repository is: {repository}")
if repository in self.whiteList:
logger.info(f"{repository} in white list")
continue
repositoryTags = self.registryAPI.getImageTags(repository)
if repositoryTags is not None:
for tag in repositoryTags:
logger.info(f"current tag is: {tag}")
digest = self.registryAPI.getImageDigest(repository, tag)
logger.info(f"current digest is: {digest}")
if digest is not None:
self.registryAPI.deleteImageDigest(repository, digest)
else:
pass
"""
注意,后续3个可执行命令,根据实际生产环境的情况需要适配一下。如果直接在部署registry的机器上执行,则可以去掉ssh的步骤。
"""
def getRegistryId(self):
"""获取registry容器ID"""
cmd = "docker ps | grep registry | awk '{print $1}'"
r = os.popen(f'ssh -o "StrictHostKeyChecking no" {VM_USER}@{REGISTRY_HOST} "{cmd}"')
data = r.readlines()
print(data)
return data
def garbageCollect(self):
"""registry 垃圾回收"""
containerId = self.getRegistryId()
cmd = f"docker exec -it {containerId} sh -c ' registry garbage-collect /etc/docker/registry/config.yml'"
r = os.popen(f'ssh -o "StrictHostKeyChecking no" {VM_USER}@{REGISTRY_HOST} "{cmd}"')
data = r.readlines()
print(data)
def restartRegistry(self):
"""重启registry容器"""
containerId = self.getRegistryId()
cmd = f"docker restart {containerId}"
r = os.popen(f'ssh -o "StrictHostKeyChecking no" {VM_USER}@{REGISTRY_HOST} "{cmd}"')
data = r.readlines()
print(data)
def main():
"""整体的流程:清理镜像文件,执行垃圾回收,重启镜像容器"""
registryClean = RegistryClean(whiteList=whiteList)
registryClean.deleteAllImages()
registryClean.getRegistryId()
registryClean.garbageCollect()
registryClean.restartRegistry()
if __name__ == "__main__":
main()
参考链接
感谢大佬们的帖子,参考链接如下:
docker配置详解
Docker彻底删除私有库镜像