回顾
当我们最早学习 Docker 的时候,我们总是会和虚拟机做对比,大家可能会经常看到下面这张图:
下图来自阿里云的一篇文章【1】,我们可以对比看:
以下是虚拟化和容器化的一个对比(虚拟机 vs 容器):
特性 |
虚拟机 |
容器 |
隔离级别 |
系统级 |
进程级 |
隔离技术 |
Hypervisor虚拟化整套硬件环境 |
Cgroups+ Namespace虚拟化进程运行环境 |
系统内核 |
独立内核 |
共享内核 |
性能开销 |
2~5% |
<0.5% |
启动时间 |
分钟级 |
秒级 |
磁盘占用 |
GB级 |
MB级 |
部署密度 |
单机一般几十个 |
单机支持上千个容器 |
那这里说到的共享内核是什么含义呢?为了理解共享内核的含义,首先我们要来了解 Linux 内核和发行版两者的联系与区别。
Linux 内核 vs发行版
Linux 内核是 Linux 操作系统(OS)的核心组件,它是计算机硬件与其进程之间的核心接口。详细可以参考:《BPF 之巅:洞悉Linux系统和应用性能》读书笔记(三)Linux Kernel 相关知识。
内核是运行在特殊 CPU 模式下的程序,这一特殊的 CPU 模式叫做【内核态】,在这一状态下,设备的一切访问及特权指令的执行都是被允许的。用户程序(进程)运行在【用户态】下,对于内核特权操作(例如 I/O)的请求是通过系统调用传递的。
内核对上层屏蔽了底层硬件操作的细节,对其提供了统一的系统调用对硬件进行统一的操作。但是系统调用接口一般比较原始,涉及和操作系统相关的细节,不同操作系统之间的系统调用基本完全不同,哪怕同系列的Linux与UNIX都不相同。
基于以上原因,一般应用程序是通过标准的运行库来使用系统调用,运行库的优点是本身是语言级别的,设计比较友好,标准、形式统一,不会随着操作系统或编译器的变化而变化。
Linux上的运行库即是glibc,其位于用户程序与系统调用之间
从这里可以看出,容器实际上是通过系统调用层(glibc)和操作系统内核交互,这也是它与主机共享内核的秘密。
Linux 发行版是一个包含 Linux 内核及其周边工具、库、软件包和系统配置的完整操作系统软件包。它是为了方便用户安装、配置和使用 Linux 操作系统而创建的。
发行版选择特定版本的 Linux 内核,并将其与其他组件(用户空间工具、图形界面、包管理工具等)组合在一起,形成一个可用的操作系统。
不同的发行版针对不同的用户需求和使用场景。有些发行版专注于桌面用户友好的体验,而其他发行版可能专注于服务器、嵌入式系统等。
发行版通常有自己的包管理系统,用于方便用户安装、更新和卸载软件。例如,Debian 系统使用 apt,Red Hat 系统使用 yum 或 dnf。
发行版会提供定期的更新,包括安全更新、软件包更新等。它们也提供技术支持,有时有长期支持(LTS)版本。
由于不同发行版使用的都是同一个Linux内核,所以在内核的层面并不存在兼容性问题。
验证
查看主机内核信息:
uname -r3.10.0-1160.102.1.el7.x86_64
查看主机上 glibc 的版本信息:
ldd --versionldd (GNU libc) 2.17Copyright (C) 2012 Free Software Foundation, Inc.This is free software; see the source for copying conditions. There is NOwarranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.由 Roland McGrath 和 Ulrich Drepper 编写。whereis libclibc: /usr/lib64/libc.so
通过 lsmod 查看主机上内核模块信息:
lsmodModule Size Used bynetlink_diag 12669 0xt_nat 12681 0ebtables 35009 0tun 40260 0ipip 13465 0tunnel4 13252 1 ipipip_tunnel 25163 1 ipiparptable_filter 12702 1macvlan 19239 0veth 13458 0......
现在我们使用eclipse-temurin:8u392-b08-jdk这个镜像来做验证。下面信息来自 docker hub ,我们可以查看它包含了哪些 package:
我们查看一下镜像的信息
docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEeclipse-temurin 8u392-b08-jdk 0cd6a34b50c5 2 weeks ago 318MB
现在我们来运行这个镜像,并执行相关命令:
docker run -d --name test eclipse-temurin:8u392-b08-jdk tail -f /dev/nulldocker exec -it test /bin/bashuname -r3.10.0-1160.102.1.el7.x86_64ldd --versionldd (Ubuntu GLIBC 2.35-0ubuntu3.5) 2.35Copyright (C) 2022 Free Software Foundation, Inc.This is free software; see the source for copying conditions. There is NOwarranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.Written by Roland McGrath and Ulrich Drepper.
我们可以看到容器中的内核版本和主机一致,但是 glibc 的版本和主机不一样。
如果我们需要在容器中加载内核模块,可以参考【3】。不过这里操作的前提是,容器启动使用了privileged 模式。这实际上就是在主机上执行加载内核操作。
总结
容器和主机共享内核,这得益于Linux有一个统一的内核体系。不同的 Linux 发行版使用了相同的内核体系,这使得基于不同 Linux 发行版构建的容器可以在一个 Linux 发行版上运行。那容器和主机共享内核有什么影响吗?具体来说对于运行在宿主机上的容器进程分三种情况【2】:
相同内核版本的情况下,不存在兼容性问题。
宿主机内核版本高于容器发行版内核版本时,由于高版本内核保证向后兼容性(backward compatibility),一般也可以正常运行。
宿主机内核版本低于容器发行版内核,若容器中使用了低版本内核中不存在的系统调用,则无法正常运行。
参考
容器和虚拟机之间有什么区别?
https://www.alibabacloud.com/zh/knowledge/difference-between-container-and-virtual-machine
尝试理解 Linux 容器进程与宿主机共享内核的具体含义
https://www.cnblogs.com/AcAc-t/p/linux_container_share_kernel_meaning.html
Loading Kernel Modules in a Docker Container
https://www.baeldung.com/linux/docker-container-kernel-modules