Data Engineering Zoomcamp:用 Docker 容器化 Python 数据管道(pip 与 uv 双方案实战)
2026/9/12 9:40:07 网站建设 项目流程

Data Engineering Zoomcamp:用 Docker 容器化 Python 数据管道(pip 与 uv 双方案实战)

【免费下载链接】data-engineering-zoomcampData Engineering Zoomcamp is a free 9-week course on building production-ready data pipelines. Join the course here 👇🏼项目地址: https://gitcode.com/GitHub_Trending/da/data-engineering-zoomcamp

导读

本篇指南是 Data Engineering Zoomcamp 2027 模块 01「Containerization and Infrastructure as Code」的第三个单元,讲解如何把上一单元中编写的 Python 数据管道脚本容器化。你将学会编写两种Dockerfile(基于pip与基于uv),掌握docker build/docker run的核心用法与参数传递方式,并结合仓库中真实的数据摄取脚本(ingest_data.py)理解容器化在「CSV → 数据管道 → PostgreSQL」场景下的实际落地方式。读完本文,你可以独立把一个 pandas 管道打包成可复用的 Docker 镜像。

1. 为什么要把数据管道容器化

在前一单元 02-virtual-environment.md 中,我们创建了一个pipeline目录,并在其中编写了pipeline.py:脚本接收命令行参数(如日期day),用 pandas 构造 DataFrame 并输出为 Parquet 文件。为了管理 pandas、pyarrow 等依赖,我们引入了uv虚拟环境来隔离项目依赖。

但虚拟环境只解决了「本机依赖隔离」的问题。当管道需要部署到其他机器、云端或 CI 环境时,仍然会遇到:

  • 目标机器上缺少 Python 与依赖库,需要重复安装;
  • 不同环境间 Python 版本、依赖版本不一致导致「在我机器上能跑」;
  • 每次部署都要手工执行安装步骤,难以保证可复现。

Docker 的解决方案是把「代码 + 运行时 + 依赖」一起打包进一个不可变镜像,让管道在任何装有 Docker 的机器上以完全一致的方式运行。这正是本单元 03-dockerizing-pipeline.md 要完成的事。

2. 方案一:基于 pip 的简单 Dockerfile

文档首先给出了一个最直观的容器化方案——直接在基础镜像中用pip安装依赖:

# base Docker image that we will build on FROM python:3.13.11-slim # set up our image by installing prerequisites; pandas in this case RUN pip install pandas pyarrow # set up the working directory inside the container WORKDIR /app # copy the script to the container. 1st name is source file, 2nd is destination COPY pipeline.py pipeline.py # define what to do first when the container runs # in this example, we will just run the script ENTRYPOINT ["python", "pipeline.py"]

2.1 指令逐行解析

