انتقل إلى المحتوى الرئيسي

الفصل 4: Health Checks

مقدمة​

تسمح Health Checks بالتحقق مما إذا كان تطبيقك واعتمادياته تعمل بشكل صحيح. وهي ضرورية لموازنات التحميل، Kubernetes، والمراقبة.


🎯 أهداف الفصل​

في نهاية هذا الفصل، ستعرف:

  1. ✅ فهم مؤشرات الصحة الأصلية
  2. ✅ إنشاء فحوصات صحة مخصصة
  3. ✅ تكوين مجموعات الصحة
  4. ✅ تنفيذ liveness و readiness probes

1. فحص الصحة الأساسي​

التكوين​

# عرض التفاصيل
management.endpoint.health.show-details=always

# الخيارات: never, when_authorized, always

الاستجابة​

curl http://localhost:8080/actuator/health
{
"status": "UP",
"components": {
"db": {
"status": "UP",
"details": {
"database": "H2",
"validationQuery": "isValid()"
}
},
"diskSpace": {
"status": "UP",
"details": {
"total": 499963174912,
"free": 234567891234,
"threshold": 10485760,
"path": "/app/.",
"exists": true
}
},
"ping": {
"status": "UP"
}
}
}

الحالات الممكنة​

الحالةالوصفكود HTTP
UPكل شيء يعمل200
DOWNهناك شيء لا يعمل503
OUT_OF_SERVICEخارج الخدمة (صيانة)503
UNKNOWNحالة غير معروفة200

2. مؤشرات الصحة الأصلية​

يتضمن Spring Boot مؤشرات صحة تلقائية:

المؤشريتم اكتشافه إذا...
DataSourceHealthIndicatorتم تكوين DataSource
DiskSpaceHealthIndicatorنشط دائمًا
MongoHealthIndicatorتم تكوين MongoDB
RedisHealthIndicatorتم تكوين Redis
RabbitHealthIndicatorتم تكوين RabbitMQ
ElasticsearchHealthIndicatorتم تكوين Elasticsearch
MailHealthIndicatorتم تكوين البريد

تعطيل مؤشر​

# تعطيل فحص مساحة القرص
management.health.diskspace.enabled=false

# تعطيل فحص قاعدة البيانات
management.health.db.enabled=false

3. فحص صحة مخصص​

تنفيذ بسيط​

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class ExternalApiHealthIndicator implements HealthIndicator {

private final ExternalApiClient apiClient;

public ExternalApiHealthIndicator(ExternalApiClient apiClient) {
this.apiClient = apiClient;
}

@Override
public Health health() {
try {
boolean isUp = apiClient.ping();

if (isUp) {
return Health.up()
.withDetail("api", "external-api")
.withDetail("status", "reachable")
.build();
} else {
return Health.down()
.withDetail("api", "external-api")
.withDetail("status", "unreachable")
.build();
}
} catch (Exception e) {
return Health.down()
.withDetail("api", "external-api")
.withDetail("error", e.getMessage())
.build();
}
}
}

النتيجة​

{
"status": "UP",
"components": {
"externalApi": {
"status": "UP",
"details": {
"api": "external-api",
"status": "reachable"
}
}
}
}

فحص صحة مع عتبات​

@Component
public class QueueHealthIndicator implements HealthIndicator {

private final TaskQueue taskQueue;
private static final int WARNING_THRESHOLD = 100;
private static final int CRITICAL_THRESHOLD = 500;

public QueueHealthIndicator(TaskQueue taskQueue) {
this.taskQueue = taskQueue;
}

@Override
public Health health() {
int queueSize = taskQueue.size();

Health.Builder builder = new Health.Builder()
.withDetail("queueSize", queueSize)
.withDetail("warningThreshold", WARNING_THRESHOLD)
.withDetail("criticalThreshold", CRITICAL_THRESHOLD);

if (queueSize >= CRITICAL_THRESHOLD) {
return builder.down()
.withDetail("message", "Queue is critically full!")
.build();
} else if (queueSize >= WARNING_THRESHOLD) {
return builder.status("WARNING")
.withDetail("message", "Queue is getting full")
.build();
} else {
return builder.up()
.withDetail("message", "Queue is healthy")
.build();
}
}
}

فحص صحة غير متزامن​

لتجنب الحظر إذا كان الفحص بطيئًا:

