본문 바로가기
삵
Nginx

Keepalived로 Nginx 두 대에 VIP 이중화

강강철지그·2026년 9월 27일·조회 1

앞단에 클라우드 로드밸런서를 하나 더 세우는 게 부담스러운 규모의 서비스가 꽤 많다. 트래픽은 크지 않은데 웹 서버 한 대가 죽으면 서비스 전체가 멈추는 구조, 이런 곳에서 가장 자주 꺼내 드는 카드가 Keepalived다. 예전에 L4 스위치로 VIP를 잡던 걸 소프트웨어로 대신한다고 보면 된다. 이 글에서는 Nginx 두 대에 VIP 하나를 걸어 한 대가 죽으면 다른 대로 자동 절체되도록 만드는 순서를 정리한다.

결론부터 말하자면, Keepalived를 두 노드에 설치하고 vrrp_instance로 MASTER/BACKUP 우선순위를 나눈 뒤, vrrp_script로 Nginx 상태를 감시하도록 묶으면 된다. MASTER의 Nginx가 죽으면 우선순위가 떨어지거나 FAULT 상태로 빠지고, VIP는 BACKUP으로 넘어간다. 클라우드 LB 비용 없이 L4 수준의 이중화가 완성된다.

1. VRRP와 VIP가 무엇인지부터

VRRP(Virtual Router Redundancy Protocol)는 여러 대의 라우터나 서버가 하나의 가상 IP를 공유하면서, 그중 한 대만 실제로 그 IP를 소유하도록 조율하는 프로토콜이다. IP 프로토콜 번호 112번을 쓰고, 같은 그룹에 속한 노드끼리 주기적으로 advertisement 패킷을 주고받는다.

VIP(Virtual IP)는 물리 서버 고유 IP와 별개로 떠다니는 가상 주소다. 평소에는 우선순위가 높은 MASTER 노드의 인터페이스에 붙어 있다가, MASTER가 죽으면 BACKUP 노드로 옮겨 붙는다. 클라이언트와 DNS는 이 VIP만 바라보므로, 뒤에서 어느 서버가 실제로 응답하는지는 알 필요가 없다.

Keepalived는 이 VRRP를 리눅스에서 구현한 데몬이다. 원래 LVS 로드밸런서의 헬스체크용으로 나왔지만, 지금은 VIP 이중화 용도로 더 많이 쓴다.

2. 구성 개요

같은 서브넷에 있는 Nginx 두 대와 VIP 한 개로 구성한다. VRRP는 같은 L2 세그먼트(동일 서브넷)에서 gratuitous ARP로 VIP의 MAC 위치를 광고하는 방식이라, 두 노드가 같은 서브넷에 있어야 정상 동작한다.

  • node1 (MASTER): 10.0.0.11, priority 150
  • node2 (BACKUP): 10.0.0.12, priority 100
  • VIP: 10.0.0.100
  • 인터페이스: eth0(환경에 따라 ens192 등)

AWS 같은 퍼블릭 클라우드는 멀티캐스트와 임의 IP의 ARP 광고를 막는 경우가 많다. 이 경우는 6절에서 unicast와 notify 스크립트로 우회하는 방법을 따로 다룬다.

3. Keepalived 설치

RHEL 계열은 yum(또는 dnf)으로, Debian/Ubuntu는 apt로 설치한다. 두 노드에 동일하게 깐다.

# RHEL 9 / Rocky / AlmaLinux
sudo dnf install -y keepalived

# Ubuntu / Debian
sudo apt update && sudo apt install -y keepalived

# 버전 확인 (버전 번호는 저장소 상태에 따라 다를 수 있음)
keepalived --version
Keepalived v2.2.8 (04/04,2023)

설정 파일은 /etc/keepalived/keepalived.conf 하나다. 배포판에 따라 기본 파일이 없을 수 있으니 새로 작성한다.

4. MASTER/BACKUP 설정

먼저 node1(MASTER)의 /etc/keepalived/keepalived.conf다.

vrrp_script chk_nginx {
    script "/etc/keepalived/check_nginx.sh"
    interval 2        # 2초마다 실행
    timeout 3         # 3초 넘으면 실패로 간주
    fall 2            # 연속 2회 실패하면 FAULT
    rise 2            # 연속 2회 성공하면 복귀
}

vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 150
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass s3cr3tpw   # 최대 8자, 두 노드 동일
    }
    virtual_ipaddress {
        10.0.0.100/24
    }
    track_script {
        chk_nginx
    }
    notify_master "/etc/keepalived/notify.sh master"
    notify_backup "/etc/keepalived/notify.sh backup"
    notify_fault  "/etc/keepalived/notify.sh fault"
}

node2(BACKUP)는 두 줄만 다르다. state BACKUP, priority 100. 나머지 virtual_router_id, auth_pass, virtual_ipaddress는 반드시 양쪽이 같아야 한다. virtual_router_id가 다르면 서로를 같은 그룹으로 인식하지 못해 둘 다 MASTER가 된다.

# node2 에서 다른 부분만
    state BACKUP
    priority 100

주요 키워드를 한 줄씩 정리한다.

priority

VRRP 그룹 안에서 누가 VIP를 가질지 결정하는 숫자다(1~254). 값이 큰 쪽이 MASTER가 된다. 여기서는 150 대 100으로 벌려 뒀다.

advert_int

MASTER가 살아 있다고 알리는 advertisement 전송 간격(초). 기본 1초다. BACKUP은 이 패킷이 약 3배 주기 동안 안 오면 MASTER가 죽었다고 판단하고 VIP를 가져온다.

virtual_router_id

VRRP 그룹 식별자(0~255). 같은 네트워크에 여러 VRRP 그룹이 있으면 겹치지 않게 배정한다. 겹치면 서로 간섭한다.

authentication

auth_type PASS는 평문 비밀번호로 advertisement를 구분한다. 암호화는 아니고 오설정 방지 수준이다. 최대 8자까지만 유효하다.

5. health check 스크립트

Keepalived 기본 동작은 노드 자체가 죽거나 네트워크가 끊겼을 때만 절체한다. 서버는 살아 있는데 Nginx 프로세스만 죽은 경우는 감지하지 못한다. 그래서 vrrp_script로 Nginx 상태를 직접 확인하고, track_script로 인스턴스에 연결한다.

/etc/keepalived/check_nginx.sh를 만든다. 프로세스 존재만 보지 말고 실제 응답까지 확인하는 편이 정확하다.

#!/bin/bash
# 프로세스 확인
if ! systemctl is-active --quiet nginx; then
    exit 1
fi
# 로컬 응답 확인
curl -sf -o /dev/null http://127.0.0.1/ || exit 1
exit 0
sudo chmod +x /etc/keepalived/check_nginx.sh

여기서 한 번 짚고 넘어갈 게 있다. vrrp_script에 weight를 지정하지 않으면 기본값이 0이고, 이때는 fall 횟수만큼 연속 실패하면 인스턴스가 FAULT 상태로 빠져 VIP를 완전히 내려놓는다. 그러면 BACKUP이 VIP를 확실하게 가져간다.

반대로 weight에 값을 주면 동작이 달라진다. 예를 들어 weight -20이면 스크립트 실패 시 priority가 150에서 130으로만 떨어진다. BACKUP의 100보다 여전히 높아서 절체가 안 일어난다. 이게 초보자가 자주 걸리는 함정이다. 확실하게 넘기고 싶으면 weight를 빼서 FAULT 방식으로 가거나, BACKUP priority 아래로 떨어질 만큼 충분히 큰 음수를 줘야 한다. 나는 단순하게 weight 없이 FAULT로 넘기는 쪽을 권한다.

6. 실행과 절체 확인

두 노드에서 데몬을 올린다.

sudo systemctl enable --now keepalived
sudo systemctl status keepalived

MASTER에서 VIP가 붙었는지 확인한다. VIP는 별도 인터페이스로 안 보이고 eth0의 secondary 주소로 붙는다.

ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
    inet 10.0.0.11/24 brd 10.0.0.255 scope global eth0
    inet 10.0.0.100/24 scope global secondary eth0

BACKUP에서 같은 명령을 치면 10.0.0.100이 안 보여야 정상이다. 둘 다 보이면 split-brain이니 7절로 간다.

