容器技术 - Docker 101

问题 & 缘起

做项目或产品的程序猿们肯定没少见过下面的这种文档,从更改记录中可以看到完成一个文档的艰辛。更要命的是一个文档通常只对应一个版本,一个平台,一种环境,一种配置。

The Problem

The Problem

在现代化和大规模的软件作业里,文档的问题是显而易见的,维护文档更是苦不堪言。特别是做大型软件产品:

The Problem

文档的问题其实反应了背后的工程问题 - 软件的安装和配置,还好有自动化运维工具,如 Ansible 来帮忙(见我之前的博客):

DevOps tool

但如何应对现代软件开发中多环境的问题:

The Challenge

所以这些工具并没有从根本上解决软件的安装和配置的复杂性,只不过将之自动化而已,而容器技术将之改观。

容器(Container)是什么

Welcome 2 Docker World

JVM vs. Container

先来看两个简单例子:
Hardware OS/Kernel JVM Java Applications

> java HelloWorld

Hardware OS/Kernel Container Engine Container Applications

> docker run mysql:5.7

第一个启动一个 Java HelloWorld 程序,第二个启动一个 Mysql 容器。

JVM Container
HelloWorld mysql: 5.7
Java byte code
Jar package
all Java dependencies
file system
container image (AUFS)
all runtime dependencies including OS libraries
java HelloWorld docker run mysql: 5.7
start a jvm process
JVM executes byte code
shared computation resources with other process
start a container init process
start application under this process
do NOT share computation resources with other process (isolated)

什么是容器呢?通过上面的例子可以看到容器有两面,一个是容器存储时的静态打包格式,另一个是它运行时的动态格式。静态格式就像 Java 的 class 文件,运行时由 JVM 执行操作,容器也一样,静态时以容器特殊的『image』形式存在,由容器引擎运行,从 image 产生一个容器(以 image 为蓝本)。但和 Java class 的 byte code 不同的是容器的 image 不只是上层的应用代码,而是整个文件系统,或者说运行环境,所以不只是跨语言,而且是自满足,不依赖基础环境或第三方库或其它应用程序,例如可以把 Java class,JVM,database 通通打包到 image 里。而运行时,和 JVM 不同的是,容器带来是系统级别的隔离,只共享操作系统内核,每个容器自带 CPU,内存,网络(这点上类似虚拟机 VM),JVM 没有提供这种隔离功能,同个宿主机上的各个 JVM 还是使用共同的资源。由于这个跨平台,自满足的特性,软件的安装和配置就消失了,image 下载后就能运行(这点上也类似虚拟机 VM)。

What is Container

namespace & cgroup

要想真正理解什么是容器,必须稍微了解 Linux 内核和 process,所有的计算,储存,通讯都是一系列系统调用至内核完成的『system call』,一个大致的新线程流程是,父线程通过 system call 让内核产生新的线程,内核保留父程序的上下文,准备初始化子线程的上下文,切换至子线程,操作权这时交到了子线程,子线程的逻辑开始真正运行,逻辑运行结束后,会向父进程发信号,内核回收子进程资源,操作权回到父进程,父进程继续执行接下去的逻辑。举个例子,当在 bash 里运行 ls 的时候,bash 的 process id 假设为 10,该 process 通过 fork()产生出一个新的 process,id 为 11,process 11 通过 execve()把当前的程序/bin/bash unload,接着 load 入/bin/ls,当 ls 运行结束后,process 11 通过系统调用 exit()通知 process 11 的结束状态(status code),内核这时通过 wait()唤醒终止运行的 process 10,process 10 收到 process 11 的 status code 并继续运行。这就是普通 process 产生过程。

Linux 启动后的第一个线程是『init』,其 process id 为 1,所有的程序运行都是通过 init 产生子线程来完成的,init 就是个上帝线程。容器的秘密就发生在 process 产生的时候。

Linux Process

