85·模块十六:工程化与部署高级

容器化入门:Docker 与 Python

dockercontainerizationdeployment

第 85 课 - 容器化入门:Docker 与 Python

学习目标

通过本课学习,你将能够:

  1. 理解容器化的核心价值:明白"在我机器上可以运行"问题为何存在,以及 Docker 如何解决它。
  2. 掌握 Dockerfile 的编写:学会为 Python 应用编写清晰、规范的 Dockerfile。
  3. 独立构建并运行 Docker 容器:完成从构建镜像到启动容器的完整流程。
  4. 实现数据持久化与网络配置:了解如何在容器中挂载数据卷和管理容器网络。
  5. 初步接触多容器编排:了解使用 docker-compose 管理多个相关联容器的基本方法。

核心概念

什么是容器化?

想象一下,你开发了一个很棒的 Python Web 应用,它在你的电脑上运行得非常完美。但当你的同事尝试在它的电脑上运行时,却报错了,因为操作系统版本、Python 环境、第三方库版本等条件不一致。这就是经典的 "在我机器上可以运行" 问题。

容器化 就是解决这个问题的方案。它像是一个 "标准集装箱"。你将你的应用程序代码、运行时环境(如 Python 解释器)、系统工具和库、以及所有依赖配置,都打包到一个名为 容器(Container) 的轻量级、独立的单元中。这个容器可以在任何安装了 Docker 的机器上(你的笔记本、同事的台式机、公司的服务器、云端)以完全相同的方式启动和运行,保证了环境的绝对一致。

Docker 是实现容器化最流行的工具。

Docker 的核心组件

  • 镜像(Image):一个只读的模板,相当于一个"食谱"或"蓝图"。它包含了运行应用所需的一切。例如,你可以有一个基于 Ubuntu 的 Python 3.9 系统的镜像,里面预装了 Flask 库。
  • 容器(Container):镜像的运行实例。你可以从一个镜像启动多个容器,就像用同一个食谱(镜像)做出多道菜(容器)。容器是动态的,你可以启动、停止、删除它们。
  • Dockerfile:一个文本文件,包含了一系列指令,用于告诉 Docker 如何自动构建(Build)一个镜像。它就是你编写"食谱"的地方。
  • Docker Hub:一个类似 GitHub 的仓库,用于存储和分发公共(或私有)的 Docker 镜像。你可以拉取(pull)别人做好的镜像,也可以推送(push)自己的镜像上去。

一个生动的比喻

  • Dockerfile 是你写的 "菜谱"
  • 镜像(Image) 是根据这个菜谱做好的、封装好的 "半成品料理包"
  • 容器(Container) 是把料理包 "加热" 后得到的 "一盘热菜",你可以同时做好几盘相同的菜。
  • Docker Hub"美食平台",你可以在上面找到别人分享的菜谱(Dockerfile)或直接购买成品料理包(镜像)。

代码示例

让我们通过一个完整的例子,将一个简单的 Python Flask 应用容器化。

第一步:创建一个简单的 Flask 应用

创建一个新目录 my_flask_app,并在其中创建文件 app.py

# app.py
from flask import Flask
import os

app = Flask(__name__)

# 从环境变量获取消息,默认为 "Hello from Dockerized Python!"
MESSAGE = os.getenv('GREETING_MESSAGE', 'Hello from Dockerized Python!')

@app.route('/')
def home():
    return f'''
    <h1>{MESSAGE}</h1>
    <p>This app is running inside a Docker container!</p>
    <p>Python version: {os.sys.version}</p>
    '''

if __name__ == '__main__':
    # 监听所有可用网络接口,端口 5000
    app.run(host='0.0.0.0', port=5000)

在同一目录下,创建一个 requirements.txt 文件,声明依赖:

Flask==2.3.2

第二步:编写 Dockerfile

my_flask_app 目录下,创建名为 Dockerfile 的文件(注意,没有文件扩展名)。

# Dockerfile

# 1. 使用官方 Python 3.9 的精简版镜像作为基础
FROM python:3.9-slim

# 2. 设置工作目录为 /app
WORKDIR /app

# 3. 先将依赖文件复制进去(利用 Docker 缓存层优化)
COPY requirements.txt .

# 4. 安装依赖
RUN pip install --no-cache-dir -r requirements.txt

# 5. 将当前目录下所有文件复制到容器的 /app 目录
COPY . .

# 6. 声明容器运行时监听的端口
EXPOSE 5000

# 7. 定义容器启动时执行的命令
CMD ["python", "app.py"]

指令解释

  • FROM:指定基础镜像。
  • WORKDIR:为后续指令设置工作目录。
  • COPY:从构建上下文(你的本地目录)复制文件到镜像中。
  • RUN:在镜像构建过程中执行命令(如安装软件)。
  • EXPOSE:声明容器打算使用的端口(仅为文档作用,实际映射需在运行时指定)。
  • CMD:指定容器启动时默认执行的命令。

第三步:构建 Docker 镜像

my_flask_app 目录下,打开终端,执行以下命令:

