从‘它怎么又挂了’到‘服务真稳’:我是如何用Docker给老旧Python脚本‘续命’的

张开发
2026/4/3 10:58:26 15 分钟阅读
从‘它怎么又挂了’到‘服务真稳’:我是如何用Docker给老旧Python脚本‘续命’的
从‘它怎么又挂了’到‘服务真稳’我是如何用Docker给老旧Python脚本‘续命’的那天凌晨三点手机又响了。第N次被这个2008年写的Python 2.7数据抓取脚本叫醒时我盯着报错日志里那句ImportError: No module named MySQLdb突然意识到——是时候给这个老古董做个全面改造了。这个脚本不仅依赖着早已停止维护的Python 2.7还需要特定版本的CentOS 6系统库更夸张的是它的配置文件居然散落在/etc、/opt和用户目录三个地方。每次部署新服务器都要经历一场持续两天的依赖炼狱。1. 解剖祖传代码的技术债打开这个名为legacy_crawler.py的脚本扑面而来的是十年前的技术栈用urllib2发HTTP请求、用BeautifulSoup 3解析HTML、用MySQLdb连接数据库。更棘手的是隐藏在代码深处的环境假设# 硬编码的配置文件路径 CONFIG_PATH /etc/crawler/config.ini if os.path.exists(/etc/crawler) else ~/crawler_config.ini # 依赖特定系统库的魔法数字 LIBC_VERSION subprocess.check_output([ldd, --version]).decode().split()[-1] if LIBC_VERSION ! 2.12: raise RuntimeError(Requires glibc 2.12)这些技术债带来的直接后果是环境依赖敏感只能在特定版本的CentOS上运行配置管理混乱配置文件路径优先级逻辑复杂部署效率低下新环境配置平均耗时8小时故障难以排查90%的问题源于环境不一致2. 容器化改造的核心策略2.1 构建安全的Python 2.7运行时虽然Python 2.7已停止维护但直接升级到Python 3存在语法兼容风险。我们选择用Docker隔离出一个安全的Python 2.7环境FROM centos:6 as base # 锁定特定版本的依赖库 RUN yum install -y \ python-2.7.5-89.el6_10 \ mysql-devel-5.1.73-8.el6_8 \ glibc-2.12-1.212.el6 # 安装旧版pip并固定库版本 RUN curl -O https://bootstrap.pypa.io/2.7/get-pip.py \ python get-pip.py \ pip install \ BeautifulSoup3.2.1 \ MySQL-python1.2.5 \ requests1.2.3关键技巧使用centos:6作为基础镜像而非最新版显式指定每个系统包的完整版本号通过pip freeze锁定所有Python依赖2.2 配置管理的三种进化形态原始脚本的配置管理是灾难级的我们分三步进行改造环境变量优先# 改造后的配置加载逻辑 config_path os.getenv(CRAWLER_CONFIG, /app/config.ini)配置文件标准化; config.ini [database] host ${DB_HOST:localhost} port ${DB_PORT:3306}Secret管理# 在Dockerfile中指定敏感数据挂载点 VOLUME /run/secrets3. 从单次运行到常驻服务原脚本采用手动触发模式我们通过容器化将其转变为可靠的服务3.1 进程管理方案对比方案优点缺点适用场景直接运行简单直接无崩溃恢复机制测试环境Supervisor轻量级需额外配置单一进程监控Systemd系统集成度高需要特权容器生产环境健康检查重启原生Docker支持重启有延迟无状态服务最终选择组合方案HEALTHCHECK --interval30s --timeout3s \ CMD python -c import urllib2; urllib2.urlopen(http://localhost:8080/health) ENTRYPOINT [/usr/bin/supervisord, -c, /etc/supervisord.conf]3.2 日志收集的现代化改造原始脚本用print输出日志我们升级为结构化日志import logging import json_log_formatter formatter json_log_formatter.JSONFormatter() handler logging.StreamHandler() handler.setFormatter(formatter) logger logging.getLogger(crawler) logger.addHandler(handler) # 使用示例 logger.info(Page fetched, extra{url: url, status: response.status})对应的Docker日志驱动配置docker run --log-driverjson-file --log-opt max-size10m --log-opt max-file34. 持续交付流水线搭建为这个老古董建立现代化CI/CD流程# 示例的GitLab CI配置 stages: - test - build - deploy containerize: stage: build script: - docker build -t legacy-crawler:$CI_COMMIT_SHA . - docker tag legacy-crawler:$CI_COMMIT_SHA registry.example.com/legacy-crawler:latest - docker push registry.example.com/legacy-crawler:latest only: - master关键改进点每次提交自动构建带Git SHA标签的镜像通过CI环境变量注入构建参数使用Kaniko实现无Docker环境的构建5. 监控与告警体系升级从跑挂了才知道到主动预警# Prometheus指标暴露 from prometheus_client import start_http_server, Counter REQUESTS_TOTAL Counter(crawler_requests_total, Total fetch requests) ERRORS_TOTAL Counter(crawler_errors_total, Total fetch errors) def fetch_page(url): try: REQUESTS_TOTAL.inc() # 抓取逻辑... except Exception as e: ERRORS_TOTAL.inc() raise对应的Grafana监控面板配置{ panels: [{ title: 抓取成功率, type: singlestat, targets: [{ expr: rate(crawler_requests_total[5m]) / rate(crawler_errors_total[5m]) }] }] }改造后的架构对比维度改造前改造后部署时间8小时/次3分钟/次环境依赖仅限CentOS 6任意支持Docker的环境配置管理散落在多个目录统一环境变量管理故障恢复手动介入自动重启告警资源占用独占整个服务器可与其他服务共享资源监控粒度仅错误日志实时性能指标结构化日志这个项目给我最深的体会是技术债不会因为被容器化而消失但Docker给了我们一个冻结技术债的机会。就像把古董放进恒温恒湿的展示柜虽然不能改变它的年代但能确保它在现代环境中继续安全运转。

更多文章