Docker之二十:深入了解架构和核心概念

容器发展历史

在这里插入图片描述
2013 年诞生的 Docker 正在让容器技术得到全世界技术公司和开发人员的关注。

Docker 容器和虚拟机

虚拟机是用来进行硬件资源划分的解决方案,它利用硬件虚机化技术,例如 VT-xAMD-V 或者 privilege(权限等级)会同时通过一个 hypervisor 层来实现资源的彻底隔离。

容器则是操作系统级别的虚拟化,利用的是内核的 Cgroup 和 Namespace 特性,此功能完全通过软件来实现,仅仅是进程本身就可以与其他进程隔开,不需要任何辅助。

Docker 容器与主机共享操作系统内核,不同的容器之间可以共享部分系统资源,因此容器更加轻量级,消耗的资源更少。而虚拟机会独占分配给自己的资源,几乎不存在资源共享,各个虚拟机之间近乎完全隔离,因此虚拟机更加重量级,也会消耗更多资源。

Docker 技术架构

在这里插入图片描述
Docker 基于容器技术的轻量级需计划,并没有传统虚拟化中的 Hypervisor 层。其虚拟化技术基于内核的 CgroupNamespace 技术。

通信上,Docker 并不直接与内核交换,它通过更底层工具 Libcontainer 与内核交互。Libcontainer 是真正意义上的容器引擎,它通过 clone 系统调用直接创建容器,通过 pivot_root 系统调用进入容器,且通过直接操作 cgroupfs 文件实现资源管控,Docker 本身侧重于处理更上层业务。

LXC

什么是 LXC ?目前代表两种含义:

这里长指第二种。Docker 在内核容器技术(Cgroup 和 Namespace)的基础上,提供了更高层的控制工具,改工具包含以下特性:

  • 跨主机部署。LXC 实现了“进程沙盒”,是跨主机部署的前提条件。Docker 将目标程序运行依赖的主机特定配置,包括网络、存储、发行版等配置进行抽象,并与应用程序一同打包,所以可以保证在不同硬件、不同配置的机器上 Docker 容器中运行的程序和其所依赖的环境及配置是一致的。
  • 以应用为中心。简化了应用程序的部署。
  • 自动构建。Docker 提供了一套能够从源码自动构建镜像的工具。该工具可以灵活的使用 makemavenchefpuppetsaltdebian 包、RPM 包和源码包形式,将应用程序的依赖、构建工具和安装包进行打包处理,而且当前机器的配置不会影响镜像的构建过程。
  • 版本管理。
  • 组件重用。任何容器都可以用作生成另一个组件的基础镜像。
  • 共享。Docker 用户可以访问公共的镜像 Registry
  • 工具生态链。Docker 定义了一系列 API 来定制容器的创建和部署过程并实现自动话。有许多工具能够与 Docker 集成并扩展 Docker 的能力,包括类 PaaS 部署工具(DokkuDeisFlynn)、多节点编排工具(MaestroSaltMesosOpenStack nova)、管理面板(Docker-uiOpenStack HorizonShipyard)、配置管理工具(ChefPuppet)、持续集成工具(JenkinsStriderTravis)等。

Docker 容器

Docker 通过 Libcontainer 实现对容器生命周期的管理、信息的设置和查询,以及监控和通信等功能。容器以镜像为基础,同时又为镜像提供一个标准的和隔离的执行环境。

容器很好的诠释了 Docker 集装箱的理念,它可以安装任意的软件和库文件,做任意的运行环境。开发和运维人员在转移和部署应用的时候,并不用关心容器里面装什么软件,也不用了解他们是如何配置的。

容器的组成

容器 = cgroup + namespace + rootfs + 容器引擎(用户态工具)

  • CGroup:资源控制。
  • Namespace:访问控制。
  • rootfs:文件系统隔离。
  • 容器引擎:生命周期控制。

容器的创建原理

Step 1:通过 clone 系统调用,并传入各个 Namespace 对应的 clone flag,创建一个新的子进程,该进程拥有自己的 Namespace

/* 由以下代码可知,该进程拥有自己的 pid、mount、user、net、ipc、uts、namspace */
pid = clone(fun, stack, flags, clone_arg);
(flags: CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWUSER |
        CLONE_NEWNET | CLONE_NEWIPC | CLONE_NEWUTS |
...)

Step 2:将 Strp 1 产生的进程 pid 写入各个 Cgroup 子系统中,这样该进程就可以受到相应 Cgroup 子系统的控制。

echo $pid > /sys/fs/cgroup/cpu/tasks
echo $pid > /sys/fs/cgroup/cpuset/tasks
echo $pid > /sys/fs/cgroup/blkio/tasks
echo $pid > /sys/fs/cgroup/memory/tasks
echo $pid > /sys/fs/cgroup/devices/tasks
echo $pid > /sys/fs/cgroup/freezer/tasks

