dev/Cloud & Infra

High Availability 를 위한 VRRP와 keepalived (2) - keepalived

lugi 2019. 5. 2. 00:53

https://springboot.cloud/23 의 글에서 고가용성(HA)의 개념과 이를 달성하기 위한 프로토콜인 VRRP 에 대해서 살펴보았다. 운영 중인 장비에서 문제가 생겼을 때, 자동으로 예비 장비로 전환하여 서비스의 지속성을 유지 시켜주는 기능을 장애극복(failover)라고 한다.

 

이번 글에서는 리눅스에서 failover 를 위해서 사용하는 keepalived 에 대해서 살펴볼 것이다.

 

keepalived란?

공식 홈페이지의 설명에 따르면 keepalived는 C로 작성된 로드밸런싱 및 고가용성을 제공하는 프레임워크이다.

로드밸런싱을 하기 위해서는 LVS(Linux Virtual Server)의 구성 요소인 IPVS를 사용한다. keepalived와 조합하여 HAPROXY 와 같은 로드밸런서를 사용하여 L7 로드밸런싱을 할 수도 있지만, keepalived에서 제공하는 로드밸런싱 기능을 사용하여 L4 로드밸런싱을 할 수도 있다. 무턱대고 처음 keepalived 를 사용했을 때는 로드밸런서 설정까지 함께하고 있는 설정 파일을 보고 헷갈렸던 경험이 있는데, 고가용성 달성을 위해서는 앞 글에서 설명한 VRRP 프로토콜을 사용하며, 이와 관련된 부분은 로드밸런서 설정을 위한 LVS와는 독립적이다.

 

keepalived 의 구조는 아래와 같다.

출처 : https://www.keepalived.org/doc/software_design.html

로드밸런싱 쪽에서 사용되는 LVS(IPS)와 NAT, Masquerading에 쓰이는 Netfilter, VIP 할당 및 해제에 쓰이는 Netlink, VRRP Advertisement 패킷 전송을 위해 사용되는 Multicast와 같은 커널 컴포넌트를 사용한다.

처음 keepalived를 쓰면서 헷갈렸던 점이 Virtual-IP가 가상의 장치에 할당되어 사용된다는 것은 알겠는데, /etc/sysconfig/network-scripts에 그것을 설정 해 주는 예제도 있고, 그렇지 않은 예제도 있다는 점이었다. keepalived의 고가용성은 자신의 IP 주소와는 별개로 VIP를 설정해두고, 문제가 생겼을 때는 이 VIP를 다른 곳으로 인계하여 같은 IP주소를 통해서 서비스가 지속될 수 있도록 해주는 것이 핵심인데, keepalived가 VIP의 할당, 해제를 자동으로 해 주기 때문에 keepalived를 쓰면서 그 부분을 특별히 설정 해 주지 않아도 사용에는 별 문제가 없다.

 

keepalived 는 상태 체크를 위해 여러가지 방법을 구현하고 있다. 물론 VRRP를 통해서 물려 있는 서버들이 동작하고 있는지 상태 확인이 가능하지만, 이는 해당 VIP가 물린 장치의 status 가 down 임을 감지했을 때를 의미한다.

 

반면에 넓은 의미에서 서비스가 장애라는 것은, 네트워크 장치가 죽었거나 동작하지 못 했을 때일 수도 있고, 해당 네트워크와의 통신이 실패하는 때일 수도 있고, 장치 및 네트워크는 동작하고 있지만, 요청을 Listen 하고 있는 서비스가 죽었거나, 살아 있음에도 불구하고 이상동작을 보이는 때일 수도 있다. 그렇기 때문에 HEALTH CHECK를 위해서는 다양한 경우에 대한 대비가 있어야 한다.

 

일반적인 관점에서 장애인지 검출할 수 있는 방법은 다음과 같다.

1. ICMP (L3)

흔히들 ping 명령어를 사용해서 날리는 메시지이다. 네트워크 구간이 정상적이고, 해당 서버가 살아있다면 ICMP echo 요청에 대해 응답이 돌아올 것이다. 그러나 ICMP 요청이 웜 바이러스의 침투 방법으로 악용된 사례 등으로 인해 보안 목적으로 ICMP 핑을 차단하는 경우라거나, 서버는 살아 있지만, 포트 보안 정책, 혹은 내부의 서비스의 down 등으로 서비스가 장애인 경우를 검출하지는 못 하기 때문에 다른 방법이 필요하다.

2. TCP 요청(L4)