Linux 内核通过『namespace』提供了资源隔离的功能,各种 namespace 对应于各种资源的抽象数据结构(共 7 种),内核通过这种结构来管理资源,有了 namespace,相当于代码的 package name,每个 process 有自己的资源视角,资源的使用可以单独定制。

在产生子线程时,可以通过参数告诉内核是否共享或为子线程产生单独的 namespace,容器的核心其实落实到这么一个 system call。

例如一旦新的 process 自带 PID namespace,脱离父进程的 PID namespace,其 process id 将变为 1,其创建用户为 root,这让它看起来就像是一个 init process,所以启动一个容器就像启动了一台新的 Linux 系统。

Namespace

Namespace

有了资源隔离还不够,还需要资源限制 - Linux 内核也提供了这种功能 『CGroup』,各种 CGroup 对应这各种资源的使用限制,与 Linux 的设计原则一致,CGroup 通过文件形式定义的。

CGroup

所以容器的本质就是一个具备资源隔离和限制的 procss,Namespace 和 CGroup 是容器的两大核心技术(Linux 操作系统提供),Docker Engine 利用这两大内核功能进行系统调用完成容器的产生,交互,删除等等工作。这两大功能 Linux 内核十年前已经提供,所以 Docker 基本上在现有的各种 Linux 运行毫无问题,没有依赖性的问题。当然和 VM 不同的是,这种资源隔离和限制是内核提供的『软』设置,从安全系数来讲是没有 VM 彻底的。

image

接下来是容器的存储格式『image』,Docker 采用 AUFS 来存储容器的 image,一种层级格式的文件系统,有点像 Source Version Control,每一层都是新的叠加,Docker 遵循 immutable infrastruture,每一层都是不可更改的(只读),只有运行时最外的一层可写,一旦 commit(把运行的容器发生的一切存储为新的一层),新的一个 image 在物理存储介质所谓的硬盘上诞生了,如果不 commit 成 image,所有的变更将随着容器的删除而消失。

Docker image 可通过 Docker file 来构建,该文件包含了一系列构建的指令(伴随着增加新的层级),类似 make/ant file 等编程构建机制,如上所述,当然也可以把运行的一个容器存为 image,背后的原理是一样的。

这种层级设计,带来了两大亮点,一是类似开发软件,不需要从头开始 build image,可以在使用别人的 image,例如可以在标准的 tomcat image 上定制自己所需的 tomcat image,软件安装和配置的最佳方案通过软件得到重用而不是文档或安装手册,Docker Hub 上已有大量现成的 image,各种 OS,各种数据库,各种中间件,各种软件;二是由于 image 是分层的,tomcat on CentOS 和 mysql on CentOS 两个 image 不需要下载 CentOS 这个层两次,和下载 VM image 相比,使用 Docker 的速度快了不是一点半点,速度就是生命,就是一切。

所以 Docker 不仅仅是省工省时,而且本质上是一种更好的方法。

AUFS

Docker Image

Docker Build File

Docker

Docker 太成功,以至于成为了容器的代名词,Docker 即容器,容器即 Docker,就像 Sun 的 JVM 我们直接简称为 JVM 而非 Sun JVM,其实 Docker 只是容器技术的一种实现,还有其它同样利用了 Linux Namespace 和 CGroup 的容器技术如 CoreOS 和 rkt。

首先 Docker 是一个软件公司的名字,其次它的软件产品也叫 Docker,Docker 最初只是一个 tool,后来迅速发展成众多的 components/tools,发展太快有点混乱,网上经常统统叫 Docker,初学时会搞不清楚,其实 Docker 包含了以下几个的东西:

  • Docker Image
  • Docker Container
  • Docker Engine
  • Docker Registry
  • Docker Machine
  • Docker Compose
  • Docker Swarm
  • Docker Hub
  • ……

下面是 Docker Engine 的架构:

Docker Engine

Docker 采用 RESTful 架构,client 端,默认的是 Docker 命令行,把 Docker 的命令(下载 image,产生容器,运行容器,停止容器,删除容器,等等)以 REST 形式传递给 Docker Engine 执行。

