📋 文档内容概览:
第一部分: 高频面试题 (10题)
- Docker核心概念
- Docker vs 虚拟机
- 网络模式详解
- Dockerfile指令对比
- 数据持久化方案
- 镜像优化技巧
- 容器间通信
- Docker Compose
- 分层存储原理
- 故障排查方法
1. 什么是Docker?Docker的核心概念是什么?
答案:
Docker是一个开源的容器化平台,用于自动化应用程序的部署、扩展和管理。它将应用程序及其依赖项打包到一个可移植的容器中。
核心概念:
- 镜像(Image): 只读模板,包含运行应用所需的代码、运行时、库、环境变量和配置文件
- 容器(Container): 镜像的运行实例,是一个独立的可执行软件包
- 仓库(Repository): 存储和分发镜像的地方,如Docker Hub
- Dockerfile: 用于构建镜像的文本文件,包含一系列指令
- Docker Engine: Docker的核心组件,负责创建和运行容器
2. Docker与虚拟机的区别?
答案:
| 特性 | Docker容器 | 虚拟机 |
|---|---|---|
| 启动速度 | 秒级启动 | 分钟级启动 |
| 性能 | 接近原生性能 | 有一定性能损耗 |
| 资源占用 | MB级别 | GB级别 |
| 隔离级别 | 进程级隔离 | 操作系统级隔离 |
| 操作系统 | 共享宿主机内核 | 每个VM有独立OS |
| 可移植性 | 强,一次构建到处运行 | 较弱,依赖虚拟化平台 |
详细说明:
- Docker容器共享主机操作系统内核,只打包应用及其依赖
- 虚拟机包含完整的操作系统,运行在Hypervisor之上
- Docker更轻量级,适合微服务架构
- 虚拟机提供更强的隔离性,适合需要完全隔离的场景
3. Docker的网络模式有哪些?
答案:
Docker支持以下网络模式:
:::tips
1 网络命名空间

每个容器就像一个独立的小房间,有自己的网络设备。
- 网卡:计算机与网络之间的物理接口 ,负责将计算机的数据转换为网络信号(电信号/光信号/无线电波),并在网络中传输。
<font style="color:rgba(0, 0, 0, 0.9);">ifconfig</font>方式查看 - IP地址:设备在网络中的逻辑地址,用于在网络层标识设备位置,实现跨网络通信。
- 端口:端口是应用程序的逻辑标识,用于区分同一IP地址上的不同网络服务。IP地址找到主机,端口找到主机上的具体服务。
2 为什么需要Docker网络?
docker网络让容器之间可以相互通信、访问外网、以及让外部访问容器
:::
1. Bridge模式(默认)
- 容器连接到docker0虚拟网桥
- 容器间可以通过IP通信
- 通过NAT访问外网
- 适用场景: 单机容器间通信
:::tips

- 外部访问容器A的nginx服务:
docker run -d -p 8080:80 --name web nginx===>在宿主机的8080端口访问容器内部网络的80端口(容器内nginx的服务端口)
:::
2. Host模式
- 容器与宿主机共享网络命名空间
- 容器直接使用宿主机IP和端口
- 性能最好,但端口冲突风险高
- 适用场景: 需要高性能网络的应用
:::tips

- 优点:性能最好,不需要端口映射,容器直接使用宿主机端口
- 缺点:端口冲突、安全性较低
- 运行:
docker run -d --network host --name web nginx===>直接在宿主机的80端口访问
:::
3. None模式
- 容器有独立网络命名空间,但不配置网络
- 需要手动配置网络
- 适用场景: 自定义网络配置
:::tips

docker run -d --network none --name isolated alpine sleep 3600
:::
4. Container模式
- 新容器与已存在容器共享网络命名空间
- 适用场景: 容器间需要紧密网络耦合
:::tips

先启动容器A:docker run -d --name nginx nginx;再启动容器B,共享容器A的网络:docker run -d --network container:nginx --name php php-fpm====>在php容器中可以用localhost:80访问nginx
:::
5. 自定义网络(Overlay/Macvlan)
- 为什么要使用自定义网络?
- 默认bridge网络的问题:容器只能用IP通信,不能用容器名。可是IP在容器重启后会变化。
- 自定义网络的优势:可以用容器名通信、更好隔离、更灵活的配置。
:::tips

:::
bash
# 1. 创建自定义网络
docker network create mynet
# 2. 在自定义网络中启动容器
docker run -d --name web --network mynet nginx # -d 在后台运行
docker run -d --name cache --network mynet redis
4. Dockerfile中常用指令有哪些?区别是什么?
核心指令:
- FROM: 指定基础镜像
dockerfile
FROM ubuntu:20.04
- RUN: 构建时执行命令,生成新层
dockerfile
RUN apt-get update && apt-get install -y nginx
- CMD: 容器启动时默认执行的命令(可被覆盖)
dockerfile
CMD ["nginx", "-g", "daemon off;"]
- ENTRYPOINT: 容器启动时执行的命令(不易被覆盖)
dockerfile
ENTRYPOINT ["python", "app.py"]
- COPY vs ADD :
- COPY: 简单复制文件
- ADD: 复制+自动解压+支持URL(推荐用COPY)
dockerfile
COPY app.py /app/
ADD app.tar.gz /app/
- WORKDIR: 设置工作目录
dockerfile
WORKDIR /app
- ENV: 设置环境变量
dockerfile
ENV APP_ENV=production
- EXPOSE: 声明容器监听端口
dockerfile
EXPOSE 80
- VOLUME: 创建挂载点
dockerfile
VOLUME /data
- CMD vs ENTRYPOINT 区别:
- CMD可以被docker run的参数覆盖
- ENTRYPOINT不容易被覆盖,适合设置容器主进程
- 可以组合使用: ENTRYPOINT定义执行程序,CMD定义默认参数
5. Docker数据持久化有哪些方式?
:::tips
为什么需要数据持久化?
容器默认是临时的,容器删除后,数据就没有了,为了让数据持久化,我们将数据保存在容器外部,容器删了数据还在。
三种持久化方式

