Docker容器原理

容器

首先,容器在Linux中是一组进程,是与Linux系统相互隔离开的一组进程。容器技术就像一个集装箱,将应用装起来,借用Singularity容器技术PPT的一个图片,可以形象的展示容器的作用。

容器就像集装箱一样可以搬来搬去,而且不同集装箱也不会相互干扰。容器技术就是通过一组进程的约束以及动态修改,人为的在Linux系统中创建一个边界。Linux容器(Linux Container,简称LXC)主要用到三种技术:分别是namespace与cgroup以及很早就出现的chroot。

namespace-资源隔离

通过namespace可以使得进程看到与之相关的一部分资源,另一组进程看到另一部分与之相关的资源,进程间不会相互干扰,是完全无感知的。实现方式就是把一个或者多个进程相关的资源指定在同一个namespace中。
Linux namespaces是对全局系统资源的封装隔离,在不同namespace中的进程拥有独立的全局系统资源,改变一个namespace中的资源只是相当于只影响当前namespace中的进程。可以类比于Linux的父进程与子进程的关系,通过PID来标识进程号,通过namespace来标识一组进程从而进行资源隔离。
Docker容器原理_第1张图片

Cgroup-资源控制

资源隔离只解决了资源在Linux系统上的划分的问题,但是由于在系统中进程与进程之间是平等的竞争关系(少数特权进程除外),进程A,B是平级的,但是为什么只分配给A进程资源呢。因此需要对于资源进行控制和管理。
Linux Control groups除了用来为进程设置资源限制,还能够对进程进行优先级设置、审计,以及将进程挂起和恢复等操作。在 Linux 中,Cgroups 给用户暴露出来的操作接口是文件系统,即它以文件和目录的方式组织在操作系统的 /sys/fs/cgroup 路径下。
image.png
除 CPU 子系统外,Cgroups 的每一项子系统都有其独有的资源限制能力,比如:blkio,为块设备设定I/O 限制,一般用于磁盘等设备;cpuset,为进程分配单独的 CPU 核和对应的内存节点;memory,为进程设定内存使用的限制。它就是一个子系统目录加上一组资源限制文件的组合。对于 Docker 等 Linux 容器项目来说,只需要在每个子系统下面,为每个容器创建一个控制组(即创建一个新目录),然后在启动容器进程之后,把这个进程的 PID 填写到对应控制组的 tasks 文件中即可。
可以这么理解,一个Docker容器,就是一个拥有多个namespace的进程,每个namespace使用的资源量,通过cgruop来进行限制。

chroot-更改root文件系统

通过Namespace对进程进行视图隔离,通过Cgroup对资源进行约束限制,但是为什么在容器中看到的文件结构和宿主机操作系统的一致,但是却不是宿主机的文件目录呢?
问题原因在于文件系统并不知道用户通过Namespace和Cgroups对这个容器做了什么样的限制。这个时候需要chroot命令(change root file system),改变进程的根目录到指定位置。在应用程序内,根目录“/”的位置变成了指定的目录了,且这个应用程序不能访问目录之外的其他目录

容器与虚拟化

通过上述描述,可以简单的认为容器技术 = 虚拟化技术?但是不能完全等同的。简言之虚拟化使得许多操作系统可同时在单个系统上运行。容器则可共享同一个操作系统内核,将应用进程与系统其他部分隔离开。
Docker容器原理_第2张图片
通过上图可以得知,通过多个操作系统在单个虚拟机上实现虚拟化,并不能像容器一样那么的轻量级,如果需要部署大量应用呢,使用虚拟机显然是不合适的。Linux 容器可从单个操作系统运行,在所有容器中共享该操作系统,因此应用和服务能够保持轻量级,并行快速运行。
虚拟化软件是虚拟机最主要的部分(hypervisor、Citrix XenServer、VMware workstation、Oracle virtual box、Linux kvm)。通过硬件虚拟化功能,模拟出了运行一个操作系统需要的各种硬件,比如 CPU、内存、I/O 设备等。然后,在这些虚拟的硬件上安装了一个新的操作系统,Guest OS。用户就可以使用虚拟机,在虚拟机中运行进程。容器技术使用容器软件替代虚拟化软件,因此容器技术也被称之为轻量级虚拟化技术。
容器与虚拟化不同的是,容器使用的归根结底还是同样的一组进程,虚拟机则是使用虚拟化出来的单独的Guest OS里面的内容,是实实在在的。容器在使用进程时候,拥有不同的namespace参数,这些进程就会觉得自己是各自 PID Namespace 里的第 1 号进程,只能看到各自 Mount Namespace 里挂载的目录和文件,只能访问到各自 Network Namespace 里的网络设备,就仿佛运行在一个个集装箱容器里面,与世隔绝。
由此可以看出虚拟化技术与容器技术的异同点,另外容器技术中的namespace也有缺点,如共用操作系统;有些资源是不能namespace化的,比如时间;cgroup也有缺陷,比如说Linux下的/proc目录存储的是记录当前内核运行状态的一系列特殊文件,用户可以通过访问这些文件,查看系统以及当前正在运行的进程的信息,比如CPU使用情况、内存占用率等,这些文件也是top指令查看系统信息的主要数据来源,但是在容器中执行top命令的话,也会显示宿主机的相关信息,这显然是不合理的。另外,目前比较流行的Docker底层是共享宿主机内核的,这就意味着在Windows宿主机上运行Linux容器,或者在低版本的Linux宿主机上运行高版本的Linux容器,都是行不通的。

