docker
最后更新于
最后更新于
我们使用Docker打包开发的应用代码, 是希望:
将应用程序正常运行依赖的环境打包管理起来, 避免对复杂繁琐的环境配置进行重复操作
使得应用代码及其环境具有高度的可移植性能, 可以快速的部署到任何环境中
在应用程序之间产生隔离, 避免为了适配某个应用对环境做出改动时, 对其他无关的应用产生影响
总结下来, 我们的目的是通过Docker, 将应用(App)变成一种标准化的, 可移植的, 自管理的组件, 我们可以在任何主流的操作系统中开发, 调试和运行.
上述的需求使用虚拟机也可以满足, 那么Docker和虚拟机之间又有什么区别和联系呢, Docker是一种虚拟机吗?
虚拟机是计算机系统的仿真器, 通过软件模拟具有完整硬件系统功能的, 运行在一个完全隔离环境中的完整计算机系统, 能提供物理计算机的功能.
上图是一个运行了两个虚拟机的示例图.
虚拟机运行在宿主机上, 最底层的Server
对应的就是宿主机的硬件资源
Host OS
是宿主机的操作系统, 一般是各种版本的Linux系统
Hypervisor
是一种虚拟机管理系统, 它对提供了创建虚拟机的方法, 并管理这些虚拟机资源
这种系统向下结合宿主机的操作系统, 对接真实的物理机的硬件操作
向上提供虚拟的硬件接口, 供虚拟机操作系统使用
Guest OS
就是虚拟机操作系统, 可以看到虚拟机提供了物理机硬件级别的操作系统隔离
这让不同虚拟机之间的隔离很彻底, 不同虚拟机之间绝无共享的资源
也正是由于隔离的彻底性, 需要消耗很多资源
Bins/Libs
是一些上层环境软件, 如各种数据库, 语言编译器, 解释器等
App
就是我们最终的应用代码了
虚拟机的隔离特性是完美的, 但实际生产中, 对于一些很底层的资源, 如操作系统的kernel, 是可以共享的, 这样可以带来很多好处:
资源占用少
启动快, 几乎没有性能损耗
Docker就是这么一种技术, 它使用的是容器技术. 容器可以提供操作系统级别的进程隔离. 它复用宿主机的操作系统(内核).
当我们运行Docker容器时, 此时容器本身只是操作系统中的一个进程, 只是利用操作系统提供的各种功能实现了进程间网络, 空间, 权限等隔离, 让多个Docker容器进程相互不知道彼此的存在.
回想Linux系统中的不同用户, 每个用户用ssh登录后, 大家共享着一些系统内核等一些底层软件, 但每个用户的权限不同, 能够使用的硬件资源也会不同(如NAS盘只有部分用户能使用), 但相互之间并不会互相干扰.
这种资源隔离的形式就与Docker的很相似. 实际上, 这种用户之间和Docker容器之间的资源隔离, 都是依赖Linux Namespace完成的. Linux Namespace是Linux内核提供的功能, 它可以隔离一系列的系统资源, 如PID(进程ID), UserID, Network, 文件系统等. Linux Namespace提供了6中不同类型的Namespace, 如下图:
通过PID Namespace, 每个Namespace就像一个新的Linux, 有自己的init进程(初始进程, PID为1), 其他进程的PID在init进程的PID上递增. 如下图, 子Namespace对应的init进程在父Namespace对应的PID为3, 但在自己的Namespace中对应的PID就为1.
Docker利用Linux Namespace功能实现多个Docker容器相互隔离, 具有独立环境的功能. Go语言对Namespce API进行了相应的封装, 从Docker源码中可以看到相关的实现.
Docker通过Linux Namespace帮进程隔离出自己单独的空间/资源, 那Docker如何限制进程对这些资源的使用呢. Docker容器本质依旧是一个进程, 多个Docker容器运行时, 如果其中一个Docker进程占用大量CPU和内存资源, 就会导致其他Docker进程响应缓慢. 为了避免这种情况, 可以通过Linux Cgroups(简称Cgroups)技术对资源进行限制.
Cgroups可以对一组进程及这些进程的子进程进行资源限制, 控制和统计, 这里的资源包括CPU, 内存等硬件资源, 以及存储, 网络, 设备访问权等. 通过Cgroups可以很轻松的限制某个进程的资源占用并且统计该进程的实时使用情况.
就像虚拟机一样, 保存的Docker镜像也是以文件的形式, 存储在宿主机上的. 每一个Docker镜像都可以看做是一套独立的文件系统. Docker使用一种高效的方式, 尽可能用最少的空间存储所有镜像.
首先我们在 pull 获取镜像, 或打包得到镜像时, 都会看到这些过程是被划分成层(layer)执行的. 其实Docker镜像是一种分层结构, 每一层构建在其他层之上, 从而实现增量增加内容的功能.
可以理解为一个镜像中的环境是一层一层搭建成的, 最底层是系统信息之类的基础数据, 有些环境需要依赖其他环境才能搭建. 因此镜像的存储就可以采用增量的方式, 每层分开存储.
而这些都依赖于 Union File System, 它是为Linux系统设计的将其他文件系统联合到一个联合挂载点的文件系统服务. UnionFS 使用 branch 将不同文件系统的文件和目录透明地叠加覆盖, 形成一个单一一致的文件系统. 同时使用 Copy on Write 技术来提高合并后文件系统的资源利用.
Docker使用的AUFS系统, 一种与UnionFS功能相同, 但具有更高的性能和可靠性, 此外还引入了如负载均衡等新功能. AUFS 可以在基础的文件系统上增量的增加新的文件系统, 通过叠加覆盖的形式最终形成一个文件系统.
运行 Docker 容器如果没有指定 volume 或 bind mount, 即挂载位置, 则 Docker 容器结束后, 运行时产生的数据便丢失了.