:::
1. Volume(数据卷) - 推荐方式
- 由Docker管理,存储在
/var/lib/docker/volumes/ - 独立于容器生命周期
- 可在容器间共享
bash
bash
# 1. 创建一个数据卷
docker volume create mydata
# 2. 查看所有数据卷
docker volume ls
# 3. 查看某个卷的详细信息
docker volume inspect mydata
# 4. 删除数据卷
docker volume rm mydata
# 方式一:使用已创建的卷
docker volume create mydata
docker run -d --name my-nginx -v mydata:/usr/share/nginx/html nginx
# 方式二:运行时自动创建(如果卷不存在)
docker run -d --name my-nginx -v mydata:/usr/share/nginx/html nginx
# -v mydata:/usr/share/nginx/html
# │ │
# │ └── 容器内的路径(数据要存到容器的这个位置)
# │
# └── 卷的名字(宿主机上 Docker 管理的存储)
2. Bind Mount(绑定挂载)
- 挂载宿主机的目录或文件
- 完全依赖宿主机文件系统
- 适合开发环境
bash
bash
docker run -v /host/path:/container/path nginx
# docker run -v /宿主机路径:/容器内路径 镜像名
3. tmpfs Mount(临时挂载)
- 存储在宿主机内存中
- 容器停止后数据消失
- 适合临时敏感数据###
bash
bash
docker run --tmpfs /tmp nginx
区别对比:
- Volume: 最佳实践,Docker管理,跨平台
- Bind Mount: 灵活但依赖主机路径结构
- tmpfs: 临时数据,性能最好
6. 如何优化Docker镜像大小?
优化策略:
- 使用更小的基础镜像
dockerfile
# 不推荐
FROM ubuntu:20.04
# 推荐
FROM alpine:3.14
- 多阶段构建(Multi-stage Build)
- 多阶段构建 = 同一条 Dockerfile 里写多个 FROM,
- 前一个阶段当工具人(这个阶段里 gcc、go mod、源码、临时对象文件全都有,体积巨大),后一个阶段只 COPY --from= 拿成品(最终镜像 = alpine + 一个静态二进制,可能 < 10 MB;而 builder 阶段几百 MB 的层不会被打包进最终镜像)。
- 既能让编译环境齐全,又能让运行镜像瘦身,还只生成一个镜像(后面的阶段)。
dockerfile
# 构建阶段
FROM golang:1.17 AS builder
WORKDIR /app
COPY . .
RUN go build -o main .
# 运行阶段
FROM alpine:3.14
COPY --from=builder /app/main /main
CMD ["/main"]
- 合并RUN指令,减少层数
dockerfile
# 不推荐
RUN apt-get update
RUN apt-get install -y nginx
RUN apt-get clean
# 推荐
RUN apt-get update && \
apt-get install -y nginx && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
- 使用.dockerignore文件
:::info
- docker build会先把整个构建上下文(通常是Dockerfile所在目录)打包成tar发给docker引擎,目录越大-->tar越大-->传输时间越长,缓存越容易失效。
- .dockerignore的作用:在打包前按照规则排除文件/目录,减小上下文体积。避免敏感文件被打进去,让Copy指令跳过这些文件,镜像层更干净
:::
plain
node_modules
.git
*.log
- 清理缓存和临时文件
dockerfile
RUN apt-get update && apt-get install -y package \
&& rm -rf /var/lib/apt/lists/*
- 使用特定版本标签,避免使用latest
- "latest"让缓存失效 → 反复拉新层 → 旧层堆积 → 体积膨胀;
- 钉死版本号 → 缓存一直有效 → 不再重复存相同层 → 实际占用最小。
7. Docker容器间如何通信?

1. 自定义通信网络
bash
┌──────────────────────────────────────────┐
│ 自定义网络 (mynet) │
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │ web │ ←─────→ │ app │ │
│ │ 容器 │ 可以用 │ 容器 │ │
│ │ │ 名字通信 │ │ │
│ └─────────┘ └─────────┘ │
│ │
│ web容器可以直接用 "app" 这个名字访问 │
│ app容器可以直接用 "web" 这个名字访问 │
└──────────────────────────────────────────┘
# 第一步:创建一个自定义网络
docker network create mynet
# 第二步:启动容器并加入这个网络
docker run -d --name web --network mynet nginx
docker run -d --name app --network mynet python:3.9 sleep 3600
# 第三步:容器间通过名字通信
# 进入 app 容器
docker exec -it app bash
# 在 app 容器内部,可以直接用 "web" 这个名字访问
curl http://web:80
ping web
为什么可以用名字访问?===>Docker的自定义网络内置了DNS服务
bash
┌────────────────────────────────────────────┐
│ mynet 网络 │
│ │
│ ┌─────────────────────────────────┐ │
│ │ Docker DNS 服务器 │ │
│ │ │ │
│ │ web → 172.18.0.2 │ │
│ │ app → 172.18.0.3 │ │
│ └─────────────────────────────────┘ │
│ │
│ 当 app 容器访问 "web" 时: │
│ 1. 询问 DNS:"web 的 IP 是多少?" │
│ 2. DNS 回答:"172.18.0.2" │
│ 3. app 连接到 172.18.0.2 │
└────────────────────────────────────────────┘
2. 使用Link(已废弃,不推荐)
bash
docker run --name db mysql
docker run --name web --link db:database nginx
# --link db:database 的意思是:
# 在 web 容器中,可以用 "database" 这个名字访问 db 容器
3. 通过宿主机端口转发
- 适用场景:临时测试、外部访问容器服务
bash
┌─────────────────────────────────────────────────┐
│ 宿主机 │
│ IP: 192.168.1.100 │
│ │
│ 端口 3306 ←───────────────────┐ │
│ ↑ │ │
│ │ 端口映射 │ │
│ ↓ ↓ │
│ ┌─────────┐ ┌─────────┐ │
│ │ MySQL │ │ App │ │
│ │ 容器 │ │ 容器 │ │
│ │ :33 │ │ │ │
│ └─────────┘ └─────────┘ │
│ │
│ App 容器通过 192.168.1.100:3306 访问 MySQL │
└─────────────────────────────────────────────────┘
# 启动 MySQL,把容器的 33 端口映射到宿主机的 3306
docker run -d --name mysql -p 3306:33 -e MYSQL_ROOT_PASSWORD=123456 mysql
# 启动应用容器
docker run -d --name app myapp
# 在 app 容器中,通过宿主机 IP 访问 MySQL
# 连接地址:宿主机IP:3306
# 获取宿主机的方法
# 方法1:在容器内使用特殊域名(Docker Desktop 支持)
host.docker.internal
# 方法2:在容器内获取网关 IP
ip route | grep default
# 输出类似:default via 172.17.0.1 dev eth0
# 172.17.0.1 就是宿主机在 Docker 网络中的 IP
4. 使用Docker Compose
用一个配置文件定义多个容器,自动创建网络,容器间自动可以用名字通信。
yaml
# docker-compose.yml
version: '3' # Compose 文件版本
services: # 定义服务(每个服务 = 一个容器)
web: # 服务名(也是容器在网络中的名字)
image: nginx # 使用的镜像
ports:
- "80:80" # 端口映射
app: # 另一个服务
image: python:3.9
depends_on: # 依赖关系:app 要等 web 启动后才启动
- web
db:
image: mysql
environment:
MYSQL_ROOT_PASSWORD: 123456
执行 docker-compose up 后:
1. 自动创建网络(项目名_default)
2. 启动所有容器并加入该网络
3. 容器间可以用服务名通信
┌─────────────────────────────────────────┐
│ 自动创建的网络 │
│ (myproject_default) │
│ │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ web │ ↔ │ app │ ↔ │ db │ │
│ └─────┘ └─────┘ └─────┘ │
│ │
│ app 中可以: │
│ - curl http://web:80 │
│ - mysql -h db -u root -p │
└─────────────────────────────────────────┘
:::success
不指定网络时,容器使用默认bridge网络,此时不能用容器名通信。
:::
8. 什么是Docker Compose?有什么作用?
答案:
Docker Compose是用于定义和运行多容器Docker应用的工具。
yaml
┌─────────────────────────────────────────────────────────┐
│ │
│ Docker Compose = 多容器应用的「配置文件 + 管理工具」 │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ │ │ │ │
│ │ YAML 配置文件 │ + │ 命令行工具 │ │
│ │ (声明式定义) │ │ (docker-compose)│ │
│ │ │ │ │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
核心作用:
- 使用YAML文件配置应用服务
- 一条命令启动所有服务
- 管理服务间的依赖关系
- 简化复杂应用的部署
示例配置:

服务配置详解
bash
# -------------------------- image:指定镜像 -------------------------------
services:
web:
image: nginx:latest # 使用官方镜像
app:
image: myregistry/myapp:v1 # 使用私有仓库镜像
# --------------------------- build:从Dockerfile构建 ------------------------
services:
app:
build: ./app # 简单写法:指定 Dockerfile 所在目录
api:
build: # 详细写法
context: ./backend # 构建上下文目录
dockerfile: Dockerfile.prod # 指定 Dockerfile 文件名
args: # 构建参数
- VERSION=1.0
# --------------------------- ports:端口映射 -----------------------------
services:
web:
ports:
- "80:80" # 宿主机:容器
- "443:443"
- "8080:80" # 宿主机8080 映射到 容器80
- "127.0.0.1:3000:3000" # 只绑定到本机
# ---------------------------- volumes:数据卷挂载 --------------------------
services:
db:
volumes:
# 命名卷(推荐用于数据持久化)
- db-data:/var/lib/mysql
# Bind Mount(开发时挂载代码)
- ./src:/app/src
# 只读挂载
- ./config:/etc/app/config:ro
volumes:
db-data: # 声明命名卷
# --------------------------- environment:环境变量 ---------------------------
services:
app:
environment:
# 方式1:列表格式
- DB_HOST=db
- DB_PORT=3306
- DEBUG=true
db:
environment:
# 方式2:字典格式
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: myapp
# ----------------------------- env_file:从文件读取环境变量 -----------------------
services:
app:
env_file:
- .env # 读取 .env 文件
- .env.local # 可以指定多个文件
# .env 文件内容
DB_HOST=db
DB_PASSWORD=secret
DEBUG=true
# ----------------------------- depends_on:依赖关系 -----------------------------
services:
web:
depends_on:
- app # web 会等 app 启动后再启动
app:
depends_on:
- db # app 会等 db 启动后再启动
- redis
启动顺序:db → redis → app → web
停止顺序:web → app → redis → db(反过来)
⚠️ 注意:depends_on 只保证启动顺序,不保证服务"就绪"
# --------------------------- networks:网络配置 --------------------------------
services:
web:
networks:
- frontend # 只加入前端网络
app:
networks: # 同时加入两个网络
- frontend
- backend
db:
networks:
- backend # 只加入后端网络
networks:
frontend: # 定义前端网络
backend: # 定义后端网络
网络隔离效果:
┌────────────────┐ ┌────────────────┐
│ frontend │ │ backend │
│ │ │ │
│ ┌────┐ ┌────┐│ │┌────┐ ┌────┐ │
│ │web │↔│app ││ ││app │↔│ db │ │
│ └────┘ └────┘│ │└────┘ └────┘ │
│ │ │ │
└────────────────┘ └────────────────┘
web 和 db 不在同一网络,无法直接通信(更安全)
app 同时在两个网络,可以连接 web 和 db
# ----------------------------- restart:重启策略 ----------------------------
services:
web:
restart: always # 总是重启(推荐生产环境)
app:
restart: unless-stopped # 除非手动停止,否则重启
worker:
restart: on-failure # 只在失败时重启
test:
restart: "no" # 不重启(默认)
# --------------------------- command:默认覆盖指令 --------------------------
services:
app:
image: python:3.9
command: python app.py # 覆盖镜像默认的 CMD
worker:
image: python:3.9
command: ["python", "worker.py", "--queue", "high"]
# --------------------------- networks:网络定义 ----------------------------
networks:
frontend: # 使用默认配置创建网络
backend:
driver: bridge # 指定驱动(bridge 是默认值)
existing-net:
external: true # 使用已存在的外部网络
常用命令:
bash
# 启动所有服务(后台运行)
docker-compose up -d
# 启动所有服务(前台运行,看日志)
docker-compose up
# 停止并删除所有服务、网络
docker-compose down
# 停止并删除所有服务、网络、数据卷
docker-compose down -v
# 只停止服务(不删除)
docker-compose stop
# 启动已停止的服务
docker-compose start
# 重启服务
docker-compose restart
# 查看服务状态
docker-compose ps
# 输出示例:
# Name Command State Ports
# ---------------------------------------------------------
# myapp_web_1 nginx -g ... Up 0.0.0.0:80->80/tcp
# myapp_app_1 python app.py Up
# myapp_db_1 docker-entry.. Up 3306/tcp
# 查看所有服务的日志
docker-compose logs
# 查看特定服务的日志
docker-compose logs web
# 实时跟踪日志(类似 tail -f)
docker-compose logs -f
# 只看最后 100 行
docker-compose logs --tail=100 web
# 构建/重新构建镜像
docker-compose build
# 构建并启动
docker-compose up --build
# 强制重新构建(不使用缓存)
docker-compose build --no-cache
# 扩展服务实例数量
docker-compose up -d --scale app=3
# 这会启动 3 个 app 容器:
# myapp_app_1
# myapp_app_2
# myapp_app_3
9. Docker的分层存储是什么?有什么优势?
Docker镜像采用UnionFS(联合文件系统),将多个层叠加在一起形成最终的镜像。
什么是镜像分层?:
bash
┌─────────────────────────────────────────────────┐
│ │
│ Docker 镜像就像「千层饼」或「汉堡」 │
│ │
│ 每一层 = Dockerfile 中的一条指令的结果 │
│ │
└─────────────────────────────────────────────────┘
┌───────────────────────┐
│ COPY index.html │ ← 第4层(最上层)
├───────────────────────┤
│ RUN apt install │ ← 第3层
├───────────────────────┤
│ RUN apt update │ ← 第2层
├───────────────────────┤
│ FROM ubuntu:20.04 │ ← 第1层(基础层)
└───────────────────────┘
UnionFS(联合文件系统)是什么?:
bash
想象一下「透明胶片叠加」:
┌─────────────────────────────────────────────────────────┐
│ │
│ 📄 第1张透明片:画了一个房子的轮廓 │
│ 📄 第2张透明片:画了窗户 │
│ 📄 第3张透明片:画了门 │
│ 📄 第4张透明片:画了烟囱 │
│ │
│ 把它们叠在一起 → 看到的是一个完整的房子 🏠 │
│ │
│ 这就是 UnionFS 的原理! │
│ 多个「层」叠加在一起,呈现为一个统一的文件系统 │
│ │
└─────────────────────────────────────────────────────────┘
UnionFS 把多个目录「联合」挂载到同一个目录:
目录A (Layer 1) 目录B (Layer 2) 目录C (Layer 3)
│ │ │
├── /bin ├── /etc ├── /app
├── /lib ├── /var └── index.html
└── /usr └── nginx.conf
↓ 联合挂载
┌──────────────────────────┐
│ 最终看到的文件系统 │
├──────────────────────────┤
│ /bin (来自 Layer 1) │
│ /lib (来自 Layer 1) │
│ /usr (来自 Layer 1) │
│ /etc (来自 Layer 2) │
│ /var (来自 Layer 2) │
│ /app (来自 Layer 3) │
│ index.html (来自 Layer 3)│
└──────────────────────────┘
Dockerfile指令和层的关系:
dockerfile
构建过程:
┌────────────────────────────────────────────────────────────┐
│ Step 1: FROM ubuntu:20.04 │
├────────────────────────────────────────────────────────────┤
│ │
│ 拉取 ubuntu:20.04 镜像作为基础 │
│ 这个镜像本身已经有很多层了 │
│ │
│ ┌─────────────────────┐ │
│ │ ubuntu:20.04 的层 │ Layer 1 (约 72MB) │
│ └─────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────┘
↓
┌────────────────────────────────────────────────────────────┐
│ Step 2: RUN apt-get update │
├────────────────────────────────────────────────────────────┤
│ │
│ 执行命令,产生的文件变化形成新的一层 │
│ │
│ ┌─────────────────────┐ │
│ │ apt-get update 的层 │ Layer 2 (约 27MB) │
│ ├─────────────────────┤ │
│ │ ubuntu:20.04 的层 │ Layer 1 │
│ └─────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────┘
↓
┌────────────────────────────────────────────────────────────┐
│ Step 3: RUN apt-get install -y nginx │
├────────────────────────────────────────────────────────────┤
│ │
│ 安装 nginx,产生的文件形成新的一层 │
│ │
│ ┌─────────────────────┐ │
│ │ 安装 nginx 的层 │ Layer 3 (约 56MB) │
│ ├─────────────────────┤ │
│ │ apt-get update 的层 │ Layer 2 │
│ ├─────────────────────┤ │
│ │ ubuntu:20.04 的层 │ Layer 1 │
│ └─────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────┘
↓
┌────────────────────────────────────────────────────────────┐
│ Step 4: COPY index.html /var/www │
├────────────────────────────────────────────────────────────┤
│ │
│ 复制文件,形成最终的一层 │
│ │
│ ┌─────────────────────┐ │
│ │ COPY 文件的层 │ Layer 4 (几KB) │
│ ├─────────────────────┤ │
│ │ 安装 nginx 的层 │ Layer 3 │
│ ├─────────────────────┤ │
│ │ apt-get update 的层 │ Layer 2 │
│ ├─────────────────────┤ │
│ │ ubuntu:20.04 的层 │ Layer 1 │
│ └─────────────────────┘ │
│ │
│ 最终镜像 = 4层叠加 │
│ │
└────────────────────────────────────────────────────────────┘
优势:
- 节省磁盘空间: 相同的层只存储一次
bash
场景:你有两个镜像都基于 ubuntu:20.04
镜像A (Web服务器) 镜像B (数据库)
┌─────────────────┐ ┌─────────────────┐
│ 应用层 (10MB) │ │ MySQL层 (200MB) │
├─────────────────┤ ├─────────────────┤
│ Nginx层 (50MB) │ │ 依赖层 (100MB) │
├─────────────────┤ ├─────────────────┤
│ ubuntu (72MB) │ ←共享→ │ ubuntu (72MB) │
└─────────────────┘ └─────────────────┘
不分层: 72 + 72 = 144MB (基础层存两份)
分层: 72MB (基础层只存一份)
节省了 72MB!镜像越多,节省越多!
- 加速构建: 未改变的层使用缓存
bash
# 假设这是你的 Dockerfile
FROM node:18
COPY package.json .
RUN npm install # 这一步很慢(要下载很多依赖)
COPY . .
CMD ["node", "app.js"]
第一次构建:
Step 1: FROM node:18 ← 拉取镜像
Step 2: COPY package.json . ← 复制文件
Step 3: RUN npm install ← 安装依赖(耗时 2 分钟)
Step 4: COPY . . ← 复制代码
Step 5: CMD ["node", "app.js"] ← 设置命令
总耗时: 约 3 分钟
---------------------------------------------
你修改了 app.js,再次构建:
Step 1: FROM node:18 ← Using cache ✓
Step 2: COPY package.json . ← Using cache ✓
Step 3: RUN npm install ← Using cache ✓ (package.json 没变)
Step 4: COPY . . ← 重新执行(代码变了)
Step 5: CMD ["node", "app.js"] ← 重新执行
总耗时: 约 10 秒!省了 2 分钟 50 秒!
- 快速分发: 只传输新增或修改的层
bash
场景:推送/拉取镜像到仓库
┌─────────────────────────────────────────────────────────┐
│ │
│ 你的镜像 (500MB) │
│ ┌─────────────────┐ │
│ │ 你的代码 (5MB) │ ← 只有这层需要传输! │
│ ├─────────────────┤ │
│ │ 依赖包 (200MB) │ ← 服务器已有,跳过 │
│ ├─────────────────┤ │
│ │ 基础镜像 (295MB) │ ← 服务器已有,跳过 │
│ └─────────────────┘ │
│ │
│ 实际传输: 5MB(而不是 500MB) │
│ 节省 99% 的带宽! │
│ │
└─────────────────────────────────────────────────────────┘
- 版本控制: 每层有唯一ID,易于追踪
bash
# 每个层都有唯一的 SHA256 哈希值
docker inspect nginx --format='{{.RootFS.Layers}}'
# 输出类似:
# [sha256:a1b2c3d4...
# sha256:e5f6g7h8...
# sha256:i9j0k1l2...]
# 作用:
# - 精确知道镜像由哪些层组成
# - 验证镜像完整性
# - 追踪变化
层的只读特性:
bash
┌─────────────────────────────────────────────────────────┐
│ │
│ 镜像的每一层都是 READ-ONLY(只读) │
│ │
│ ┌─────────────────┐ │
│ │ Layer 4 │ 只读 🔒 │
│ ├─────────────────┤ │
│ │ Layer 3 │ 只读 🔒 │
│ ├─────────────────┤ │
│ │ Layer 2 │ 只读 🔒 │
│ ├─────────────────┤ │
│ │ Layer 1 │ 只读 🔒 │
│ └─────────────────┘ │
│ │
│ 一旦创建,永不改变! │
│ │
└─────────────────────────────────────────────────────────┘
那容器里怎么写文件呢? → 这就需要「容器层」和「Copy-on-Write」机制
Copy-on-Write机制:
bash
当你运行容器时:
docker run nginx
┌─────────────────────────────────────────────────────────┐
│ 容器 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Container Layer (容器层) │ │
│ │ 可读写 📝 │ │
│ │ (容器运行时产生的所有改动都在这里) │ │
│ └─────────────────────────────────────────────────┘ │
│ ↑ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 镜像的只读层 │ │
│ │ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ Layer 4 │ 只读 🔒 │ │
│ │ ├─────────────────┤ │ │
│ │ │ Layer 3 │ 只读 🔒 │ │
│ │ ├─────────────────┤ │ │
│ │ │ Layer 2 │ 只读 🔒 │ │
│ │ ├─────────────────┤ │ │
│ │ │ Layer 1 │ 只读 🔒 │ │
│ │ └─────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
场景:容器要修改一个文件(比如 /etc/nginx/nginx.conf)
┌────────────────────────────────────────────────────────────┐
│ │
│ 原始状态: │
│ ┌───────────────────────┐ │
│ │ 容器层 (空的) │ │
│ ├───────────────────────┤ │
│ │ Layer 3: nginx.conf │ ← 文件在只读层 │
│ ├───────────────────────┤ │
│ │ ...其他层... │ │
│ └───────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────┘
↓
容器修改文件
↓
┌────────────────────────────────────────────────────────────┐
│ │
│ Copy-on-Write 发生: │
│ │
│ 1. 从只读层「复制」nginx.conf 到容器层 │
│ 2. 在容器层的副本上进行修改 │
│ 3. 原只读层的文件保持不变 │
│ │
│ ┌───────────────────────┐ │
│ │ 容器层: nginx.conf ✏️ │ ← 修改后的副本在这里 │
│ ├───────────────────────┤ │
│ │ Layer 3: nginx.conf 🔒│ ← 原文件不变! │
│ ├───────────────────────┤ │
│ │ ...其他层... │ │
│ └───────────────────────┘ │
│ │
│ 读取时:优先读容器层的文件,找不到才往下层找 │
│ │
└────────────────────────────────────────────────────────────┘
实际演示:
bash
# 启动一个 nginx 容器
docker run -d --name test-nginx nginx
# 查看容器内的配置文件
docker exec test-nginx cat /etc/nginx/nginx.conf
# 修改配置文件
docker exec test-nginx sh -c "echo '# modified' >> /etc/nginx/nginx.conf"
# 再次查看,看到了修改
docker exec test-nginx cat /etc/nginx/nginx.conf
# 重要:原镜像没有任何改变!
# 启动另一个容器,配置文件是原始的
docker run --rm nginx cat /etc/nginx/nginx.conf
# 没有 "# modified"!
# 清理
docker rm -f test-nginx
⚠️** 删除文件不会减小镜像体积的常见误区:**
bash
# 错误示范:删除了文件但镜像没变小
FROM ubuntu:20.04
RUN apt-get update # Layer 1: +27MB
RUN apt-get install -y gcc g++ # Layer 2: +200MB
RUN rm -rf /var/lib/apt/lists/* # Layer 3: 标记删除,但 0MB 减少!
# 总大小还是 227MB+
# 因为 Layer 1 和 Layer 2 的文件还在!
为什么?
┌─────────────────────────────────────┐
│ Layer 3: rm -rf (标记删除) │ ← 只是说"这些文件不可见了"
├─────────────────────────────────────┤
│ Layer 2: gcc g++ (200MB) │ ← 文件还在这里!
├─────────────────────────────────────┤
│ Layer 1: apt-get update (27MB) │ ← 文件还在这里!
└─────────────────────────────────────┘
层是只读的,不能真正删除之前层的文件!
===============>正确做法:
# 正确示范:在同一个 RUN 中清理
FROM ubuntu:20.04
RUN apt-get update && \
apt-get install -y gcc g++ && \
rm -rf /var/lib/apt/lists/* # 同一层中清理!
# 现在只有一层,清理生效
10. 如何排查Docker容器故障?
常用排查命令:
- 查看容器日志
bash
bash
docker logs -f --tail 100 container_name
- 进入容器排查
bash
bash
docker exec -it container_name /bin/bash
docker exec -it container_name sh # Alpine镜像
- 查看容器资源使用
bash
bash
docker stats container_name
docker top container_name
- 检查容器详细信息
bash
bash
docker inspect container_name
docker inspect --format='{{.State.Status}}' container_name
- 查看容器进程
bash
bash
docker top container_name
- 查看容器端口映射
bash
bash
docker port container_name
- 查看网络连接
bash
bash
docker network inspect bridge
常见问题排查:
- 容器启动失败: 检查日志,查看配置,验证镜像
- 网络问题: 检查网络模式,端口映射,防火墙规则
- 性能问题: 使用docker stats查看资源,限制CPU/内存
- 存储问题: 检查磁盘空间,Volume挂载
第二部分: 语法知识详解
- 镜像管理 (pull/build/tag/push)
- 容器管理 (run/exec/logs)
- 数据卷管理
- 网络管理
- Dockerfile完整语法
- Docker Compose配置
- 实用组合命令
1. Docker 命令结构
plain
docker [OPTIONS] COMMAND [ARG...]
2. 镜像管理命令
2.1 拉取镜像
bash
bash
docker pull [OPTIONS] NAME[:TAG|@DIGEST]
# 示例
docker pull nginx # 拉取最新版本
docker pull nginx:1.21.0 # 拉取指定版本
docker pull mysql:8.0 # 拉取MySQL 8.0
2.2 查看本地镜像
bash
bash
docker images [OPTIONS] [REPOSITORY[:TAG]]
# 示例
docker images # 列出所有镜像
docker images nginx # 列出nginx相关镜像
docker images -q # 只显示镜像ID
docker images --filter "dangling=true" # 显示悬空镜像
2.3 删除镜像
bash
bash
docker rmi [OPTIONS] IMAGE [IMAGE...]
# 示例
docker rmi nginx:1.21.0 # 删除指定镜像
docker rmi $(docker images -q) # 删除所有镜像
docker rmi -f image_id # 强制删除
2.4 构建镜像
bash
bash
docker build [OPTIONS] PATH | URL | -
# 示例
docker build -t myapp:1.0 . # 从当前目录构建
docker build -t myapp:1.0 -f Dockerfile.prod . # 指定Dockerfile
docker build --no-cache -t myapp:1.0 . # 不使用缓存
docker build --build-arg VERSION=1.0 . # 传递构建参数
2.5 标记镜像
bash
bash
docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
# 示例
docker tag myapp:1.0 myregistry.com/myapp:1.0
docker tag nginx:latest nginx:backup
2.6 推送镜像
bash
bash
docker push [OPTIONS] NAME[:TAG]
# 示例
docker push myregistry.com/myapp:1.0
3. 容器管理命令
3.1 运行容器
bash
bash
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
# 常用选项详解
-d # 后台运行
-it # 交互式终端
--name container_name # 指定容器名称
-p host_port:container_port # 端口映射
-P # 随机端口映射
-v host_path:container_path # 挂载数据卷
-e KEY=VALUE # 设置环境变量
--network network_name # 指定网络
--restart=always # 自动重启策略
--memory=1g # 限制内存
--cpus=2 # 限制CPU
--rm # 容器停止后自动删除
# 实战示例
docker run -d \
--name my-nginx \
-p 8080:80 \
-v /host/data:/usr/share/nginx/html \
-e NGINX_HOST=example.com \
--restart=unless-stopped \
nginx:latest
# MySQL容器示例
docker run -d \
--name mysql-server \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=secret \
-e MYSQL_DATABASE=mydb \
-v mysql-data:/var/lib/mysql \
mysql:8.0
# Redis容器示例
docker run -d \
--name redis-server \
-p 6379:6379 \
-v redis-data:/data \
redis:alpine redis-server --appendonly yes
3.2 查看容器
bash
bash
docker ps [OPTIONS]
# 示例
docker ps # 查看运行中的容器
docker ps -a # 查看所有容器(包括停止的)
docker ps -q # 只显示容器ID
docker ps -n 5 # 显示最近创建的5个容器
docker ps --filter "status=running" # 过滤运行中的容器
docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Status}}" # 格式化输出
3.3 启动/停止/重启容器
bash
bash
docker start [OPTIONS] CONTAINER [CONTAINER...]
docker stop [OPTIONS] CONTAINER [CONTAINER...]
docker restart [OPTIONS] CONTAINER [CONTAINER...]
# 示例
docker start my-nginx
docker stop my-nginx
docker restart my-nginx
docker stop $(docker ps -q) # 停止所有运行中的容器
3.4 删除容器
bash
bash
docker rm [OPTIONS] CONTAINER [CONTAINER...]
# 示例
docker rm my-nginx # 删除停止的容器
docker rm -f my-nginx # 强制删除运行中的容器
docker rm $(docker ps -aq) # 删除所有容器
docker container prune # 删除所有停止的容器
3.5 进入容器
bash
bash
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
docker attach CONTAINER
# 示例
docker exec -it my-nginx /bin/bash # 交互式bash
docker exec -it my-nginx sh # Alpine系统使用sh
docker exec my-nginx ls /etc # 执行单个命令
docker exec -u root my-nginx whoami # 以root用户执行
# exec vs attach区别
# exec: 新建进程,exit不会停止容器
# attach: 附加到主进程,exit会停止容器
3.6 查看容器日志
bash
bash
docker logs [OPTIONS] CONTAINER
# 示例
docker logs my-nginx # 查看所有日志
docker logs -f my-nginx # 实时跟踪日志
docker logs --tail 100 my-nginx # 查看最后100行
docker logs --since 30m my-nginx # 查看最近30分钟日志
docker logs -t my-nginx # 显示时间戳
3.7 容器与宿主机文件复制
bash
bash
docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH
docker cp [OPTIONS] SRC_PATH CONTAINER:DEST_PATH
# 示例
docker cp my-nginx:/etc/nginx/nginx.conf ./nginx.conf # 从容器复制到主机
docker cp ./app.py my-app:/app/app.py # 从主机复制到容器
4. 数据卷管理
4.1 Volume命令
bash
bash
# 创建数据卷
docker volume create [OPTIONS] [VOLUME]
docker volume create mydata
# 列出数据卷
docker volume ls
docker volume ls --filter "dangling=true" # 未使用的数据卷
# 查看数据卷详情
docker volume inspect mydata
# 删除数据卷
docker volume rm mydata
docker volume prune # 删除所有未使用的数据卷
# 使用数据卷
docker run -v mydata:/app/data nginx # 命名卷
docker run -v /host/path:/container/path nginx # 绑定挂载
docker run --mount type=volume,source=mydata,target=/app/data nginx # 推荐语法
5. 网络管理
5.1 网络命令
bash
bash
# 列出网络
docker network ls
# 创建网络
docker network create [OPTIONS] NETWORK
docker network create mynet # 默认bridge
docker network create --driver bridge mynet
docker network create --driver overlay swarm-net # Swarm模式
# 查看网络详情
docker network inspect mynet
# 连接容器到网络
docker network connect mynet my-container
docker network connect --alias db mynet mysql-server
# 断开容器网络连接
docker network disconnect mynet my-container
# 删除网络
docker network rm mynet
docker network prune # 删除所有未使用的网络
6. Dockerfile 语法详解
6.1 完整Dockerfile示例
dockerfile
dockerfile
# 基础镜像
FROM python:3.9-slim AS base
# 维护者信息
LABEL maintainer="your-email@example.com"
LABEL version="1.0"
LABEL description="Python web application"
# 设置环境变量
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
APP_HOME=/app
# 设置工作目录
WORKDIR $APP_HOME
# 安装系统依赖
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
libpq-dev && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# 复制依赖文件
COPY requirements.txt .
# 安装Python依赖
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 创建非root用户
RUN useradd -m -u 1000 appuser && \
chown -R appuser:appuser $APP_HOME
USER appuser
# 暴露端口
EXPOSE 8000
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# 数据卷
VOLUME ["/app/data"]
# 容器启动命令
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
6.2 多阶段构建示例
dockerfile
dockerfile
# 构建阶段
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# 运行阶段
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
7. Docker Compose 语法详解
yaml
yaml
version: '3.8' # Compose文件版本
services:
web:
# 使用已有镜像
image: nginx:latest
# 或从Dockerfile构建
build:
context: ./web
dockerfile: Dockerfile
args:
- BUILD_ENV=production
# 容器名称
container_name: web-server
# 端口映射
ports:
- "80:80"
- "443:443"
# 数据卷
volumes:
- ./html:/usr/share/nginx/html:ro # 只读
- nginx-logs:/var/log/nginx
# 环境变量
environment:
- NGINX_HOST=example.com
- NGINX_PORT=80
# 或从文件加载
env_file:
- .env
# 网络
networks:
- frontend
- backend
# 依赖关系
depends_on:
- app
- db
# 重启策略
restart: unless-stopped
# 资源限制
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
# 健康检查
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
app:
build: ./app
volumes:
- app-data:/data
networks:
- backend
environment:
DATABASE_URL: postgresql://user:pass@db:5432/mydb
db:
image: postgres:13
volumes:
- db-data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: secret
POSTGRES_USER: user
POSTGRES_DB: mydb
networks:
- backend
# 网络定义
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # 内部网络
# 数据卷定义
volumes:
nginx-logs:
app-data:
db-data:
driver: local
8. 常用组合命令
bash
bash
# 清理系统
docker system prune -a # 清理所有未使用的资源
docker system df # 查看磁盘使用情况
# 批量操作
docker stop $(docker ps -q) # 停止所有容器
docker rm $(docker ps -aq) # 删除所有容器
docker rmi $(docker images -q) # 删除所有镜像
# 查看资源使用
docker stats # 实时查看资源使用
docker top container_name # 查看容器进程
# 导出/导入镜像
docker save -o myapp.tar myapp:1.0 # 导出镜像
docker load -i myapp.tar # 导入镜像
docker export container_id > container.tar # 导出容器
docker import container.tar myapp:latest # 导入容器为镜像
# 查看历史
docker history image_name # 查看镜像构建历史
9. 最佳实践建议
- 使用.dockerignore文件
- 一个容器只运行一个进程
- 使用多阶段构建减小镜像体积
- 不要在容器中存储数据,使用Volume
- 使用官方镜像作为基础镜像
- 合并RUN指令减少层数
- 使用具体版本标签,避免latest
- 定期清理未使用的资源
- 使用健康检查确保服务可用
- 容器以非root用户运行