흔히 서비스를 올린 후, 방화벽 개통 작업을 수행한 후 정상적으로 방화벽이 개통되었는지 확인하기 위해서 telnet [host] [port] 명령어를 날려서 정상인지 체크하기도 한다. 이는 TCP 요청이 정상적으로 응답하는지를 확인하는 방법이다. 서비스가 살아 있는지는 확인이 가능하지만, 서비스가 살아 있어 port Listen은 하고 있지만, 서버 설정이나 프로그램적으로 오류가 있어 Status 500 을 반환하는 경우 등에는 정확한 확인이 불가능한 단점이 있다.

3. HTTP 요청(L7)

실제 서비스가 올라가 있는 요청에 HEALTH CHECK를 위한 endpoint를 두고 해당 서비스에 요청을 날려 200 OK 가 날아오는지 확인하는 것이다. 서비스를 구동시킨 후 실제로 제대로 올라갔는지 확인하기 위해 curl 등으로 확인하는 것과 같다. 실제 서비스가 살아있는 것을 확인할 수 있지만, HTTP 자체가 위의 경우들보다는 좀 더 무겁기 때문에 이 부분에 대해서는 고려가 필요하다.

 

keepalived는 HEALTH CHECK를 위해 아래와 같은 방법 지원한다.

1. TCP_CHECK : 비동기 / timed-out TCP 요청을 통해 장애를 검출하는 방식이다. 응답하지 않는 서버는 서버 pool 에서 제외한다.

2. HTTP_GET ; HTTP GET 요청을 날려서 서비스의 정상 동작을 확인한다

3. SSL_GET : 위와 같지만 HTTPS 기반이다.

4. MISC_CHECK : 시스템상에서 특정 기능을 확인하는 script를 돌려 그 결과가 0인지 1인지를 가지고 장애를 검출하는 방법이다. 네트워크가 아니라 시스템상에서 돌고 있는 서비스의 정상 동작을 확인하는데 유용하다.

위의 4가지 방법은 메뉴얼상으로는 keepalived 1.3.x 에서 지원하는 방법이며, 2.0.x 의 메뉴얼상에는 SMTP_CHECK, DNS_CHECK, BFD_CHECK의 검출 방법이 추가로 들어가 있다.

 

단 혼동하지 말아야 할 부분은 위의 HEALTH CHECK 옵션은 keepalived 의 LVS Configuration 에 포함된 virtual server 설정 시에 사용하는 방법이며, 위와 관련된 설정은 VRRP를 통한 failover 에서 사용하는 것이 아니라 keepalived를 L4 레이어의 로드밸런서로 사용할 때 설정하는 내용이다.

keepalived 를 VRRP 기반의 failover 에 사용할 때는 앞 글에서 설명한 VRRP 프로토콜의 멀티캐스트를 통해 이러한 HEALTH CHECK가 이루어진다. 물론 VRRP 프로토콜만으로는 서비스가 정상적으로 동작하고 있는 상태까지는 검출하지 못 하기 때문에 VRRPD Configuration 의 vrrp_script 라는 설정을 사용해서 스크립트를 사용해서 서비스의 정상 가동 여부를 확인할 수 있다.

 

VIP 인계를 이용한 고가용성 달성

VRRP 및 keepalived를 이용한 고가용성 달성의 핵심은 장비가 자신의 고유IP 외의 VIP를 두고, 장애가 발생했을 경우 이VIP를 다른 장비로 인계하여 같은 주소로 계속 서비스가 지속될 수 있게 한다는 점이다.

여기서 고려해주어야 하는 상황은 IP를 이용한 통신은 어떤 경우에도 무조건 달성되는 것이 아니라는 점이다. (바로 뒤의 서술에 대해서는 네트워크 장비는 매우 복합적인 구조와 기능을 가지지만 기본적인 내용으로 설명한다) 특정 IP를 목적으로 한 요청 프레임이 L2 레이어의 스위치에 도달했을 때 L2 레이어의 스위치는 들어온 요청에 대해 학습된 MAC ADDRESS를 기준으로 하여 목적지 MAC이 존재하는 포트로 요청을 전달하거나, 학습되지 않은 MAC ADDRESS 로부터 요청이 들어올 경우 프레임이 들어온 포트를 제외한 다른 모든 포트로 확인 요청을 날린다. (이를 flooding 이라고 한다)

그렇기 때문에 VIP를 인계할 경우 해당 VIP가 정확한 MAC ADDRESS와 매핑될 수 있도록 하는 과정이 필요하다. 이를 위해 가상의 MAC 주소를 VIP와 맵핑시키고 이를 인계하거나, VIP를 인계받은 대상이 자신의 IP와 MAC ADDRESS를 전파하는 GAPR 요청을 날려주어야 한다. 

