RockyLinux 9 버전에서 HAProxy+QUIC 조합을 사용하여 구성하는 방법에 대해서 설명한다.

1. HAProxy란?

 

HAProxy는 C 언어로 개발된 TCP 및 HTTP 기반 애플리케이션을 위해 고가용성 로드밸런서와 리버스 프록시를 제공하는 오픈소스 소프트웨어다.

TCP 및 HTTP 애플리케이션을 로드 밸런싱하고 고가용성을 제공하는데 주로 사용되며 로드밸런싱은 여러 서버 사이에 트래픽을 분산하여 서버의 성능을 향상시키고 가용성을 높이는 기술이다.
여러 서버로 들어오는 요청을 분산하여 부하를 공정하게 분배하고, 각 서버의 상태를 모니터링하며 문제가 있는 서버를 제외시킴으로써 신뢰성을 높일수 있다.

또한, 프록시 서버로 사용될 수 있어 클라이언트와 서버 간의 통신을 중개하는 역할을 수행할 수 있어서, 보안, 로깅, SSL 종단 감시 등 다양한 기능을 제공할 수 있다.

높은 성능과 안정성, 유연성을 제공하며, HTTP/1.1, HTTP/2, HTTP/3, TCP, SSL 등 다양한 프로토콜을 지원한다.
주로 웹 애플리케이션 및 마이크로서비스 아키텍처(MicroService Architecture, MSA)에서 사용되며, 대규모 웹 사이트 및 애플리케이션에서 많이 사용된다.

MSA에서 사용되는 경우로는 RedHat OpenShift의 Ingress Controller에서 기본으로 사용되고 있으며,
실제로 본인은 현업에서 OpenShift 엔지니어로 재직 중이고, 성능 테스트시 여러 시나리오에서 대규모 트래픽을 잘 처리하는 것을 확인 했다.

2. QUIC란?

 

QUIC (Quick UDP Internet Connections)는 웹 프로토콜의 하나로서, Google이 YouTube, Gmail 등에서 사용하기 위해 개발되었다.
기존의 TCP(Transmission Control Protocol)를 대체하고자 설계되었으며, OSI 7 계층의 Layer 4 전송 계층(Transport Layer)에서 작동하는 UDP(User Datagram Protocol)를 기반으로 한다.

QUIC는 TCP와 비교하여 여러가지 이점을 제공하는데, 일반적인 TCP 연결에서 발생하는 Handshake와 RTT(Round-Trip Time)을 줄여 빠른 연결 설정을 가능하게 한다.
그리고, TCP의 혼잡 제어(TCP Congestion Control)와 재전송 기능을 개선하여 패킷 손실 및 지연을 감소시키며, 여러 개의 동시 스트림을 사용하여 병렬로 데이터를 전송할 수 있어서,
웹 페이지의 리소스들을 동시에 다운로드하고 처리할 수 있게 하여, 더욱 빠른 웹 페이지 로딩 시간을 단축 시키고 네트워크 연결의 효율성을 향상시킬 수 있다.

QUIC는 주로 웹 브라우저와 웹 서버 간의 통신에서 사용되며, HTTP/3(Hypertext Transfer Protocol version 3)와 함께 사용된다.
현재 QUIC은 IETF(Internet Engineering Task Force)에서 2021년 5월에 표준화 발표하였으며, 많은 인터넷 기업들이 QUIC를 지원하고 도입하고 있다.

2.1. QUIC 특징

 

– 성능 향상

 

QUIC는 UDP를 사용하므로 TCP와 비교했을 때 더 낮은 지연 시간과 더 높은 대역폭을 제공한다.
이는 웹 페이지의 로딩 속도를 향상시키고, 실시간 통신이 필요한 애플리케이션에 적합하다.

– 연결 설정

 

