容器技术 - 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
  • 容器化应用
    • 创建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%