HAProxy 설치 및 로드밸런싱 설정 - MySQL 로드밸런싱 및 Client HA 구성

Share

Last Updated on 10월 30, 2023 by Jade(정현호)

안녕하세요 
이번 포스팅에서는 L4, L7 기능 및 고가용성 기능을 지원하는 오픈소스 솔루션인 HAProxy 에 대해서 설치 및 설정에 관하여 확인해 보겠습니다. 

What is HAProxy

HAProxy 는 하드웨어 기반의 L4/L7 스위치를 대체하기 위한 오픈소스 소프트웨어 솔루션으로 TCP 및 HTTP 기반 애플리케이션을 위한 고가용성, 로드 밸런싱 및 프록시 기능을 제공하는 매우 빠르고 안정적인 무료 Reverse 프록시입니다. 

설치가 쉽고 빠르며, 로드밸런싱 및 고가용성 구성을 위한 다양한 기능을 제공하고 있기 때문에 많은 용도로 사용되고 있습니다.

Proxy(프록시) 는 대리 또는 대신의 의미로 클라이언트가 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해주는 기술로써 프록시 서버는 클라이언트와 프록시 서버의 뒷단 서버의 입장에서 볼 때 서로 반대의 역할을 하게 되는 것처럼 보이게 됩니다.

클라이언트가 프록시 서버를 바라보면 프록시가 서버와 같이 동작을 하게 되는 것이며, 반대로 서버에서 프록시를 바라본다면 프록시는 클라이언트 와 같이 동작을 하게 되는 것입니다.

Proxy 서버는 동작 방식에 따라 크게 두가지로 나누어 지게 됩니다.
하나는 Forward proxy 이며, 하나는 Reverse Proxy입니다.

해당 포스팅에서는 Proxy 자체의 내용보다는 HAProxy에 관한 내용을 집중되어 있어서 프록시 종류에 관한 설명은 아래 포스팅이나 기타 다른 기술 블로그에서 확인 해보셔도 좋을 것 같습니다.

      

로드 밸런싱

HAProxy 에 대해서 얘기하기전에 로드밸런싱과 고가용성(HA) 에 대해서 간략하게 설명하도록 하겠으며 내용은 digitalocean.com 의 내용을 참고/번역한 내용입니다.


로드 밸런싱 없음

로드 밸런싱이 없는 간단한 웹 애플리케이션 환경은 아래와 같은 구성으로 이루어 지게 됩니다.

이 예제에서는 사용자가 웹 서버에 직접 연결을 하게 되며 로드 밸런싱은 없습니다.

단일 웹 서버가 문제가 생기게 되면 사용자는 더 이상 웹 서버에 엑세스 할 수 없게 됩니다. 또는 동시에 서버를 여러 사용자가 엑세스를 하려고 할 경우에도 요청한 로드를 다 처리할 수 없어서 속도가 느려지거나 서비스가 되지 않는 문제가 발생할 수도 있습니다.


Layer 4(L4) 로드 밸런싱

제목의 의미와 마찬가지로 OSI 7 계층에서 4번째 Layer 인 Transport Layer 에 대한 로드 밸런싱을 의미합니다.

