2.1 核心功能

作为云原生制品仓库服务,Harbor的核心功能是存储和管理Artifact。Harbor允许用户用命令行工具对容器镜像及其他Artifact进行推送和拉取,并提供了图形管理界面帮助用户查阅和删除这些Artifact。在Harbor 2.0版本中,除容器镜像外,Harbor对符合OCI规范的Helm Chart、CNAB、OPA Bundle等都提供了更多的支持。另外,Harbor为管理员提供了丰富的管理功能,特别是作为开源软件,随着版本的迭代,很多社区用户的反馈和贡献被吸收进来以便更好地适应企业应用场景。本节将对Harbor的主要管理功能做简要介绍。

2.1.1 访问控制

访问控制是多个用户使用同一个仓库存储Artifact时的基本需求,也是Harbor早期版本提供的主要功能之一。Harbor 提供了“项目”(project)的概念,每个项目都对应一个和项目名相同的命名空间(namespace)来保存Artifact,各个命名空间都是彼此独立的授权单元,将 Artifact 隔离开来。当使用 Docker 等命令行工具向Harbor推送和拉取镜像等Artifact时,这个命名空间也是URI的一个组成部分。用户要对项目中的Artifact进行读写,就首先要被管理员添加为项目的成员,具体的权限由成员的角色决定。加入项目的成员可以有以下角色。

◎ 项目管理员(project admin):管理项目成员,删除项目,管理项目级的策略,读写、删除Artifact及项目中的其他资源。

◎ 项目维护人员(master):管理项目级的策略,读写、删除Artifact及项目中的其他资源。注意:在Harbor 2.0的后续版本中,该角色的英文名将改为maintainer,中文翻译不变。

◎ 开发者(developer):读写Artifact及项目中的其他资源。

◎ 访客(guest):对Artifact及项目中的其他资源有读权限。

◎ 受限访客(limited guest):仅用于拉取Artifact,对项目中的其他资源如操作日志(log)没有读权限。

以如下命令为例:

img

如果用户user1需要推送以上golang 镜像(Tag为1.14)到Harbor仓库,则需要由管理员在管理控制台上将其加为development项目的成员,并赋予开发者及以上的角色。这种管理思路也适用于其他OCI Artifact,如当用户使用Helm推送Helm Chart时,也要求用户在项目下有相应的权限。

“项目”是Harbor里一个重要的概念,既被当作命名空间对资源进行隔离,也作为管理单元,管理员可以在它上面创建和添加批量删除、安全控制等策略来管理项目中的Artifact。一般来说,由Harbor的系统管理员创建项目,并根据实际情况将普通用户作为成员添加到不同的项目中。普通用户在使用Harbor时,都根据自己的权限在被授权的项目中进行各种操作。

在第5章中会对访问控制及授权模型进行更详细的介绍。

2.1.2 镜像签名

镜像在本质上是软件的封装形式,从安全角度来看,开发人员在部署镜像前需要保证镜像内容的完整性(integrity)。也就是说,这个镜像必须是软件的提供者创建、打包并推送的,在这个过程中镜像并没有被篡改。为了解决这个问题,Docker提供了内容信任的功能(Docker Content Trust,DCT),帮助镜像发布者在推送镜像时自动进行签名,并在必要时自动生成密钥。镜像的签名会被存储在Notary服务中。Notary是由Docker公司基于TUF(The Update Framework)更新框架开发的,通过对不同层次的信息进行签名,可以抵御中间人攻击、重放攻击等恶意行为,保证软件分发的可靠性。

Harbor作为镜像仓库,也通过与Notary集成提供了对内容信任的支持。用户在安装Harbor时可选择性地安装一个内置的Notary组件,在安装成功后,Notary的服务默认会通过4443端口暴露出来(对于不同的安装方式,端口可能不同),用户在推送和拉取镜像前,可以按Docker客户端的要求打开内容信任开关的环境变量:

img

之后,在用Docker命令行工具推送镜像时,会增加给镜像签名的环节:

img

在拉取镜像时也会首先查找签名,然后根据签名对应的摘要(SHA256)找到镜像并拉取:

img

用户在Harbor的管理界面上也可以看到该镜像的签名状态,如图2-1所示。

img

图2-1

从前面环境变量的配置中可以看到,Docker的内容信任是一个纯粹的客户端配置,用户可以通过在客户端关掉开关,跳过对签名的检查。Harbor为镜像的管理者提供了更强的措施,项目管理员可以通过在项目中配置策略,强制只有已签名的镜像才可以被拉取,无论客户端的配置如何。

此外,Harbor与Notary在用户权限上进行了集成。当使用Notary的命令行客户端对Harbor内部的Notary进行操作时,如删除某个镜像的签名时,必须提供Harbor的用户名和密码,而且此用户必须对所操作的镜像有写权限。Notary的命令比较复杂,因此不在本节中详述。

镜像签名相关的内容会在第6章中有更详细的讨论和介绍。

