首发于EnjoyMoving
Docker快速入门之原理篇

Docker快速入门之原理篇

虚拟机与容器的区别

虚拟机

虚拟机是一种模拟系统,即在软件层面上通过模拟硬件的输入和输出,让虚拟机的操作系统得以运行在没有物理硬件的环境中(也就是宿主机的操作系统上)。其中,这个能够模拟出硬件输入输出,让虚拟机的操作系统可以启动起来的程序,被叫做hypervisor。

一般来说,虚拟机都会有自己的kernel,自己的硬件,这样虚拟机启动的时候需要先做开机自检,启动kernel,启动用户进程等一系列行为,虽然现在电脑运行速度挺快,但是这一系列检查做下来,也要几十秒,也就是虚拟机需要几十秒来启动。

容器

宿主机和虚拟机的kernel是一致的(与虚拟机区别:不用做硬件输入输出的搬运工了,只需要做kernel输入输出的搬运工即可),这种虚拟机被命名为操作系统层虚拟化,也被叫做容器。
由于在虚拟机的系统中,虚拟机认为自己有独立的文件系统,进程系统,内存系统,等等一系列,所以为了让容器接近虚拟机,也需要有独立的文件系统,进程系统,内存系统,等等一系列,为了达成这一目的,宿主机系统采用的办法是:只要隔离容器不让它看到主机的文件系统,进程系统,内存系统,等等一系列,那么容器系统就是一个接近虚拟机的玩意了。

Docker就是一种容器。

Docker应用场景

传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;

而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便:

  • 快速交付你的应用程序:Docker允许开发者在本地包含了应用程序和服务的容器进行开发,之后可以集成到连续的一体化和部署工作流中。举个例子,开发者们在本地编写代码并且使用Docker和同事分享其开发栈。当开发者们准备好了之后,他们可以将代码和开发栈推送到测试环境中,在该环境进行一切所需要的测试。从测试环境中,你可以将Docker镜像推送到服务器上进行部署。
  • 开发和拓展更加简单:Docker的以容器为基础的平台允许高度可移植的工作。Docker容器可以在开发者机器上运行,也可以在实体或者虚拟机上运行,也可以在云平台上运行。Docker的可移植、轻量特性同样让动态地管理负载更加简单。你可以用Docker快速地增加应用规模或者关闭应用程序和服务。Docker的快速意味着变动几乎是实时的。
  • 达到高密度和更多负载:Docker轻巧快速,它提供了一个可行的、 符合成本效益的替代基于虚拟机管理程序的虚拟机。这在高密度的环境下尤其有用

Docker基本架构

Docker 使用客户端-服务器 (C/S) 架构模式,包括客户端和服务端。

使用远程API来管理和创建Docker容器:Docker daemon 作为服务端接受来自客户的请求,并处理这些请求( 创建、运行、分发容器) 。 客户端和服务端既可以运行在一个机器上,也可通过 socket 或者 RESTful API 来进行通信。

Docker 容器通过 Docker 镜像来创建。容器与镜像的关系类似于面向对象编程中的对象与类:

Docker原理

Docker使用Go语言编写,并且使用了一系列Linux内核提供的特性来实现其功能。

一个能执行Docker的系统分为两大部分:

  • Linux的核心元件
  • Docker相关元件

Docker使用的Linux核心模块功能包括下列各项:

  • Cgroup – 用来分配硬件资源
  • Namespace – 用来隔离不同Container的执行空间
  • AUFS(chroot) – 用来建立不同Container的档案系统
  • SELinux – 用来确保Container的网路的安全
  • Netlink – 用来让不同Container之间的行程进行沟通
  • Netfilter – 建立Container埠为基础的网路防火墙封包过滤
  • AppArmor – 保护Container的网路及执行安全
  • Linux Bridge – 让不同Container或不同主机上的Container能沟通

linux运行docker原理

docker容器和宿主机共享linux kernel。为了让容器像虚拟机那样有独立的文件系统,进程系统,内存系统,等等一系列,linux宿主机系统采用的办法是:通过隔离容器不让它看到主机的文件系统,进程系统,内存系统,等等一系列。

mac os和windows运行docker的原理

通过boot2docker(boot2docker.io/ )启动一个虚拟linux kernel,所有的docker容器都跑在这个kernel上。

docker 其实真正想做的事情是把资源隔离的接口标准化(最新的版本里windows的接口也被抽象到了docker自己的体系),严格说它是所有相似资源隔离的一层抽象和搬运工。

文件系统隔离

每个进程容器运行在完全独立的根文件系统里。

容器中的文件系统都是挂载到了宿主机真实系统中的一个目录下面的。对于不同的容器,挂载点是不一样的,而容器不能穿越根目录上一级去访问, 所以这里对每一个容器都做到了文件系统隔离。

外挂数据卷:

可以把war包等文件的放置路径映射到主机上的某个路径,达到业务逻辑和数据持久化分割开(类似MVC思想)。

资源隔离

Cgroups:Cgroups是Linux内核功能,它让两件事情变成可能:限制Linux进程组的资源占用(内存、CPU);为进程组制作 PID、UTS、IPC、网络、用户及装载命名空间。

docker使用cgroup为每个进程容器分配不同的系统资源,将不同进程的资源使用隔离开。

命名空间

