使用docker in docker离线私有registry
by 伊布
- 1 笨拙的docker registry
- 2 灵活一点:打一个大包
- 3 隔离:docker in docker
- 4 docker in docker的问题
- 5 docker in docker优化
- 6 综述
1 笨拙的docker registry
由于我厂离线交付的特殊性,我们需要在客户现场运行一个私有registry,该registry包含我们产品的所有app的镜像;有registry的好处是,当kubernets上的pod在不同节点上漂移时,只要从私有registry上docker pull镜像即可。
私有registry的搭建比较简单,可以参考这篇文章,但这样搭建出来的registry中没有任何docker镜像,因此需要:
- 开发打包时,将产品所有app都docker save为对应app的tar包;
- 客户现场部署时,启动私有registry;
- 客户现场部署时,将各app的tar包先docker load到本地,然后docker push到私有registry。
我把这个过程叫做”回放”。
这个方案的优势是,多个开发人员同时打包时,互相之间不受影响;但有一些缺点:
- 产品交付件会比较大。我们知道docker镜像是分层的,多个app会有复用的layer;但由于每个app都是独立的tar包,因此每个tar包里都有一个复用layer的副本,自然交付件会比较大,比较浪费。
- regitry回放时,docker load 和docker push都非常耗时,在我们不算差的服务器上基本需要半个小时的时间,体验很糟糕。
2 灵活一点:打一个大包
还记得小时候玩的一张卡带300游戏的红白机小霸王吗?拿到以后是不是感觉特别爽,300个游戏啊!但其实,90坦克和疯狂坦克可能只是敌方坦克的速度不同,超级马里奥和水管玛丽可能只是衣服颜色换了换。300个游戏,其实只是几个游戏的衍生版。
到k8s这,其实也差不多;我们的改进,也可以参考下小霸王游戏机。
改进点1:
前面我们提到docker镜像是分层的,如果将所有镜像都打包为一个文件,自然会节省很大的空间。
改进点2:
docker registry中保存的信息,通常保存在/var/lib/registry中,其中包含了2大信息:存储在blobs中的image layer,和存储在repositories的image元数据(有哪些project、哪些image等)。
因此在开发打包时,在开发环境拉起一个私有registry,将产品所有app从harbor上docker pull下来以后,再docker push到该私有registry,并将registry的/var/lib/registry
打包;在客户现场部署时,只要将这个包解封后挂载到新的私有registry,就可以完成回放了。
通过这两个改进,最终交付的registry离线包就只有1个大包,而且这个大包只要在客户现场解封后挂载到新docker registry容器中即可;这样耗时的操作放到了频率很低的开发打包阶段,能够提高部署的效率;包的size也变小了很多,我们实测的结果是,之前10GB的app在合并后只有3GB多一点,这“压缩比”太可观了。
但将”回放“移到开发打包阶段的坏处是,多个开发人员同时打包时,都在操作同一个docker registry,势必会互相影响:最终打包的不知道是张三的版本还是李四的版本,这取决于张三和李四谁push的更晚。
开发者们需要隔离。
要隔离不同的开发者,一个可选的方案是使用虚拟机,独门独院,在虚拟机中启动独立的registry容器,不同开发人员完全隔离,但我们没这个条件(无法动态的创建、删除虚拟机);另一个可选的方案是,使用docker in docker。
3 隔离:docker in docker
顾名思义,docker in docker是指在docker容器中运行docker。我们可以在docker容器里运行web应用、mysql服务甚至spark,那么自然也可以在docker容器里运行docker服务本身。
在dind(docker in docker)容器里,可以看到一个新的docker daemon,它与宿主机上的docker daemon没有任何关系。我们可以在dind容器里docker pull/push拉取推送镜像,也可以docker run启动一个新的docker,宛如一个虚拟机。
有了dind,前面所说的隔离就很好解决了。
3.1 开发打包registry.tar
1 启动docker in docker
docker run --privileged -v {registry_dir}:/root/registry:rw -v {pwd}:/root:rw --name {name} -d {image} --insecure-registry docker.ieevee.com
说明几点。
- dind容器必须要privileged高权限
- 将
{registry_dir}
映射到/root/registry
,这个目录用来存储docker registry的blogs和repositories。之后在dind中启动docker registry时需要将/root/registry
再映射到docker registry的/var/lib/registry
目录,通过2层-v映射,打到离线registry的目的。 - 由于需要在dind中pull harbor的镜像,而我们的harbor并没有启动https,所以需要传一个
insecure-registry
参数给dind容器,该参数最终会传给dind中启动的docker daemon。 - 不需要设置
--storage-driver
。默认dind的docker daemon使用的是vfs(据说会有点慢),如果你的os是ubuntu,那么可以传一个--storage-driver=aufs
给dind,但如果你的os是centos 7.2,传--storage-driver=devicemapper
给docker daemon,会造成在dind中docker pull时报错。统一起见,不要设置了。
2 在dind中pull产品所有app
在宿主机上:
docker exec -it {dind id} sh -c docker pull {image}
3 在dind容器中拉起docker registry容器
在宿主机上:
docker exec -it {dind id} sh -c docker run -d -p 80:5000 --restart always -v /root/registry:/var/lib/registry:rw --name registry registry:2
4 在dind容器中将所有app镜像push到docker registry容器
在宿主机上:
docker exec -it {dind id} sh -c docker push {image}
5 将1中映射的{registry_dir}打包,但不要压缩
tar cf registry.tar {registry_dir}
3.2 客户现场恢复registry.tar
客户现场的操作比较简单了,只需2步:
tar xf {repo_tar} -C /home/
docker run -d -p 80:5000 --restart always -v /home/{registry_dir}:/var/lib/registry:rw --name registry registry:2
由于打包时没有压缩,这里tar的速度还是挺快的。
4 docker in docker的问题
虽然dind很方便,作者Jérôme Petazzoni建议,不要用docker in docker来做CI。
具体解释一下,这里是指,CI运行在容器中,如果要启动新的app容器,那么势必就要在容器中启动容器了,也就是docker in Docker。
Jérôme Petazzoni 在其blog中提到了dind的3个问题:
1 dind跟Linux Security Modules (LSM)配合不好。
when starting a container, the “inner Docker” might try to apply security profiles that will conflict or confuse the “outer Docker.”
不同linux distribution的LSM不同,有的是AppArmor,有的是SELinux,内部docker启动时会修改外部docker的安全配置,如果内外不同可能会有问题。
我这里倒是ok,因为centos上的SELinux是Disable的。
2 storage driver
外部docker daemon的storage driver通常是一个传统的文件系统,例如我们用的是ext4(–storage-opt dm.fs=ext4),但内部docker daemon只能用copy-on-write文件系统,如aufs,devicemapper, brtfs等。内外docker daemon的stroage driver不是随意配对的,只能固定组合。aufs on aufs很好用,但brtfs on brtfs在subvolume时会造成父volume卸载失败,而Devicemapper则干脆就不支持namespace。
我们的解决办法是使用vfs,目前来看没遇到什么问题。
3 build cache
由于dind容器是个全新的环境,一个docker镜像都没有,一切都需要从头开始pull。dotCloud将/var/lib/docker
共享给了多个docker daemon,但遇到了数据损坏的问题。
这个没有很好的解决方法,只能先忍了。
5 docker in docker优化
Jérôme Petazzoni 给了一个优化方法:将宿主机的docker.sock挂载到dind容器里去。
docker run -v /var/run/docker.sock:/var/run/docker.sock -ti docker
这样dind中的docker client连接的是宿主机的docker daemon,新创建的docker容器,不再是dind容器的子容器,而是dind容器的兄弟姐妹:因为它们是宿主机的docker daemon创建的!
这样能够解决CI in docker的问题,因为CI要求的,其实只是能在docker中跑CI即可,至于CI拉起的app容器,并不要求一定要在docker里面。
但这正是我想避开的:多个docker registry在同一个宿主机上,他们无法隔离,甚至因为registry都需要映射80端口,实际只能跑一个registry。
6 综述
总的来说,虽然docker in docker有一些缺点,但我这里都不是事;用docker in docker 来做docker registry的离线,体验还不错,值得推荐。
Subscribe via RSS