将你的应用容器化随着环境的就绪,我们可以把注意力转到程序的容器化上来。 我们更倾向于thin container能准确地做一件事。例如一个unicorn(Unicorn是一个Unix和局域网/本地主机优化的HTTP服务器)master和工作线程服务web请求,或者一个Resque(Resque使用Redis创建后台任务,存储进队列,并随后执行。它是Rails下最常用的后台任务管理工具之一)工作线程服务一个特定的队列。thin container允许细粒度的缩放比例(一般是指远程方法调用的接口的颗粒大小),以满足需求。例如,我们可以增加检查垃圾邮件攻击响应的 Resque工作线程的数目。 我们发现采取一些标准约定对于在容器中的代码布局会有用处:
我们还确立了一些容器化git repo(repo封装了对git的操作)的约定:
我们用一个简单的Makefile驱动建立,也可以本地运行。我们的 Dockerfile 看起来像这样:取消编译阶段的目标是产生一个是即刻准备运行的容器。Docker关键的优势之一就是在容器启动时超级快速启动而不被损坏因为额外的工作。为了实现这一目标您需要了解您的整个部署过程。 举几个例子:
在本例中,编译阶段包括以下的逻辑步骤:
为了保持这一公告(邮件)的大小合理,我们简化了一些细节。密钥管理是一个主要的细节,我们还没有在这里讨论。不要将其登记入源码管理。我们已经获取了用来加密密钥的代码,致力于该主题的博客帖子很快就会来了。 调试和细节运行在容器中的应用程序和容器之外的表现绝大多数情况是相同的。此外,你的大多数调试工具(例如:strace,gdb,/proc/filesystem)是运行在Docker所在的host上。还有大家熟悉的工具nsenter或nsinit,可以用他们挂载到一个运行中的容器里进行调试。 docker exec,作为docker 1.3.0版本中提供的新工具,能够被用来向运行的容器中注入进程。但是,不幸的是,如果你注入进程需要root权限,你还是需要通过nsenter,有些地方的情况可能不会像人们预料的那样。
进程分层尽管我们运行轻量级的容器,仍然需要初始化进程(pid=1)从而与监控工具,后台管理,服务发现等做到紧密集成,也能给我们细粒度的健康监测。 除了初始化进程,我们在每个容器中添加了一个ppidshim进程(pid=2),应用程序进程的pid=3。由于ppdishim进程的存在,应用程序没有直接继承自初始化进程,避免它们以为自己是后台守护进程,造成错误的后果。 最终的进程层次是这样的: Signals如果你在使用容器技术,你很可能会修改现有的运行脚本或写一套新的脚本,其中包含了docker run的调用。默认的情况下,docker run会代理signal到你的应用程序,所以你必须理解应用程序是如何解读signal的。 标准的UNIX做法是发送SIGTERM去请求有序地关闭一个进程。为了确保应用程序遵守这个惯例,Resque使用SIGQUIT来有序地关闭进程,SIGTERM来紧急关闭进程。幸运的是,Resque可以被配置使用SIGTERM来优雅地关闭进程。 Hostnames我们选择容器名称来描述工作负载(例如 unicorn-1, resque-2),并结合这些主机名来进行简单追溯。最终的结果会是这样:unicorn-1.server2.shopify.com.我们使用Docker的主机名标签将结果传入容器中,这使大多数应用程序报告出正确的值。一些程序(Ruby)询问主机名来得到缩略名(unicorn-1),而不用询问预期的FQDN。由于Docker管理着/etc/resolv.conf 文件,我们当前版本不允许任意变更,所以我们通过LD_PRELOAD,重写了gethostname()和uname()的方法,并注入到library中。最终结果会是,监控工具发布我们想要得到的主机名却不用去更改应用程序代码。 注册和部署我们发现构建容器的过程中复制‘bare metal’的行为相当于一个不断调试的过程。如果你是理智的的,你肯定想自动化的去构建容器。 为了每个人都能push,我们使用github commit hook去触发一个容器的构建,构建过程中我们会提交状态日志来判定是否构建成功。我们使用git提交SHA到容器的“docker tag”,所以你能准确的看到什么样的代码版本被包含在容器中。为了更容易被脚本调试和使用,我们同样也把SHA放到容器里的一个文件中(/app/REVISION)。 一旦你的构建是正常的,你会想把容器push到一个中央的注册表仓库。为了提高部署速度和减少外部依赖我们选择运行数据中心中自己的库。 我们在Nginx反向代理(其中缓存了GET请求的内容)背后运行多个标准Python注册表的副本,如下图所示: 当多个Docker主机请求相同的镜像时,我们发现大型网络接口(10 Gbps)和反向代理,在解决”thundering herd“这样的代码部署相关的问题是很有效的。代理方法还允许我们运行多种类型的库,并在发生故障的情况下,提供自动故障转移。 如果遵循这个蓝图,你可以自动化去构建容器,并且安全地把容器存储在一个中央仓库注册表中,这些都可以融入你的部署过程。 在本系列的下一篇文章,作者将描述如何管理应用程序的秘密。 |