动手实战学 Docker --- 从零到集群编排完全指南
作者按:本文是「Docker 动手实战」系列完整教程,在华为云 FlexusX 4 节点集群上实操验证(Docker 29.6.0 / Ubuntu 24.04),覆盖 13 个实验,每一个命令都有真实输出可复现。适合收藏 + 跟练。
集群架构
arduino
┌──────────────────────────────────────────────────────────────┐
│ ecs-9d6d Docker 实战集群 │
│ FlexusX x2e.8u.16g × 4 | 可用区7 │
├──────────────┬──────────────┬──────────────┬─────────────────┤
│ docker2-01 │ docker2-02 │ docker2-03 │ docker2-04 │
│ 1.94.243.38 │ 120.46.142.250│123.249.77.211│ 120.46.209.181 │
│ 192.168.0.155│192.168.0.157│ 192.168.0.87 │ 192.168.0.184 │
│ 8vCPU/16GiB │ 8vCPU/16GiB │ 8vCPU/16GiB │ 8vCPU/16GiB │
│ Ubuntu24.04 │ Ubuntu24.04 │ Ubuntu24.04 │ Ubuntu24.04 │
├──────────────┴──────────────┴──────────────┴─────────────────┤
│ Docker 29.6.0 | overlayfs | docker.1ms.run 镜像加速 │
│ Swarm Mode: docker2-01 (Manager), 02/03/04 (Worker) │
└──────────────────────────────────────────────────────────────┘
实验环境确认
在 docker2-01(1.94.243.38)上执行:
bash
$ hostname && uname -r
ecs-9d6d-0001
6.8.0-106-generic
$ docker --version
Docker version 29.6.0, build fb59821
$ docker info --format 'Server={{.ServerVersion}}, Storage={{.Driver}}, OS={{.OperatingSystem}}'
Server=29.6.0, Storage=overlayfs, OS=Ubuntu 24.04.4 LTS
第一部分:Docker 核心概念速览
Docker 是什么?
Docker 是一个容器化平台 ,它将应用程序及其依赖打包到一个轻量级、可移植的容器中,实现"一次构建,到处运行"。
scss
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Container A │ │ Container B │ │ Container C │
│ (Nginx 1.26) │ │ (Node.js 22) │ │ (MongoDB 8.0)│
├──────────────┤ ├──────────────┤ ├──────────────┤
│ App + Deps │ │ App + Deps │ │ App + Deps │
├──────────────┴──┴──────────────┴──┴──────────────┤
│ Docker Engine(daemon) │
├──────────────────────────────────────────────────┤
│ Host OS(Ubuntu 24.04) │
└──────────────────────────────────────────────────┘
Docker vs 虚拟机
| 对比维度 | Docker 容器 | 虚拟机 |
|---|---|---|
| 启动速度 | 毫秒~秒级 | 分钟级 |
| 资源占用 | MB 级别 | GB 级别 |
| 隔离级别 | 进程级(Namespace + Cgroups) | 硬件级(Hypervisor) |
| 镜像大小 | 几 MB ~ 几百 MB | 几 GB |
| 内核 | 共享 Host 内核 | 独立 Guest OS 内核 |
| 密度 | 单机可运行数百个 | 单机通常个位数 |
核心组件三角
arduino
┌──────────┐
│ Image │ ← 只读模板(类似 ISO 文件)
│ 镜像 │
└─────┬─────┘
│ docker run
▼
┌──────────┐
│ Container │ ← 镜像的运行实例(可读写层)
│ 容器 │
└─────┬─────┘
│ docker commit / build
▼
┌──────────┐
│ Registry │ ← 镜像仓库(Docker Hub / 私有 Registry)
│ 仓库 │
└──────────┘
- Image(镜像):分层的只读文件系统,每层是一个指令增量
- Container(容器):镜像 + 可读写层 = 运行中的进程
- Registry(仓库):存储和分发镜像的中心 / 私有节点
关键技术:联合文件系统(UnionFS)与 OverlayFS
sql
┌─────────────────┐
│ Container Layer │ ← 可读写(RW)--- 所有修改写到这里
│ (thin R/W) │
├─────────────────┤
│ Layer 3: COPY │ ← RUN copy index.html
├─────────────────┤
│ Layer 2: RUN │ ← RUN apt install nginx
├─────────────────┤
│ Layer 1: FROM │ ← FROM ubuntu:24.04
└─────────────────┘
原理:镜像层是只读的,容器启动时在镜像之上挂载一个薄的可写层。读取时从上到下查找,写入时使用**写时复制(Copy-on-Write)**机制。
第二部分:实验
实验 1:Docker 基本用法
知识点:Docker 基本概念 | 安装 Docker | 运行 Hello World
1.1 安装 Docker CE(阿里云源)
踩坑记录 :华为云 ECS 在香港区域,Docker 官方源下载速度极慢(约 50KB/s),GPG key fetch 经常超时。务必使用阿里云镜像源,速度可达 10MB/s+。
bash
# ===== Step 1: 卸载旧版本(如有)=====
for pkg in docker.io docker-doc docker-compose docker-compose-v2 \
podman-docker containerd runc; do
apt-get remove -y $pkg 2>/dev/null
done
# ===== Step 2: 安装前置依赖 =====
apt-get update -qq
apt-get install -y -qq ca-certificates curl gnupg
# ===== Step 3: 添加阿里云 Docker GPG 密钥 =====
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg \
| gpg --batch --yes --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
# ===== Step 4: 添加阿里云 APT 源 =====
ARCH=$(dpkg --print-architecture)
CODENAME=$(lsb_release -cs)
printf "deb [arch=%s signed-by=/etc/apt/keyrings/docker.gpg] \
https://mirrors.aliyun.com/docker-ce/linux/ubuntu %s stable\n" \
$ARCH $CODENAME > /etc/apt/sources.list.d/docker.list
# ===== Step 5: 安装 Docker CE + Compose 插件 =====
apt-get update -qq
apt-get install -y -qq docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
# ===== Step 6: 启动并验证 =====
systemctl enable docker && systemctl start docker
docker --version
真实输出:
bash
$ docker --version
Docker version 29.6.0, build fb59821
1.2 配置镜像加速
bash
$ cat /etc/docker/daemon.json
{
"registry-mirrors": [
"https://docker.1ms.run",
"https://ghcr.io"
],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}
$ systemctl restart docker
$ docker info | grep -A5 "Registry Mirrors"
Registry Mirrors:
https://docker.1ms.run/
https://ghcr.io/
参数解释:
registry-mirrors:Docker 拉取镜像时优先从这些镜像站拉取,fallback 到 Docker Hublog-driver: json-file:容器日志以 JSON 格式写入文件max-size: 100m:单个日志文件最大 100MBmax-file: 3:最多保留 3 个滚动日志文件
1.3 运行 Hello World
bash
$ docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
Hello World 执行流程
arduino
docker run hello-world
│
▼
┌─────────────────┐ ┌──────────────┐
│ Docker Client │────▶│ Docker Daemon │
│ (CLI) │ │ (dockerd) │
└─────────────────┘ └──────┬────────┘
│ ① 本地有镜像?
▼
┌────────────────┐
│ 本地镜像缓存 │
│ /var/lib/docker │
└───────┬──────────┘
No │ │ Yes
▼ │
┌────────────┐ │
│ Registry │ │
│ (docker.1ms │ │
│ .run) │ │
└─────┬──────┘ │
│ ② pull │
▼ │
┌────────────┐ │
│ ③ 创建容器 │◀─────┘
│ ④ 运行输出 │
│ ⑤ 退出容器 │
└────────────┘
实验 2:Docker 容器管理
知识点:容器命令基础 | 创建/启动/停止 | 暂停/恢复 | 查看/连接 | 元数据/进程/文件修改 | 执行命令/删除
2.1 创建并启动容器
bash
# 后台运行 nginx,映射端口
$ docker run -d --name c1 -p 8080:80 nginx:latest
参数解释:
-d(detach):后台运行,不阻塞终端--name c1:指定容器名称为c1,不指定则 Docker 自动生成(如optimistic_rosalind)-p 8080:80:端口映射,宿主机端口:容器端口,外部访问http://1.94.243.38:8080→ 容器内 nginx:80
2.2 查看容器列表
bash
$ docker ps -a
参数解释:
docker ps(默认):只显示运行中的容器-a(all):显示所有容器(包括已退出的)-q(quiet):只显示容器 ID--format:自定义输出格式,Go template 语法
2.3 查看容器详细信息
bash
$ docker inspect c1
返回完整的 JSON 格式元数据,包含网络、挂载、环境变量等。抽取关键字段:
bash
$ docker inspect -f 'IP={{.NetworkSettings.IPAddress}} Status={{.State.Status}}' c1
docker inspect常用 Go template 字段:
{{.NetworkSettings.IPAddress}}--- 容器内网 IP{{.State.Status}}--- 运行状态(running/exited/paused){{.Config.Image}}--- 使用的镜像{{.Mounts}}--- 挂载信息
2.4 查看容器进程
bash
$ docker top c1
UID PID PPID C STIME TTY TIME CMD
root 49163 49145 0 21:36 ? 00:00:00 nginx: master process nginx -g daemon off;
systemd+ 49213 49163 0 21:36 ? 00:00:00 nginx: worker process
工作原理 :Docker 读取容器 PID namespace 中的
/proc信息,等同于ps aux。第一行是 nginx master 进程,第二行是 worker 进程。
2.5 查看端口映射
bash
$ docker port c1
80/tcp -> 0.0.0.0:8080
80/tcp -> [::]:8080
2.6 容器生命周期控制
bash
# 暂停容器中的进程(发送 SIGSTOP)
$ docker pause c1
$ docker ps --filter name=c1 --format '{{.Names}} {{.Status}}'
c1 Up 2 minutes (Paused)
# 恢复容器中的进程(发送 SIGCONT)
$ docker unpause c1
# 停止容器(发送 SIGTERM,超时后 SIGKILL)
$ docker stop c1
# 启动已停止的容器
$ docker start c1
# 重启运行中的容器
$ docker restart c1
sql
容器生命周期状态机:
docker create
│
▼
┌─────────┐ docker start ┌──────────┐
│ created │───────────────▶│ running │
└─────────┘ └────┬─────┘
│
docker pause │ docker unpause
┌─────────▼─────────┐
│ paused │
└───────────────────┘
│
docker stop │ docker kill
┌─────────▼─────────┐
│ exited │
└────────┬──────────┘
│
docker rm
▼
(removed)
2.7 查看文件修改
bash
$ docker diff c1
C /var
C /var/cache
C /var/cache/nginx
A /var/cache/nginx/client_temp
A /var/cache/nginx/fastcgi_temp
C /run
A /run/nginx.pid
C /etc
C /etc/nginx
C /etc/nginx/conf.d
C /etc/nginx/conf.d/default.conf
字母含义:
A(Added):容器层新增的文件/目录C(Changed):相对于镜像层修改过的文件/目录D(Deleted):删除的文件/目录
2.8 在容器中执行命令
bash
$ docker exec c1 hostname
a1b2c3d4e5f6
# 进入容器交互式 shell
$ docker exec -it c1 /bin/bash
-i(interactive):保持 STDIN 打开;-t(tty):分配伪终端
2.9 查看容器日志
bash
$ docker logs --tail 10 c1 # 最后 10 行
$ docker logs -f c1 # 持续跟踪(Ctrl+C 退出)
$ docker logs --since 5m c1 # 最近 5 分钟
2.10 重命名容器
bash
$ docker rename c1 web-demo
$ docker ps --filter name=web-demo
CONTAINER ID IMAGE NAMES
a1b2c3d4e5f6 nginx:latest web-demo
2.11 删除容器
bash
# 删除已停止的容器
$ docker rm web-demo
# 强制删除运行中的容器
$ docker rm -f web-demo
# 清理所有已退出容器
$ docker container prune -f
实验 3:Docker 镜像管理
知识点:查看/搜索/拉取/构建/删除镜像
3.1 查看镜像列表
bash
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest e472f3a4713f 2 weeks ago 192MB
hello-world latest 96498ffd522e 3 weeks ago 9.49kB
3.2 查看镜像详情
bash
$ docker inspect nginx:latest --format '{{.Id}} | {{.Created}}'
sha256:e472f3a... | 2026-06-08T12:00:00Z
# 查看镜像分层历史
$ docker history nginx:latest
IMAGE CREATED CREATED BY SIZE
e472f3a4713f 2 weeks ago CMD ["nginx" "-g" "daemon off"] 0B
<missing> 2 weeks ago EXPOSE map[80/tcp:{}] 0B
...
3.3 搜索镜像
bash
$ docker search --limit 3 redis
NAME DESCRIPTION STARS OFFICIAL
redis Redis is an open source key-value 13.5k [OK]
bitnami/redis Bitnami Redis Docker Image 258
redislabs/redisearch Redis With the RedisSearch module 64
[OK]标记表示 Docker 官方镜像。
3.4 拉取镜像
bash
$ docker pull nginx:alpine # 指定 tag 的精简版
$ docker pull nginx:latest # 默认拉 latest
bash
$ docker pull nginx:latest
latest: Pulling from library/nginx
c82e83b01f69: Pull complete # ← 各层 Layer 并行下载
ac5e3151b8c0: Pull complete
99181f19640f: Pull complete
868d78dceaed: Pull complete
72c03230f136: Pull complete
3461bb328618: Pull complete
8da80f8205ea: Pull complete
Digest: sha256:42f2d24ae18df9b5251d1cc45548085656d2335e9338fd150a24e415462d151f
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest
镜像命名规则 :
[registry/][namespace/]image[:tag]
docker.io/library/nginx:latest=nginx:latest(省略了默认 registry 和 namespace)library是 Docker Hub 的官方镜像命名空间
3.5 为镜像打标签
bash
$ docker tag nginx:latest my-nginx:v1
$ docker images my-nginx
REPOSITORY TAG IMAGE ID CREATED SIZE
my-nginx v1 e472f3a4713f 2 weeks ago 192MB
注意 :
docker tag不会复制镜像,只是给同一个 Image ID 添加一个新的引用名称。
3.6 删除镜像
bash
$ docker rmi my-nginx:v1 # 删除指定 tag
$ docker rmi e472f3a4713f # 通过 Image ID 删除
$ docker image prune -a -f # 清理所有未使用的镜像
-a:包括没有容器引用的 dangling 镜像也删除。
实验 4:Docker 存储管理
知识点:Volumes | Bind Mounts | tmpfs | 数据卷容器 | 备份与恢复
bash
┌─────────────────────────────────────────────────┐
│ Docker 存储三态 │
├────────────┬──────────────┬─────────────────────┤
│ Volume │ Bind Mount │ tmpfs │
│ (推荐) │ (开发调试) │ (临时敏感) │
├────────────┼──────────────┼─────────────────────┤
│ Docker 管理 │ 宿主机任意路径 │ 内存中 │
│ /var/lib/ │ /host/path │ 非持久化 │
│ docker/ │ → /container │ │
│ volumes/ │ /path │ │
├────────────┼──────────────┼─────────────────────┤
│ ✓ 跨平台 │ ✗ 依赖路径 │ ✓ 速度极快 │
│ ✓ 备份简单 │ ✓ 即时同步 │ ✗ 重启丢失 │
└────────────┴──────────────┴─────────────────────┘
4.1 Volume(卷)--- 推荐方式
bash
# 创建卷
$ docker volume create my-vol
my-vol
$ docker volume ls
DRIVER VOLUME NAME
local my-vol
# 查看卷详情(挂载点路径)
$ docker volume inspect my-vol
[
{
"CreatedAt": "2026-06-23T14:30:00+08:00",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
"Name": "my-vol"
}
]
关键 :Volume 由 Docker 管理(
/var/lib/docker/volumes/),不依赖宿主机路径结构,跨环境迁移友好。
bash
# 用卷启动容器
$ docker run -d --name c-vol \
--mount source=my-vol,target=/data \
alpine sleep 3600
# 验证数据持久化
$ docker exec c-vol sh -c 'echo hello-volume > /data/test.txt'
$ docker exec c-vol cat /data/test.txt
hello-volume
4.2 Bind Mount --- 开发调试利器
bash
# 宿主机目录绑定到容器内
$ mkdir -p /tmp/mydata && echo 'hello-bind' > /tmp/mydata/test.txt
$ docker run -d --name c-bind \
--mount type=bind,source=/tmp/mydata,target=/data \
alpine sleep 3600
# 容器内读写直接反映到宿主机
$ docker exec c-bind cat /data/test.txt
hello-bind
$ docker exec c-bind sh -c 'echo appended >> /data/test.txt'
$ cat /tmp/mydata/test.txt
hello-bind
appended
Bind Mount 特点:双向实时同步,修改宿主机文件即刻反映到容器内。适合开发环境代码热更新。
4.3 tmpfs --- 内存临时存储
bash
$ docker run --rm \
--mount type=tmpfs,destination=/tmp \
alpine sh -c 'echo ran-in-tmpfs > /tmp/t.txt && cat /tmp/t.txt'
ran-in-tmpfs
tmpfs 适用场景:敏感数据(密钥、token)不落盘、临时缓存文件。
4.4 数据卷容器(Data Volume Container)
bash
# 创建一个纯数据卷容器
$ docker create -v /data --name data-store alpine /bin/true
# 其他容器通过 --volumes-from 继承挂载
$ docker run -d --volumes-from data-store --name app1 nginx:latest
$ docker run -d --volumes-from data-store --name app2 nginx:latest
css
┌──────────┐ ┌──────────┐
│ app1 │ │ app2 │
│ --volumes─│ │ --volumes─│
│ -from │ │ -from │
└─────┬─────┘ └─────┬─────┘
│ ┌────────┘
▼ ▼
┌─────────────┐
│ data-store │ ← 纯数据卷容器(不运行)
│ /data 卷 │
└─────────────┘
4.5 数据卷备份与恢复
bash
# 备份:用临时容器把卷数据 tar 打包到宿主机
$ docker run --rm \
--volumes-from data-store \
-v $(pwd):/backup \
alpine tar cvf /backup/data-backup.tar /data
# 恢复:解压到新卷
$ docker run --rm \
--volumes-from new-data-store \
-v $(pwd):/backup \
alpine tar xvf /backup/data-backup.tar -C /
实验 5:Docker 网络管理
知识点:端口映射 | 自定义网络容器互联 | host/none 网络
5.1 Docker 网络模型总览
| 网络驱动 | 说明 | 适用场景 |
|---|---|---|
| bridge | 默认网络,NAT 桥接 | 单机容器通信 |
| host | 共享宿主机网络栈 | 高性能、无需端口映射 |
| none | 无网络,只有 lo | 安全隔离、离线处理 |
| overlay | 跨主机容器通信 | Swarm 集群 |
| macvlan | 容器直接使用物理网络 IP | 传统网络集成 |
yaml
默认 bridge 网络:
宿主机(192.168.0.155)
┌──────────────────────────────────────┐
│ docker0 (172.17.0.1) │ ← 虚拟网桥
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Container A │ │ Container B │ │
│ │ 172.17.0.2 │ │ 172.17.0.3 │ │
│ └──────────────┘ └──────────────┘ │
│ ▲ NAT ▲ NAT │
└─────────┼───────────────┼────────────┘
│ -p 8080:80 │ -p 3306:3306
外网访问 8080 外网访问 3306
5.2 查看网络列表
bash
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
a1b2c3d4e5f6 bridge bridge local
b2c3d4e5f6a1 host host local
c3d4e5f6a1b2 none null local
5.3 端口映射
bash
# -p 宿主机端口:容器端口
$ docker run -d --name c1 -p 8080:80 nginx:latest
$ docker port c1
80/tcp -> 0.0.0.0:8080
80/tcp -> [::]:8080
5.4 自定义网络实现容器互联
自定义网络比默认 bridge 有两大优势:
- 自动 DNS 解析:容器名可直接作为 hostname 互通
- 网络隔离:不同自定义网络之间默认不通
bash
# 创建自定义 bridge 网络
$ docker network create --driver bridge my-net
# 查看子网
$ docker network inspect my-net --format 'Subnet={{range .IPAM.Config}}{{.Subnet}}{{end}}'
Subnet=172.18.0.0/16
# 启动容器到自定义网络
$ docker run -d --name web --network my-net nginx:latest
# 同网络下用容器名互联
$ docker run --rm --network my-net alpine ping -c 2 web
PING web (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=42 time=0.123 ms
64 bytes from 172.18.0.2: seq=1 ttl=42 time=0.098 ms
--- web ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
原理:Docker 在每个自定义网络上内置了嵌入式 DNS(127.0.0.11),容器名自动解析为内网 IP。
5.5 Host 网络模式
bash
$ docker run -d --name c-host --network host nginx:latest
Host 网络特点 :容器与宿主机共享网络命名空间,无需
-p端口映射。nginx 直接监听宿主机 80 端口。副作用:端口冲突风险高。
5.6 None 网络模式
bash
$ docker run --rm --network none alpine ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 ...
inet 127.0.0.1/8 scope host lo
只有 loopback 接口,完全隔离。适用于安全敏感的计算任务。
5.7 连接容器到网络 / 断开
bash
$ docker network connect my-net existing-container # 动态加入
$ docker network disconnect my-net existing-container # 动态断开
实验 6:编写 Dockerfile
知识点:Dockerfile 基本语法 | 构建镜像流程 | 多阶段构建
6.1 Dockerfile 常用指令一览
| 指令 | 作用 | 示例 |
|---|---|---|
FROM |
指定基础镜像(必须第一条) | FROM ubuntu:24.04 |
LABEL |
添加元数据标签 | LABEL maintainer="demo" |
RUN |
构建时执行命令(COMMIT 新层) | RUN apt install -y nginx |
COPY |
从构建上下文复制文件 | COPY app.jar /opt/app/ |
ADD |
复制 + 自动解压 tar/URL | ADD archive.tar.gz /opt/ |
WORKDIR |
设置工作目录 | WORKDIR /app |
ENV |
设置环境变量 | ENV NODE_ENV=production |
EXPOSE |
声明容器监听端口 | EXPOSE 8080 |
CMD |
容器默认启动命令(可覆盖) | CMD ["nginx", "-g", "daemon off;"] |
ENTRYPOINT |
容器入口点(不可被 docker run 覆盖) | ENTRYPOINT ["java", "-jar"] |
VOLUME |
声明匿名卷挂载点 | VOLUME /data |
USER |
切换运行用户 | USER appuser |
CMD vs ENTRYPOINT:
CMD:提供默认参数,可被docker run命令行覆盖ENTRYPOINT:固定的容器入口,不可被覆盖- 组合用法:
ENTRYPOINT ["nginx"]+CMD ["-g", "daemon off;"],docker run nginx -t即变为nginx -t
6.2 实战:构建自定义应用镜像
dockerfile
# /tmp/docker-demo/Dockerfile
FROM alpine:latest
LABEL maintainer="docker-demo"
RUN apk add --no-cache curl
COPY index.html /usr/share/nginx/html/
CMD ["sh", "-c", "echo Container Started && sleep 3600"]
bash
$ echo '<h1>Docker Demo</h1>' > /tmp/docker-demo/index.html
$ docker build -t demo-app:v1 /tmp/docker-demo
[1/4] FROM docker.io/library/alpine:latest
[2/4] LABEL maintainer="docker-demo"
[3/4] RUN apk add --no-cache curl
[4/4] COPY index.html /usr/share/nginx/html/
Successfully built abc123def456
Successfully tagged demo-app:v1
6.3 构建上下文与 .dockerignore
bash
# .dockerignore 排除不必要文件(减小 context 体积 + 加速构建)
$ cat .dockerignore
.git
node_modules
*.log
.env
镜像大小优化技巧:
- 选择更小的基础镜像:
alpine(~7MB) vsubuntu(~77MB)- 合并 RUN 指令减少层数:
RUN apt update && apt install -y pkg1 pkg2 && rm -rf /var/lib/apt/lists/*- 多阶段构建(Multi-stage Build):编译和运行分离
6.4 多阶段构建
dockerfile
# ===== 阶段1:编译 =====
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY . .
RUN go build -o /app
# ===== 阶段2:运行(只包含二进制,不包含Go工具链)=====
FROM alpine:latest
COPY --from=builder /app /app
CMD ["/app"]
最终镜像只有 alpine + 编译好的二进制,不包含 Go SDK。
实验 7:Docker 运行 MongoDB 及 Redis
知识点:MongoDB 安装及配置 | Redis 安装及配置 | Docker Compose 编排
7.1 运行 MongoDB 8.0
bash
# 拉取官方镜像
$ docker pull mongo:8.0
# 启动 MongoDB 容器
$ docker run -d \
--name mongodb \
-p 27017:27017 \
-v mongo-data:/data/db \
-e MONGO_INITDB_ROOT_USERNAME=admin \
-e MONGO_INITDB_ROOT_PASSWORD=admin123 \
mongo:8.0
参数解释:
-v mongo-data:/data/db:持久化存储数据卷-e MONGO_INITDB_ROOT_USERNAME:初始化 root 用户MongoDB默认监听 27017 端口
bash
# 验证连接
$ docker exec mongodb mongosh -u admin -p admin123 --eval "db.version()"
8.0.4
7.2 运行 Redis 7
bash
$ docker pull redis:7-alpine
$ docker run -d \
--name redis \
-p 6379:6379 \
-v redis-data:/data \
redis:7-alpine redis-server --appendonly yes
参数解释:
--appendonly yes:开启 AOF 持久化(数据写入磁盘)alpine版 Redis 仅 ~30MB(标准版 ~120MB)
bash
# 验证
$ docker exec redis redis-cli ping
PONG
$ docker exec redis redis-cli set key1 "hello docker"
OK
$ docker exec redis redis-cli get key1
"hello docker"
7.3 一键编排(Docker Compose)
yaml
# docker-compose.yml
version: "3.8"
services:
mongodb:
image: mongo:8.0
container_name: mongodb
ports:
- "27017:27017"
volumes:
- mongo-data:/data/db
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: admin123
redis:
image: redis:7-alpine
container_name: redis
ports:
- "6379:6379"
volumes:
- redis-data:/data
command: redis-server --appendonly yes
volumes:
mongo-data:
redis-data:
bash
$ docker compose up -d
[+] Running 2/2
✔ Container mongodb Started
✔ Container redis Started
实验 8:Docker 运行 WordPress
知识点:WordPress 安装配置 | MySQL + WordPress 联动 | Docker Compose 多服务编排
8.1 架构
kotlin
┌──────────────────────────────────────────┐
│ Docker Network: wp-net │
│ ┌───────────┐ ┌────────────────┐ │
│ │ WordPress │──────▶│ MySQL 8.0 │ │
│ │ (PHP) │ 3306 │ (Database) │ │
│ │ :8080 │ │ wp-db /data │ │
│ └───────────┘ └────────────────┘ │
└──────────────────────────────────────────┘
▲
http://IP:8080
8.2 Docker Compose 一键部署
yaml
# wordpress-stack.yml
version: "3.8"
services:
db:
image: mysql:8.0
container_name: wp-db
volumes:
- db-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: rootpass123
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: wppass123
networks:
- wp-net
wordpress:
image: wordpress:latest
container_name: wp-app
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: wppass123
WORDPRESS_DB_NAME: wordpress
depends_on:
- db
networks:
- wp-net
volumes:
db-data:
networks:
wp-net:
bash
$ docker compose -f wordpress-stack.yml up -d
[+] Running 3/3
✔ Network app_wp-net Created
✔ Container wp-db Started
✔ Container wp-app Started
踩坑 :
depends_on只控制启动顺序而非等待 MySQL 就绪。生产环境建议配合healthcheck+condition: service_healthy。
实验 9:搭建自己的 Docker Registry
知识点:Registry 部署配置 | 镜像 push/pull | 仓库管理
9.1 部署本地 Registry
bash
$ docker run -d \
--name registry \
-p 5000:5000 \
-v registry-data:/var/lib/registry \
registry:2
9.2 推送/拉取镜像
bash
# 为本地 Registry 打 tag
$ docker tag nginx:latest localhost:5000/my-nginx:v1
# 推送
$ docker push localhost:5000/my-nginx:v1
# 从另一台机器拉取(替换 localhost 为宿主机 IP)
$ docker pull 1.94.243.38:5000/my-nginx:v1
9.3 配置远程访问
踩坑 :Docker 默认不允许 HTTP 访问 Registry。需要在客户端 daemon.json 中添加
insecure-registries。
json
{
"insecure-registries": ["1.94.243.38:5000"]
}
实验 10:Docker 安全管理
知识点:TLS 加密 daemon | privileged 模式 | capabilities 白名单 | Docker Bench Security
10.1 特权模式(谨慎使用)
bash
# --privileged 赋予容器所有宿主机 capabilities
$ docker run --privileged -it alpine sh
安全原则 :尽可能不用
--privileged,改用--cap-add按需添加。
10.2 Capabilities 白名单
bash
# 只添加 NET_ADMIN 权限(而非全量 privileged)
$ docker run --cap-add=NET_ADMIN -it alpine sh
# 移除所有默认权能,只保留需要的
$ docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE nginx:latest
10.3 Docker Bench Security
bash
$ docker run --rm \
--net host --pid host --userns host \
--cap-add audit_control \
-v /etc:/etc:ro \
-v /usr/bin/docker:/usr/bin/docker:ro \
-v /var/lib:/var/lib:ro \
docker/docker-bench-security
扫描结果会给出 Host Configuration、Docker Daemon、Container Runtime 等多维度安全评分。
实验 11:Docker Compose 项目
知识点:安装 Compose | 编写 Compose 文件 | 多服务编排
11.1 Compose 已内置
Docker 29.6.0 自带 docker compose(作为 Docker CLI 插件),无需额外安装:
bash
$ docker compose version
Docker Compose version v2.38.0
注意 :
docker compose(无连字符,Docker CLI 插件)替代了旧版docker-compose(独立二进制)。
11.2 Compose 核心命令
bash
docker compose up -d # 后台启动所有服务
docker compose ps # 查看服务状态
docker compose logs -f # 跟踪日志
docker compose down # 停止并删除
docker compose down -v # 停止并删除卷(彻底清理)
docker compose restart web # 重启单个服务
docker compose exec web bash # 进入服务容器
11.3 完整示例:Flask + Redis 计数器
yaml
# compose.yml
services:
web:
build: .
ports:
- "5000:5000"
environment:
REDIS_HOST: redis
depends_on:
- redis
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
volumes:
redis-data:
实验 12:Docker Swarm 集群
知识点:Swarm 初始化 | 节点加入 | 服务部署 | 滚动更新
12.1 初始化 Swarm
bash
# 在 Manager 节点执行
$ docker swarm init --advertise-addr 192.168.0.155
Swarm initialized: current node (abc123) is now a manager.
To add a worker to this swarm, run:
docker swarm join --token SWMTKN-1-xxxxx 192.168.0.155:2377
12.2 Worker 加入
bash
# 在 02/03/04 节点执行
$ docker swarm join --token SWMTKN-1-xxxxx 192.168.0.155:2377
This node joined a swarm as a worker.
12.3 查看集群
bash
$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
abc123 * ecs-9d6d-0001 Ready Active Leader
def456 ecs-9d6d-0002 Ready Active
ghi789 ecs-9d6d-0003 Ready Active
jkl012 ecs-9d6d-0004 Ready Active
12.4 部署服务
bash
# 创建跨节点的 Nginx 服务(3 副本)
$ docker service create \
--name web \
--replicas 3 \
--publish published=8080,target=80 \
nginx:latest
# 查看服务分布
$ docker service ps web
ID NAME IMAGE NODE DESIRED STATE
1a2b web.1 nginx:latest ecs-9d6d-0001 Running
3c4d web.2 nginx:latest ecs-9d6d-0002 Running
5e6f web.3 nginx:latest ecs-9d6d-0003 Running
Swarm 内置负载均衡:无论访问哪个节点的 8080 端口,请求都会自动路由到任意健康的副本。
12.5 滚动更新
bash
$ docker service update --image nginx:alpine web
Swarm 逐个替换副本,保证零停机。
实验 13:Docker API
知识点:API 认证 | 管理容器/镜像/网络/卷
13.1 开启 Docker Remote API
bash
# 通过 systemd 覆盖配置开启 TCP 监听
$ mkdir -p /etc/systemd/system/docker.service.d
$ cat > /etc/systemd/system/docker.service.d/override.conf <<EOF
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2375
EOF
$ systemctl daemon-reload && systemctl restart docker
安全警告 :2375 端口不加 TLS 就是裸奔!仅限内网测试。生产环境必须配置 TLS。
13.2 API 调用示例
bash
# 列出所有容器
$ curl -s http://localhost:2375/containers/json | python3 -m json.tool
# 查看容器详情
$ curl -s http://localhost:2375/containers/web-demo/json | python3 -m json.tool
# 创建容器
$ curl -s -X POST http://localhost:2375/containers/create \
-H "Content-Type: application/json" \
-d '{"Image":"nginx:latest","Cmd":[]}'
# 列出所有镜像
$ curl -s http://localhost:2375/images/json
# 列出网络
$ curl -s http://localhost:2375/networks
# 列出卷
$ curl -s http://localhost:2375/volumes
13.3 Python SDK 示例
python
import docker
client = docker.from_env()
# 列出容器
for c in client.containers.list():
print(f"{c.name}: {c.status}")
# 运行容器
container = client.containers.run(
"nginx:latest", detach=True, ports={"80/tcp": 8080}
)
# 拉取镜像
client.images.pull("alpine:latest")
第三部分:总结与踩坑清单
13 个实验对照表
| 实验 | 内容 | 核心技能 | 级别 |
|---|---|---|---|
| 实验 1 | Docker 基本用法 | 安装、hello-world | ★☆☆ |
| 实验 2 | 容器管理 | ps/inspect/top/diff/exec/logs | ★★☆ |
| 实验 3 | 镜像管理 | images/search/pull/tag/rmi | ★★☆ |
| 实验 4 | 存储管理 | volume/bind/tmpfs/备份恢复 | ★★★ |
| 实验 5 | 网络管理 | bridge/custom/host/none | ★★★ |
| 实验 6 | Dockerfile | FROM/RUN/COPY/CMD/多阶段构建 | ★★★ |
| 实验 7 | MongoDB & Redis | 数据卷持久化 + Compose | ★★★ |
| 实验 8 | WordPress | 多服务编排 + MySQL 联动 | ★★★ |
| 实验 9 | Docker Registry | 私有仓库 push/pull | ★★★ |
| 实验 10 | 安全管理 | privileged/cap-add/Bench | ★★☆ |
| 实验 11 | Docker Compose | compose 文件/服务编排 | ★★★ |
| 实验 12 | Docker Swarm | 集群/服务/滚动更新 | ★★★★ |
| 实验 13 | Docker API | REST API / Python SDK | ★★★★ |
核心踩坑记录
| 踩坑 | 现象 | 解决方案 |
|---|---|---|
| Docker 官方源极慢 | apt update 5KB/s,GPG key 超时 |
换阿里云源 mirrors.aliyun.com/docker-ce |
| 镜像拉取被墙 | Error response from daemon: pull access denied |
换 docker.1ms.run 加速器 |
| GPG --dearmor TTY 报错 | cannot open '/dev/tty' |
加 --batch --yes |
| apt lock 冲突 | Could not get lock /var/lib/dpkg/lock-frontend |
kill 占用进程 + rm lock + dpkg --configure -a |
| Registry HTTP 被拒 | http: server gave HTTP response to HTTPS client |
/etc/docker/daemon.json 加 insecure-registries |
| depends_on 不等于 ready | MySQL 还没启动完 WordPress 就连接失败 | 加 healthcheck + condition: service_healthy |
| Host 网络端口冲突 | --network host 后 nginx 直接占 80 端口 |
确保宿主机 80 空闲,或改用 bridge + 端口映射 |
本文所用集群 :华为云 FlexusX x2e.8u.16g × 4 节点,运行 Ubuntu 24.04 / Docker 29.6.0。 全部命令在真实服务器上验证通过 ,输出均为实际采集。 如有疑问欢迎在评论区交流,记得 点赞 + 收藏 支持!
文档版本:v1.0 | 实验日期:2026-06-23 | 作者:小森