Docker

基础知识

Docker既是一家公司的产品,也是一项容器化技术,支持创建使用Docker容器,同时Docker公司及开源社区开发的Docker componment以及Docker Swarm等容器编排技术。
Docker 技术使用 Linux 内核和内核功能(例如 Cgroups 和 namespaces)来分隔进程,目前Docker主要使用以下五种命名空间,以便各进程相互独立运行。这种独立性正是采用容器的目的所在;它可以独立运行多种进程、多个应用程序,更加充分地发挥基础设施的作用,同时保持各个独立系统的安全性。容器工具(包括 Docker)可提供基于镜像的部署模式。这使得它能够轻松跨多种环境,与其依赖程序共享应用或服务组。Docker 还可在这一容器环境中自动部署应用程序(或者合并多种流程,以构建单个应用程序)。
Docker容器原理_第3张图片

**pid namespace:**用于隔离进程 ID。
**net namespace:**隔离网络接口,在虚拟的 net namespace 内用户可以拥有自己独立的 IP、路由、端口等。
**mnt namespace:**文件系统挂载点隔离。
**ipc namespace:**信号量,消息队列和共享内存的隔离。
**uts namespace:**主机名和域名的隔离。

此外,Docker一开始是基于上文提到的LXC技术实现的,后来拜托了对于LXC技术的依赖。Docker不仅可以运行容器,还具有包括简化用于构建容器、传输镜像以及控制镜像版本的流程。传统的 Linux 容器使用 init 系统来管理多种进程,所有应用程序都作为一个整体运行。与此相反,Docker 技术对应用程序各自独立运行其进程,并提供相应工具以实现这一功能。
Docker容器原理_第4张图片
除了上文提到的Cgroups以及namespace,Docker使用UnionFS联合文件系统,构建出容器中进程运行时文件系统根基。将操作系统二进制指令、依赖配置文件、程序介质等通过镜像分层叠加构建出程序运行时看到的整个文件系统环境。不同的目录在这个虚拟目录里面又可以有独立的权限,也即是将两个不同的目录,挂载到同一个目录下面,然后通过读写权限的设置,使得呈现最终的文件目录给容器(模拟成了一个完整的操作系统)。
Docker的主要目标是"Build、Ship and Run any App\Anywhere",构建,运输,处处运行。构建:做一个docker镜像;运输:docker pull;运行:启动一个容器。每一个容器,他都有自己的文件系统rootfs。

三大基础概念

Docker镜像

Docker镜像,相当于一个文件系统,是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。 镜像不包含任何动态数据,其内容在构建之后也不会被改变。Docker设计时,就充分利用Union FS的技术,将其设计为分层存储的架构。 镜像实际是由多层文件系统联合组成。
镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。
分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。
Docker镜像可以从Docker仓库拉取,也可以将镜像推送。为了解决应用多版本的问题,引入tag,即相同的应用由于具有不同的版本,从而拥有不同的tag。另外Docker镜像的构架也可以通过Dockerfile构建。通过执行Docker镜像就得到了Docker容器,而Docker容器也可以打包成镜像。Docker镜像的生命周期如下图所示:
Docker容器原理_第5张图片

Docker容器

Docker镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等 。容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。前面讲过镜像使用的是分层存储,容器也是如此。容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。
按照Docker最佳实践的要求,容器不应该向其存储层内写入任何数据 ,容器存储层要保持无状态化。所有的文件写入操作,都应该使用数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此, 使用数据卷后,容器可以随意删除、重新run,数据却不会丢失。

Docker仓库

仓库就是上图中的Docker registry。镜像构建完成后,可以很容易的在当前宿主上运行,但是, 如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry就是这样的服务。
  一个 Docker Registry中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。所以说:镜像仓库是Docker用来集中存放镜像文件的地方类似于我们之前常用的代码仓库。
Docker Registry公开服务是开放给用户使用、允许用户管理镜像的Registry服务。一般这类公开服务允许用户免费上传、下载公开的镜像,并可能提供收费服务供用户管理私有镜像。最常使用的Registry公开服务是官方的Docker Hub ,这也是默认的Registry,并拥有大量的高质量的官方镜像,网址为:hub.docker.com/ 。国内也有一些云服务商提供类似于Docker Hub的公开服务。