[https://shlee0882.tistory.com/110]

사용자는 로드 밸런서에 접속을 하게 되고, 로드 밸런서는 설정(정의된) 된 여러대의 웹서버나 나 서버 중 한곳으로 전달 해주는 구조 입니다.

일반적으로 IP 와 PORT 를 기반으로 트래픽 전달에 대한 설정을 하게 되고 정의된 여러 서버에 대한 접속 방식이 여러가지가 있겠으나 보통적으로 사용되는 방식은 라운드 로빈 방식으로 사용되고 있습니다.


Layer 7(L7) 로드 밸런싱

Layer 4 가 웹 서버 하나를 선택에서 모든 트래픽을 전달하는 방식이었다면, Layer 7 로드 밸런싱은 Application layer 에서의 로드 밸런싱을 하는 것의 의미합니다.

레이어 7을 사용한 로드 밸랜싱은 사용자 요청의 내용에 따라 다른 백엔드 서버로 요청을 전달하게 됩니다. 이런 로드 밸런싱 모드를 사용하면 동일한 도메인 및 포트에서 여러 웹 애플리케이션 서버를 실행 및 사용할 수 있게 됩니다.

위의 예시에서는 사용자가 domain.com/blog 를 요청하면 /blog 에 해당하는 blog-backend 에 정의된 서버로 트래픽을 전달하게 됩니다. 

다른 요청의 경우 web-backend 에 정의된 서버로 전달하게 됩니다.

사용자의 요청과 설정에 따라서 각각의 다른 서버로의 전달되는 것을 Layer 7 로드 밸런싱 이라고 할 수 있습니다.
        

HA 고가용성

위에서 설명한 레이어 4 및 7의 로드 밸런싱 설정은 모두 로드 밸런서를 사용하여 트래픽을 여러 백엔드 서버 중 하나로 전달하게 됩니다.
그러나 로드 밸런서는 이러한 설정에서 단일 실패 지점(SPOF,  single point of failure) 이 되게 됩니다.

다운이 되거나 요청이 많아지면서 높은 대기 시간이나 다운타임이 발생할 수도 있습니다.

고 가용성(HA) 설정은 단일 실패 지점(SPOF) 이 없는 인프라 구성을 의미합니다. 아키텍처의 모든 계층에서 중복성을 추가/유지하여 단일 서버/시스템의 오류가 다운 타임이 되는 것을 방지하는데 목적이 있습니다.

로드 밸런서는 백엔드 계층(웹/앱/API/DB 시스템 등) 에 대한 중복성을 용이하게 하지만 진정한 고가용성 설정을 위해서는 로드 밸런서도 중복으로 있어야 합니다.


사용자가 웹사이트에 엑세스 하면 접속한 외부IP 주소를 통해 활성(Active) 로드 밸런서로 이동하며, 해당 로드 밸런서가 문제가 생긴다면 장애 조치 매커니즘이 이를 감지하고 다른 Passive 서버 중 하나의 IP 주소를 자동으로 재할당하는 형태로 진행이 필요 합니다.
       

HAProxy 설치

HAProxy 를 설치해보도록 하겠습니다. 

구성 환경
- CentOS 7.x 버전
- HAProxy 2.5.5
- root 유저로 진행하였습니다.


Linux의 OS 패키지 시스템 레파지토리를 이용해서도 HAProxy 를 설치는 할 수 있습니다(yum or apt)
다만 사용하는 OS에 따라서 설치 가능한 버전은 지금의 최신 버전에 비해 많이 차이가 날 수도 있습니다(버전이 낮을수 있음)

그래서 포스팅에서는 패키지 시스템이 아닌 다른 애플리케이션에 비해 컴파일 설치가 어렵지 않는 부분을 감안하여 컴파일로 최신 버전을 설치하여 진행하였습니다

포스팅에서는 MySQL 의 master 서버에 대한 표현으로 primary , source , write 로 표현하고 있습니다. 

        

OS 설정

먼저 OS 영역에 해당하는 부분 부터 진행하도록 하겠습니다.


커널 파라미터 설정

아래와 같이 커널 파라미터를 설정 및 파일에 기록하도록 하겠습니다.

sysctl -w net.ipv4.tcp_keepalive_time=110
sysctl -w net.ipv4.tcp_keepalive_intvl=30
sysctl -w net.ipv4.tcp_keepalive_probes=3


## 파일 생성 및 아래 내용 입력
vi /etc/sysctl.d/haproxy.conf

net.ipv4.tcp_keepalive_time=110
net.ipv4.tcp_keepalive_intvl=30
net.ipv4.tcp_keepalive_probes=3

수치는 시스템 상황이나 운영 정책에 따라서 사용하시면 됩니다.


Install prerequisite packages

필요한 사전 패키지를 설치하도록 하겠습니다.
포스팅에서 설치하는 HAProxy 2.5.5 버전에서는 LUA 패키지 5.3 버전이 필요하지만, CentOS 7.x 버전의 YUM Repository 에서는 5.1 버전까지만 설치가 가능하기 때문에, 외부(3rd Party) YUM Repository 를 추가하여 LUA 패키지를 설치하도록 하겠습니다.

외부 yum repository 구성을 위해서 아래와 같이 RPM 설치를 진행합니다.

rpm -ivh http://www.nosuchhost.net/~cheese/fedora/packages/epel-7/x86_64/cheese-release-7-1.noarch.rpm


위의 RPM 설치가 완료되었다면 아래 진행 절차대로 필요 패키지 설치를 진행합니다.

yum repolist

yum -y install make gcc perl pcre-devel zlib-devel \
openssl-devel libgudev1 lua-devel systemd-devel \
rsyslog socat nc



유저 및 디렉토리 생성

HAProxy 에서 사용할 Linux OS 유저 및 사용할 디렉토리를 생성하도록 하겠습니다.

## 유저 생성
groupadd --gid 1555 haproxy
useradd -M -s /sbin/nologin -g haproxy -u 1555 haproxy


## 디렉토리 생성 및 소유권 변경
sudo mkdir -p /var/log/haproxy
sudo mkdir /var/run/haproxy

sudo chown haproxy:haproxy /var/log/haproxy
sudo chown haproxy:haproxy /var/run/haproxy

참고1) 생성한 haproxy OS유저는 실제로 su - haproxy 와 같이 switch 하여 사용할 용도가 아님으로 -M 옵션을 사용해서 홈디렉토리를 생성하지 않았으며, /sbin/nologin 옵션을 사용해서 유저의 로그인 쉘을 사용할 수 없도록 생성하였습니다.

참고2) socket/PID/log 파일이 생성될 경로는 사용환경 마다 다를 수 있으며, 경로는 편하신 위치로 설정하여 사용하시면 됩니다.
다만 생성하는 디렉토리 경로와 아래 설명할 haproxy.cfg 파일내에서 경로가 일치해야 합니다.


/var/run/haproxy 디렉토리 생성 설정

OS 가 재시작 되면 /var/run 아래 디렉토리는 초기화가 되며, 디렉토리를 사용하기 위해서는 직접 생성해주거나 별도의 설정이 필요 합니다.
/usr/lib/tmpfiles.d 디렉토리 경로에 conf 파일을 생성하고 내용을 입력하여 재부팅 시에도 디렉토리가 생성될 수 있도록 설정하겠습니다.

## 경로 이동
[root]# cd /usr/lib/tmpfiles.d


## vi 나 nano 편집기 등 편한 편집기로 파일을 생성하면서 아래 내용을 입력 합니다.
[root]# vi haproxy.conf

d /var/run/haproxy 0755 haproxy haproxy  -

