面向已部署(见 DEPLOYMENT.md)后日常运维:监控日志、扩缩容、防护治理、压测、优雅停机、排障、容灾。
可观测栈:deploy/docker/observability/(Prometheus 9090 / Grafana 3000 / Loki 3100 / Promtail),host 网络模式。
cd deploy/docker/observability && docker compose up -d prometheus grafana loki promtail- 指标:Grafana → JVM·HTTP 概览面板;Prometheus
http://localhost:9090看 targets 全up。 - 日志:Grafana → Explore → Loki 数据源,查
{namespace="cloud-platform"}(k8s)或{job="cloud-platform"}(docker,Promtail 采宿主/tmp/*.log)。 - 链路:Zipkin
http://localhost:9411,按serviceName/traceId看调用链。
K8s 内可观测性(21-observability.yaml)经 Service DNS 抓取,并为 k6 开 Prometheus Remote Write;访问用 kubectl -n cloud-platform port-forward svc/grafana 3000:3000。
日志经 logback FileAppender 直接落宿主目录(/home/zw/cloud-logs/<name>.log,容器内 /tmp/logs),不依赖 stdout 管道(JVM stdout 块缓冲会丢日志)。镜像模式由 LOGGING_FILE_NAME 注入路径。
- 控制台:WEB_CONSOLE.md「状态」页
POST /api/scale(等价scale.sh <x> <N>)。 - kubectl:
kubectl -n cloud-platform scale deploy/<svc> --replicas=<N>。 - Headlamp:
Workloads → Deployments → Scale(控制台独立,不碰副本)。 - HPA:默认不部署(避免与手动 scale 冲突);需启用启动集群加
--with-governance(CPU 70% 自动伸缩,min2/max4)。
设计与实现原理见 ARCHITECTURE §4;本节仅运行时操作。
限流参数来自运行时可变 bean,可即时改写,无需重启网关、不重建镜像。
curl http://localhost:8080/actuator/ratelimit # 查当前
curl -X POST http://localhost:8080/actuator/ratelimit -H 'Content-Type: application/json' -d '{"enabled":false}' # 放开(压测/演练)
curl -X POST http://localhost:8080/actuator/ratelimit -H 'Content-Type: application/json' -d '{"enabled":true}' # 恢复
curl -X POST http://localhost:8080/actuator/ratelimit -H 'Content-Type: application/json' -d '{"replenishRate":200,"burstCapacity":400}' # 仅调阈值- 约束:
burstCapacity ≥ replenishRate(端点兜底:若传入小于 replenishRate 自动取等,不挂网关)。 - 端点与 Nacos 写同一 bean;正式限流参数以 Nacos 配置为准,端点临时改动会被后续 Nacos 推送覆盖。勿注入
GATEWAY_RATELIMIT_*环境变量(会覆盖 Nacos)。 - Nacos 放开:控制台把
cloud-gateway.yaml的gateway.ratelimit.enabled改false并发布,无需重启。
压测脚本跑 k6 前自动 POST /actuator/ratelimit {"enabled":false},trap EXIT 保证跑完(含异常)自动恢复 true;镜像/进程模式同 HTTP 端点,无需区分形态、无需重启网关。
user-service / order-service 路由挂路由级熔断(fallbackUri: forward:/fallback):被路由实例全部不可用时网关直接 fallback,避免雪崩。下游恢复注册(Nacos 约 30s)后自动半开→闭合。与限流串行(先限流后熔断)。
AuthGlobalFilter(HS256),默认 gateway.security.enabled=false 关闭以保持演示可用。
- 生产启用:
java -jar cloud-gateway.jar --gateway.security.enabled=true --gateway.security.secret=<强密钥>(生产应写入 Nacos);建议认证服务签发、网关只验签(RS256)。
依赖已引入、Dashboard 随可观测栈一键拉起(8858)。落地需:业务方法加 @SentinelResource + 各服务 yml 配 spring.cloud.sentinel.transport.dashboard=127.0.0.1:8858。
k6 以官方镜像 grafana/k6:0.52.0 容器化(无需宿主装 k6)。三种形态共用 test/loadtest/script/(唯一真源),启动逻辑共用 test/loadtest/loadtest-common.sh,仅部署动作不同:
| 形态 | 启动 | BASE_URL(寻址) |
|---|---|---|
| proc | 宿主 k6 直接读 | http://localhost:8080(同机 loopback,dev/demo) |
| docker | test/loadtest/docker/start-loadtest-docker.sh |
http://host.docker.internal:8080(独立加压机打宿主) |
| k8s | test/loadtest/k8s/start-loadtest-k8s.sh |
http://cloud-gateway:8080(集群内 Service DNS,生产姿态) |
前提:①业务服务已起(网关 :8080 可访问)②可观测栈已起(Prometheus 开 Remote Write Receiver)。
bash test/loadtest/docker/start-loadtest-docker.sh order # 下单全链路
bash test/loadtest/docker/start-loadtest-docker.sh user # 用户查询
bash test/loadtest/docker/start-loadtest-docker.sh mixed # 混合 80% 读 / 20% 写
VUS=200 DURATION=120s bash test/loadtest/docker/start-loadtest-docker.sh mixed # 梯度加压参数(constant-arrival-rate,RATE 直接决定吞吐):RATE(默认 200) / VUS(100) / MAXVUS(1000) / DURATION(120s) / BASE_URL 经 env 覆盖。
观察:Grafana → k6 Load Test 面板(VU/QPS/p99/错误率);k6 指标走 Remote Write 推送,空闲时面板空属正常。
部署 → 梯度压测取拐点 → 按实测容量留安全余量设限流 → 重压验证。
脚本 test/loadtest/run-loadtest.sh <service> <replicas...> [--scenario S] [--rates R...] 只测量、不扩容:<service> 为任意业务 Deployment 名(纯参数),读实际副本校验与声明一致后才压测。每档期望副本先 kubectl scale 设好。
kubectl -n cloud-platform scale deploy/user-service --replicas=3
bash test/loadtest/run-loadtest.sh user-service 3 # 单档
# 全量 1-4 副本
for svc in user-service order-service; do
for r in 1 2 3 4; do
kubectl -n cloud-platform scale deploy/$svc --replicas=$r
kubectl -n cloud-platform rollout status deploy/$svc
bash test/loadtest/run-loadtest.sh $svc $r
done
done结果落 test/loadtest/results/(INDEX.md 汇总 + 每档 *.log)。
判读:对副本数 r,failed%<0.01 且 p95 在 SLO 内(user<300ms / order<500ms)的最大 RATE 即可持续容量 C(r)。
由 C(r) 推导限流:
- 拐点 = C(r)。
- 扩展效率
eff = C(2r)/C(r):≈2扩副本线性有效、限流随副本等比放大;<<2瓶颈在下游,限流直接钉死 C(r)。 - SCG per-IP(Redis 令牌桶):
replenishRate ≈ C(r)/N_clients × 安全系数(N_clients 估算 10~50,下限保底 ≥20/s)。套用POST /actuator/ratelimit {"replenishRate":50,"burstCapacity":100}或 Nacosgateway.ratelimit.*热推。 - edge 全局(当前未设):
Σ C(r) ≤ edge_global ≤ gateway_tier_capacity,edge 守总量、SCG 守单体。 - 运行阈值取上限 70%~80%,套用后重跑压测
enabled:true确认 failed%<1%、p95 达标,闭环。
注意:压测务必 enabled:false,否则单 k6 IP 被 SCG 钉死 100/s,测不出真实容量。
| 层 | 配置 | 值 |
|---|---|---|
| 应用 | server.shutdown |
graceful |
| 应用 | spring.lifecycle.timeout-per-shutdown-phase |
30s |
stop-services.sh |
docker compose down -t |
35s(比应用多 5s 余量) |
stop-docker.sh |
关闭顺序 | 边缘 → 业务 → 可观测 → 中间件 |
stop-docker.sh |
DRAIN_WAIT |
3s(断边缘后等在途排空) |
- 数据卷默认保留:
down不带-v,命名卷全部保留;彻底重置用bash deploy/docker/reset-docker.sh(破坏性,CLEAN_IMAGES=1可删业务镜像)。 - 配置在源码
application.yml,改后须重建(build-images.sh或mvn package);Spring Boot 默认 30s 优雅窗口已生效。 - K8s 侧:
terminationGracePeriodSeconds ≥ 30s,滚动发布配preStop平滑下线;已加startupProbe(宽限 300s)避免冷启动误杀。
kubectl -n cloud-platform get pods -o wide
kubectl -n cloud-platform get events --sort-by=.lastTimestamp # 排障首选
kubectl -n cloud-platform logs deploy/<svc> --tail=100 -f
kubectl -n cloud-platform logs <pod> --previous # 看上次崩溃(CrashLoop 必看)
kubectl -n cloud-platform describe pod <pod> # 事件/镜像/拉取失败| 现象 | 根因 | 处理 |
|---|---|---|
全集群 ContainerCreating |
节点缺 pause 沙箱镜像 | ctr import 补 pause 后重建 Pod |
UnknownHostException: nacos / CrashLoopBackOff |
CoreDNS 缺失致集群 DNS 死 | ctr import coredns 后 kubectl -n kube-system rollout restart deploy/coredns |
ImagePullBackOff |
镜像未导入节点 / 版本号与清单不一致 | 按 DEPLOYMENT §2 导入;核对 VERSION 与清单硬编码版本 |
k3d 导入 content digest not found 却假成功 |
k3d 5.9.0 导出 tar 损坏(已知 bug) | 用 ctr import 回退通道(docker save → cp → ctr import) |
离线 ContainerCreating / DNS 死 |
k3d 节点是独立 containerd,不读宿主 daemon.json 加速 |
离线须预先把 pause/coredns/中间件镜像导入节点 |
镜像离线导入(k3d 节点 containerd 命名空间 k8s.io):
k3d image import cloud/user-service:1.0.0 -c cloud-platform
# 回退通道
NODE=k3d-cloud-platform-server-0
docker save cloud/user-service:1.0.0 -o /tmp/user.tar && docker cp /tmp/user.tar $NODE:/tmp/ && docker exec $NODE ctr -n k8s.io images import /tmp/user.tarkubectl -n cloud-platform rollout restart deploy/user-service
kubectl -n cloud-platform set image deploy/order-service order-service=cloud/order-service:1.1.0
kubectl -n cloud-platform rollout status deploy/cloud-gateway --timeout=180s
kubectl -n cloud-platform rollout undo deploy/user-servicebash deploy/kubernetes/stop-k3d.sh # 软停止:容器暂停,状态全留
k3d cluster start cloud-platform # 随时恢复,Pod 原样回来
DELETE=1 bash deploy/kubernetes/stop-k3d.sh # 硬删除:销毁集群、释放 30080(无 PVC,数据丢)
BUILD_MODE=skip bash deploy/kubernetes/start-k3d.sh # 复用本地镜像、跳过构建,快速恢复仅更新业务代码不要删集群:重新导入镜像 + rollout restart 即可。web-console 是独立容器,改它只需重启自身,与集群生命周期无关。
按「堵数据丢失(P0) → 去入口单点(P1) → 中间件高可用(P2) → 跨区多活(P3)」分层。
P0 已落地(数据不丢,已实测)
- 中间件持久化卷:MySQL(
mysql-data)、Redis(redis-data+appendonly yesAOF)、Kafka(kafka-data)、Zookeeper(zk-data) 均挂命名卷;容器重建不再丢数据。 - MySQL 定时备份:
deploy/docker/middleware/backup-mysql.sh复用cloud-mysql做mysqldump压缩落./backups/、保留 7 天,可接入 crontab。 - 代价:首次重建
redis/zk/kafka清空演示数据(缓存/历史消息),业务自动回源/重连,可接受。
P1/P2/P3(计划,未实施)
- P1 去入口单点:Gateway 改多实例 + Nginx
upstream挂多网关health_check;Nginx 单点靠 keepalived / 云 LB 做 VIP 漂移。 - P2 中间件高可用:Nacos 集群、MySQL 主从、Redis 哨兵、Kafka 多 broker(
min.insync.replicas≥2);生产建议云托管 / K8s Operator。 - P3 跨区多活:多可用区 K8s +
podAntiAffinity打散、数据跨区复制、定期恢复演练,定义 RTO/RPO。