生产环境docker历险记

首先感谢产品狗jinkey(https://jinkey.ai/)把他的服务器托付给我部署应用,下面讲一下最近在生产环境使用docker惊心动魄的经历。

docker是双面刃

你爱的人往往伤你最深,docker的环境隔离,快速交付,持续构建支持等优势往往让人爱不惜手,但是越依赖于docker的时候,你却会发现越来越无助,因为高级的资料真的不好找。

环境隔离分析

首先我们来看一下docker环境隔离的实现原理,其实本质是利用linux系统的进程命名空间隔离进程。本身linux系统只有一个命名空间,每个命名空间下面可以有很多进程树。但是当新建了一个命名空间之后,该命名空间下的进程不能知道其它命名空间的进程,所以达到了隔离进程的目的。但是注意命名空间同级进程可以知道命名空间下的进程,这也是我们在系统输入ps aux可以看到docker容器跑起来的进程。

参考:http://blog.csdn.net/tongtest/article/details/53440437

使用了uts namespace隔离网络,每个容器使用一个主机名。

http://www.infoq.com/cn/articles/docker-kernel-knowledge-namespace-resource-isolation

docker是使用虚拟文件系统,然后主要有只读文件系统,然后读写文件系统组成,同一个image运行的容器共享一个只读文件系统,新增的文件则写入各自的读写文件系统。

http://www.thinkphp.cn/topic/33316.html

传统模式下应用隔离

一般来说,在不用docker的情况下。多个应用之间防止相互读写文件一般使用不同用户或者用户组来限制权限。

然后,一台主机上运行不同版本的框架也有很多解决方案。例如python使用virtualenv,nodejs依赖可以安装在项目目录下等。

docker带来的只有快速部署吗?

后端的同学都知道,当我们要部署一个应用到一台新的服务器是一件非常痛苦的事情,因为要配置很多东西,并且不同操作系统安装配置环境的方式也不一样。

但是docker的到来,我们只需要docker pull 镜像名字:版本,就等同于配置好了环境,无论当前你服务器是什么系统。

然后只需要一句命令,docker run 镜像名字:版本 cmd即可跑起一个应用,是不是很诱人。

docker让自动部署变得更加简单

我们知道我们应用是跑在容器里面的,然后容器基于镜像。然后镜像等于平时系统的快照。我们只需要把应用打包成一个镜像,然后就可以快速在各台机器运行起应用。

然后构建镜像,我们可以基于一个叫做Dockerfile的文件。所以我们可以通过git钩子,在发现push代码之后,自动拉取最新代码,通过Dockerfile构建出一个镜像。持续构建就是这么简单。

docker让扩容门槛降到最低

想想当我们一台服务器无法支撑起我们应用的时候,我们用上十几台机器部署我们应用的时候,每当我们更新或者回退版本的时候,我们就需要依靠复杂的构建部署系统来实现这一操作,在机器越来越多的时候根本不能依靠人力维护。

docker1.2版本都可以通过内置的docker swarm解决跨机器运行容器的问题,集群部署仅需两条命令。

自动重启异常的应用

相信大多数后端的同学都会遇到应用僵死,要重启进程才能恢复访问。但是如果每次出问题我们才去手工重启就需要专门编写监控系统通知我们。而我们使用docker部署应用的时候,可以使用它内置的健康检查,通过间隔时间段执行shell脚本判断应用是否处于正常状态,如果不正常则自动重启。

应用灾难恢复

每次接手运维工作的时候,我都会想加入机器出现故障,在全新系统上恢复到正常场景要多久时间。对于优读Uread(http://aiuread.com),由于已经把90%的微服务纳入自主研发的自动运维平台,并且尝试过故障预演,所以有把握在20分钟内恢复正常,主要消耗时间仅仅是网络开销而已。

我们来分析一下为什么用docker可以如此快速恢复系统,首先在新系统我们由于基于centos7,安装docker仅需要一条命令,然后自主研发的运维系统基于python,并且依赖的库没依赖过多的c扩展,无障碍pip安装。然后该系统提供了一功能,一键重新拉取所有项目的代码并且构建出一个一个镜像,然后创建service,应用就这样子跑起来,整个流程非常简单。

其实就是省去我们配置环境的时间,这个在多机环境下影响更大。

docker是降低微服务成本的好工具

微服务优点是让开发变得更加简单,每个服务仅仅关注解决一个很小的点。而微服务的挑战主要来自于部署和相互调用。

用过docker都知道,部署是那么的简单,所以微服务50%的难题就此解决。

警惕pull镜像过慢

由于默认镜像仓库在国外服务器,所以有可能pull几十m的镜像就需要几个小时。

当然国内很多docker服务商都提供了镜像加速功能,具体那家就自己去尝试。

小心镜像体积过大

一开始,由于没深入了解docker,仅仅停留在能用的阶段。但是由于优读uread背后依靠大量的机器学习服务,这些服务都带着大量的模型,并且编译过程有点复杂。直接结果是一个镜像就占了2,3G硬盘空间。所以突然某一天愉快的吃着晚餐的时候,就受到服务器异常的提醒,上去一查发现磁盘占用100%。

我们知道镜像油Dockerfile构建而成,而每一条Dockerfile指令就会生成一层镜像,注意,每多一层镜像,只会增加体积,哪怕这层是删除文件操作。

为了节省镜像体积,那么尽可能减少构建层数,并且每一层最后的指令要删除不必要的文件。然后我们的基础镜像也要选择一个轻量级的,例如alpine。下面给出一个demo:

FROM yubang/base_image:v20170829

MAINTAINER yubang(yubang93@gmail.com)

RUN apk add --update \  
python \  
python-dev \  
py-pip \  
mysql-dev \  
&& mkdir -v ~/.pip \
&& echo -e "[global]\ntimeout = 60\nindex-url = https://pypi.douban.com/simple" > ~/.pip/pip.conf \
&& rm -rf /var/cache/apk/*

经过这两步优化之后,原来2,3g的镜像变成1g,这么多镜像瞬间节省了大量的空间。

docker是一个不断吞噬磁盘的家伙

在维护uread自动运维系统的这段时间内,由于应用不断迭代,所以生成大量容器和镜像。然后在清除掉旧的镜像和容器之后,硬盘空间竟然没有释放。这个时候知道麻烦大了,不能释放空间,就意味着服务器在某一天就会崩溃了。

至今为止没有找到解决方案,所以只好用暴力的方法,删除/var/lib/docker目录下的文件,重新初始化,当然这个方案是由于我们上集群并且可以快速恢复,才采用的一个折中方案。

docker镜像仓库

由于我们集群环境,所以我们需要一个镜像仓库保存代码构建成的镜像。并且由于使用docker swarm做集群的时候,并不会自动拉取镜像,所以就需要各台服务器自动去拉取镜像回来。(这个坑暂时没有找到解决方案,只能靠自己的脚本去折中解决)

所以这个仓库最好放在内网,因为速度快。

基于docker的运维之路依旧漫长

虽然现在uread基于docker,成功的扛住大量的微服务。但是随着更多的微服务接入,特别是机器学习这种庞然大物,分配调度策略亟需优化。

还是那句老话吧,业务驱动技术,当遇上技术难题的时候,总能找到解决方案,没有现成的方案借鉴,没有丰富经验,那么就用会的东西拼凑出独树一帜的技术栈。