Step 3:该 fun 函数由上面的新进程执行,在 fun 函数中,通过 privot_root 系统调用,使进程进入一个新的 rootfs,之后通过 exec 系统调用,在新的 NamespaceCgrouprootfs 中支持 “/bin/bash” 程序,

fun()
{
...
    pivot_root("path_of_roofs/", path);
...
    exec("/bin/bash");
...
}

通过上面的步骤,成功地在一个“容器”中运行了一个 bash 程序。

CGroup

概念

Cgroupcontrol group 的简写,属于 Linux 内核提供的一个特性,用于限制和隔离一组进程对系统资源(包括 CPU、内存*、block I/O* 和网络带宽)的使用,也就是做资源的 QoS

从实现角度来看,Cgroup 实现了一个通用的进程分组的框架,而不同资源的具体管理则是各个 Cgroup 子系统实现的,Cgroup 实现的子系统及其作用如下:

  • devices:设备权限控制。
  • cpuset:分配指定的 CPU 和内存节点。
  • cpu:控制 CPU 占用率。
  • cpuacct:统计 CPU 使用情况。
  • memory:限制内存的使用上限。
  • greezer:冻结(暂停)Cgroup 中的进程。
  • net_cls:配合 tc(traffic controller)限制网络带宽。
  • net_prio:设置进程的网络流量优先级。
  • huge_tlb:限制 HugeTLB的使用。
  • perf_event:允许 Peft 工具基于 Cgroup 分组做性能检测。

Cgroup 的接口和使用

CGroup 的原生接口通过 cgroupfs 提供,类似于 procfs 和 sysfs,是一种虚拟文件系统。

# 1、挂载 cgroups
mount-t cgroup-o cpuset cpuset /sys/fs/cgroup/cpuset

# 2、查看 cggroups
# 列表中,以 cpuset 开头的控制文件都是 cpuset 子系统产生的,其他文件由 Cgropu 产生
# 列表中 tasks 文件记录了这个 Cgroup 的所有进程(包括线程)
ls /sys/fs/cgroup/cpuset

# 3、创建 Cgroup
mkdir /sys/fs/cgroup/cpuset/child

# 4、配置 Cgroup
echo 0 > /sys/fs/cgroup/cpuset/child/cpuset.cpus
echo 0 > /sys/fs/cgroup/cpuset/child/cpuset.mems

# 5、使能 Cgroup
echo $$ > /sys/fs/cgroup/cpuset/child/tasks

Cgroup 子系统

实际的资源的分配是由各个 Cgroup 子系统完成的。

  1. cpuset 子系统

    cpuset 可以为一组进程分配指定的 CPU 和内存节点,主要接口包括 cpuset.cpus(允许经常使用的 CPU 列表) 和 cpuset.mems(允许进程使用的内存节点列表)。

  2. cpu 子系统

    cpu 子系统用于限制进程的 CPU 占用率。CPU 比重分配(cpu.shares 接口)、CPU 带宽限制(cpu.cfs_period_uscpu.cfs_quota_us 接口)、实时进程的 CPU 带宽限制(cpu.rt_period_uscpu.rt_runtime_us 接口)。

  3. cpuacct 子系统

    cpuacct 子系统用来统计各个 Cgroup 的 CPU 使用情况。主要接口包括 cpuacct.stat(报告这个 Cgroup 分别在用户态和内核态消耗的 CPU 实际)、cpuacct.usage(报告这个 Cgroup 消耗的总 CPU 时间)、cpuacct.usage_percpu(报告这个 Cgroup 在各个 CPU 上消耗的 CPU 时间,总和就是 cpuacct.usage 的值)。

  4. memory 子系统

    memory 子系统用来控制 Cgroup 所能使用的内存上线。主要接口包括 memory.limit_in_bytes、memory.memsw.limit_in_bytes(设定内存加上交换分区的使用总量)、memory.oom_control(设置为 0,内存使用量超过上限时,系统会“杀死”进程)、memory.stat(汇报内存使用信息)。

  5. blkio 子系统

    blkio 子系统用来现在 Cgroup 的 block I/O 带宽。主要接口包括 bljio.weight(设置权重值,范围在 100 到 1000 之间)、bljio.weight_device(对具体的设备设置权重值,这个值会覆盖 bljio.weigh)、bljio.throttle.read_bps_device(对具体的设备设置每秒读磁盘的带宽)、bljio.throtle.white_bps_device(设置每秒写磁盘的带宽上限)、blkio.throttle.read_iops_device(对指定设备设置每秒读取磁盘的 IOPS 上限)、blkio.throttle.write_iops_device(对指定设备设置每秒写磁盘的 IOPS 上限)。

  6. devices 子系统

    device 子系统用来控制 Cgroup 的进程对哪些设备有访问权限。主要接口包括 devices.list(只读文件,显示目前允许被访问的设备列表,包括类型(a、c 和 b,分别表示所有设备、字符设备和块设备)、设备号(格式为 major:minor 的设备号)、权限(r、w 和 m,分别表示可读、可写、可创建设备节点 mkmod) 3 个条目,比如 “a*:*rmw” 表示所有设备都可以被访问)、devices.allow(只写文件,写入该文件可以允许相应的设备的访问权限)、devices.deny(只写文件,写入该文件可以禁止相应的设备的访问权限)。