24시간 365일 서버/인프라를 지탱하는 기술(제이펍)에 따르면 keepalived는 RFC3768을 정확하게 구현하고 있지 않기 때문에 VMAC 을 사용하지 않고 GARP 의 지연 요청을 사용한다고 나와있는데, 이 책이 2009년에 국내에 출간된 점을 감안하면 약간의 내용 변경이 있는 것 같다. keepalived 1.3.x (2016년 11월 출시) 버전의 메뉴얼에 따르면, 이 패치를 한 커널에서는 VMAC을 이용한 VRRP를 사용할 수 있다고 언급되어 있다 (현재 가장 최신 버전은 2.0.15)

 

keepalived를 이용한 고가용성 달성

그럼 이제 keepalived를 이용하여 HA를 구성해보자.

일단 환경은 다음과 같다

VM1 : 192.168.0.111

VM2 : 192.168.0.112

VIP : 192.168.0.110

 

keepalived를 VM1, VM2 에 다운 받고 압축을 풀어주자. (현재 시점 최신 버전 2.0.15 설치)

wget https://www.keepalived.org/software/keepalived-2.0.15.tar.gz
tar -xvf keepalived-2.0.15.tar.gz

현 시점 기준으로 공식 홈페이지의 메뉴얼은 설정 부분만이 나와 있을 뿐 다른 부분이 상당히 부실하다. (1.3.x 버전의 메뉴얼 사이트가 존재하긴 한다) 설치를 위해 필요한 의존성이 무엇인지 확인을 하기 위해서는 압축이 풀린 페이지 안의 INSTALL 파일을 살펴보자.

 

이 포스트는 CentOS7 을 기준으로 작성되어 있으므로 RedHat Based systems 부분을 참고할 것인데, 각 배포판에 따라 설치해야 하는 의존성에 대해서는 INSTALL 파일에 정리가 되어 있다. Redhat 기준으로 의존성은 다음과 같다.

- 빌드 용도

make

autoconf automake (tarball 이 아니라 git 소스에서 빌드하는 경우)

- 설치해야 하는 라이브러리

openssl-devel libn13-devel ipset-devel iptables-devel

- magic file 식별 용도

file-devel

- SNMP 지원 용도

net-snmp-devel

- DBUS 지원 용도

glib2-devel

- JSON 지원 용도

json-c-devel

- PCRE 지원 용도

pcre2-devel

- nftables 지원 용도

libnftnl-devel, libmnl-devel

 

기본적으로 필요한 의존성들만 설치 해 보자.

yum install openssl-devel libnl3-devel ipset-devel iptables-devel

keepalived 를 빌드하고 시스템에 설치하자

./configure
make
make install

keepalived 의 default configuration 디렉토리는 /etc/keepalived/keepalived.conf 이다. 이곳에 설정 파일을 만들자, 만약 별도의 설정 파일을 지정하기 위해서는 -f 옵션으로 설정 파일 이름을 지정할 수 있다.

mkdir -p /etc/keepalived
vi keepalived.conf

keepalived 의 설정 파일의 구조에 대해서 조금 살펴보면 다음과 같다.

- GLOBAL 설정 : 전역 설정 및 Linkbeat 인터페이스, 정적인 트래킹 그룹, 주소, 경로 규칙을 정의한다.

- BFD 설정 : 인접한 두 시스템의 포워딩 평면간에 양방향 전송 경로를 이용해서 오류를 검출하는 프로토콜이라고 한다, RFC5880 에 스펙이 정의되어 있는 듯하며, keepalived 설명에 따르면 BFD를 이용하여 VRRP의 상태전이를 좀 더 빠르게 할 수 있다고 한다.

- VRRPD 설정 - HA 설정을 할 때는 이 부분을 주로 설정한다.

- LVS 설정 - L4레이어 수준에서 로드밸런싱을 keepalived로 하려면 이 부분을 보면 된다.

 

이번 포스트는 keepalived 를 VRRP를 통한 HA에만 사용할 것이다.

/usr/local/etc/keepalived/samples에 가보면 