위와 같이 설정 후 재부팅하게 되면 0755 퍼미션에 소유권은 haproxy:haproxy 으로 생성되게 됩니다.


syslog for haproxy logging

haproxy 의 로그 생성 관리하는 방법 중에서 OS에서 사용중인 syslog(rsyslog) 를 통해서 로그를 기록할 수 있습니다.
/etc/rsyslog.d 경로에 haproxy.conf 파일을 생성하면서 내용을 입력하시면 됩니다.

[root]# vi /etc/rsyslog.d/haproxy.conf

# Collect log with UDP
$ModLoad imudp
$UDPServerAddress 127.0.0.1
$UDPServerRun 514

# Creating separate log files based on the severity
local0.* /var/log/haproxy/haproxy-traffic.log
local0.notice /var/log/haproxy/haproxy-admin.log


rsyslogd 서비스를 재시작 하겠습니다.

systemctl restart rsyslog.service

          

Install HAProxy

HAProxy 는 아래 링크에서 원하는 버전으로 다운로드 받을 수 있습니다.

포스팅에서는 2.5 버전의 Source 를 Compile 하여 설치하였습니다.

파일 다운로드 및 압축 해제

## 경로는 편하신 경로에서 하시면 됩니다.
mkdir -p pkg
cd pkg

## 파일 다운로드 및 압축 해제
wget -qO- http://www.haproxy.org/download/2.5/src/haproxy-2.5.5.tar.gz | tar zxvf -

이제 make 를 통해서 컴파일을 진행하도록 하겠습니다.


make & make install

# 압축해제한 경로로 이동
cd haproxy-2.5.5/


# 컴파일 실행(싱글 스레드)
make TARGET=linux-glibc \
USE_LUA=1 USE_PCRE=1 USE_OPENSSL=1 USE_ZLIB=1 \
USE_SYSTEMD=1 USE_PROMEX=1

또는

# # 컴파일 실행(병렬 스레드)
make -j 4 TARGET=linux-glibc \
USE_LUA=1 USE_PCRE=1 USE_OPENSSL=1 USE_ZLIB=1 \
USE_SYSTEMD=1 USE_PROMEX=1커

컴파일 실행시(make 실행시) -j 옵션을 통해서 병렬 스레드로 진행할 수 있습니다.
CPU Core 수나 CPU Count 수를 감안하여 설정하여 컴파일 할 수 있습니다.


컴파일이 완료되었다면 make install 을 진행합니다.

[root]# make instal
`haproxy' -> `/usr/local/sbin/haproxy'
`doc/haproxy.1' -> `/usr/local/share/man/man1/haproxy.1'
install: creating directory `/usr/local/doc'
install: creating directory `/usr/local/doc/haproxy'
`doc/configuration.txt' -> `/usr/local/doc/haproxy/configuration.txt'
`doc/management.txt' -> `/usr/local/doc/haproxy/management.txt'
`doc/seamless_reload.txt' -> `/usr/local/doc/haproxy/seamless_reload.txt'
`doc/architecture.txt' -> `/usr/local/doc/haproxy/architecture.txt'
`doc/peers-v2.0.txt' -> `/usr/local/doc/haproxy/peers-v2.0.txt'
`doc/regression-testing.txt' -> `/usr/local/doc/haproxy/regression-testing.txt'
`doc/cookie-options.txt' -> `/usr/local/doc/haproxy/cookie-options.txt'
`doc/lua.txt' -> `/usr/local/doc/haproxy/lua.txt'
`doc/WURFL-device-detection.txt' -> `/usr/local/doc/haproxy/WURFL-device-detection.txt'
`doc/proxy-protocol.txt' -> `/usr/local/doc/haproxy/proxy-protocol.txt'
`doc/linux-syn-cookies.txt' -> `/usr/local/doc/haproxy/linux-syn-cookies.txt'
`doc/SOCKS4.protocol.txt' -> `/usr/local/doc/haproxy/SOCKS4.protocol.txt'
`doc/network-namespaces.txt' -> `/usr/local/doc/haproxy/network-namespaces.txt'
`doc/DeviceAtlas-device-detection.txt' -> `/usr/local/doc/haproxy/DeviceAtlas-device-detection.txt'
`doc/51Degrees-device-detection.txt' -> `/usr/local/doc/haproxy/51Degrees-device-detection.txt'
`doc/netscaler-client-ip-insertion-protocol.txt' -> `/usr/local/doc/haproxy/netscaler-client-ip-insertion-protocol.txt'
`doc/peers.txt' -> `/usr/local/doc/haproxy/peers.txt'
`doc/close-options.txt' -> `/usr/local/doc/haproxy/close-options.txt'
`doc/SPOE.txt' -> `/usr/local/doc/haproxy/SPOE.txt'
`doc/intro.txt' -> `/usr/local/doc/haproxy/intro.txt'


버전 확인

[root]# haproxy -vv
HAProxy version 2.5.5-384c5c5 2022/03/14 - https://haproxy.org/
Status: stable branch - will stop receiving fixes around Q1 2023.
Known bugs: http://www.haproxy.org/bugs/bugs-2.5.5.html
Running on: Linux 3.10.0-1160.15.2.el7.x86_64 #1 SMP Wed Feb 3 15:06:38 UTC 2021 x86_64
<이하 생략>



halog 설치

