镜像仓库,顾名思义就是存储镜像的。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 容器名
目录类似于 blobs 目录,但是它不存储真是数据仅仅以 link 文件保存每个 layer 的 sha256 编码。保存该 repository 长传过得所有 layer 的 sha256 编码信息
该 repository 的上传的所有版本(tag)的 manifest 信息。其目录下有 revisions 目录和 tags 目录
每个 tag 一组记录(v1), 每个 tag 下面有 current 目录和 index 目录, current 目录下的 link 文件保存了该 tag 目前的 manifest 文件的 sha256 编码,而 index 目录则列出了该 tag 历史上传的所有版本的 sha256 编码信息
目录里存放了该 repository 历史上上传版本的所有 sha256 编码信息
是一个临时目录,一旦镜像上传完成,该目录下的文件就被删除
2 上传镜像流程
认证
到认证服务获取 token
查询仓库中是否有欲上传的层
开始上传 blob
大块用 patch 分块传,小块用 put。分块上传后也要以一个 put 请求表示完成上传。
当所有的 blob 上传完成后需上传文件清单。
注意:
如果上传镜像的某一层在仓库中已经存在,并且有读的权限。docker 会先获取 token,之后携带这个 toke 进行 mount,减少重复层的上传,加快 push 速度
mount 信息处理其实就是在生产对应 layer 的信息放在_layers 目录下。
对于已经存在的层,但是没有权限的,客户端需要重新上传,但是最终存储还是一份。但是文件系统做 move 时,先判断目的路径是否存在,存在则不进行覆盖。
对于已经存在的镜像 HEAD 请求时世界返回 200,表示不需要上传。
本文转载自公众号 360 云计算(ID:hulktalk)。
原文链接:
https://mp.weixin.qq.com/s/GkISsJ6bBQ5_Kn6UdjoEeg
评论