@Component
public class SlowServiceHealthIndicator implements HealthIndicator {

private final SlowService slowService;
private volatile Health cachedHealth = Health.unknown().build();

@Scheduled(fixedRate = 30000) // كل 30 ثانية
public void checkHealth() {
try {
boolean isUp = slowService.ping();
cachedHealth = isUp
? Health.up().withDetail("lastCheck", Instant.now()).build()
: Health.down().withDetail("lastCheck", Instant.now()).build();
} catch (Exception e) {
cachedHealth = Health.down()
.withDetail("error", e.getMessage())
.withDetail("lastCheck", Instant.now())
.build();
}
}

@Override
public Health health() {
return cachedHealth; // يعيد الكاش فورًا
}
}

4. مجموعات الصحة​

تكوين المجموعات​

# مجموعة لموازن التحميل (فحوصات سريعة)
management.endpoint.health.group.liveness.include=ping
management.endpoint.health.group.liveness.show-details=never

# مجموعة لـ Kubernetes readiness (فحوصات كاملة)
management.endpoint.health.group.readiness.include=db,redis,externalApi
management.endpoint.health.group.readiness.show-details=when_authorized

# مجموعة للمراقبة (كل شيء)
management.endpoint.health.group.full.include=*
management.endpoint.health.group.full.show-details=always

الوصول إلى المجموعات​

# فحص أساسي
curl http://localhost:8080/actuator/health

# مجموعة liveness
curl http://localhost:8080/actuator/health/liveness

# مجموعة readiness
curl http://localhost:8080/actuator/health/readiness

# مجموعة كاملة
curl http://localhost:8080/actuator/health/full

5. Kubernetes: Liveness vs Readiness​

المفاهيم​

Liveness Probe:
"هل التطبيق حي؟"
→ إذا كان DOWN: Kubernetes يعيد تشغيل الـ pod

Readiness Probe:
"هل التطبيق جاهز لاستقبال الحركة؟"
→ إذا كان DOWN: Kubernetes يزيل الـ pod من موازن التحميل

تكوين Spring Boot​

# تفعيل probes لـ Kubernetes
management.endpoint.health.probes.enabled=true

# مجموعات تلقائية تم إنشاؤها:
# /actuator/health/liveness
# /actuator/health/readiness

نشر Kubernetes​

apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
containers:
- name: my-app
image: my-app:latest
ports:
- containerPort: 8080

livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3

readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3

تخصيص المجموعات​

@Component
public class CustomLivenessIndicator implements HealthIndicator {

@Override
public Health health() {
// تحقق بسيط: التطبيق يرد
return Health.up().build();
}
}

@Component
public class CustomReadinessIndicator implements HealthIndicator {

private final DataSource dataSource;
private final RedisTemplate<?, ?> redis;

@Override
public Health health() {
// تحقق من أن الاعتماديات جاهزة
try {
dataSource.getConnection().isValid(1);
redis.getConnectionFactory().getConnection().ping();
return Health.up().build();
} catch (Exception e) {
return Health.down().withException(e).build();
}
}
}
management.endpoint.health.group.liveness.include=customLiveness,ping
management.endpoint.health.group.readiness.include=customReadiness,db,redis

6. فحص الصحة لموازن التحميل​

نقطة نهاية مخصصة بسيطة​

@RestController
public class HealthController {

private final AtomicBoolean healthy = new AtomicBoolean(true);

@GetMapping("/health/lb")
public ResponseEntity<String> lbHealthCheck() {
if (healthy.get()) {
return ResponseEntity.ok("OK");
}
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("DOWN");
}

// للصيانة: وضع التطبيق خارج الخدمة
@PostMapping("/admin/maintenance/enable")
public void enableMaintenance() {
healthy.set(false);
}

@PostMapping("/admin/maintenance/disable")
public void disableMaintenance() {
healthy.set(true);
}
}

7. ملخص​

التكوين الموصى به​

# تفاصيل حسب التفويض
management.endpoint.health.show-details=when_authorized
management.endpoint.health.show-components=when_authorized

# تفعيل probes لـ K8s
management.endpoint.health.probes.enabled=true

# مجموعات مخصصة
management.endpoint.health.group.liveness.include=ping,livenessState
management.endpoint.health.group.readiness.include=readinessState,db,redis

مؤشر صحة مخصص​

@Component
public class MyHealthIndicator implements HealthIndicator {

@Override
public Health health() {
if (isHealthy()) {
return Health.up()
.withDetail("key", "value")
.build();
}
return Health.down()
.withDetail("error", "Something wrong")
.build();
}
}

مجموعات Kubernetes​

المجموعةالاستخدامالتضمين
livenessهل الـ pod حي؟ping, فحوصات أساسية
readinessجاهز للحركة؟db, redis, APIs خارجية

🎯 الخطوة التالية​

حان وقت التطبيق! لنقم بتكوين Actuator من الألف إلى الياء.

➡️ TP11.1: تكوين Actuator


⬅️ العودة إلى الوحدة 11