Actuator、健康状态与优雅停机:实例何时该接收流量
/actuator/health 把应用内的健康结果转成管理接口。平台读取 liveness 决定是否重启实例,读取 readiness 决定是否继续路由;业务请求仍经过自己的服务器和处理链。管理状态、平台动作与请求执行需要分别观察。
让管理接口只暴露需要的能力
Endpoint 的访问和暴露
一个端点需要具备实现、允许的操作访问级别和相应技术暴露,才能成为可调用接口。Boot 4.1.1 的 management.endpoint.<id>.access 可以设为 none、read-only 或 unrestricted;management.endpoints.access.max-permitted 再给全局访问设置上限。禁止访问的端点会从 Context 移除,exposure 则控制 Web/JMX 等技术出口。端点访问配置
management.endpoints.web.exposure.include=health,metrics
management.endpoint.health.show-details=never这不是用户鉴权规则。访问级别允许某类操作之后,仍要由 Spring Security、安全代理和网络策略约束调用者。Boot 默认只通过 Web 和 JMX 暴露 health;include=* 会显著扩大诊断面。env、configprops、heapdump、threaddump、loggers 可能泄漏秘密或允许状态修改,不应因为“内网”就全部开启。端点暴露与安全
应用声明任意 SecurityFilterChain 后,Boot 的默认 Web 安全配置会退让。只写一条匹配 Actuator 的安全链,仍需给其余业务路径配置另一条安全链。认证成功也不能解决敏感详情过度返回的问题,应同时使用最小端点集合和脱敏策略。
在回环环境启动实验
下载健康与停机实验,在 Linux Bash 解压为 actuator-health-shutdown 并进入目录。需要 Docker、curl 7.76+ 和有 Docker 权限的宿主普通用户。项目使用 Boot 4.1.1、Java 25、Maven 3.9.12。运行要求
PROJECT_DIR="$PWD"
CACHE_DIR="$HOME/.cache/boot-08-maven"
BUILD_IMAGE=maven:3.9.12-eclipse-temurin-25
RUN_IMAGE=eclipse-temurin:25.0.4_7-jdk
mkdir -p "$CACHE_DIR"
docker run --rm --user "$(id -u):$(id -g)" -e MAVEN_CONFIG=/tmp/maven \
-v "$PROJECT_DIR:/work" -v "$CACHE_DIR:/cache" -w /work \
"$BUILD_IMAGE" mvn -B -Dmaven.repo.local=/cache clean verify两个测试启动真实 HTTP 服务器,检查健康分组、状态变化与默认未启用的实验接口。构建需为零失败。Maven 容器使用宿主 UID/GID 和可写缓存,应用容器使用 10001;Docker daemon 权限单独管理。镜像和依赖不可达时使用受信企业仓库或完整离线缓存,不修改到任意公共代理源。
CONTAINER=boot-health-lab
docker run -d --name "$CONTAINER" --user 10001:10001 \
--read-only --tmpfs /tmp:rw,nosuid,nodev,size=128m \
--cap-drop ALL --security-opt no-new-privileges \
-p 127.0.0.1:18083:8080 \
-v "$PROJECT_DIR/target/actuator-health-shutdown-lab-1.0.0.jar:/app.jar:ro" \
"$RUN_IMAGE" java -jar /app.jar --lab.controls.enabled=true
docker logs "$CONTAINER"
curl -q --noproxy '*' --fail-with-body http://127.0.0.1:18083/actuator/health服务器启动后,响应包含 "status":"UP",并列出 liveness、readiness 健康组。lab.controls.enabled=true 只为本机演示注册两个无鉴权的状态切换接口;不传该参数时接口不存在。禁止把它连同实验镜像部署到公网或共享生产网络。
配置中还启用了 health probes 和附加路径,因此同一业务端口上有 /livez 和 /readyz。管理独立端口适合隔离运维流量,但只测试管理 Connector 无法观察业务 Connector 是否饱和;主端口附加路径可缩小这一盲区。Kubernetes 探针
健康聚合与可用性为什么会不同
HealthContributor、Status 和 HTTP
HealthIndicator 返回一个组件结果,多个结果可以组成复合 HealthContributor。StatusAggregator 根据状态顺序得到聚合状态,HttpCodeStatusMapper 再映射 HTTP 状态。默认 DOWN 和 OUT_OF_SERVICE 对应 503;自定义映射会影响默认映射,需要显式保留仍需要的状态。
实验的 search indicator 使用一个原子布尔值模拟可选搜索依赖是否可用,健康聚合本身由真正的 Boot Actuator 完成:
@Bean
AtomicBoolean searchAvailable() {
return new AtomicBoolean(true);
}
@Bean
HealthIndicator search(AtomicBoolean searchAvailable) {
return () -> searchAvailable.get()
? Health.up().build()
: Health.down().build();
}生产 HealthIndicator 应用有界、低副作用的检查获取状态;不能每次探针执行昂贵全表扫描或远程写入。慢 indicator 告警是观测,不会自动替业务客户端设置超时。健康端点扩展
curl -q --noproxy '*' --fail-with-body -X POST \
'http://127.0.0.1:18083/lab/dependency?up=false'
STATUS=$(curl -q --noproxy '*' -sS -o /tmp/boot-health-body.json \
-w '%{http_code}' http://127.0.0.1:18083/actuator/health)
TRANSPORT=$?
test "$TRANSPORT" -eq 0
test "$STATUS" = 503
cat /tmp/boot-health-body.json
curl -q --noproxy '*' --fail-with-body http://127.0.0.1:18083/livez
curl -q --noproxy '*' --fail-with-body http://127.0.0.1:18083/readyz总体 health 为 DOWN/503,但 livez 与 readyz 仍是 UP/200。默认 readiness 组没有自动包含每个外部依赖。可选搜索故障可以让依赖告警触发,同时允许其他业务继续处理;如果搜索是全部请求的必要条件,则应另行配置 readiness 组或应用状态策略。
把共享数据库加入 liveness,数据库故障时会同时触发所有实例重启,重启无法修复数据库,还会增加新连接和初始化压力。Readiness 是否加入共享依赖也需考虑所有实例同时被摘除后的入口行为;它不是一个应无脑复制的依赖全集。应用可用性
Readiness 是状态信号
ApplicationAvailability 保存 LivenessState 与 ReadinessState。Context 成功刷新后,Boot 发布 CORRECT;Runner 完成、Ready 事件之后发布 ACCEPTING_TRAFFIC。关闭时切到 REFUSING_TRAFFIC。
实验用真实事件改变 readiness:
AvailabilityChangeEvent.publish(events, this, ReadinessState.REFUSING_TRAFFIC);当第一个参数的类型为 ApplicationEventPublisher 时,三参数形式中的 this 是事件来源;不要误用要求 ApplicationContext 的双参数重载。AvailabilityChangeEvent API
curl -q --noproxy '*' --fail-with-body -X POST \
'http://127.0.0.1:18083/lab/readiness?accepting=false'
STATUS=$(curl -q --noproxy '*' -sS -o /tmp/boot-ready-body.json \
-w '%{http_code}' http://127.0.0.1:18083/readyz)
TRANSPORT=$?
test "$TRANSPORT" -eq 0
test "$STATUS" = 503
curl -q --noproxy '*' --fail-with-body http://127.0.0.1:18083/helloreadyz 返回 503,hello 仍然成功。这不是探针失效,而是本实验直接访问应用端口,没有平台替它摘流量:
应用状态 REFUSING_TRAFFIC
├─ /readyz 返回 503 → 平台探测 → 更新可路由实例集合
└─ 直接访问 /hello → 普通 HTTP 处理链仍可能成功若应用需要主动拒绝业务请求,应通过专门的入口过滤或业务策略实现,并与健康信号保持一致。多个组件分别发布 accepting/refusing 时可能相互覆盖,通常应有一个协调组件汇总本地条件,再统一改变状态。
探针和指标各自提供什么信息
startup probe 保护长初始化阶段,在它成功前平台不会按正常 liveness/readiness 逻辑过早处理;readiness 失败影响服务路由,liveness 失败按阈值触发容器重启。不同探针的路径、超时与 failureThreshold 需要结合启动和健康端点实测耗时配置。Kubernetes 探针机制
例如一次检查每 5 秒执行、连续 3 次失败触发动作,容忍时间还受首次延迟、调度和单次超时影响,不能精确承诺为固定 15 秒。负载高时如果管理接口和业务共享同一个饱和线程池,过短 liveness 超时可能把可恢复的排队放大成重启;应先观察请求队列、CPU 和下游池。
实验允许查询已有指标名称:
curl -q --noproxy '*' --fail-with-body http://127.0.0.1:18083/actuator/metrics
curl -q --noproxy '*' --fail-with-body \
http://127.0.0.1:18083/actuator/metrics/http.server.requests先请求 hello 再查询,可看到请求计时器的统计与可用 tags。指标的计数和耗时取决于本次请求,不写死成跨机器预期。metrics 端点用于诊断已注册 meter,Prometheus 抓取还需要对应 registry 依赖和单独暴露的端点。Micrometer 指标
URL 模板、方法、状态适合做标签,用户 ID、完整查询 URL、异常原文会产生高基数。健康状态当前为 UP 无法说明一段时间内的延迟和错误分布;需要同时观察请求速率、错误率、延迟和资源等待,再判断为何 readiness 抖动或请求超时。
端点返回 404 时依次检查依赖、access、exposure、management 基路径和端口,不要首先关闭鉴权。401/403 则说明请求可能已进入安全链;403 还可能涉及 CSRF,不能凭单一状态码认定密码错误。
SIGTERM 下完成在途请求
先恢复状态,再建立一个活动请求
curl -q --noproxy '*' --fail-with-body -X POST \
'http://127.0.0.1:18083/lab/dependency?up=true'
curl -q --noproxy '*' --fail-with-body -X POST \
'http://127.0.0.1:18083/lab/readiness?accepting=true'
curl -q --noproxy '*' --fail-with-body --max-time 20 \
http://127.0.0.1:18083/slow > /tmp/boot-slow-result.txt &
REQUEST_PID=$!
curl -q --noproxy '*' --fail-with-body http://127.0.0.1:18083/activeslow 在服务端等待 10 秒,active 在处理开始后为 1,finally 中减回 0。若 active 仍为 0,等待很短时间后再次请求;如果 slow 已结束,就重发慢请求。必须确认请求已进入服务端再停止,避免把未发出的请求当成在途请求。
docker stop -t 25 "$CONTAINER"
wait "$REQUEST_PID"
cat /tmp/boot-slow-result.txt
docker logs --tail 30 "$CONTAINER"
docker inspect "$CONTAINER" --format '{{.State.ExitCode}}'在开始后的 10 秒窗口内停止,已有请求应在预算内返回 slow-complete,curl 正常结束,日志出现 graceful shutdown 完成。应用配置的每停机阶段预算是 15 秒,Docker 给 25 秒;这些都是本地演示值。超时后服务器和平台仍会终止,长请求不得依赖无限等待。Boot 优雅停机
从入口到下游逐步关闭
服务器排空只覆盖它管理的 HTTP 请求。后台消费者、调度任务和独立线程池需要单独停止入口并等待;数据库连接池应在仍使用它的工作完成后关闭。SmartLifecycle 的 phase 决定生命周期启动与反向停止次序,单个 phase 的超时不是整个进程的总预算。
平台终止预算还包含 preStop、路由摘除传播和多个停止阶段。收到 refusing 到入口停止路由存在延迟,已有 keep-alive 连接也需按服务器实现处理。长轮询、SSE、WebSocket 和异步 Servlet 应实测终止及客户端重连,不能从一个短 HTTP 实验推出全部长连接行为。
如果 Docker 退出码为 137,说明进程可能受到 SIGKILL;结合 OOMKilled、停止超时和宿主事件进一步判断,不能只凭 137 断定内存不足。发生强杀时,资源销毁回调没有执行保证,业务恢复依靠持久状态和幂等,而不是停机时最后一次保存。
删除已停止的本次容器:
docker rm "$CONTAINER"权威资料与规范地址
端点访问、探针语义、指标与停止流程的官方入口如下。