halog 는 생성된 로그 파일을 보기 편한 형태로 파싱 해서 보여주는 툴입니다.
다운로드 받은 Source Code 에도 같이 있으며 컴파일이 필요함으로 컴파일로 HAProxy 를 설치하는 시점에 한 번 더 컴파일을 진행해서 설치하도록 하겠습니다.

## 디렉토리 이동 및 컴파일 실행
cd /root/pkg/haproxy-2.5.5
make admin/halog/halog


## 컴파일이 완료 되면 빌드 까지 완료 되며
## 빌드된 파일을 /usr/local/sbin 으로 복사 합니다.
cp admin/halog/halog /usr/local/sbin/


halog 실행 예시

$ halog -srv -H < haproxy.log | column -t
190000 lines in, 10 lines out, 0 parsing errors
#srv_name       1xx  2xx    3xx  4xx  5xx  other  tot_req  req_ok  pct_ok  avg_ct  avg_rt
api/web5        0    12969  163  851  0    1      13984    13983   100.0   0       60
api/web6        0    12976  149  854  5    0      13984    13979   100.0   1       150
httpd/<NOSRV>   0    0      8    0    0    0      8        0       0.0     0       0
httpd/web1      84   534    0    6    2    0      626      626     100.0   0       342
httpd/web2      72   3096   0    9    10   0      3187     3183    99.9    1       1509
static/static1  0    74491  171  17   1    0      74680    74679   100.0   0       2
static/static2  0    72989  155  11   0    0      73155    73155   100.0   1       4
stats/<STATS>   0    465    0    0    0    0      465      458     98.5    0       0	 	



haproxy 서비스 등록

systemctl  로 제어 가능하도록 systemd 에 등록하도록 하겠습니다.
다운로드 받은 Source Code 파일내에 서비스 파일을 활용하여 등록하도록 하겠습니다.


서비스 파일 복사

cd pkg/haproxy-2.5.5
cp ./admin/systemd/haproxy.service.in /lib/systemd/system/haproxy.service

복사한 haproxy.service 파일내 "@SBINDIR@" 을 실제로 사용하는 경로로 치환이 필요하며, pid 나 sock 파일의 경로가 수정이 필요할 경우 수정을 해야 합니다.

포스팅에서는 pid, socket파일 경로가 /var/run/haproxy 아래 파일을 사용하도록 위에서 디렉토리를 생성하였기 때문에 그에 맞게 수정하도록 하겠습니다.


sed 로 경로 치환

sed -i 's/@SBINDIR@/\/usr\/local\/sbin/g' /lib/systemd/system/haproxy.service
sed -i 's/run/run\/haproxy/g' /lib/systemd/system/haproxy.service

       

haproxy.cfg 설정

haproxy.cfg 은 크게 5가지로 영역으로 나눠 볼수 있습니다.

Global ,Defaults , Frontend , Backend, Listen


- Global 은 전체 영역에 걸쳐서 적용되는 설정
- defaults 는 그 다음에 오는 section 에 적용되는 공통 설정 내역
- frontend : 클라이언트 연결에 관한 설정
- bakend : frontend 에서 접속된 트래픽을 전달할 프록시 서버에 대한 설정 과 HealthCheck 등의 설정
- listen : 프론트엔드와 백엔드의 기능이 결합된 완전한 프록시를 정의하며, 별도의 서버 풀로 트래픽을 분할해야 하거나 애플리케이션이 점점 더 커지는 경우에는 개별 frontend 및 backend 섹션을 사용하는 것이 좋습니다.
      

웹 서버 로드밸런싱

아래는 테스트를 위해 도커로 구성한 apache 웹 서버 2개에 대해서 접속하는 간단한 설정 내용이 담긴 cfg 파일 내용입니다.

/etc/haproxy/haproxy.cfg 파일

global
    log           127.0.0.1:514 local1

    chroot        /
    external-check
    insecure-fork-wanted
    stats socket  /var/run/haproxy/stats.sock mode 660 group haproxy level admin expose-fd listeners
    stats timeout 30s
    pidfile       /var/run/haproxy/haproxy.pid
    ulimit-n      655350
    maxconn       100000
    user          haproxy
    group         haproxy
    daemon
    nbthread      4

defaults
    mode                    http
    log                     global
    option                  tcplog
    option                  dontlognull
    option  tcpka
    timeout queue           1m
    timeout connect         5s
    timeout client          480m
    timeout server          480m
    timeout check           5s

listen stats
    bind *:9000
    mode  http
    option dontlog-normal
    stats enable
    stats realm Haproxy\ Statistics
    stats uri /haproxy
    http-request use-service prometheus-exporter if { path /metrics }

frontend http-front
    bind *:80
    mode http
    default_backend http-backend

backend http-backend
    balance roundrobin
    mode http
    option forwardfor
    option httpchk GET /
    http-check expect string OK
    http-request set-header X-Forwarded-Port %[dst_port]

    server websrv1 192.168.56.113:81 check inter 1s fastinter 500ms rise 1 fall 1 weight 1
    server websrv2 192.168.56.113:82 check inter 1s fastinter 500ms rise 1 fall 1 weight 1

[참고] nbproc 옵션은 HAProxy 2.5 버전에서 Deprecated 되었습니다(announcing haproxy 2.5)

위에서 설정한 cfg 파일에 대해서 몇 가지 내용을 확인해보도록 하겠습니다.


chroot 의 기본값은 /var/lib/haproxy 등으로 설정되며 다음에 소개할 서버의 healthcheck 의 방식 중에서 external-script 를 사용하기 위해서 시작 경로를 / 으로 변경하였습니다.

