镜像仓库,顾名思义,就是存储镜像。Docker仓库的概念与Git类似,注册服务器可以理解为GitHub等托管服务。用户制作一个镜像,推送到仓库中,这样下次在别的机器上使用这个镜像,只需要从仓库中拉取即可。本文主要介绍360私有云HULK使用的镜像仓库Harbor。一、什么是HarborHabor是VMWare开源的容器镜像仓库,是一个企业级的Registry服务器,用于存储和分发Docker镜像。Harbor主要提供一些企业级的管理功能,而dockerregistry用于镜像存储,相当于dockerregistry的反向代理。1.Harbor架构如上图所示,Harbor由六个组件组成:Proxy:nginx反向代理。上图来自官网,有所滞后。目前所有对harbor的请求都必须经过nginx,包括上图中的Proxy-->Registry。Registry:负责存储Docker镜像和处理Dockerpush/pull命令。由于Harbor要求对图像进行更严格的访问控制,因此注册表会将客户端定向到令牌服务,以便为每个拉取或推送请求获取有效令牌。Coreservices:端口的核心功能,主要提供以下服务:UI:提供web管理页面,当然也包括前端页面和后端API。Webhook:Registry中的配置,镜像复制,日志更新都是通过这个功能实现的。Tokenservice:令牌服务,如果Docker客户端发送的请求中没有令牌,注册中心会将请求重定向到令牌服务。作业服务:镜像复制。日志收集器:当天收集。2、HULK使用的Harbor功能(1)用户管理基于角色的访问控制:用户分为项目管理员(MDRWS)、开发者(RWS)和访客(RS)三种角色,当然还有creatoradmin系统行政人员。注:M:管理,D:删除,R:读取,W:写入,S:查询。(2)项目管理项目管理是系统最重要的功能模块,一个项目是镜像仓库的逻辑集合,是权限管理和资源管理的单元划分。一个项目下有多个镜像仓库,它们关联着不同角色的成员。镜像复制也是基于项目的。通过添加复制规则,可以将项目下的镜像从一个harbor迁移到另一个harbor,并且可以通过日志查看复制过程有重试机制。(3)权限管理配置管理主要是配置harbor的认证方式,供企业内部使用,通常连接到公司的LDAP。我们目前使用的是数据库认证;您还可以设置令牌的有效时间。(4)镜像复制HULK多机房是通过镜像复制功能实现的,可以同步不同数据中心、不同运行环境之间的镜像。目前在HULK上,用户申请容器服务后,我们会为他创建一个Harbor项目(下图中的xxl-api是Harbor中的项目名),并给他分配两个用户名,一个是RWS,一个是RS,xxl-api是只读用户,还有一个对用户隐藏的xxl-api-p开发者用户。以达到用户只能操作自己的私有仓库的目的。3、Harbor的高可用负载均衡通过三个Harbor完成高可用部署,通过负载均衡器(LVSonHULK)对外提供服务。共享数据库和缓存。多机房、多机房可以应对单机房S3异常、机房孤岛等特殊情况,同时可以减轻主机房的负担。目前我们有两套harbors,bjyt(master)和shyc2(slave),都是push到master,k8s在拉镜像的时候可以选择拉master或者slave。每个机房的Harbor组件是完全独立的,包括s3和数据库。目的是即使有孤岛也不影响服务。2、什么是镜像?联合文件系统(UnionFS),目前使用的驱动是overlay2。镜像的基础层是rootfs:任何程序在运行时都会有依赖,无论是开发语言层的依赖库,还是各种系统库、操作系统等,这些库在不同的系统上可能不一样,也可能缺失。docker为了让容器运行时一致,将依赖的操作系统和各种lib依赖集成打包在一起(也就是镜像),然后在容器启动的时候,作为它的根目录(根文件系统rootfs),这样容器进程的各种依赖调用都在这个根目录下,这样就达到了环境的一致性。Layer:Dockerfile的基础是rootfs,后面的每一个操作都是一个layer,比如:RUN,ADD等命令。为了减小镜像体积,可以将多个RUN命令合并为一行,让多层变成一层。只有最上层的镜像是可读写的,其余都是只读的(目录的without属性)。所谓无属性联合文件系统,如果被删除的文件在只读层,顶层看到文件已经被删除,但是只读层文件还存在,修改文件不将文件隐藏在顶层。rmmnt/haha.log操作与toucha/.wh.haha.log效果相同。1、容器镜像挂载docker支持多种graph驱动,包括vfs、devicemapper、overlay、overlay2、aufs,docker镜像存储驱动目前使用的是overlay2。docker默认存放目录为/var/lib/docker[root@p22295vzhangzhifei]#ls-lrt/var/lib/docker/total156drwx--x--x3rootroot4096Dec62018containerddrwx------4rootroot4096Dec62018pluginsdrwx------3rootroot4096Decimager6201------2rootroot4096Dec62018trustdrwxr-x---3rootroot4096Dec62018networkdrwx------2rootroot4096Dec62018swarmdrwx------2rootroot4096Dec62018builderdrwx------89rootroot12288Jul1711:07volumesdrwx------2rootroot4096Julrun714:32rootroot4096Jul2312:51tmpdrwx------758rootroot94208Jul2919:12overlay2drwx------80rootroot12288Jul2919:12containers我们运行个容器演示下:[root@p22295vzhangzhifei]#dockerrun-it-dkraken-agent:dev83555ad8c034682ad885fc9e320bfb1f8b75498b61a1a8684d738c411caa930b启动一个容器,在/varAcontainerviewlayeris在/lib/docker/overlay2目录下生成,包括diff、link、lower、merged、work。diff记录的是每一层自身内容的数据,link记录的是本层的链接目录(实际上是l目录下层的链接),比如在容器中创建目录或者在diff中添加目录。根据存储数据和功能,这些层可以分为三部分:只读层init层(夹在只读层和读写层之间,专门用来存放/etc/hosts,/etc/resolv.conf等信息。之所以需要这么一层是因为这些文件原本是只读系统镜像层的一部分,但是用户在启动容器的时候往往需要写入一些指定的值比如hostname,所以需要在可读可写层。但是,这些修改往往只对当前容器有效,我们不希望在执行dockercommit时将这些信息连同可读写层一起提交。所以,Docker的做法是,修改完这些文件后,单独挂载一层。但是当用户执行dockercommit时,只会提交可读写层,所以不包含这些内容。)读写层(这个目录是空的,直到没有文件被写入。Andonceyoudoawriteoperationinthecontainer,thecontentyoumodifiedwillappearinthislayerinanincrementalway)Viewthecontainermountdirectory[root@p22295vzhangzhifei]#cat/var/lib/docker/image/overlay2/layerdb/mounts/83555ad8c034682ad885fc9e320bfb1f8b75498b61a1a8684d738c411caa930b/mount-id3695f349587aaa2cdc82fcde1a380c7b567ef870a47e4c28b8b279e4edc9eb40[root@p22295vzhangzhifei]##读写层[root@p22295vzhangzhifei]#ls/var/lib/docker/overlay2/3695f349587aaa2cdc82fcde1a380c7b567ef870a47e4c28b8b279e4edc9eb40/diff/[root@p22295vzhangzhifei]##只读层[root@p22295vzhangzhifei]#ls/var/lib/docker/overlay2/65e5cdd72f2995da4c73f2d9b90e8d974b9d2f18829a2479296aaec24e67d185/diff/binbootdevetchomeliblib64mediamntoptprocrootrunsbinsrvsystmpusrvar#只读层(Dockerfile时ADD的二进制程序)[root@p22295vzhangzhifei]#ls-lrt/var/lib/docker/overlay2/852fa5138c3da5070b59e6402348a5a281378b28ee08fede9c635e4101f91092/diff/usr/bin/total28836-rwxr-xr-x1rootroot29526888Jul1016:23kraken-origin最终,这写层都被联合挂载到/var/lib/docker/overlay2/3695f349587aaa2cdc82fcde1a380c7b567ef870a47e4c28b8b279e4edc9eb40/merged目已记录,代表一个完整的操作系统和运行时环境供容器使用[root@p22295vzhangzhifei]#mount|grep3695f349587aaa2cdc82fcde1a380c7b567ef870a47e4c28b8b279e4edc9eb40overlayon/var/lib/docker/overlay2/3695f349587aaa2cdc82fcde1a380c7b567ef870a47e4c28b8b279e4edc9eb40/mergedtypeoverlay(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/AM4PHUFWOD4UHYIVOL/var/docker7Q6Goverlay2/l/7XLJNT7Q3UQIKHDNV4QG4EX2C3:/v??ar/lib/docker/overlay2/l/3RAVSDXXRS3BASAKZFPT2ESY2K:/var/lib/docker/overlay2/l/FFNAQF5ADFSTEBNZZ4O2R3CP4N:/var/lib/docker/overlay2/l/X6BOWOZKYDPRN3DZvar/FY6docker/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@p22295vzhangzhifei]#ls/var/lib/docker/overlay2/3695f349587aaa2cdc82fcde1a380c7b567ef870a47e4c28b8b279e4edc9eb40/mergedbinbootdevetchomeliblib64mediamntoptprocrootrunsbinsrvsystmpusrvar三、镜像在镜像仓库中的存储1.镜像Thestoragedirectorystructuretakeslocalstorageasanexample.Itisin/data/registry/docker/registry/v2bydefault,andanylayerofmirrorstoragewillnotberepeated.├──blobs│└──sha256││└──dfa94d685d1c2179324f02bf2a119f6d8ee0d380cef5506566012f7c4936a04a││└──data│├──e6││└──e6ae4ac760c8457aca9be07de8ca66b3a358a19b950389a0d158ae885178f6cf││└──data│├──e7││└──e71de1ca8f2b18993c258e2bf50edea8c23ea4a78a821bcfef181de50b3c32f4││└-丁基氨基-雷神├├├├├邮等-列为-派---帕斯-马特-摩托-──-─link││├---------------revisionS││││└--sha256││││└---sha256││││└----9e4cf4691735c02e59dd49ee561a3f56bccf78d578d57ea94581e29f69a5169a5162bd││││└--─-─-?v1│││├-─-current││││└istrent││││└-─-丁基│││└─-in-ind氨256│││└-─---─--9e4cf4691735c02e59dd49ee561a3f5e56bccf76bccf78d578d57aea94581e29f69af69a515162bdbdbdbd│││└---─_uploads│├──push-new││├──_layers│││└──sha256│││├──1b1ad4542c99b8881265610cf5dc09e37d38445529a7584edb2a607fd783216f││││└──link││├──_manifests│││├──revisions│││││──sha256││││└──9e4cf4691735c02e59dd49ee561a3f5e56bccf78d57eaa94581e29f69a5162bd││││└──链接││││...)blobs目录是一个特定的文件,它存储每一层数据(gzip)和一个镜像清单信息(json)。一个仓库名,registry-share-private相当于project的概念,push-mount容器名_layers:该目录类似于blobs目录,但不存放真实数据,只保存每层的sha256编码一个链接文件,用来保存repository长度传递的所有层的sha256编码信息_manifests:repository上传的所有版本(tags)的manifest信息。其目录下有revisions目录和tags目录tags:每个tag有一组记录(v1),每个tag有当前目录和index目录,当前目录下的link文件保存了tag当前的sha256编码清单文件。index目录列出了所有历史上传版本标签的sha256编码信息。_revisions:该目录存放版本库所有历史上传版本的sha256编码信息。_uploads:这是一个临时目录。图片上传完成后,该目录下的文件将被删除。2.上传图片过程并认证到认证服务获取token。查询仓库中是否有要上传的图层。开始上传blob。如果用大块,可以分块上传,如果用小块,可以用put。分片上传后,还需要一个put请求来表示上传完成。上传清单当所有的blob都上传后,需要上传文件列表。注意:如果上传图片的图层在仓库中已经存在,并且有读取权限。Docker会先获取token,然后携带这个token进行挂载,减少重复层的上传,加快推送速度。mount信息处理其实就是产生layer对应的信息,放到_layers目录下。对于已经存在但没有权限的图层,客户端需要重新上传,但最终存储的还是一份副本。但是在移动文件系统时,首先会判断目的路径是否存在,如果存在则不会被覆盖。对于已有的镜像HEAD请求,world返回200,表示不需要上传。【本文为栏目组织360科技、微信公众号《360科技(id:qihoo_tech)》原创文章】点此阅读作者更多好文
