Post

PGO-Deploy

部署方案:不仅仅是运行个二进制

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 在这个场景下有几个杀手级特性:

  1. 简单的 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,
    
  2. 更方便的操作命令: list/start/stop/restart/delete/logs等命令很容易记忆,尤其是list提供的表格展示很清晰。

  3. Prometheus 集成: 安装 pm2-prom-module 后,它能直接吐出 metrics 给 Prometheus 抓取,零代码侵入即可获得基础监控指标。

启动检查:BootCheck

多服务部署经常遇到一个问题: 数据库等依赖还没完全 ready,业务服务就启动了,导致 panic 退出。 虽然 Docker Compose 有 depends_on,但那只检测容器是否启动,不代表端口已监听。 尤其是数据库容器启动后,还有一个初始化的过程,这段时间数据库是无法访问的。

我引入了一个名为 bootCheck 的自研微服务,作为所有业务服务的启动守门员

  1. 它最先启动。
  2. 循环检查 MySQL, Redis, RabbitMQ 的端口连通性。
  3. 执行必要的数据库 Migration(建表/改表)。
  4. 只有当一切就绪,才通过 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等时间间隔收集一次数据。而日志是突发的、不可预测的,所以采用主动推送的方式

This post is licensed under CC BY 4.0 by the author.