2.1.3 镜像扫描

容器镜像打包了代码、软件及其所需的运行环境,已发布的软件及其依赖的库都可能存在安全漏洞。有安全漏洞的镜像被部署在开发或生产系统中时,有可能被恶意利用或攻击,造成系统性风险,甚至发生数据泄露等灾难性后果。之前也有研究显示,即使是Docker Hub上的官方镜像,平均也有上百个不同等级的安全漏洞,足见容器镜像在带来方便的同时存在很多安全隐患。

为了帮助用户减少这种风险,Harbor 项目与一些安全服务商制定了一套扫描适配器(Scanner Adapter)的标准API,其中包含如何描述自己支持的Artifact类型、与仓库的认证方式,以及触发扫描、查询报告等功能。Harbor可以通过调用这些API驱动扫描器对仓库中的Artifact进行扫描,并得到统一格式的包含详细通用漏洞披露(Common Vulnerabilities Exposures,CVE)列表的报告。只要扫描器的开发者实现了这套API,就可以在保证网络连通的前提下,由Harbor管理员添加多个扫描器,在项目视图下选择扫描器并发起扫描任务,得到详细的报告并保存在Harbor的数据库中。

Harbor管理员管理扫描器的界面如图2-2所示。

img

图2-2

扫描完成后,项目成员可通过管理界面查看镜像的漏洞列表,如图2-3所示。

img

图2-3

此外,Harbor允许项目的管理员以项目级别设置安全策略,保证只有经过扫描而且没有高危安全漏洞的Artifact才可以被成功拉取和部署,如图2-4所示。

img

图2-4

安全漏洞的发现和公布是一个动态的过程,相同版本的软件会随着时间的推移报出越来越多的安全漏洞。一般而言,安全漏洞扫描器每隔一段时间就需要下载并导入最新的漏洞数据包中。推荐管理员定期对Harbor中的镜像及Artifact反复进行扫描,以确保及时发现漏洞并安装安全补丁。在这个过程中还存在一种特殊情况:在扫描某个Artifact时发现了新的安全漏洞,由于安全策略的设置导致它无法被部署,包含漏洞的相应软件对于整个系统很重要,而针对这个漏洞的补丁还需要一定时间才会发布。在这种情况下,为了不影响系统上线,Harbor允许管理员设置白名单,即在确认安全风险可控的前提下,在应用安全策略时故意跳过某些特定的CVE,以便Artifact被正常部署。

关于安全漏洞扫描和安全策略配置,在第6章有更详细的介绍。

2.1.4 高级管理功能

除了以上基本功能,Harbor在版本迭代中还根据社区反馈,为管理员及用户提供了很多高级管理功能以支持更加复杂的使用场景,包括Artifact复制策略、存储配额管理、Tag保留策略(Artifact保留策略)和垃圾回收等。本节带读者概览这些功能,在第7章和第8章中将进行详细描述。

1.Artifact复制策略

出于业务需要,用户经常要部署和管理多个 Harbor 仓库。举例来说,若一个企业给每个分公司都分别搭建了一个仓库,则在每次软件升级时,相同的镜像都需要被多次推送到不同的仓库。另外,以镜像形式发布软件的用户会为不同的开发阶段搭建独立的仓库实例,比如开发用的仓库、测试用的仓库等。持续集成的流水线会将代码仓库中不同服务的代码推送到开发仓库,当开发达到某个里程碑如功能全部完成时,会将某个版本的一组镜像推送到测试用的仓库进行测试。在这些情况下往往需要额外编写脚本,这又带来了更多的维护工作,如何高效管理多个仓库中的Artifact成为用户的一个挑战。

为了解决这类问题,Harbor允许管理员创建灵活的复制策略,以便在不同的仓库中复制镜像等Artifact。管理员首先需要建立目标仓库,提供目标地址(URL),根据需要配置用户名和密码,之后在创建复制规则时引用该目标仓库。用户可以选择把镜像从当前Harbor仓库推送到目标仓库,或者从目标仓库中拉取镜像到本地。在资源过滤器中,用户可以指定镜像的名称、Tag、标签和资源类型,以便只有符合条件的镜像才会被复制。复制规则还支持各种触发模式,除了支持手动触发,还支持定时触发(比如每周六午夜12点)、事件触发,其中事件触发比较适合前面提到的开发、测试场景。比如管理员可以在规则中指定所有包含v1.0-rc这个Tag的镜像被复制到测试仓库,这样当开发小组将标有v1.0-rc的镜像推送到开发仓库时,复制动作会被立刻触发,镜像能以最快速度到达测试小组并开始集成测试。如图2-5所示是一个复制规则配置的示例,test项目下带有“rc”字符串的Tag的镜像会被复制到Docker Hub上。

