我干了八年运维,混迹过IDC机房,也打理过云上几十台机器,去年帮朋友把一个日活五万的博客系统从裸机迁到Docker,本来预估两个晚上搞定,结果整整折腾了一周,今天不聊高大上的编排,就说说用Docker部署网站时,那些文档里不写、论坛里没人细说、但新手几乎必踩的坑。
第一个坑:镜像源比代码更早让你崩溃。
别以为docker pull nginx就一定快,我服务器在阿里云上海区,默认源拉一个Ubuntu镜像能卡十分钟,更坑的是,某次半夜升级,拉取到一半连接重置,容器起不来了,解决方案就一句话:国内服务器务必配置镜像加速器,阿里云、腾讯云控制台都有专属加速地址,填进/etc/docker/daemon.json再重启服务,速度立刻从乌龟变火箭,记得systemctl daemon-reload,这步漏了,配置不生效,你还会怀疑是自己防火墙的问题。
别急着上K8s!我用Docker部署网站踩过的五个坑,每个都让你白熬夜
第二个坑:数据卷权限,让你体验“文件只读”的绝望。
我当年部署一个WordPress,把/var/www/html挂载出来,结果容器内PHP写入文件时提示“Permission denied”,排查了半小时,突然醒悟:宿主机目录权限是root:root,但容器内PHP-FPM跑的是www-data用户(UID 33),看似简单,但若你用的是官方镜像,它不会帮你改权限。最稳妥的做法:挂载前先在宿主机上chown -R 33:33 /path/to/html,或者用docker run -u指定用户,否则你会在“容器里改权限不生效”和“宿主机改完容器里还是老样子”之间来回碰壁。
第三个坑:数据库“假死”,其实是容器被OOM杀掉了。
我的一个数据报表站点,每天凌晨定时任务一跑,MySQL容器就死。docker stats看不出大问题,但docker logs mysql_container里全是InnoDB: Assertion failure,后来查监控发现是宿主机内存不足,内核OOM Killer优先杀了内存占用最高的MySQL进程,别迷信--memory限制,那只能限制写入cgroup的值,真正应该做的是给容器添加--restart=always,并在宿主机层面调大swap,数据库数据目录务必挂载到宿主机,否则容器重建=数据全灭,这个教训是我用三个月用户数据换来的。
第四个坑:SSL证书更新,别用“进程内重启”糊弄。
我见过很多人用docker exec nginx nginx -s reload来重新加载新证书,对于纯Nginx容器没问题,但如果你用Certbot的Webroot模式,在容器里申请证书,然后又挂载了宿主机目录,就很容易遇到证书路径权限或软链失效,我的方案是:宿主机上用Certbot的standalone模式独立申请证书(占用80端口),然后通过挂载目录给Nginx容器,更新后只需docker restart nginx,不用进容器执行任何命令,这避免了容器内cron和宿主机cron的时间竞争。证书续期不是部署网站的最后一步,而是定期维护的第一步。
第五个坑:域名解析和反向代理的“隐藏延迟”。
有次客户反馈网站间歇性打不开,我查了Docker网络、Nginx配置、宿主机防火墙,都没问题,最后用dig命令发现,域名解析的TTL设成了600秒,而Docker容器每次重建后内网IP变了,但Nginx的proxy_pass还指向老IP,虽然我用了--link,但容器重建后链接会失效。解决方案:别在Nginx配置里写死的容器IP,直接写proxy_pass http://web:8080;,然后让所有业务容器加入到同一个user-defined bridge网络(docker network create myweb),这样Docker内置DNS就能自动解析容器名,这个改动,让我再也没为“改代码后连不上DB”这类问题熬过夜。
关于长期维护,我最后说三句掏心窝的话:
第一,每周至少执行一次docker system prune -f,清除悬空镜像和停止的容器,不然半年后你的磁盘会悄无声息满掉,网站白屏你都不知道为什么,第二,把docker-compose.yml当作代码一样纳入版本控制,每次变更记录注释,不然三个月后你看着一堆容器,根本不知道哪个对应哪个服务,第三,别在容器内装ssh,需要进容器排查就用docker exec -it,保持镜像最小化,这样升级基础镜像时冲突最少。
Docker部署网站,本质是把复杂的环境问题“封装”起来,但封装不等于消失,那些坑,都藏在挂载目录的权限、网络模式的细节、以及你对“容器生命周期”的理解里,少走弯路的最好方法,不是背命令,而是理解镜像与容器是“图纸与房子”的关系,数据卷才是房子里的家具,而网络则是房子的门牌号,搞懂这三样,你的网站就能安稳睡觉,你也能。



发表评论