云计算实验笔记(三):应用级 IaC —— 从 docker commit 到 Docker Compose 的全流程实战
关键词:云计算、Infrastructure as Code、IaC、Dockerfile、Docker Compose、多阶段构建、CMD、ENTRYPOINT、声明式 vs 命令式、不可变基础设施
本文是云计算系列的第三篇。前两篇我们打通了 IaaS(虚拟机)和 PaaS(容器)的本地实现,这一篇我们解决一个更进一步的问题——怎么把\”搭环境\”这件事本身也变成代码,让它可版本化、可复现、可分享。完整覆盖 Lab 3 的全部实验步骤:从手动 docker commit → Dockerfile → 多阶段构建 → Docker Compose 多容器编排,并在每一步深挖一点技术细节。
目录
- 一、什么是 Infrastructure as Code(IaC)
- 二、环境准备
- 三、第一步:手动定制镜像(docker commit)
- 四、第二步:用 Dockerfile 复刻同样的成果
- 五、第三步:改进脚本 + CMD vs ENTRYPOINT
- 六、第四步:Dockerfile + C 程序(alpine_gcc)
- 七、第五步:多阶段构建(Multi-stage Build)
- 八、第六步:多容器环境 —— Docker Compose 入门
- 九、综合实战:三个完整场景
- 十、深入:声明式 vs 命令式 + 不可变基础设施
- 十一、安全与最佳实践
- 十二、Docker 概念 ↔ 云原生 / DevOps 映射表
- 十三、总结
一、什么是 Infrastructure as Code(IaC)
Infrastructure as Code(基础设施即代码) 的核心思想是:
把\”搭建一套环境\”从一系列 手动操作,变成一份可读、可写、可版本化的 配置文件。
传统运维做法:
SSH 进服务器 → apt install nginx → vim 改配置 → systemctl restart → 截图发邮件
IaC 做法:
git push → CI 拿到代码 → docker build / terraform apply → 环境自动起来
它带来的收益是结构性的:
| 可复现性 | 看运维心情 | 任何机器、任何时间都一样 |
| 版本控制 | 几乎不可能 | Git 全程留痕 |
| 协作 | 文档驱动,靠人 | 代码驱动,靠 Pull Request |
| 自动化 | 难 | 天然适合 CI/CD |
| 回滚 | 重写文档 | git revert |
在容器世界里,IaC 主要由两类文件承担:
- Dockerfile —— 描述 单个镜像 应该长什么样。
- compose.yaml —— 描述 一组容器 怎么协同工作。
它们俩共同构成了 应用级 IaC。再往上还有 基础设施级 IaC(Terraform、CloudFormation、Pulumi),那是描述云资源(VPC、子网、负载均衡器、数据库实例)的,更宏观,但思想完全一致。
二、环境准备
继续用 Lab 1 创建的 Debian VM(NAT 网络),并在其中安装好 Docker Engine(详见 Lab 2)。
sudo systemctl status docker
sudo systemctl start docker
docker –version
docker compose version # 注意:现代 Docker 内置 compose 插件,命令是 docker compose(中间空格)
历史版本叫 docker-compose(带横杠),由 Python 写成;新版改名 docker compose,是 Docker CLI 的内置子命令,性能更好。两者命令几乎兼容。
三、第一步:手动定制镜像(docker commit)
实验目标:把 alpine 镜像定制一下,在 /root 加一个 README.txt 和一个 hello.sh。
3.1 启动一个交互容器
docker run -it –name alpine_1 alpine
进入容器后:
# / cd /root
~ # echo \”I am a custom image.\” > README.txt
~ # cat README.txt
I am a custom image.
~ # exit
容器退出但还在(docker ps -a 看得到)。
3.2 把容器\”快照\”成镜像
docker commit -m \”my custom alpine image\” -a \”alice\” alpine_1 custom_alpine:v1
- -m:提交说明(类似 git commit message)。
- -a:作者。
- alpine_1:源容器名。
- custom_alpine:v1:目标镜像名:tag。
这条命令做的事情:把容器可读写层的所有改动凝固成新的一层,叠加到原镜像之上,形成一个新镜像。
3.3 清理 + 检验
docker rm alpine_1
docker inspect custom_alpine:v1
在输出里你可以看到:
\”Comment\”: \”my custom alpine image\”,
\”Author\”: \”alice\”,
\”Config\”: {
… },
\”RootFS\”: {
\”Type\”: \”layers\”,
\”Layers\”: [
\”sha256:…\”, ← Alpine 原始层
\”sha256:…\” ← 我们 commit 出来的新层
]
}
这就是 镜像分层 的实物证据——任何对镜像的改动都是新加一层,旧层永远不动。
3.4 导出为 tar 归档(离线分发)
docker save -o custom_alpine_v1.tar custom_alpine:v1
docker rmi custom_alpine:v1
docker load -i custom_alpine_v1.tar
docker images # 验证又回来了
save / load 操作的是 镜像(包含所有层 + 元数据); export / import 操作的是 容器(只有当前文件系统快照,丢失分层信息)。 永远优先用 save / load,除非你有特殊需求。
3.5 进入容器写一个 shell 脚本
docker run -it custom_alpine:v1
~ # cd /root
~ # echo \’echo \”hello $1\”\’ > hello.sh
~ # chmod +x hello.sh
~ # ./hello.sh alice
hello alice
~ # exit
每一行的解释(题目要求):
| cd /root | 切到 root 家目录 |
| echo \’echo \”hello $1\”\’ > hello.sh | 把字符串 echo \”hello $1\” 写到 hello.sh;外层单引号防止 shell 提前解释 $1 |
| chmod +x hello.sh | 给文件加可执行权限 |
| ./hello.sh alice | 执行脚本,alice 作为第一个位置参数传给 $1 |
$1 是 Bourne shell 的 位置参数:脚本被调用时,命令行后面第 N 个词就是 $N。$0 是脚本自身名字。
3.6 从宿主再执行一次 + 二次 commit
退出容器后,容器停止但没删。重启 + 在宿主上直接执行容器里的脚本:
docker start <container_id>
docker exec <container_id> /root/hello.sh bob
# 输出 hello bob
把这次的容器再 commit 成 v2:
docker commit -m \”add hello.sh\” -a \”alice\” <container_id> custom_alpine:v2
docker run –rm -it custom_alpine:v2 /root/hello.sh charlie
# hello charlie
3.7 关键问题:v2 的父镜像 SHA256 是什么?
docker inspect custom_alpine:v2 | grep -A1 Parent
docker history custom_alpine:v2
Parent 字段就是 v1 的 image ID,因为:
v2 = v1 + 一层新的可读写层差分
docker history 会列出每一层的 SHA256 和体积。这种\”父→子\”链式关系,就是 Docker 镜像的版本控制底层。
3.8 为什么 docker commit 不是好做法
经过上面这一通操作,你应该感受到了:
- 不可复现:换一个人,可能漏掉某一步。
- 无版本控制:除非每次 commit 都另起一个 tag,否则改了什么完全不知道。
- 黑盒:你看不到一个镜像里到底装了什么。
- 难协作:没办法在 GitHub 上 review 一个\”镜像\”的\”改动\”。
正确做法是把\”我在容器里做了什么\”写成 Dockerfile,让 Docker 自动复现整个过程。
四、第二步:用 Dockerfile 复刻同样的成果
4.1 目录结构
custom_alpine/
├── Dockerfile
├── README.txt
└── hello.sh
README.txt:
I am a custom image.
hello.sh:
#!/bin/sh
echo \”hello $1\”
Dockerfile:
FROM alpine:latest
WORKDIR /root
COPY README.txt .
COPY hello.sh .
RUN chmod +x hello.sh
CMD [\”sh\”]
4.2 Dockerfile 指令逐条解析
| FROM <image> | 指定基础镜像,永远是第一行(除了 ARG) | ✅ |
| WORKDIR <path> | 切换工作目录,相当于 mkdir -p && cd< |

