镜像仓库,顾名思义就是存储镜像的。Docker 仓库的概念跟 Git 类似,注册服务器可以理解为 GitHub 这样的托管服务。用户制作好镜像 push 到仓库,这样下次在另外一台机器上使用这个镜像时候,只需要从仓库上 pull 下来就可以了。本文主要介绍 HULK 使用的镜像仓库 Harbor。
什么是 Harbor
Habor 是由 VMWare 公司开源的容器镜像仓库,是一个用于存储和分发 Docker 镜像的企业级 Registry 服务器。
Harbor 主要是提供了一些企业级的管理功能,而镜像存储用的还是 docker registry,相当于 docker registry 的反向代理。
1 Harbor 架构
如上图所示,Harbor 由 6 个组件组成:
1.Proxy: nginx 反向代理。上图来自官网,已经滞后了。目前到 harbor 的所有请求都必须走 UI,包括上图中 Proxy–> Registry 这条。
2.Registry: 负责存储 Docker 图像和处理 Docker push/pull 命令。由于 Harbor 需要加强对映像的访问控制,因此注册中心将引导客户端到令牌服务,以便为每个 pull 或 push 请求获得一个有效的令牌。
3.Core services: Harbor 的核心功能,主要提供以下服务:
a.UI: 提供了一个 web 管理页面,当然还包括了一个前端页面和后端 API。
b.Webhook:在 Registry 中配置,镜像复制,日志更新都是通过该功能实现。
c.Token service: 令牌服务,如果从 Docker 客户机发送的请求中没有令牌,注册中心将把请求重定向到令牌服务。
d.Job services: 镜像复制。
e.Log collector: 日志收集。
2 HULK 使用的 Harbor 功能
用户管理
基于角色的访问控制:用户分为三种角色:项目管理员(MDRWS)、开发人员(RWS)和访客(RS),当然还有一个造物主 admin 系统管理员。
注:M: 管理、D: 删除、R: 读取、W: 写入、S: 查询。
项目管理
项目管理是系统最主要的一个功能模块,项目是一组镜像仓库的逻辑集合,是权限管理和资源管理的单元划分。一个项目下面有多个镜像仓库,并且关联多个不同角色的成员,镜像复制也是基于项目的,通过添加复制规则,可以将项目下面的镜像从一个 harbor 迁移到另一个 harbor。
配置管理
配置管理主要是配置 harbor 的认证模式,企业内部使用,通常都是对接到公司 LDAP 上面,我们目前用的数据库认证;还可以设置 token 的有效时间。
镜像复制
HULK 多机房就是通过镜像复制功能实现的,可在不同的数据中心、不同的运行环境之间同步镜像。
目前 HULK 上,用户申请容器服务后,我们会为其创建个 Harbor 的 project(下图中的 xxl-api 即为 Harbor 中的项目名),
并为其分配两个用户名,一个 RWS、一个 RS,xxl-api 是只读用户,还有一个对用户隐藏的 xxl-api-p 开发人员用户。以达到用户只能操作自己私有仓库的目的。
3 Harbor 的高可用
- 负载均衡
通过三个 harbor 完成高可用部署,前面通过负载均衡器(HULK 上的 LVS)对外提供服务。共享数据库与缓存。
- 多机房
多机房可以应对单机房 s3 异常,机房孤岛等及特殊情况,同时可以减轻主机房负担。
目前我们有 bjyt(主)和 shyc2(从)两套 harbor,push 都到主,k8s 拉镜像可以选择拉主或者从。
每个机房的 harbor 组件完全独立,包括 s3 和数据库。目的就是为了即使出现孤岛也不会影响服务。
什么是镜像
镜像就是,联合文件系统(UnionFS),目前用的驱动是 overlay2。
镜像的基础层是 rootfs:任何程序运行时都会有依赖,无论是开发语言层的依赖库,还是各种系统 lib、操作系统等,不同的系统上这些库可能是不一样的,或者有缺失的。为了让容器运行时一致,docker 将依赖的操作系统、各种 lib 依赖整合打包在一起(即镜像),然后容器启动时,作为它的根目录(根文件系统 rootfs),使得容器进程的各种依赖调用都在这个根目录里,这样就做到了环境的一致性。
Layer:Dockerfile 中的基础是 rootfs,而之后的每一个操作都是一层,如:RUN、ADD 等命令。所以为了镜像体积小写,可以把多个 RUN 命令整合成一行,这样多层就变成一层了。
镜像只有最上一层是读写的,其余都是只读的(目录的 whiteout 属性)。所谓 whiteout 属性 union 文件系统中,如果删除的文件在只读层,最上层看到文件已经删除,但是只读层文件依然存在,在最上层做改文件 whiteout 隐藏文件实现。rm mnt/haha.log 操作和 touch a/.wh.haha.log 效果相同。
1 容器的镜像挂载
docker 支持多种 graphDriver,包括 vfs、devicemapper、overlay、overlay2、aufs,docker-ce 镜像存储驱动目前用的是 overlay2。
docker 默认的存储目录是 /var/lib/docker
[root@p22295v zhangzhifei]# ls -lrt /var/lib/docker/ total 156 drwx--x--x 3 root root 4096 Dec 6 2018 containerd drwx------ 4 root root 4096 Dec 6 2018 plugins drwx------ 3 root root 4096 Dec 6 2018 image drwx------ 2 root root 4096 Dec 6 2018 trust drwxr-x--- 3 root root 4096 Dec 6 2018 network drwx------ 2 root root 4096 Dec 6 2018 swarm drwx------ 2 root root 4096 Dec 6 2018 builder drwx------ 89 root root 12288 Jul 17 11:07 volumes drwx------ 2 root root 4096 Jul 17 14:30 runtimes drwx------ 2 root root 4096 Jul 23 12:51 tmp drwx------ 758 root root 94208 Jul 29 19:12 overlay2 drwx------ 80 root root 12288 Jul 29 19:12 containers
我们运行个容器演示下:
[root@p22295v zhangzhifei]# docker run -it -d kraken-agent:dev 83555ad8c034682ad885fc9e320bfb1f8b75498b61a1a8684d738c411caa930b
启动一个容器,在 /var/lib/docker/overlay2 目录下生成一个容器视图层,目录包括 diff,link,lower,merged,work。
diff 记录每一层自己内容的数据,link 记录该层链接目录(实际是 l 目录下到层的链接),比如在容器中创建目录或在 diff 新增该目录。
根据存储数据及功能可以把这些层分为 3 部分:
1、只读层
2、init 层(夹在只读层和读写层之间,专门用来存放 /etc/hosts、/etc/resolv.conf 等信息。需要这样一层的原因是,这些文件本来属于只读的系统镜像层的一部分,但是用户往往需要在启动容器时写入一些指定的值比如 hostname,所以就需要在可读写层对它们进行修改。可是,这些修改往往只对当前的容器有效,我们并不希望执行 docker commit 时,把这些信息连同可读写层一起提交掉。所以,Docker 做法是,在修改了这些文件之后,以一个单独的层挂载了出来。而用户执行 docker commit 只会提交可读写层,所以是不包含这些内容的。)
3、读写层(在没有写入文件之前,这个目录是空的。而一旦在容器里做了写操作,你修改产生的内容就会以增量的方式出现在这个层中)
查看容器挂载目录:
[root@p22295v zhangzhifei]# cat /var/lib/docker/image/overlay2/layerdb/mounts/83555ad8c034682ad885fc9e320bfb1f8b75498b61a1a8684d738c411caa930b/mount-id 3695f349587aaa2cdc82fcde1a380c7b567ef870a47e4c28b8b279e4edc9eb40[root@p22295v zhangzhifei]# #读写层 [root@p22295v zhangzhifei]# ls /var/lib/docker/overlay2/3695f349587aaa2cdc82fcde1a380c7b567ef870a47e4c28b8b279e4edc9eb40/diff/ [root@p22295v zhangzhifei]# #只读层 [root@p22295v zhangzhifei]# ls /var/lib/docker/overlay2/65e5cdd72f2995da4c73f2d9b90e8d974b9d2f18829a2479296aaec24e67d185/diff/ bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var #只读层(Dockerfile 时 ADD 的二进制程序) [root@p22295v zhangzhifei]# ls -lrt /var/lib/docker/overlay2/852fa5138c3da5070b59e6402348a5a281378b28ee08fede9c635e4101f91092/diff/usr/bin/ total 28836 -rwxr-xr-x 1 root root 29526888 Jul 10 16:23 kraken-origin
最终,这些层都被联合挂载到 /var/lib/docker/overlay2/3695f349587aaa2cdc82fcde1a380c7b567ef870a47e4c28b8b279e4edc9eb40/merged 目录下,表现为一个完整的文件系统和运行时环境供容器使用。
[root@p22295v zhangzhifei]# mount | grep 3695f349587aaa2cdc82fcde1a380c7b567ef870a47e4c28b8b279e4edc9eb40 overlay on /var/lib/docker/overlay2/3695f349587aaa2cdc82fcde1a380c7b567ef870a47e4c28b8b279e4edc9eb40/merged type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/Z7QMVXSKSNAKCUEJ6ZMU5YTFWG:/var/lib/docker/overlay2/l/2OYCXTK7M4QN3DT7IYJK6J7VYT:/var/lib/docker/overlay2/l/UZTDJDVUOBHU2VERRLXF5KMIQO:/var/lib/docker/overlay2/l/NAXXPRFMO4ATUIG6SFPU4LBUUV:/var/lib/docker/overlay2/l/AM4PHUFWOD4UHYIVO5Q6GVZ5L7:/var/lib/docker/overlay2/l/7XLJNT7Q3UQIKHDNV4QG4EX2C3:/var/lib/docker/overlay2/l/3RAVSDXXRS3BASAKZFPT2ESY2K:/var/lib/docker/overlay2/l/FFNAQF5ADFSTEBNZZ4O2R3CP4N:/var/lib/docker/overlay2/l/X6BOWOZKYRN3DZFY6QLLP7OFDP:/var/lib/docker/overlay2/l/P3EO3WHIM2XPDNPIFUP42EGMQI:/var/lib/docker/overlay2/l/EOSBLWDBASO7GKSDILC4XVGO45:/var/lib/docker/overlay2/l/7K7266OIDWAVXLAN6AA3SZXZQZ,upperdir=/var/lib/docker/overlay2/3695f349587aaa2cdc82fcde1a380c7b567ef870a47e4c28b8b279e4edc9eb40/diff,workdir=/var/lib/docker/overlay2/3695f349587aaa2cdc82fcde1a380c7b567ef870a47e4c28b8b279e4edc9eb40/work) [root@p22295v zhangzhifei]# ls /var/lib/docker/overlay2/3695f349587aaa2cdc82fcde1a380c7b567ef870a47e4c28b8b279e4edc9eb40/merged bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
镜像在镜像仓库中的存储
1 镜像存储的目录结构
以本地存储为例,默认在 /data/registry/docker/registry/v2,镜像存储的任何一层都不会重复。
├── blobs │ └── sha256 │ │ └── dfa94d685d1c2179324f02bf2a119f6d8ee0d380cef5506566012f7c4936a04a │ │ └── data │ ├── e6 │ │ └── e6ae4ac760c8457aca9be07de8ca66b3a358a19b950389a0d158ae885178f6cf │ │ └── data │ ├── e7 │ │ └── e71de1ca8f2b18993c258e2bf50edea8c23ea4a78a821bcfef181de50b3c32f4 │ │ └── data └── repositories ├── registry-share-private │ ├── push-mount │ │ ├── _layers │ │ │ └── sha256 │ │ │ ├── 1b1ad4542c99b8881265610cf5dc09e37d38445529a7584edb2a607fd783216f │ │ │ │ └── link │ │ ├── _manifests │ │ │ ├── revisions │ │ │ │ └── sha256 │ │ │ │ └── 9e4cf4691735c02e59dd49ee561a3f5e56bccf78d57eaa94581e29f69a5162bd │ │ │ │ └── link │ │ │ └── tags │ │ │ └── v1 │ │ │ ├── current │ │ │ │ └── link │ │ │ └── index │ │ │ └── sha256 │ │ │ └── 9e4cf4691735c02e59dd49ee561a3f5e56bccf78d57eaa94581e29f69a5162bd │ │ │ └── link │ │ └── _uploads │ ├── push-new │ │ ├── _layers │ │ │ └── sha256 │ │ │ ├── 1b1ad4542c99b8881265610cf5dc09e37d38445529a7584edb2a607fd783216f │ │ │ │ └── link │ │ ├── _manifests │ │ │ ├── revisions │ │ │ │ └── sha256 │ │ │ │ └── 9e4cf4691735c02e59dd49ee561a3f5e56bccf78d57eaa94581e29f69a5162bd │ │ │ │ └── link │ │ │ └── tags │ │ │ └── v1 │ │ │ ├── current │ │ │ │ └── link │ │ │ └── index │ │ │ └── sha256 │ │ │ └── 9e4cf4691735c02e59dd49ee561a3f5e56bccf78d57eaa94581e29f69a5162bd │ │ │ └── link
1、blobs
目录是存放每层数据(gzip)以及一个镜像的 manifests 信息(json)的具体文件
2、repositories
存储镜像的组织信息,类似于元数据
- 仓库名
registry-share-private/push-mount 就是一个仓库名,registry-share-private 相当于 project 的概念,push-mount 容器名
- _layers
目录类似于 blobs 目录,但是它不存储真是数据仅仅以 link 文件保存每个 layer 的 sha256 编码。保存该 repository 长传过得所有 layer 的 sha256 编码信息
- _manifests
该 repository 的上传的所有版本(tag)的 manifest 信息。其目录下有 revisions 目录和 tags 目录
- tags
每个 tag 一组记录(v1), 每个 tag 下面有 current 目录和 index 目录, current 目录下的 link 文件保存了该 tag 目前的 manifest 文件的 sha256 编码,而 index 目录则列出了该 tag 历史上传的所有版本的 sha256 编码信息
- _revisions
目录里存放了该 repository 历史上上传版本的所有 sha256 编码信息
- _uploads
是一个临时目录,一旦镜像上传完成,该目录下的文件就被删除
2 上传镜像流程
- 认证
- 到认证服务获取 token
- 查询仓库中是否有欲上传的层
- 开始上传 blob
大块用 patch 分块传,小块用 put。分块上传后也要以一个 put 请求表示完成上传。
- 上传 mainfest
当所有的 blob 上传完成后需上传文件清单。
注意:
如果上传镜像的某一层在仓库中已经存在,并且有读的权限。docker 会先获取 token,之后携带这个 toke 进行 mount,减少重复层的上传,加快 push 速度
mount 信息处理其实就是在生产对应 layer 的信息放在 _layers 目录下。
对于已经存在的层,但是没有权限的,客户端需要重新上传,但是最终存储还是一份。但是文件系统做 move 时,先判断目的路径是否存在,存在则不进行覆盖。
对于已经存在的镜像 HEAD 请求时世界返回 200,表示不需要上传。
本文转载自公众号 360 云计算(ID:hulktalk)。
原文链接:
评论