Jason Pan

Docker注册表服务及存储

黄杰 / 2023-06-29


前两天,作为基础设施的公司镜像服务异常,影响时间以天计,严重影响研发流程。

一个镜像服务为什么会影响这么大?故障是什么原因造成的?为什么恢复需要天级别的时间。

带着这些问题,今天来了解一下 Docker 注册表(Registry)的原理。

1. 容器化的重要性

容器化是软件开发的一种方法,通过该方法可将应用程序或服务、其依赖项及其配置(抽象化为部署清单文件)一起打包为容器映像。 然后,可将容器化应用程序作为单元进行测试,并将其作为容器映像实例部署到主机操作系统 (OS)。

docker-host

容器化在整个应用程序生命周期工作流中提供了隔离性、可移植性、灵活性、可伸缩性和可控性等诸多优点。 最重要的优点是可在开发和运营之间实现环境隔离。

虽然以上说的更多是容器在部署方面的优点,但是开发流程中也深深的依赖容器的。一方面,持续集成 CI 需要在特定的容器中进行构建,另一方面,构建的结果需要放到特定的容器镜像中,才能进行部署。

容器化依赖于容器镜像的生成、传输。简单但不精准的理解:加载运行起来的镜像就是容器,容器的内容dump出来就是镜像。之前文章《给 Docker 镜像瘦身》中有详细介绍容器、镜像、层的概念和关系。

因此,镜像服务是开发、部署环节中不可或缺的一环。出现异常必然会严重影响开发流程。

2. docker pullpush 做了什么

大家比较熟悉的 docker pull ubuntu:22.04 可以直接拉取 hub.docker.com 上管理的 Ubuntu 指定版本的镜像。

有的同学也有经验手动往 hub.docker.com 上推送一个镜像,比如使用 docker push panzhongxian/lottery-tools 指令:

docker-hub-show

实际上 docker pull ubuntu:22.04docker pull docker.io/library/ubuntu

docker-pull

如果是私有镜像仓库,就需要指定域名不能省略:

docker pull myregistrydomain:port/foo/bar

那这两个指令做了什么? 本质上就是将镜像,发送到远端服务器,或者从远端的服务器上取回来。 只不过,这个远端的服务有一个特殊的名称叫做 Docker Registry

3. Docker Registry 简介

Docker Registry 注册表是一个无状态、高度可扩展的服务端应用程序,用于存储并允许分发 Docker 映像。也就在 docker pulldocker push 的服务端。

basic-taxonomy-in-docker-from-microsoft

Registry 的源代码在 CNCF distribution 项目中,遵循 Apache 许可协议开源,可以在GitHub上找到源代码 https://github.com/distribution/distribution

Docker、CoreOS等容器供应商于 2015 年 6 月创立了开放容器倡议 OCI (Open Container Initiative)。目前包含三个规范:运行时规范(runtime-spec)、镜像规范(image-spec)和分发规范(distribution-spec)。Registry 遵循 OCI 的分发规范

Docker Hub 是由 Docker 维护的公共注册表。AWS、Google和其他云厂商也有容器注册表的服务。

出于对性能、可靠性、权限控制、集成CI/CD系统等方面的考虑,企业内部也会搭建自己私有的 Registry,正如背景中提到的出问题的镜像服务。个人也可以搭建本地的 Registry。

4. Docker Registry 的其他细节

除了上边基本的使用 docker pull/push 指令与 Registry 进行交互之外,本小节还会介绍另外一些注册表的其他也很重要的细节。

存储:Registry 将存储委托给驱动程序。默认存储驱动程序是本地 posix 文件系统,适合开发或小型部署。还支持其他基于云的存储驱动程序,例如 S3、Microsoft Azure、OpenStack Swift 和 Aliyun OSS。只要实现 Storage API(实际是源码中的Go接口storagedriver.StorageDriver)就相当于实现了存储驱动,就可以将特定的存储系统接入到 Docker Registry。

Registry 说白了本质上是个特定协议的存储系统。本文开头提到的本次公司内的故障也因为后端存储系统异常而引起的。公司内使用的后端存储为 ceph,计划迁移到 COS。

docker-storage-backend

鉴权:对托管镜像保护,不能让别人随便访问是至关重要,因此注册表本身支持 TLS 和基本身份验证。注册表的源码仓库中,还含了高级身份验证和授权方法,提供给大型的活或公共部署可以使用更复杂的鉴权方式。

通知系统:Registry 附带了一个强大的通知系统,可以调用 webhook 来响应活动,以及广泛的日志记录和报告,这对于想要收集指标的大型系统非常有用。

以 hub.docker.com 为例,我们 push 上去镜像,并不是理所当然的就更新了页面上的内容,而是应该通过 docker.io 的 Registry 在完成了传输之后,通过通知系统触发了镜像仓库的一些处理动作。