# -t 给镜像起一个名字(标签),后面跟着"点"表示使用当前目录作为构建上下文
docker build -t my-flask-app:1.0 .

构建成功后,你会看到 Successfully tagged my-flask-app:1.0 的提示。

第四步:运行 Docker 容器

# -p 用于端口映射,将主机的 8080 端口映射到容器的 5000 端口
# -d 表示以后台模式运行容器(守护态)
docker run -p 8080:5000 -d my-flask-app:1.0

现在,打开你的浏览器,访问 http://localhost:8080,你应该能看到 Flask 应用的欢迎页面。

扩展:数据持久化与多容器示例

如果你的应用需要读写文件(如数据库、日志),容器停止后数据会丢失。可以使用 数据卷(Volume) 来持久化数据。

# 运行一个容器,并将主机的 `./data` 目录挂载到容器的 `/app/data` 目录
docker run -p 8080:5000 -v ./data:/app/data -d my-flask-app:1.0

对于更复杂的应用(如 Web + 数据库),可以使用 docker-compose.yml 文件来定义和运行多容器应用。

创建一个 docker-compose.yml 文件:

version: '3.8'

services:
  web:
    build: . # 基于当前目录的 Dockerfile 构建
    ports:
      - "8080:5000"
    depends_on:
      - db # 表示 web 服务依赖 db 服务
    environment:
      - DATABASE_URL=postgresql://user:password@db:5432/mydb

  db:
    image: postgres:13 # 直接使用官方的 PostgreSQL 镜像
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data # 使用命名卷持久化数据库数据

volumes:
  postgres_data: # 声明一个命名卷

然后使用一条命令启动整个应用栈:

docker-compose up -d

实践练习

练习 1:基础容器化

  1. 创建一个简单的 Python 脚本 hello.py,功能是打印 "Hello from the Container!" 和当前时间。
  2. 为它编写一个 Dockerfile。
  3. 构建镜像并运行一个容器,在终端中看到输出。

预期输出

Hello from the Container!
Current time: 2023-10-27 14:30:00.123456

(时间会随当前系统时间变化)

练习 2:带环境变量的应用

  1. 修改上面的 app.py,增加一个 /greet/<name> 路由,问候语从环境变量 GREETING 读取(默认为 Hello)。
  2. 重新构建镜像。
  3. 运行容器时,通过 -e 参数传入环境变量 GREETING=Welcome,验证效果。

预期输出(访问 http://localhost:8080/greet/Alice):

Welcome, Alice!

练习 3:多容器编排挑战(选做)

  1. 基于上面的 docker-compose.yml 示例,尝试将你的 Flask 应用与一个 Redis 缓存服务连接。
  2. 修改 Flask 应用,使其能够连接到 Redis 并记录访问次数。
  3. 使用 docker-compose 启动并测试整个应用。

常见错误

  1. Dockerfile 指令顺序错误:例如,在安装依赖(RUN pip install ...)之前就复制了所有代码(COPY . .)。这会导致每次修改代码后都要重新安装依赖,降低构建速度。应该先复制依赖文件,安装依赖,再复制代码
  2. 端口映射混淆docker run -p 8080:5000 中,左边(8080)是主机端口,右边(5000)是容器内部端口。如果容器应用监听的是 8000 端口,却映射到主机的 8080,那么访问 localhost:8080 是连不上的。
  3. 忽略 .dockerignore 文件:构建上下文(docker build . 中的 .)会包含当前目录所有文件,如 .git__pycache__node_modules 等。这会极大增加构建时间和镜像体积。应创建一个 .dockerignore 文件来排除它们。
    # .dockerignore
    .git
    __pycache__
    *.pyc
    .env
    venv
    
  4. 构建上下文过大:上一个问题的延伸。确保你的项目目录干净,或者使用 .dockerignore,否则会感觉构建过程异常缓慢。
  5. 不理解镜像层缓存:Docker 构建是分层的。如果修改了代码(COPY . . 这一层),它之后的所有层(如 RUN 指令)都会重新执行。合理安排指令顺序(把不常变的层放在前面)可以极大利用缓存,加速重复构建。

小结

本课我们踏入了容器化的世界,核心收获如下:

  • Docker 解决了环境一致性问题,通过将应用及其所有依赖打包到一个独立的容器中,实现了"构建一次,到处运行"。
  • 核心流程是:编写 Dockerfile -> 构建镜像 -> 运行容器
  • 一个良好的 Dockerfile 应该层次清晰,善用 .dockerignore 和构建缓存。
  • 使用 -p 进行端口映射-v 进行数据卷挂载以实现持久化。
  • 对于多服务应用,docker-compose 是定义和运行复杂环境的利器。

掌握了 Docker,你就拥有了将 Python 应用标准化、可移植地部署到任何环境的强大能力。下一课,我们将学习如何将这个过程自动化,进入 CI/CD 的领域。

练习编辑器

python
Loading...

继续学习

完成本课后,建议继续学习下一课「持续集成/持续部署(CI/CD)概念」 以巩固所学知识。