指令作用说明
FROM指定基础镜像这里使用官方python:3.13.11-slim,slim 变体体积更小,适合作为运行管道的基础
RUN在构建期间执行命令pip install pandas pyarrow一次性安装管道所需依赖
WORKDIR设置容器内工作目录后续COPYENTRYPOINT都相对于该目录执行(此处为/app
COPY将文件复制进镜像第一个参数是宿主机源文件,第二个参数是容器内目标路径
ENTRYPOINT定义容器启动时执行的默认命令此处直接运行python pipeline.py

2.2 构建与运行

假设pipeline.pyDockerfile位于同一目录,并在该目录下执行构建:

docker build -t test:pandas .
  • 镜像名(repository)为test,标签(tag)为pandas
  • 若省略标签,Docker 会默认使用latest
  • 命令末尾的.表示构建上下文(build context)为当前目录。

运行容器并传入参数,使管道脚本通过sys.argv接收到它:

docker run -it test:pandas some_number

运行后你应看到与直接执行python pipeline.py some_number完全一致的输出——这正是容器化的意义:同样的代码、同样的行为-it表示以交互模式挂接终端,便于观察管道日志输出。

注意:以上说明假定pipeline.pyDockerfile在同一目录,且 Docker 命令也在该目录下执行,否则构建上下文将无法找到源文件。

2.3 pip 方案的局限

pip 方案足够简单,但存在两个工程隐患:

  1. 版本不可复现pip install pandas pyarrow每次构建都拉取当时的最新版本,依赖漂移会导致镜像内容随时间变化;
  2. 构建慢、缓存差:代码与依赖安装混在一起,改动一行代码就要重新解析依赖。

这正是文档随后引入uv方案的原因。

3. 方案二:基于 uv 的 Dockerfile(推荐)

uv是前序单元中已经使用的现代 Python 包管理器(用 Rust 编写,由 02-virtual-environment.md 介绍),本单元把它带入容器构建流程:

# Start with slim Python 3.13 image FROM python:3.13.10-slim # Copy uv binary from official uv image (multi-stage build pattern) COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/ # Set working directory WORKDIR /app # Add virtual environment to PATH so we can use installed packages ENV PATH="/app/.venv/bin:$PATH" # Copy dependency files first (better layer caching) COPY "pyproject.toml" "uv.lock" ".python-version" ./ # Install dependencies from lock file (ensures reproducible builds) RUN uv sync --locked # Copy application code COPY pipeline.py pipeline.py # Set entry point ENTRYPOINT ["uv", "run", "python", "pipeline.py"]

3.1 与 pip 方案的关键差异

  • COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/:直接从官方 uv 镜像把二进制复制进当前镜像,属于多阶段构建(multi-stage build)模式,无需在容器内再用pip install uv
  • 先复制依赖清单、再复制代码pyproject.tomluv.lock.python-version先进入镜像并执行RUN uv sync --locked。由于 Docker 按层缓存,只要依赖文件没变,这层缓存就能被后续构建复用——改动代码时无需重新解析安装依赖
  • RUN uv sync --locked:严格按照锁文件uv.lock安装依赖,保证每次构建产出的环境完全一致(可复现构建);
  • ENV PATH="/app/.venv/bin:$PATH":把 uv 创建的虚拟环境加入PATH,使容器内的python直接解析到虚拟环境中的解释器;
  • ENTRYPOINT ["uv", "run", "python", "pipeline.py"]:通过uv run启动脚本,它会自动确认虚拟环境已就绪。

3.2 为什么依赖清单里要有这三个文件

文件作用
pyproject.toml项目声明文件,记录元数据与直接依赖及其版本约束
uv.lock锁定全部依赖(含传递依赖)的精确版本,是可复现构建的基石
.python-version声明项目要求的 Python 版本,uv 会据此解析解释器

在仓库的 pipeline/pyproject.toml 中可以看到,真实项目的依赖不止 pandas 与 pyarrow,还包括click(命令行参数解析)、psycopg2-binary(PostgreSQL 驱动)、sqlalchemy(写库 ORM)、tqdm(进度条),以及 dev 依赖组jupyterpgcli。这种「依赖清单驱动构建」的方式,正是 uv 方案面向生产可维护性的设计。

4. 仓库中的真实 Dockerfile:从 pipeline.py 到 ingest_data.py

文档中的pipeline.py是教学用的最小示例。在课程后续单元中,这个管道会演进为真正对接 PostgreSQL 的数据摄取脚本ingest_data.py(见 06-ingestion-script.md),其容器化 Dockerfile 位于 pipeline/Dockerfile:

FROM python:3.13.11-slim COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/ WORKDIR /code ENV PATH="/code/.venv/bin:$PATH" COPY pyproject.toml .python-version uv.lock ./ RUN uv sync --locked COPY ingest_data.py . ENTRYPOINT ["python", "ingest_data.py"]

4.1 与教学版 Dockerfile 的差异

  • WORKDIR /code替代/app,目录命名更贴近「代码运行目录」的语义;
  • 依赖清单复制与uv sync --locked的顺序保持一致,同样利用层缓存;
  • COPY ingest_data.py .只复制单个入口脚本,镜像更精简;
  • 入口为python ingest_data.py(注意此处未再显式加uv run,因为PATH已指向虚拟环境)。

4.2 从最小示例到生产管道的思路迁移

ingest_data.py使用click定义了一组命令行选项(--pg-user--pg-pass--pg-host--pg-port--pg-db--year--month--target-table--chunksize),以 10 万行/块的方式流式读取 NYC 出租车数据并写入 PostgreSQL。容器化后,构建与运行方式为:

cd pipeline docker build -t taxi_ingest:v001 . docker run -it \ --network=pg-network \ taxi_ingest:v001 \ --pg-user=root \ --pg-pass=root \ --pg-host=pgdatabase \ --pg-port=5432 \ --pg-db=ny_taxi \ --target-table=yellow_taxi_trips

这里有两个关键点(详见 08-dockerizing-ingestion.md):

  1. --network=pg-network必须放在镜像名之前,用于让摄取容器找到另一个网络中的 PostgreSQL 容器;
  2. --pg-host不再指向localhost,而是指向 PostgreSQL 容器的服务名/容器名pgdatabase)。

