고가용성 아키텍처 설계와 자동 페일오버 운영
고가용성(HA)의 중복성, 헬스 체크, 페일오버 구조와 클러스터·복제·네트워크 설계 시 고려할 운영 요소를 다룬다.
2026-08-15 · 최초 발행 2026-01-12
다운타임을 줄이는 HA의 범위
고가용성(High Availability, HA)은 장애가 발생해도 서비스 중단을 최소화하고 빠르게 복구하도록 구성하는 시스템 아키텍처다. 핵심은 계획되지 않은 다운타임을 줄이는 데 있으며, 중복 구성·페일오버·모니터링을 함께 사용한다.
HA는 일반적으로 99.9%(Three 9s)에서 99.99%(Four 9s) 수준의 가용성을 목표로 한다. Fault Tolerant 시스템처럼 무중단을 목표로 하지는 않으며, 짧은 서비스 중단을 허용하는 대신 비용 효율과 비즈니스 연속성의 균형을 맞춘다.
가용성 수준은 허용 가능한 중단 시간과 비용을 함께 판단할 기준이 된다.
| 등급 | 가용성 | 연간 다운타임 | 월간 다운타임 |
|---|---|---|---|
| Two 9s | 99% | 3.65일 | 7.2시간 |
| Three 9s | 99.9% | 8.76시간 | 43.2분 |
| Four 9s | 99.99% | 52.56분 | 4.32분 |
| Five 9s | 99.999% | 5.26분 | 25.9초 |
일반적인 시스템 가용성은 95-99% 범위이고, 월간 다운타임은 수분에서 수시간까지 발생할 수 있다. HA는 99.9-99.99%를 대상으로 월간 중단을 분 단위로 줄이며, Fault Tolerant는 99.999% 이상과 연간 초 단위 다운타임을 지향한다. 이에 따라 비용도 낮음, 중간, 높음으로 달라진다.
장애를 대신 처리할 구성 만들기
HA는 장애가 난 구성 요소를 다른 구성 요소가 이어받을 수 있어야 한다. 서버, 네트워크, 스토리지, 전원 중 어느 하나가 단일 장애점(SPOF)으로 남으면 전체 가용성 목표가 무너질 수 있다.
서버는 액티브-스탠바이 또는 액티브-액티브로 중복하고, 네트워크는 다중 경로와 스위치로 분리한다. 스토리지는 공유 스토리지 또는 복제를 사용하며, 전원도 이중 공급으로 구성한다.
장애 감지는 단순한 서버 생존 확인에 그치지 않는다. 네트워크, OS, 애플리케이션 수준에서 주기적으로 상태를 확인하고, 응답이 없거나 연속 실패가 임계값을 넘으면 장애로 판단한다. 장애가 확인되면 대기 시스템을 자동으로 활성화하고, 세션과 데이터를 동기화한 상태로 서비스를 넘긴다. 복구된 기존 시스템을 다시 원래 역할로 돌리는 과정은 페일백이다.
다음 예시는 UDP 기반 Heartbeat를 보내고, 실패 횟수가 임계값에 도달하면 VIP 인수와 서비스 시작을 수행하는 구조를 보여준다.
# Python Heartbeat 예시
import socket
import threading
import time
class HeartbeatMonitor:
def __init__(self, peer_ip, port=5000, interval=1, timeout=3):
self.peer_ip = peer_ip
self.port = port
self.interval = interval # Heartbeat 간격 (초)
self.timeout = timeout # 장애 판정 임계값 (실패 횟수)
self.failure_count = 0
self.is_peer_alive = True
def send_heartbeat(self):
"""주기적으로 Heartbeat 전송"""
while True:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(0.5)
# Heartbeat 메시지 전송
message = "HEARTBEAT"
sock.sendto(message.encode(), (self.peer_ip, self.port))
# ACK 대기
data, addr = sock.recvfrom(1024)
if data.decode() == "ACK":
self.failure_count = 0
self.is_peer_alive = True
print(f"[OK] Peer {self.peer_ip} is alive")
except socket.timeout:
self.failure_count += 1
print(f"[WARNING] Heartbeat timeout ({self.failure_count}/{self.timeout})")
if self.failure_count >= self.timeout:
self.is_peer_alive = False
print(f"[CRITICAL] Peer {self.peer_ip} is DOWN!")
self.trigger_failover()
except Exception as e:
print(f"[ERROR] Heartbeat failed: {e}")
finally:
sock.close()
time.sleep(self.interval)
def trigger_failover(self):
"""Failover 수행"""
print("[ACTION] Starting Failover...")
# VIP 인수
self.acquire_vip()
# 서비스 활성화
self.start_services()
def acquire_vip(self):
"""Virtual IP 인수"""
# Linux 예시
import os
vip = "192.168.1.100"
interface = "eth0"
os.system(f"ip addr add {vip}/24 dev {interface}")
print(f"[VIP] Acquired {vip}")
def start_services(self):
"""서비스 시작"""
# 애플리케이션 서비스 시작
import os
os.system("systemctl start myapp")
print("[SERVICE] Application started")
# 사용 예시
monitor = HeartbeatMonitor(peer_ip="192.168.1.2")
threading.Thread(target=monitor.send_heartbeat, daemon=True).start()
페일오버 전환 시간은 수초~수분 내를 목표로 한다.
서비스 부하와 대기 자원을 배치하는 방식
액티브-스탠바이는 정상 상태에서 액티브 노드만 서비스를 제공하고, 장애가 나면 스탠바이 노드가 VIP를 인계받는 구조다. 역할이 분명하고 구성이 단순하지만, 스탠바이 자원이 유휴 상태가 된다.
Keepalived에서는 두 노드의 상태와 우선순위를 다음처럼 구성할 수 있다.
# /etc/keepalived/keepalived.conf (Primary)
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100 # Primary 우선순위 높음
advert_int 1 # Heartbeat 간격 (1초)
authentication {
auth_type PASS
auth_pass secret123
}
virtual_ipaddress {
192.168.1.100/24 # VIP
}
}
# /etc/keepalived/keepalived.conf (Standby)
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 90 # Standby 우선순위 낮음
advert_int 1
authentication {
auth_type PASS
auth_pass secret123
}
virtual_ipaddress {
192.168.1.100/24
}
}
액티브-액티브는 모든 노드가 동시에 요청을 처리한다. 특정 노드에 장애가 나면 남은 노드가 부하를 분담하므로 자원 활용률과 성능을 높일 수 있다. 대신 데이터 동기화와 세션 관리가 복잡해진다.
N+1 중복은 N개의 액티브 서버에 1개의 스페어 서버를 추가하는 방식이다. 정상 상태에서는 N개 서버가 부하를 나눠 처리하고, 장애가 발생하면 스페어가 장애 서버를 대체한다. 하나의 스페어로 여러 서버를 보호할 수 있다는 점이 특징이다.
Pacemaker와 Corosync를 사용하면 각 노드가 서로 다른 서비스를 우선 담당하도록 구성할 수 있다.
# Pacemaker + Corosync 구성
# Node A
crm configure primitive WebService ocf:heartbeat:apache \
params configfile="/etc/apache2/apache2.conf" \
op monitor interval="10s"
crm configure primitive DBService ocf:heartbeat:mysql \
params datadir="/var/lib/mysql" \
op monitor interval="10s"
# Node A는 WebService를 Primary로
crm configure location WebService-on-NodeA WebService 100: node-a
# Node B는 DBService를 Primary로
crm configure location DBService-on-NodeB DBService 100: node-b
여러 노드가 같은 데이터에 동시 접근하는 구성에서는 DLM(Distributed Lock Manager)으로 락의 획득과 해제를 조정할 수 있다.
// DLM 개념적 예시
void update_shared_data(int node_id, Data* data) {
// 분산 락 획득
lock_handle = dlm_lock("shared_data", EXCLUSIVE);
if(lock_handle != NULL) {
// 공유 데이터 수정
modify_data(data);
write_to_shared_storage(data);
// 락 해제
dlm_unlock(lock_handle);
} else {
// 락 획득 실패 - 재시도 또는 대기
handle_lock_failure();
}
}
클러스터는 리소스의 순서를 관리한다
HA 클러스터는 노드, 하트비트 네트워크, 클러스터 관리자를 중심으로 동작한다. 클러스터 관리자는 Virtual IP, 애플리케이션, 파일 시스템 같은 리소스를 관리하며, 리소스 간 시작 순서와 의존성도 처리한다. 노드는 공유 스토리지를 함께 사용한다.
클라이언트가 접속하는 가상 IP, 데이터베이스나 웹 서버 같은 애플리케이션, 공유 스토리지 마운트가 대표적인 클러스터 리소스다.
하트비트는 노드의 생존을 확인하는 기반이지만, 응답이 없다는 사실만으로 상대 노드 장애를 단정할 수는 없다. 네트워크 단절로 하트비트만 끊긴 상태에서 양쪽 노드가 각각 서비스를 인수하면 Split-Brain이 발생할 수 있다. Ethernet과 Serial/RS-232 같은 하트비트 경로를 이중화하고, 서비스 포트·프로세스·애플리케이션 상태를 함께 확인해 장애를 재판정해야 한다.
모든 확인 경로가 끊겼을 때에는 Quorum으로 서비스 소유권을 판단하고, Fencing 또는 STONITH(Shoot The Other Node In The Head)로 기존 노드를 확실히 격리한 뒤 리소스를 인수한다. 이 설계는 네트워크 장애를 노드 장애로 오인해 두 노드가 동시에 같은 리소스를 활성화하는 경로를 줄인다.
사용할 수 있는 클러스터 소프트웨어는 다음과 같다.
- Linux: Pacemaker + Corosync, DRBD
- Windows: Windows Server Failover Clustering(WSFC)
- 상용: Veritas Cluster Server, Oracle RAC
- 클라우드: AWS ECS, Azure Service Fabric, Kubernetes
복제 방식은 일관성과 지연의 선택이다
데이터 복제는 HA와 원격 DR 구성을 나누는 중요한 기준이다. 동기 복제에서는 쓰기 작업이 모든 노드에 완료될 때까지 대기한다. 데이터 일관성은 완벽하지만 지연 시간이 증가하며, 로컬 HA 클러스터에 사용한다.
비동기 복제는 주 노드의 쓰기 완료 뒤 백그라운드에서 복제를 수행한다. 빠른 쓰기 성능을 얻는 대신 약간의 데이터 지연이 가능하며, 원격 DR 사이트에 사용한다.
네트워크와 스토리지도 장애 경로를 남겨둔다
NIC 티밍 또는 본딩은 Active-Backup, Load Balancing, LACP 모드를 사용할 수 있다. 링크 상태와 MII 모니터링으로 장애를 감지하고, 장애가 난 NIC를 자동으로 제외한다. 애플리케이션은 이 전환을 투명하게 처리할 수 있다.
VRRP(Virtual Router Redundancy Protocol)에서는 여러 라우터가 하나의 가상 IP를 공유한다. 우선순위 기반으로 마스터를 선출하고 광고 패킷으로 상태를 확인한다. 마스터에 장애가 발생하면 백업 라우터가 가상 IP를 인계한다.
스토리지 계층에서는 여러 경로로 스토리지에 접근하는 다중 경로(MPIO), 액티브-액티브 컨트롤러 이중화, RAID, 스냅샷을 조합한다. 다중 경로는 접근 경로 장애를 견디게 하고, 컨트롤러 이중화는 스토리지 배열의 단일 장애점을 줄인다. RAID는 디스크 장애를 보호하며, 스냅샷은 빠른 백업과 복구에 사용한다.
DRBD를 이용한 동기 복제 구성은 다음과 같이 정의할 수 있다.
# DRBD 설정 예시
# /etc/drbd.d/r0.res
resource r0 {
protocol C; # 동기식 복제
on primary-node {
device /dev/drbd0;
disk /dev/sdb1;
address 192.168.1.1:7789;
meta-disk internal;
}
on standby-node {
device /dev/drbd0;
disk /dev/sdb1;
address 192.168.1.2:7789;
meta-disk internal;
}
}
# Primary에서 스토리지 장애 시 자동 전환
drbdadm primary r0
mount /dev/drbd0 /mnt/data
감시는 서비스가 실제로 제공되는지 확인해야 한다
헬스 체크는 네트워크, OS, 애플리케이션, 서비스 레벨로 나눌 수 있다. 네트워크에서는 Ping과 TCP 연결을 보고, OS에서는 CPU·메모리·디스크·프로세스를 확인한다. 애플리케이션 레벨에서는 응답과 오류율을, 서비스 레벨에서는 비즈니스 로직을 확인한다.
다음과 같이 애플리케이션의 실제 기능을 검사하는 헬스 체크를 구성할 수 있다.
# 애플리케이션 레벨 Health Check
class ApplicationHealthCheck:
def __init__(self):
self.last_success = time.time()
def check_health(self):
"""실제 애플리케이션 기능 테스트"""
try:
# DB 쿼리 테스트
db.execute("SELECT 1")
# API 엔드포인트 테스트
response = requests.get("http://localhost/health", timeout=5)
if response.status_code != 200:
raise Exception("Health check failed")
# 비즈니스 로직 테스트
result = process_sample_request()
if not result:
raise Exception("Business logic failed")
self.last_success = time.time()
return True
except Exception as e:
print(f"Health check failed: {e}")
return False
# Heartbeat 전송 전에 Health Check
if health_checker.check_health():
send_heartbeat()
else:
# Health Check 실패 → Heartbeat 전송 중단 → Failover 유도
pass
모니터링 도구는 환경에 따라 선택한다.
- 오픈소스: Nagios, Zabbix, Prometheus
- 상용: Datadog, New Relic, Dynatrace
- 클라우드: CloudWatch, Azure Monitor, GCP Monitoring
- 클러스터: 클러스터 소프트웨어 내장 모니터링
장애가 감지되면 SMS, 이메일, 슬랙 등으로 즉시 알리고 단계별 에스컬레이션을 적용한다. 자동 페일오버나 재시작 같은 대응을 연결할 수 있으며, 모든 이벤트는 로깅해야 한다.
장애 감지와 재확인은 다음과 같이 분리할 수 있다.
def detect_failure(heartbeat_monitor):
"""Heartbeat 타임아웃으로 장애 감지"""
if heartbeat_monitor.failure_count >= THRESHOLD:
return True
return False
def confirm_failure(primary_ip):
"""다중 경로로 장애 재확인"""
# Ethernet Heartbeat
eth_alive = check_heartbeat_ethernet(primary_ip)
# Serial Heartbeat (대체 경로)
serial_alive = check_heartbeat_serial()
# ICMP Ping
ping_alive = os.system(f"ping -c 1 {primary_ip}") == 0
if not eth_alive and not serial_alive and not ping_alive:
return True # 확실한 장애
else:
return False # 네트워크 문제일 가능성
장애가 확인된 뒤에는 VIP 인수, 공유 디스크 마운트, 기존 Primary 격리 순서로 리소스를 넘긴다.
#!/bin/bash
# Failover 스크립트 예시
# 1. VIP 인수
ip addr add 192.168.1.100/24 dev eth0
echo "VIP acquired"
# 2. 공유 디스크 마운트
mount /dev/sdb1 /mnt/shared
echo "Shared disk mounted"
# 3. 이전 Primary 격리 (STONITH)
fence_node primary-node
echo "Primary node fenced"
서비스를 시작한 뒤에는 상태를 확인하고 클라이언트가 VIP로 새 Active 노드에 연결되도록 한다.
# 4. 애플리케이션 서비스 시작
systemctl start postgresql
systemctl start apache2
echo "Services started"
# 5. 상태 확인
systemctl status postgresql
systemctl status apache2
# 클라이언트 측 재연결 로직
import time
def connect_with_retry(vip, port, max_retries=5):
for attempt in range(max_retries):
try:
conn = socket.create_connection((vip, port), timeout=5)
print("Connected successfully")
return conn
except Exception as e:
print(f"Connection failed (attempt {attempt+1}): {e}")
time.sleep(2) # Failover 대기
raise Exception("Connection failed after all retries")
# VIP로 연결 (Failover 시 자동으로 Standby로 연결됨)
conn = connect_with_retry("192.168.1.100", 5432)
애플리케이션이 Hang 상태이면 프로세스가 살아 있어도 응답하지 않을 수 있다.
# 애플리케이션이 Hang 상태
while True:
# 무한 루프에 빠진 상태
# 프로세스는 살아있지만 응답 없음
pass
# Heartbeat는 정상 전송 (프로세스 살아있음)
# → Failover 미발동
# → 서비스 응답 없음 지속
중요도에 따라 HA 범위를 넓힌다
HA는 단일 서버를 기준선으로 삼아 대기 노드를 추가하고, 자동 페일오버와 모니터링 튜닝으로 확장할 수 있다. 모든 시스템에 같은 수준의 구성을 적용하기보다 중요 시스템부터 HA를 적용하고, 시스템 중요도에 따라 다른 HA 수준을 두는 방식이 비용을 관리하기 쉽다.
클라우드 기반 HA 서비스를 사용하거나 상용 솔루션 대신 오픈소스 솔루션을 검토하는 것도 선택지다.
장애 전환은 실제로 반복해 봐야 한다
페일오버 테스트에서는 정기적으로 장애를 계획해 전환을 확인한다. 프로덕션 영향은 최소화하고, 페일오버 시간·데이터 손실·세션 복구를 측정한 뒤 결과와 개선 사항을 문서화한다.
재해 복구 훈련은 전체 사이트 장애를 시뮬레이션해 DR 절차서를 검증하는 과정이다. RTO 목표 달성 여부를 확인하고, 분기 또는 반기별로 훈련한다.
Split-Brain 방지에는 Quorum, Fencing, Tiebreaker를 함께 사용할 수 있다.
# Pacemaker Quorum 설정
crm configure property no-quorum-policy=stop
# 3노드 중 과반(2개) 이상만 Active
# 분리된 소수 노드는 자동 중지
# STONITH 설정 (전원 차단)
crm configure primitive fence_primary stonith:fence_ipmilan \
params ipaddr=192.168.1.1 login=admin passwd=secret \
op monitor interval=60s
# Primary 응답 없으면 강제 전원 차단
# → Standby만 유일한 Active 노드 보장
# Corosync Quorum Device
# 제3의 노드 또는 네트워크 장비가 중재자 역할
pvecm qdevice setup 192.168.1.254
서비스 유형에 따른 HA 구성
웹 서비스는 다중 로드 밸런서와 무상태(Stateless) 다중 웹 서버가 요청을 분산하고, 애플리케이션 클러스터가 서비스를 처리하며, 데이터베이스는 액티브-스탠바이로 구성할 수 있다. DNS Round Robin도 로드 밸런서 다중화 구성에 사용할 수 있다.
Nginx와 Keepalived를 조합하면 Nginx 상태를 추적해 VIP의 우선순위에 반영할 수 있다.
# Primary 노드 (192.168.1.1)
# /etc/keepalived/keepalived.conf
vrrp_script check_nginx {
script "/usr/local/bin/check_nginx.sh"
interval 2
weight -20
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100
track_script {
check_nginx
}
virtual_ipaddress {
192.168.1.100/24
}
}
# /usr/local/bin/check_nginx.sh
#!/bin/bash
if systemctl is-active nginx > /dev/null; then
exit 0
else
exit 1
fi
PostgreSQL을 사용하는 이 구성에서는 데이터베이스의 액티브-스탠바이에 Streaming Replication을 적용하고 Patroni로 역할 전환을 관리할 수 있다. 웹 계층의 다중화와 PostgreSQL 복제·전환 구성을 함께 설계해야 웹 서버 장애와 데이터베이스 노드 장애에 각각 대응할 수 있다.
# Patroni 설정
scope: postgres-cluster
name: node1
restapi:
listen: 192.168.1.1:8008
connect_address: 192.168.1.1:8008
etcd:
hosts: 192.168.1.3:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
postgresql:
use_pg_rewind: true
postgresql:
listen: 0.0.0.0:5432
connect_address: 192.168.1.1:5432
data_dir: /var/lib/postgresql/13/main
pgpass: /tmp/pgpass
authentication:
replication:
username: replicator
password: secret
superuser:
username: postgres
password: secret
데이터베이스 제품별 구성 예시는 다음과 같다.
- PostgreSQL: Streaming Replication, Patroni
- MySQL: Master-Slave Replication, Galera Cluster
- Oracle: RAC(Real Application Clusters)
- SQL Server: Always On Availability Groups
가상화 환경에서는 VMware의 vSphere HA·vMotion, Hyper-V의 Live Migration·Failover Clustering, KVM의 Live Migration·Pacemaker를 사용할 수 있다. 클라우드에서는 Auto Scaling과 Multi-AZ를 활용한다.
설계와 운영에서 확인할 조건
설계 단계에서는 모든 SPOF를 식별하고 중요 구성 요소를 이중화해야 한다. 장애 시나리오 테스트와 정기적인 아키텍처 리뷰로 설계가 계속 유효한지 확인한다.
자동 페일오버, 자동 재시작과 재구성, 부하에 따른 자동 스케일링, 24/7 자동 모니터링은 사람 개입을 줄이는 구성 요소다. 다만 자동화가 의도한 순서로 동작하는지는 검증이 필요하다. 분기별 페일오버 테스트, 무작위 장애를 주입하는 카오스 엔지니어링, 최대 부하 상황의 페일오버 테스트, DR 훈련을 운영 계획에 포함한다.
기술적으로는 비즈니스 요구사항에 맞는 RTO/RPO 목표, 향후 성장을 고려한 확장성, 기존 인프라와의 호환성, 운영 가능한 수준의 복잡도를 검토한다. 운영 측면에서는 필요한 기술 수준과 인력, 장애 대응 절차서, 정기 교육, 24/7 모니터링 체계가 필요하다.
비용을 검토할 때는 하드웨어·소프트웨어·구축에 드는 초기 투자와 인력·유지보수·라이선스의 운영 비용을 함께 본다. TCO는 5년 총 소유 비용으로, ROI는 다운타임 비용 대비 투자 효과로 평가한다.