从‘它怎么又挂了’到‘服务真稳’:我是如何用Docker给老旧PHP项目续命的
从‘它怎么又挂了’到‘服务真稳’:我是如何用Docker给老旧PHP项目续命的
每次凌晨三点被报警短信吵醒,看着屏幕上熟悉的"500 Internal Server Error",我都恨不得把那个十年前写的PHP项目一把火烧了。这个祖传代码库就像个定时炸弹,明明上周还能跑,今天突然就崩了——因为某个系统库悄悄升级了,或者某个神秘的PHP扩展突然罢工。直到我把整个环境塞进Docker容器,这场持续五年的噩梦才真正结束。
1. 解剖这只"老古董":环境依赖全图谱
接手遗留项目就像考古,第一铲子必须挖清楚它到底依赖哪些"文物级"组件。我创建了一个depcheck.sh脚本,用以下命令全面扫描环境:
# 抓取PHP版本及加载的扩展
php -v
php -m
# 检查系统库依赖
ldd $(which php) | grep -E 'libssl|libcurl|libxml'
# 分析项目使用的特殊函数(需要pecl安装的扩展)
grep -r "mysql_pconnect\|mcrypt_\|xdebug_" src/
扫描结果让我倒吸凉气:这个项目需要:
- PHP 5.4.16(2013年发布)
- 已废弃的
mysql扩展(不是mysqli!) - 神秘的
ionCube Loader解密模块 - 系统自带的OpenSSL 1.0.1(存在严重漏洞)
更可怕的是,项目根目录下还有个readme.txt写着:"需先执行sudo apt-get install libfreetype6-dev libjpeg62-turbo-dev",却没人知道哪些功能依赖这些库。
2. 打造时间胶囊:精准构建Docker镜像
锁定环境版本就像制作标本,必须用正确的"防腐剂"。我的Dockerfile从选择基础镜像开始就充满玄机:
# 使用官方存档仓库的旧版镜像
FROM php:5.4.16-apache
# 安装指定版本系统库(精确到小版本号)
RUN apt-get update && apt-get install -y \
libssl1.0.0=1.0.1t-1+deb8u12 \
libcurl3=7.38.0-4+deb8u18 \
--no-install-recommends
# 编译安装老版本扩展(注意配置参数差异)
RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ \
--with-jpeg-dir=/usr/include/ \
&& docker-php-ext-install gd mysql
遇到最棘手的ionCube加载器,解决方案是手动下载历史版本:
# 在Dockerfile中添加
ADD https://downloads.ioncube.com/loader_downloads/ioncube_loaders_lin_x86-64_5.4.tar.gz /tmp/
RUN tar zxvf /tmp/ioncube_loaders_lin_x86-64_5.4.tar.gz -C /usr/local/lib/ \
&& echo "zend_extension=/usr/local/lib/ioncube/ioncube_loader_lin_5.4.so" > /usr/local/etc/php/conf.d/00-ioncube.ini
注意:老版本PHP的Docker镜像通常不再更新安全补丁,建议仅在隔离网络中使用
3. 驯服文件系统:权限与日志的陷阱
容器化后最意外的坑来自文件系统。我们的项目有这些"坏习惯":
/var/www/html/uploads需要www-data用户写权限/var/log/app目录会被PHP代码直接写入cache/目录下的临时文件需要777权限
解决方案是在Dockerfile中加入智能初始化脚本:
COPY entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
entrypoint.sh内容示例:
#!/bin/bash
# 动态处理权限问题
chown -R www-data:www-data /var/www/html/uploads
mkdir -p /var/log/app && chown www-data /var/log/app
# 保持原日志文件不被覆盖
if [ ! -f /var/log/app/error.log ]; then
touch /var/log/app/error.log
chown www-data /var/log/app/error.log
fi
exec "$@"
4. 从单机到集群:现代化部署方案
虽然容器化了老系统,但我们可以用现代编排工具管理它。这是我的docker-compose.yml配置精髓:
version: '3.7'
services:
legacy_app:
build: .
ports:
- "8080:80"
volumes:
- ./src:/var/www/html
- app_logs:/var/log/app
environment:
- DB_HOST=mysql_legacy
- TZ=Asia/Shanghai
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health.php"]
interval: 30s
mysql_legacy:
image: mysql:5.5
volumes:
- mysql_data:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=this_is_legacy_code_anyway
volumes:
app_logs:
mysql_data:
关键优化点:
- 使用健康检查自动恢复(虽然应用老,但编排工具新)
- 分离数据卷避免容器销毁丢失重要日志
- 保持原有时区配置(老代码里可能有硬编码的时间计算)
5. 那些年我们踩过的坑:经验备忘录
三年间我记录了这些典型问题及解决方案:
| 现象 | 根本原因 | 解决方案 |
|---|---|---|
| 页面乱码 | 容器缺少中文语言包 | Dockerfile添加RUN apt-get install -y locales && locale-gen zh_CN.UTF-8 |
| 上传文件失败 | php.ini中upload_max_filesize配置过小 |
创建自定义php.ini覆盖容器默认配置 |
| 定时任务不执行 | crontab服务未启动 | 使用supervisord管理多进程 |
| 性能突然下降 | 容器内存限制过小 | 调整docker run的-m 512m参数 |
最讽刺的是,有次部署失败仅仅因为Docker Hub下架了某个旧镜像。后来我学会了自建私有镜像仓库,把确认可用的镜像全部备份到本地。
现在这个"老古董"不仅稳定运行,还能通过GitLab CI实现自动化部署。每次看到监控面板上平稳的直线,都会想起那些凌晨三点debug的夜晚。或许这就是工程师的浪漫——用新技术延续旧系统的生命,就像给老爷车装上电动引擎。
更多推荐
所有评论(0)