Harbor仓库的复制规则不仅限于Harbor本身的实例。Harbor在代码中定义了一组接口,只要实现这些接口,就可以作为目标仓库被加入复制规则中。在社区开发者的共同努力下,Harbor的复制策略已经支持Docker Hub、亚马逊ECR、阿里云镜像仓库等多种仓库。在上面的例子中,测试小组可以再建一条复制规则,将测试过的镜像复制到Docker Hub上,方便用户通过互联网拉取并使用镜像。

值得一提的是,在Harbor v2.0.0之后用户可以用统一的流程管理各种 OCI 兼容的Artifact,因此复制规则不仅限于容器镜像,符合复制规则的Artifact(Helm Chart、CNAB等)都会被复制。

当复制动作被触发时,Harbor会将被复制的Artifact分组到多个异步任务中进行复制,任务会在遇到网络问题等常见错误时自动重试。用户还可以通过管理界面查看任务日志,分析错误原因等。

img

图2-5

2.存储配额管理

对于仓库的管理员来说,空间一定是其最关心的资源。对应到Harbor这样的Artifact仓库,在日积月累的使用中,消耗最显著的就是存储资源了。管理员需要一种方法来控制用户对存储资源的使用,以免由于存储资源被某些用户过度占用而影响其他用户使用。为此,Harbor 提供了配额(Quota)管理功能。在创建一个项目时,管理员可以指定这个项目的存储配额,当项目使用的存储超过这个配额后,向这个项目推送或复制Artifact就会失败。用户需要删除一些Artifact来释放空间,或者请管理员增加配额才能推送成功。管理员可以通过项目的概要分页查看项目存储的使用情况,如图2-6所示。

img

图2-6

最常用的容器镜像是分层存储的,仓库中的多个镜像常常由同一个基础镜像构建而成,这时它们共用的一些数据层(layer)并不会被存储多次。这种设计提高了存储的效率,但是给计算实际的存储使用量带来了挑战。在计算项目占用的存储配额时,如果简单地将镜像的大小相加,得到的结果则会远远超过实际值。图2-7给出了一个项目中不同镜像共享数据层的示例。

img

图2-7

为了解决这个问题,Harbor在代码中通过中间件(middleware)截获了所有客户端推送镜像和Artifact的请求,在请求中得到了每层的大小,并在数据库中记录了层和项目之间的关系。这种方式一方面能帮助我们更加准确地计算一个项目实际占用的存储空间,另一方面因为在每层推送前都会对配额进行检查,因此在推送过程中项目使用的存储空间达到存储配额限制时,推送行为可以被及时中止。

3.保留策略

在给项目设置了存储配额限制后,为了在使用过程中避免配额被用尽,项目管理员需要经常删除镜像,以减少存储的使用量。特别是将Harbor用于持续集成的用户,每次代码入库都会触发构建和推送新的镜像到镜像仓库,存储使用量增长得很快,而且镜像个数过多,不便于查看和管理。

Harbor提供了Tag保留策略(又叫作Artifact保留策略),将用户从繁复的删除镜像工作中解放出来。它的设计思路是,由项目管理员定义一组范围和规则,当策略被触发时,在给定的范围内,满足这些规则的Tag会被保留,其余的会被删除。管理员可以在规则中设置名称匹配的模式、保留的个数等。除去手动触发,用户还可以给策略配置执行时间,这样就起到了定期批量清理的作用。管理员配置Tag保留策略的界面如图2-8所示。

img

图2-8

此外,每次策略执行时都会产生执行日志,里面详细记录了策略的执行时间、哪些镜像被清理掉等信息。这个功能涉及批量删除用户数据的操作,在多条规则组合在一起的情况下有可能使保留的条件变得复杂。而且策略特别提供了“模拟运行”功能,在管理员选择“模拟运行”后,系统会生成一条执行记录,提示用户哪些Tag会被清理,但不会真的进行删除操作,这样一些规则上的错误可以被及时发现,避免有镜像被误删。图2-9是触发策略执行及查看结果的界面。

img

图2-9

4.垃圾回收(垃圾清理)

前面提到过,仓库中的Artifact是分层存储的,Artifact和层是引用的关系,某些层会被多个Artifact同时引用,这就要求我们从存储中删除层数据时格外小心,因为有可能有正在推送引用这个层的Artifact,在这个时候很可能导致数据不完整。

因此,当用户删除一个Artifact时,并不会立即将Artifact引用的层从存储中删除。如果要真正减少存储的使用量,则需要由管理员触发垃圾回收任务。垃圾回收任务是由异步任务系统运行的,管理员可以通过Harbor的图形管理界面选择立即或定期执行它(如图2-10所示)。之后,可以通过“历史记录”页面来查看任务的执行进度和结果。

img

图2-10

为了保证数据的一致性,在垃圾回收任务执行的过程中,Harbor会进入只读状态,这时任何推送和删除Artifact的动作都会失败。这给使用带来一些影响。目前社区和开发人员正在积极探索,加入更细粒度的控制,避免将整个系统置为只读。