ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Dockerizing the Ingestion Script:用 Docker 容器化 NY Taxi 数据摄取脚本的完整实战指南

Dockerizing the Ingestion Script:用 Docker 容器化 NY Taxi 数据摄取脚本的完整实战指南 Dockerizing the Ingestion Script用 Docker 容器化 NY Taxi 数据摄取脚本的完整实战指南【免费下载链接】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 第 1 模块「Docker 与 PostgreSQL」系列教程的核心一环讲解如何把此前用 Python click pandas SQLAlchemy 写好的数据摄取脚本 ingest_data.py 封装进 Docker 镜像使其可以脱离宿主机环境、跨机器可复现地运行并与已经容器化的 PostgreSQL 通过 Docker 虚拟网络通信。读完本文你将掌握基于 uv 锁文件构建可复现 Python 镜像的完整思路、docker build/docker run的关键参数语义以及容器间--network与--name协作的底层原理从而把「写脚本 → 容器化 → 入库」这条链路跑通。前置知识我们从哪里来在进入本节之前教程已经完成了两件关键准备工作它们是理解本篇文章的前提PostgreSQL 已容器化并加入pg-network网络在 04-postgres-docker.md 中我们用docker run启动了postgres:18容器并通过--networkpg-network --name pgdatabase将其注册到名为pg-network的 Docker 虚拟网络主机名pgdatabase就是容器在该网络内的 DNS 名称。数据摄取脚本已具备命令行参数化能力在 06-ingestion-script.md 中脚本已用click将数据库连接信息用户、密码、主机、端口、库名与数据参数年份、月份、目标表名、分块大小全部参数化见 pipeline/ingest_data.py。之所以要「容器化摄取脚本」是因为脚本依赖 pandas、SQLAlchemy、psycopg2 等多个 Python 库在不同机器上手动安装极易出现版本不一致、依赖冲突的问题。把脚本连同其精确的依赖环境一起打包进镜像就能实现「一次构建、处处运行」。核心配置文件pipeline/Dockerfile 逐行拆解容器化的核心是 pipeline/Dockerfile。教程文档给出了基于uv的推荐写法完整内容如下FROM python:3.13.11-slim COPY --fromghcr.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 [uv, run, python, ingest_data.py]逐行解读每一层指令的实际作用指令作用与设计意图FROM python:3.13.11-slim以精简版 Python 3.13 镜像为基础相比完整版大幅减小镜像体积只保留运行 Python 所需的最小系统组件COPY --fromghcr.io/astral-sh/uv:latest /uv /bin/采用「多阶段构建」模式不从源码编译、也不在基础镜像里pip install uv而是直接从官方 uv 镜像复制编译好的 uv 二进制到/bin/保证版本一致且构建更快WORKDIR /code设定容器内的工作目录后续COPY、RUN、ENTRYPOINT都在该目录下执行ENV PATH/code/.venv/bin:$PATH把 uv 创建的虚拟环境可执行目录加入 PATH使得容器内可直接调用已安装的包与工具COPY pyproject.toml .python-version uv.lock ./先只复制依赖清单文件再执行依赖安装。这样只要依赖没有变化Docker 就会复用缓存的层大幅加快重复构建速度RUN uv sync --locked严格按照uv.lock锁文件安装依赖--locked表示若 lock 文件与pyproject.toml不一致则直接报错从而保证任何机器、任何时间构建出的环境完全一致可复现构建COPY ingest_data.py .依赖安装完成后再复制应用代码业务代码变更不影响依赖层缓存ENTRYPOINT [uv, run, python, ingest_data.py]设置容器启动时默认执行的命令通过 uv 在虚拟环境中运行摄取脚本一个值得注意的细节教程文档中ENTRYPOINT写作[uv, run, python, ingest_data.py]而仓库实际提交的 pipeline/Dockerfile 中是[python, ingest_data.py]。两者功能等价——因为ENV PATH已把.venv/bin加入 PATH容器内的python实际上就是虚拟环境中的解释器而显式使用uv run则更强调「在项目虚拟环境上下文中执行」。无论哪种写法最终效果都是在已锁定依赖的虚拟环境中运行脚本。依赖清单pyproject.toml 决定了镜像里装什么uv sync --locked安装的内容由 pipeline/pyproject.toml 定义[project] name pipeline version 0.1.0 requires-python 3.13 dependencies [ click8.3.1, pandas2.3.3, psycopg2-binary2.9.11, pyarrow22.0.0, sqlalchemy2.0.44, tqdm4.67.1, ] [dependency-groups] dev [ jupyter1.1.1, pgcli4.3.0, ]dependencies运行摄取脚本所必需的 6 个包——click负责命令行参数解析pandas负责读取与分块处理 CSVpsycopg2-binary是 PostgreSQL 驱动pyarrow用于高效的列式数据交换sqlalchemy提供数据库 ORM/连接层tqdm显示分块导入进度条。[dependency-groups].devjupyter与pgcli仅用于开发调试转换 notebook、连接数据库检查不会被打进生产镜像——这正是 04-postgres-docker.md 中uv add --dev pgcli把开发依赖与运行依赖分开的意义。由于uv sync --locked使用uv.lock精确锁定每个传递依赖的版本镜像中的环境与本地开发环境完全一致消除了「在我机器上能跑」的问题。构建镜像docker build在pipeline目录下执行构建命令cd pipeline docker build -t taxi_ingest:v001 .命令解析-t taxi_ingest:v001指定镜像名称为taxi_ingest标签为v001。标签相当于版本号后续可以构建v002、v003而互不干扰如果省略标签Docker 默认使用latest。.构建上下文为当前目录Docker 会把该目录下未被.dockerignore排除的文件发送给守护进程供COPY使用。因此命令必须在包含Dockerfile、pyproject.toml、uv.lock、.python-version、ingest_data.py的目录中执行。构建过程会依次执行 Dockerfile 中的指令拉取python:3.13.11-slim基础镜像 → 复制 uv 二进制 → 复制依赖清单并执行uv sync --locked→ 复制脚本 → 生成可运行的镜像。首次构建较慢后续改动业务代码时得益于「先复制依赖文件」的分层设计依赖层会被缓存复用构建会快很多。运行容器化摄取docker run 全参数实战镜像构建完成后用下面的命令启动容器执行数据摄取docker run -it \ --networkpg-network \ taxi_ingest:v001 \ --pg-userroot \ --pg-passroot \ --pg-hostpgdatabase \ --pg-port5432 \ --pg-dbny_taxi \ --target-tableyellow_taxi_trips这条命令需要拆成「Docker 参数」与「脚本参数」两部分理解Docker 侧参数镜像名之前-it以交互模式-i保持标准输入打开、-t分配伪终端运行便于实时观察 tqdm 的进度条输出。--networkpg-network将容器接入名为pg-network的 Docker 虚拟网络该参数必须放在镜像名之前。这是容器能够找到 Postgres 的关键——脱离该网络容器将无法解析pgdatabase这个主机名。脚本侧参数镜像名之后镜像的ENTRYPOINT是uv run python ingest_data.pydocker run命令中镜像名之后的所有内容会被作为参数追加给该入口命令。脚本通过click接收这些参数与 06-ingestion-script.md 中本地运行方式完全一致。各参数含义与默认值如下默认值取自 pipeline/ingest_data.py参数默认值说明--pg-userrootPostgreSQL 用户名--pg-passrootPostgreSQL 密码--pg-hostlocalhostPostgreSQL 主机容器化场景下必须改为 Postgres 容器名pgdatabase--pg-port5432PostgreSQL 端口int--pg-dbny_taxi目标数据库名--year2021摄取数据的年份int--month1摄取数据的月份int--target-tableyellow_taxi_data写入的目标表名--chunksize100000读取 CSV 的分块行数int容器化场景与本地运行的本质区别本机直接运行时--pg-host用localhost即可因为脚本与数据库同处宿主机网络但 Postgres 运行在独立容器中宿主机视角的localhost在容器内部指向的是容器自身因此必须把--pg-host指向 Postgres 容器在网络内的名字pgdatabase——这正是--network与容器--name pgdatabase见 04-postgres-docker.md配合发挥作用的机制Docker 内置 DNS 会把容器名解析为对应的容器 IP。脚本内部究竟做了什么从 pipeline/ingest_data.py 的源码可以看到容器化摄取脚本的实际执行流程构造数据 URL脚本根据--year/--month拼接纽约出租车数据的下载地址例如yellow_tripdata_2021-01.csv.gz。建立数据库连接通过 SQLAlchemy 构造postgresqlpsycopg://user:passhost:port/db连接串。分块读取 CSVpd.read_csv(..., iteratorTrue, chunksizechunksize)以 10 万行为一个块迭代读取避免超大文件一次性载入内存导致 OOM同时通过dtype映射如VendorID、PULocationID用Int64金额字段用float64与parse_datestpep_pickup_datetime、tpep_dropoff_datetime解析为时间类型保证列类型正确。分批写入首个分块先执行to_sql(..., if_existsreplace)创建目标表后续分块追加写入if_existsappend。这意味着即使表已存在脚本也会自动重建replace目标表无需手动 drop。这个分块写入设计正是 06-ingestion-script.md 中「分块处理大文件以避免内存不足」策略的容器化落地。注意事项三条必须记住的坑教程文档明确给出了以下三点关键注意事项它们是容器化摄取能否成功的决定性细节--network必须放在镜像名之前docker run的参数解析中镜像名之后的内容全部会被当作容器入口命令ENTRYPOINT的参数因此--networkpg-network这类 Docker 层参数一旦写到镜像名之后就会被错误地传给ingest_data.py导致启动失败。--pg-host必须指向 Postgres 容器名由于 Postgres 运行在独立容器中--pg-hostpgdatabase而非localhost。容器名是容器在 Docker 网络中的主机名是容器间互相发现的唯一凭据。表会自动重建脚本首个分块使用if_existsreplace写入因此即使目标表已存在脚本运行时会自动删除并重建该表。你可以在 pgAdmin 中先手动删除表可选但并非必需。进阶配合 Docker Compose 使用教程后续的 09-docker-compose.md 展示了用 pipeline/docker-compose.yaml 一键启动 Postgres pgAdmin 的方式。如果数据库是用 Docker Compose 启动的Compose 会自动创建一个虚拟网络默认命名规则为项目目录名_default例如pipeline_default此时运行摄取容器只需把--network换成该网络名docker network ls docker run -it --rm \ --networkpipeline_default \ taxi_ingest:v001 \ --pg-userroot \ --pg-passroot \ --pg-hostpgdatabase \ --pg-port5432 \ --pg-dbny_taxi \ --target-tableyellow_taxi_trips注意这里的--rm表示容器运行结束后自动清理适合一次性摄取任务而教程主流程使用-it便于观察交互输出。仓库附带的辅助脚本仓库在 pipeline/docker-helper-scripts/docker-ingest.sh 中提供了一个可直接执行的 bash 封装脚本内部封装了「2021 年 1 月数据摄取」的完整命令docker run -it --rm \ --networkpg-network \ taxi_ingest:v001 \ --year2021 \ --month1 \ --pg-userroot \ --pg-passroot \ --pg-hostpgdatabase \ --pg-port5432 \ --pg-dbny_taxi \ --chunksize100000 \ --target-tableyellow_taxi_trips该脚本与本文主流程完全同构只是显式指定了--year2021 --month1说明摄取脚本天然支持按年月参数化批量导入历史数据——想摄取其他月份只需改动这两个参数。验证结果与清理数据写入完成后可以用以下任一方式验证通过 07-pgadmin.md 中配置好的 pgAdminhttp://localhost:8085在ny_taxi库中查看yellow_taxi_trips表的行数与数据或在宿主机用 pgcli 连接开发依赖中已包含uv run pgcli -h localhost -p 5432 -u root -d ny_taxi连接后执行SELECT COUNT(*) FROM yellow_taxi_trips;即可确认分块导入的总行数。任务完成后可参考 11-cleanup.md 清理容器与网络资源docker network rm pg-network # 移除虚拟网络小结本篇文章完整走通了「构建镜像 → 运行容器 → 数据入库」的容器化链路核心要点可总结为uv lock 文件实现了依赖环境的可复现打包COPY顺序的设计让构建缓存最大化Docker 虚拟网络--network 容器--name是容器间互相发现的基础设施摄取容器必须与 Postgres 同处一个网络并以容器名作为--pg-hostdocker run参数分区镜像名前是 Docker 运行时参数镜像名后是脚本参数两者不能混淆分块 replace/append的写入策略让亿级数据也能稳定入库且表会自动重建无需预处理。至此摄取脚本已完全脱离宿主机环境约束。下一步教程将在 09-docker-compose.md 中用一份 YAML 声明式地编排 Postgres、pgAdmin 与摄取任务彻底告别繁琐的docker run命令链。【免费下载链接】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),仅供参考
返回列表