Docker为什么 (一)

jopen 4年前

原文  http://dockone.io/article/357

引言

作为最近一年最火热的服务端技术(也许没有之一),Docker引发了大量的关注和讨论,我在从事持续集成服务平台方面的工作,因此也在关注和了解的以后尝试将现有系统进行了docker化的改造。这个过程加深了认识和理解,本文就是对这些工作的一个总结。

后面的讨论假设你知道Docker是什么,如果不了解,可以去google一下再回来。

理解特点

首先明确技术要点,Docker技术就是基于容器的虚拟化技术,相对于其它虚拟化技术,它的特点是:

  • 轻量级:单机可以轻松支持上百container,让各种个位数虚拟化的方案相形见绌
  • 快速就绪:一秒以内启动,即使是以资源快速就绪著称的青云IAAS也无法相比
  • 弱安全:docker能够对多种OS资源进行隔离,但是它本质上依托于内核,因此所有的内核漏洞都是docker的致命伤

上述三个特点都不是实现上的差异,而是设计方案的先天结果,因此上述结论会在很长一段时间内有效。

理解上述这些特点非常重要。

很多刚接触docker的人会把它和虚拟机做类比,然而这种类比和docker的最佳实践场景有所不同,一个docker容器不像是一台虚拟机,而更像是一个服务单元,如果要类比,反倒更像进程。

当然,docker容器从技术层面上看确实也就是一个进程,然而这不是关键,“轻量级”这个特点带来的其实是服务器软件工作方式的回归。不妨来看看服务器软件的工作单元的抽象工作方式。

服务器软件的抽象模型是有限状态机,不难想到,在这类场景下,服务单元最终要做的,无非就是下面这些事情:

  1. 从网络得到请求
  2. 结合内部状态进行计算
    2.1 访问独有或共享的内存
    2.2 访问独有或共享的文件系统
    2.3 访问其它服务单元
  3. 通过同步或者异步的方式将计算结果由网络反馈出去

最为复杂的部分当属第2步,不过如果考虑到服务器软件需要进行水平扩展,业务也会逐渐分布化,我们会想到一句话,“不要通过共享内存进行通信,而是要通过通信共享内存”,所以,2.1. 可以改为访问独有的内存

实际上,由于有2.3的存在,2.2 也可以简化为访问独有的文件系统,不过文件系统本身的IO性能很差,一旦到了这个层面,往往是分布式和水平扩展的压力不大的场景,所以可以保留。

除了功能性需求,我们还有管理和控制需求,包括:

  • 可见性——服务单元应该通过某种方式暴露自己的运行以及资源使用情况
  • 可操作性——服务单元应该有某种渠道接受和响应控制信号

这样,我们可以大致勾勒出一个“服务单元”需要具备的能力列表:

  • 独享内存访问
  • 独享磁盘访问
  • 对网络的访问能力
  • 实时暴露自身状态和资源占用
  • 实时反馈运行情况
  • 接受和响应外部控制信号

说了这么多显得很罗嗦,因为这显然就是一个普通的操作系统进程要做的——结合cgroups和 proc 文件系统这样的技术,现代linux可以将进程对内存、网络、文件系统的访问隔离开,并能实时掌握进程的状态;通过标准输出和syslog,进程可以持续 反馈运行情况;通过信号量,进程有机会对外部信号作出反应......那为什么还要docker?

因为传统虚拟机的传统进程做的太多了。

举几个例子:

  1. 应用如何变成daemon,很长一段时间都是被应用系统开发人员忽略的问题,这个问题虽然有解决办法,但是各种语言和框架的办法并不统一,如果交给运维人员,千差万别的脚本会带来很高的维护成本,而这本来应该是进程管理的事,和具体的技术栈无关。
  2. 我们常常要从安全角度考虑问题,一些铁律也是这么出现的,比如“不要使用root”,然而对线上服务器而言,它常常是单一进程,其它帐号根本就用不到,所以会出现这样的怪事:把要做的事分为root和admin,然后运维脚本是 sudo 开头的
  3. 由于可以随意登录,并进行变更,profile的load过程很复杂并且容易引入不确定性,所以大家形成共识——不用环境变量来约束和客 制化软件的行为(虽然环境变量本来就是做这个的)。这样的后果是,c/c++、java、python、ruby各种社区都形成了自己的配置文件习惯用 法,更进一步,java社区曾经有个趋势,把配置文件本身都做的快和代码没区别了(复杂度相当,也是在编译时而不是运行时确定实际参数值)

而在docker时代,这些都不是问题——

  • container自己就是个daemon,应用系统进程放在里面不用再考虑daemonize。
  • container是独占的,因此你完全可以仅用root帐号做事,并且不会破坏授权模型。
  • 对于运行中的进程,可以很简单的使用docker exec命令获取其当前的环境变量,而且Dockerfile和docker-compose.yml都可以设定环境变量,这样,各种千奇百怪的“配置文件”的需要就可以大幅度简化。

不太严谨的说,Docker其实是重新定义和简化了linux,它是在针对服务器的场景进行裁剪,把核心问题解决的更干净纯粹,剥离枝节,使之更加贴近服务器环境的需要。

从这个角度出发再看看之前提到的三个特点,会觉得这是很实用的设计:

轻量级

表面上看,轻量级容器更容易启动,但更重要的是,这是对 linux 本身设计的某种回归,所以虽然简单却并不简陋。如果我们实践中感到不便,也许正应反思是否把运维做复杂了。

快速就绪

不难理解,由于本身仅仅是一个普通的进程,我们可以很快的启动容器,然而问题不止于此,个人理解,这也是一个“回归”——这是“以进程为计算单元的协作方式”的回归,all in one 其实并不是很好,每个计算单元做的少而精才是 linux 的理念。

当然,传统 linux 脚本组装的少而精并不能应对web/internet,docker容器的少而精实际上是在另一个角度进行了回归——让每个容器都能专注一个方面,通过容器的快速建立/释放来协调资源,通过标准规范的容器间协作来搭建复杂的系统。

弱安全

这一点其实是一个推论——如果要发挥系统的计算能力,虚拟化应该尽可能的薄,然而过于单薄将对系统的稳定性和安全性造成影响,docker做了一个trade-off——它将安全性交给了IAAS系统,而专注做好稳定性。

docker自身显然还是要依托于物理系统或者xen/kvm这样的虚拟机,而这些系统的安全边界比较清晰,所以docker没有必要再背起这 么重的负担,唯一需要考虑的是容器间的资源独占导致系统稳定性降低,因此重点不在多租户支持,而在故障隔离,后者其实就是资源隔离。

理解了docker的特点,我们才能进一步分析和讨论docker到底能带来什么。

</div> </div>
</div>