이제 MASTER의 Nginx를 죽여서 절체를 확인한다.

# node1 (MASTER)
sudo systemctl stop nginx

# node2 (BACKUP) 로그
journalctl -u keepalived -f
keepalived[1234]: (VI_1) Backup received priority 0 advertisement
keepalived[1234]: (VI_1) Entering MASTER STATE

node2에서 ip addr show eth0를 다시 보면 VIP가 넘어와 있다. 외부에서 curl http://10.0.0.100/를 계속 때려 보면 절체 순간 짧게 끊겼다가 다시 응답한다. 끊기는 시간은 환경에 따라 다르지만 대략 몇 초 수준이다.

node1의 Nginx를 다시 살리면 priority가 회복되면서 VIP가 되돌아온다. 이 되돌림이 싫으면 nopreempt를 쓴다(7절).

7. split-brain 방지

split-brain은 두 노드가 서로의 VRRP advertisement를 못 받아서 양쪽 다 자기가 MASTER라고 착각하는 상태다. VIP가 두 노드에 동시에 붙어 ARP가 충돌하고 연결이 널뛴다. 원인은 대부분 방화벽이 VRRP 패킷을 막거나, 멀티캐스트가 차단된 네트워크다.

방화벽에서 VRRP 허용

VRRP는 IP 프로토콜 112번을 쓴다. TCP/UDP 포트가 아니라 프로토콜 자체를 열어야 한다.

# firewalld (RHEL 계열)
sudo firewall-cmd --permanent --add-protocol=vrrp
sudo firewall-cmd --reload

# 멀티캐스트 주소 224.0.0.18 이 기본 대상이다
# 확인: 상대 노드에서 VRRP 패킷이 잡히는지
sudo tcpdump -i eth0 proto 112 -n

tcpdump에 상대 노드발 패킷이 안 잡히면 그 구간이 막힌 것이다.

멀티캐스트가 막힌 환경은 unicast로

AWS 같은 클라우드나 일부 스위치는 멀티캐스트를 막는다. 이때는 advertisement를 상대 노드로 직접 unicast 전송하도록 바꾼다. node1 설정에 다음을 추가한다.

    unicast_src_ip 10.0.0.11
    unicast_peer {
        10.0.0.12
    }

node2는 unicast_src_ip 10.0.0.12, peer는 10.0.0.11로 반대로 적는다. unicast를 쓰면 멀티캐스트 그룹에 의존하지 않아 split-brain 위험이 크게 준다.

불필요한 되돌림 막기

MASTER가 복구될 때마다 VIP가 되돌아오면 그 순간 또 짧게 끊긴다. 장애가 잦으면 flapping이 된다. BACKUP이 MASTER를 물고 있을 때 굳이 되뺏지 않게 하려면 BACKUP 쪽 인스턴스에 nopreempt를 넣고 state를 BACKUP으로 둔다.

vrrp_instance VI_1 {
    state BACKUP
    nopreempt
    priority 100
    ...
}

8. 오류와 해결책

둘 다 MASTER가 된다

virtual_router_id나 auth_pass가 노드 간에 다르면 서로를 같은 그룹으로 인식하지 못한다. 두 값을 양쪽에서 똑같이 맞춘다. 그래도 그러면 방화벽에서 프로토콜 112가 막힌 것이니 7절대로 tcpdump proto 112로 확인한다.

Nginx가 죽어도 절체가 안 된다

vrrp_script에 weight를 줬는데 그 값이 작아서 priority가 BACKUP 위에 머물러 있는 경우다. 5절대로 weight를 빼서 FAULT 방식으로 바꾼다. 스크립트가 실행되는지 자체가 의심되면 로그를 본다.

journalctl -u keepalived | grep -i script
# 스크립트가 안 돌면 실행 권한(chmod +x)이나 경로를 먼저 의심한다

HAProxy에서 VIP 바인딩 실패

HAProxy를 특정 VIP에 직접 bind 10.0.0.100:80으로 묶으면, VIP가 없는 BACKUP 노드에서 기동 자체가 실패한다. 로컬에 없는 IP에도 바인딩할 수 있게 커널 옵션을 켠다.

