这是《Docker 与 Kubernetes 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 容器的本质是被隔离和受限制的进程。Docker 让镜像构建、分发和运行变得简单,Kubernetes 则让成千上万个容器可以被调度、扩缩容、发布和治理。
本章建立容器技术全景:容器解决什么问题、和虚拟机的区别、核心依赖的内核能力、镜像与容器的生命周期、容器编排要解决什么问题。
1.1 环境不一致问题
传统部署:
开发本机
-> 测试服务器
-> 生产服务器
常见问题:
- 操作系统版本不同;
- Python / Java / Node 版本不同;
- 依赖库缺失;
- 配置文件差异;
- 端口占用冲突;
- 启动命令不同;
- 磁盘路径不同。
于是出现经典对话:
在我机器上是好的。
容器把应用运行所需的内容打包成镜像:
Application Code
Runtime
Dependencies
Config Template
Entrypoint
镜像构建一次,可以在开发、测试、生产中运行同一个版本。
1.2 容器不是虚拟机
| 维度 | 虚拟机 | 容器 |
|---|---|---|
| 隔离级别 | 硬件级虚拟化 | 进程级隔离 |
| 内核 | 每个 VM 独立内核 | 共享宿主机内核 |
| 启动速度 | 分钟级 | 秒级 |
| 镜像大小 | GB 级 | MB 到数百 MB |
| 密度 | 较低 | 较高 |
| 安全边界 | 强 | 相对弱 |
| 典型软件 | KVM、VMware | Docker、containerd |
虚拟机:
Host OS
-> Hypervisor
-> Guest OS
-> App
容器:
Host OS
-> Container Runtime
-> App Process
容器共享内核,因此不能把容器视为绝对安全边界。强隔离需求应考虑虚拟机、Kata Containers、gVisor 等方案。
1.3 容器核心依赖的内核能力
Namespace
Namespace 让进程看不到全局系统资源,实现视图隔离。
| Namespace | 隔离内容 |
|---|---|
| PID | 进程编号 |
| NET | 网络栈、网卡、路由、端口 |
| MNT | 文件系统挂载点 |
| UTS | 主机名和域名 |
| IPC | 进程间通信 |
| USER | 用户和组映射 |
| Cgroup | Cgroup 视图 |
| Time | 系统时间视图 |
查看进程 namespace:
ls -l /proc/$$/ns
Cgroup
Cgroup 限制和统计资源,例如:
- CPU;
- 内存;
- IO 带宽;
- PID 数量;
- 设备访问。
示例限制:
docker run --cpus=1 --memory=512m nginx
内存限制不是“建议值”。超过限制的容器可能被 OOM Kill。
UnionFS
容器镜像采用分层文件系统。
Image Layer 1: Base OS
Image Layer 2: Runtime
Image Layer 3: Dependencies
Image Layer 4: App Code
Container Layer: Writable
特点:
- 镜像层只读;
- 容器层可写;
- 层可以被复用;
- 构建缓存可以加速;
- 删除容器会丢失容器层。
1.4 Docker 的核心对象
| 对象 | 说明 |
|---|---|
| Image | 只读分层模板 |
| Container | 镜像的运行实例 |
| Dockerfile | 镜像构建脚本 |
| Registry | 镜像仓库 |
| Volume | 持久化数据卷 |
| Network | 容器网络 |
| Compose | 多容器编排工具 |
基本命令:
docker version
docker info
docker images
docker ps
docker pull nginx:1.27
docker run -d --name web -p 8080:80 nginx:1.27
docker logs -f web
docker exec -it web sh
docker stop web
docker rm web
1.5 镜像与容器生命周期
Dockerfile
-> docker build
-> Image
-> docker push
-> Registry
-> docker pull
-> docker run
-> Container
-> start / stop / restart
-> rm
一个简单 Dockerfile:
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY target/order-service.jar app.jar
ENV JAVA_OPTS="-XX:MaxRAMPercentage=70"
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar"]
生产镜像要求:
- 使用固定基础镜像版本;
- 不把密码打进镜像;
- 使用非 root 用户运行;
- 镜像尽量小;
- 构建可复现;
- 按变更频率分层;
- 扫描漏洞;
- 有清晰健康检查。
1.6 为什么需要 Kubernetes
单机 Docker 可以运行几个容器,但生产系统需要:
| 问题 | Kubernetes 能力 |
|---|---|
| 容器挂了怎么办 | 自愈、重启、重建 |
| 多机如何调度 | Scheduler |
| 流量如何接入 | Service、Ingress |
| 如何滚动发布 | Deployment |
| 如何扩缩容 | HPA、VPA |
| 配置如何管理 | ConfigMap、Secret |
| 存储如何挂载 | PV、PVC |
| 秘密如何保护 | Secret、RBAC |
| 多服务如何治理 | 网络策略、服务网格 |
Kubernetes 集群组成:
Control Plane
|-- API Server
|-- etcd
|-- Scheduler
+-- Controller Manager
Worker Node
|-- kubelet
|-- kube-proxy
+-- Container Runtime
| 组件 | 职责 |
|---|---|
| API Server | 集群入口和 REST API |
| etcd | 保存集群状态 |
| Scheduler | 选择 Pod 运行节点 |
| Controller Manager | 维护期望状态 |
| kubelet | 管理节点上的 Pod |
| kube-proxy | Service 流量转发 |
1.7 声明式模型
传统运维是命令式:
启动 3 个容器
Kubernetes 是声明式:
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: order-service
image: registry.example.com/order-service:1.0.0
含义是:
我希望始终有 3 个健康副本
如果 Pod 异常,控制器会创建新 Pod;如果节点故障,Pod 会被重新调度。
1.8 云原生部署观念
云原生应用应尽量满足:
- 配置外置;
- 无状态优先;
- 健康检查完善;
- 优雅启动和退出;
- 日志输出标准流;
- 指标暴露;
- 水平扩展;
- 故障可自愈;
- 发布可回滚;
- 依赖下游可降级。
容器只是起点。真正改变系统稳定性的是围绕容器的配置、观测、发布和治理体系。
本章小结
容器是依赖 Namespace、Cgroup 和分层文件系统运行的隔离进程,它解决了环境一致性和快速交付问题。Docker 负责单机镜像与容器,Kubernetes 负责多节点调度、自愈、流量、配置、存储和发布。理解容器的隔离边界和声明式模型,是学习云原生的基础。
思考题
- 容器和虚拟机的隔离边界有什么区别?
- Namespace 和 Cgroup 分别解决什么问题?
- 镜像层和容器可写层有什么关系?
- 为什么容器内数据需要 Volume?
- Kubernetes 的声明式模型带来哪些运维方式变化?