Namespace

概念

Namespace 是将内核的全局资源做封装,使得每个 Namespace 都有一份独立的资源,因此不同的进程在各自的 Namespace 内对同一种资源的使用不会互相干扰。

目前 Linux 内核总共实现了 6 种 Namespace:

  • IPC:隔离 System V IPC 和 POSIX 消息队列(进程间通信隔离)。IPC Namespace 使使用相同的标识符(如消息队列的标识符)在两个 Namespace 中代表不同的消息队列,这样使得两个 Namespace 中的进程不能通过 IPC 进行通信了。

  • Nerwork:隔离网络资源。每个 Network Namespace 都有自己的网络设备、IP 地址、路由表、/proc/net 目录、端口号等。新创建的 Network Namespace 会有一个 loopback 设备,除此之外不会有任何网络设备。IP 工具已经支持 Network Namespace:

    # 创建 Network Namespace
    ip netns add new_ns
    # 管理特定的 Namespace
    ip netns execnew_ns ip link list
    # 启用 loopback 网络接口
    ip netns exec new-ns ip link set dev lo up
    # 测试 loopback 可用
    ip netns exec new-ns ping 127.0.0.1
    # 删除 Network Namespace
    ip netns delete new-ns
    
  • Mount:隔离文件系统挂载点。每个进程能看大的文件系统都记录在 /proc/$$/mounts 里。在创建了一个新的 Mount Namespace 后,进程系统对文件系统挂载/卸载的动作就不会影响到其他的 Namespace。

  • PID:隔离进程 ID,不同的 Namespace 里的进程 PID 可以相同。当创建一个 PID Namespace 时,第一个进程的 PID 号是 1,也就是 init进程。init 进程需要负责回收所有的孤儿进程的资源,且发送给 init 进程的任何信号都会别屏蔽,即使是 SIGKILL信号,也就是说容器无法“杀死” init 进程。

  • UTS:隔离主机名和域名,也就是 uname 系统调用使用的结构体 struct utsname 里面的 nodename 和 domainname 这两个字段。UTS 隔离是因为主机名可以用来代替 IP 地址,在网络中访问某台机器,如果不做隔离,这个机制在容器里面就会出问题。

  • User:隔离用户 ID 和组 ID。也就是说进程在 Namespace 里的用户和组 ID 与它在 host 里的 ID 可以不一样,host 的普通用户进程在容器里可以是 0 号用户,也就是 root 用户,这样,进程在容器内可以做各种特权操作。

Namespace 的接口和使用

对于 Namespace 的操作,主要通过 clone、setns 和 unshare 这 3 个 系统调用来完成。

clone 用来创建新的 Namespace。

unshare 为已有的进程创建新的 Namespace。

Docker 镜像

概念

如果说容器提供了一个完整的、隔离的运行环境,那么镜像则是这个运行环境的静态体现,是一个还没运行起来的“运行环境”。

Docker 镜像是一个可定制的 rootfsLinux 根文件系统)。Docker 镜像的另一个创新是层级的并且是可复用的。多数基于相同发行版的镜像,在大多数文件的内容上都是一样的。

Docker 镜像的典型表示方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TcruyDXm-1614309269245)(.\res\image-11-docker image.png)]

Docker image 包含着数据及必要的元数据,数据由一层一层的 image layer 组成,元数据则是一些 JSON 文件,用来描述数据之间的关系以及一些配置信息。

通过 docker inspect 可以得到 image 的元数据,通过这些元数据信息可以得到某个 image 的所有 layer ,进而组合出容器的 rootfs,再加上元数据的配置信息(环境变量、启动参数、体系架构等)作为容器启动时的参数。

Docker 仓库

Registry 称为 Docker 仓库注册服务,它是存放仓库的地方,其上往往可以存放多个仓库。每个仓库集中存放某一类镜像,往往包括多个镜像文件,通过不同的标签(tag)来进行区分。

Docker 公司提供的官方 Registry 叫做 Docker Hub

Registry 本身就是一个开源项目,任何人都可以下载后自己部署一个 Registry

为了满足容灾需求,Docker 仓库后端采用分布式存储(亚马逊 S3、微软 Azure 和华为 UDS 等)。

Registry 内部结构:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二流人物

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值