容器基础Docker
本章帮助你理解容器是什么、为什么需要它,以及掌握 Docker 的核心用法。学完本章后,你将能够编写实用的 Dockerfile、使用常用 Docker 命令管理容器和镜像,并为后续学习 Kubernetes 打下基础。
容器 vs 虚拟机#
容器和虚拟机都做"隔离",但思路和开销完全不同。
虚拟机(VM) 是在物理机上跑一个完整的操作系统,每个 VM 有自己的内核、完整的 OS 文件系统、虚拟硬件。隔离性强,但重。
容器 共享宿主机的操作系统内核,通过 Linux namespace 和 cgroup 实现进程级隔离。不需要跑多个操作系统,所以轻。
| 对比维度 | 虚拟机 | 容器 |
|---|---|---|
| 隔离机制 | Hypervisor 虚拟化硬件,每个 VM 有独立 OS 内核 | Linux namespace(隔离视图)+ cgroup(限制资源),共享宿主机内核 |
| 启动速度 | 分钟级(要启动整个 OS) | 秒级(只需启动进程) |
| 资源开销 | 高:每个 VM 占用数百MB内存跑OS本身 | 低:容器进程本身只占几MB,几乎没有额外开销 |
| 镜像大小 | GB 级(包含完整 OS) | MB 级(只含应用和依赖) |
| 适用场景 | 需要强隔离、运行不同 OS(如 Windows on Linux)、传统单体应用 | 微服务、CI/CD、大规模集群部署、Kubernetes 调度单元 |
一句话总结:虚拟机是"装修好的整套房子",容器是"合租房里的一间卧室"——共享水电网(内核),但各自有独立空间(namespace)和用电上限(cgroup)。
注意事项:容器不是虚拟机,容器里的"root"和宿主机的 root 在某种程度上是同一个——这也是为什么生产环境容器不能以 root 运行。
镜像、容器与 Layer#
核心概念#
镜像(Image) 是一个只读的文件包,包含运行应用所需的一切:代码、运行时、系统工具、系统库、配置文件。
容器(Container) 是镜像的运行实例。镜像好比是"程序",容器是"进程"——镜像是静态的,容器是动态的。
Layer(层) 是镜像的构成单元。Docker 镜像不是一个大文件,而是由多层叠加而成的。每一层是一个只读的文件系统快照,上一层可以覆盖下一层的文件(写时复制,Copy-on-Write)。
Layer 类比:乐高积木#
把镜像构建过程想象成搭乐高:
- 第一层(基础层):放一块 8x8 的底板 → 对应
FROM ubuntu:22.04 - 第二层:在底板上放几个块 → 对应
RUN apt-get install -y curl - 第三层:再放几个块 → 对应
RUN pip install -r requirements.txt - 第四层:放一个小人 → 对应
COPY . /app - 最顶层(容器层):可以在底板上放临时块,拿走也不影响下面的结构 → 对应容器运行时可写层
每一层都是只读的,多个镜像可以共享相同的层(比如 10 个基于 ubuntu:22.04 的镜像,底层的 Ubuntu 只需在磁盘上存一份)。
镜像层(只读,叠加组成完整镜像):
┌─────────────────────────┐ Layer 5: CMD ["python", "app.py"] (仅 1KB 元数据)
├─────────────────────────┤ Layer 4: COPY . /app (你的代码,几 MB)
├─────────────────────────┤ Layer 3: RUN pip install -r requirements.txt
├─────────────────────────┤ Layer 2: RUN apt-get install -y python3
├─────────────────────────┤ Layer 1: FROM ubuntu:22.04 (几十 MB)
└─────────────────────────┘
容器运行时(可写层,容器删除后可写层消失):
┌─────────────────────────┐ writable layer: 容器运行时产生的临时文件
├═════════════════════════┤ (镜像层只读,通过 CoW 机制"看起来"可修改)
│ 镜像层(只读) │
└─────────────────────────┘注意事项:Layer 越多,镜像越大,拉取越慢。合并多个
RUN指令到一行(用&&连接)可以减少层数。
Dockerfile 写法与构建#
基本结构#
# 指定基础镜像(必须有)
FROM python:3.11-slim
# 设置工作目录(后续指令的当前目录)
WORKDIR /app
# 先复制依赖声明文件(利用缓存,这步很关键)
COPY requirements.txt .
RUN pip install -r requirements.txt
# 再复制应用代码(代码变动不会导致上面依赖层缓存失效)
COPY . .
# 暴露端口(文档性质,实际映射靠 docker run -p)
EXPOSE 8000
# 容器启动命令
CMD ["python", "app.py"]多阶段构建#
多阶段构建是生产 Dockerfile 的核心技巧:构建阶段需要编译工具和完整依赖,运行时只需要最终产物。
# ========== 阶段1:构建 ==========
FROM golang:1.22-alpine AS builder
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# CGO_ENABLED=0 生成纯静态二进制,不依赖系统库
RUN CGO_ENABLED=0 GOOS=linux go build -o app .
# ========== 阶段2:运行 ==========
FROM alpine:3.19
# 从构建阶段复制产物(只复制需要的文件)
COPY --from=builder /build/app /app
EXPOSE 8080
CMD ["/app"]最终镜像只有阶段2的内容,构建阶段的编译器、源码、中间产物都不会进入最终镜像。上面的例子最终镜像只有不到 20MB(alpine + 静态二进制),而如果直接用 golang 镜像运行,镜像会超过 300MB。
缓存策略要点#
Docker 构建缓存的核心规则:指令不变,缓存就命中;前面任何一层失效,后面所有层都失效。
所以这行指令的顺序非常关键:
# ❌ 错误:每次改一行代码,整个镜像重新构建
COPY . . # 代码变动 → 此层及后面所有层缓存失效
RUN pip install -r requirements.txt
# ✅ 正确:依赖文件不变就复用缓存
COPY requirements.txt .
RUN pip install -r requirements.txt # 这层缓存命中,省去几十秒安装时间
COPY . . # 只有代码层会重建经验法则:把变更频率低的指令放前面,变更频率高的放后面。
| 指令顺序 | 变更频率 | 说明 |
|---|---|---|
FROM | 极低 | 基础镜像,很少变动 |
RUN apt-get install | 低 | 系统依赖,变动不多 |
COPY requirements.txt / go.mod | 中 | 依赖声明,变动较少 |
RUN pip install / go mod download | 中 | 依赖安装,依赖声明不变就命中缓存 |
COPY . . | 高 | 业务代码,每次变动 |
RUN build | 高 | 构建步骤,代码变动后执行 |
注意事项:
--no-cache参数会禁用缓存,调试 Dockerfile 时有用,但日常构建不要加。
常用命令速查#
docker run —— 创建并启动容器#
# 基本启动(前台运行,Ctrl+C 停止)
docker run python:3.11 python app.py
# 后台运行(-d detached)
docker run -d --name my-web -p 8080:80 nginx:alpine
# 端口映射(-p 主机端口:容器端口)
docker run -d -p 3306:3306 mysql:8.0
# 挂载数据卷(-v 主机路径:容器路径)
docker run -d -v /data/mysql:/var/lib/mysql mysql:8.0
# 环境变量(-e)
docker run -d -e MYSQL_ROOT_PASSWORD=secret mysql:8.0
# 自动删除容器(退出后自动 rm,适合临时测试)
docker run --rm -it python:3.11 bash
# 交互式终端(-it)
docker run -it --name my-ubuntu ubuntu:22.04 bashdocker exec —— 在运行中的容器里执行命令#
# 进入容器 shell(调试首选)
docker exec -it my-web bash
# 直接执行命令
docker exec my-web ls /app
# 没有 bash 的镜像用 sh
docker exec -it my-web shdocker logs —— 查看容器日志#
# 查看日志
docker logs my-web
# 实时跟踪(类似 tail -f)
docker logs -f my-web
# 查看最近 100 行
docker logs --tail 100 my-webdocker ps —— 查看容器列表#
# 查看运行中的容器
docker ps
# 查看所有容器(包括已停止的)
docker ps -a
# 只显示容器 ID(脚本处理时有用)
docker ps -qdocker rm / docker rmi —— 删除容器和镜像#
# 删除已停止的容器
docker rm my-web
# 强制删除运行中的容器
docker rm -f my-web
# 删除镜像
docker rmi nginx:alpine
# 清理所有已停止的容器
docker container prune
# 清理所有未使用的镜像
docker image prune -a注意事项:
docker rm删容器,docker rmi删镜像,别搞混。容器删除后数据卷默认不删除,加-v参数可以连同匿名卷一起删除。
数据卷(Volume)#
容器本身的生命周期是短暂的,容器删除后容器内产生的数据也会消失。数据卷解决的就是"容器死了,数据还活着"的问题。
Volume vs Bind Mount#
| 对比维度 | Volume(数据卷) | Bind Mount(绑定挂载) |
|---|---|---|
| 存储位置 | Docker 管理的区域(/var/lib/docker/volumes/) | 宿主机上任意指定的路径 |
| 创建方式 | docker volume create mydata 或 -v mydata:/data | -v /host/path:/container/path |
| 适合场景 | 数据库数据、应用持久化数据 | 配置文件挂载、开发时代码热加载 |
| 权限管理 | Docker 自动处理 | 需手动确保宿主机目录权限正确 |
| 备份迁移 | docker run --rm -v mydata:/data -v $(pwd):/backup alpine tar czf /backup/data.tar.gz /data | 直接操作宿主机文件 |
使用示例#
# 创建命名数据卷
docker volume create mysql-data
# 使用数据卷(Volume)
docker run -d \
--name mysql \
-v mysql-data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=secret \
mysql:8.0
# 使用绑定挂载(Bind Mount)
# 适合开发场景:本地改代码,容器内实时生效
docker run -d \
--name web \
-v $(pwd):/app \
-p 8000:8000 \
python:3.11-slim \
python /app/app.py
# 查看数据卷
docker volume ls
# 查看数据卷详情(包括实际存储路径)
docker volume inspect mysql-data注意事项:生产环境优先用 Volume,因为 Docker 管理权限更可靠,且支持云存储驱动(如 AWS EBS)。Bind Mount 更适合开发环境。
镜像仓库#
镜像仓库(Registry)是存储和分发镜像的服务。默认是 Docker Hub(docker.io),也可以搭建私有仓库。
三步操作:tag → push → pull#
# 1. tag:给镜像打上仓库地址的标签
docker tag myapp:latest registry.example.com/myapp:1.0.0
# 2. push:推送到仓库
docker push registry.example.com/myapp:1.0.0
# 3. pull:从仓库拉取镜像
docker pull registry.example.com/myapp:1.0.0私有仓库#
生产环境通常搭建私有镜像仓库,常见方案:
- Harbor:最受欢迎的开源私有仓库,带 UI、RBAC、漏洞扫描
- Docker Registry:Docker 官方开源版本,功能简单
- 云厂商托管:AWS ECR、阿里云 ACR、腾讯云 TCR
私有仓库的概念了解即可,后续 Kubernetes 章节会详细讲解如何配置 ImagePullSecret 从私有仓库拉取镜像。
小结#
本章覆盖了容器技术的核心基础:
- 容器 vs 虚拟机:容器共享内核、秒级启动、MB 级开销,适合微服务场景;虚拟机强隔离、GB 级开销,适合异构 OS 需求
- 镜像/容器/Layer:镜像是只读模板,容器是运行实例,Layer 像乐高积木一层层叠加,相同层在磁盘上只存一份
- Dockerfile:多阶段构建让最终镜像只含运行时产物;指令顺序影响缓存命中率,依赖声明文件要放前面
- 常用命令:
docker run(启动)、docker exec(进入)、docker logs(日志)、docker ps(查看)、docker rm/rmi(删除) - 数据卷:Volume 适合生产持久化数据,Bind Mount 适合开发时代码挂载
- 镜像仓库:
docker tag+docker push+docker pull三步完成镜像分发