QUIC은 기존의 TCP와 달리 연결 설정 단계가 필요하지 않다.
이는 웹 페이지 로딩 시간을 단축시키는 데 도움이 되며, TCP HOL(Head-of-Line Blocking) 문제를 해결하여 여러 개의 요청을 병렬로 처리할 수 있다.

– 보안

 

QUIC는 TLS(Transport Layer Security) 암호화를 기본적으로 지원하고, 데이터의 기밀성과 무결성을 보장한다.

– 이동성

 

QUIC는 IP 주소가 변경되는 경우에도 연결을 유지할 수 있으며, 모바일 장치와 같이 네트워크 연결이 불안정한 환경에서 유용하다.

2.2. QUIC 프로토콜 스펙 문서

 

– QUIC Transport Protocol

 

RFC 9000: https://datatracker.ietf.org/doc/html/rfc9000

– QUIC TLS

 

RFC 9001: https://datatracker.ietf.org/doc/html/rfc9001

– QUIC Recovery and Congestion Control

 

RFC 9002: https://datatracker.ietf.org/doc/html/rfc9002

– QUIC Loss Detection and Congestion Control

 

RFC 9003: https://datatracker.ietf.org/doc/html/rfc9003

3. HAProxy 빌드

 

RockyLinux 9 기반에서 HAProxy를 소스 컴파일 방법에 대해서 설명한다.

3.1. HAProxy 사용자 & 그룹 생성

 

HAProxy에서 사용할 사용자와 그룹을 생성한다.

[root@haproxy ~]# HAPROXY_UID=1000
[root@haproxy ~]# HAPROXY_GID=1000
[root@haproxy ~]# groupadd -g $HAPROXY_GID haproxy
[root@haproxy ~]# useradd -d $PREFIX_DIR -c "HAProxy User" -u $HAPROXY_UID -g $HAPROXY_GID -s /sbin/nologin haproxy

3.2. 필요 패키지 설치

 

소스 컴파일에 필요한 패키지를 설치 한다.

[root@haproxy ~]# dnf groupinstall 'Development Tools'
[root@haproxy ~]# dnf install epel-release
[root@haproxy ~]# dnf install systemd-devel git-core procps-ng perl-FindBin perl-IPC-Cmd perl-Pod-Html cmake make gcc g++ perl python

3.3. Golang 설치

 

Go는 2007년에 개발된 오픈 소스 프로그래밍 언어다.
Google에서 개발한 Go는 간결하면서도 효율적인 프로그래밍을 위한 목적으로 설계되었으며,
정적 타입의 컴파일 언어로, C와 같은 시스템 수준의 프로그래밍을 지원하면서도 자동 메모리 관리 및 가비지 컬렉션을 제공한다.

BoringSSL 빌드시 Golang을 필요로하기 때문에 구성한다.
QuicSSL을 사용하는 경우 Golang을 굳이 구성할 필요는 없다.

3.3.1. Golang 구성

 

[root@haproxy ~]# cd /opt
[root@haproxy ~]# curl -LO "https://go.dev/dl/go1.20.5.linux-amd64.tar.gz"
[root@haproxy ~]# tar xzvf go1.20.5.linux-amd64.tar.gz
[root@haproxy ~]# mkdir /opt/gopath

3.3.2. Golang Profile 변수 설정

 

[root@haproxy ~]# vi /etc/profile
# GoLang
export GO_HOME=/opt/go
export GOPATH=/opt/gopath
export PATH=$PATH:$GO_HOME/bin
[root@haproxy ~]# source /etc/profile

3.4. QUIC library 빌드

 

QUIC를 사용하기 위해서는 QUIC 전용 SSL library를 사용해야 한다.
library로는 크게 Google BoringSSL과 QuicTLS가 있다.

이 문서는 라이브러리의 설명과 함께 상황에 맞게 선택하여 사용 할 수 있도록 2가지 방안을 제시하겠다.

3.4.1. BoringSSL

 

