我们都知道Docker是一个容器化工具,那么什么是容器呢,Docker和容器有什么关系,Docker又能解决什么问题呢?
麻烦的环境配置
软件开发最大的麻烦事之一就是环境配置,在开发之前我们需要准备各种运行环境、IDE、辅助工具。就像我们要使用电脑前,先要安装操作系统一样。
而一个可用软件的交付过程通常包含两个部分 - 开发和维护。不幸的是,我们很难保证软件开发测试和运行维护阶段的软件能运行在一模一样的环境下。开发常说:"It works on my machine",很多时候我们都要花大量的时间去配置环境和教别人配置环境。
现在的应用程序
- 以前的应用程序:
- 几乎都是单块应用: 大系统, 多模块
- 紧耦合: 内部调用
- 不常变更: 需求稳定(改动成本高)
- 如今的应用程序:
- 解耦: 微服务/异步
- 经常变更: 快速迭代
- 动态创建和部署: 服务化
新架构的挑战
- 多样化的技术栈
- 需要动态创建机器
- 很多活动组件
- 运维人员需要管理复杂的架构
在新的应用程序架构下,我们部署应用的成本大大增加。
不但要搭建不同语言、不同技术栈适配的运行环境,还要部署到多个服务器主机上;并且这些主机还可能来自不同地方(公/私有云主机、物理主机)。
统一的管理
其实在软件开发的过程中我们就思考过类似的问题 - 如何统一的管理我们的代码包。例如,Maven、Gradle、NPM,我们使用一个相同的格式(规范)将我们的代码划分成模块,并使用一套工具去管理他们。在此之后我们不需要重复的copy代码、复制文件,只需要声明式的引入我们需要的代码包就可以了。
当然,我们希望我们的应用部署也能如此简单。
“容器”
“容器”是一个黑盒,对于它的使用者来说:
- 无需关心里面有什么:只关注“容器”能做什么
- 有一套工具来管理黑盒:打包、运输、运行
- 减少了部署单元的数量,从而减少了花销:多个工具聚集在一个“容器”内
- 更容易管理多个环境:以“容器”为单位进行部署和管理
虚拟化技术
-
虚拟机
-
精心配置的虚拟机也是满足我们(基本)要求的容器,我们能够通过虚拟机镜像来打包我们的应用。但是,庞大的操作系统占用了大量的系统资源,使运行成本大大上升。
-
-
容器
-
(作为进程)共用内核并提供额外的隔离手段,避免虚拟的操作系统占用。
-
虚拟机
- 应用
- 运行环境(Java/数据库/libs...)
- 客户机操作系统(Guest Operating System)
- 虚拟机管理系统(Hypervisor)
- 操作系统级:MacOS(HyperKit),Windows的Hyper-V
- 应用软件级:VirtualBox,VMWare Workstation
- 主操作系统(Host Operating System)
- 硬件(Infrastructure)/云主机
容器
- 应用
- 运行环境(Java/数据库/libs...)
- Docker守护进程(Docker Daemon): 类似虚拟机管理系统
- 主操作系统(Host Operating System)
- 硬件(Infrastructure)/云主机
容器技术
容器技术已经发展了一段时间了, 例如, LXC, BSD Jails, Solaris Zones...
- 看起来像虚拟机
- 可以SSH到容器
- 具有root的访问权限
- 可以安装包
- 可以mount文件系统
- 拥有自己的eth0接口
- 可以修改iptables 规则和routing table
- 共享宿主机的内核
- 隔离 cgroups (memory, cpu, blkio)
- 拥有进程空间 (pid, mnt, net, ipc, uts)
- pid - 隔离进程PID
- mnt - 允许创建不同的文件系统层级
- net - 隔离网络控制、iptables、防火墙、路由
- ipc - 定义内部进程交流的范围
- uts - 允许修改hostname
容器技术的局限
- 容器没有标准的格式
- 所以容器是不可移植的
- 没有标准的工具,所以比如要自己管理网络
- 没有可重用的模块和API
Docker
- 使容器变得更容易使用
- 容器镜像的商品化
- 可插拔的模块易于吸引供应商加入
- 适当的API可用来创建高层次抽象的工具
- 和微服务诞生在同一个时代
Docker架构
- Docker Client:接收命令和Docker Host进行交互的客户端
- Docker Host:运行Docker服务的主机
- Docker Daemon:守护进程,用于管理所有镜像和容器
- Docker Images/Containers:镜像和容器实例
- Registry(Hub):镜像仓库
Docker底层实现
Docker核心技术
- Namespace 命名空间
Linux 的命名空间机制提供了以下七种不同的命名空间,包括CLONE_NEWCGROUP、CLONE_NEWIPC、CLONE_NEWNET、CLONE_NEWNS、CLONE_NEWPID、CLONE_NEWUSER、CLONE_NEWUTS
,通过这七个选项我们能在创建新的进程时设置新进程应该在哪些资源上与宿主机器进行隔离。因此容器只能感知内部的进程,而对宿主机和其他容器一无所知。 - CGroups (Control Groups)
Linux 的 CGroup 能够为一组进程分配资源,也就是我们在上面提到的 CPU、内存、网络带宽等资源. -
UnionFS
Docker中的每一个镜像都是由一系列的只读层组成的,Dockerfile 中的每一个命令都会在已有的只读层上创建一个新的层。通过 docker run 命令可以在镜像的最上层添加一个可写的层 - 容器层,所有对于运行时容器的修改其实都是对这个容器读写层的修改。
容器和镜像的区别就在于,所有的镜像都是只读的,而每一个容器其实等于镜像加上一个可读写的层,也就是同一个镜像可以对应多个容器。同时已构建的每一层镜像也可以作为其他镜像的基础层进行共用。