关于 docker 的一些总结和观点

docker 背后的技术支撑

1. namespaces 资源隔离

The purpose of each namespace is to wrap a particular global system resource in an abstraction that makes it appear to the processes within the namespace that they have their own isolated instance of the global resource, 即每个 namespace 实例拥有一份隔离的全局资源, 这些全局资源目前包括了:

  • Mount Namespaces (CLONE_NEWNS, Linux 2.4.19): 文件挂载, 通过对 Mount 隔离来支持文件系统隔离. 酷壳上有个对 Docker Mount Namespace 的模仿示例.
  • UTS namespaces (CLONE_NEWUTS, Linux 2.6.19): 主机名, 域名. Docker中利用 UTS namespaces,每个镜像基本都以自己所提供的服务命名了 hostname, 而不会对宿主机产生任何影响.
  • IPC namespaces (CLONE_NEWIPC, Linux 2.6.19): 信号量, 共享内存, 消息队列.
  • PID namespaces (CLONE_NEWPID, Linux 2.6.24): 进程ID, PID namespaces 会组成一个树状结构, 父节点可以影响它的后代, 但是不能影响它的父节点或者兄弟节点. dockerinit 进程就是一个 PID namespaces 中的父节点, 它如同 Linux 中的 init 进程一样, 是当前 namespace 下的 1 号进程, 需要负责当前 namespace 下的进程监控和资源回收.
  • Network namespaces (CLONE_NEWNET, Linux 2.6.24, 2.6.29): 网络设备, 协议栈, 端口号, 防火墙等. 一般来说, 物理网络设备都在 root namespace 中, 然后通过创建 veth pair 来实现 namespace 的通信. Docker 容器的网络通信使用了IPVLAN技术来保证性能.
  • User namespaces (CLONE_NEWUSER, Linux 2.6.23, 3.8): 用户和用户组, 与 PID namespaces 类似, User namespaces 也是一个树状结构, 用户在子节点中拥有全部权限, 但是在父节点和兄弟节点中没有任何权限, 哪怕是 root.

从 Linux kernel 2.4.19 引入 mount namespaces 开始, 直到 Linux 3.8, namespaces 功能日趋完善, 虽然还有一些例如 user namespaces 安全性等问题, 但 "is now functionally complete".

2. cgroups 资源限制

"本质上来说,cgroups 是内核附加在程序上的一系列钩子(hooks), 通过程序运行时对资源的调度触发相应的钩子以达到资源追踪和限制的目的".

cgroups (Control Groups) 技术提供的功能包括了:

  • 资源限制 (Resource Limitation): cgroups 可以对进程组使用的资源总额进行限制, 例如 memory.
  • 优先级分配 (Prioritization): cgroups 可以分配的 CPU 时间片数量及硬盘 IO 带宽大小, 实际上就相当于控制了进程运行的优先级
  • 资源统计 (Accounting): cgroups 可以统计系统的资源使用量, 如 CPU 使用时长, 内存用量等等, 这个功能非常适用于计费.
  • 进程控制 (Control): cgroups 可以对进程组执行挂起, 恢复等操作.

cgroups 的资源控制系统即 subsystem, 目前支持的类型有:

  • blkio: block io
  • cpu
  • cpuacct, cpu 的统计
  • cpuset: cpu + mem
  • devices: 设备的访问
  • freezer: 挂起/恢复 task
  • memory
  • perfevent: 做性能测试
  • net_cls: 允许TC(traffic control)识别 cgroup 网络包

cgroups 的模型由多个 hierarchy 组织: 每个 hierarchy 可以限定一个或者多个 subsystem 资源, hierarchy 下包包含多个 cgroup 节点, cgroup 即任务组, 可以包含一个或者多个子系统任务 task.

3. 文件系统

docker 需要 Linux kernel 的文件系统支持, 目前可以支持的文件系统驱动包括了: AUFS, btrfs, dm(device mapper), OverlayFS 和 ZFS.