listen stats 에서 stats enable 로 설정함으로써 HAProxy 의 모니터링 페이지 기능을 활성화 하게 되며 접속 포트는 9000 으로 경로는 /haproxy 로 설정하였습니다.
그래서 접속은 http://ip주소:9000/haproxy 로 접속하면 됩니다.
포트 번호나 경로는 정해진 부분이 아님으로 자유롭게 사용하시면 됩니다.


위의 설정에서는 내용이 없지만 페이지 자동 갱신(Auto Refresh) 를 하기 위해서는 "stats refresh 10s" 을 추가로 입력하시면 됩니다.

...
stats enable
stats refresh 10s
...

10s 는 10초를 의미하며, 시간은 자유롭게 변경하여 사용할 수 있습니다.

frontend , backend 는 접속과 그 뒷단의 서버에 대한 설정과 healthcheck 등의 정보가 기술되어 있습니다.
접속은 80포트이며, 접속 후 default_backend 설정에 의해서 http-backend 라는 backend 섹션으로 트래픽이 전달됩니다.

http-backend 백엔드 에서는 websrv1 , websrv2 라는 alias 를 설정한 192.168.56.113 IP의 81포트, 82 포트 2개의 웹 서버로 접속되는 설정입니다.

balance 에는 roundrobin , static-rr , leastconn , source , uri , url_param , hdr(<name>) , rdp-cookie , rdp-cookie(name) 가 설정이 가능하고 포스팅에서는 로드밸런싱을 round robin 으로 설정하였습니다.

자세한 내용은 아래 document 를 참고하시면 됩니다.


mode 에서 사용되는 http 는 Layer 7 이며 , mode tcp 는 layer 4 를 의미 합니다.


HAProxy 서비스 시작

설치 및 haproxy.cfg 파일 설정이 모두 완료되었다면 아래의 방법대로 HAProxy 를 시작하시면 됩니다.

systemctl enable haproxy

systemctl start haproxy



HAProxy 를 통한 접속 테스트

user$ curl 192.168.56.104:80
<html><body><h1>It works!</h1></body></html>
Container apache-1

user$ curl 192.168.56.104:80
<html><body><h1>It works!</h1></body></html>
Container apache-2

user$ curl 192.168.56.104:80
<html><body><h1>It works!</h1></body></html>
Container apache-1

user$ curl 192.168.56.104:80
<html><body><h1>It works!</h1></body></html>
Container apache-2

HAProxy 가 동작중인 서버의 IP 주소를 통해서 접속을 하면 되며 Frontend 의 포트가 80으로 설정되어 있기 때문에 80 포트로 접속을 하면 위와 같이 2개의 웹 서버가 round robin 방식으로 번갈아 가면서 접속되는 것을 확인할 수 있습니다.


Statistics 페이지를 접속하면 아래와 같이 서버 상태나 통계를 확인할 수 있습니다.
Statistics 페이지는 위에서 "stats uri /haproxy" 으로 설정하였기 때문에 접속 주소는 아래와 같이 됩니다.
http://IP주소:9000/haproxy




그리고 서버가 다운이 되면 아래와 같이 다운된 서버 정보를 확인할 수 있습니다.


         

MySQL 로드밸런싱

이번에는 MySQL 에 대한 로드밸런싱 설정을 진행해보도록 하겠습니다.

테스트를 위해 3대의 MySQL 서버가 Primary 1대, Replica 2대 로 구성되어 있습니다.
기존 haproxy.cfg 파일에 아래의 내용을 추가하도록 하겠습니다.

listen mysql-primary
    bind *:3306
    mode tcp
    balance roundrobin
    option external-check
    external-check path "/usr/bin:/bin"
    external-check command /etc/haproxy/script/mysql_primary_chk.sh
    option allbackups
    server mysrv1 192.168.56.113:3306 check inter 5s on-marked-down shutdown-sessions
    server mysrv2 192.168.56.113:3307 check inter 5s on-marked-down shutdown-sessions
    server mysrv3 192.168.56.113:3308 check inter 5s on-marked-down shutdown-sessions

listen mysql-replica
    bind *:3307
    mode tcp
    balance roundrobin
    option external-check
    external-check path "/usr/bin:/bin"
    external-check command /etc/haproxy/script/mysql_replica_chk.sh
    option allbackups
    server mysrv1 192.168.56.113:3306 check inter 5s on-marked-down shutdown-sessions
    server mysrv2 192.168.56.113:3307 check inter 5s on-marked-down shutdown-sessions
    server mysrv3 192.168.56.113:3308 check inter 5s on-marked-down shutdown-sessions

포스팅에서는 Write 가 가능한 Primary Pool 과 ReadOnly 의 replica 용 Pool 을 각각 만들었으며, 각 MySQL 인스턴스별 Primary(Source) 인지, Replica 인지를 확인 하기 위해서 external-check 를 사용하였습니다.

기본적 MySQL 에 대한 체크는 option mysql-check 또는 option mysql-check user 유저명 으로 해서 체크가 가능 합니다
다만 MySQL 포트로의 TCP 레벨의 Healthcheck 또는 유저의 접속 가능 여부 등으로 상태 체크이며, 해당 서버가 Source 서버인지 Replica 서버인지에 대한 Role 확인은 불가능 합니다

