1. 6.4 生产环境部署建议
6.1–6.3 解决「看见系统」;本节从 拓扑、容器、探针、依赖与退出 角度归纳生产约定。二进制名、配置路径以你的 cmd/ 与仓库为准,文中 server 仅为示例。
1.1. 6.4.1 部署架构
一个典型的生产部署:
┌─────────────┐
│ Etcd × 3 │ 服务发现
└──────┬──────┘
│
┌──────┴──────┐
│ NATS × 3 │ 跨进程通信
└──────┬──────┘
│
┌──────────────┼──────────────┐
│ │ │
┌──────┴─────┐ ┌──────┴─────┐ ┌──────┴─────┐
│ 进程 1 │ │ 进程 2 │ │ 进程 3 │
│ Gate+IM │ │ Gate+IM │ │ Gate+Chat │
│ Match │ │ Chat │ │ Match │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
┌──────┴─────┐ ┌──────┴─────┐ ┌──────┴─────┐
│ Prometheus │ │ Grafana │ │ Jaeger │
└─────────────┘ └─────────────┘ └─────────────┘
关键依赖:
- 单进程部署:无外部依赖(Etcd/NATS 都不需要)
- 多进程部署:Etcd(服务发现)+ NATS(跨进程通信)
- 可选:Prometheus + Grafana(强烈建议)
1.2. 6.4.2 容器化
1.2.1. Dockerfile
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server ./cmd/server
FROM alpine:3.19
RUN apk add --no-cache ca-certificates tzdata
COPY --from=builder /app/server /server
COPY --from=builder /app/configs /configs
EXPOSE 8000 9090
ENTRYPOINT ["/server"]
注意事项:
- CGO_ENABLED=0:静态编译,避免依赖 glibc
- 多阶段构建:最终镜像只有二进制 + 配置文件,约 20MB
- 时区数据:
tzdata确保日志时间正确
1.2.2. K8s 部署
apiVersion: apps/v1
kind: Deployment
metadata:
name: zhenyi-server
spec:
replicas: 3
template:
spec:
containers:
- name: server
image: zhenyi:latest
ports:
- containerPort: 8000 # 客户端连接
- containerPort: 9090 # Prometheus 指标
env:
# 需要稳定分片序号时请用 StatefulSet,并从 pod 名或 Downward API 解析 index;
# 普通 Deployment 没有 statefulset 的 pod-index 标签,勿照搬此字段。
resources:
requests:
cpu: "1"
memory: "512Mi"
limits:
cpu: "4"
memory: "2Gi"
livenessProbe:
httpGet:
path: /healthz
port: 9090
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /readyz
port: 9090
initialDelaySeconds: 5
periodSeconds: 5
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"] # 等待 NATS/Etcd 感知下线
1.2.3. StatefulSet vs Deployment
如果需要固定的进程索引(比如 PROC_INDEX=0 用于区分分片),用 StatefulSet:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: zhenyi-server
spec:
serviceName: zhenyi
replicas: 3
# Pod 名: zhenyi-server-0, zhenyi-server-1, zhenyi-server-2
StatefulSet 保证 Pod 名称和顺序稳定,适合需要进程 ID 作为分片标识的场景。
如果不需要固定索引,普通 Deployment 就够了。
1.3. 6.4.3 Etcd 部署
1.3.1. 推荐配置
Etcd 至少 3 节点集群(容忍 1 节点故障):
# 关键参数
--quota-backend-bytes=8589934592 # 8GB 存储上限
--max-request-bytes=1048576 # 1MB 单次请求上限
--snapshot-count=10000 # 每 1 万次提交做快照
--auto-compaction-retention=1h # 1 小时后自动压缩
1.3.2. 资源需求
| 规模 | CPU | 内存 | 磁盘 |
|---|---|---|---|
| 小型(< 10 节点服务) | 1 核 | 512MB | SSD 10GB |
| 中型(10~50 节点) | 2 核 | 1GB | SSD 50GB |
| 大型(> 50 节点) | 4 核 | 2GB | SSD 100GB |
Etcd 对磁盘 I/O 敏感,必须用 SSD,不要用网络存储。
1.4. 6.4.4 NATS 部署
1.4.1. 单节点(开发/小型)
nats-server -js # 启用 JetStream(可选持久化)
1.4.2. 集群(生产)
3 节点集群:
# 节点 1
nats-server --name nats-1 \
--cluster nats://0.0.0.0:6222 \
--routes nats://nats-2:6222 --routes nats://nats-3:6222 \
--port 4222 --monitor 8222
# 节点 2、3 类似
1.4.3. 资源需求
| 规模 | CPU | 内存 |
|---|---|---|
| 小型 | 0.5 核 | 128MB |
| 中型(< 10 万 QPS) | 1 核 | 256MB |
| 大型(> 10 万 QPS) | 2~4 核 | 512MB~1GB |
1.5. 6.4.5 资源配置建议
1.5.1. zhenyi 进程
| 场景 | CPU | 内存 | goroutine |
|---|---|---|---|
| 开发 | 1 核 | 256MB | < 100 |
| 生产(单 Gate + 少量 Actor) | 2 核 | 1GB | < 500 |
| 生产(Gate + 多业务 Actor) | 4 核 | 2GB | < 2000 |
| 生产(高并发 + 大量在线) | 8 核 | 4GB | < 10000 |
goroutine 数量可以通过 go_goroutines 指标监控。如果超过预期,可能有 goroutine 泄漏。
1.5.2. GOMAXPROCS
默认等于 CPU 核心数。如果容器限制了 CPU(比如 limit 4 核),K8s 会自动设置。手动设置:
runtime.GOMAXPROCS(runtime.NumCPU())
1.5.3. GOGC
默认 100(堆达到当前 2 倍时触发 GC)。对低延迟场景,可以调高:
debug.SetGCPercent(200) // 堆达到 3 倍才 GC,减少频率但增加内存
通过 go_gc_gogc_percent 指标可以监控当前值。
1.6. 6.4.6 优雅退出
zhenyi 的优雅退出流程:
收到 SIGTERM
↓
1. metricsSrv.SetDraining() → /healthz 与 /readyz 均可返回 503(draining/不健康语义见 zmetrics/server.go)
↓
2. 停止接受新连接(网络层关闭监听)
↓
3. 等待 Mailbox 排空(Actor 处理完队列中的消息)
↓
4. Etcd 注销(Unregister + Revoke lease)
↓
5. NATS 连接关闭
↓
6. 进程退出
K8s 的 terminationGracePeriodSeconds 应该设置得比排空时间长:
spec:
terminationGracePeriodSeconds: 30 # 给 30 秒排空时间
containers:
- name: server
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"] # 额外等 5 秒,确保 NATS/Etcd 感知
1.6.1. 热更新配合
利用第三章讲的热更新能力,很多参数调整不需要重启:
// 运行时调整,不需要重启
actor.UpdateWorkerPoolSize(64)
actor.UpdateRateLimit(10000, 20)
但 Actor 代码变更仍然需要重启(Go 是编译型语言)。
1.7. 6.4.7 配置管理
1.7.1. 外部化配置
不要硬编码配置,用环境变量或配置文件:
// 推荐方式
etcdURL := os.Getenv("ETCD_URL") // "http://etcd:2379"
natsURL := os.Getenv("NATS_URL") // "nats://nats:4222"
metricsAddr := os.Getenv("METRICS_ADDR") // ":9090"
1.7.2. 敏感信息
密码、密钥不要放在环境变量里(K8s 会明文记录到 Pod spec 中)。用 Secret:
apiVersion: v1
kind: Secret
metadata:
name: zhenyi-secret
type: Opaque
stringData:
db-password: xxx
tls-cert: |
-----BEGIN CERTIFICATE-----
...
1.8. 6.4.8 监控告警规则
1.8.1. 必须告警的指标
groups:
- name: zhenyi-critical
rules:
# Actor 频繁重启
- alert: ActorRestartTooFrequent
expr: rate(zhenyi_actor_restarts_total[5m]) > 0.1
for: 2m
labels:
severity: critical
# 消息丢失
- alert: MessagesDropped
expr: rate(zhenyi_actor_msg_dropped_total[5m]) > 0
for: 1m
labels:
severity: critical
# NATS 发布失败
- alert: NatsPublishError
expr: rate(zhenyi_nats_publish_errors_total[5m]) > 0
for: 1m
labels:
severity: critical
# 对象池重复释放(有 bug)
- alert: MsgPoolDoubleRelease
expr: increase(zhenyi_msgpool_double_release_total[5m]) > 0
for: 0m
labels:
severity: warning
# RPC 超时率过高
- alert: RPCTimeoutRateHigh
expr: |
rate(zhenyi_rpc_timeout_total[5m])
/ rate(zhenyi_rpc_sent_total[5m]) > 0.1
for: 5m
labels:
severity: warning
1.8.2. 建议告警的指标
- name: zhenyi-warning
rules:
# 处理延迟飙升
- alert: HandlerLatencyHigh
expr: |
histogram_quantile(0.99,
rate(zhenyi_handler_latency_ms_bucket[5m])) > 100
for: 5m
labels:
severity: warning
# 队列堆积
- alert: MailboxBacklog
expr: zhenyi_actor_queue_depth > 1000
for: 5m
labels:
severity: warning
# goroutine 泄漏
- alert: GoroutineLeak
expr: go_goroutines > 10000
for: 10m
labels:
severity: warning
1.9. 6.4.9 日志采集
1.9.1. 推荐方案:Loki + Promtail
轻量、原生支持 Kubernetes:
# Promtail 配置
scrape_configs:
- job_name: zhenyi
kubernetes_sd_configs:
- role: pod
pipeline_stages:
- json:
expressions:
level: level
msg: msg
traceIdHi: traceIdHi
actorId: actorId
- labels:
level:
traceIdHi:
1.9.2. 日志保留策略
| 环境 | 保留时间 | 原因 |
|---|---|---|
| 开发 | 3 天 | 够排查就行 |
| 生产 | 7~30 天 | 满足运维排查和审计要求 |
zhenyi 的日志默认输出到 stdout,容器化后由日志采集组件收集。
1.10. 6.4.10 安全加固
1.10.1. 网络层
- Gate 端口只对客户端开放,不暴露内网服务
- Etcd 和 NATS 端口只在内网开放
- K8s NetworkPolicy 限制 Pod 间通信
1.10.2. TLS
zhenyi 支持标准 TLS 和国密 GM-TLS:
// 标准 TLS
gate.SetStandardTLS("server.crt", "server.key")
// GM-TLS(信创环境)
gate.SetTLSConfig(&baseziface.TLSConfig{
Mode: baseziface.TLSModeGMTLS,
CertFile: "sm2_cert.pem",
KeyFile: "sm2_key.pem",
})
1.10.3. 限流
单连接级限流防止恶意客户端:
限流阈值来自 ActorConfig.Rate / Burst 或运行时 UpdateRateLimit(见 3.4);与 Gate/Channel 绑定方式以你的启动代码为准。
1.11. 6.4.11 本节要点
- 模式:单机可无 Etcd/NATS;多进程需 发现 + TopicBus(常用 Etcd + NATS)。
- 镜像:多阶段 +
CGO_ENABLED=0;暴露 业务端口 +zmetrics端口(如 9090)。 - 探针:
/healthz/readyz;SetDraining后两者均可 503,配合terminationGracePeriodSeconds。 - 配置:环境变量/Secret;敏感数据优先 K8s Secret 而非明文 env。
- 可观测:Prometheus 抓取
/metrics;日志 stdout;追踪见 6.3。 - 安全:
SetStandardTLS/SetTLSConfig(类型在zhenyi-base/ziface);内网隔离 Etcd/NATS。
第七章(大纲)进入实战业务组装;本章素材已覆盖运维侧常见决策点。