本文讲的是在容器部署上Docker不一定最佳,【编者的话】很多时候,大家都把Docker跟容器等同起来,它确实能够解决运维中众多的问题,如:APP运行环境打包、容器移植性高、提供分层文件系统等特性;但是还有很多容器技术被忽略了,如LXC;而在某些特定环境下,Docker确实是有它的软肋,下文就是阐述这个问题的。
这是我去年在LinuxCon Europ的一个演讲的文字版。因为现在还没有放出视频,所以我很高心你们能看我演讲的PPT。
问题
假设你的是个运维人员管理着大规模的自动化的运维项目,这个项目可能包括几百个甚至几千个容器,你负责他们的正常启停与运行;开发人员将程序调通,然后一股脑的将运维它们的任务全部丢给了你,你负则维护他们的正常运行、服务可用与安全;而容器里面跑着什么服务都不是你关心的。当然,你可能会有个Git仓库来保存生成容器的脚本,一个Docker Registry来保存最新push的容器更新;你并不需要掌控一切细节。
更进一步假设,你管理的这些容器都运行web服务器,而这些web服务器都是Apache,因为维护Apache是你擅长的。你的开发人员使用各种语言来编写应用——Python、Ruby或者PHP,但是他们都会最终用Apache来承载,因为你的开发人员相信你能够将Apache管理得很好,你能够使各种应用保持稳定的运行,因为你精通Apache的运维。
但是,经常你需要面对一些问题,包括打一些系统安全补丁,如:SSL库的漏洞、glibc的安全漏洞等(是否似曾相识?),那你改如何应对呢?
没有容器的时代如何打补丁
打个比方,你负责修补系统中的OpenSSL与glibc的安全漏洞;作为运维人员,必须在系统遭受实际上的攻击或者造成实质的损失之前修补系统。如果应用没有运行在容器中,那么你必须有值得信奈的“安全补丁更新源”,通过这些源来下载安全补丁;然后通过专业的运维工具或者自动化运维工具来自动的为你的系统安装补丁。
总之,你可能会焦急的等待安全更新的发布,然后画上几分钟时间来更新系统。
现在呢?
随着容器时代的到来,应用打包、打包部署应用程序以及应用的依存关系跟踪就变成了繁重而恐怖的差事;因为用容器你可以将一个应用运行所依赖的所有上下文都打包到一个容器镜像中,并为每个服务部署一个容器;而且不用担心在一台物理机上部署不同的服务会有什么问题。
这样非常好,能解决很多棘手的问题,如:开发人员对不同的应用程序MySQL的配置不一样,那可以为不同的需求的应用程序打包到不同的容器中,它们有自己单独的MySQL配置,单独的库文件,单独的二进制文件,并且单独运行,互不干扰——问题解决!而且,当下,存储非常便宜,容器运行的开销又十分的小,所以容器非常的好。更比如,如果需要对应用所依赖的MySQL升级版本,运维人员只要重新构建(rebuild)容器,并将其替换掉以前的就行。
但是,现在我们面对的补丁是系统级的,你需要重新构建所有的容器。
给GlibC打补丁?
需要重构所有的 @Docker 镜像?
—
Josh Long (龙之春) (@starbuxman)
February 19, 2016
所以,为了解决问题,你必须重新构建所有的容器,可能有几百,几千个。在一般情况下,你必须熟悉每个编译链,知道每个容器的版本,精确的知道漏洞会影响到哪些应用,有自动化的补丁工具去编译与部署补丁;并且在完成补丁后,有好的文档指导你如何正确的启动或重新部署相关的服务,而不需要开发人员的介入,因为他们可能请了病假不在或者正在休假或者正好不在公司。
当然,其实我们就是这么工作的对吧?
这是容器本身的问题吗?
当然不是,问题的根源不是你使用容器,或者是容器技术去部署服务;问题是所有人都教你用一成不变的方式使用容器(注:指使用Docker),而恰好从运维人员角度出发,这个方法是彻底错误的,这个不是一般的错误,而是彻底的错误,我想你可以把这个错误称为——Docker误用。(注:作者认为大家把容器跟Docker划等号是错误的,至少在运维层面上。)
这是个坏消息,但是好消息是存在更好的,更优雅,更干净的方法来处理这个问题,使你的运维生涯更轻松些,当然可以使你跟开发人员的关系更友好。
什么是好方法?
用这个方法你可以更简单,更朴素,也不是很刺激的使用容器——总之,这是个更好的方法。
定义核心平台
所有公司都必须花费精力去选择把自己的服务或者产品部署到一个特定的Linux发行版上;可能你使用一个特定的或者同时使用多个发行版版Linux。我猜你会同时使用:最新的Ubuntu LTS、最新的CentoOS与最新的Debian。不管你如何选择,你必须在这些平台上定义一个“最小化的安装包”集合(就是核心平台),比如:C运行库、Shell、init System、coreutils、NTP……等等组件,这个单子可能有超过100个核心系统组件,你必须要保证他们的安全正常运行;当然跟你一起工作的程序员才不会关心这些,他们认为这些都是理所当然的事情。
当然没有理所当然的事情,这一切都要感谢那些制作安装包与分发包提供商,以及他们不知疲倦的工作,你才能将补丁及时的应用到系统中。
部署你的核心平台
将这个核心平台部署到你的物理机器上,在你每个要部署容器的平台上部署这个核心平台;你最好使用自动化工具去部署,这样会容易很多,你不需要手动登陆到每个机器上去完成工作。
把容器部署到OverlayFS上
OverlayFS 是一个union mount文件系统(注:就像汉堡包一样,实现了一个分层的文件系统,每一层都可以是不同的文件系统),它被包含在最新的Linux Kernel版本中,OverlayFS可以带来如下好处:
- 它使用一个只读的基础文件系统(base filesystem)与可以读写的上层文件系统(overlay)分离;
- 写入操作只会发生在上层的文件系统中,不会影响到基础文件系统;
- 使用 opaque directories技术隐藏了基础文件系统中的数据;
- 基于一个基础文件系统可以创建多个不同的上层文件系统的union mount(可以把union mount理解为容器的镜像);
- 只需对基础文件系统打补丁就能快速的应用到所有依赖于它的union mount上。
这些特性使得OverlayFS技术与LXC配合起来能够发挥更大的威力。你可以定义一系列overlay的文件夹——每个文件夹中运行一个容器(注:这里不是docker容器,而是LXC容器)——这些容器共享一个基础文件系统——你的物理OS的root文件系统(root filesystem)。
这样就使得所有宿主机上的所有文件对容器变得只读,而且作为容器的root文件系统;当然如果用opaque directories技术可以影藏这些内容;所有容器运行时的更改或者产生的数据都只会存在于overlay文件系统中。如果你要删除这个容器,只需要删除overlay所在的文件夹就行。
以下是一段LXC容器的配置示例:
# For additional config options, please look at lxc.container.conf(5) # Common configuration lxc.include = /usr/share/lxc/config/ubuntu.common.conf # Container specific configuration lxc.arch = amd64 # Network configuration lxc.network.type = veth lxc.network.link = lxcbr0 lxc.network.flags = up lxc.network.hwaddr = 00:16:3e:76:59:10 # Automatic mounts lxc.mount.auto = proc sys cgroup lxc.rootfs = overlayfs:/var/lib/lxc/host/rootfs:/var/lib/lxc/mytestcontainer/delta0 lxc.utsname = mytestcontainer
注意到配置中将容器的lxc.rootfs配置项指定为OverlayFS,目录为/var/lib/lxc
的子目录下;如果让容器与宿主公用同一个root文件系统,只需要将/
mount bind
到/var/lib/lxc/host/rootfs
目录即可(注:命令mount --bind / /var/lib/lxc/host/rootfs)。
至于程序员们想在Overlay层放什么东西——PyPl、Ruby Gems、NPMs等等,这是他们的事情,而root文件系统是运维人员管理的范围。
自动化、自动化、自动化
当然,我必须反复强调,虽然是显而易见的,我们必须自动化去部署容器。你可以任意选择工具去实现,但是我还是要推荐Ansible——它非常适合LXC容器的部署。
这是一个Ansible的playbook脚本示例,它创建了100个基于宿主机root文件系统的容器
- hosts: localhost tasks: - name: Create a local bind mount for the host root filesystem mount: name: /var/lib/lxc/host/rootfs src: / opts: bind fstype: none state: mounted - name: Create a template container using the host root lxc_container: name: host state: stopped directory: /var/lib/lxc/host/rootfs config: /var/lib/lxc/host/config container_config: - "lxc.mount.auto = proc sys cgroup" - "lxc.include = /usr/share/lxc/config/ubuntu.common.conf" - name: Create 100 OverlayFS based containers lxc_container: name: host backing_store: overlayfs clone_snapshot: true clone_name: "mytestcontainer{{ item }}" state: started with_sequence: count=100
当然,这里要运转良好必须要让研发人员与运维人员都要会写Ansible配置文件,这是非常好的事情——研发与运维用相同的工具来维护与开发系统;而且如果你的开发人员会写Dockerfile就不会对Ansible感到难学了。
那么有什么改善呢?
还是最开始那个任务,更新数百个或者几千个容器的glibc组件,你在LXC容器中只要两步:
- 更新宿主机的glibc;
- 重启容器。
只有这些!这些就是你为几百个容器更新glibc所要做的所有事情。当容器重启,LXC会remount容器的OverlayFS,因为所有的容器都构建在一个root文件系统下,所以会使得这些补丁迅速得到生效。
在Ubuntu系统你甚至可以自动来打补丁:
# /etc/apt/apt.conf.d/50unattended-upgrades // Automatically upgrade packages from these (origin:archive) pairs Unattended-Upgrade::Allowed-Origins { "${distro_id}:${distro_codename}-security"; };
# /etc/apt/apt.conf.d/05lxc DPkg::Post-Invoke { "/sbin/service lxc restart"; };
所以,更新所有容器只在一瞬间——没有重建镜像、没有重新部署——这些都没有。打包容器运行环境固然重要,也很酷,但是要看在什么场景下使用了。(注:作者的观点是docker在容器的移植方面有很多优势,但是在特定场景下不一定最佳。而LXC能够使得容器共享宿主的很多用态库——glibc,但是在移植性上又存在很大的问题,所以如何选择只能见仁见智了。)
原文链接:Containers: Just Because Everyone Else is Doing Them Wrong, Doesn't Mean You Have To(翻译:肖劲)
原文发布时间为: 2016-02-24
本文作者:amwtke
本文来自合作伙伴DockerOne,了解相关信息可以关注DockerOne。
原文标题:在容器部署上Docker不一定最佳