【云原生•容器】容器的崛起之路
「容器技术作为当前最为热门的技术,为软件行业带来了颠覆性的变革,基础设施云化,软件云原生化已成了整个社会科技发展的必然趋势。云原生技术抽象了底层基础设施,使开发人员能够专注于应用本身开发,而无需担心底层系统架构等运维问题,让应用更加弹性、高效。」
「说起容器很多人的第一反应Docker,可见Docker对容器的影响力之大,容器和Docker之间并不能画等号,容器技术起源其实是很早的,从1979年chroot的首次问世到2013年Docker的横空出世,之间的几十年容器相关技术一直不停地更新迭代,但是这期间并没有实质性突破和明星产品落地,导致容器的影响力和关注度都不高,直到2013年Docker的出现彻底打破这种局面使得容器技术迅速发展,而后 Kubernetes 则趁着容器的"东风",借助 Google 和 CNCF 的强力"背书",击败了 Docker Swarm 和 ApacheMesos,成为了"容器编排"领域的王者至尊。短时间内相继出现两款现象级的爆款产品Docker和Kubernetes,让容器技术一跃成为当前IT界最热门的话题。」
chroot
「提到容器很多人都会联想到Docker,Docker让容器技术得以迅速发展,Docker技术是2013年出现,但是容器技术最早可以追溯1979年chroot技术的诞生,它被认为容器技术的最早雏形。」chroot 是 change root directory的缩写,通俗地说 chroot 就是可以改变某进程的根目录,使这个程序不能访问目录之外的其它目录,在 Linux 系统中,系统默认的目录结构都是以'/',有了 chroot 就意味着我们可以把任何目录更改为当前进程的根目录,这个跟我们在一个容器中看到的文件系统是很相似的。
「下面我们通过一个示例演示下 chroot 功能。」
「1、首先我们在当前目录下创建一个 rootfs 目录」
可以使用现成的 busybox 镜像来创建一个系统,因为镜像一般都是包含 rootfs 的,
[root@VM-4-14-centos docker]# mkdir rootfs_ubuntu
[root@VM-4-14-centos docker]# cd rootfs_ubuntu/
[root@VM-4-14-centos rootfs_ubuntu]# docker export $(docker create ubuntu) -o ubuntu.tar
[root@VM-4-14-centos rootfs_ubuntu]# tar -xf ubuntu.tar
执行完上面的命令后,在 rootfs_ubuntu 目录下,我们会得到一些目录和文件。下面我们使用 ls 命令查看一下 rootfs 目录下的内容。
[root@VM-4-14-centos rootfs_ubuntu]# ls -alh
total 68K
drwxr-xr-x 17 root root 4.0K Nov 1 00:35 .
drwxr-xr-x 6 root root 4.0K Nov 1 00:33 ..
lrwxrwxrwx 1 root root 7 Mar 1 2023 bin -> usr/bin
drwxr-xr-x 2 root root 4.0K Apr 18 2022 boot
drwxr-xr-x 4 root root 4.0K Nov 1 00:40 dev
-rwxr-xr-x 1 root root 0 Nov 1 00:34 .dockerenv
drwxr-xr-x 32 root root 4.0K Nov 1 00:34 etc
drwxr-xr-x 2 root root 4.0K Apr 18 2022 home
lrwxrwxrwx 1 root root 7 Mar 1 2023 lib -> usr/lib
lrwxrwxrwx 1 root root 9 Mar 1 2023 lib32 -> usr/lib32
lrwxrwxrwx 1 root root 9 Mar 1 2023 lib64 -> usr/lib64
lrwxrwxrwx 1 root root 10 Mar 1 2023 libx32 -> usr/libx32
drwxr-xr-x 2 root root 4.0K Mar 1 2023 media
drwxr-xr-x 2 root root 4.0K Mar 1 2023 mnt
drwxr-xr-x 2 root root 4.0K Mar 1 2023 opt
drwxr-xr-x 2 root root 4.0K Apr 18 2022 proc
drwx------ 2 root root 4.0K Mar 1 2023 root
drwxr-xr-x 5 root root 4.0K Mar 1 2023 run
lrwxrwxrwx 1 root root 8 Mar 1 2023 sbin -> usr/sbin
drwxr-xr-x 2 root root 4.0K Mar 1 2023 srv
drwxr-xr-x 2 root root 4.0K Apr 18 2022 sys
drwxrwxrwt 2 root root 4.0K Mar 1 2023 tmp
drwxr-xr-x 14 root root 4.0K Mar 1 2023 usr
drwxr-xr-x 11 root root 4.0K Mar 1 2023 var
「2、执行chroot指令」
可以看到我们在 rootfs 目录和 Linux 系统的目录结构很类似,因为我们下载的是ubuntu镜像,可以理解为这就是一个 ubuntu 系统,下面让我们通过一条命令来见证 chroot 的神奇之处。使用以下命令,可以启动一个 sh 进程,并且把 /disk/study/rootfs 作为 sh 进程的根目录。
[root@VM-4-14-centos rootfs_ubuntu]# chroot /disk/study/docker/rootfs_ubuntu/ /bin/sh
#
此时,我们的命令行窗口已经处于上述命令启动的 sh 进程中。在当前 sh 命令行窗口下,我们使用 ls 命令查看一下当前进程,看是否真的与主机上的其他目录隔离开了。
# ls -alh /
total 68K
drwxr-xr-x 17 root root 4.0K Oct 31 16:35 .
drwxr-xr-x 17 root root 4.0K Oct 31 16:35 ..
-rwxr-xr-x 1 root root 0 Oct 31 16:34 .dockerenv
lrwxrwxrwx 1 root root 7 Mar 1 2023 bin -> usr/bin
drwxr-xr-x 2 root root 4.0K Apr 18 2022 boot
drwxr-xr-x 4 root root 4.0K Oct 31 16:40 dev
drwxr-xr-x 32 root root 4.0K Oct 31 16:34 etc
drwxr-xr-x 2 root root 4.0K Apr 18 2022 home
lrwxrwxrwx 1 root root 7 Mar 1 2023 lib -> usr/lib
lrwxrwxrwx 1 root root 9 Mar 1 2023 lib32 -> usr/lib32
lrwxrwxrwx 1 root root 9 Mar 1 2023 lib64 -> usr/lib64
lrwxrwxrwx 1 root root 10 Mar 1 2023 libx32 -> usr/libx32
drwxr-xr-x 2 root root 4.0K Mar 1 2023 media
drwxr-xr-x 2 root root 4.0K Mar 1 2023 mnt
drwxr-xr-x 2 root root 4.0K Mar 1 2023 opt
drwxr-xr-x 2 root root 4.0K Apr 18 2022 proc
drwx------ 2 root root 4.0K Mar 1 2023 root
drwxr-xr-x 5 root root 4.0K Mar 1 2023 run
lrwxrwxrwx 1 root root 8 Mar 1 2023 sbin -> usr/sbin
drwxr-xr-x 2 root root 4.0K Mar 1 2023 srv
drwxr-xr-x 2 root root 4.0K Apr 18 2022 sys
drwxrwxrwt 2 root root 4.0K Mar 1 2023 tmp
drwxr-xr-x 14 root root 4.0K Mar 1 2023 usr
drwxr-xr-x 11 root root 4.0K Mar 1 2023 var
「3、进程和主机文件系统隔离」
这里可以看到当前进程的根目录已经变成了主机上的 /disk/study/docker/rootfs_ubuntu/ 目录,这样就实现了当前进程与主机的隔离。
使用 cat /etc/os-release
指令查看,显示当前系统是 ubuntu:
使用 ubuntu 系统中用于查看当前当前系统已安装软件dpkg -l
的指令,发现也可以使用:
使用 exit 退出当前sh,再执行 dpkg 指令发现找不到,因为当前是 centos 系统,需要使用 rpm -qa 方式查看:
到此为止,一个目录隔离的"容器"就完成了,这就很类似于:**「我们将镜像(image)下载解压,然后作为容器进程的根文件系统。」**但是,到现在还比较简陋,只具有容器的目录隔离特性,基本还没法称为容器,在上一步(使用 chroot 启动命令行窗口)执行以下命令查看主机名:
# hostname
VM-4-14-centos
主机名和宿主机一致,实际上进程等信息此时也并未隔离。「要想实现一个完整的容器,我们还需要 Linux 的其他三项技术:Namespace、Cgroups 和联合文件系统。Docker 是利用 Linux 的 Namespace 、Cgroups 和联合文件系统三大机制来保证实现的,它的原理是使用 Namespace 做主机名、网络、PID 等资源的隔离,使用 Cgroups 对进程或者进程组做资源(例如:CPU、内存等)的限制,联合文件系统用于镜像构建和容器运行环境。」
Namespace(资源隔离)
「Namespace 其实就是一种隔离机制,主要目的是隔离运行在同一个宿主机上的容器,让这些容器之间不能访问彼此的资源。」这种隔离有两个作用:「第一是可以充分地利用系统的资源,也就是说在同一台宿主机上可以运行多个用户的容器;第二是保证了安全性,因为不同用户之间不能访问对方的资源。」
Docker 就是利用作系统的隔离技术-「NameSpace」, 来实现在同一个操作系统中,不同容器之间的资源独立运行。 Linux Namespace 是 Linux 系统内核提供的一种资源隔离机制,可实现系统资源隔离的列表如下:
「Docker 使用上述6种Namespace技术,基本涵盖了一个小型操作系统的运行要素,包括主机名、用户权限、文件系统、网络、进程号、进程间通信等。」
「下面通过UTS Namespace的案例了解下Namespace如何对资源进行隔离。」
❝
UTS Namespace 主要提供了主机名和域名的隔离,这样每个Docker容器就可以拥有独立的主机名和域名,在网络上可以被视为一个独立的节点,而非宿主机上的一个进程。例如我们的主机名称为 VM-4-14-centos,使用 UTS Namespace 可以实现在容器内的主机名称为 container-docker 或者其他任意自定义主机名。
❞
「1、首先我们使用 unshare 命令来创建一个 UTS Namespace」
# unshare --uts --fork /bin/bash
创建好 UTS Namespace 后,宿主机shell下lsns列出namespace信息,会发现最后一条就是我们使用unshare创建了一个uts类型的namespace:
[root@VM-4-14-centos ~]# lsns
NS TYPE NPROCS PID USER COMMAND
4026531836 pid 121 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 22
4026531837 user 125 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 22
4026531838 uts 119 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 22
4026531839 ipc 121 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 22
4026531840 mnt 119 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 22
4026531856 mnt 1 18 root kdevtmpfs
4026531956 net 121 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 22
4026532293 uts 2 29033 root unshare --uts --fork /bin/bash
「2、回到上步uts命名空间shell下,使用 hostname 命令(hostname 可以用来查看主机名称)设置一下主机名:」
[root@VM-4-14-centos ~]# hostname
VM-4-14-centos
[root@VM-4-14-centos ~]# hostname -b container-docker
[root@VM-4-14-centos ~]# hostname
container-docker
通过 hostname
查看到主机名和宿主机的主机名一致都是 VM-4-14-centos
,让后通过 hostname
指令将 UTS Namespace
内的主机名修改为 container-docker
。
「3、回到宿主机shell下,查看一下主机的 hostname:」
[root@VM-4-14-centos ~]# hostname
VM-4-14-centos
可以看到主机的名称仍然为 VM-4-14-centos
,UTS Namespace隔离环境中修改主机名并不会影响到宿主机。
「4、使用nsenter切换到新创建的 UTS Namespace 下,验证UTS Namespace下修改是否生效:」
[root@VM-4-14-centos ~]# nsenter -t 29033 -u bash
[root@container-docker ~]# hostname
container-docker
从上面可以看到进入到 UTS Namespace,使用 hostname 查看主机名是修改后的 container-docker
,但是宿主机上的主机名并未受到影响,即验证 「UTS Namespace 可以实现主机名隔离。」
Cgroups(资源控制)
Namespace 技术实现在操作系统上解决资源相互隔离的问题,然后对于计算型资源,比如:CPU、内存、磁盘IO、网络IO等,我们需求是希望能限制某个进程的资源使用,而不源资源隔离,于是就出现了Cgroups 技术。**「Cgroups 的全称是 Control Group,它最主要的作用,就是限制进程或者进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。」**通过控制分配特定比例的cpu时间,IO时间,可用内存大小等,用于解决资源限制的问题,防止有些资源消耗较大的容器,将整个物理机器(宿主机)的硬件资源(CPU, Memory) 占满。该技术最初由Google的工程师提出,后来被整合进Linux内核中,2018 年 1 月正式发布的 Linux 内核 v2.6.24 开始提供此能力。
Cgroups中的 重要概念是"子系统",也就是资源控制器,即每种子系统就可以限制一种类型资源,比如cpu子系统是控制cpu时间分配的。下面列出常见的Cgroup子系统及起始内核支持版本:
Cgroups 到目前为止,有两个大版本, Cgroups v1 和 v2 ,Cgroups v1 发布于 2008 年,因此很多功能和特性已经不适应现代操作系统,Cgroups v1 的接任者 Cgroups v2 由 Facebook 的工程师 Tejun Heo 开发,并且合并进了 kernel 4.5 版本。
「如何识别 Linux 节点上的 Cgroups 版本?」
Cgroups 版本取决于正在使用的 Linux 发行版和操作系统上配置的默认 Cgroups 版本。 要检查你的发行版使用的是哪个 Cgroups 版本,请在该节点上运行 stat -fc %T /sys/fs/cgroup/
命令:
stat -fc %T /sys/fs/cgroup/
对于 Cgroups v2,输出为 cgroup2fs
。
对于 Cgroups v1,输出为 tmpfs
。
「Cgroups 被Linux内核支持,有得天独厚的性能优势,发展势头迅猛。在很多领域可以取代虚拟化技术分割资源。Cgroups默认有诸多资源组,可以限制几乎所有服务器上的资源:cpu mem iops,iobandwide,net,device acess等,目前 cgroups 已经成为很多技术的基础,比如 LXC、docker、systemd等。」
「下面通过 Cgroups cpu 子系统的案例了解下 Cgroups 如何对资源进行限制。」
「1、在宿主机上执行如下脚本CPU会飙升到100%:」
[root@VM-4-14-centos ~]# while : ; do : ; done &
[1] 27070
记录下进程PID=27070。
「2、查看CPU资源:」
27070号进程的进程CPU使用率100%,即消耗了1核CPU资源,我这里主机是2核的,从idle一栏看出当前系统CPU使用率在53%左右。
「3、使用 Cgroups cpu 子系统限制该进程只能使用0.2核CPU:」
[root@VM-4-14-centos ~]# cd /sys/fs/cgroup/cpu
[root@VM-4-14-centos cpu]# mkdir cgroups_demo
[root@VM-4-14-centos cpu]# echo 20000 > ./cgroups_demo/cpu.cfs_quota_us
[root@VM-4-14-centos cpu]# echo 27070 > ./cgroups_demo/tasks
「4、再次验证CPU资源使用:」
可以看到27070号进程的进程CPU使用率从100%下降到20%,即消耗0.2核CPU资源,系统CPU使用率从53%下降到15%左右,即Cgroup cpu子系统限制进程的CPU资源使用发生作用。
「下面验证下Docker 对 Cgroups 机制的使用。」
「1、在 docker 启动容器中可以使用 --cpus 参数指定CPU配额:」
[root@VM-4-14-centos ~]# docker run -it --cpus="0.5" nginx /bin/bash
「2、获取容器ID:」
「3、查看 cpu.cfs_quota_us 配置:」
[root@VM-4-14-centos docker]# cd /sys/fs/cgroup/cpu/docker
#找到容器ID开头目录
[root@VM-4-14-centos docker]# cd edf8078bd76950219fcfa801518627912e6504273e503634918fc7665f4ab69d/
[root@VM-4-14-centos edf8078bd76950219fcfa801518627912e6504273e503634918fc7665f4ab69d]# more cpu.cfs_quota_us
50000
cpu.cfs_quota_us 文件中配置50000,即限制使用0.5核CPU资源。
总结
「本文主要讲述了容器发展历程中涉及到的chroot、Namespace、Cgroups等相关容器技术,这些特性主要通过集成到Linux内核中不停迭代更新,这时的容器技术基本处于探索阶段,没有出现相关的明星产品,容器技术的影响力和关注度都还很较低。就这样经过几十年的发展容器走过了它的前半生,稍显平淡无奇,下文将继续关注容器的崛起之路,看看Docker、Kubernetes等产品如何群雄并起,一举奠定容器技术的繁荣盛世。」