BoringSSL은 Google에서 개발한 OpenSSL의 포크(Fork) 버전으로, Google의 내부 프로젝트를 위해 개발되었지만 안정성과 보안성에 중점을 둔 암호화 오픈소스 라이브러리다.
OpenSSL의 코드 베이스를 기반으로 하지만 경량화 및 간소화된 버전으로 개발되었다.

BoringSSL은 다음과 같은 특징을 가지고 있다.

– 간소화된 구현

 

BoringSSL은 OpenSSL에 비해 코드 베이스가 간소화되어 있으며, 코드의 가독성과 유지 보수의 용이성을 향상시킨다.

– 보안 강화

 

BoringSSL은 OpenSSL의 취약점을 보완하고, 새로운 보안 기능과 알고리즘을 구현하여 보안 강화에 초점을 두고 개발되었다.

– 최신 암호화 지원

 

BoringSSL은 최신의 암호화 알고리즘과 프로토콜을 지원하며, TLS 1.3, ChaCha20-Poly1305, ECDSA(Elliptic Curve Digital Signature Algorithm) 등의 암호화 기능을 제공한다.

– 경량성

 

BoringSSL은 경량화된 디자인을 통해 작은 크기와 낮은 메모리 사용량을 가지고 있어서 리소스 제약이 있는 장치나 환경에서 유용하다.

설명은 이쯤에서 하고, BoringSSL의 소스 빌드를 진행한다.
nproc 명령어는 시스템의 CPU 코어 개수를 출력하여 GCC 빌드시 multi thread로 컴파일을 할 수 있도록 한다.
안전하게 컴파일 하고 싶다면, -j $(nproc) 제거하고 make만 실행하면 된다.

[root@haproxy ~]# git clone https://github.com/google/boringssl.git /opt/boringssl
[root@haproxy ~]# cd /opt/boringssl
[root@haproxy ~]# mkdir build
[root@haproxy ~]# cd build
[root@haproxy ~]# cmake ..
[root@haproxy ~]# make -j $(nproc)
[root@haproxy ~]# make install

만약, Container 환경에서 빌드시 아래와 같이 Threads가 없다고 나오는 경우가 있다.

Could NOT find Threads (missing: Threads_FOUND)

이 경우에는 CMakeLists.txt 파일에 아래 내용을 추가 후 빌드하면 된다.

[root@haproxy ~]# vi CMakeLists.txt
set(CMAKE_THREAD_LIBS_INIT "-lpthread")
set(CMAKE_HAVE_THREADS_LIBRARY 1)
set(CMAKE_USE_WIN32_THREADS_INIT 0)
set(CMAKE_USE_PTHREADS_INIT 1)
set(THREADS_PREFER_PTHREAD_FLAG ON)

또는, sed 명령어로 한줄로 처리할때는 아래와 같이 사용한다.

[root@haproxy ~]# sed -i "1s/.*/set(CMAKE_THREAD_LIBS_INIT \"-lpthread\")\nset(CMAKE_HAVE_THREADS_LIBRARY 1)\nset(CMAKE_USE_WIN32_THREADS_INIT 0)\nset(CMAKE_USE_PTHREADS_INIT 1)\nset(THREADS_PREFER_PTHREAD_FLAG ON)\n\ncmake_minimum_required(VERSION 3.10)/g" CMakeLists.txt

3.4.2. QuicTLS

 

QuicTLS는 Google의 BoringSSL 프로젝트의 일부로 개발되었고, BoringSSL에서 가져온 기술과 코드를 기반으로 QUIC 프로토콜의 TLS 적용을 지원하여 데이터의 기밀성, 무결성, 인증을 보장한다.
또한, 다양한 언어 및 프레임워크에서 사용할 수 있는 라이브러리로 제공되며, 이를 통해 QUIC 프로토콜을 구현하는 애플리케이션에서 보안 요구 사항을 충족시키고 안전한 통신을 제공할 수 있다.

QuicTLS의 주요 특징과 기능은 다음과 같다.