Docker充分利用了一项称为namespaces的技术来提供隔离的工作空间,我们称之为container(容器)。当你运行一个容器的时候,Docker为该容器创建了一个命名空间。这样提供了一个隔离层,每一个应用在它们自己的命名空间中运行而且不会访问到命名空间之外。

一些Docker使用到的命名空间有:

  • pid命名空间: 进程隔离(PID: Process ID)
  • net命名空间: 管理网络接口(NET: Networking)
  • ipc命名空间: 管理进程间通信资源 (IPC: InterProcess Communication)
  • mnt命名空间: 管理挂载点 (MNT: Mount)
  • uts命名空间: 隔离内核和版本标识 (UTS: Unix Timesharing System)

namespace让进程隔离更灵活:

linux实现进程的方法为fork,实现的方式分为两个步骤:

  1. 在内存中复制一个父进程,得到“子进程”,此时子进程就是父进程上下文的简单克隆,内容完全一致
  2. 设置子进程的 pid,parent_pid,以及其他和父进程不一致的内容

从进程被制造的步骤可以看出,进程大部分资源和父进程共享,如果需要制造一个看起来像虚拟机的进程,我们需要比普通的进程多做几步:

  • 可以自定义rootfs,比如我们把整个ubuntu发行版的可执行文件以及其他文件系统都放在目录/home/admin/ubuntu/ 下,当我们重定义rootfs = /home/admin/ubuntu 后,则该文件地址被印射为 "/"
  • 把自身pid 印射为0,并看不到其他任何的pid,这样自身的pid成为系统内唯一存在pid,看起来就像新启动了系统
  • 用户名隔离,可以把用户名设置为“root”
  • hostname隔离,可以另取一个hostname,成为新启动进程的hostname
  • IPC隔离,隔离掉进程之间的互相通信
  • 网络隔离,隔离掉进程和主机之间的网络

这些隔离资源需要的方法代码在linux系统内核中已经有提供支持。

所以虽然docker帮助我们准备好了rootfs地址,镜像里面的文件,以及各种资源隔离的配置,但是在启动一个容器的时候,它只是调用系统中早已内置的可以隔离资源的方法,而kernel支持这些方法,也是在创建进程的方法上做了一层资源隔离的扩展而已。这就解释了docker两个特性:

  • 启动速度快,因为本质来说容器和进程差别没有想象中的大,共享了很多代码,流程也差的不多
  • linux内核版本有最低的要求,因为linux是在某个版本后开始支持隔离特性

网络隔离

每个进程容器运行在自己的网络命名空间里,拥有自己的虚拟接口和IP地址

在安装好docker后,会默认初始化一个docker0的网桥。在host机器上,会为每一个容器生成一个默认的网卡,这个网卡的一端连接在容器的eth0,一端连接到docker0。这样就实现了每个容器有一个单独的IP。

如果需要外部能够访问容器,需要做端口映射规则,和配置虚拟机一样的道理, 只不过这里的80端口并没有占用了本地端口,而是在容器内部做了监听,外部是通过docker0桥接过去的,每个容器间也做到了端口和网络隔离。

映射端口:

负责接受把docker容器里的某个端口与主机某个端口绑定。

日志记录

Docker将会收集和记录每个进程容器的标准流(stdout/stderr/stdin),用于实时检索或批量检索。

联合文件系统

联合文件系统(UnionFS)是用来操作创建层的。Union文件系统:在union文件系统里,文件系统可以被装载在其他文件系统之上,其结果就是一个分层的积累变化。

Docker使用Union文件系统,把所有的操作记录在Dockerfile(构建镜像的蓝图)。Docker可以使用很多种类的UnionFS包括AUFS, btrfs, vfs, and DeviceMapper。

docker运作

当我们想运行一个容器的时候,docker会:

  1. 拉取镜像,若本地已经存在该镜像,则不用到Docker仓库去拉取
  2. 使用镜像创建新的容器
  3. 分配union文件系统并且挂载一个可读写的层,任何修改容器的操作都会被记录在这个读写层上,你可以保存这些修改成新的镜像,也可以选择不保存,那么下次运行改镜像的时候所有修改操作都会被消除
  4. 分配网络/桥接接口,创建一个允许容器与本地主机通信的网络接口
  5. 设置ip地址,从池中寻找一个可用的ip地址附加到容器上,换句话说,localhost并不能访问到容器
  6. 运行你指定的程序
  7. 捕获并且提供应用输出:连接并且记录标准输出、输入和错误让你可以看到你的程序是如何运行的。

Docker的执行元件:

  • Docker client – 呼叫Docker Daemon (本机或其它客户端)
  • Docker daemon – 执行Docker功能并用 (本机)
  • libcontainer - 和Linux核心沟通的library (本机)
  • Docker Image – 建立容器用的映像档 (本机或云端映像库)

Docker的执行过程:

docker client呼叫Docker daemon,然后daemon透过libcontainer呼叫Linux的核心模组来完成建立容器的的步骤。


参考来源:

zhuanlan.zhihu.com/p/22

zhuanlan.zhihu.com/p/22

runoob.com/docker/docke

cnblogs.com/Bozh/p/3958

ruby-china.org/topics/2

jianshu.com/p/7a58ad7fa

kancloud.cn/kancloud/do

kancloud.cn/kancloud/do

kancloud.cn/kancloud/do


(本文首发于微信公众号:EnjoyMoving)

发布于 2017-12-03

文章被以下专栏收录