그래서 위의 같은 설정은 Server Side HA(MHA 또는 Orchestrator 등등) 에서 장애조치 프로세스(Failover) 나 Role Switch(Takeover) 가 발생하였을 때 Client Side 에서도 대응하기 위해서 작성된 설정 내역입니다.

MySQL 접속을 하여 여러 값을 조회를 해야함으로 MySQL DB에서 HAProxy 가 접속하여 상태조회를 하는 전용 DB 계정을 생성하여 사용하도록 하겠습니다.

CREATE USER `haproxy`@`%` IDENTIFIED WITH 'mysql_native_password' by 'haproxy';
GRANT RELOAD, PROCESS, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO `haproxy`@`%`;

위에서 사용한 client host IP대역 이나 패스워드 등은 모두 예시입니다. 그리고 별도의 관리 계정이나 위와 같은 권한을 가지는 별도의 계정이 있다면 계정 생성 없이 해당 계정으로 사용해도 됩니다.

그 다음은 스크립트가 위치할 디렉토리를 만들고 아래와 같이 스크립트를 생성하도록 하겠습니다.

## External-Check Script 를 위한 디렉토리 생성
mkdir -p /etc/haproxy/script

## 생성한 디렉토리로 이동
cd /etc/haproxy/script

경로는 꼭 위의 경로를 해야 하는 것은 아니며 변경하여 사용 가능하며 경로나 파일이름의 변경 시 haproxy.cfg 파일도 같이 수정이 필요 합니다.


• Source Server Check Script

#!/bin/bash

## MySQL Primary Check Script

MYSQL_HOST="$3"
MYSQL_PORT="$4"
MYSQL_USERNAME='haproxy'
MYSQL_PASSWORD='haproxy'
MYSQL_BIN='/bin/mysql'

HAPROXY_LOG_DIR=/var/log/haproxy
SHELL_DIR='/etc/haproxy/script'
LOG_DIR="${HAPROXY_LOG_DIR}"
LOG_FILE="${LOG_DIR}/mysql_primary_chk.log"
FORCE_FAIL="${SHELL_DIR}/proxyoff"
MYSQL_OPTS="--no-defaults -sN --connect-timeout=10"


return_ok()
{
    exit 0
}

return_fail()
{
    local message="$1"
    if [ ! -z "$message" ]
    then
        echo "`date` ${message}" >> ${LOG_FILE}
    fi

    exit 255
}

if [ -f "$FORCE_FAIL" ]
then
    return_fail "$FORCE_FAIL found"
fi


CMDLINE="$MYSQL_BIN $MYSQL_OPTS --host=$MYSQL_HOST --port=$MYSQL_PORT --user=$MYSQL_USERNAME -e"

