自动清理 Nexus 中的过期镜像
自动清理过期镜像
Nexus Repository 是一款非常好用的私有仓库,支持 Maven、Docker、Yum、PyPi等等,基本主流的类型都支持。自从使用了持续集成以后,我们会自动对新代码进行编译打包发布到开发环境中,以便尽快地对新代码进行测试。这样导致的问题就是 Nexus 中的 Docker 库占用的空间越来越大。Nexus 本身提供了 Cleanup 功能,可以将一段时间之前的镜像删除,但是如果全部按过期时间去删除,会导致一些长时间以前上传,但是还需要使用的镜像也删除。
幸运的是,Nexus 支持基于 Groovy 的 Script 功能,可以自己编写 Script 来实现不同的功能。这里提供一个小脚本,可以按更新时间清理过期的镜像,同时还可以选择保留最近的几个镜像,不会把历史镜像全部删除。我们目前的 Tag 命名规则,是 {COMMITID}-(prod|test|dev)-{BUILD_NUMBER}
,COMMITID 对应构建镜像时代码库的 COMMITID 取前8位, prod、test、dev则对应不用的部署环境,生产环境、测试环境、开发环境等。如果是 Jenkins 构建的,还会加上 BUILD_NUMBER。清理脚本的逻辑就是把每一个应用的所有镜像按 prod、test、dev 分别按修改时间进行排序,保留最新的 5 个,把其它的都删除。代码如下:
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import org.sonatype.nexus.repository.storage.Component
import org.sonatype.nexus.repository.storage.Query
import org.sonatype.nexus.repository.storage.StorageFacet
def request = new JsonSlurper().parseText(args)
assert request.repoName: 'repoName parameter is required'
// tag filter
if (!request.filter) {
request.filter = ["test-", "dev-", "prod-"]
}
// should delete or not
if (!request.delete) {
request.delete = false
}
def result = [:]
def repoName = request.repoName
def keepSize = 5
def repo = repository.repositoryManager.get(repoName)
def tx = repo.facet(StorageFacet).txSupplier().get()
try {
tx.begin()
def components = tx.findComponents(Query.builder().where('1').eq(1).build(), [repo])
components.each { Component cmp ->
def version = cmp.version()
for (String matchTag in request.filter) {
if (version =~ matchTag) {
result.get(cmp.name(),[:]).get(matchTag,[]).add(cmp)
break
}
}
}
tx.commit()
} catch (Exception e) {
log.warn("Transaction failed {}", e.toString())
tx.rollback()
} finally {
tx.close()
}
def deleted = []
for (groups in result) {
for (components in groups.value) {
List<Component> items = components.value as List<Component>
if (items.size() <= keepSize) {
continue
}
def sorted = items.sort { a, b ->
def a_updated = a.lastUpdated()
def b_updated = b.lastUpdated()
return a_updated.compareTo(b_updated)
}
def toDelete = sorted[0..-(keepSize + 1)]
for (Component c in toDelete) {
deleted.add("${c.name()}:${c.version()}")
if (request.delete) {
def tx_del = repo.facet(StorageFacet).txSupplier().get()
try {
tx_del.begin()
tx_del.deleteComponent(c)
tx_del.commit()
} catch (Exception e) {
log.warn("Delete Component failed {}", e.toString())
tx_del.rollback()
} finally {
tx_del.close()
}
}
}
}
}
return JsonOutput.toJson(deleted)
运行 Script
写好的脚本需要上传到 Nexus 并执行。这里推荐使用 nexus3-cli 来操作 Nexus。下面只列出 Script 相关的操作,nexus3-cli 的详细功能可参考官方文档 https://nexus3-cli.readthedocs.io/en/latest/index.html。
# 安装 nexus3-cli
foo@bar:~$ pip install nexus3-cli
# 登陆到 nexus
foo@bar:~$ nexus3 login --url http://your-nexus-url
# 将上面的脚本保存为 removeOutdateComponents.groovy 然后上传
foo@bar:~$ nexus3 script create remove-outdate-components removeOutdateComponents.groovy
# 查看会被删除的镜像,但并不删除
foo@bar:~$ nexus3 script run remove-outdate-components -a \
'{"repoName":"your-hosted-docker-repoName"}'
# 删除过期镜像并返回
foo@bar:~$ nexus3 script run remove-outdate-components -a \
'{"repoName":"your-hosted-docker-repoName","delete":true}'