docker 与容器技术畅谈

背景

2018 年可以说是 docker 容器技术 最火热的一年,各大厂商,互联网公司都开始构建自己的容器化平台,CI/CD 工具
那么为什么 docker 会这么火,以及 **docker 技术内涵是什么?**,这篇文章,我将以一个学习者的身份详细解析 docker 容器化技术

为什么选择 docker

在容器化时代到来之前,AWS 以及 OpenStack 可谓盛极一时,与此同时还有以 Cloud Foundry 为代表的开源 PaaS 项目。

PaaS 开源项目火热的主要原因主要是它提供了一种应用托管 的能力。各个国内外技术厂商都在进行上云体验。
具体的操作就是:Cloud Foundry 为每种主流编程语言都定义了一种打包格式,类似 cf push app 的操作就是将 app 的可执行文件和启动命令
打包进一个压缩包中,上传到 Cloud Foundry 的存储中。然后 Cloud Foundry 会通过调度器选择一个可以运行这个应用的虚拟机,然后通过这个机器上的 agent 将应用压缩包下载下来启动。

对于一个虚拟机来说,需要在其上启动很多个来自不同用户的应用,Cloud Foundry 会调用操作系统 CgroupsNamespace 机制为每个应用单独创建一个
称作沙盒的隔离环境,然后在沙盒环境中启动这些应用,这样就形成了一个个隔离的环境运行多个应用。

以上就是 PaaS 项目的核心的功能,也就是容器化技术

也正是和 docker 项目相同的技术核心,通过 CgroupsNamespace来构建一个资源和边界,实现沙盒环境。

那么为什么 docker 技术为何在短短几个月就风靡全世界了?

  • 实则正是 docker image

对于 Paas 项目而言,将项目运行的文件打包是一件极其麻烦的事情,对于每种语言,每种框架,每个版本都需要维护一个打好的包。

docker image 可以基于你所需要的系统文件,构建一个可以基于云端环境运行的压缩包。

容器化技术的发展

在以上的文章中,介绍了 docker 技术为何火热的原因。
对于一个大型的业务应用来说,从前端,后端,到数据库系统,文件系统等,往往是需要多个容器的,那么就存在着,如何能够优雅的在云端环境有序地运行。
于是,docker 公司便开发出了一个容器编排化的工具, docker Swarm 项目

与此同时,Mesos 社区也开发出了一个类似的工具Marathon, 它虽然不能像 Swarm 那样提供原生的 Docker API, 但是其却拥有一个独特的核心竞争力:

  • 超大规模集群的管理经验

也就在这个时候,google 公司根据公司多年的发展经验和借助成熟的内部项目Borg系统,宣布了 Kubernetes 项目的诞生。
而也是仅仅在两年之前,各个国内外的互联网厂商开始将自己的项目接入 k8s,宣布了 k8s 时代的到来。

本文仅仅讨论 docker 容器化技术,对于容器化编排不做过多的介绍。

容器到底是怎么一回事?

在上述的文章中,已经简单介绍了几个概念:

  • 虚拟器
  • 容器
  • 容器化技术
  • 镜像

我们可以总结一下:容器其实就是一种沙盒技术,使用 CgroupsNamespace 技术,将你的应用通过限制和隔离的技术在指定的环境中,良好的运行起来

先说结论,容器其实就是一种特殊的进程而已

隔离技术 - Namespace

那么隔离技术是如何实现的?

首先创建一个容器:

1
docker run -it --name=ubuntu-test ubuntu:18.04 /bin/sh
1
2
3
4
5
# ps
PID TTY TIME CMD
1 pts/0 00:00:00 sh
7 pts/0 00:00:00 ps
#

我们可以看到这个容器中,一共有两个进程,通过 ps 可以看到 sh ,就是这个容器内部的第 1 号进程(PID=1)。

这是为什么呢?

本来,当我们在宿主机上运行了一个 /bin/sh 程序的时候,我们的操作系统都会给其分配一个进程编号,例如 PID=100。但是通过 docker run 命令时,docker
就会在当前的进程时,调整其 namespace 相关参数,将其变为在这个 docker 中的 1 号进程。

这种技术就是Linux 里面的 Namespace 机制

我们知道,在 Linux 系统中创建进程的系统调用是 clone():

1
int pid = clone(main_function, stack_size, SIGCHID, NUll);

通过指定 CLONE_NEWPID 参数,而每个 Namespace里的应用进程,都会认为自己是当前容器里的第一号进程。

1
int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL)

