PGO-Deploy
部署方案:不仅仅是运行个二进制
在上一篇关于 CI/CD 的文章中,我提到了利用 SSH + PM2的watch功能 实现服务的热更新。 本文记录一下 pgo 项目的整体部署架构。对于一个微服务架构的 Go 后端,仅仅把二进制跑起来是不够的,我们还需要解决依赖管理、服务编排、日志收集和全方位监控。
Docker Compose
整体架构主要分为两部分:核心业务容器 与 基础设施容器群。
- 核心业务容器:一个基于 Rocky9 构建的容器
- 所有 Go 微服务(User, School, Task…)都统一运行在这一个容器内。
- 为什么不每个服务一个容器?
- 在开发测试阶段,频繁的构建镜像 + 重启容器非常耗时。
- 既然是 Go,直接把编译好的二进制丢进容器运行最快。
- 挂载了宿主机的部署目录,配合CD工具SSH上传文件,PM2监控文件变更,实现秒级部署。
另外,我连开发也是用同样的镜像,如果不是大规模编排集群,开发和生成使用一致的环境也并没有造成什么问题。你的开发环境也并不缺那点磁盘空间来安装/运行这个大一点的镜像。让自己开发/调试/运维更加舒服吧。
- 基础设施容器群:围绕核心业务的独立组件容器。
- 数据层: MySQL, Redis, RabbitMQ
- 工具层: Swagger UI
- 监控层: Prometheus, Grafana, Loki …
PM2 进程管理
为什么 Go 项目要用 PM2? PM2 是 Node.js 生态著名的进程管理器,但它对 Go 同样好用。 相比于 Systemd 或 Supervisord,PM2 在这个场景下有几个杀手级特性:
简单的 Watch 模式: 注意:在 Docker 卷挂载的情况下,在宿主机更新文件,容器内的PM2常规watch功能无法监测到文件变化 于是,我配置了 polling 策略。当 CI 工具通过 SSH 覆盖了新的二进制文件,PM2 监测到文件变化,会自动重启服务。
1 2 3 4 5 6 7 8 9
// watch: false, // 关闭全局监视,这会监视整个目录的文件 watch: ["/backend/userService"], // 只监视这一个可执行文件 // 关键配置:使用轮询,否则容器内inotify无法检查到宿主机文件变化 watch_options: { usePolling: true, // 或使用 legacyWatch: true interval: 5000 // 轮询间隔(毫秒) }, // 变更后延迟2秒再重启,等待上传/更新完成,否则文件开始写时会触发多次重启 watch_delay: 2000,
更方便的操作命令: list/start/stop/restart/delete/logs等命令很容易记忆,尤其是list提供的表格展示很清晰。
Prometheus 集成: 安装
pm2-prom-module后,它能直接吐出 metrics 给 Prometheus 抓取,零代码侵入即可获得基础监控指标。
启动检查:BootCheck
多服务部署经常遇到一个问题: 数据库等依赖还没完全 ready,业务服务就启动了,导致 panic 退出。 虽然 Docker Compose 有 depends_on,但那只检测容器是否启动,不代表端口已监听。 尤其是数据库容器启动后,还有一个初始化的过程,这段时间数据库是无法访问的。
我引入了一个名为 bootCheck 的自研微服务,作为所有业务服务的启动守门员:
- 它最先启动。
- 循环检查 MySQL, Redis, RabbitMQ 的端口连通性。
- 执行必要的数据库 Migration(建表/改表)。
- 只有当一切就绪,才通过 PM2 唤醒其他的业务服务。
可以看出,他的职责是业务代码的依赖检查,有别于如docker的depends_on的环境依赖,他只关心业务数据库是否存在并结构一致,并不关心数据库是如何部署并提供服务的。
监控平台
为了实现可观测性,我基于 Prometheus + Grafana 搭建了一套三层的监控体系:
- 宿主机层面 (Host): 关注服务器基础设施健康度。
- 组件:
node-exporter - 作用: 监控 CPU 使用率、内存消耗、磁盘 I/O、网络流量等物理指标,第一时间发现服务器负载过高或资源耗尽的问题。
- 组件:
- 容器层面 (Docker): 关注各个容器及其资源限制。
- 组件:
cAdvisor(Google Container Advisor) - 作用: 监控 MySQL、Redis 以及业务容器的 CPU/内存真实使用量、网络吞吐等。防止单个容器资源泄露拖垮宿主机。
- 组件:
- 服务进程层面 (Service): 关注 Go 业务进程的运行时状态。
- 组件:
pm2-prom-module - 作用: 结合 PM2 的进程管理,直接吐出 Go 进程的重启次数、内存占用、HTTP 响应延时等指标。无需在 Go 代码中埋点即可获得基础的应用级监控。
- 组件:
- 日志系统 (Logging): 轻量级的日志聚合与缺陷排查。
- 组件:
Promtail+Loki - 作用:
Promtail负责收集 Docker 容器的标准输出 (stdout/stderr) 并发送给Loki。通过 Label 机制,无需部署笨重的 ELK,就能在 Grafana 里快速筛选查询所有微服务的日志流。
- 组件:
现在,无论是基础设施炸了、容器挂了、还是代码逻辑错了,Grafana 都能给我看的一清二楚。
搭建监控过程遇到的一些问题:
细节1: Grafana的使用,一开始是直接在界面上添加数据源和仪表盘, 即使仪表盘官网也提供了很多模板,但是部署新环境依然需要手动操作。 所以找到了Provisioning功能来初始化Grafana。
细节2: pm2-prom-module是对比了很多个监控模块或方案之后的最终选型。 大部分其他模块支持的都是代码级别的监控上报,需要在程序代码嵌入指标的统计和上报。 而我更多是需要把pm2 list的内容展示到Grafana上。
细节3: 整理了容器的启动顺序,结合BootCheck完整构建了整套服务的启动顺序:- 依赖链条1:mysql/redis/rabbitmq > Rocky9 > BootCheck > pm2 > 服务进程 - 依赖链条2:node-exporter/cadvisor收集 > prometheus抓取 > grafana展示 - 依赖链条3:loki等待接收 > promtail主动推送 > grafana展示 特别说明,2和3的方向不一样,源于监控指标周期性很强,一般定义成如5s等时间间隔收集一次数据。而日志是突发的、不可预测的,所以采用主动推送的方式