原创:陈斌 张少华
本期导读
1.为提升部署效率持续集成测试环境从胖docker向深度容器化的改进;
2.架构采用Rancher + Harbor,网络采用pipework为容器配置固定的IP地址;
物理架构
在测试环境建设过程中由于模块数量快速增加,为了提高部署效率和稳定性我们在容器化的基础上进行了深度容器化的探索,深度容器化采用了Rancher + Harbor的架构,该架构有非常优良的性能,非常适合以pod为单位进行子系统级别测试环境建设。
网络配置
如上图所示,每一个Node节点对应现在的一套测试环境,拥有独立的ip。
其实现原理为Node节点是Dind生成容器,创建后使用pipework为容器配置固定的IP地址,之后可在Node中启动多个Pod,分享使用Node的IP地址。
对比Cattle和K8S使用过程中的差异
由于我们测试需求的特殊性,目前Rancher上面的服务群并不需要频繁的做升级,而是会频繁的创建和释放。Cattle的稳定性和易用性要优于K8S,但是K8S提供了更加强大的周边功能包括资源管理、监控、配置管理等。
Cattle:
(1)多服务应用,每个项目都作为独立的服务受Rancher管理,升级比较方便。缺点是需要以应用(stack)为单位进行扩展和调度,因为我们其实是把一个应用里的多个容器包在一起对外提供服务。
扩展方式 :stack-copy、或者通过模板创建(效率略低于主从容器方式)。
(2)主从容器,一个容器作为主容器,添加多个从容器,耦合关系更紧密,优点是以服务(service)为单位进行扩展和调度。
扩展方式:由于资源对象的编号,直接scale即可。
缺点:
当需要对该服务做升级时,会重启所有容器,从容器越多,升级越慢。因为升级的最小操作单元就是服务。
当在服务中使用负载均衡时,而该服务又拥有从服务的时候,你需要使用主服务作为负载均衡器的目标。从服务不能成为目标。
从服务不能使用links/external_links来创建服务别名。
内部访问:Cattle提供了内部DNS解析,直接以容器名访问。
K8S:
多容器Pod
以Pod为单位进行调度,一个Pod里面启动多个容器,类似于Cattle的主从容器方式,但是差异在于网络。
扩展方式:scale(效率不高)。
内部访问:Pod内的所有容器共享网络namespace,其实就是都通过-net=container(Pod创建时有一个sleep容器) 共享了同一个网络,所以端口不能冲突,容器直接以localhost(或者127.0.0.1)访问。
缺点:
扩展效率低下。
容器之间的相互通信不是十分稳定。
优点:有一系列的强大的利于编排的功能,比如 initContainer、HPA、PV等都是非常好的特性。
CI 工作流
数据库组件容器化
Mysql容器化
我们测试环境使用的mysql版本是5.6.41,所以我们制作mysql-5.6.41的镜像,制作过程比较简单
(1)拉取官方镜像启动,环境变量必须要设置。
docker run -p 3300:3306 --name mysql -e MYSQL_ROOT_PASSWORD=root123 -d docker.io/mysql:5.6.41
(2)利用docker cp 将我们的配置文件复制到该容器中
docker cp ${PWD}/my.cnf mysql:/etc/mysql/
(3)利用docker commit命令 将修改部分提交,
docker commit mysql 192.168.1.251:7003/qa/mysql:5.6.41-private
(4)推送到Harbor
docker push 192.168.1.251:7003/qa/mysql:5.6.41-private
(5)启动试一下:
docker run -p3301:3306 -e MYSQL_ROOT_PASSWORD=root@123 -e TZ=Asia/Shanghai --name mysql-p -d -v /home/demo/data2:/var/lib/mysql 192.168.1.251:7003/qa/mysql:5.6.41-private
(6)客户端连接,使用我们启动时指定的root的密码,然后查看一下配置是否正确,找一个我们配置文件里的自定义选型比对一下:
(7)没问题,完成。注意数据卷的使用,不要把数据放在容器内部,这部分,具体使用还要再做规划。
Mongo容器化(3.6.4)
先看下咱们的配置文件,没有什么特殊配置,可以直接拉取官方镜像启动,然后看下mongo镜像的启动参数:
环境变量:
配置文件默认在 /etc下,启动时 使用–config /etc/mongo/mongo.conf 来指定自定义配置,这里不需要。我们只需要直接启动官方镜像,把数据文件映射出来即可。
docker run -p 27000:27017 -v /home/demo/mongodata:/data --name mongo-p -d -e MONGO_INITDB_ROOT_USERNAME=admin -e MONGO_INITDB_ROOT_PASSWORD=admin -e TZ=Asia/Shanghai 192.168.1.251:7003/qa/mongo:3.6.4
客户端连接一下试试:
Redis容器化(3.2.12)
其实原理跟mysql类似,这里简单写了个Dockerfile,因为redis启动必须指定配置文件
FROM docker.io/redis:3.2.12
MAINTAINER xxx@xxx.com
COPY redis.conf /usr/redis.conf
CMD [ "redis-server", "/usr/redis.conf" ]
对配置文件做了一点修改,把日志文件去掉了,在容器里,我们一般都是使用docker logs 查看日志,需要把日志输出出来,而不是写入文件里。
docker build -t 192.168.1.251:7003/qa/redis:3.2.12 .
docker run -p 6300:6379 -v /home/demo/redisdata2:/data -e TZ=Asia/Shanghai --name redis-p -d 192.168.1.251:7003/qa/redis:3.2.12
连接成功,注意这里没设置密码验证。如果想要加上密码,也非常简单,redis支持一系列的command line,使用方式跟在配置文件里面的key值完全一样,比如这里我们要加上密码验证,只需要在docker 启动命令最后加上
(注意docker run的启动参数是有顺序的,在镜像名字后面的内容都认为是command,平时启动镜像,一般的参数都写在镜像名字之前)
应用容器化
S2I 对接生产
由于生产使用OpenShift,原有的镜像构建流水线做出的镜像虽然比较简单易用,但无法与OpenShift兼容,所以借助OpenShift提供的镜像构建工具 s2i 去做镜像。缺陷就是目前这个基础镜像太大了。
本地构建方式:
(1)安装s2i,并拉取基础镜像到本地
(2)拉取代码,并切换到项目根目录,例如:/home/work/dev/projects/hydra
(3)执行s2i构建:s2i build . 192.168.1.251:7003/qa/python:2.7-oc hydra -e GIT_REPO_NAME=hydra (必填:通过这个环境变量来指定项目名称,并且会影响到代码在容器中的路径)
(4)启动镜像:通过上面的命令,构建出了一个名字为 hydra,tag为latest的镜像,要启动这个镜像,也非常简单,需要指定几个环境变量
- env:
- name: GIT_REPO_NAME
value: hydra
- name: INI_CMD
value: (测试环境一般不需要这个变量)
- name: RUN_CMD
value: make start_test
- name: RUN_CMD_DIR
value: /data/home/work/hydra/
应用修改:
(1)测试时同一个Pod里面启动多个容器,所以nginx 不能启动会有端口冲突。uwsgi启动需要将socket替换为http-socket并且端口不能冲突,由于日志管理不便,暂时舍弃。
(2)Makefile 添加用于测试环境的启动和初始化的命令
启动命令(RUN_CMD):start_test,先做make local_config操作,然后做启动操作,直接runserver启动即可
初始化命令(INI_CMD):测试一般不需要,线上一般会复制nginx配置
(3)以hydra 的修改为例子:
修改tools/config_templates/gen_config.py 直接vim编辑,将class LocalSettings(object) 这个类里的localhost全部替换为127.0.0.1,因为localhost不走物理网卡,一些数据库连接会直接在容器内部找socket,导致连不上。
Makefile 根据需要启动的服务类型添加了两个target:
start_test:local_config
DJANGO_SETTINGS_MODULE="hydra.settings" python manage.py runserver 0.0.0.0:19010 --noreload
start_celery-hydra_upload-1_test:local_config
DJANGO_SETTINGS_MODULE="hydra.settings" PYTHONPATH="." /data/home/work/hydra/.venv/bin/celery worker -A async_tasks -Q hydra,hydra_loan_notify,hydra_repay_notify -P eventlet -c 200 -l info --
logfile=/tmp/celery.log --without-heart
启动hydra:分别传入不同的RUN_CMD来启动两个服务,例如:make start_test
应用接入方式
配置修改
总体方式是——根据该项目依赖的外部服务,将配置修改为通过环境变量读取,由于之前的不规范,大部分项目都涉及如下三个文件的修改:
(1)Makefile 增加 docker_config,以hydra为例:
(2)修改tools/config_templates/gen_config.py
(3)修改tools/config_templates/local_settings.py.tmpl 配置模板里面全局替换一下
项目根目录添加启动脚本:
entrypoint.sh 原因:部分项目需要在启动之前先生成配置文件
根据不同项目框架 操作不同:
django项目:
#!/bin/bash
make docker_config
python manage.py runserver 0.0.0.0:8080 --noreload
flask项目:
#!/bin/bash
make docker_config
gunicorn -k gevent -c gunicorn_dev.py yin.app:app
容器化现存问题
日志采集
需求:
(1)持久化存储
将日志文件持久化存储,按hostname区分。一个pod里面通常会有小到几个大到几十个容器,统一把日志挂载到其中的stubs容器中,然后再挂载的pv上面。
缺点:
1.查看不方便,动态pv不能根据名字区分出来是哪个deploy的日志。
2.多个replicaSet副本的日志都绑到了同一个目录下,文件众多,查找不便。
备选方案一:阿里云logtail(收费)
备选方案二:efk
备选方案三:graylog
当前采取的方式是:
在stubs容器中安装了ssh服务,然后通过rsync去同步case日志。
在释放资源之前,利用kubectl cp 持久化存储服务日志。
去除依赖
目前自动化体系,对基础库的每日构建、stubs更新等依赖严重。