在 Linux 操作系统中,还提供了 MountUTSIPCNetworkUser 这些 Namespace,用来对各种不同的进程上下文进行”障眼法”操作。

限制技术 - Cgroups

Linux Cgroups 的全称是 Linux Control Group。它最主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等
在 Linux 中,Cgroups 给用户暴露出来的操作接口是文件系统,即它以文件和目录的方式组织在操作系统的 /sys/fs/cgroup 路径下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#
# mount -t cgroup
cpuset on /sys/fs/cgroup/cpuset type cgroup (ro,nosuid,nodev,noexec,relatime,cpuset)
cpu on /sys/fs/cgroup/cpu type cgroup (ro,nosuid,nodev,noexec,relatime,cpu)
cpuacct on /sys/fs/cgroup/cpuacct type cgroup (ro,nosuid,nodev,noexec,relatime,cpuacct)
blkio on /sys/fs/cgroup/blkio type cgroup (ro,nosuid,nodev,noexec,relatime,blkio)
memory on /sys/fs/cgroup/memory type cgroup (ro,nosuid,nodev,noexec,relatime,memory)
devices on /sys/fs/cgroup/devices type cgroup (ro,nosuid,nodev,noexec,relatime,devices)
freezer on /sys/fs/cgroup/freezer type cgroup (ro,nosuid,nodev,noexec,relatime,freezer)
net_cls on /sys/fs/cgroup/net_cls type cgroup (ro,nosuid,nodev,noexec,relatime,net_cls)
perf_event on /sys/fs/cgroup/perf_event type cgroup (ro,nosuid,nodev,noexec,relatime,perf_event)
net_prio on /sys/fs/cgroup/net_prio type cgroup (ro,nosuid,nodev,noexec,relatime,net_prio)
hugetlb on /sys/fs/cgroup/hugetlb type cgroup (ro,nosuid,nodev,noexec,relatime,hugetlb)
pids on /sys/fs/cgroup/pids type cgroup (ro,nosuid,nodev,noexec,relatime,pids)
rdma on /sys/fs/cgroup/rdma type cgroup (ro,nosuid,nodev,noexec,relatime,rdma)
cgroup on /sys/fs/cgroup/systemd type cgroup (ro,nosuid,nodev,noexec,relatime,name=systemd)
#

其中,例如 cpusetcpumemory 这样的子目录,也叫子系统。这些都是可以被 Cgroups 进行限制的资源种类。

现在我们看一下子系统中关于 CPU 的相关配置文件

1
2
3
# ls /sys/fs/cgroup/cpu
cgroup.clone_children cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release
cgroup.procs cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks

这些输出配置中,可以看到 cpu.cfs_period_uscpu.cfs_quota_us,这两个参数需要组合使用,可以用来限制进程在长度cfs_period的一段时间内,
只能被分配到总量为 cfs_quota 的 CPU 时间。

另开一个终端,输入:

1
2
3
while : ; do : ; done &

23223

键入:

1
2
top
%Cpu0 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st

可以看到 CPU 的使用率已经 100% 了, quota 还没有任何限制(-1)

1
2
3
4
$ cat /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us 
-1
$ cat /sys/fs/cgroup/cpu/container/cpu.cfs_period_us
100000

在现在的默认配置中,在 100 ms 中的 cpu 时间中,使用 100ms 的 CPU 时间,也就是 100 %。
现在调整为 20%:

1
$ echo 20000 > /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us

然后将限制的进程 PID 写入 container 组里的 tasks 文件:

1
2

$ echo 23223 > /sys/fs/cgroup/cpu/container/tasks

然后使用 top 指令查看:

1
%Cpu0 : 20.3 us, 0.0 sy, 0.0 ni, 79.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st

现在可以看到已经为 20% 左右了
其他关于 blkiocpusetmemory 可以自行尝试

docker 运行时的限制参数

1
$ docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu:18.04 /bin/bash

这个配置就会将对应的值写入到 docker-container 的配置文件中,这个 Docker 容器,只能使用到 20% 的 CPU 带宽。

虚拟机和容器对比

虚拟机的工作原理,通过硬件虚拟化功能,模拟出了运行一个操作系统需要的各种硬件,例如 CPU、内存、I/O 设备等等。
docker 项目帮助用户启动的,还是原来的应用进程,但是 Docker 会为它们加上了各种各样的 Namespace 参数。

所以,”敏捷” 和 “高性能” 是容器相较于虚拟机最大的优势,也是它能够在 PaaS 这种更细粒度的资源管理平台上大行其道的重要原因。

-------------THANKS FOR READING-------------