上个月公司把内部一套业务系统从本地机房迁到腾讯云,整套流程走下来踩了不少坑,最狼狈的一次是安全组端口没放行,排查了半天才发现问题根本不在应用。这篇文章把从服务器选型、Docker 容器化部署、域名解析、SSL 证书到 CDN 加速和监控告警的完整链路复盘一遍,配上了实际用到的命令和配置,给同样要做部署的同学一份可参考的实操记录。
这套系统是公司自研的内部业务平台,打包后是标准的容器化交付物。选服务器时我主要看三个指标:CPU、内存和系统盘类型。应用运行时本身不吃 CPU,但流程引擎和数据表服务在并发审批高峰时内存占用会明显上升,实测 50 人左右的日常使用规模,4 核 8G 是比较稳妥的起点,如果公司有几百人的使用量,直接上 8 核 16G 可以省掉后面扩容的折腾。
系统盘我选了云硬盘的高性能型而不是基础型。原因很实际:容器镜像加上日志,磁盘 IO 在早晚高峰差得不是一点半点。操作系统选了 CentOS 7.9 的替代方案 OpenCloudOS,跟腾讯云的容器生态贴合度更好,yum 源和内核参数调优都是现成的。
另外提醒一句,买服务器的时候就把地域定了,后面换地域等于重新来一遍。我按使用团队所在的网络出口选了就近地域,内网穿透和后续的云数据库互通都省事了。
这是我这次部署最大的坑。容器起来之后浏览器怎么也访问不了,我以为是大镜像没跑完,把日志翻了个底朝天,应用明明正常监听在 8080 端口。折腾了大半天,同事提醒了一句:安全组放行了没?我才反应过来新建的安全组只放了 22 和 443,8080 根本没开。
后来我学乖了,部署前先画一张端口规划表:
安全组规则的原则就一条:公网只暴露 80 和 443,其余端口全部收进内网。应用端口让 Nginx 在本机做反向代理转发,这样即使应用层有漏洞,攻击面也被限制在两个标准端口上。改完规则之后一切豁然开朗,那半天的教训换一条经验:端口不通,先查安全组,再看应用日志。
服务器到手先装 Docker 运行环境。OpenCloudOS 的软件源里有现成的包,安装完把服务设为开机自启,再确认一下版本和运行状态。这一步没有难度,但要记得配置国内镜像加速地址,否则拉取基础镜像的速度会让人等得心焦。
我们的应用交付物是一个 tar 包形式的离线镜像,这样不依赖外部镜像仓库,也方便在其他环境复用。导入用一条命令:
# 导入离线镜像包并确认
docker load -i business-app-v3.2.tar
docker images | grep business
# 输出确认镜像存在后,给镜像打一个规范 tag
docker tag internal/webapp:3.2 registry.local/business/webapp:3.2导入之后我习惯立刻打一个带版本号的 tag,后续回滚和升级都靠这个版本号说话。生产环境的镜像管理宁可啰嗦一点,也不要留一个叫 latest 的悬念。
容器启动是整个部署的核心动作。我把数据库连接、文件存储目录、时区这些容易变的参数全部拆成环境变量或者挂载卷,这样换环境只需要改一份 .env 文件,镜像本身一行不动:
docker run -d \
--name biz-app \
--restart=always \
-p 8080:8080 \
-v /data/app/uploads:/app/uploads \
-v /data/app/logs:/app/logs \
-e TZ=Asia/Shanghai \
-e DB_HOST=10.0.2.15 \
-e DB_PORT=3306 \
-e DB_NAME=biz_prod \
registry.local/business/webapp:3.2有两个参数值得单独说。--restart=always 保证服务器重启或容器异常退出后自动拉起,这一项在没有上 Kubernetes 的单机部署里就是你的"高可用";日志和上传目录必须挂出来,否则容器一删,业务附件全没了,这是不可接受的。
容器起来之后用 docker ps 确认状态是 Up,再用 curl http://127.0.0.1:8080/healthz 验证健康检查接口,本机通了再去配外层的代理,顺序不要反。
应用跑通之后,下一步是给它一个正式的域名。我在域名注册的控制台加了一条 A 记录,把 app.company.example 指到服务器的公网 IP,TTL 设成了 600 秒。这里有个小技巧:刚上线阶段 TTL 不要设太长,万一要换 IP 或者接 CDN,短 TTL 能让切换在十分钟内生效,等一切稳定之后再调大。
解析生效可以用 nslookup 或者在线的 dig 工具确认。别急着往下走,先确认解析真的生效了,否则后面配证书的时候报一堆"域名验证失败",你分不清是解析问题还是证书问题。
公网入口我用了服务器上自装的 Nginx 做反向代理。它承担两件事:把 443 的 HTTPS 流量转给本机 8080 的容器,以及做 WebSocket 的升级转发。系统里的在线协作编辑器和消息通知都依赖长连接,漏配 WebSocket 升级头的话,页面能打开但编辑器会卡在加载状态,这是第二个让我排查了一阵的坑。核心配置如下:
server {
listen 443 ssl;
server_name app.company.example;
ssl_certificate /etc/nginx/ssl/app.company.example.pem;
ssl_certificate_key /etc/nginx/ssl/app.company.example.key;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket 支持:实时协作与消息推送依赖长连接
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
}
}配置里 X-Forwarded-Proto 这个头不能省,应用要靠它判断原始请求是不是 HTTPS,否则系统后台的登录回调地址会拼错,出现"登录后又被踢回 HTTP"的诡异现象。
证书直接在腾讯云申请了免费的 DV 证书,验证方式选 DNS 自动验证,几分钟就签发下来。下载 Nginx 格式的证书文件,把 pem 和 key 放到 /etc/nginx/ssl/ 目录,也就是上一段配置里引用的路径。
免费证书有效期一年,但我的习惯是不依赖人工记忆,用 certbot 的续期机制做了托管,续期动作里自动重载 Nginx。证书相关的定时任务配置片段:
# 每天凌晨检查证书有效期,剩余不足 30 天自动续期并平滑重载 Nginx
0 3 * * * certbot renew --quiet --deploy-hook "nginx -s reload"
# 手动验证一次续期流程是否通畅
certbot renew --dry-run--dry-run 一定要跑一次,模拟续期能提前暴露 DNS 验证失败、目录权限不对这类问题。证书过期的事故我见过别人的:全公司业务系统突然浏览器一片红色警告,IT 部门的电话一个接一个。一条 crontab 就能规避的风险,没必要冒这个险。
系统稳定运行两周后,有外地分支的同事反馈打开首页附件预览偏慢。排查发现慢的主要是静态资源和图片,于是我把静态资源接入了腾讯云 CDN:源站设置为刚才那台服务器的公网地址,加速业务类型选静态网页加速,回源协议跟随。
接入 CDN 之后有一个非常关键的联动动作:安全组里给 CDN 的回源网段放行 80 端口,同时把域名解析加一条 CNAME 到 CDN 分配的加速域名。如果域名要继续走 HTTPS,CDN 侧也要配置证书,可以直接托管已有的证书文件,全链路 HTTPS 才不会断。
缓存策略上,我把 /static/ 和 /uploads/ 路径的缓存时间设成了 7 天,接口路径全部设置不缓存。这里有一个教训:上线第二天有用户反馈头像换了不更新,就是因为把带用户内容的路径也配了长缓存。静态归静态、动态归动态,这条边界划清楚,CDN 才是加速器而不是麻烦来源。接入后实测静态资源首字节时间从 400ms 以上降到 100ms 以内,分支同事的体验改善非常明显。
上线不等于完事,监控才是长期运转的保障。我用了云监控插件装在服务器上,CPU、内存、磁盘、网络四项基础指标自动上报,全部免费。自定义监控项里加了两个进程级指标:Docker 容器运行状态和 Nginx 进程存活,通过采集脚本定时上报。
告警通道配置了两条:企业微信机器人用于一般告警,短信用于严重告警。阈值设置上我吃过亏——最开始把 CPU 告警阈值设成 80%,结果每天早上登录高峰必报警,狼来了三次之后大家都不看了。后来调整为:CPU 持续 5 分钟超过 90% 才告警,磁盘使用率超过 80% 告警,容器退出立即告警。告警宁可少而准,也不要多而杂。
应用日志除了落到挂载目录,我还接了一份到腾讯云的日志服务,按天建索引。日志服务里配了两个关键字告警:出现 ERROR 连续超过 20 条、出现 OutOfMemory 立即触发。有了这个,之前"用户先发现系统挂了才知道"的被动局面彻底扭转。
每周一次的人工巡检保留着,主要看三件事:磁盘增长趋势(容器日志和业务附件是两个增长大户)、证书剩余有效期、安全组有没有被误改。自动化再完善,定期的人眼复核依然是最后一道保险。整套部署从选服务器到监控告警全部就位,大约花了三天,其中半天浪费在安全组上——如果这篇记录能让谁少走这一个弯路,它就算值了。
按顺序查三层:先 docker ps 确认容器状态是 Up 且没有反复重启;再 curl http://127.0.0.1:8080/healthz 确认本机端口通;最后查云服务器安全组是否放行了对应端口。我的实际经历是前两层都正常、卡在安全组没放行 8080,排查了半天。端口不通优先怀疑安全组,再怀疑应用本身,能少走一半弯路。
大概率是 WebSocket 升级头没配。反向代理默认不转发 Upgrade 和 Connection 头,长连接建立不起来,依赖实时推送的组件就会卡住。在 location 里补上 proxy_http_version 1.1;、proxy_set_header Upgrade $http_upgrade; 和 proxy_set_header Connection "upgrade"; 三行,再适当调大 proxy_read_timeout,问题即可解决。
看使用场景和数据敏感度。使用者在公司内网或有 VPN 条件、数据不允许出内网,就选纯内网部署,只配安全组和内网域名;有外地分支或移动办公需求,就需要公网域名加 HTTPS,建议配合 CDN 加速静态资源。两种方案的应用镜像完全一致,差异只在外层的域名、证书和代理配置上,规划时把这两层解耦,后续切换成本会很低。
核心是靠 2.1 节的版本 tag 机制。升级时先导入新版本镜像,停掉旧容器(记下原容器名和启动参数),用同样的参数启动新版本,healthz 通过后再观察十分钟;回滚就是用旧 tag 重复一遍启动动作。两个细节:启动参数全部收在 .env 和挂载卷里,升级时镜像换了但参数不动,出错概率小一半;新旧容器不要同时监听 8080,单机部署没有流量调度,起来之前先把旧的停干净。
整套流程走下来,真正难的从来不是某条命令,而是把安全组、容器、代理、解析、证书这几层的因果关系串成一条链。部署前多画一张端口规划表、多跑一次 dry-run,上线后就少一次半夜爬起来看日志。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。