본문으로 건너뛰기

헬스체크 설정: 장애 노드 자동 감지와 제거

로드밸런싱 환경에서 헬스체크는 없어서는 안 될 핵심 기능입니다. 백엔드 서버가 장애를 겪을 때 로드밸런서가 이를 자동으로 감지해 해당 서버로의 트래픽을 차단하고, 복구되면 자동으로 다시 포함해야 합니다. 헬스체크 없이는 사용자가 오류 응답을 직접 받게 됩니다.


Passive vs Active 헬스체크​

헬스체크에는 두 가지 방식이 있습니다.

Health Check Flow

Passive 헬스체크 (소극적)​

실제 사용자 요청에 대한 응답을 모니터링합니다. 오류가 임계값 이상 발생하면 서버를 자동 배제합니다.

사용자 요청 → App1 (정상)   ✓
사용자 요청 → App2 (오류) ✗ ← 카운트
사용자 요청 → App2 (오류) ✗ ← 카운트
사용자 요청 → App2 (오류) ✗ ← 임계값 도달 → App2 배제
이후 요청 → App1, App3만 사용

장점: 별도 트래픽 없이 실제 요청으로 상태 감지. 단점: 실제 사용자가 오류를 받은 후에야 감지됨. 감지 지연 발생.

Active 헬스체크 (적극적)​

로드밸런서가 주기적으로 전용 헬스체크 엔드포인트에 요청을 보내 사전에 서버 상태를 확인합니다.

[로드밸런서]
│ (30초마다)
├──▶ App1 /health → 200 OK ✓ (정상)
├──▶ App2 /health → 연결 거부 ✗ → 자동 배제
└──▶ App3 /health → 200 OK ✓ (정상)

장점: 사용자 영향 없이 사전 감지. 훨씬 빠른 장애 감지. 단점: 별도 트래픽 발생. Nginx Plus, HAProxy, AWS ALB 등 상용/고급 버전에서 지원.


Nginx Passive 헬스체크​

오픈소스 Nginx는 Active 헬스체크를 지원하지 않습니다. 대신 Passive 헬스체크를 max_fails와 fail_timeout으로 구성합니다.

upstream backend {
server 10.0.0.1:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.2:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.3:8080 max_fails=3 fail_timeout=30s;
}

동작 원리​

fail_timeout(30초) 기간 내에 max_fails(3회) 실패 발생
→ 해당 서버를 fail_timeout(30초) 동안 배제
→ 30초 후 자동 복구 시도 (요청 1건 시도)
→ 성공하면 복귀, 실패하면 다시 30초 배제

실패로 간주되는 상황​

기본적으로 연결 오류와 타임아웃만 실패로 처리합니다. HTTP 오류 코드(502, 503 등)를 추가하려면 proxy_next_upstream을 설정해야 합니다.

upstream backend {
server 10.0.0.1:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.2:8080 max_fails=3 fail_timeout=30s;
}

server {
location / {
proxy_pass http://backend;

# 이 상태 코드도 실패로 간주
proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 10s;
}
}

Passive 헬스체크 한계 극복: 헬스체크 엔드포인트 모니터링​

순수 Nginx에서 Active 헬스체크와 유사한 효과를 내는 방법으로, 외부 모니터링 도구(Prometheus, Nagios 등)와 연계합니다.

# 간단한 셸 스크립트로 헬스체크 시뮬레이션
#!/bin/bash
SERVERS=("10.0.0.1:8080" "10.0.0.2:8080" "10.0.0.3:8080")

for server in "${SERVERS[@]}"; do
response=$(curl -s -o /dev/null -w "%{http_code}" \
--max-time 5 "http://$server/health")
if [ "$response" != "200" ]; then
echo "ALERT: $server is down (HTTP $response)"
# 알림 발송 (Slack, PagerDuty 등)
fi
done

Nginx Plus Active 헬스체크​

Nginx Plus (상용 버전)는 Active 헬스체크를 지원합니다.

upstream backend {
zone backend 64k; # 공유 메모리 존 (Active 헬스체크 필수)

server 10.0.0.1:8080;
server 10.0.0.2:8080;
server 10.0.0.3:8080;
}

server {
location / {
proxy_pass http://backend;

# Active 헬스체크 설정
health_check
interval=10s # 10초마다 체크
fails=3 # 3번 연속 실패 시 배제
passes=2 # 2번 연속 성공 시 복귀
uri=/health # 헬스체크 엔드포인트
match=server_ok; # 응답 검증 조건
}
}

# 응답 검증 조건 정의
match server_ok {
status 200;
header Content-Type ~ "application/json";
body ~ '"status":"UP"'; # 응답 body 패턴 매칭
}

Apache mod_proxy_hcheck (Active 헬스체크)​

Apache 2.4.21+에서 mod_proxy_hcheck 모듈을 통해 Active 헬스체크를 지원합니다.

# 모듈 활성화
sudo a2enmod proxy_hcheck
sudo systemctl reload apache2
# /etc/apache2/sites-available/app-lb.conf

<Proxy "balancer://mycluster">
BalancerMember "http://10.0.0.1:8080"
hcmethod=GET # 헬스체크 HTTP 메서드
hcuri=/health # 헬스체크 엔드포인트 URI
hcinterval=10 # 체크 주기 (초)
hcpasses=2 # N번 성공 시 복귀
hcfails=3 # N번 실패 시 배제
hcexpr=hc200ok # 응답 검증 표현식