容器编排

使用 Docker Compose / Swarm可以轻松、高效的管理容器,它是一个用于定义和运行多容器 Docker 的应用程序工具。

docker components

Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose,可以使用 YML 文件来配置应用程序需要的所有服务。然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有服务。
Compose 使用的三个步骤:

  • 使用 Dockerfile 定义应用程序的环境。
  • 使用 docker-compose.yml 定义构成应用程序的服务,这样它们可以在隔离环境中一起运行。
  • 最后,执行 docker-compose up 命令来启动并运行整个应用程序。

docker swarm

Swarm是Docker公司推出的用来管理docker集群的平台,几乎全部用GO语言来完成的开发的,代码开源在https://github.com/docker/swarm, 它是将一群Docker宿主机变成一个单一的虚拟主机,Swarm使用标准的Docker API接口作为其前端的访问入口,各种形式的DockerClient(compose,docker-py等)均可以直接与Swarm通信,甚至Docker本身都可以很容易的与Swarm集成,这大大方便了用户将原本基于单节点的系统移植到Swarm上,同时Swarm内置了对Docker网络插件的支持,用户也很容易的部署跨主机的容器集群服务。

Docker容器原理_第6张图片
在结构图可以看出 Docker Client使用Swarm对 集群(Cluster)进行调度使用。Swarm是典型的master-slave结构,通过发现服务来选举manager。manager是中心管理节点,各个node上运行agent接受manager的统一管理,集群会自动通过Raft协议分布式选举出manager节点,无需额外的发现服务支持,避免了单点的瓶颈问题,同时也内置了DNS的负载均衡和对外部负载均衡机制的集成支持。

Docker Swarm 和 Docker Compose 一样,都是 Docker 官方容器编排项目,但不同的是:

  • Docker Compose 是一个在单个服务器或主机上创建多个容器的工具
  • Docker Swarm 则可以在多个服务器或主机上创建容器集群服务

Docker网络

Docker有四种网络模型:

网络类型 配置 说明
None –net=none 容器有独立的namespace,但是没对其有任何的网络配置,比如分配IP,网桥连接等
Container –net=container:containerID 与另一个运行中的容器共享Net Namespace,k8s中pod就是多个容器共享一个net namespace
Bridge 默认 Docker设计的NAT网络模型(默认类型)
Host –net=host 与主机共享Network Namespace

None模式

hots模式是和宿主机公用一个网段和端口,缺陷是会隔离性差,会占用宿主机的端口,做不到自定任意端口。优点,无需做网络策略,只要能访问到宿主机,就能访问到容器。
Docker容器原理_第7张图片
使用none模式,Docker容器拥有自己的Network Namespace,但是,并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。

Host模式

hots模式是和宿主机公用一个网段和端口,缺陷是会隔离性差,会占用宿主机的端口,做不到自定任意端口。优点,无需做网络策略,只要能访问到宿主机,就能访问到容器
Docker容器原理_第8张图片

Container模式

这个模式是两个容器之间可以相互通信,虽然是两个容器,但可以理解为在同一容器里面,可以用localhost访问。两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。
Docker容器原理_第9张图片

Bridge模式

bridge模式下容器没有一个公有ip,只有宿主机可以直接访问,外部主机是不可见的,但容器通过宿主机的NAT规则后可以访问外网。
Docker容器原理_第10张图片
优点:
Docker Daemon 利用 veth pair 技术,在宿主机上创建两个虚拟网络接口设备,假设为veth0 和 veth1。而
veth pair 技术的特性可以保证无论哪一个 veth 接收到网络报文,都会将报文传输给另一方。
Docker Daemon 将 veth0 附加到 Docker Daemon 创建的 docker0网桥上。保证宿主机的网络报文可以发往 veth0;
Docker Daemon 将 veth1 添加到 Docker Container 所属的 namespace 下,并被改名为eth0。如此一来,保证宿主机的网络报文若发往 veth0,则立即会被 eth0 接收,实现宿主机到Docker Container网络的联通性;同时,也保证 Docker Container 单独使用 eth0,实现容器网络环境的隔离性
缺点:
该模式下 Docker Container 不具有一个公有 IP,即和宿主机的 eth0 不处于同一个网段。导致的结果是宿主机以外的世界不能直接和容器进行通信。
虽然 NAT 模式经过中间处理实现了这一点,但是 NAT 模式仍然存在问题与不便,如:容器均需要在宿主机上竞争端口,容器内部服务的访问者需要使用服务发现获知服务的外部端口等。
另外 NAT 模式由于是在三层网络上的实现手段,故肯定会影响网络的传输效率。

关于Docker容器原理可以参考:http://t.csdn.cn/RELpL
关于Docker命令使用参考:Docker入门

你可能感兴趣的:(容器,docker,linux,运维)