5. Docker Registry 的部署

部署一个本地的 Registry 是非常简单的一件事情。官方文档提供的方式是直接使用 Docker 去拉取并运行 Registry:

docker run -d -p 5000:5000 --restart=always --name registry registry:2

实际上边的指令运行能运行起来,但是无法通过localhost 访问到 Registry 服务,可以在 run 后边使用 --network="host" 来解决,也要注意不要对外暴露端口:

docker run --network="host" -d -p 127.0.0.1:5000:5000 --restart=always --name registry registry:2

其实有另外一种是也非常简单,就是从上边 Github 源码拉取之后,直接 make 就能得到 registry 的二进制文件,但是其启动命令行需要认真的研究一下,而不是像第一种方式一样,连启动命令都写在了镜像里。

本地部署如此的简单,可能你运行完指令都没有意识到已经有一个本地 Registry 可以接受你的推送和拉取镜像了:

docker tag ubuntu:22.04 localhost:5000/my-ubuntu
docker push localhost:5000/my-ubuntu

推送成功:

docker-push-to-localhost

关于更多部署的配置,可以参考官方文档。其实都是围绕着上一节介绍的 Registry 的特性进行的支持:

容器内配置文件的位置在 /etc/docker/registry/config.yml,使用外部自己的配置文件,可以直接通过 -v 映射:

docker run --network="host" -d -p 127.0.0.1:5000:5000 \
  --restart=always --name registry \
  -v `pwd`/config.yml:/etc/docker/registry/config.yml \
  registry:2

6. Docker Registry 存储配置

之前解释了存储对于 Docker Registry 的重要性,本小节重点介绍一下对存储的配置和支持。

配置文件中的 storage 选项是必需的,它定义正在使用哪个存储后端。有且只能有一个后端配置。如果配置更多,注册表将返回错误。

storage:
  filesystem:
    rootdirectory: /var/lib/registry
    maxthreads: 100
  azure:
    accountname: accountname
    accountkey: base64encodedaccountkey
    container: containername
    rootdirectory: /az/object/name/prefix
    credentials:
      type: client_secret
      clientid: client_id_string
      tenantid: tenant_id_string
      secret: secret_string
    copy_status_poll_max_retry: 10
    copy_status_poll_delay: 100ms

官方支持的后端配置包括:

扩展话题

e1. 分布式存储与对象存储

上边列举的,除了本地文件系统外,其他都是分布式存储系统,有几个是特别的对象存储系统。

并不是有网络的系统就是分布式存储系统,比如我们熟悉的 NFS 只是普通的网络存储,但不是分布式存储。

分布式指代了一种独特的系统架构类型,这种系统架构是由一组通过网络进行通信,为了完成共同的任务而协调工作的计算机节点组成。简言之,就是将数据分散存储到多个数据存储节点上,利用更多的机器,处理更多的数据

常见的分布式文件系统有:Ceph、Gluster、 Swift 、HDFS等。

对象存储是用于存储非结构化数据的数据存储架构,它将数据划分为单元——对象,并存储在结构扁平的数据环境中。区分于文件存储,对象存储创建一个称为存储桶的平面结构,而不是分层或分级存储,每个对象都包含数据以及应用可用于轻松访问和检索对象的元数据和唯一标识符。其主要优势是近乎无限的可扩展性以及为数据湖、云原生应用程序、分析、日志文件和机器学习(ML)等使用场景存储大量数据的成本较低。

e2. 驱动程序

在文中介绍后端存储的时候,提到了存储驱动。所谓驱动程序,就是一个软件组件,可让高层可以和底层的组件进行通信,比如操作系统和设备彼此通信。

不仅仅操作系统对设备的操控会被称为驱动,这种其实是设备驱动程序,另外还有软件驱动程序,通常是指提供给用户层操作内核数据和资源的程序。

文中提到的驱动,实际是提供给 Registry 读写存储系统,尤其是分布式存储或对象存储系统的。具体地,需要实现其规定的接口,即可被Registry 所使用。

e3. Ceph

Ceph是一个统一的分布式存储系统,设计初衷是提供较好的性能、可靠性和可扩展性。

Ceph项目最早起源于Sage就读博士期间的工作(最早的成果于2004年发表),初衷是变成一个可避免单节点故障的统一的分布式文件系统,提供较好的性能、可靠性和PB级别的扩展能力。之后被贡献给开源社区,在经过了数年的发展之后,目前已得到众多云计算厂商的支持并被广泛应用。RedHat及OpenStack都可与Ceph整合以支持虚拟机镜像的后端存储。

Ceph可用于对象存储、块设备存储和文件系统存储。