使用 Harbor 管理个人容器镜像
坑边闲话:容器是现代开发、运维都要学习的技术,开发人员要让应用的逻辑尽可能地做到可水平扩展、可监控状态,运维人员要尽可能地将服务以云原生的方式部署并实现高可用。在这个过程中,镜像管理非常重要,这涉及到软件的供应链安全,也关系到整个 DevOps 的流程顺滑性。
1. Harbor 简介·
在使用容器的时候,我们实际需要的是进程。进程是一个动态的概念,它需要某些二进制代码、可执行文件。后这就是我们说的 Application. 在传统环境中,我们需要到软件官网将程序下载下来,然后装入内存,得到一个动态的进程。使用容器部署任务与此类似,毕竟容器只是加了更强的隔离属性的进程。容器所需要的代码软件就是镜像。
因此,镜像与软件有相似的属性,也面临相似的问题。比如,容器也面临网络传播的体积限制,我们总是希望打包出来体积越小越好。此外,软件的供应链安全也是一个敏感话题,如何对镜像进行签名也是一个绕不开的问题。
如何才能像 Apple 的 App Store 一样将软件的上架、分发、更新等流程管理好呢?那当然是搭建一个属于自己的 Image Store 啊!
铺垫了这么多,笔者就是想说明搭建一套属于自己的镜像服务是多么的重要!
- 如果有几十个 Node 都要拉取某个镜像,那么同时从 DockerHub 拉取就容易造成外线拥堵,甚至有可能触发 DockerHub 的 DDoS 防御。但如果使用 Harbor 先把副本拉下来,然后其他 Node 通过内网从 Harbor 获取镜像便可绕过该问题。因此,自建 Registry 的缓存功能在生产中非常重要。
- 如果某个镜像非常大,而且性质有些敏感,不宜推送到公共镜像托管平台,则将之托管到内网里的 Harbor 私有 Registry 就非常合适。
- 一来不用被外线带宽限制
- 二来可以保护镜像的私密性
2. Harbor 部署·
2.1 容器部署的一般方式·
在 Windows 系统上执行程序,可以通过双击 exe
可执行文件的方式。其中的逻辑是
- 首先找到程序在文件系统中的路径
- Windows 的文件系统子系统将可执行文件从磁盘块设备中逐块读取到内存,并将 PE 文件头部解析出来装入进程结构体,最终交付给进程调度器。
- CPU 开始轮转,从宏观上看就是操作系统启动了一个进程
在 Linux 上执行 SRv4 的 ELF 文件也具有类似的逻辑。前面我们提到,容器和镜像的关系,十分类似于进程和可执行程序。
exe
文件即容器技术中的镜像- 进程即容器技术中的容器实例
既然如此,容器也跟进程类似,他将拥有
- 特权级
- 是否是守护进程
等属性。反映到 Docker 命令中就是 --priviledge
和 -d
两个 docker run
启动参数。
在云原生领域,我们认为服务以容器方式部署,因此这种服务都是要在后台常驻,而不是执行完一闪而过。在实际开发与测试中,我们只是希望借助容器的环境执行一些我们的代码,因此可以通过目录、文件映射的方式,让容器执行我们的代码,随后退出容器(即杀死进程)并删除容器。关于最后一点,有些新手或许比较好奇,为什么容器的退出和普通进程的退出不一样呢?普通进程退出就什么都没了,但是容器退出后还会在 docker ps -a
中留下一个表项。这一点很好理解,保存一个表项主要是为了方便我们后续快速重新启动这个容器。此外,容器具有部分虚拟机的性质,比如容器启动之后可能就是在空转,里面并没有什么代码在跑,只有容器中的某个具体可执行文件被运行的时候,容器才是真正开始执行。因此,可以把容器退出后留下一个表项理解为虚拟机关机。
因为容器的启动需要大量参数,在复杂的部署场景中这并不是个容易处理的工作。因此程序员发明了 compose
,通过用 YAML 配置文件指定启动容器时的参数,可以快速启动容器。当然,在这个 Kubernetes 一统云原生的时代,K8S 配置脚本可能更受欢迎一些,只是在个人单机应用场景下,compose
YAML 脚本更方便罢了。
2.2 总结·
3. Harbor 实战·
Harbor 的上手颇为繁琐,因为它是一个多容器组成的服务,配置文件比较复杂。
此外,Harbor 的 docker compose 配置文件需要根据 YAML 模板指导生成,因此先要理解 Harbor 的模板文件。
3.1 Harbor 的安装·
首先下载 Harbor 最新的安装包,我建议选择在线版本,其中不包含 docker images,因此可以在线拉取。
解压之后,会得到目录 harbor
,结构如下:
1 | ├── common |
随后,执行:
1 | ./prepare |
即可得到一个 docker-compose.yml
文件,以此启动服务即可。
下面将对模板文件的主要内容做讲解。harbor.yml
大概有三百多行,其中大部分是注释。
data_volume
, Harbor 数据存储的目录external_url
, Harbor 服务使用的 URL
3.2 启用 HTTPS·
3.3 客户端如何使用 Harbor 服务·
4. 使用 Cosign 对镜像签名·
Harbor 的存在就是为了更好地对容器镜像进行管理。所谓良好的管理,不仅仅要实现完备的功能,还需要实现较高的安全性和可用性。古往今来,软件的供应链安全问题经久不衰,镜像作为传统软件的扩展,也面临同样的问题。然而,镜像最初只是为了满足可用性,并没有在格式定义上对完整性校验、数字签名等功能做特殊处理。如今镜像的使用已经非常普及,再造一个标准难以为市场接纳,扩展现有标准在技术上困难重重。所以我们该如何抉择?
问题确实存在,问题必须解决!
使用 Cosign 工具对容器进行签名,随后将签名单独存储于 Registry 或许是个可行的方案。Cosign 是 SigStore 组织的核心工具之一,如今主流的 Linux 发行版已经在官方库里提供了 Cosign. 我们可以借助 Cosign 轻松实现对 OCI 容器镜像的数字签名。
通过参考官方文档可以快速安装 Cosign,以 Debian 12 为例:
1 | sudo apt install cosign |
4.1 什么是数字签名·
数字签名是公钥密码体系里的一个概念,在现实生活中也应用极广。在介绍数字签名之前,先要介绍公钥密码的特性。公钥密码与对称密码不同,它有两个密钥,分别是公钥(Public Key)和私钥(Private Key),其中,私钥一般用 key 来指代。如果有人问你要 Key,他一般是要你的私钥,要坚决保密!
- 私钥保持在你私人手里,绝对不能告诉其他任何人;
- 公钥可以通过直接分发的方式散播出去,或者以证书的方式散播;
在加解密特性上要满足:
- 公钥加密的内容,只有私钥能解密
- 私钥可以对内容做签名,用公钥可以验签
其中的数学原理比较复杂,涉及到有限域运算的一些定理,此处不展开介绍。
4.2 生成 Cosign Key·
通过以下命令可以生产 Cosign 公私密钥对:
1 | cosign generate-key-pair |
因为 Cosign 要求对私钥加密,因此这个过程会要求你输入一个解密的口令。留空即可创建无加密的密钥对。
随后,你将得到一对公私钥:
1 | cosign.key |
4.3 使用 Cosign 对本地的镜像做签名·
1 | cosign sign --key /path/to/cosign.key <image_name>:<tag> |
Cosign 签名是需要联网的,因为上述命令会完成:
- 生成签名
- 上传签名到 Registry
登录到 Harbor,即可查看 Cosign 的结果。
4.3 验证 Cosign 签名·
在下载了一个镜像之后,我们可以像镜像的提供者索要他的 Cosign 公钥,并以此做验证:
1 | cosign verify --key /path/to/cosign.pub <image_name>:<tag> | jq |
通过上述命令,即可验证签名的有效性。由于结果是 Json 格式,所以用 jq
命令可查看结果。