– QUIC 프로토콜과의 통합

 

QuicTLS는 QUIC 프로토콜과 긴밀하게 통합되어 안전한 데이터 전송을 위한 TLS 기능을 제공하며, QUIC의 특성에 맞게 성능을 최적화하고, QUIC 연결 설정과 핸드쉐이크를 지원한다.

– TLS 보안 기능

 

QuicTLS는 TLS의 기능과 보안 프로토콜을 제공하여 데이터의 기밀성, 무결성, 인증 보장 및 암호화 알고리즘, 디지털 인증서, 키 교환 등의 기능을 제공한다.

– 성능 최적화

 

QuicTLS는 BoringSSL의 기술과 최적화를 활용하여 성능을 향상시키며, QUIC의 특성과 요구사항에 맞게 디자인되어, 빠른 연결 설정과 낮은 지연을 제공한다.

– 다양한 플랫폼 지원

 

QuicTLS는 다양한 플랫폼에서 동작하도록 설계되어 있어서, Linux, Windows, macOS 등 다양한 운영체제와 호환된다.

설명은 이쯤에서 하고, QuicTLS의 소스 빌드를 진행한다.
nproc 명령어는 시스템의 CPU 코어 개수를 출력하여 GCC 빌드시 multi thread로 컴파일을 할 수 있도록 한다.
안전하게 컴파일 하고 싶다면, -j $(nproc) 제거하고 make만 실행하면 된다.

[root@haproxy ~]# git clone https://github.com/quictls/openssl.git /opt/quictls
[root@haproxy ~]# cd /opt/quictls
[root@haproxy ~]# ./Configure
[root@haproxy ~]# make -j $(nproc)
[root@haproxy ~]# make install

3.5. HAProxy 소스 컴파일

 

3.5.1. 다운로드 및 압축 해제

 

[root@haproxy ~]# curl -LO "https://www.haproxy.org/download/2.8/src/haproxy-2.8.0.tar.gz"
[root@haproxy ~]# tar xzvf haproxy-2.8.0.tar.gz

3.5.2. Makefile 변수 추가 및 변경

 

QUIC를 사용하기 위해 Makefile에 해당 변수 내용을 추가한다.
(일부 변수들은 사용자 환경에 맞게 추가하거나 제거한다.)

[root@haproxy ~]# cd /opt/haproxy-2.8.0
[root@haproxy ~]# vi Makefile
# Valid USE_* options are enumerated in the "use_opts" variable and are listed
# below. Most of them are automatically set by the TARGET, others have to be
# explicitly specified :
USE_EPOLL=1
USE_NETFILTER=1
USE_THREAD=1
USE_PTHREAD_PSHARED=1
USE_REGPARM=1
USE_TPROXY=1
USE_LINUX_TPROXY=1
USE_LIBCRYPT=1
USE_GETADDRINFO=1
USE_FUTEX=1
USE_ACCEPT4=1
USE_PRCTL=1
USE_ZLIB=1
USE_CPU_AFFINITY=1
USE_TFO=1
USE_OBSOLETE_LINKER=1
USE_THREAD_DUMP=1
USE_SYSTEMD=1

# QUIC 활성화
USE_QUIC=1
USE_OPENSSL=1

# BoringSSL 또는 QuicTLS 위치
# 별도의 Prefix를 사용해서 디렉토리 위치를 지정한 경우 사용한다.
#SSL_INC=/opt/quictls/include
#SSL_LIB=/opt/quictls/lib
#LDFLAGS="-Wl,-rpath,/opt/quictls/lib"

#### Installation options.
DESTDIR =
INSTALL = install
PREFIX = /opt/haproxy
SBINDIR = $(PREFIX)/sbin
MANDIR = $(PREFIX)/share/man
DOCDIR = $(PREFIX)/doc/haproxy