mysql_version_check()
{
    V_MYSQL_VER_CHK=`MYSQL_PWD=$MYSQL_PASSWORD $CMDLINE "select substr(version(),1,1), substr(version(),5,2)"`

    MYSQL_MAIN_VER=${V_MYSQL_VER_CHK%$'\t'*}
    MYSQL_MINOR_VER=${V_MYSQL_VER_CHK#*$'\t'}

    if [ "$MYSQL_MAIN_VER" == "5" ]
    then
        REP_CMD_TYPE='slave'
        REP_HOSTS_CHK_CMD='show slave hosts'

    else
        if [ "$MYSQL_MINOR_VER" -gt "22" ]
        then
            # MySQL version More than ver 8.0.22
            REP_CMD_TYPE='replica'
            REP_HOSTS_CHK_CMD='show replicas'

        else
            # MySQL not more than ver 8.0.21
            REP_CMD_TYPE='slave'
            REP_HOSTS_CHK_CMD='show slave hosts'
        fi
    fi
}

mysql_version_check

READ_ONLY_CHK=`MYSQL_PWD=$MYSQL_PASSWORD $CMDLINE "SHOW GLOBAL VARIABLES LIKE 'read_only'" | awk {'print $2'}`
REPLICA_INFO_CHK=`MYSQL_PWD=$MYSQL_PASSWORD $CMDLINE "SHOW $REP_CMD_TYPE STATUS"`
REP_HOSTS_INFO_CHK=`MYSQL_PWD=$MYSQL_PASSWORD $CMDLINE "$REP_HOSTS_CHK_CMD"`

# Primay Check Condition
if [ "$READ_ONLY_CHK" == "OFF" ]
then
    if [ "$REPLICA_INFO_CHK" == "" ]
    then
        if [ "$REP_HOSTS_INFO_CHK" != "" ]
        then
            return_ok
        else
            return_fail
        fi
    else
        return_fail
    fi
else
    return_fail

fi



• Replica Server Check Script

#!/bin/bash

## MySQL Replica Check Script

MYSQL_HOST="$3"
MYSQL_PORT="$4"
MYSQL_USERNAME='haproxy'
MYSQL_PASSWORD='haproxy'
MYSQL_BIN='/bin/mysql'

HAPROXY_LOG_DIR=/var/log/haproxy
SHELL_DIR='/etc/haproxy/script'
LOG_DIR="${HAPROXY_LOG_DIR}"
LOG_FILE="${LOG_DIR}/mysql_replica_chk.log"
FORCE_FAIL="${SHELL_DIR}/proxyoff"
MYSQL_OPTS="--no-defaults -sN --connect-timeout=10"

return_ok()
{
    exit 0
}

return_fail()
{
    local message="$1"
    if [ ! -z "$message" ]
    then
        echo "`date` ${message}" >> ${LOG_FILE}
    fi

    exit 255
}

if [ -f "$FORCE_FAIL" ]
then
    return_fail "$FORCE_FAIL found"
fi


CMDLINE="$MYSQL_BIN $MYSQL_OPTS --host=$MYSQL_HOST --port=$MYSQL_PORT --user=$MYSQL_USERNAME -e"

mysql_version_check()
{
    V_MYSQL_VER_CHK=`MYSQL_PWD=$MYSQL_PASSWORD $CMDLINE "select substr(version(),1,1), substr(version(),5,2)"`

    MYSQL_MAIN_VER=${V_MYSQL_VER_CHK%$'\t'*}
    MYSQL_MINOR_VER=${V_MYSQL_VER_CHK#*$'\t'}

    if [ "$MYSQL_MAIN_VER" == "5" ]
    then
        REP_CMD_TYPE='slave'
        REP_HOSTS_CHK_CMD='show slave hosts'

    else
        if [ "$MYSQL_MINOR_VER" -gt "22" ]
        then
            # MySQL version More than ver 8.0.22
            REP_CMD_TYPE='replica'
            REP_HOSTS_CHK_CMD='show replicas'

        else
            # MySQL not more than ver 8.0.21
            REP_CMD_TYPE='slave'
            REP_HOSTS_CHK_CMD='show slave hosts'
        fi
    fi
}

mysql_version_check

READ_ONLY_CHK=`MYSQL_PWD=$MYSQL_PASSWORD $CMDLINE "SHOW GLOBAL VARIABLES LIKE 'read_only'" | awk {'print $2'}`
REPLICA_INFO_CHK=`MYSQL_PWD=$MYSQL_PASSWORD $CMDLINE "SHOW $REP_CMD_TYPE STATUS"`
REP_HOSTS_INFO_CHK=`MYSQL_PWD=$MYSQL_PASSWORD $CMDLINE "$REP_HOSTS_CHK_CMD"`

# Replica Check Condition
if [ "$READ_ONLY_CHK" == "ON" ]
then
    if [ "$REPLICA_INFO_CHK" != "" ]
    then
        if [ "$REP_HOSTS_INFO_CHK" == "" ]
        then
            return_ok
        else
            return_fail
        fi
    else
        return_fail
    fi
else
    return_fail

fi



작성된 스크립트는 Role 이나 Healthcheck 에 중점이 되어 간단하게 작성되어 있습니다. 추가적으로 개선이나, 추가적인 아이디어에 따라서 변경해서 사용하시면 됩니다.

Healthcheck 나 Role 에 대한 체크도 연계해서 사용하는 다른 솔루션(예를 들어 HA 솔루션) 을 통해서 확인되는 MySQL의 상태값 등을 통해서 더 유연하거나 좋은 체크 기능을 추가할 수도 있을 것으로 예상하고 있습니다.

위에서 작성한 스크립트 내용에 대해서 몇 가지 확인해보면

스크립트 실행시 HAProxy 로 부터 받을 수 있는 인자값은 기본적으로 4개의 값이 전달 됩니다.
<proxy_address> = $1  , <proxy_port> = $2 , <server_address> = $3 , <server_port> = $4

그외 필요에 따라서 추가로 정보를 받아서 스크립트에서 활용할 수 있으며 전달 되는 인자값 정보는 아래 링크를 참고하시면 됩니다.

예를 들어 전달받을 수 있는 인자 값 중에서 HAPROXY_PROXY_NAME 를 사용하려고 한다면 아래와 같이 인자 값을 변수로 받아서 사용할 수 있습니다
PROXY_NAME=$HAPROXY_PROXY_NAME 

External-script 에서 사용되는 위의 스크립트에서는 상태 체크 및 Role 확인에 대해서 아래 3개의 조건이 맞는 지를 체크하게 됩니다.
- READ_ONLY 여부
- SHOW REPLICA(SLAVE) STATUS 조회 결과가 있는지 여부
- SHOW REPLICAS 또는 SHOW SLAVE HOSTS의 조회 결과가 있는지 여부


위의 조건에 맞는지 아닌지에 따라서 Source(Primary) 인스턴스 인지 Replica 인스턴스 인지를 구분하게 되고 각 Pool 별로 조건에 맞지 않으면 HealthCheck Fail로 Mark가 되게 됩니다.

그래서 Source인 mysql-primary pool 에서는 등록된(설정된) DB인스턴스 중에 Source(Primary) 인스턴스만 상태 값이 UP 이 되고 나머지 인스턴스는 DOWN 으로 확인되게 됩니다.

반대로 mysql-replica 에서는 3개의 인스턴스 중에서 Source(Primary) 인스턴스가 DOWN 으로 확인되고, 2대의 Replica 인스턴스의 상태는 UP 으로 확인되어 2개의 인스턴스가 round robin 방식으로 로드밸런싱 되어서 접속되게 되는 구조입니다.

Source 서버에 장애가 발생하여 Server Side HA에 의해서 장애조치(Failover) 가 발생되어서 Replica 중에서 하나가 승격(Promote) 된다면 위의 조건에 따라서 다시 상태의 UP/DOWN 이 변경이 되게 되고 새롭게 UP 으로 확인되어 접속이 가능한 서버로 접속되게 됩니다.

위의 조건을 수행을 위해서는 MySQL 서버로 접속이 필요하며 접속할 계정과 패스워드 정보가 필요하고, MySQL Client 의 경로정보가 스크립트에 기재되어 있는 상태입니다. mysql 의 경로가 사용하는 환경에 맞게 수정해서 사용하시면 되며, 계정과 패스워드에 대한 정보는 위와 같은 직접 입력 이외에 여러 다른 방식으로 구현하여 사용하시면 됩니다.


on-marked-down shutdown-sessions 옵션은 HealthCheck 에 실패한 서버에 대해서 DOWN 으로 마킹 되면 기존 세션(커넥션)을 강제로 종료하는 기능(옵션) 입니다.
         

MySQL 로드밸런스 테스트

위의 내용과 같이 설정이 완료되었다면 먼저 Statistics 페이지를 접속해서 확인해 보도록 하겠습니다



2개의 서버풀에서 Primary 의 경우 1개만 Up 이고 나머지 2개 서버는 Down 으로 확인되며, 반대로 Replica 서버풀에는 2개가 Up 으로 확인되며 1개가 Down 으로 되어있는 것을 확인할 수 있습니다.


이 상태에서 MHA나 Orchestrator 와 같이 HA 솔루션을 통해서 Failover 나 Takeover 를 진행하거나, 별도로 Source <-> Replica Change 를 진행하게 되면 아래와 같이 서버 상태가 변경된 것을 확인할 수 있게 됩니다.



Primary 서버풀에서는 mysrv1 이 Down 이 되었고 mysrv2 Up 이 되었고, Replica 서버풀에서는 mysrv1 Up 이 되었고, mysrv2 Down 이 된 것을 확인할 수 있습니다.
           

Change a server's state

set server 명령어 를 통해서 서버의 상태 값을 변경할 수 있으며, 해당 기능을 통해서 haproxy.cfg 를 수정없이 Pool 에서 서버의 상태 값이나 접속 허용 여부 등을 설정할 수 있으며 

자세한 내용은 아래 2개의 문서를 참조하시면 됩니다.
 • haproxy.com/enable-disable-servers
 • haproxy.com/set-server

사용 목적과 방법에 따라서 사용할 인자 값이나 옵션 값이 달라질 수 있으며 간단하게 특정 서버의 접속 불가 와 접속 허용 상태로 변경에 대해서는 아래와 같이 maint 와 ready 를 사용할 수 있습니다.


• mysql-replica 서버풀의 mysrv1 서버의 상태를 변경할 경우

## maint
echo "set server mysql-replica/mysrv1 state maint" \
| sudo socat stdio /var/run/haproxy/stats.sock


## ready
echo "set server mysql-replica/mysrv1 state ready" \
| sudo socat stdio /var/run/haproxy/stats.sock

위의 명령어는 socat 패키지가 필요함으로 설치가 안되어 있다면 설치가 필요 합니다
(포스팅에서는 패키지 설치 과정에서 포함된 패키지입니다.)

mysql-replica/mysrv1 에서 "mysql-replica" 는 backend의 서버풀을 의미하며, mysrv1 는 해당 서버풀의 서버를 의미합니다.


먼저 maint 를 실행하면 아래와 같이 maintenance 로 확인되면서 접속이 불가한 상태가 되게 됩니다. 즉 3개의 서버 중에 남은 mysrv3 로 접속이 되게 됩니다.



위에서 스크린샷에서 확인되는 내용처럼 Replica 풀에서의 mysrv1 서버가 maintenance 상태로 되었으며, 풀에서 접속이 가능한 상태(상태 Up) 인 다른 서버로 접속하게 됩니다.

만약 풀에서 접속이 가능한, 즉 1개 남은 mysrv3 서버도 maint 를 실행할 경우 해당 풀로는 더이상 접속이 안되며, 서버 풀에서 접속할 서버가 없다는 내용을 로그 나 Linux 의 Broadcast 메세지(wall) 로 확인할 수 있게 됩니다.

Message from syslogd@mgr at Mar 18 12:00:05 ...
 haproxy[1225]:proxy mysql-replica has no server available!



다시 ready 명령어를 수행하게 되면 상태 확인 및 접속 가능을 위한 Up 상태로 변경됩니다.



set server 명령을 통해서 다양한 유형으로 서버의 상태를 변경할 수 있으며, Pool 시스템을 사용할 경우 서버의 추가/제거하는 과정과 유사하게 HAProxy 에도 접속 불가/가능 을 set server 를 수행함으로써 가능함으로 운영 정책에 맞게 Pool 관리면에서 사용하면 될 것 같습니다.
    

Reference

Reference URL

cbonte.github.io/2.5/configuration
haproxy.com/intro-haproxy-loging
cbonte.github.io/2.5/config#option-mysql-check
haproxy.com/enable-disable-server
haproxy.com/set-server
haproxy.com/layer4-layer7
haproxy.com/exploring-haproxy-stats-page
haproxy.com/announcing-haproxy-2-5

digitalocean.com/how-to-use-mysql-loadbalance
digitalocean.com/intro-haproxy-and-loadbalance
haproxy.com/haproxy-expose-prometheus
haproxy.com/how-to-enable-health-checks
loadbalancer.com/how-to-external-healthcheck-haproxy
haproxy.com/haproxy-high-mysql-request-rate
cbonte.github.io/external-check-command

github.com/ashraf-s9s/mysqlchk


관련된 다른 글

 

 

 

 

 

     

2
0
글에 대한 당신의 생각을 기다립니다. 댓글 의견 주세요!x