[root@vm1 samples]# ls -al
total 116
drwxr-xr-x. 2 root root 4096 May  1 14:29 .
drwxr-xr-x. 3 root root   44 May  1 13:31 ..
-rw-r--r--. 1 root root 1745 May  1 13:31 client.pem
-rw-r--r--. 1 root root  245 May  1 13:31 dh1024.pem
-rw-r--r--. 1 root root  543 May  1 13:31 keepalived.conf.conditional_conf
-rw-r--r--. 1 root root  433 May  1 13:31 keepalived.conf.fwmark
-rw-r--r--. 1 root root  675 May  1 13:31 keepalived.conf.HTTP_GET.port
-rw-r--r--. 1 root root  736 May  1 13:31 keepalived.conf.inhibit
-rw-r--r--. 1 root root  957 May  1 13:31 keepalived.conf.IPv6
-rw-r--r--. 1 root root  595 May  1 13:31 keepalived.conf.misc_check
-rw-r--r--. 1 root root  510 May  1 13:31 keepalived.conf.misc_check_arg
-rw-r--r--. 1 root root 2467 May  1 13:31 keepalived.conf.quorum
-rw-r--r--. 1 root root  910 May  1 13:31 keepalived.conf.sample
-rw-r--r--. 1 root root 2763 May  1 13:31 keepalived.conf.SMTP_CHECK
-rw-r--r--. 1 root root 1567 May  1 13:31 keepalived.conf.SSL_GET
-rw-r--r--. 1 root root  909 May  1 13:31 keepalived.conf.status_code
-rw-r--r--. 1 root root  736 May  1 13:31 keepalived.conf.track_interface
-rw-r--r--. 1 root root  877 May  1 13:31 keepalived.conf.virtualhost
-rw-r--r--. 1 root root 1070 May  1 13:31 keepalived.conf.virtual_server_group
-rw-r--r--. 1 root root 1445 May  1 13:31 keepalived.conf.vrrp
-rw-r--r--. 1 root root 3019 May  1 13:31 keepalived.conf.vrrp.localcheck
-rw-r--r--. 1 root root 1056 May  1 13:31 keepalived.conf.vrrp.lvs_syncd
-rw-r--r--. 1 root root  975 May  1 13:31 keepalived.conf.vrrp.routes
-rw-r--r--. 1 root root  578 May  1 13:31 keepalived.conf.vrrp.rules
-rw-r--r--. 1 root root 1145 May  1 13:31 keepalived.conf.vrrp.scripts
-rw-r--r--. 1 root root  591 May  1 13:31 keepalived.conf.vrrp.static_ipaddress
-rw-r--r--. 1 root root 1740 May  1 13:31 keepalived.conf.vrrp.sync
-rw-r--r--. 1 root root  802 May  1 13:31 root.pem
-rw-r--r--. 1 root root  322 May  1 13:31 sample.misccheck.smbcheck.sh
-rw-r--r--. 1 root root 2588 May  1 13:31 sample_notify_fifo.sh

참고할 수 있는 예제들이 참 많다. 이 중에 우리는 keepalived.conf.vrrp 를 참고해보자.

 

해당 파일의 내용을 살펴보자

1    ! Configuration File for keepalived
2
3    global_defs {
4        notification_email {
5            acassen
6        }
7        notification_email_from Alexandre.Cassen@firewall.loc
8        smtp_server 192.168.200.1
9        smtp_connect_timeout 30
10       router_id LVS_DEVEL
11    }
12
13    vrrp_instance VI_1 {
14        state MASTER
15        interface eth0
16        garp_master_delay 10
17        smtp_alert
18        virtual_router_id 51
19        priority 100
20        advert_int 1
21        authentication {
22            auth_type PASS
23            auth_pass 1111
24        }
25        virtual_ipaddress {
26            192.168.200.16
27            192.168.200.17
28            192.168.200.18
29
30            # optional label. should be of the form "realdev:sometext" for
31            # compatibility with ifconfig.
32            192.168.200.18 label eth0:1
33        }
34     }
... 후략

3~11 라인은 GLOBAL 설정이다.

GLOBAL 설정에서는 통보가 갈 이메일 주소 등의 정보를 입력한다.

 

13~34 라인이 Virtual Router 에 관한 설정이다. (VRRP instance의 이름이 VI_1 이다)

14라인 의 state는 MASTER or BACKUP이 될 수 있으며

15라인의 interface 는 현재 시스템에서 요청을 받아들이는 네트워크 인터페이스의 이름을 입력한다.

16라인의 garp_master_delay 10은 keepalived 의 경우 MASTER를 인계 받은 후 스위치의 MAC 학습 상태를 갱신하기 위해 GARP 요청을 송신하지만, 여러가지 이유로 갱신이 지연되거나 실패할 여지도 있다, 이 경우 좀 더 확실한 MAC 학습 및 ARP엔트리 갱신을 위해 delay가 지난 후 다시 GARP 요청을 송신하도록 하는 옵션이다.

17 라인의 smtp_alert은 global 에서 설정한 e-mail 정보를 통해 경보를 발신할 것인지 여부이며

18 라인은 이 VRRP 인스턴스가 참여할 가상 라우터의 id 이다. 같은 그룹이라면 이 id가 같아야 한다.

