nacos平滑下线思路集成deployment
·
✅ 目标:实现一个 /graceful-shutdown 接口
调用该接口后:
- 服务从 Nacos 主动注销
- 停止接收新请求(可选)
- 等待正在处理的请求完成
- 最终关闭应用(或让 K8s 处理终止)
✅ 一、接口设计思路
| 步骤 | 操作 |
|---|---|
| 1 | 提供 POST /graceful-shutdown 接口 |
| 2 | 接口内:从 Nacos 注销本实例 |
| 3 | 触发 Spring 优雅停机(或主动退出) |
| 4 | 配合 preStop 或直接调用,实现平滑下线 |
✅ 二、Spring Boot 实现代码(Java)
1. 创建控制器:GracefulShutdownController.java
package com.example.demo.controller;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PreDestroy;
import java.net.InetAddress;
import java.net.UnknownHostException;
@RestController
public class GracefulShutdownController {
@Value("${spring.application.name}")
private String serviceName;
@Value("${server.port}")
private int port;
@Value("${spring.cloud.nacos.discovery.server-addr}")
private String nacosServerAddr;
private volatile boolean isShuttingDown = false;
@PostMapping("/graceful-shutdown")
public String gracefulShutdown() {
if (isShuttingDown) {
return "Already shutting down";
}
isShuttingDown = true;
new Thread(() -> {
try {
// 1. 获取本机 IP
String ip = InetAddress.getLocalHost().getHostAddress();
System.out.println("🛑 开始平滑下线: " + serviceName + " at " + ip + ":" + port);
// 2. 从 Nacos 注销
NamingService namingService = NamingFactory.createNamingService(nacosServerAddr);
namingService.deregisterInstance(serviceName, ip, port);
System.out.println("🗑️ 已从 Nacos 注销服务");
// 3. 等待正在处理的请求完成(可配置)
Thread.sleep(3000); // 等待 3s 传播 + 请求完成
// 4. 触发应用关闭
System.exit(0); // 或调用 /actuator/shutdown
} catch (Exception e) {
System.err.println("❌ 平滑下线失败: " + e.getMessage());
e.printStackTrace();
System.exit(1);
}
}).start();
return "Graceful shutdown triggered";
}
}
💡 你也可以不
System.exit(0),而是返回后由 K8s 的preStop或滚动更新来终止。
2. 启用 Actuator(可选,用于对比)
如果你更信任 /actuator/shutdown,也可以调用它:
management:
endpoint:
shutdown:
enabled: true
endpoints:
web:
exposure:
include: health,info,shutdown
然后接口里调用:
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("🔥 应用正在关闭...");
}));
System.exit(0);
✅ 三、Deployment 配置(关键:暴露接口)
deployment.yaml(片段)
apiVersion: apps/v1
kind: Deployment
meta
name: demo-service
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: demo-service
template:
meta
labels:
app: demo-service
spec:
containers:
- name: demo-service
image: your-registry/demo-service:1.0.0
ports:
- containerPort: 8080
# 健康检查
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
# 关键:PreStop 调用平滑下线接口
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- >
# 调用平滑下线接口
curl -X POST http://localhost:8080/graceful-shutdown ||
sleep 5
resources:
limits:
memory: "1Gi"
cpu: "500m"
terminationGracePeriodSeconds: 30 # 给足时间注销
---
apiVersion: v1
kind: Service
meta
name: demo-service
spec:
selector:
app: demo-service
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP
✅ 四、如何使用这个接口?
场景 1:K8s 滚动更新时自动触发(推荐)
✅ 通过 preStop 自动调用,无需人工干预。
场景 2:手动触发下线(运维操作)
# 获取 Pod IP
POD_IP=$(kubectl get pod demo-service-7d6f8c7b7b-abcde -o jsonpath='{.status.podIP}')
# 调用平滑下线接口
curl -X POST http://$POD_IP:8080/graceful-shutdown
⚠️ 注意:调用后 Pod 会退出,K8s 会创建新实例(如果是 Deployment)
场景 3:蓝绿/灰度发布时控制流量
你可以先调用 /graceful-shutdown 下线旧版本,再上线新版本。
✅ 五、安全加固(重要!)
⚠️ 不要让这个接口被随意调用!
方案 1:加 Token 验证
@PostMapping("/graceful-shutdown")
public String gracefulShutdown(@RequestHeader(value = "X-Auth-Token") String token) {
if (!"your-secret-token".equals(token)) {
return "Unauthorized";
}
// ... 执行下线
}
方案 2:只允许 localhost 调用
@PostMapping("/graceful-shutdown")
public String gracefulShutdown(HttpServletRequest request) {
String remoteAddr = request.getRemoteAddr();
if (!remoteAddr.equals("127.0.0.1") && !remoteAddr.equals("0:0:0:0:0:0:0:1")) {
return "Forbidden";
}
// ... 执行下线
}
方案 3:NetworkPolicy 限制访问
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
meta
name: allow-only-local
spec:
podSelector:
matchLabels:
app: demo-service
policyTypes:
- Ingress
ingress:
- from:
- podSecurityContext: {} # 只允许本 Pod 自己调用(实际需更细粒度)
更合理的是:只允许 K8s 节点或特定服务调用。
✅ 六、替代方案:不暴露接口,全靠 preStop
如果你不想暴露接口,可以直接在 preStop 中:
- 用
curl调用 Nacos REST API 注销 - 或执行
jcmd发送 shutdown 命令
preStop:
exec:
command:
- /bin/sh
- -c
- >
IP=$(hostname -i) &&
curl -X DELETE "http://nacos.middleware.svc.cluster.local:8848/nacos/v1/ns/instance?serviceName=demo-service&ip=$IP&port=8080" &&
sleep 5
这样就不需要写 Java 接口了。
✅ 七、总结:两种方式对比
| 方式 | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|
写 /graceful-shutdown 接口 |
灵活,可加逻辑、日志、监控 | 需开发,暴露接口有风险 | 需要复杂逻辑(如通知、审计) |
preStop 直接调 REST API |
简单,无需改代码 | 不够灵活,难调试 | 大多数场景推荐 |
✅ 最终建议
- ✅ 大多数情况:使用
preStop + Nacos REST API,无需写接口 - ✅ 需要复杂逻辑:写
/graceful-shutdown接口,加 Token 验证 - ✅ 必须配合:
terminationGracePeriodSeconds和readinessProbe
如果你需要我生成:
- ✅ 完整的 GitHub 项目(含接口 + YAML)
- ✅ 使用 JWT 鉴权的高级版本
- ✅ 配合 Istio 实现流量零丢失的方案
欢迎继续提问!我可以为你定制企业级平滑发布方案。
更多推荐

所有评论(0)