1. Linux 内核三大件
Namespace(环境隔离)
Namespace 负责“视觉隔离”,让容器进程觉得自己拥有独立的资源栈。
PID Namespace:容器内的进程 ID 从 1 开始,看不到宿主机的其他进程。
Net Namespace:拥有独立的网络设备、IP 地址、端口范围。
Mount Namespace:挂载独立的文件系统,容器内的
/etc或/usr与宿主机隔离。UTS/IPC/User Namespace:分别隔离主机名、进程间通信和用户权限。
Cgroups (Control Groups,资源限制)
防止某个容器耗尽宿主机的全部资源。
CPU 限制:控制容器占用的 CPU 时间片比例。
内存限制:限制容器使用的内存上限(OOM 机制)。
I/O 限制:控制磁盘读写带宽。
UnionFS (联合文件系统,镜像存储)
容器镜像(Image)之所以能做到分层复用和秒级启动,全靠 Copy-on-Write (CoW) 机制。
分层存储:镜像是由多个只读层叠在一起的。当你修改容器文件时,它会把底层文件复制到最顶部的“可读写层”进行修改。
存储驱动:如 Overlay2、AUFS。
2. 容器 VS 虚拟机
核心区别在于:是否拥有独立的内核。
3. Docker
架构
Docker 并非一个单一的二进制程序,它是一个典型的 C/S 架构 系统:
Docker Client:我们常用的
docker命令行工具。它不负责创建容器,只负责把你的指令(如run,build)翻译成 REST API 调用。Docker Daemon (dockerd):守护进程,运行在宿主机上。它监听 API 请求,管理镜像、容器、网络和磁盘卷。
containerd & runc:这是更底层的组件。Docker 为了符合 OCI(开放容器标准),将容器运行时拆分了出来。
runc负责真正调用内核接口创建容器,containerd负责管理容器的生命周期。
为什么 Docker 比 VM 快
这源于其独特的文件系统和进程隔离。
联合文件系统:
Docker 镜像是由一系列只读层 (Read-only Layers) 组成的。
分层设计:如果你有 10 个 Java 应用镜像,它们都基于
openjdk:17,那么在磁盘上,这份基础镜像只会被存储一份。写时复制 (Copy-on-Write):当你启动容器时,Docker 会在镜像层顶部加一个极薄的可读写层 (Container Layer)。所有对文件的修改都发生在这里,原始镜像毫发无伤。
进程隔离(共享内核与 Cgroups 限制):
由于 Docker 共享宿主机内核,它不需要像 VM 那样加载复杂的 BIOS、引导程序和内核镜像。它只是宿主机上的一个进程,通过 Cgroups 强制规定了:
这个进程组最多只能用 512MB 内存。
这个进程组最多占用 10% 的 CPU 时间片。
三大核心对象
镜像 (Image) —— 静态的“类”
镜像是一个包含了运行程序所需的所有依赖(库、环境变量、配置)的压缩包。它是不可变的。
容器 (Container) —— 动态的“实例”
容器是镜像运行时的表现。一个镜像可以启动多个容器,每个容器相互隔离。
仓库 (Registry) —— “代码托管所”
类似于 Maven 仓库或 GitHub,用于存放和分发镜像。
网络模式
在单机环境下,Docker 提供四种主要网络模式,这是后端排查网络连接问题的关键:
Bridge (默认):每个容器分配一个虚拟 IP,通过宿主机的
docker0网桥进行通信,并利用 NAT(网络地址转换)访问外网。Host:容器直接共享宿主机的网络栈(没有自己的 IP)。性能最高,但端口容易冲突。
None:容器只有本地回环网卡(lo),不联网。适用于高安全要求的计算任务。
Container (联合网络):新容器共享一个已存在容器的网络命名空间。K8s 的 Pod 内部多个容器通信就是基于这种原理。
如何合理使用
减少镜像层数:每一个
RUN,COPY,ADD指令都会产生一层。尽量用&&合并命令。使用
.dockerignore:防止把target/目录或.git等无用大文件打包进镜像,减少镜像体积。多阶段构建 (Multi-stage Builds):
阶段一:用带有 Maven 的镜像编译代码。
阶段二:只把编译好的
.jar拷入只有 JRE 的精简镜像。结果:镜像体积可能从 800MB 缩减到 100MB。
信号处理:容器内的进程 PID 必须为 1 才能正确接收
SIGTERM信号。如果使用sh -c "java -jar ..."启动,Java 进程将无法优雅停机。