人人都能学编程
English 开始学习
专项 · DevOps第 1 课 / 共 5 课免费阅读

容器与 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"]
逐步执行
  1. L1从一个小巧的官方 Python 镜像开始,并固定版本。
  2. L3先只复制依赖清单(下一课解释原因)。
  3. L4在构建时安装库,这样启动容器就很快。
  4. L5然后复制项目的其余部分。
  5. L7不要用 root 运行:应用被攻破时,攻击者能拿到的权限更少。
  6. 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
逐步执行
  1. L1latest 会随时间变化:下个月构建时可能拿到另一个版本的 Python。
  2. L5任何能拉取这个镜像的人都能读到这个密钥。应该在容器启动时再传入密钥。
  3. L6没有 USER 这一行,所以应用以 root 身份运行。

常见错误

使用 :latest

今天的 python:latest 和三个月后的是不同的镜像,构建就不可重复了。固定一个版本,比如 python:3.12-slim。

把密钥打进镜像

ENV 和 COPY 会把值永久留在镜像层里。密钥应该在容器启动时提供(环境变量或密钥管理服务)。

以 root 身份运行

默认情况下容器里的进程是 root。加一行 USER,这样应用里的漏洞造成的破坏会小得多。

小测验

1. 镜像和容器有什么区别?

  1. A镜像是按配方打包的结果;容器是它的一个运行副本
  2. B它们是同一个东西的两个名字
  3. C容器是更大的镜像
查看答案

答案:A

构建一次得到镜像,然后用它启动任意多个容器。

2. `RUN pip install flask` 和 `CMD ["flask", "run"]`。它们分别在什么时候运行?

  1. ARUN 在构建镜像时;CMD 在每次容器启动时
  2. B都在容器启动时
  3. C都在构建时
查看答案

答案:A

构建时的工作写在 RUN 里;启动命令写在 CMD 里。

动手练习

3 道练习,分三层:先预测程序的输出,再按新需求改程序,最后从零写一个。代码在浏览器里运行,提交马上判题。

  1. 预测输出实际启动的是什么?

    用这个镜像启动一个容器。运行的是什么、用哪个用户、在哪个目录?

  2. 改需求修好有风险的 Dockerfile

    修好三个问题:把基础镜像固定为 python:3.12-slim,用普通用户运行(USER 1000),并且把密码从镜像里拿掉(容器启动时会再传进来)。

  3. 从零写把 Node 应用装进容器

    给一个 Node.js 服务器写 Dockerfile:

    • 基础镜像 node:20-slim,工作目录 /app
    • 先复制 package.json 和 package-lock.json,运行 npm ci
    • 再复制项目的其余部分
    • 服务器监听 3000 端口
    • 用 node 用户运行(镜像里已经有这个用户)
    • 用 node server.js 启动(用 ["..."] 写法)

在互动课里练这一课

打开互动课,这些例子都能直接改、直接运行,练习题提交马上判题。免费开始,不用注册。

本课术语

术语中文英文定义
DockerDockerThe most common tool for building and running containers.
DockerfileDockerfileThe recipe for a container image.
Container image容器镜像An app packaged with everything it needs to run.