容器底层原理¶
学习目标¶
学完本章后,学习者应该能够:
- 理解容器不是虚拟机,而是 Linux 隔离与限制机制的组合。
- 解释 namespace、cgroups、OverlayFS、capabilities、seccomp 的作用。
- 理解容器资源限制如何影响 Go 服务。
- 能把容器底层知识连接到 Kubernetes 排障。
容器到底是什么¶
容器本质上是宿主机上的进程,只是这个进程被隔离、限制,并使用了特定文件系统视图。
简化理解:
它比虚拟机轻量,因为容器共享宿主机内核。
namespace¶
namespace 用于隔离进程看到的系统资源。
常见 namespace:
| namespace | 隔离内容 |
|---|---|
| PID | 进程 ID 视图 |
| NET | 网络设备、IP、端口 |
| MNT | 挂载点和文件系统视图 |
| UTS | hostname |
| IPC | 进程间通信资源 |
| USER | 用户和用户组映射 |
容器内看到的 PID 1,通常只是该 PID namespace 中的一号进程。
cgroups¶
cgroups 用于限制和统计资源:
- CPU。
- 内存。
- IO。
- 进程数量。
Kubernetes 的 CPU limit、memory limit 底层依赖 cgroups。容器超过 memory limit 时,可能被 OOMKilled。
OverlayFS¶
容器镜像由多层文件系统组成。OverlayFS 把只读镜像层和可写容器层组合成一个统一视图。
这解释了:
- 镜像为什么可以复用层。
- 容器写文件为什么写在可写层。
- 容器删除后,容器层数据通常也会消失。
- 日志和持久化数据不应该随便写在容器本地层。
capabilities 与 seccomp¶
Linux capabilities 把 root 权限拆成更细粒度的能力。容器可以只保留需要的能力。
seccomp 可以限制进程能使用哪些系统调用,减少攻击面。
生产容器应该尽量:
- 使用非 root 用户运行。
- 删除不必要 capabilities。
- 避免特权容器。
- 使用只读根文件系统时谨慎设计临时目录。
Go 后端实际应用例子¶
例子一:容器内处理 SIGTERM¶
Kubernetes 停止 Pod 时会发送 SIGTERM。Go 服务应该优雅退出:
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
defer stop()
go func() {
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_ = server.Shutdown(shutdownCtx)
}()
这能减少发布或扩缩容时的请求中断。
例子二:尊重容器内存限制¶
Go 新版本可以配合 GOMEMLIMIT 控制运行时目标内存:
在 Kubernetes 中,可以通过环境变量把内存限制传递给 Go runtime,减少 GC 与 OOM 风险。
常见误区¶
- 误区一:容器是小型虚拟机。
容器共享宿主机内核,不是完整虚拟机。内核参数、文件系统、网络和安全边界都要用容器视角理解。
- 误区二:容器内写文件就是持久化。
容器可写层随容器生命周期变化。需要持久化的数据应使用 volume、对象存储或外部数据库。
- 误区三:容器里进程退出无所谓。
容器主进程退出,容器就会停止。PID 1 的信号处理和子进程回收也需要注意。
线上问题案例¶
某 Go 服务在 Kubernetes 中没有处理 SIGTERM。滚动发布时 Pod 收到终止信号后直接退出,正在处理的请求被中断,调用方出现大量 502。
修复方式是实现 graceful shutdown,配置 readiness probe、preStop hook 和合理的 terminationGracePeriodSeconds。
实战任务¶
为 Go HTTP 服务设计容器友好的运行方案:
- 非 root 用户运行。
- 正确处理
SIGTERM。 - 日志输出到 stdout。
- 配置 CPU 和内存限制。
- 思考本地临时文件写到哪里。
参考答案
Dockerfile 中可以创建非 root 用户,并使用 USER 切换;服务启动后监听 SIGTERM 并调用 http.Server.Shutdown;日志输出到 stdout 交给容器运行时采集;Kubernetes 中配置 requests 和 limits,并根据内存限制设置 GOMEMLIMIT。
临时文件可以写到明确挂载的临时目录,并设置容量和清理策略。不能把容器可写层当作长期持久化存储。
面试题¶
1. 容器和虚拟机有什么区别?¶
参考答案
虚拟机通常有独立操作系统内核,隔离边界更重。容器共享宿主机内核,通过 namespace、cgroups、文件系统和安全策略实现隔离与限制。
容器更轻量、启动快、镜像分层复用好,但安全边界和内核依赖需要特别关注。
2. namespace 和 cgroups 分别解决什么问题?¶
参考答案
namespace 解决“看见什么”的问题,例如进程、网络、挂载点、hostname 的隔离。cgroups 解决“能用多少”的问题,例如 CPU、内存、IO、进程数量的限制与统计。
容器隔离通常依赖 namespace,资源限制通常依赖 cgroups。
3. 为什么容器中的 Go 服务要处理 SIGTERM?¶
参考答案
Kubernetes 删除或更新 Pod 时,会先发送 SIGTERM,给进程机会优雅退出。如果服务不处理信号,可能直接退出,导致正在处理的请求中断。
正确做法是停止接收新请求,等待已有请求完成,并在超时时间内释放连接、刷新必要状态,然后退出。