
✨ HAProxy 및 Keepalived를 로드 밸런서와 클러스터 외에도 보안(취약한 SSL/암호화 사용(LOGJAM) (CVE-2015-4000) 취약점 방지) 및 높은 부하에 대응하여 다룹니다.

HAProxy는 고가용성(high availability)을 갖춘 로드 밸런싱(load balancing)과 함께 TCP 및 HTTP 기반 애플리케이션을 위한 proxy 서버 서비스를 제공하는 오픈 소스 소프트웨어입니다.
Keepalived는 둘 이상의 서버를 위해 IP failover(장애 조치) 기능을 제공할 수 있는 오픈 소스 소프트웨어입니다. Keepalived는 서로 간에 Multicast 통신 기술을 사용합니다.
우리는 구성에서 HAProxy 로드 밸런서를 위해, Keepalived를 IP를 넘겨주는, 즉 HAProxy 구성을 Cluster 상태로 만들기 위해 사용할 것입니다.
시나리오에는 3개의 서버가 있습니다. 이 3개 서버에 HAProxy를 설치하여 로드 밸런서로 만들 것입니다. 그런 다음 Keepalived 서비스를 설치하여 서버 중 하나가 다운되면 IP failover를 통해 중단 없이 다른 서버로 전환하여 로드 밸런서 서비스가 계속 작동하도록 할 것입니다.
이를 위해 4개의 IP를 사용할 것입니다(IP는 완전히 임의의 값입니다).
이제 3개 서버 모두에 HAProxy 및 Keepalived 서비스를 다음과 같이 설치합니다.
sudo add-apt-repository ppa:vbernat/haproxy-2.7 -y
sudo apt update
sudo apt install haproxy keepalived -y
sudo openssl dhparam -out /etc/haproxy/dhparams.pem 2048
NoT1 : 서버에 “net.ipv4.ip_nonlocal_bind=1”이 설정되어 있어야 합니다. 그렇지 않으면 HAProxy에 사용할 구성에서 동시에 동일한 IP를 보유할 수 없으므로 바인드 오류가 발생하고 서비스가 작동하지 않습니다. 따라서 다음 절차를 따라야 합니다.
먼저 “vi /etc/sysctl.conf” 파일을 편집하고 다음 매개변수를 붙여넣은 뒤 저장하고 종료하세요.
net.ipv4.ip_nonlocal_bind=1
그런 다음 다음 명령을 실행하세요.
sysctl -p
Not2: 443 SSL을 사용할 경우 “/etc/ssl/private/” 디렉터리 안에 “haproxy.pem” 이름으로 SSL 인증서의 Bundle(.crt,.ca,*.key) 형태가 있어야 합니다.
이제 예시로 아래 HAPoxy 구성을 살펴보겠습니다. 구성의 각 HAProxy에 동일한 구성을 사용하게 됩니다.
이를 위해 “/etc/haproxy/haproxy.cfg” 파일을 편집할 것입니다. 다음 구성은 최소 HAv2를 지원합니다.
vi /etc/haproxy/haproxy.cfg
다음은 기본값입니다.
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
user haproxy
group haproxy
daemon
# maxconn 100000 #replaceable
# Default SSL material locations
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
# Default ciphers to use on SSL-enabled listening sockets.
# For more information, see ciphers(1SSL). This list is from:
# https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
# An alternative list with additional directives can be obtained from
# https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=haproxy
ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
#ssl-default-bind-options no-sslv3
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
ssl-dh-param-file /etc/haproxy/dhparams.pem
#tune.ssl.default-dh-param 2048
# nbproc 1
# nbthread 8
tune.maxrewrite 16384
tune.bufsize 32768
defaults
log global
mode http
option httplog
option dontlognull
option forwardfor
# maxconn 1000000 #replaceable
timeout connect 3000000
timeout client 6000000
timeout server 6000000
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
HAProxy용 Dashboard 구성 부분(모니터링을 위한 prometheus 통합 추가됨)
listen stats
bind fatlan.com:8989
mode http
stats enable
stats uri /stats
option http-use-htx
http-request use-service prometheus-exporter if { path /metrics }
#stats hide-version
stats realm HAProxy\ Statistics
stats auth admin:admin
다음 구성에서는 두 블록에서 ACL이 사용되었습니다. 첫 번째 블록은 링크의 어디에서든 “rest” 단어가 나오면 middleware-fatlan-backend 블록이 동작하고, 두 번째는 다른 도메인 요청(forum.fatlan.com)일 때 backend fatlan-forum-backend가 동작하며, 그 외의 모든 요청은 fatlan-backend443 블록에서 동작합니다. 다른 포트 리다이렉션은 제외합니다.
80 포트를 443 포트로 리다이렉션하는 부분
frontend fatlan80
bind fatlan.com:80
mode http
redirect scheme https if !{ ssl_fc }
frontend fatlan443
bind fatlan.com:443 ssl crt /etc/ssl/private/haproxy.pem ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
option httplog
option forwardfor
#http-request set-header X-Client-IP req.hdr_ip([X-Forwarded-For])
#option forwardfor except 127.0.0.0/8
option http-server-close
http-request set-header X-Forwarded-Proto https
# reqadd X-Forwarded-Proto:\ https #old config
mode http
default_backend fatlan-backend443
ACL 예제 구성(proxypass) 링크의 어디에서든 rest 단어가 나오면 리다이렉트합니다
acl middleware-fatlan path_beg /rest
use_backend middleware-fatlan-backend if middleware-fatlan
ACL 다른 forum 호스트로 리다이렉트
acl host_fatlanforum hdr(host) -i forum.fatlan.com
use_backend fatlan-forum-backend if host_fatlanforum
기본 리다이렉션 부분, 443용 리다이렉션 부분
backend fatlan-backend443
mode http
balance roundrobin
stick store-request src
stick-table type ip size 256k expire 30m
option forwardfor
# option httplog
option httpchk HEAD /
server frontend_01 10.10.37.12:8001 check port 8001 inter 3000 rise 2 fall 3
server frontend_02 10.10.37.13:8001 check port 8001 inter 3000 rise 2 fall 3
ACL로 들어온 rest 리다이렉션 부분
backend middleware-fatlan-backend
mode http
balance roundrobin
stick store-request src
stick-table type ip size 256k expire 30m
option forwardfor
# option httplog
option httpchk OPTIONS /login HTTP/1.0
http-check expect status 200
http-request replace-path (.*)(?:rest\/)(.*) \1\2
# reqrep ^([^\ :]*)\ /rest[/]?(.*) \1\ //\2 #old config
server middleware_01 10.10.37.34:3000 check port 4000 inter 12000 rise 3 fall 3
server middleware_02 10.10.37.35:3000 check port 4000 inter 12000 rise 3 fall 3
ACL로 들어온 forum 리다이렉션 부분
backend fatlan-forum-backend
mode http
option forwardfor
# option httplog
option httpchk HEAD /
server forum_01 10.10.37.45:8080 check port 8080 inter 3000 rise 2 fall 3
외부 예제도 다음과 같이 구성할 수 있습니다.
5000 포트 예제;
frontend Panel5000
bind fatlan.com:5000
# option httplog
option forwardfor except 127.0.0.0/8
#option http-server-close
http-request set-header X-Forwarded-Proto https
# reqadd X-Forwarded-Proto:\ https #old config
mode http
default_backend panel-backend5000
5000 포트 리다이렉션 부분
backend panel-backend5000
mode http
balance roundrobin
stick store-request src
stick-table type ip size 256k expire 30m
option forwardfor
# option httplog
option httpchk HEAD /
server panel_01 10.10.37.43:5000 check port 5000 inter 12000 rise 3 fall 3
server panel_02 10.10.37.44:5000 check port 5000 inter 12000 rise 3 fall 3
3306 mysql 예제;
frontend fatlanmysql
bind fatlan.com:3306
mode tcp
default_backend fatlanmysql-backend3306
3306 포트 리다이렉션 부분
backend fatlanmysql-backend3306
mode tcp
server mysql_01 10.10.37.60:3306 check
server mysql_02 10.10.37.61:3306 check backup
server mysql_03 10.10.37.62:3306 check backup
다른 도메인, 서브도메인 또는 포트에 대한 리다이렉션도 동일한 haproxy를 통해 수행할 수 있습니다. 사실 위에서 서브도메인에 대한 ACL 리다이렉션도 이미 보았습니다.
egitim.fatlan.com 서브도메인용 4444 포트 예제;
frontend egitim4444
bind egitim.fatlan.com:4444
# option httplog
option forwardfor except 127.0.0.0/8
#option http-server-close
http-request set-header X-Forwarded-Proto https
# reqadd X-Forwarded-Proto:\ https #old config
mode http
default_backend egitim-backend4444
egitim.fatlan.com 서브도메인 4444 포트 리다이렉션 부분
backend egitim-backend4444
mode http
balance roundrobin
stick store-request src
stick-table type ip size 256k expire 30m
option forwardfor
# option httplog
option httpchk HEAD /
server egitim_01 10.10.37.77:4444 check port 4444 inter 12000 rise 3 fall 3
server egitim_02 10.10.37.78:4444 check port 4444 inter 12000 rise 3 fall 3
위에서 예시 HAProxy 구성에 대해 언급했으며, 저는 자신의 구성을 만들고 3개 서버에 동일한 구성을 붙여넣었습니다.
이제 Keepalived 구성을 해보겠습니다. Keepalived의 경우 3개 서버에서도 부분적으로 다른 매개변수 설정이 적용됩니다. 이를 위해 “/etc/keepalived/keepalived.conf” 파일을 생성하여 구성하겠습니다. 참고로 “priority”가 높은 쪽이 우선입니다.
NoT1: Keepalived는 다른 peer들과 multicast로 통신하며, 이 방식을 통해 master와 backup이 결정됩니다.
Tcpdump로도 peer 간 multicast 통신을 capture할 수 있습니다(tcpdump -n "multicast").
1. 서버(HAProxy+Keepalived)
vrrp_sync_group haproxy {
group {
VI_01
}
}
vrrp_script haproxy_check_script {
script "killall -0 haproxy"
interval 2 # checking every 2 seconds (default: 5 seconds)
fall 3 # require 3 failures for KO (default: 3)
rise 6 # require 6 successes for OK (default: 6)
}
#Virtual interface
vrrp_instance VI_01 {
state MASTER
interface ens3
### 61 id'sini degistirin, diger peer'lerde de aynı olacak
virtual_router_id 61
### 103 id'sini degistirin, diger peer'lerde azalan şekilde olacak
priority 103
authentication {
auth_type PASS
auth_pass 123456
}
# Virtual ip address – floating ip
virtual_ipaddress {
10.10.5.5
}
track_script {
haproxy_check_script
}
}
2. 서버(HAProxy+Keepalived) (차이점만 적습니다)
state BACKUP
priority 102
3. 서버(HAProxy+Keepalived) (차이점만 적습니다)
state BACKUP
priority 101
NoT2: 서버 간 multicast 통신이 불가능한 경우(예: kvm, 클라우드 환경 등) unicast 통신을 사용하여 구성해야 합니다. 그렇지 않으면 작동하지 않습니다.
Unicast Config: 다음과 같이 구성 사이에 추가할 수 있습니다.
###Her sunucu için diğer eşneliği(peer) ip olarak belirtilmelidir(yazılır)
...
#Virtual interface
...
unicast_peer {
<anaother_peer_ip>
<anaother_peer_ip>
}
# Virtual ip address – floating ip
...
설정은 여기까지입니다. 모든 서버에서 HAProxy가 문제없이 작동해야 하며 keepalived 서비스도 마찬가지입니다. 서버나 서비스에 장애가 발생하면 작동 중인 다른 서버에서 로드밸런서가 계속 서비스를 제공할 것입니다.
고부하를 위한 HAProxy 및 Linux 커널 설정
sudo vi /etc/security/limits.conf
* soft no le 1000000
* hard no le 1000000
root soft no le 1000000
root hard no le 1000000
sudo vi /etc/default/haproxy
ulimit 1000000
sudo vi /lib/systemd/system/haproxy.service
LimitNOFILE=1000000
sudo vi /etc/sysctl.conf
net.ipv4.ip_local_port_range=1024 65535
net.ipv4.tcp_max_syn_backlog = 100000
net.core.somaxconn = 100000
net.core.netdev_max_backlog = 100000
sudo vi /etc/haproxy/haproxy.cfg
global
nbproc 1
nbthread 8
tune.maxrewrite 16384
tune.bufsize 32768
maxconn 1000000
tune.ssl.cachesize 1000000
defaults
maxconn 1000000
그런 다음 reboot를 수행하고, reboot가 즉시 불가능하다면
sudo systemctl daemon-reload
sudo systemctl restart haproxy.service
또한 취약한 SSL/암호 사용(LOGJAM) (CVE-2015-4000) 보안 테스트를 수행하려면 다음 명령을 사용하거나 https://www.ssllabs.com/ssltest/를 이용할 수 있습니다.
sudo nmap -sV --script ssl-enum-ciphers -p 443 fatlan.com
sudo nmap -p 443 --script ssl-cert fatlan.com
openssl s_client -connect fatlan.com:443
또한 위의 config들에서 client ip를 얻기 위해 제공되는 x-forwarder-for(option forwardfor) 구성의 검증, 즉 IP를 haproxy 쪽에서 capture 하기 위해 tcpdump를 사용할 수 있습니다.
sudo tcpdump -i ens3 -A -s 10240 | grep -v IP | egrep --line-buffered "..(GET |\.HTTP\/|POST |HEAD )|^[A-Za-z0-9-]+: " |sed -r 's/..(GET |HTTP\/|POST |HEAD )/\n\n\1/g'
