把采集服务装进 Docker:从裸机脚本到可交付镜像的工程实践

采集服务在裸机上跑了半年,没出过大事——直到换服务器那天:Python 版本对不上、cron 任务忘配、字体缺了导致报表乱码,迁移花了一整天。迁完我就把服务容器化了:换环境从「一天」变成「一条命令」。这篇记录容器化过程中的实践和坑。

第一步:写 Dockerfile

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app/ ./app/

RUN useradd -r -u 1001 collector \
    && chown -R collector:collector /app
USER collector

CMD ["python", "-m", "app.collect"]

三个细节:slim 基础镜像(够用、体积小)、依赖先于代码拷贝(代码改动不会让依赖层缓存失效)、非 root 运行(安全惯例,数据目录权限一并处理)。

requirements.txt 记得锁版本(requests==2.32.3 这种),否则某天上游更新,镜像构建直接炸——裸机上踩的「依赖漂移」,容器化后并不会自动消失。

第二步:状态外置(最重要的一条)

容器的原则是「可丢弃、可重建」——所以 SQLite 文件、日志、任何有状态的东西,都不能留在容器里:

# docker-compose.yml
services:
  collector:
    build: .
    env_file: .env
    volumes:
      - ./data:/app/data        # serp.db、logs 全在这里
    restart: unless-stopped

./data 挂到宿主机上,docker compose down 重建容器,数据纹丝不动。反过来说:如果你发现有文件必须留在容器里才能工作,那是个 bug,迟早会在重建时丢数据。

第三步:调度方式的选择

采集任务大多是「定时批量 + 偶尔长跑」,调度有三种姿势:

方式 做法 适合
容器内 cron 镜像里装 cron/守护 不推荐:日志和退出码都难管
宿主机 cron + 一次性容器 docker compose run --rm collector 批量任务首选
常驻 worker 服务自己带调度循环 需要常驻并发/队列时

我用的第二种,宿主机 crontab 就一行:

0 6 * * * cd /srv/collector && docker compose run --rm collector >> run.log 2>&1

每次跑一个干净容器,跑完即退,退出码就是成败——这比在容器里养一个 cron 守护进程干净得多。

第四步:密钥从环境进,不进镜像

# .env(不进 git)
SERPBASE_API_KEY=xxx
    env_file: .env

两个禁忌:别用 ENV/ARG 把 Key 写进 Dockerfile(docker history 能看到镜像层里的值);别把 .env 打进镜像(.dockerignore 里加一行)。

第五步:日志与健康检查

日志:让程序输出到 stdout,宿主机统一收:

    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

不配上限的话,json-file 日志能把磁盘悄悄吃满——之前磁盘篇讲的坑,容器里换个形式再来一遍。

健康检查:别只测「进程活着」,要测「能力还在」——比如最近一次成功采集的时间:

    healthcheck:
      test: ["CMD", "python", "-c",
             "import app.health, sys; sys.exit(0 if app.health.fresh(240) else 1)"]
      interval: 5m
      retries: 3

踩坑记录

坑 1:数据留在容器里。 重建容器数据全没。volume 挂载是第一条纪律。

坑 2:时区不对。 容器默认 UTC:日志时间是 UTC、datetime.today() 是 UTC——本地跑没问题、容器里跑出来的「今天」差 8 小时。要么统一按 UTC 思考(存储层推荐),要么显式设 TZ 环境变量,别稀里糊涂。

坑 3:密钥进了镜像层。 ARG API_KEY + ENV API_KEY=$API_KEY 的写法,docker history 一览无余。密钥只走运行时环境。

坑 4:依赖没锁版本。 镜像构建某天突然失败,因为上游发了新版本。requirements.txt 固定到具体版本。

坑 5:root 跑容器。 数据文件权限全归 root,宿主机上想改都费劲。非 root + 目录 chown。

坑 6:迁移完忘了 crontab。 容器化解决了代码和环境,但「谁在几点触发」还是宿主机的事——迁移清单里加上 cron 这一项。

工程清单

  1. slim 镜像 + 依赖锁版本 + 非 root
  2. 状态全部外置(volume),容器可随时重建
  3. 批量任务用「宿主机 cron + run --rm」,别在容器里养 cron
  4. 密钥走运行时 env,.dockerignore 排除 .env
  5. 日志 stdout + 大小上限;healthcheck 测真实能力
  6. 时区显式声明(UTC 或 TZ),迁移清单含 cron

容器化的价值不在「时髦」,而在把「环境」变成「代码」:过去散落在服务器上的 Python 版本、依赖、字体、cron,现在都在 Dockerfile 和 compose 里,可审查、可重建、可迁移。对一个人的采集项目来说,这是性价比很高的一次工程化。

接口的鉴权方式(Key 走环境变量)在 SerpBase 官方文档 有说明,配合容器化时注意别让密钥进镜像。你们的采集服务跑在裸机还是容器里?迁移时踩过什么坑?评论区聊聊。

posted @ 2026-09-27 11:45  蜘蛛人  阅读(9)  评论(0)    收藏  举报