从“它怎么又挂了”到“服务稳如狗”:我是如何用Docker给老旧Python项目续命的

三年前接手这个“祖传”Django 1.11项目时,我天真地以为最大的挑战是理解那些写满魔法方法的代码。直到第一次在生产环境部署——pip install 报错、ImportError 连环出现、静态文件404,我才明白真正的噩梦是环境一致性

今天分享的,不是教科书式的Docker入门教程,而是一个真实战场上的生存指南。你会看到:

  • 如何用docker build --no-cache暴力破解gcc编译依赖的玄学报错
  • multi-stage build把2.3GB的镜像瘦身到200MB的实操细节
  • 老项目特有的*.pyc文件缓存坑及其一键清理方案

1. 解剖“祖传”项目的依赖地狱

打开项目的requirements.txt,你可能看到这样的内容:

Django==1.11.23
celery==3.1.26.post2
MySQL-python==1.2.5  # 是的,这个包在Python 3上根本装不上

1.1 依赖冻结与兼容性分析

运行以下命令生成当前环境的真实依赖树:

pip freeze > requirements_actual.txt
diff requirements.txt requirements_actual.txt

典型问题排查表

问题类型解决方案示例命令
缺失版本约束补充==~=版本限定符sed -i 's/MySQL-python/MySQL-python==1.2.5/' requirements.txt
不兼容Python 3寻找替代包mysqlclient替换MySQL-python
依赖冲突使用pip-compile生成锁定文件pip-compile --generate-hashes requirements.in

1.2 构建基础镜像的智慧

不要直接使用python:3.6这样的官方镜像。基于debian:buster-slim自定义镜像能减少70%的构建体积:

FROM debian:buster-slim as builder

RUN apt-get update && apt-get install -y \
    python3.6 \
    python3-pip \
    && rm -rf /var/lib/apt/lists/*

RUN ln -s /usr/bin/python3.6 /usr/bin/python

提示:老项目常需要libssl1.0等已淘汰的库,可通过apt-get install libssl1.0-dev显式安装

2. Dockerfile中的“外科手术”

2.1 处理C扩展编译问题

当遇到error: command 'gcc' failed时,在Dockerfile中加入:

RUN apt-get update && apt-get install -y \
    gcc \
    python3-dev \
    libmysqlclient-dev \
    && rm -rf /var/lib/apt/lists/*

2.2 静态文件收集的陷阱

老版本Django的collectstatic可能在容器中失效,需要手动指定:

docker run -e DJANGO_SETTINGS_MODULE=prod_settings \
    your_image python manage.py collectstatic --noinput

3. 部署后的“老年护理”

3.1 日志监控方案

docker-compose.yml中配置日志轮转:

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

3.2 健康检查策略

对老项目特别重要的存活探针:

HEALTHCHECK --interval=30s --timeout=3s \
  CMD curl -f http://localhost:8000/healthz/ || exit 1

4. 那些年我们踩过的坑

4.1 时区问题终极解决方案

在Dockerfile中永久固定时区:

ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

4.2 内存泄漏应急方案

限制容器内存并自动重启:

docker run -m 512m --memory-swap 1g --restart=on-failure:5 your_image

最后分享一个真实案例:某次更新后容器不断崩溃,最终发现是/tmp目录爆满。现在我会在所有Dockerfile中加入:

RUN chmod 1777 /tmp && find /tmp -type f -atime +1 -delete

更多推荐