从‘它怎么又挂了?’到‘服务稳如狗’:我是如何用Docker给老旧Node.js项目续命的
从‘它怎么又挂了?’到‘服务稳如狗’:我是如何用Docker给老旧Node.js项目续命的
三年前接手这个项目时,每次半夜收到报警短信,我的第一反应都是:"怎么又是你?"——一个运行在CentOS 6上的Node.js 8应用,像极了体弱多病的老人,稍微有点风吹草动就会崩溃。直到把整个项目塞进Docker容器,才终于结束了这场持续两年的"运维马拉松"。
1. 老项目的典型症状诊断
那个春天的早晨,当我第17次重启服务时,突然意识到必须找到根治方案。这个电商促销系统有三个致命伤:
- 依赖地狱:package.json里躺着37个"^1.0.0"这样宽泛的版本声明
- 环境绑定:必须运行在glibc 2.12环境下,且依赖三个特定版本的系统库
- 配置散落:数据库连接字符串硬编码在五个不同位置的JS文件里
最讽刺的是,连当初的部署文档都找不到了。唯一能确定的是,如果重装服务器,这个系统就会变成永远无法启动的"僵尸"。
2. Docker化改造四步疗法
2.1 制作时间胶囊般的镜像
关键是要创建一个时间胶囊,把2016年的运行环境完整封存。我的Dockerfile开头是这样的:
FROM centos:6.10
RUN yum install -y glibc-2.12-1.209.el6 \
libstdc++-4.4.7-23.el6 \
&& rm -rf /var/cache/yum
这个基础镜像就像考古学家用的刷子,小心翼翼地保留了原始土壤层。特别注意要禁用yum的缓存清理,否则后续调试时连查看当初装了什么都做不到。
2.2 依赖版本的精确冷冻
在node_modules的废墟里,我像法医一样提取出真实的依赖版本:
$ grep -r "version" node_modules/**/package.json | sort -u > frozen-dependencies.txt
得到的版本清单转换成package-lock.json后,性能立即提升了40%。原来之前每次npm install都在上演俄罗斯轮盘赌。
2.3 配置的集中化管理
用环境变量替代硬编码时,我发明了"配置考古学"的方法:
// 原始配置
const dbHost = '192.168.1.100';
// 改造后
const dbHost = process.env.DB_HOST || require('./legacy-config.json').dbHost;
这种渐进式改造既保证了容器化部署,又保留了回退路径。最终所有配置都收敛到了.env文件:
# .env.production
DB_HOST=postgres-prod
DB_PORT=5432
REDIS_URL=redis://cache:6379/0
2.4 数据卷的时空隧道
处理上传目录时,用了这个巧妙的挂载方式:
VOLUME ["/var/lib/uploads"]
配合docker-compose.yml实现宿主机持久化:
services:
app:
volumes:
- uploads:/var/lib/uploads
volumes:
uploads:
driver_opts:
type: none
device: /data/legacy-uploads
o: bind
3. 部署架构的进化之路
3.1 从单点部署到集群化
最初的改造只是简单替换,后来演进成完整编排方案:
# 原始启动方式
$ node server.js
# 容器化启动
$ docker run -d --name legacy-app \
-p 3000:3000 \
--env-file .env \
-v $(pwd)/uploads:/var/lib/uploads \
legacy-app:1.0
3.2 监控体系的升级
在容器里,传统的pm2监控变成了更现代的方案:
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:3000/health || exit 1
配合Prometheus的node-exporter,终于能看清这个老系统的真实状态。
4. 那些年踩过的坑
4.1 时区问题的幽灵
容器内的时间总是UTC,导致订单时间错乱8小时。解决方案是在Dockerfile里:
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime
4.2 文件权限的迷宫
宿主机和容器内的用户ID不一致导致上传失败。最终用这个魔法数字解决:
RUN useradd -u 1001 appuser
USER appuser
4.3 内存泄漏的捉迷藏
老旧的Node.js 8没有很好的内存限制机制,需要在启动时:
CMD ["node", "--max-old-space-size=1024", "server.js"]
5. 现代化改造的意外收获
最惊喜的是发现这个"老古董"居然能无缝接入CI/CD流水线。原本需要手动操作的部署过程,现在变成了:
$ docker build -t legacy-app .
$ docker tag legacy-app:latest registry.example.com/legacy-app:${CI_COMMIT_SHA}
$ docker push registry.example.com/legacy-app:${CI_COMMIT_SHA}
甚至还能做蓝绿部署。那些曾经让我夜不能寐的部署恐惧,现在变成了可预测的自动化过程。
更多推荐
所有评论(0)