Last Updated on 10월 30, 2023 by Jade(정현호)
안녕하세요
이번 포스팅에서는 L4, L7 기능 및 고가용성 기능을 지원하는 오픈소스 솔루션인 HAProxy 에 대해서 설치 및 설정에 관하여 확인해 보겠습니다.
Contents
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
관련된 다른 글
Principal DBA(MySQL, AWS Aurora, Oracle)
핀테크 서비스인 핀다에서 데이터베이스를 운영하고 있어요(at finda.co.kr)
Previous - 당근마켓, 위메프, Oracle Korea ACS / Fedora Kor UserGroup 운영중
Database 외에도 NoSQL , Linux , Python, Cloud, Http/PHP CGI 등에도 관심이 있습니다
purityboy83@gmail.com / admin@hoing.io
좋은 자료 감사합니다.
소중한 코멘트 감사합니다.
즐거운 주말 연휴 되세요~
감사합니다.