从‘它怎么又挂了’到‘服务真稳’:我是如何用Docker给老旧Node.js项目续命

接手一个祖传Node.js项目就像继承一座年久失修的老宅——表面光鲜,实则暗藏无数隐患。上周五深夜,当我第7次被报警短信吵醒,面对生产环境又一次莫名其妙的Error: Cannot find module 'lodash'时,终于下定决心用Docker给这个2016年的"老古董"做个全面手术。

1. 解剖老项目的"疑难杂症"

那个凌晨三点,我对着报错日志做了次彻底尸检。这个使用Express 4.x和Node 8构建的电商后台系统,存在三类典型"老年病":

依赖地狱(Dependency Hell)

  • package-lock.json从未被提交到Git仓库
  • 开发环境用npm 5,生产环境用npm 6
  • 三个子模块分别依赖不同版本的bluebird

环境玄学(Environment Voodoo)

# 在Jenkins上总是报错的神秘命令
NODE_ENV=production && npm install --production && node server.js

配置漂移(Configuration Drift)

  • 数据库连接字符串散落在5个配置文件中
  • 开发环境用.env,测试环境用config.json
  • 生产环境配置居然写在服务器/etc/environment

提示:使用npm ls --depth=10可以可视化依赖树,快速发现版本冲突

2. 构建Docker化的手术方案

2.1 制作项目"标本"

首先用Docker的node:8-alpine镜像制作基础环境标本——这个2016年的项目只能用Node 8运行:

# 基于官方Node 8镜像
FROM node:8-alpine as builder

# 锁定npm版本
RUN npm install -g npm@5.10.0

# 解决时区问题
RUN apk add --no-cache tzdata && \
    cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

2.2 解决依赖冲突

通过多阶段构建隔离不同环境的依赖:

# 开发阶段安装所有依赖
FROM builder as dev
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]

# 生产阶段只装必要依赖
FROM builder as production
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
CMD ["node", "server.js"]

关键技巧:

  • 使用alpine版本减少镜像体积(从900MB降到120MB)
  • 通过npm ci替代npm install保证依赖一致性
  • .dockerignore排除node_modules

3. 配置管理的现代化改造

3.1 环境变量统一管理

将散落的配置集中到config.js中:

module.exports = {
  db: {
    host: process.env.DB_HOST || 'localhost',
    port: process.env.DB_PORT || 27017
  },
  // 其他配置...
}

3.2 启动脚本标准化

创建可靠的启动脚本start.sh

#!/bin/sh
set -e

# 等待数据库就绪
while ! nc -z $DB_HOST $DB_PORT; do
  sleep 1
done

exec node server.js

在Dockerfile中赋予执行权限:

COPY start.sh .
RUN chmod +x start.sh
CMD ["./start.sh"]

4. 从CI/CD到监控的全链路加固

4.1 构建优化检查清单

检查项 传统方式 Docker方案
依赖一致性 ❌ 各环境不同 ✅ 锁定在镜像中
环境变量管理 ❌ 容易泄露 ✅ 通过-e传递
快速回滚 ❌ 需重新部署 ✅ 切换镜像标签
资源隔离 ❌ 端口冲突 ✅ 独立网络命名空间

4.2 监控方案升级

docker-compose.yml中添加健康检查:

services:
  app:
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3

配合Prometheus实现指标收集:

# 暴露监控端口
EXPOSE 3000 9090

# 安装监控代理
RUN npm install prom-client --save

5. 那些年我们踩过的坑

字体缺失问题
Alpine镜像默认没有中文字体,导致PDF生成乱码:

RUN apk add --no-cache wqy-zenhei

时区陷阱
容器内默认UTC时间,需显式设置:

ENV TZ=Asia/Shanghai

内存泄漏诊断
旧版Node的--inspect参数在容器中需要特殊处理:

docker run -p 9229:9229 --cap-add=SYS_PTRACE app

现在这个曾经每天崩溃三次的系统已经稳定运行了217天。上周当我看到新来的实习生用docker-compose up三秒搭起完整开发环境时,突然理解了什么叫做"前人栽树后人乘凉"。

更多推荐