本文来自网易云社区
作者:孙婷婷
背景
在前文《测试环境docker化—基于ndp部署模式的docker基础镜像制作》中已经详述了docker镜像制作及模块部署的过程,按照上述做法已可以搭建测试环境。但是在实践过程中发现存在很多问题:
在一台云主机上搭建多个模块,容易出现资源不足的情况(我们在实验过程中有台云主机好几次宕机,经常要删掉不用的镜像容器);
部分模块之间需要相互调用,为方便部署多套环境简化配置修改,部署时需要确定容器的ip地址;
手动敲命令一个个构建容器,n个模块就要敲n个构建指令,构建容易出错;
基于上述原因,同时参考组内大神倪子的实践《细说Mammut大数据系统测试环境Docker迁移之路》,我们也尝试了引入容器集群编排,来进一步优化测试环境docker化的过程。
容器集群搭建
为解决一台测试环境云主机可能支撑不了一套环境的问题,我们考虑使用多台云主机组成一个容器集群,将多个deploy容器负载均衡的部署到多台云主机上,来实现环境的稳定和资源的合理利用。
要实现多主机的容器部署和分配,需要引入集群调度工具。目前业界比较主流的调度管理工具有Docker Swarm、Google Kubernetes 和 Apache Mesos(基于Marathon框架),其中目前已占领上风的是Kubernetes,成为容器编排管理的最佳实践工具。但是参考倪子的文章和介绍,我最终决定继续选择Swarm来进行实践。在Swarm的学习调研过程中,我发现区别于倪子实践使用的Docker Swarm,docker1.12版本已经进一步推出了内置的简单易用的Swarm mode集群。老的Docker Swarm使用独立的外部KV存储(比如Consul、etcd、zookeeper),搭建独立运行的Docker主机集群,使用KV存储配置进行服务发现。新版的Swarm mode则将docker swarm包含到docker引擎中,并内置了服务发现工具,使得集群搭建进一步简化为直接使用docker swarm命令初始化一台主机作为manager,其他机器加入该swarm成为worker即可。具体命令如下(我们的测试环境共使用3台云主机):
#将一台主机指定为manager,该命令会返回唯一一个集群唯一token,且该节点会同时作为manager和worker运行sudo docker swarm init --listen-addr 10.165.148.87:2377 --advertise-addr 10.165.148.87#在另两台主机上运行上述命令返回的join命令,即可加入该swarm集群sudo docker swarm join \ --token SWMTKN-1-0cytn4js5b8jn0vibtk935y7jptqxv63ttiqui1qhi2uo07emx-cr80z41yz9v2tsn8vyxgy6k2u \ 10.165.148.87:2377
以上命令即完成集群搭建,可通过sudo docker node ls命令查看集群中的节点。此后用户便可以通过manager节点操作整个集群,把多台Docker主机当做一台主机来部署容器服务和管理。
跨主机通信
docker官方文档介绍,当我们在一台主机上初始化一个swarm或者将一台云主机加入到一个swarm中时,该主机上将会默认创建两种新的网络:
一种是名称为ingress的overlay型网络。ingress主要负责swarm集群中服务之间的负载均衡和服务发布。若用户在新建swarm service时未指定自定义的overlay网络,它会自动加入ingress。
另一个中名称为docker_gwbridge的bridge型网络。它是一个用于连接overlay型网络(包括ingress)和独立docker主机的物理网络的虚拟网桥。
备注:理论上docker_gwbridge是自动创建的,但是不知道为什么我在实践过程中没有找到docker_gwbridge,最后自己重新创建了该网络。若想自定义创建和设置docker_gwbridge,那么必须在初始化或者加入swarm前创建,否则之后创建的容器端口将无法成功映射至主机端口。
当我们发布服务时,可以创建一个私有网络,实现跨主机之间的容器通信。在Docker1.9版本之后,Dokcer给大家带来了一种原生的跨主机容器网络解决方案,该方案基于VXLAN的覆盖网技术、依靠独立的外部KV存储(比如Consul、etcd、zookeeper)来通过注册于同一存储的配置来实现“服务发现”和“DNS”解析,从而实现私有网络的跨主机通信;同时在Docker1.12版本之后,我们只要通过Swarm mode搭建集群,而后创建overlay网络,直接用“原生态”的swarm来发现进行通信。
#若需要自定义创建docker_gwbridge或加入swarm后未发现docker_gwbridge,可以通过退出swarm后自定义创建;注意若想手动创建,必须要在加入swarm集群之前sudo docker network create --subnet 192.168.18.0/24 --opt com.docker.network.bridge.name=docker_gwbridge --opt com.docker.network.bridge.enable_icc=false --opt com.docker.network.bridge.enable_ip_masquerade=true docker_gwbridge#创建私有overlay网络,用于跨主机容器通信,-- opt 指定网络安全模式sudo docker network create --driver overlay --subnet 192.168.47.0/24 --opt encrypted overlay-net
私有网络创建完成后,只需在创建容器服务时指定自定义网络,容器变会在创建时自动加入该网络,实现容器间通信。
好了,现在容器之间可以互相访问了。但是每次容器启动的时候overlay都会动态分配一个虚拟IP地址,这样容器之间互相调用的时候就会需要在每次容器启动后再去容器中修改IP地址,当配置文件较多时修改起来会更加麻烦。熟悉docker engine的人一定知道link这个功能,Swarm mode则提供了一个类似的service discovery功能,它会将容器的虚拟IP映射到swarm的内置dns中,使用服务名称service name来作为域名。那么访问容器的IP则只需要访问服务名称,使得容器间通信更加方便。
compose部署
到此其实测试环境docker化的基本工作已经完成了,但是结合前文镜像制作的内容,我们的测试环境部署需要至少1个compile容器+n个deploy容器,deploy容器中也可能有多个web服务,各个容器需要暴露port,添加各自的entry.sh,若是通过docker service create命令一个个创建,一不小心就会配置错误导致容器无法启动;配置多套环境的可复制性也会差很多。
一个简单的办法是使用compose进行容器编排,将各命令简化为只需要编写compose的yaml文件即可。Docker1.13版本之后增加了compose v3,在Swarm的基础上引入了stack对service镜像的管理和编排,使得容器服务的部署更加简单。compose v3的语法可以参考Compose file version 3 reference,注意其与v2有些许不同。
我们在项目部署中,考虑到compile容器需要先行创建,以便在deploy部署前完成项目代码的构建,我们将整个测试环境的部署分割为3个yaml文件:
compose-compile.yaml:用于compile容器先行创建
compose-dependency.yaml:用于项目中依赖模块容器创建(比如zk、kafka、redis、mysql等)
compose-deploy.yaml:里面添加了项目各个模块的容器创建指令
我们在搭建完swarm环境+准备好各配置文件后,由于compose同时可以自定义创建network,最终我们仅需要三行命令即可创建一套测试环境:
#生成dependency应用,该应用中包含项目部署所需要的相关依赖,包括kafka、zk等 sudo docker stack deploy --with-registry-auth -c ./compose-dependency.yaml antispamdependency#生成compile应用,创建antispamcompile-compile服务完成项目编译sudo docker stack deploy --with-registry-auth -c ./compose-compile.yaml antispamcompile#生成antispam应用,该应用中包含各模块部署的相关服务sudo docker stack deploy --with-registry-auth -c ./compose-deploy.yaml antispam
测试环境docker自动部署
在完成compile和deploy镜像的制作后,可以将镜像保存在蜂巢,以方便部署环境时拉取。同时为保证每个deploy容器在创建后能自动拉取compile容器中构建好的编译模块进行模块部署,我们为各个功能模块编写了相应的entry.sh并保存在网易git仓库中,entry.sh文件实现相应的构建模块拉取、部分配置修改和模块启动。同时在deploy镜像中内置一个inter-entry.sh,该脚本用于deploy启动时拉取entry项目,并通过容器启动参数运行相应的entry.sh文件。
inter-entry.sh文件内容如下:
#!/bin/bashgit clone ssh://git@xxxx:22222/hzsuntingting/xxx-docker-entries.git /usr/local/docker-entry/xxx-docker-entries/echo $1chmod 777 /usr/local/docker-entry/xxx-docker-entries/entries/$1/usr/local/docker-entry/xxx-docker-entries/entries/$1
同时docker-entries项目中保存了compile编译项目需要的build.xml和容器编排需要的yaml文件,这样项目测试环境docker化只需要增加一个docker-entries项目,该项目目录结构如下:
最终,在搭建完swarm环境和准备好文件后,一个项目使用docker部署的流程如下图所示:
主机下载各依赖模块的镜像创建相应的依赖模块的容器;
修改被部署项目的接口调用配置为docker服务的service name,例如部分模块需要调用zk,而docker部署环境中zk的service name为dependency_zookeeper,则直接将调用ip改为该服务名即可;
主机下载compile镜像创建compile容器,容器在git拉取项目代码和docker-entries(里面含有build.xml),进行项目构建打包,并httpServer等待其他模块拉取;
主机下载deploy镜像创建各deploy容器,容器内置的inter-entry.sh运行在git拉取docker-entries;
各deploy模块拉取docker-entries后,根据容器启动的entrypoint参数运行相应的entry.sh,到compile容器中拉取相应的构建模块,解压,修改配置并启动。
总结
到此,测试环境docker化的自动化部署过程基本完成,组内成员已可实现用几行命令即完成一套环境的部署工作。但是在实践过程中,还有很多需要优化的地方,例如:目前各跨主机容器之间的文件共享采用的是起一个简单的httpServer+wget的方式以及git的方式替代,必须要指定确定的路径,在脚本编写过程中需要仔细比对,容易出错,若实现跨主机的文件共享则会避免更多错误问题。同时,查看网上资料,Swarm mode集群会折损系统约50%的性能,后续替换为k8s来实践,肯定为更有利于测试环境来完成异常测试等对系统稳定和性能要求比较高的测试。
坑
例行一坑,让大家少走弯路~
1. 指定主机为manager时,若机器有多个ip地址,需在其后加参数“--advertise-addr 10.165.148.87 ”,否则会报错
2. 创建overlay网络时可以用subnet参数指定用于网络的子网,如果不指定,swarm会自动选择一个子网,但是根据官网的描述这个自动指定的子网可能会引起container通信的问题
3.compose的yaml编写时,volumes中type:volume需要版本在3.2及以上,否则会返回services.compile.volumes.0 must be a string
4.在一开始从蜂巢拉取镜像的时候报错如下,原因是其他主机节点无权限,需要通过--with-registry-auth参数来增加权限。
unable to pin image hub.c.163.com/neteaseqa2017/compile to digest: errors: denied: requested access to the resource is denied unauthorized: authentication required
网易云容器服务为用户提供了无服务器容器,让企业能够快速部署业务,轻松运维服务。容器服务支持弹性伸缩、垂直扩容、灰度升级、服务发现、服务编排、错误恢复及性能监测等功能, 点击可免费试用。
网易云免费体验馆,0成本体验20+款云产品!
更多网易研发、产品、运营经验分享请访问网易云社区。
相关文章:
【推荐】 “代码变更覆盖率”在后端测试中的实践
【推荐】 用SolrJ操作Solr做搜索(下篇)