BalancerMember "http://10.0.0.2:8080"
hcmethod=GET
hcuri=/health
hcinterval=10
hcpasses=2
hcfails=3
hcexpr=hc200ok

ProxySet lbmethod=bybusyness
</Proxy>

# 응답 검증 조건: 200 OK만 정상으로 간주
<IfDefine hc200ok>
# hcexpr 이름 정의
</IfDefine>

# 정규식으로 헬스체크 응답 검증
ProxyHCExpr hc200ok {%{REQUEST_STATUS} =~ /^[23]/} # 2xx, 3xx 정상
ProxyHCExpr hcjsonok {%{body} =~ /"status"\s*:\s*"UP"/} # JSON body 검증

<VirtualHost *:80>
ServerName example.com

ProxyPass "/" "balancer://mycluster/"
ProxyPassReverse "/" "balancer://mycluster/"
</VirtualHost>

hcmethod 옵션​

값설명
GETHTTP GET 요청으로 헬스체크
HEADHTTP HEAD (body 없음, 가벼움)
OPTIONSHTTP OPTIONS
TCPTCP 연결만 확인 (HTTP 불필요)
CPINGAJP 프로토콜 CPING (Tomcat 전용)

애플리케이션 헬스체크 엔드포인트 구현​

로드밸런서의 헬스체크가 의미있으려면, 백엔드 애플리케이션이 올바른 헬스체크 응답을 반환해야 합니다.

Spring Boot Actuator (Java)​

// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-actuator'
# application.yml
management:
endpoints:
web:
exposure:
include: health
endpoint:
health:
show-details: always

Spring Boot Actuator가 /actuator/health 엔드포인트를 자동 생성합니다:

{
"status": "UP",
"components": {
"db": {"status": "UP"},
"diskSpace": {"status": "UP"},
"ping": {"status": "UP"}
}
}

DB 연결이 끊기면 자동으로 "status": "DOWN" + HTTP 503을 반환합니다.

커스텀 헬스체크 엔드포인트 (Node.js 예시)​

// Express.js 헬스체크 라우터
const express = require('express');
const app = express();

// 데이터베이스 연결 상태 확인
async function checkDatabase() {
try {
await db.query('SELECT 1');
return { status: 'UP' };
} catch (e) {
return { status: 'DOWN', error: e.message };
}
}

app.get('/health', async (req, res) => {
const dbStatus = await checkDatabase();

const health = {
status: dbStatus.status === 'UP' ? 'UP' : 'DOWN',
timestamp: new Date().toISOString(),
components: {
database: dbStatus,
}
};

const statusCode = health.status === 'UP' ? 200 : 503;
res.status(statusCode).json(health);
});

헬스체크 엔드포인트 설계 원칙​

✅ 올바른 헬스체크 설계:
- GET /health → 200 OK (서비스 정상)
- GET /health → 503 Service Unavailable (서비스 불가)
- DB 연결, 외부 서비스 의존성 확인 포함
- 응답 시간 < 5초 (타임아웃 이전 응답)
- 인증 불필요 (로드밸런서 접근 용이)

❌ 잘못된 헬스체크:
- 항상 200 OK 반환 (의미 없음)
- DB 포함 모든 의존성 체크 (너무 엄격, 오탐 발생)
- 응답 시간이 긴 로직 포함 (타임아웃 오류)
- IP 제한 또는 인증 요구 (로드밸런서 접근 불가)

실전 헬스체크 전략​

계층별 헬스체크 구성​

[로드밸런서]
│
├── Shallow Check (빠름, 30초마다)
│ GET /health/ping → 200 OK
│ (애플리케이션 프로세스가 살아있는지만 확인)
│
└── Deep Check (느림, 5분마다 외부 모니터링)
GET /health/deep → 200 OK
(DB, Redis, 외부 API 연결 모두 확인)

로드밸런서는 Shallow Check만 수행합니다. Deep Check는 Prometheus, Nagios 등 외부 모니터링 도구가 담당해 알림만 발생시킵니다.

헬스체크 로그 관리​

헬스체크 요청이 액세스 로그에 쌓이면 노이즈가 됩니다. 필터링합니다:

# Nginx: /health 요청 로그 제외
map $request_uri $loggable {
~^/health 0;
default 1;
}

access_log /var/log/nginx/access.log combined if=$loggable;
# Apache: /health 요청 로그 제외
SetEnvIf Request_URI "^/health$" dontlog
CustomLog /var/log/apache2/access.log combined env=!dontlog

헬스체크 설정 권장값 요약​

환경intervalfailspassesfail_timeout
고가용성 서비스5s2310s
일반 웹 서비스10s3230s
배치성/내부 서비스30s3260s

핵심 원칙:

  • fails를 너무 낮게 → 일시적 지연으로도 배제 (오탐)
  • fails를 너무 높게 → 장애 감지 지연 (미탐)
  • passes를 너무 낮게 → 불안정한 서버가 조기 복귀 (플래핑)

다음 페이지에서는 점진적 트래픽 이동(카나리 배포)과 장애 노드 복구 전략을 알아봅니다.