echo 'net.ipv4.ip_nonlocal_bind = 1' | sudo tee /etc/sysctl.d/99-haproxy.conf
sudo sysctl -p /etc/sysctl.d/99-haproxy.conf

Nginx를 listen 80처럼 0.0.0.0으로 열면 이 옵션은 필요 없다.

클라우드에서 VIP로 트래픽이 안 온다

AWS 같은 환경은 VRRP로 VIP를 노드에 붙여도 클라우드 네트워크가 임의 IP의 ARP를 신뢰하지 않아 외부 트래픽이 도달하지 않을 수 있다. 이때는 notify_master 스크립트에서 AWS CLI로 secondary private IP나 EIP를 현재 MASTER 노드로 재할당하는 방식을 쓴다.

# notify.sh 안에서 (master 로 전환될 때)
aws ec2 assign-private-ip-addresses \
  --network-interface-id eni-xxxx \
  --private-ip-addresses 10.0.0.100 \
  --allow-reassignment

이 경우 VRRP는 어느 노드가 MASTER인지 선출하는 역할만 하고, 실제 IP 이동은 클라우드 API가 담당한다.

마무리

Keepalived는 별도 하드웨어나 클라우드 LB 없이 소프트웨어만으로 L4 이중화를 만든다. 핵심은 세 가지다. priority로 MASTER/BACKUP을 나누고, vrrp_script와 track_script로 Nginx 상태를 직접 감시하며, 방화벽에서 프로토콜 112를 열고 필요하면 unicast로 split-brain을 막는다. 같은 서브넷의 온프레미스라면 위 설정 그대로 동작하고, 클라우드라면 unicast와 notify 스크립트를 얹어 API로 IP를 옮기면 된다.

자주 묻는 질문

VRRP는 어떤 포트를 열어야 하나?

VRRP는 TCP/UDP 포트가 아니라 IP 프로토콜 번호 112번을 사용한다. 방화벽에서 포트가 아니라 프로토콜 자체를 허용해야 한다. firewalld는 firewall-cmd --add-protocol=vrrp로 열고, 기본 멀티캐스트 대상 주소는 224.0.0.18이다.

Nginx 프로세스만 죽었을 때도 절체되게 하려면?

Keepalived 기본 동작은 노드나 네트워크 장애만 감지한다. vrrp_script로 Nginx 상태를 확인하는 스크립트를 등록하고 track_script로 인스턴스에 연결해야 프로세스 단위 장애도 절체된다. weight를 지정하지 않으면 fall 횟수만큼 실패 시 FAULT 상태로 빠져 VIP를 넘긴다.

두 노드가 모두 MASTER가 되는 split-brain은 왜 생기나?

노드끼리 VRRP advertisement를 주고받지 못할 때 서로 상대가 죽었다고 판단해 둘 다 MASTER가 된다. 대부분 방화벽이 프로토콜 112를 막았거나 멀티캐스트가 차단된 경우다. tcpdump -i eth0 proto 112로 패킷 도달을 확인하고, 멀티캐스트가 막힌 환경은 unicast_peer로 전환한다.

MASTER가 복구될 때 VIP가 자동으로 되돌아오는 것을 막으려면?

BACKUP 인스턴스에 nopreempt를 추가하고 state를 BACKUP으로 둔다. 그러면 우선순위가 높은 원래 MASTER가 복구돼도 현재 VIP를 쥔 노드가 계속 유지해 잦은 절체(flapping)를 막는다.

AWS 같은 클라우드에서도 VRRP VIP가 그대로 동작하나?

그대로는 어렵다. 클라우드 네트워크는 멀티캐스트와 임의 IP의 ARP 광고를 막는 경우가 많다. unicast_peer로 advertisement를 직접 보내고, notify_master 스크립트에서 AWS CLI로 secondary private IP나 EIP를 현재 MASTER로 재할당하는 방식을 쓴다. VRRP는 선출만, IP 이동은 클라우드 API가 담당한다.

관련 글

댓글 0

로그인 후 댓글을 남길 수 있습니다.

아직 댓글이 없습니다.