容器与 Dockerfile · DevOps:容器、CI 与 Kubernetes
怎么交付一个应用,让它在哪里都一样地运行?
课程目录为什么需要它
「在我电脑上是好的」——发生这种事,是因为服务器上 Python 版本不同、少了某个库,或者某个设置不一样。容器镜像把应用和它需要的一切打包在一起,所以笔记本、测试服务器和生产环境跑的是完全相同的东西。如今大多数后端软件都是用 Docker 镜像来交付的。
直觉理解
Dockerfile 是镜像的配方。每一行是一条指令,从上往下执行:
| 指令 | 作用 |
|---|---|
FROM python:3.12-slim | 从一个基础镜像开始(一个操作系统加上 Python) |
WORKDIR /app | 进入这个目录(不存在就创建) |
COPY requirements.txt . | 把项目里的文件复制进镜像 |
RUN pip install … | 构建时运行一条命令 |
ENV PORT=8000 | 设置一个环境变量 |
EXPOSE 8000 | 说明应用监听的端口 |
USER 1000 | 用这个用户运行,而不是 root |
CMD ["python", "app.py"] | 容器启动时要运行的命令 |
docker build -t shop . 把 Dockerfile 变成一个镜像;docker run -p 8000:8000 shop 用它启动一个容器。一个镜像可以运行很多个容器。
点 运行,可以看到每个构建步骤和最终镜像的概要。警告会指出常见的错误。输出里的镜像大小是模拟器的粗略估计;真实大小用 docker images 查看。
定义
镜像
A packaged, read-only snapshot of an app and everything it needs, built from a Dockerfile.应用及其所需一切的只读打包快照,由 Dockerfile 构建而来。
容器
A running instance of an image, isolated from the other processes on the machine.镜像的一个运行实例,和机器上的其他进程相互隔离。
RUN 与 CMD
RUN executes while the image is built (installing things); CMD is what runs each time a container starts.RUN 在构建镜像时执行(安装东西);CMD 是每次容器启动时运行的命令。
示例
一个 Python Web 应用的 Dockerfile
在互动课里运行运行它,看看构建步骤和最终镜像会做什么。
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
USER 1000
CMD ["python", "app.py"]- L1从一个小巧的官方 Python 镜像开始,并固定版本。
- L3先只复制依赖清单(下一课解释原因)。
- L4在构建时安装库,这样启动容器就很快。
- L5然后复制项目的其余部分。
- L7不要用 root 运行:应用被攻破时,攻击者能拿到的权限更少。
- L8容器启动时运行的内容。["..."] 写法直接运行程序,不经过 shell。
一个有问题的 Dockerfile
在互动课里运行它能构建,但警告列出了三个真实的风险。
FROM python:latest
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
ENV API_KEY=sk_live_123
CMD python app.py- L1latest 会随时间变化:下个月构建时可能拿到另一个版本的 Python。
- L5任何能拉取这个镜像的人都能读到这个密钥。应该在容器启动时再传入密钥。
- L6没有 USER 这一行,所以应用以 root 身份运行。
常见错误
使用 :latest
今天的 python:latest 和三个月后的是不同的镜像,构建就不可重复了。固定一个版本,比如 python:3.12-slim。
把密钥打进镜像
ENV 和 COPY 会把值永久留在镜像层里。密钥应该在容器启动时提供(环境变量或密钥管理服务)。
以 root 身份运行
默认情况下容器里的进程是 root。加一行 USER,这样应用里的漏洞造成的破坏会小得多。
小测验
1. 镜像和容器有什么区别?
查看答案
答案:A
构建一次得到镜像,然后用它启动任意多个容器。
2. `RUN pip install flask` 和 `CMD ["flask", "run"]`。它们分别在什么时候运行?
查看答案
答案:A
构建时的工作写在 RUN 里;启动命令写在 CMD 里。
动手练习
3 道练习,分三层:先预测程序的输出,再按新需求改程序,最后从零写一个。代码在浏览器里运行,提交马上判题。
- 预测输出实际启动的是什么?
用这个镜像启动一个容器。运行的是什么、用哪个用户、在哪个目录?
- 改需求修好有风险的 Dockerfile
修好三个问题:把基础镜像固定为
python:3.12-slim,用普通用户运行(USER 1000),并且把密码从镜像里拿掉(容器启动时会再传进来)。 - 从零写把 Node 应用装进容器
给一个 Node.js 服务器写 Dockerfile:
- 基础镜像
node:20-slim,工作目录/app - 先复制
package.json和package-lock.json,运行npm ci - 再复制项目的其余部分
- 服务器监听
3000端口 - 用
node用户运行(镜像里已经有这个用户) - 用
node server.js启动(用["..."]写法)
- 基础镜像
本课术语
| 术语 | 中文 | 英文定义 |
|---|---|---|
| Docker | Docker | The most common tool for building and running containers. |
| Dockerfile | Dockerfile | The recipe for a container image. |
| Container image | 容器镜像 | An app packaged with everything it needs to run. |