各个文件系统的特点如下:

  • AUFS, Another/Advanced Union FS, 基于文件, 可以将多个 directory mount 到同一个虚拟文件系统, 多个 directory 可以认为多个层, 每层的 rw 权限不同, 从而为 docker 加载多层 image 提供支持.
  • OverlayFS, Linux 3.18 之后开始支持, 与 AUFS 比较类似, 只提供了两层, 但是每一层又可以是另外一个 Overlay, 所以同样也可以支持多层. 据说 OverlayFS 的代码实现比较简洁, 所以被优先 merge 到 Linux trunk 中 (相比 AUFS).
  • Device Mapper, Linux 2.6.9 之后支持, 基于块的存储, 通过 Thin Provisioning Snapshot 技术来实现分层映射, 不过按照陈皓给出的观点, 这个东东还不成熟, 不建议生成环境使用.
  • btrfs, 号称下一代 cow 文件系统, 基于文件, 但是可以像 Device Mapper 一样直接操作设备块.
  • ZFS, 新文件系统, 完全抛弃了 Volumn 方式的文件管理, 而是采取"存储池"的概念来管理存储设备.

对每一种文件系统的具体分析还有 benchmark 都可以参考这里, 摘抄一段 benchmark 结论:

  • AUFS 在读的方面性能相比 Overlay 要差一些, 但在写的方面性能比 Overlay 要好.
  • Device Mapper 在 512M 以上文件的读写性能都非常的差, 但在 512M 以下的文件读写性能都比较好.
  • btrfs 在 512M 以上的文件读写性能都非常好, 但在 512M 以下的文件读写性能相比其他的存储驱动都比较差.
  • ZFS 整体的读写性能相比其他的存储驱动都要差一些.

可以有个简单的初步结论: 比较适合 docker 生产环境的是 AUFS 或者 OverlayFS, 具体采用哪个可以看 Linux 版本的支持.

  1. namespaces 对资源隔离, cgroups 调整资源的权重, 底层文件系统有支撑, 这几点足够支持容器的基础功能.

docker 的优势

从 docker 的核心技术 Linux namespaces, cgroups 技术就可以看出, docker 容器技术是基于插桩, hook的手段在 kernel 中做的隔离, 轻量级, 性能必然很高, 但是相比于虚拟机技术, 隔离性会差一些.

docker 的技术实现为它带来了两个显而易见的优点:

  • 灵活, 可以轻松的在 runtime 调整 cpu, memory 这样的硬件指标.
  • 插桩的实现方式, 带来接近原生的性能, 即虚拟化的开销减少到几乎最低.

单单这两点来说, 几乎已经足够支撑 IaaS 基础了. 然而, 这并不是重点.

docker 充分利用自身的优势, 重新定义了发布 pipeline, 为开发/运维带来了更多的想象空间, 这才是 docker 得以流行的真正原因:

以前我们发布产品, 一般都需要运维同学配置基础的发布环境, 根据不同的需要, 安装配置例如 nginx, tomcat, memcached, mysql 等基础组件, 开发同学再发布打包好的二进制程序到环境, 通过脚本热加载或者重启. 环境部署和程序发布是天然割裂的, 每一次环境的变更(扩容/缩容/切换), 很容易碰到部署与配置的问题, 严重依赖运维工具的成熟度和负责同学的细心耐心.

现在不一样了, 利用 docker 技术, 我们可以直接发布镜像(image), 包括各种安装好的组件, 环境配置, 打包好的二进制程序一起, 在 OS 级别上做发布, 而非进程级别. 环境和程序一起作为发布的对象, 流程简单清晰, 开发/生产环境统一, 易于标准化, 极大程度减少了出错的环节. 这也是 docker 这个名字的真正含义 —— "集装箱", 标准化, 环境无关.

当然, 缺陷也是有的, docker 的技术实现限制了它的 container 必然是 Linux. 现在很多在 Mac OS 或者 Windows 下跑的 docker 也是先利用虚拟机技术跑一个 Linux, 再在此基础上运行 docker.

database in docker

最近在看到两篇文章讨论是否应该在 docker 容器中跑 db, 原文链接在这里:

《论数据库容器化的目标和价值》

《数据库不适合Docker及容器化的7大原因》

两篇文章的意见相左, 各有观点, 感兴趣的同学可以都读一下.

这里给一点个人拙见:

  1. docker 带来的装箱能力很 nice.
  2. docker 本身的性能是 OK 的, 就这点来说没什么问题.
  3. 真正 matter 的容器本身的稳定性和安全性, db 作为线上服务的最后一层, 是产品命脉所在, 它对稳定性和安全性要求最高.
  4. docker 技术很热, 但大规模应用到生产环境的不多, 很多还是在摸索实践, 可以再等一等, 等技术的完成成熟落地.
  5. 坚持为产品负责的心态.

延伸阅读