5. 容器化管道的完整工作流:结合 Docker Compose

在实际课程练习中,PostgreSQL 与 pgAdmin 往往通过 docker-compose.yaml 一键拉起(详见 09-docker-compose.md):

services: pgdatabase: image: postgres:18 environment: POSTGRES_USER: "root" POSTGRES_PASSWORD: "root" POSTGRES_DB: "ny_taxi" volumes: - ny_taxi_postgres_data:/var/lib/postgresql ports: - "5432:5432" pgadmin: image: dpage/pgadmin4 environment: PGADMIN_DEFAULT_EMAIL: "admin@admin.com" PGADMIN_DEFAULT_PASSWORD: "root" volumes: - pgadmin_data:/var/lib/pgadmin ports: - "8085:80" volumes: ny_taxi_postgres_data: pgadmin_data:

启动服务:

docker-compose up # 前台运行 docker-compose up -d # 后台(detached)运行 docker-compose down # 停止并移除容器 docker-compose down -v # 停止并连同数据卷一并移除 docker-compose logs # 查看日志

当 PostgreSQL 由 compose 管理时,摄取容器需加入 compose 自动创建的网络(名称通常形如pipeline_default,取决于目录名),运行:

docker network ls docker run -it --rm \ --network=pipeline_default \ taxi_ingest:v001 \ --pg-user=root \ --pg-pass=root \ --pg-host=pgdatabase \ --pg-port=5432 \ --pg-db=ny_taxi \ --target-table=yellow_taxi_trips

至此,**管道代码(Docker 镜像)与存储服务(compose 网络)**被清晰地解耦:镜像只管「读数据、写数据」,服务编排只负责「把数据库和监控工具跑起来」,两者通过 Docker 网络按名称互相发现。

6. 总结与实践要点

对比维度pip 方案uv 方案
依赖管理pip install,版本不锁定uv sync --locked,锁文件保证可复现
构建缓存依赖与代码同层,缓存收益低先拷依赖清单再拷代码,代码改动命中缓存
虚拟环境无显式隔离uv 自动管理.venv并写入PATH
适用场景快速原型、演示面向生产、长期维护的管道

动手实践建议按以下顺序验证:

  1. 参照 02-virtual-environment.md 编写pipeline.py并用uv初始化项目(uv init --python=3.13uv add pandas pyarrow);
  2. 依次使用 pip 与 uv 两个 Dockerfile 构建镜像(docker build -t test:pandas ./docker build -t test:uv .),对比镜像构建速度与输出;
  3. docker run -it test:pandas 10传入参数验证sys.argv传递;
  4. 进阶练习:把仓库中的 pipeline/Dockerfile 与 pipeline/ingest_data.py 结合 compose 跑通「下载 → 清洗 → 入 PostgreSQL」的完整链路。

容器化的收益在后续所有模块中都会反复体现:无论后续是 Spark 批处理、Flink 流处理还是 dbt 转换,凡是涉及 Python 依赖的环节,都可以复用本文的uv + 锁文件 + 层缓存模式,确保可复现、可迁移、可交付。

【免费下载链接】data-engineering-zoomcampData Engineering Zoomcamp is a free 9-week course on building production-ready data pipelines. Join the course here 👇🏼项目地址: https://gitcode.com/GitHub_Trending/da/data-engineering-zoomcamp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询