#### TARGET system
# Use TARGET=<target_name> to optimize for a specific target OS among the
# following list (use the default "generic" if uncertain) :
#    linux-glibc, linux-glibc-legacy, linux-musl, solaris, freebsd, freebsd-glibc,
#    dragonfly, openbsd, netbsd, cygwin, haiku, aix51, aix52, aix72-gcc, osx, generic,
#    custom
TARGET = linux-glibc

#### TARGET CPU
# Use CPU=<cpu_name> to optimize for a particular CPU, among the following
# list :
#    generic, native, i586, i686, ultrasparc, power8, power9, custom,
#    a53, a72, armv81, armv8-auto
CPU = native

#### Architecture, used when not building for native architecture
# Use ARCH=<arch_name> to force build for a specific architecture. Known
# architectures will lead to "-m32" or "-m64" being added to CFLAGS and
# LDFLAGS. This can be required to build 32-bit binaries on 64-bit targets.
# Currently, only 32, 64, x86_64, i386, i486, i586 and i686 are understood.
ARCH = x86_64

3.5.3. 빌드

 

[root@haproxy ~]# cd /opt/haproxy-2.8.0
[root@haproxy ~]# make -j $(nproc)
[root@haproxy ~]# make install

4. Systemd 서비스 등록

 

systemd를 사용하여 서비스를 구동 및 중지를 할 수 있도록 설정한다.

4.1. 서비스 파일 복사

 

[root@haproxy ~]# cp /opt/haproxy-2.8.0/admin/systemd/haproxy.service.in /lib/systemd/system/haproxy.service

4.2. 서비스 파일 수정

 

사용자 환경에 맞도록 설정 값들을 수정한다.

[root@haproxy ~]# vi /lib/systemd/system/haproxy.service
[Unit]
Description=HAProxy Load Balancer
After=network-online.target
Wants=network-online.target

[Service]
EnvironmentFile=-/etc/default/haproxy
EnvironmentFile=-/etc/sysconfig/haproxy
Environment="CONFIG=/opt/haproxy/conf/haproxy.cfg" "PIDFILE=/opt/haproxy/haproxy.pid" "EXTRAOPTS=-S /opt/haproxy/stats"
ExecStart=@SBINDIR@/haproxy -Ws -f $CONFIG -p $PIDFILE $EXTRAOPTS
ExecReload=@SBINDIR@/haproxy -Ws -f $CONFIG -c -q $EXTRAOPTS
ExecReload=/bin/kill -USR2 $MAINPID
KillMode=mixed
Restart=always
SuccessExitStatus=143
Type=notify

# The following lines leverage SystemD's sandboxing options to provide
# defense in depth protection at the expense of restricting some flexibility
# in your setup (e.g. placement of your configuration files) or possibly
# reduced performance. See systemd.service(5) and systemd.exec(5) for further
# information.

# NoNewPrivileges=true
# ProtectHome=true
# If you want to use 'ProtectSystem=strict' you should whitelist the PIDFILE,
# any state files and any other files written using 'ReadWritePaths' or
# 'RuntimeDirectory'.
# ProtectSystem=true
# ProtectKernelTunables=true
# ProtectKernelModules=true
# ProtectControlGroups=true
# If your SystemD version supports them, you can add: @reboot, @swap, @sync
# SystemCallFilter=~@cpu-emulation @keyring @module @obsolete @raw-io

[Install]
WantedBy=multi-user.target

4.3. 서비스 등록

 

[root@haproxy ~]# systemctl enable haproxy

5. HAProxy 설정 파일 생성

 

간단한 HTTP, HTTPS 설정 파일을 예제로 만든다.

