Actuator là thứ biến ứng dụng từ hộp đen thành thứ quan sát được. Bài này đo những gì nó cho.
/actuator/health
{"status":"UP","components":{
"app.KiemDichVuNgoai":{"status":"UP","details":{"doTre":"12ms"}},
"diskSpace":{"status":"UP","details":{"free":472625725440,...}},
"livenessState":{"status":"UP"},
"ping":{"status":"UP"},
"readinessState":{"status":"UP"}},
"groups":["liveness","readiness"]}
Spring tự thêm chỉ báo cho những thứ nó biết: CSDL, Redis, RabbitMQ, dung lượng đĩa. Thêm của mình rất gọn:
@Component
class KiemDichVuNgoai implements HealthIndicator {
public Health health() {
return goiDuoc()
? Health.up().withDetail("doTre", "12ms").build()
: Health.down().withDetail("loi", "khong noi duoc").build();
}
}
Lưu ý show-details: always chỉ nên bật ở môi trường nội bộ — chi tiết gồm cả đường dẫn tệp và trạng thái hạ tầng. Sản xuất dùng when-authorized.
Liveness và readiness: khác biệt quan trọng nhất
khi dịch vụ ngoài chết:
/actuator/health -> HTTP 503
/actuator/health/liveness -> HTTP 200
Dùng nhầm hai cái này gây ra một trong những sự cố tệ nhất tôi biết: dịch vụ phụ thuộc chậm đi, health tổng thành DOWN, Kubernetes khởi động lại mọi pod, và ứng dụng vốn chỉ chậm giờ chết hẳn — trong khi nguyên nhân nằm ở chỗ khác.
Quy tắc: liveness chỉ kiểm thứ mà khởi động lại sửa được. Dịch vụ ngoài chết thì khởi động lại không sửa gì, nên nó không được ảnh hưởng liveness.
management:
endpoint:
health:
probes:
enabled: true
livenessProbe:
httpGet: { path: /actuator/health/liveness, port: 8080 }
readinessProbe:
httpGet: { path: /actuator/health/readiness, port: 8080 }
Muốn một chỉ báo tham gia liveness thì khai tường minh:
management.endpoint.health.group.readiness.include: readinessState, db, dichVuNgoai
Chỉ số
http.server.requests jvm.memory.used
http.server.requests.active jvm.threads.live
jvm.gc.pause process.cpu.time
hikaricp.connections.* ...
Có sẵn, không phải viết gì. Bài 34 đã dùng hikaricp.connections.pending để phát hiện cạn pool.
Thêm chỉ số riêng:
@Timed(value = "viec.xu_ly", percentiles = {0.5, 0.95, 0.99})
String viec() { ... }
reg.counter("viec.dem").increment();
http.server.requests?tag=uri:/viec
COUNT = 20
TOTAL_TIME = 0,4569 s
MAX = 0,0289 s
Đo theo phân vị, đừng đo trung bình. Bài 86 sê-ri Java đã cho thấy trung bình giấu đi phần đuôi, mà phần đuôi mới là thứ người dùng cảm nhận. TOTAL_TIME chia COUNT ở trên cho ra 22,8 ms — nhưng nó không cho biết có request nào mất 2 giây hay không.
Bốn chỉ số tối thiểu cho một dịch vụ: tỷ lệ lỗi theo endpoint, độ trễ p95/p99, thông lượng, và tài nguyên bão hoà (pool kết nối, hàng đợi executor, heap).
Và cẩn thận với số chiều của nhãn. Gắn nhãn là id người dùng hay id đơn hàng sẽ sinh hàng triệu chuỗi thời gian và làm sập hệ thống giám sát. Nhãn phải có tập giá trị nhỏ và hữu hạn.
Prometheus
<dependency>
<groupId>io.micrometer</groupId><artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
Một phụ thuộc, và /actuator/prometheus xuất hiện với định dạng Prometheus đọc được. Không cấu hình thêm.
Đổi mức log lúc đang chạy
trước: {"effectiveLevel":"INFO"}
POST /actuator/loggers/vd.act {"configuredLevel":"DEBUG"} -> 204
sau : {"configuredLevel":"DEBUG","effectiveLevel":"DEBUG"}
Bật DEBUG cho một gói trên sản xuất mà không khởi động lại, rồi tắt đi. Đây là endpoint tôi dùng nhiều nhất khi đang gỡ lỗi thật.
Nhớ tắt lại — DEBUG trên tải cao sinh log rất nhanh, và bài 98 sê-ri Java đo được hai triệu dòng log là 34 megabyte.
Vì nó sửa được trạng thái ứng dụng, endpoint này phải được bảo vệ như mọi endpoint quản trị khác.
Những endpoint không được lộ
/actuator/env -> HTTP 404 (đúng: không nằm trong include)
Bài 46 đã nói và đây là cách cấu hình:
management:
endpoints:
web:
exposure:
include: health, info, metrics, prometheus, loggers # liệt kê tường minh
server:
port: 9090 # cổng riêng
Ba endpoint nguy hiểm nhất: /env in ra toàn bộ biến môi trường gồm mật khẩu; /heapdump tải về nguyên bộ nhớ ứng dụng; /threaddump lộ cấu trúc nội bộ.
management.server.port là biện pháp tôi thấy hiệu quả nhất: Actuator nghe cổng khác, nginx không proxy cổng đó ra ngoài, và cả lớp vấn đề biến mất bất kể ai cấu hình sai include.
/actuator/info
info:
app:
ten: demo
Thêm thông tin build và Git:
<plugin>
<groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId>
<executions><execution><goals><goal>build-info</goal></goals></execution></executions>
</plugin>
Rồi /actuator/info cho biết chính xác commit nào đang chạy. Nghe nhỏ, nhưng lúc 2 giờ sáng thì câu hỏi đầu tiên luôn là "phiên bản nào đang chạy trên đó".
Ba việc nữa đáng làm
Nối health với cảnh báo, đừng chỉ với Kubernetes. Pod bị rút khỏi cân bằng tải mà không ai được báo nghĩa là bạn có sự cố ngầm.
Kiểm tra sức khoẻ phải hỏi một endpoint thật, không chỉ kiểm tiến trình còn sống. JVM còn chạy không có nghĩa ứng dụng còn phục vụ được.
Đặt timeout cho chỉ báo gọi ra ngoài. Một HealthIndicator gọi dịch vụ ngoài không có phép chờ sẽ làm treo chính endpoint health — và Kubernetes coi treo là hỏng.
Thử ba mươi giây
curl -s localhost:8080/actuator/health | jq '.components | keys'
Danh sách đó là những thứ đang quyết định pod của bạn có nhận lưu lượng hay không. Nếu trong đó có một dịch vụ mà bạn không muốn làm rút pod khỏi cân bằng tải, hãy đưa nó ra khỏi nhóm readiness ngay hôm nay.
Ngày mai: log và truy vết trong Spring Boot — mã tương quan đi xuyên qua các dịch vụ.