第一次尝试使用docker是在去年六月。
当时基于一些原因,笔者想把UI自动化用例放到docker里执行。但发现在docker中执行UI测试不是那么靠谱,于是就老老实实地用windows测试机,搞了个jenkins slave来执行用例。前不久,有一键部署测试环境的需求,就想到用docker完成这个工作。现在这个story已经算是比较完整了,分享给大家。
基于docker的测试环境一键部署
无论是开发自测环境的搭建还是测试环境的部署,一套环境从无到有的过程往往意味着各种下载与配置。这就好比你去一个地方,要先坐地铁,再转公交,非但要转公交,从公交车上下来,还需要走15分钟的路。而一键部署就好比出门打了一辆车,你在车上听歌,微信,摇一摇。你只需等司机告诉你到了,然后开开心心下车就好。
基本思路:
图里的头像分别代表:git,docker,jenkins以及需要部署的机器。
基本的思路是:先按照需求build 基础镜像。封装与复用的概念想必大家都听过,build 基础镜像其实也是如此。把一些依赖的,不变的组件build 进基础镜像,比如tomcat,比如jdk。再把变化的那部分用git 以及jenkins 来管理,比如war 包,比如配置文件。之后把不变的那部分与变的那部分组合起来,build成一个全新的镜像(—个全新的,能被复用的,不需要配置的,run起来就是一个服务的镜像)。最后只需在部署的机器上把这个镜像run起来即可。
一个应用一般包含不止一个服务,服务之间注册与发现的机制各有不同。在用docker完成一键部署的时候,服务之间的注册与发现问题已经转换为docker间的通信问题。
实现docker间通信的基础方法有两个:
使用docker的端口映射,以宿主机为媒介完成docker间通信;
使用docker link。
兵无常势,水无常形。这两种方式各有利弊,只要灵活运用就好。
具体实现:
照着上面的思路,笔者会先说如何构建一个单独的服务镜像,然后说如何让服务间相互发现,直至成为一个完整的应用。
以构建一个webApplication的镜像为例:
1、build一个基础镜像,这里需求各不相同,不细说。
2、在jenkins新建一个job,代码用git管理
分支是一个变量,用参数化构建来管理。类似的,所有可能会发生的变化都用参数化构建来管理,不一一列举。
3、在jenkins里新建Excute Shell,大部分你想实现的功能都可以用脚本来实现。比如编译代码打war包,比如你想自定义tomcat启动时的JAVA_OPTS。
4、当然最不可缺少的就是Dockerfile。
5、然后就是构建镜像,并push到私人仓库,清理空间。
此时,一个webApplication的镜像已经在你的私人仓库里。但镜像是不能提供服务的,镜像需要run成容器,docker run有很多门道。只需在你的docker client输入docker run --help,你就会明白我所言非虚。
上面提到过,docker间通信有两种基本的方法,采用哪种方式决定了如何run。下面会分别说一说:
1)使用端口映射实现docker间通信
端口映射使用的是docker run的 -p 参数。如 -p 8081:8080,代表了宿主机的8081端口映射了容器内的8080端口。你访问宿主机的8081端口,实际访问的是容器内的8080端口提供的服务。假如此时容器内的8080端口监听的是tomcat服务,你就能在浏览器里看到那只可爱的猫了。
举个栗子,这个webApplication需要一个mongoDB服务,这个mongoDB也运行在容器内。那么问题来了,如果我们能知道MongoDB所在容器的ip地址,只需使用类似docker run的--add-host参数就能指定被依赖服务的地址。但偏偏,docker run的容器ip是很难指定的。除非进到运行mongoDB的那个docker容器里查看ip地址,否则就不能告知webApplication,mongoDB服务的地址。但既然是一键部署,当然不能用手工的方式去查看IP了!怎么办?
很简单,只需要在run mongo容器的时候把mongoDB的服务端口映射到宿主机上,如 -p 27017:27017。那么webApplication只需访问宿主机的27017端口,就是在访问docker中的mongoDB服务了。宿主机的ip可以是固定的,把被依赖者的服务端口映射给宿主机,依赖者访问宿主机的映射端口。一个应用的各个服务就能用这种方式相互发现了。
但有个问题,假如宿主机的27017被占用了呢?
这个简单,改成-p 27018:27017就好!
如果webApplication指定访问的是27017端口呢?
让开发工程师去改代码?
不是不可以。但是——
幸好还有一种方法。
2)Docker link
用docker run的 --link参数能实现docker container之间的连接。
1、run一个mongoDB服务,用 --name 参数指定container的名字为 mongo
2、查明在webApplication里依赖的mongoDB的hostName为my-mongo
3、用--link参数,启动webApplication的container
4、webApplication容器内hosts文件下就有了下面这一行
目的已经达到,webApplication已经知道mongo的ip地址。无论你有多少个服务。只要你理清楚服务之间的依赖关系,用--link参数,按顺序启动容器。一个完整的应用就起来了。
但是,用这种方式有个致命的问题,服务之间耦合太高。当其中一个服务不可用,需要重新run container时。link它的容器也需要重新run,犹如多米诺骨牌。最坏的情况是整个应用的服务全都需要重新run。如果应用包含的服务很多,那简直就是噩梦。
在实践中,笔者创建了两套job。给类似DEMO环境这种专属机器部署的时候笔者用端口映射的方式,专属机器上几乎不可能会有不可避免的端口冲突。在给个人开发机提供部署服务的时候,笔者选择用link的方式。因为此时端口冲突的几率非常大。也不用太担心那个耦合度太高的问题,能在个人机器上部署的应用,可想而知不会有太多服务。再者,假如有端口冲突,用户是不太希望去修改配置的。就像没有人希望打车的时候司机不停问路。
基于docker的jvm监控一键部署
没有性能监控的性能测试,就像没有沙拉的蔬菜沙拉。
监控至关重要。
对java应用而言,最重要的监控是对jvm的监控。通过监控jvm,你可以了解虚拟机运行时的状态,例如Heap、MemoryPool等。通过这些参数可以分析代码的缺陷等。
说起jvm的监控,大家首先想到的是jconsole,这个朴实无华却又非常实用的工具。
jconsole
若是监控本地java进程,只需打开jconsole,选择对应的本地进程即可。像这样
若要监控远程进程,配置也不复杂,只需要在java进程的启动参数中加入如下几个配置(这里有一些安全配置,不在本文讨论范围之内)
在jconsole客户端中连接远程进程
jconsole实在是一款很好用的工具
但是,jconsole有一些局限。
有时候你需要监控的远程进程运行在一台网络策略比较复杂的远程机器上,这时候使用jconsole就会很头疼。若是这个进程还运行在docker容器内,那简直就能让你的头爆炸。
怎么解决?
Jconsole是基于JMX的工具。JMX有一套API,称为MBean。也许我们可以利用MBean写一套框架,像特洛伊木马一样让它在远程机器上执行。这套框架的工作很简单:收集需要监控进程的运行时数据,发给一个数据状态收集工具,比如graphite。这个数据收集工具就像一个管家,你关心的所有数据它都能告诉你。客户端只需要保证和这个数据工具的网络互通即可。
但无论对谁来说,从零开始写一套可靠的框架都不是一件太容易的事。维护和优化需要很多的时间。
幸好,已经有了一套现成的方案
JmxTrans
这不仅是一套现成的框架,甚至有现成的镜像。你要做的就是把镜像run成容器,照着官方文档修改一下配置就能使用。如下:
1、启动容器
2、查看Dockerfile,找到配置文件所在目录,在笔者的实践中,配置文件在 /opt/jmxtrans/conf目录下
3、新建自己的配置文件
大多数你可能需要使用的配置,都可以在jmxtrans的官网上找到,但不排除有一些奇特的坑。踩踩更健康。
4、重启容器,查看jmxtrans的日志是否有报错信息,我的日志在/opt/jmxtrans/log目录下。如果你的配置没有问题,log看起来是这样的
Graphite
jmxtrans获取到java进程的运行时数据后,需要一个收集并展示的组件。对于jmxtrans而言,这个组件是一个"writer"。jmxtrans可以配合很多种"writer"使用。笔者用的是graphite,当然也是跑在docker里的。
graphite的api服务监听在80端口。可以看到,docker run时映射了宿主机的80端口,如何处理端口冲突在上篇中已经说过。现在只需在浏览器中访问http://${宿主机ip}:${宿主机映射端口}
dashbord长这样:
实现一键部署
现在我们已经能用jmxtrans和graphite完成远程jvm的监控,但配置的过程略显繁琐。尤其是每个jmxtrans都要加入新的配置文件,非常麻烦。所以,笔者也对这套环境监控做了一键部署,如下:
1、把jmxtrans的配置文件用git来管理,不同环境的配置文件用不同的分支。
2、用jenkins job做一键部署的载体,在job中拉取git仓库里的jmxtrans配置文件。
3、把配置文件Add进jmxtrans镜像,build成一个包含指定配置文件、即插即用的镜像。
4、启动graphite容器,上文提过。
5、启动jmxtrans容器,这时采用link的方式是极好的.因为在jmxtrans配置文件中声明的地址,需要在jmxtrans所在docker容器的hosts文件中定义其真实ip。docker的真实ip在一键部署中是动态变化的,没法预先知道。
然后只需把jenkins的job串联起来,监控的一键部署就完成了。但是,graphite的dashbord实在是丑。为了心情好,我们需要一个颜值高的图表
grafana
依然使用docker
然后在浏览器中打开grafana,创建DataSource
配置graphite data source
创建graph
创建所有你想看到的监控图表
比较graphite的图表,心情岂非好上许多?
本文作者:施凯(点融黑帮),目前就职于点融网infra部门,擅长质量保证体系的搭建和开发。