[root@haproxy ~]# vi /opt/haproxy/conf/haproxy.cfg
# Global settings
global
    chroot /opt/haproxy
    pidfile /opt/haproxy/haproxy.pid
    maxconn 200000
    # 800/KB
    tune.maxrewrite 8192
    tune.bufsize 32768
    user haproxy
    group haproxy
    #daemon
    log /dev/log  local0 info
    # turn on stats unix socket
    stats socket /opt/haproxy/stats level admin expose-fd listeners

    # SSL Cipher
    tune.ssl.default-dh-param 2048
    #ssl-default-bind-options no-sslv3

    # generated 2023-06-10, Mozilla Guideline v5.7, HAProxy 2.8.0, OpenSSL 3.0.9+quic, intermediate configuration
    # https://ssl-config.mozilla.org/#server=haproxy&version=2.8.0&config=intermediate&openssl=3.0.9+quic&guideline=5.7
    # intermediate configuration
    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:DHE-RSA-CHACHA20-POLY1305
    ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
    ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets

    ssl-default-server-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:DHE-RSA-CHACHA20-POLY1305
    ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
    ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets

    # mkdir /opt/haproxy/certs/
    # curl https://ssl-config.mozilla.org/ffdhe2048.txt > /opt/haproxy/certs/dhparam
    ssl-dh-param-file /opt/haproxy/certs/dhparam

    # Multi-thread mode
    nbthread 4
    #cpu-map auto:1/1-4 0-3

    # Multi-process mode
    cpu-map 1 0
    cpu-map 2 1
    cpu-map 3 2
    cpu-map 4 3

    # HAProxy Status
    #stats bind-process 4

defaults
    mode http
    log global
    #option httplog clf
    option dontlognull
    option http-server-close
    #option forwardfor except 10.88.0.0/16
    option forwardfor
    option redispatch
    retries 3
    timeout http-request 10s
    timeout queue 1m
    timeout connect 10s
    timeout client 300s
    timeout server 300s
    timeout http-keep-alive 20s
    timeout check 10s
    maxconn 200000

#### HAProxy Status ####
listen stats
    bind *:1936
    mode http
    stats enable
    stats uri /
    stats auth    'admin:admin'
    #bind-process 4

#### HTTP ####
frontend default-http
    # Service Port
    bind *:80

    # Balancer Type
    balance roundrobin

    # Syslog
    log 127.0.0.1 local1

    # Backend
    default_backend default-http

backend default-http
    # Balancer Type
    balance roundrobin

    # Backend Server
    server nginx01 192.168.0.11:80 check inter 5s maxconn 200000
    server nginx02 192.168.0.12:80 check inter 5s maxconn 200000
    server nginx03 192.168.0.13:80 check inter 5s maxconn 200000
    server nginx04 192.168.0.14:80 check inter 5s maxconn 200000

#### HTTPS ####
frontend default-https
    # Service Port
    bind :443 ssl crt /opt/haproxy/certs/ssl.pem alpn h2,http/1.1 allow-0rtt
    bind quic4@:443 ssl crt /opt/haproxy/certs/ssl.pem alpn h3 allow-0rtt

    # Balancer Type
    balance roundrobin

    # Syslog
    log 127.0.0.1 local2

    # Backend
    default_backend default-https

    # HTTP/3 (QUIC)
    http-response set-header alt-svc "h3=\":443\"; ma=31536000"

    # Enable HSTS(HTTP Strict Transport Security)
    http-response set-header Strict-Transport-Security max-age=63072000

backend default-https
    # Balancer Type
    balance roundrobin

    # Backend Server
    server nginx01 192.168.0.11:443 ssl check inter 5s verify none maxconn 200000
    server nginx02 192.168.0.12:443 ssl check inter 5s verify none maxconn 200000
    server nginx03 192.168.0.13:443 ssl check inter 5s verify none maxconn 200000
    server nginx04 192.168.0.14:443 ssl check inter 5s verify none maxconn 200000

6. 서비스 시작

 

[root@haproxy ~]# systemctl start haproxy

7. HTTP/3 확인

 

위의 내용을 기반으로 HTTP/3가 지원되고 있는지 링크를 통해 확인한다. HTTP/3 Check

끝.