Docker 知识体系
一、Docker 和虚拟机的区别
Docker 不是虚拟机。容器共享宿主机内核,虚拟机则有完整 Guest OS 和独立内核。
| 维度 | Docker 容器 | 虚拟机 |
|---|---|---|
| 隔离层级 | 进程级隔离,共享宿主机内核 | 硬件级虚拟化,独立操作系统 |
| 启动速度 | 秒级甚至更快 | 通常更慢 |
| 资源开销 | 较小 | 较大 |
| 隔离强度 | 相对弱,依赖内核能力 | 更强 |
| 典型场景 | 应用交付、微服务部署、CI | 多系统运行、强隔离环境 |
二、核心原理:Namespace、UnionFS
Docker 本质上不是“发明了一种新进程”,而是把 Linux 已有能力组合起来。
1. Namespace:让进程“看不见别人”
Namespace 负责隔离视图,让容器里的进程以为自己拥有独立的系统环境。
| Namespace | 隔离内容 | 例子 |
|---|---|---|
| PID | 进程号空间 | 容器内进程可以看到自己是 PID 1 |
| NET | 网络设备、IP、端口、路由表 | 每个容器有自己的网卡和端口空间 |
| MNT | 挂载点 | 容器看到自己的根文件系统 |
| UTS | 主机名和域名 | 容器可以有独立 hostname |
| IPC | 进程间通信资源 | 隔离共享内存、信号量等 |
| USER | 用户和用户组 ID | 容器内 root 可映射为宿主机普通用户 |
一句话:Namespace 管“看见什么”。
2. UnionFS:镜像为什么能分层
镜像由多层只读 Layer 叠加而成,容器启动后会在最上面加一个可写层。
好处:
- 复用:多个镜像可以共享相同基础层,例如
ubuntu、alpine。 - 缓存:Dockerfile 每一条指令通常会生成一层,未变化的层可以复用。
- 回滚:镜像天然有版本和层级结构,便于分发。
容器删除后,默认可写层也会删除;需要持久化的数据应该放到数据卷里。
三、镜像、容器、仓库
1. 三个概念
| 概念 | 含义 |
|---|---|
| 镜像 Image | 只读模板,包含应用和依赖 |
| 容器 Container | 镜像运行后的实例,有自己的可写层 |
| 仓库 Registry | 存放和分发镜像的地方,如 Docker Hub、Harbor |
关系可以理解为:
Dockerfile -> build -> Image -> run -> Container
四、Dockerfile 怎么写
Dockerfile 是镜像构建说明书,核心目标是:可复现、体积小、缓存友好、安全。
1. CMD 和 ENTRYPOINT 区别
CMD更像默认参数,运行时容易被覆盖。ENTRYPOINT更像固定入口,适合封装一个可执行程序。
ENTRYPOINT ["java", "-jar", "app.jar"]
CMD ["--server.port=8080"]
运行时:
docker run app --server.port=9090
会把 CMD 替换成新的参数,但入口仍是 java -jar app.jar。
2. Go 项目示例:多阶段构建
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server ./cmd/server
FROM alpine:3.19
WORKDIR /app
COPY --from=builder /app/server /app/server
EXPOSE 8080
ENTRYPOINT ["/app/server"]
多阶段构建的意义:
- 编译环境留在 builder 阶段,最终镜像只放运行产物。
- 减少镜像体积。
- 降低攻击面。
五、Docker 网络
Docker 常见网络模式:
| 模式 | 特点 | 使用场景 |
|---|---|---|
| bridge | 默认模式,容器接入 docker0 网桥 | 单机容器互通 |
| host | 共享宿主机网络命名空间 | 追求性能、端口直接暴露 |
| none | 无网络 | 极端隔离场景 |
| overlay | 跨主机容器网络 | Swarm、K8s 类场景 |
1. bridge 模式
默认情况下,Docker 会创建一个 docker0 网桥。容器连接到这个网桥上,容器访问外部网络时通常通过 NAT 出去。
docker network ls
docker network inspect bridge
端口映射:
docker run -p 8080:80 nginx
含义是:宿主机 8080 端口转发到容器内 80 端口。
2. 自定义网络
推荐使用自定义 bridge 网络,因为容器名可以自动 DNS 解析。
docker network create app-net
docker run -d --name mysql --network app-net mysql:8
docker run -d --name app --network app-net myapp:v1
此时 app 容器可以通过 mysql:3306 访问数据库。
3. host 模式
docker run --network host nginx
容器直接使用宿主机网络,不再有独立端口空间。
优点:少一层 NAT,性能和排查更直接。 缺点:隔离性下降,端口冲突风险更高。
六、数据卷
容器的可写层不适合保存长期数据,删除容器后数据可能一起丢失。持久化应该使用 Volume 或 Bind Mount。
1. Volume
由 Docker 管理,默认存放在 Docker 的数据目录下。
docker volume create mysql-data
docker run -d \
--name mysql \
-v mysql-data:/var/lib/mysql \
mysql:8
适合数据库、上传文件、需要由 Docker 管理生命周期的数据。
2. Bind Mount
把宿主机目录挂载进容器。
docker run -d \
--name nginx \
-v /host/html:/usr/share/nginx/html \
nginx
适合本地开发、配置文件挂载、日志目录挂载。
3. Volume 和 Bind Mount 对比
| 维度 | Volume | Bind Mount |
|---|---|---|
| 管理方式 | Docker 管理 | 用户指定宿主机路径 |
| 可移植性 | 更好 | 依赖宿主机目录结构 |
| 适合场景 | 生产数据持久化 | 开发调试、配置挂载 |
| 风险 | 需要理解 volume 生命周期 | 容易误改宿主机文件 |
七、Docker Compose
Compose 适合描述一组服务,比如应用、MySQL、Redis、NSQ 一起启动。
services:
app:
image: myapp:v1
ports:
- "8080:8080"
environment:
MYSQL_ADDR: mysql:3306
depends_on:
- mysql
mysql:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: app
volumes:
- mysql-data:/var/lib/mysql
volumes:
mysql-data:
注意:depends_on 只能保证启动顺序,不保证 MySQL 已经准备好接受连接。应用侧仍要有重试机制。