19 라인은 이 VRRP 인스턴스의 우선 순위이며, 숫자가 클수록 우선 순위가 높다, MASTER가 down되었을 경우 priority가 높은 순서대로 MASTER로 동작한다.

20 라인의 adver_int 는 ADVERTISEMENT를 송신할 주기이며 초 단위이다. 소수점도 지정 가능하다.

21~24 라인은 동일한 라우터 간에 인증을 위해 simple text로 된 비밀번호를 사용하는 것인데, 이 경우 VRRP에 참여하는 라우터들은 모두 동일한 인증 설정을 가져야 한다, 다만 앞의 포스트에서 설명한 바와 같이 VRRP ver.2 의 경우에는 이 옵션이 removed 되었으므로 VRRP ver.2 에서는 제거하여도 동작에는 무방한 옵션이다.

25~32가 HA의 핵심으로 keepalived 가 인계할 VIP 이다. keepalived 의 MASTER는 주기적으로 ADVERTISEMENT를 송신하며, 이를 수신하지 못할 경우 BACKUP이 자동으로 MASTER로 행동한다. 그 때 BACKUP 은 해당 virtual_ipaddress 를 자신의 VIP로 등록하고, GARP를 통해 이를 알린다.

 

처음에 약간 헷갈렸던 것은 global_defs 에 존재하는 router_id 와 vrrp_instance 에 존재하는 virtual_router_id의 차이였는데, router_id는 메뉴얼의 설명에 따르면, String identifying the machine (doesn't have to be hostname) 으로 machine을 구분하는 이름이므로 각 VM 노드마다 다르게 설정하여야 한다. 설정하지 않을 경우 default 값은 local hostname이 되므로 설정하지 않아도 무방하다. virtual_router_id는 0~255사이의 VRRP의 VRID로 사용되는 값이며, 이 vrrp_instance가 참여할 가상 라우터의 id이므로,  동일 그룹으로 묶인 노드 간에는 이 값은 같아야 한다. 네트워크상에서 동작하고 있는 VRRP 라우터가 있을 경우 해당 라우터의 MAC 어드레스의 맨 끝자리 값으로 이 값의 16진수 값이 사용된다. 그런데 RFC3768 의 스펙에서 이 값은 1~255라고 나와 있는데 keepalived 의 메뉴얼에는 0~255 라고 나와 있어서 약간 차이가 있다.

 

이를 바탕으로 실제 사용할 매우 간단한 conf 파일을 만들어보자.

만들기 전 이 포스트가 사용한 구성에 대해서 다시 한 번 짚어보자면

VM1 은 192.168.0.111

VM2 는 192.168.0.112

VIP는 192.168.0.110 을 사용할 것이다.

VM1,2 의 IP는 eth1 에 물려 있다.

 

VM1 의 keepalived.conf

global_defs {
   router_id ROUTER_VM1
}

vrrp_instance VI_1 {
    state MASTER
    interface eth1
    virtual_router_id 99
    priority 150
    virtual_ipaddress {
        192.168.0.110
    }
}

VM2 의 keepalived.conf

global_defs {
   router_id ROUTER_VM2
}

vrrp_instance VI_1 {
    state BACKUP
    interface eth1
    virtual_router_id 99
    priority 100
    virtual_ipaddress {
        192.168.0.110
    }
}

기본값을 사용할 수 있는 부분은 배제하고 필수적인 최소한의 내용만을 가지고 구성해 보았다. 이런 저런 예제를 보면서, 이 값이 무엇인지, 넣어야 하는지 몰라서 혼란스러웠던 경험이 있어서 최소한의 내용만 가지고 구성해보았는데, 더 자세한 내용은 공식 홈페이지의 Configuration을 참조하거나, samples 디렉토리의 다른 파일을 참고하면 도움이 된다.

 

설정을 완료했으면 VM1, VM2에서 keepalived 를 기본 서비스로 등록하고 시작해보자

systemctl enable keepalived
systemctl start keepalived

실행 전 후 VM1 의 네트워크 장치 현황은 다음과 같다

#keepalived 구동 전 VM1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 08:00:27:08:86:d9 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.111/24 brd 192.168.0.255 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe08:86d9/64 scope link
       valid_lft forever preferred_lft forever

#keepalived 구동 후 VM1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 08:00:27:08:86:d9 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.111/24 brd 192.168.0.255 scope global eth1
       valid_lft forever preferred_lft forever
    inet 192.168.0.110/32 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe08:86d9/64 scope link
       valid_lft forever preferred_lft forever

192.168.0.110 이 VIP로 eth1 에 추가 되었다.

 

이제 이 IP로 ping을 날려보자.

ping 192.168.0.110

Ping 192.168.0.110 32바이트 데이터 사용:
192.168.0.110의 응답: 바이트=32 시간<1ms TTL=64
192.168.0.110의 응답: 바이트=32 시간<1ms TTL=64
192.168.0.110의 응답: 바이트=32 시간<1ms TTL=64
192.168.0.110의 응답: 바이트=32 시간<1ms TTL=64

192.168.0.110에 대한 Ping 통계:
    패킷: 보냄 = 4, 받음 = 4, 손실 = 0 (0% 손실),
왕복 시간(밀리초):
    최소 = 0ms, 최대 = 0ms, 평균 = 0ms

잘 가네...

 

이제 VM1 을 halt로 죽여보자. 그리고 해당 아이피에 ping을 날려보자.

ping 192.168.0.110

Ping 192.168.0.110 32바이트 데이터 사용:
192.168.0.110의 응답: 바이트=32 시간<1ms TTL=64
192.168.0.110의 응답: 바이트=32 시간<1ms TTL=64
192.168.0.110의 응답: 바이트=32 시간<1ms TTL=64
192.168.0.110의 응답: 바이트=32 시간<1ms TTL=64

192.168.0.110에 대한 Ping 통계:
    패킷: 보냄 = 4, 받음 = 4, 손실 = 0 (0% 손실),
왕복 시간(밀리초):
    최소 = 0ms, 최대 = 0ms, 평균 = 0ms

VM1이 죽었음에도 불구하고 여전히 잘 간다.

그럼 VM2 로 가서 살펴보자.

# VM1 halt 전 VM2
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 08:00:27:8f:dd:61 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.112/24 brd 192.168.0.255 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe8f:dd61/64 scope link
       valid_lft forever preferred_lft forever

# VM1 halt 후 VM2
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 08:00:27:8f:dd:61 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.112/24 brd 192.168.0.255 scope global eth1
       valid_lft forever preferred_lft forever
    inet 192.168.0.110/32 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe8f:dd61/64 scope link
       valid_lft forever preferred_lft forever

192.168.0.110 IP가 VM2 에 추가된 것을 확인할 수 있다. 이 때 VM2가 어떻게 동작했는지 service status를 살펴보자

#systemctl status keepalived
May 01 15:28:07 vm2 systemd[1]: Starting LVS and VRRP High Availability Monitor...
May 01 15:28:07 vm2 systemd[1]: PID file /var/run/keepalived.pid not readable (yet?) after start.
May 01 15:28:07 vm2 Keepalived[22477]: Starting VRRP child process, pid=22478
May 01 15:28:07 vm2 systemd[1]: Started LVS and VRRP High Availability Monitor.
May 01 15:28:07 vm2 Keepalived_vrrp[22478]: Registering Kernel netlink reflector
May 01 15:28:07 vm2 Keepalived_vrrp[22478]: Registering Kernel netlink command channel
May 01 15:28:07 vm2 Keepalived_vrrp[22478]: Opening file '/etc/keepalived/keepalived.conf'.
May 01 15:28:07 vm2 Keepalived_vrrp[22478]: Assigned address 192.168.0.112 for interface eth1
May 01 15:28:07 vm2 Keepalived_vrrp[22478]: Assigned address fe80::a00:27ff:fe8f:dd61 for interface eth1
May 01 15:28:07 vm2 Keepalived_vrrp[22478]: Registering gratuitous ARP shared channel
May 01 15:28:07 vm2 Keepalived_vrrp[22478]: (VI_1) removing VIPs.
May 01 15:28:07 vm2 Keepalived_vrrp[22478]: (VI_1) Entering BACKUP STATE (init)
May 01 15:28:07 vm2 Keepalived_vrrp[22478]: VRRP sockpool: [ifindex(3), family(IPv4), proto(112), unicast(0), fd(11,12)]
May 01 15:31:23 vm2 Keepalived_vrrp[22478]: (VI_1) Backup received priority 0 advertisement
May 01 15:31:23 vm2 Keepalived_vrrp[22478]: (VI_1) Receive advertisement timeout
May 01 15:31:23 vm2 Keepalived_vrrp[22478]: (VI_1) Entering MASTER STATE
May 01 15:31:23 vm2 Keepalived_vrrp[22478]: (VI_1) setting VIPs.
May 01 15:31:23 vm2 Keepalived_vrrp[22478]: Sending gratuitous ARP on eth1 for 192.168.0.110
May 01 15:31:23 vm2 Keepalived_vrrp[22478]: (VI_1) Sending/queueing gratuitous ARPs on eth1 for 192.168.0.110
May 01 15:31:23 vm2 Keepalived_vrrp[22478]: Sending gratuitous ARP on eth1 for 192.168.0.110
May 01 15:31:23 vm2 Keepalived_vrrp[22478]: Sending gratuitous ARP on eth1 for 192.168.0.110
May 01 15:31:23 vm2 Keepalived_vrrp[22478]: Sending gratuitous ARP on eth1 for 192.168.0.110
May 01 15:31:23 vm2 Keepalived_vrrp[22478]: Sending gratuitous ARP on eth1 for 192.168.0.110
May 01 15:31:28 vm2 Keepalived_vrrp[22478]: Sending gratuitous ARP on eth1 for 192.168.0.110
May 01 15:31:28 vm2 Keepalived_vrrp[22478]: (VI_1) Sending/queueing gratuitous ARPs on eth1 for 192.168.0.110
May 01 15:31:28 vm2 Keepalived_vrrp[22478]: Sending gratuitous ARP on eth1 for 192.168.0.110
May 01 15:31:28 vm2 Keepalived_vrrp[22478]: Sending gratuitous ARP on eth1 for 192.168.0.110
May 01 15:31:28 vm2 Keepalived_vrrp[22478]: Sending gratuitous ARP on eth1 for 192.168.0.110
May 01 15:31:28 vm2 Keepalived_vrrp[22478]: Sending gratuitous ARP on eth1 for 192.168.0.110

VM2 의 keepalived 가 시작되었을 때 이미 네트워크상에는 MASTER가 구동 중이므로 BACKUP 상태로 진입한다. MASTER가 HALT 된 시점에서 advertisement timeout이 감지되었으므로 VIP를 등록하고 GARP를 송신하여 자신의 VIP 및 MAC을 알린다. 이 GARP 요청이 등록된 시점부터 요청은 이 keepalived로 올 것이다. 23초에 GARP 를 요청하였는데 28초에 GARP 요청을 또 송신하였다. 이는 위에서 설명한 것과 같이 MAC주소가 확실히 등록되었는지 갱신하기 위해 garp_master_delay 가 지난 후 다시 GARP 요청을 송신했기 때문이다. garp_master_delay의 기본값이 5초이다.

 

그럼 이 시점에서 VM1이 살아난다면 어떻게 될까?

#VM1 기동 후 VM2
May 01 15:41:33 vm2 Keepalived_vrrp[22478]: (VI_1) Master received advert from 192.168.0.111 with higher priority 150...rs 100
May 01 15:41:33 vm2 Keepalived_vrrp[22478]: (VI_1) Entering BACKUP STATE
May 01 15:41:33 vm2 Keepalived_vrrp[22478]: (VI_1) removing VIPs.

#VM1 기동 후 VM1
May 01 15:42:08 vm1 systemd[1]: Starting LVS and VRRP High Availability Monitor...
May 01 15:42:08 vm1 Keepalived[861]: Starting Keepalived v2.0.15 (04/04,2019), git commit v2.0.14-53-g9de2817c+
May 01 15:42:08 vm1 Keepalived[861]: Running on Linux 3.10.0-693.17.1.el7.x86_64 #1 SMP Thu Jan 25 20:13:58 UTC 2018 ....10.0)
May 01 15:42:08 vm1 Keepalived[861]: Command line: '/usr/local/sbin/keepalived' '-D'
May 01 15:42:08 vm1 Keepalived[861]: Opening file '/etc/keepalived/keepalived.conf'.
May 01 15:42:08 vm1 systemd[1]: PID file /var/run/keepalived.pid not readable (yet?) after start.
May 01 15:42:08 vm1 systemd[1]: Started LVS and VRRP High Availability Monitor.
May 01 15:42:08 vm1 Keepalived[865]: Starting VRRP child process, pid=866
May 01 15:42:08 vm1 Keepalived_vrrp[866]: Registering Kernel netlink reflector
May 01 15:42:08 vm1 Keepalived_vrrp[866]: Registering Kernel netlink command channel
May 01 15:42:08 vm1 Keepalived_vrrp[866]: Opening file '/etc/keepalived/keepalived.conf'.
May 01 15:42:08 vm1 Keepalived_vrrp[866]: Assigned address 192.168.0.111 for interface eth1
May 01 15:42:08 vm1 Keepalived_vrrp[866]: Assigned address fe80::a00:27ff:fe08:86d9 for interface eth1
May 01 15:42:08 vm1 Keepalived_vrrp[866]: Registering gratuitous ARP shared channel
May 01 15:42:08 vm1 Keepalived_vrrp[866]: (VI_1) removing VIPs.
May 01 15:42:08 vm1 Keepalived_vrrp[866]: (VI_1) Entering BACKUP STATE (init)
May 01 15:42:08 vm1 Keepalived_vrrp[866]: VRRP sockpool: [ifindex(3), family(IPv4), proto(112), unicast(0), fd(11,12)]
May 01 15:42:08 vm1 Keepalived_vrrp[866]: (VI_1) received lower priority (100) advert from 192.168.0.112 - discarding
May 01 15:42:09 vm1 Keepalived_vrrp[866]: (VI_1) received lower priority (100) advert from 192.168.0.112 - discarding
May 01 15:42:10 vm1 Keepalived_vrrp[866]: (VI_1) received lower priority (100) advert from 192.168.0.112 - discarding
May 01 15:42:11 vm1 Keepalived_vrrp[866]: (VI_1) Receive advertisement timeout
May 01 15:42:11 vm1 Keepalived_vrrp[866]: (VI_1) Entering MASTER STATE
May 01 15:42:11 vm1 Keepalived_vrrp[866]: (VI_1) setting VIPs.
May 01 15:42:11 vm1 Keepalived_vrrp[866]: Sending gratuitous ARP on eth1 for 192.168.0.110
May 01 15:42:11 vm1 Keepalived_vrrp[866]: (VI_1) Sending/queueing gratuitous ARPs on eth1 for 192.168.0.110
May 01 15:42:11 vm1 Keepalived_vrrp[866]: Sending gratuitous ARP on eth1 for 192.168.0.110
May 01 15:42:11 vm1 Keepalived_vrrp[866]: Sending gratuitous ARP on eth1 for 192.168.0.110
May 01 15:42:11 vm1 Keepalived_vrrp[866]: Sending gratuitous ARP on eth1 for 192.168.0.110
May 01 15:42:11 vm1 Keepalived_vrrp[866]: Sending gratuitous ARP on eth1 for 192.168.0.110
May 01 15:41:38 vm1 Keepalived_vrrp[866]: Sending gratuitous ARP on eth1 for 192.168.0.110
May 01 15:41:38 vm1 Keepalived_vrrp[866]: (VI_1) Sending/queueing gratuitous ARPs on eth1 for 192.168.0.110
May 01 15:41:38 vm1 Keepalived_vrrp[866]: Sending gratuitous ARP on eth1 for 192.168.0.110
May 01 15:41:38 vm1 Keepalived_vrrp[866]: Sending gratuitous ARP on eth1 for 192.168.0.110
May 01 15:41:38 vm1 Keepalived_vrrp[866]: Sending gratuitous ARP on eth1 for 192.168.0.110
May 01 15:41:38 vm1 Keepalived_vrrp[866]: Sending gratuitous ARP on eth1 for 192.168.0.110

시작한 시점에서 VIP의 GARP 요청에 대해서 등록된 노드가 있기 때문에 BACKUP state로 진입한다. 그런데, 받은 advertisement 의 priority 가 자신의 priority (150)보다 낮은 100이기 때문에 MASTER 상태로 진입하고 GARP로 등록을 한다. 그리고 VM2 는 자신보다 높은 priority를 가진 노드의 advertisement 를 받았기 때문에 BACKUP 상태로 진입하고 VIP를 제거한다.

 

이러한 동작은 앞 포스트에서 설명한 Preempt_mode 가 True이기 때문이다. Preempt_mode 가 True일 경우에 Advertisement를 송신한 BACKUP의 priority가 기존 MASTER 보다 높을 경우, 현재의 MASTER를 밀어내고 자신이 MASTER를 선점한다. keepalived 에서는 nopreempt 속성을 지정하여 BACKUP이 선점을 허용하지 않도록 지정할 수 있다. 만약 MASTER 의 상태가 불안정하여 UP & DOWN 을 반복하고 있다면 MASTER의 줬다 빼앗기가 계속 발생할 것이므로 이럴 때는 BACKUP 이 선점을 허용하지 않도록 지정하는 것이 의미가 있다.

 

이상으로 keepalived의 간단한 소개 및 이를 이용해 HA를 구성하는 방법을 알아보았다. 생각보다 간단한데 쓸데 없이 많이 적었다는 생각도 들지만... 길게 풀어써야 내 기억에도 잘 남는 버릇이 있어서 그런 것이니 이해를 부탁드린다!

 

- 참고자료

24시간 365일 서버/인프라를 지탱하는 기술 (제이펍)

https://www.keepalived.org/doc/index.html

https://www.keepalived.org/manpage.html#