Docker Architecture

Docker workflow & command

Docker workflow & command

container,image,container engine 的概念介绍后,也是最基础最核心的,其它概念如容器的 networking,scheduler,volume,registry,等等,可以进一步学习(见下节学习)。

2017-4-18 Docker 公司重新调整了 Docker 的各个 components/tools,同时取消 Docker 开源项目。容器世界的竞争和各方创新不断涌现。

Moby

Docker Engine 现在变成了Containerd,同时 Docker 也演变成了下面这个样子:

Docker

Docker 收费的企业版主要包含了私有的 Docker TrustedRegistry 和 Universal Control Plane:

Docker EE

Docker vs VM(虚机)

绝大多数的 Docker 介绍是从虚拟机/VM 开始的,通过上面的介绍可以清楚看到,两者是完全不同的东西,但两者有类似的地方,下面是 Docker 和 VM 的比较:

Docker vs. VM

Docker vs. VM

为何容器

在我看来容器带来了两个革命性的东西:

build faster, test faster, deploy faster, update faster, recover faster

  • To reusable code: libraries

    code once,import to every project

  • To reuse binary: java

    compile once,executable everywhere

  • To reuse local environment: virtual machine

    create once,share for every team member

  • To reuse infrastructure: devops tools

    define once,provision somewhere

  • To re-use immutable, deployable, runnable artifact: container:

    build once, deploy everywhere & run (without installation/configuration)

容器带来革命性的软件打包方式:app 和所有的 dependencies 都打包在一起 - a single binary,具有consistent,portable,immutable,versionable的特性。

Identical environment is crucial for delivering high quality software

consistent env

开发高质量少 BUG 的软件的关键问题在于如何确保开发与生产环境的一致性。传统做法是建立开发、调试、生产环境,在软件复杂化的情况下,整个团队如果共享单一开发环境非常不高效。同时
要支持多版本,多环境,多配置的情况下,这变成了一个 n*m 的问题,传统的做法根本行不通。所以容器不只是一个新的软件打包方式,还是一项让我们开发高质量软件的重要技术。

如何使用容器

学习

使用

  • 安装 Docker - https://docs.docker.com/install/ - https://get.docker.com/

  • 容器化应用 - 创建 Dockerfile,然后 Docker build - Dockerfile 类似 maven,ant 的 build file,Docker Hub 是公开的,个人建议参考上的 image 是如何 build 的,从中学习一些 best practices

  • 要创建不同 node 上的容器 cluster - Docker Swarm & Docker compose file,和以前不同的是,Swarm 已经包含在默认的 Docker 下载中,不需要另行安装

  • 在项目中采用 Docker - 第一个碰到的问题就是需要建立私有的 Docker registry 存放 image,可以购买 Docker EE 版本或者寻找开源方案,如Harbor

容器平台及生态

单个容器,或者单机上的容器,犹如 Hello World,上手还是很快的,但大规模的实际应用则是个 J2EE 的问题 - 如何组合多容器,如何调度,监控,扩展容器等等诸多问题。Google 的 Kubernates,Docker Swarm/Docker EE,Mesos DC/OS,Cloud Foundry,Rancher 等等就是要解决这些问题的容器平台,让容器得到大规模程度应用。

容器生态圈

容器是一切,一切是容器,容器将成为云计算的单位,同时辐射出软件工程的很多基础性问题。路漫漫,只能走一步是一步,更多的发展留待以后的博客。

更新

2018-05-14 很多时候学习的难点是名词解释,强烈推荐“简介 Docker 的历史由来和容器生态系统”:An Overall View On Docker & Its Ecosystem - Docker, Swarm, Kubernetes, Moby, Containerd, RunC .. 文字版 Youtube 版

2018-05-14 另一个超级棒的介绍,强烈推荐:A Practical Introduction to Container Terminology

docker logo

0%