๊ณ ์ฑ๋ฅ L4/L7 ๋ก๋ ๋ฐธ๋ฐ์ ์์ ๊ฐ์ด๋: frontend/backend/listen ์ํคํ
์ฒ, ๋ก๋ ๋ฐธ๋ฐ์ฑ ์๊ณ ๋ฆฌ์ฆ(roundrobin, leastconn, source), ์ค์๊ฐ ํฌ์ค ์ฒดํฌ, SSL/TLS Termination ์คํ๋ก๋ฉ, ACL ๊ฒฝ๋ก/ํค๋ ๋ผ์ฐํ
, Stick-tables ์๋ ์ ํ ๋ฐ Stats ์ค์๊ฐ ๋์๋ณด๋
## 1. HAProxy ์ค์น & ํ๊ฒฝ ๊ตฌ์ฑ ๊ฐ์ด๋ (Installation)
### ๐ป ์์คํ
์ค์น ๋ฐ ์คํ ์๊ตฌ์ฌ์ (System Requirements)
| ํญ๋ชฉ | ์ต์ ์ฌ์ (Minimum) | ๊ถ์ฅ ์ฌ์ (Recommended) | ์ฐธ๊ณ ๋ฐ ๋น๊ณ |
| :--- | :--- | :--- | :--- |
| **์ด์์ฒด์ (OS)** | Linux (Ubuntu, Debian, RHEL, Rocky, Alpine), FreeBSD, macOS (๊ฐ๋ฐ์ฉ) | Ubuntu 22.04 LTS, Rocky Linux 9, Alpine Linux 3.19+ | ๋น๋๊ธฐ ๋
ผ๋ธ๋กํน epoll ๊ธฐ๋ฐ ๊ณ ์ฑ๋ฅ ์ด๋ฒคํธ ๋ฃจํ ์ต์ ํ |
| **CPU** | 1 ์ฝ์ด ์ด์ (x86_64 / ARM64) | 4 ์ฝ์ด ~ 16 ์ฝ์ด ์ด์ | ๋ค์ค ์ฝ์ด ํ๊ฒฝ ์ `nbthread` ์ค๋ ๋ ๋ณ๋ ฌํ ์ง์ |
| **๋ฉ๋ชจ๋ฆฌ (RAM)** | 512 MB ์ด์ | 2 GB ~ 8 GB ์ด์ | ๋์ ์ฐ๊ฒฐ ์(maxconn) 100,000๊ฑด๋น ์ฝ 1GB~2GB ์๋ชจ |
| **๋์คํฌ (Storage)** | 100 MB ์ด์์ ์ฌ์ ๊ณต๊ฐ | 10 GB ~ 50 GB ์ด์์ NVMe SSD | Syslog ๋ก๊น
(rsyslog/fluentd) ๋ฐ SSL ์ธ์ฆ์ ์ ์ฅ์ฉ |
| **๋คํธ์ํฌ / ๋ฐํ์** | HAProxy 2.8 LTS ๋๋ 3.0 LTS | ํฌํธ 80(HTTP), 443(HTTPS), 8404(Stats) | ๋ฐฉํ๋ฒฝ ์ธ๋ฐ์ด๋ ํ์ฉ ๋ฐ Keepalived VIP ์ฐ๋ ๊ถ์ฅ |
---
### ๐ ์ด์์ฒด์ ๋ณ ์ค์น ๋ช
๋ น์ด
```bash
# ๐ง [1] Ubuntu / Debian ์ค์น (๊ณต์ PPA ์ต์ LTS 2.8+ ์ค์น)
sudo apt update
sudo apt install --no-install-recommends software-properties-common -y
sudo add-apt-repository ppa:vbernat/haproxy-2.8 -y
sudo apt update
sudo apt install -y haproxy socat
# ์๋น์ค ๋ฑ๋ก ๋ฐ ์์
sudo systemctl enable --now haproxy
sudo systemctl status haproxy
# ๐ง [2] RHEL / Rocky Linux 9 / AlmaLinux ์ค์น
sudo dnf install -y epel-release
sudo dnf install -y haproxy socat
sudo systemctl enable --now haproxy
# ๐ณ [3] Docker ๋จ๋
์ปจํ
์ด๋ ๋น ๋ฅธ ์คํ (Alpine ๊ฒฝ๋ ์ด๋ฏธ์ง)
# ๊ธฐ๋ณธ ์ค์ ํ์ผ ๋ง์ดํธ ํ ๋ฐฑ๊ทธ๋ผ์ด๋ ๊ตฌ๋
docker run -d --name my-haproxy \
-p 80:80 \
-p 443:443 \
-p 8404:8404 \
-v $(pwd)/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro \
--sysctl net.ipv4.ip_unprivileged_port_start=0 \
haproxy:2.8-alpine
# ๐ [4] macOS (๋ก์ปฌ ํ
์คํธ์ฉ Homebrew ์ค์น)
brew install haproxy
brew services start haproxy
```
---
## 2. HAProxy ํต์ฌ ๋ฉํ ๋ชจ๋ธ & ์ํคํ
์ฒ (Mental Model & Architecture)
**HAProxy(High Availability Proxy)**๋ ์ด๋น ์์ญ๋ง ๊ฑด์ ๋๊ท๋ชจ ํธ๋ํฝ์ ์ฒ๋ฆฌํ๋ ์ด๋ฒคํธ ๋๋ฆฌ๋ธ(Event-Driven) ๊ธฐ๋ฐ์ ๊ณ ์ฑ๋ฅ L4(TCP) ๋ฐ L7(HTTP) ์ญ๋ฐฉํฅ ํ๋ก์(Reverse Proxy) ๊ฒธ ๋ก๋ ๋ฐธ๋ฐ์์
๋๋ค.
### ๐ ํจํท ํ๋ฆ ๋ฐ ์ํคํ
์ฒ ๋ค์ด์ด๊ทธ๋จ (ASCII Diagram)
```text
[ Client Request ] (์ธ๋ฐ์ด๋ ํด๋ผ์ด์ธํธ ์์ฒญ)
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ HAProxy Server Engine โ
โ โ
โ [ global ] ํ๋ก์ธ์ค ๊ถํ, ์์ปค ์ค๋ ๋(nbthread), ํต๊ณ ์์ผ(/run/admin.sock)โ
โ [ defaults] ๊ณตํต ํ์์์(client/server/connect), ๊ธฐ๋ณธ ๋ชจ๋(http/tcp) โ
โ โ
โ [ frontend: http_in (Port 80/443) ] โ
โ โโโ SSL/TLS Termination (HTTPS ๋ณตํธํ & HTTP/2 ํ์) โ
โ โโโ ACL ์์ง ํ๊ฐ (๋๋ฉ์ธ, URL ๊ฒฝ๋ก, ํค๋, ํด๋ผ์ด์ธํธ IP) โ
โ โโโ Stick-table ๊ฒ์ฌ (์ด๋น ์์ฒญ ์ ์ ํ & DDoS ์ฐจ๋จ) โ
โ โ โ
โ โโโ path_beg /api/ โโโบ [ backend: api_servers ] โ
โ โโโ path_beg /static โโโบ [ backend: static_servers ] โ
โ โโโ default_backend โโโบ [ backend: web_servers ] โ
โ โ
โ [ backend: web_servers ] โ
โ โโโ ๋ก๋๋ฐธ๋ฐ์ฑ ์๊ณ ๋ฆฌ์ฆ (roundrobin, leastconn, source) โ
โ โโโ ์ธ์
๊ณ ์ ์ฑ (Cookie SERVERID injection) โ
โ โโโ ๋ฐฑ๊ทธ๋ผ์ด๋ ์กํฐ๋ธ ํฌ์ค ์ฒดํฌ (Health Check Inter 2000ms) โ
โ โ
โ [ listen: stats (Port 8404) ] โโโบ ์น ๊ธฐ๋ฐ ์ค์๊ฐ ํธ๋ํฝ ๋์๋ณด๋ UI โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โผ โผ โผ
โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ
โ App Server 01 โ โ App Server 02 โ โ Backup Server โ
โ 10.0.0.11:8080โ โ 10.0.0.12:8080โ โ 10.0.0.99:8080โ
โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ
```
### ๐งฉ 5๋ ์ค์ ๋ธ๋ก(Sections) ๋ฉํ ๋ชจ๋ธ ๋น๊ต
| ๋ธ๋ก ๋ช
์นญ | ์ญํ ๋ฐ ์ฃผ ๋ชฉ์ | ์ฃผ์ ํฌํจ ๋๋ ํฐ๋ธ |
| :--- | :--- | :--- |
| **`global`** | OS ๋ ๋ฒจ์ ํ๋ก์ธ์ค ๋ณด์, ๋ฆฌ์์ค ํ๊ณ, ๋ก๊ทธ ์์ผ, ๋ฐํ์ ๊ด๋ฆฌ ์์ผ ์ ์ | `maxconn`, `user`, `group`, `stats socket`, `nbthread` |
| **`defaults`** | ๋ชจ๋ ํ์ ์น์
(frontend/backend/listen)์ ์๋ ์์๋๋ ๊ณตํต ๊ธฐ๋ณธ๊ฐ | `mode`, `timeout connect`, `timeout client`, `timeout server` |
| **`frontend`** | ํด๋ผ์ด์ธํธ๊ฐ ์ ๊ทผํ๋ ์ธ๋ฐ์ด๋ IP/ํฌํธ ๋ฐ์ธ๋ฉ, SSL ๋ณตํธํ ๋ฐ ACL ๋ผ์ฐํ
ํ๋ณ | `bind`, `acl`, `use_backend`, `http-request redirect` |
| **`backend`** | ์ค์ ์๋น์ค๋ฅผ ์ฒ๋ฆฌํ๋ ๋์ ์๋ฒ ํ(Pool), ๋ก๋ ๋ฐธ๋ฐ์ฑ ๋ฐฉ์ ๋ฐ ํฌ์ค ์ฒดํฌ ์ค์ | `balance`, `cookie`, `option httpchk`, `server ... check` |
| **`listen`** | `frontend`์ `backend`๋ฅผ ํ๋๋ก ํฉ์น ์ผ์ฒดํ ๋ธ๋ก (์ฃผ๋ก Stats ๋์๋ณด๋, L4 DB์ฉ) | `bind`, `balance`, `server`, `stats enable`, `stats uri` |
---
## 3. [์ด๋ณด์ 1๋จ๊ณ] 3๋ถ ํฌ๋ก์๋: ๋ก์ปฌ ๋ก๋๋ฐธ๋ฐ์ ์ค์ต (Hello World)
๊ฐ์ฅ ๊ฐ๋จํ ํํ๋ก ํฌํธ 80์์ ์์ฒญ์ ๋ฐ์ ๋ฐฑ์๋ ์น์๋ฒ 2๋๋ก 5:5 ๊ท ๋ฑ ๋ถ๋ฐฐ(Round Robin)ํ๊ณ , 8404 ํฌํธ์์ ์ค์๊ฐ ๋ชจ๋ํฐ๋ง ์น ํ๋ฉด์ ์ ๊ณตํ๋ ์์ ํ ์ค์ ์์ ์
๋๋ค.
### โ ๋จ์ผ ์ค์ ํ์ผ ์์ฑ (`haproxy.cfg`)
```haproxy
# /etc/haproxy/haproxy.cfg - ํฌ๋ก์๋ ์ต์ ๋์ ์์
global
log stdout format raw local0
maxconn 2048
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
# [1] ํด๋ผ์ด์ธํธ ์์ฒญ ์์ ์๋ํฌ์ธํธ
frontend http_in
bind *:80
default_backend web_cluster
# [2] ๋ฐฑ์๋ ์ค์ ์ ํ๋ฆฌ์ผ์ด์
์๋ฒ ํ (2๋ ๋ผ์ด๋๋ก๋น)
backend web_cluster
mode http
balance roundrobin
server app01 127.0.0.1:8081 check
server app02 127.0.0.1:8082 check
# [3] ์น ๋ธ๋ผ์ฐ์ ์ค์๊ฐ ๋ชจ๋ํฐ๋ง ๋์๋ณด๋
listen stats
bind *:8404
mode http
stats enable
stats uri /stats
stats refresh 5s
```
### โก ํ
์คํธ์ฉ ๋๋ฏธ ๋ฐฑ์๋ ์๋ฒ 2๋ ์คํ (Python 1์ค ์น์๋ฒ)
ํฐ๋ฏธ๋ 2๊ฐ๋ฅผ ์ด์ด ๊ฐ๊ฐ ๋ค๋ฅธ ์๋ต์ ๋ด๋ณด๋ด๋ ๊ฐ๋จํ HTTP ์๋ฒ๋ฅผ ๋์๋๋ค:
```bash
# ํฐ๋ฏธ๋ A: 8081 ํฌํธ ๋ฐฑ์๋ 1 ์คํ
python3 -c "from http.server import HTTPServer, BaseHTTPRequestHandler; class H(BaseHTTPRequestHandler): def do_GET(self): self.send_response(200); self.end_headers(); self.wfile.write(b'Response from Backend 01\n'); HTTPServer(('', 8081), H).serve_forever()"
# ํฐ๋ฏธ๋ B: 8082 ํฌํธ ๋ฐฑ์๋ 2 ์คํ
python3 -c "from http.server import HTTPServer, BaseHTTPRequestHandler; class H(BaseHTTPRequestHandler): def do_GET(self): self.send_response(200); self.end_headers(); self.wfile.write(b'Response from Backend 02\n'); HTTPServer(('', 8082), H).serve_forever()"
```
### โข HAProxy ๋ฌธ๋ฒ ๊ฒ์ฌ ๋ฐ ๋ฐฑ๊ทธ๋ผ์ด๋ ์คํ
```bash
# ์ค์ ํ์ผ ๋ฌธ๋ฒ ๊ฒ์ฆ (-c ์ต์
)
haproxy -c -f haproxy.cfg
# ์ ์ ์ถ๋ ฅ: Configuration file is valid
# ํฌ๊ทธ๋ผ์ด๋ ๋๋ฒ๊ทธ ๋ชจ๋๋ก ์ฆ์ ์คํ (-d)
haproxy -f haproxy.cfg -db
```
### โฃ ๋์ ๊ฒ์ฆ (curl ๋ฐ๋ณต ์์ฒญ)
```bash
curl http://localhost:80
# ์ถ๋ ฅ: Response from Backend 01
curl http://localhost:80
# ์ถ๋ ฅ: Response from Backend 02
curl http://localhost:80
# ์ถ๋ ฅ: Response from Backend 01
```
> **์น ํต๊ณ ํ์ธ**: ์น ๋ธ๋ผ์ฐ์ ๋ก `http://localhost:8404/stats` ์ ์ ์ ๋ฐฑ์๋ 2๋์ ์ํ(๋
น์ UP ํ์)์ ์ฒ๋ฆฌ๋ ์ธ์
์นด์ดํธ๋ฅผ ์ค์๊ฐ GUI๋ก ํ์ธํ ์ ์์ต๋๋ค.
---
## 4. [2๋จ๊ณ] ํต์ฌ ๊ธฐ๋ฅ: ๋ก๋ ๋ฐธ๋ฐ์ฑ ์๊ณ ๋ฆฌ์ฆ & ํฌ์ค ์ฒดํฌ (Balancing & Health)
### โ๏ธ ๋ก๋ ๋ฐธ๋ฐ์ฑ ์๊ณ ๋ฆฌ์ฆ ๋น๊ต (`balance <algorithm>`)
| ์๊ณ ๋ฆฌ์ฆ | ์ค๋ช
๋ฐ ๋์ ์๋ฆฌ | ๊ถ์ฅ ์ค๋ฌด ์๋๋ฆฌ์ค |
| :--- | :--- | :--- |
| **`roundrobin`** | ๋ชจ๋ ์๋ฒ์ ์์ฐจ์ ์ผ๋ก ์์ฒญ์ 1๊ฐ์ฉ ๋ถ๋ฐฐ (๊ฐ์ค์น weight ๋์ ๋ณ๊ฒฝ ๊ฐ๋ฅ) | ์๋ฒ ์ฌ์์ด ๊ท ์ผํ๊ณ ๊ฐ ์์ฒญ์ ์ฒ๋ฆฌ ์๊ฐ์ด ์ ์ฌํ ์ผ๋ฐ ์น ์๋น์ค |
| **`leastconn`** | ํ์ฌ ํ์ฑ ์ฐ๊ฒฐ(Connection) ์๊ฐ ๊ฐ์ฅ ์ ์ ์๋ฒ๋ก ์ฐ์ ์ ๋ฌ | DB ์ฟผ๋ฆฌ, ํ์ผ ์
๋ก๋ ๋ฑ ์ฐ๊ฒฐ ์ ์ง ์๊ฐ์ด ๊ธธ๊ณ ํธ์ฐจ๊ฐ ํฐ ์ธ์
|
| **`source`** | ํด๋ผ์ด์ธํธ IP๋ฅผ ํด์ฑํ์ฌ ๋์ผ IP๋ ํญ์ ๋์ผํ ๋ฐฑ์๋ ์๋ฒ๋ก ๊ณ ์ | L4 TCP ๋ชจ๋ ๋๋ ๋ณ๋ ์ฟ ํค๋ฅผ ์ฌ์ฉํ ์ ์๋ ํ๊ฒฝ์ ์ธ์
๊ณ ์ |
| **`uri`** | ์์ฒญ URI ์ ์ฒด(๋๋ ํ๋ผ๋ฏธํฐ ์ ์ธ ๋ถ๋ถ)๋ฅผ ํด์ฑํ์ฌ ๋์ผ ๋ฐฑ์๋๋ก ์ ์ก | ๋ฐฑ์๋ ์บ์ ์๋ฒ(Varnish, Squid ๋ฑ)์ ์บ์ ํํธ์จ(Hit Ratio) ๊ทน๋ํ |
| **`hdr(<name>)`** | ํน์ HTTP ํค๋ ๊ฐ(์: `User-Agent`, `X-User-Id`)์ ๊ธฐ์ค์ผ๋ก ํด์ฑ | ์ฌ์ฉ์ ๊ทธ๋ฃน๋ณ ์ ๋ด ๋ฐฑ์๋ ๋ถ๊ธฐ ๋๋ A/B ํ
์คํ
ํ๊ฒฝ |
---
### ๐ฅ ํ๋ก๋์
ํฌ์ค ์ฒดํฌ ์์ธ ์ค์ (Health Checks)
๋จ์ TCP ์ฐ๊ฒฐ ํ์ธ์ ๊ทธ์น์ง ์๊ณ , ์ค์ ์ ํ๋ฆฌ์ผ์ด์
๋ ์ด์ด(HTTP ์๋ต ์ฝ๋ ๋ฐ ๋ฐ๋ ๋ด์ฉ)๋ฅผ ๊ฒ์ฌํ๋ ์ ๋ฐ ํฌ์ค ์ฒดํฌ ๊ตฌ์ฑ๋ฒ์
๋๋ค.
```haproxy
backend api_cluster
mode http
balance roundrobin
# [1] HTTP ํฌ์ค ์ฒดํฌ ํ์ฑํ ๋ฐ ์ ์ก ๋ฐฉ์ ์ ์
option httpchk
http-check send meth GET uri /api/health ver HTTP/1.1 hdr Host api.luckytech.com
# HTTP ์ํ ์ฝ๋ 200 ๋๋ 204๋ง ์ ์(Healthy)์ผ๋ก ํ์
http-check expect status 200,204
# [2] ์๋ฒ ๋ชฉ๋ก ๋ฐ ์ฅ์ /๋ณต๊ตฌ ์๊ณ์น ์ ์
# inter 2000 : 2000ms(2์ด)๋ง๋ค ํฌ์ค์ฒดํฌ ์ ์ก
# fall 3 : 3ํ ์ฐ์ ์คํจ ์ ์๋ฒ DOWN ํ์ (ํธ๋ํฝ ์ ์ธ)
# rise 2 : DOWN ์ํ์์ 2ํ ์ฐ์ ์ฑ๊ณต ์ UP ๋ณต๊ตฌ
# weight 100 : ๊ฐ์ค์น (๋ ๋์ ์ฌ์ ์๋ฒ์ ๋ ๋ง์ ํธ๋ํฝ ๋ฐฐ๋ถ)
# backup : ์ผ๋ฐ ์๋ฒ๊ฐ ๋ชจ๋ ๋ค์ด๋์์ ๋๋ง ๊ฐ๋๋๋ ๋น์ ์๋น ์๋ฒ
server api01 10.0.1.11:8080 check inter 2000 fall 3 rise 2 weight 100
server api02 10.0.1.12:8080 check inter 2000 fall 3 rise 2 weight 100
server api03 10.0.1.13:8080 check inter 2000 fall 3 rise 2 weight 50
server api_failover 10.0.1.99:8080 check backup
```
---
## 5. [2๋จ๊ณ] ์ค์๊ฐ ์น ๋์๋ณด๋ & SSL/TLS ์คํ๋ก๋ฉ (Stats & HTTPS)
### ๐ ์ค์๊ฐ ์น ๋์๋ณด๋ (Web Statistics Page)
๋ณด์ ์ธ์ฆ(HTTP Basic Auth)๊ณผ ๊ด๋ฆฌ์ ์กฐ์ ๊ถํ์ ๊ฐ์ถ ์ ์ฉ ๋ชจ๋ํฐ๋ง ํ์ด์ง ์ค์ ์
๋๋ค.
```haproxy
listen stats_dashboard
bind *:8404
mode http
stats enable
stats uri /stats
stats refresh 10s # 10์ด ์ฃผ๊ธฐ ์๋ ์๋ก๊ณ ์นจ
stats show-legends # ์์ธ ์งํ ์ค๋ช
๋ผ๋ฒจ ํ์
stats show-node # ํ์ฌ HAProxy ๋
ธ๋ ์ด๋ฆ ํ๊ธฐ
stats auth admin:SuperSecretKey99! # ์น ์ ์ ID ๋ฐ ํจ์ค์๋ ์ธ์ฆ
stats admin if TRUE # ์น UI์์ ์ง์ ์๋ฒ Ready/Maint ์ ํ ํ์ฉ
```
---
### ๐ SSL/TLS Termination (HTTPS ๋ณตํธํ & HTTP/2 ์ง์)
HAProxy๋ ์ธ์ฆ์ ํ์ผ๊ณผ ๊ฐ์ธํค(Private Key)๊ฐ ํ๋๋ก ํฉ์ณ์ง ๋จ์ผ `.pem` ํ์ผ์ ์๊ตฌํฉ๋๋ค.
#### โ Let's Encrypt ๋๋ ์์ฉ ์ธ์ฆ์ PEM ํฉ๋ณธ ์์ฑ
```bash
# ์ธ์ฆ์ ๋ณธ์ฒด์ ๊ฐ์ธํค๋ฅผ ํ๋์ ํ์ผ๋ก ๋ณํฉ
sudo cat /etc/letsencrypt/live/example.com/fullchain.pem \
/etc/letsencrypt/live/example.com/privkey.pem \
> /etc/haproxy/certs/example.com.pem
# ๋ณด์์ ์ํด ๊ฐ์ธํค ์ฝ๊ธฐ ๊ถํ์ haproxy ์ ์ฉ์ผ๋ก ์ ํ
sudo chmod 600 /etc/haproxy/certs/example.com.pem
sudo chown haproxy:haproxy /etc/haproxy/certs/example.com.pem
```
#### โก SSL ์คํ๋ก๋ฉ ๋ฐ HTTP โ HTTPS ๊ฐ์ ๋ฆฌ๋ค์ด๋ ํธ ์ค์
```haproxy
frontend https_front
# 80 ํฌํธ(HTTP) ๋ฐ 443 ํฌํธ(HTTPS) ๋์ ๋ฐ์ธ๋ฉ
bind *:80
bind *:443 ssl crt /etc/haproxy/certs/example.com.pem alpn h2,http/1.1
# ํด๋ผ์ด์ธํธ๊ฐ HTTP(ํฌํธ 80)๋ก ์ ์ํ ๊ฒฝ์ฐ ์ฆ์ HTTPS๋ก 301 ์๊ตฌ ์ด๋
http-request redirect scheme https code 301 unless { ssl_fc }
# ๋ฐฑ์๋ ์๋ฒ๋ก ์ค์ ํด๋ผ์ด์ธํธ IP ์ ๋ฌ์ ์ํ X-Forwarded-* ํค๋ ์ฃผ์
option forwardfor
http-request set-header X-Forwarded-Proto https if { ssl_fc }
http-request set-header X-Forwarded-Port %[dst_port]
# ๋ณด์ ์๋ต ํค๋ ์ถ๊ฐ (HSTS: 1๋
์ ์ฉ)
http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
default_backend web_backend
backend web_backend
mode http
balance roundrobin
server web01 10.0.0.21:8080 check
server web02 10.0.0.22:8080 check
```
---
## 6. [3๋จ๊ณ] ์ค๋ฌด ์์ฉ: ACL ์กฐ๊ฑด๋ถ ๋ผ์ฐํ
& ์ธ์
๊ณ ์ (ACL & Sessions)
### ๐ ACL(Access Control List) ๊ธฐ๋ฐ ์ง๋ฅํ ํธ๋ํฝ ๋ผ์ฐํ
URL ๊ฒฝ๋ก(Path), ๋๋ฉ์ธ(Host), ์์ฒญ ํค๋(Header) ์กฐ๊ฑด์ ๋ฐ๋ผ ์๋ก ๋ค๋ฅธ ๋ง์ดํฌ๋ก์๋น์ค ๋ฐฑ์๋๋ก ํธ๋ํฝ์ ์ ๋ฐํ๊ฒ ๋ณด๋
๋๋ค.
```haproxy
frontend main_gateway
bind *:80
mode http
# 1. ACL ์กฐ๊ฑด ๊ท์น ์ ์
acl is_api path_beg -i /api/
acl is_auth path_beg -i /oauth/ /login/
acl is_static path_end -i .jpg .jpeg .png .gif .css .js .ico .svg
acl is_admin_sub hdr_beg(host) -i admin.
acl is_mobile hdr_sub(User-Agent) -i Android iPhone iPad
# 2. ACL ๊ท์น์ ๋ฐ๋ฅธ ๋ฐฑ์๋ ๋งคํ (์๋จ๋ถํฐ ์์ฐจ ํ๊ฐ)
use_backend admin_pool if is_admin_sub
use_backend auth_pool if is_auth
use_backend api_pool if is_api
use_backend static_pool if is_static
use_backend mobile_pool if is_mobile
# ์ด๋ค ์กฐ๊ฑด์๋ ๋ง์ง ์์ ๋์ ๊ธฐ๋ณธ ๋ฐฑ์๋
default_backend default_web_pool
backend api_pool
mode http
balance leastconn
server api_node1 10.0.2.11:8080 check
server api_node2 10.0.2.12:8080 check
backend static_pool
mode http
balance roundrobin
server cdn_proxy 10.0.3.50:80 check
backend default_web_pool
mode http
balance roundrobin
server web_node1 10.0.1.11:8080 check
server web_node2 10.0.1.12:8080 check
```
---
### ๐ช ์ธ์
๊ณ ์ ์ฑ (Sticky Sessions / Session Affinity)
๋ก๊ทธ์ธ ์ํ๊ฐ ์ธ์
๋ฉ๋ชจ๋ฆฌ์ ์ ์ง๋๋ ๋ ๊ฑฐ์ ์น ํ๊ฒฝ์ ์ํด, HAProxy๊ฐ ํด๋ผ์ด์ธํธ ๋ธ๋ผ์ฐ์ ์ ์ฟ ํค๋ฅผ ์ฝ์
ํ์ฌ ์ต์ด ์ ์ํ ์๋ฒ๋ก ์ฐ๊ฒฐ์ ์๊ตฌ ๊ณ ์ ํฉ๋๋ค.
```haproxy
backend session_sticky_pool
mode http
balance roundrobin
# SERVERID ์ฟ ํค๋ฅผ ํด๋ผ์ด์ธํธ์ ์ฝ์
(insert)ํ๊ณ , ์บ์๋์ง ์๋๋ก(nocache) ์ง์
cookie SERVERID insert indirect nocache
# ๊ฐ ์๋ฒ์ ๊ณ ์ ์ฟ ํค ์๋ณ์(s1, s2) ๋ถ์ฌ
server app_srv01 10.0.1.11:8080 check cookie s1
server app_srv02 10.0.1.12:8080 check cookie s2
```
> **๋์ ๋ฉ์ปค๋์ฆ**: ์ต์ด ์์ฒญ ์ HAProxy๊ฐ ์๋ต ํค๋์ `Set-Cookie: SERVERID=s1; path=/`๋ฅผ ๋ด๋ ค์ค๋๋ค. ์ดํ ๋ธ๋ผ์ฐ์ ๊ฐ ๋ณด๋ธ ์ฟ ํค ๊ฐ์ ์ฝ์ด ํญ์ `app_srv01`๋ก๋ง ์ ์กํฉ๋๋ค.
---
## 7. [3๋จ๊ณ] L4 TCP ๋ก๋ ๋ฐธ๋ฐ์ฑ & Stick-tables DDoS ๋ฐฉ์ด (L4 & Rate Limit)
### ๐๏ธ L4 TCP ๋ชจ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค(MySQL) ํด๋ฌ์คํฐ ๋ถ์ฐ
HTTP ํค๋๋ฅผ ํ์ฑํ์ง ์๊ณ ์์ ์ ์ก ๊ณ์ธต(TCP ํจํท) ์์ค์์ ์ด๊ณ ์์ผ๋ก ๋ถํ๋ฅผ ๋ถ์ฐํฉ๋๋ค.
```haproxy
# /etc/haproxy/haproxy.cfg - MySQL ์ฝ๊ธฐ ๋ณต์ ๋ณธ(Read Replica) ๋ถํ ๋ถ์ฐ
listen mysql_read_cluster
bind *:3306
mode tcp
option tcplog
balance leastconn
# MySQL ์ฐ๊ฒฐ ํ์์์ ์ต์ ํ
timeout connect 3000ms
timeout client 1m
timeout server 1m
# MySQL ๋ค์ดํฐ๋ธ ํฌ์ค์ฒดํฌ (์ ์ ๊ถํ์ด ์๋ ํ ํจํท ๊ฒ์ฌ)
option mysql-check user haproxy_check
server db_replica_01 192.168.10.101:3306 check inter 3000 rise 2 fall 3
server db_replica_02 192.168.10.102:3306 check inter 3000 rise 2 fall 3
server db_replica_03 192.168.10.103:3306 check inter 3000 rise 2 fall 3
```
---
### ๐ก๏ธ Stick-tables ๊ธฐ๋ฐ IP๋ณ ์ด๋น ์์ฒญ๋ฅ ์ฐจ๋จ (Rate Limiting & DDoS ๋ฐฉ์ด)
์ธ๋ถ ๋น์ธ๊ฐ ์คํฌ๋ํผ๋ DoS ๊ณต๊ฒฉ์๋ฅผ ํ์งํ์ฌ, ๋จ์ผ IP๊ฐ 10์ด๊ฐ 50ํ ์ด์์ ์์ฒญ์ ๋ณด๋ผ ๊ฒฝ์ฐ ์ฆ์ `HTTP 429 Too Many Requests`๋ก ์ฐจ๋จํฉ๋๋ค.
```haproxy
frontend security_front
bind *:80
mode http
# 1. ์ธ๋ฉ๋ชจ๋ฆฌ ํ
์ด๋ธ ์ ์: ์ต๋ 100๋ง ๊ฐ IP ์ถ์ , 10์ด ๋ฏธ์ฌ์ฉ ์ ๋ง๋ฃ
# ์ถ์ ํญ๋ชฉ: 10์ด๊ฐ ๋์ HTTP ์์ฒญ ์ (http_req_rate)
stick-table type ip size 1m expire 10s store http_req_rate(10s)
# 2. ์ธ์
๋ ํด๋ผ์ด์ธํธ์ ์ถ๋ฐ์ง IP(src)๋ฅผ ์นด์ดํฐ์ ๊ธฐ๋ก
http-request track-sc0 src
# 3. ์ ๋ขฐํ ์ ์๋ ๋ด๋ถ ๊ด๋ฆฌ์ IP๋ ๊ฒ์ฌ ์์ธ(Whitelist)
acl is_whitelist src 192.168.1.0/24 10.0.0.0/8
# 4. 10์ด๊ฐ 50ํ๋ฅผ ์ด๊ณผํ IP ์์ฒญ ๊ฑฐ๋ถ (429 ๋ฐํ)
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 50 } !is_whitelist
default_backend general_backend
backend general_backend
mode http
balance roundrobin
server srv1 10.0.0.11:8080 check
server srv2 10.0.0.12:8080 check
```
---
## 8. CLI ์ ์ด & ๋ฐํ์ ์์ผ API (CLI & Runtime Socket)
### ๐ป ํ์ ์๋น์ค ๊ด๋ฆฌ ๋ฐ ๋ฌด์ค๋จ ๋ฆฌ๋ก๋
```bash
# [1] ์ค์ ํ์ผ ๋ฌธ๋ฒ ๊ฒ์ฌ (์ค์ ๋ณ๊ฒฝ ํ ๋ฐ๋์ ๋จผ์ ์คํ!)
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
# [2] ์๋น์ค ์ค๋จ ์๋ ๋ฌด์ค๋จ ๋ฆฌ๋ก๋ (Graceful Reload)
# ๊ธฐ์กด ์ฐ๊ฒฐ์ ์ ์งํ๋ฉด์ ์๋ก์ด ํ๋ก์ธ์ค๋ก ์์ฐ์ค๋ฝ๊ฒ ์ ํ
sudo systemctl reload haproxy
# [3] ์ ํต์ ์ธ ๋ฐ์ด๋๋ฆฌ ์ง์ ๋ฌด์ค๋จ ๋ฆฌ๋ก๋ (systemd ์์ ๋)
haproxy -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -sf $(cat /run/haproxy.pid)
# [4] ์๋น์ค ์์ / ์ค์ง / ํ์ฌ ์ํ ํ์ธ
sudo systemctl start haproxy
sudo systemctl stop haproxy
sudo systemctl status haproxy
```
---
### ๐ Unix Domain Socket์ ํตํ ๋ฌด์ค๋จ ๋ฐํ์ ์ ์ด (`socat`)
`global` ์น์
์ `stats socket /run/haproxy/admin.sock mode 660 level admin`์ด ์ค์ ๋์ด ์๋ค๋ฉด, ํ๋ก์ธ์ค ์ฌ์์ ์์ด ๋ฐํ์์ ์๋ฒ๋ฅผ ์ฆ์ ๊ฒฉ๋ฆฌํ๊ฑฐ๋ ๊ฐ์ค์น๋ฅผ ๋ฐ๊ฟ ์ ์์ต๋๋ค.
```bash
# [1] HAProxy ์ ์ฒด ์์คํ
์ํ ๋ฐ ๋ฉํธ๋ฆญ ์์ฝ ์กฐํ
echo "show info" | sudo socat stdio /run/haproxy/admin.sock
# [2] ๋ชจ๋ ํ๋ก ํธ์๋/๋ฐฑ์๋ ์๋ฒ ์ํ ์ค์๊ฐ CSV ์ถ๋ ฅ
echo "show stat" | sudo socat stdio /run/haproxy/admin.sock
# [3] ๐ ๋ฌด์ค๋จ ์ ํ๋ฆฌ์ผ์ด์
๋ฐฐํฌ ์: ํน์ ์๋ฒ ํธ๋ํฝ ์ฆ์ ์ฐจ๋จ (์ ๊ฒ ๋ชจ๋)
# ์๋ก์ด ์ธ์
์ ์
์ ๋ง๊ณ ๊ธฐ์กด ํ์ฑ ์ธ์
์๋ฃ ํ ์ฐ์ํ๊ฒ ์ ์ธ๋จ
echo "disable server web_backend/web01" | sudo socat stdio /run/haproxy/admin.sock
# [4] ๋ฐฐํฌ ์๋ฃ ํ: ํน์ ์๋ฒ ์ ์ ํธ๋ํฝ ํฌ์
echo "enable server web_backend/web01" | sudo socat stdio /run/haproxy/admin.sock
# [5] ๋ฐํ์์ ์๋ฒ ๊ฐ์ค์น(Weight) ๋์ ๋ณ๊ฒฝ (๊ฐ์ค์น๋ฅผ 50์ผ๋ก ํํฅ)
echo "set server web_backend/web01 weight 50" | sudo socat stdio /run/haproxy/admin.sock
# [6] ํ์ฑ Stick-table ์กฐํ (๊ณต๊ฒฉ์ IP ๋ชฉ๋ก ์ค์๊ฐ ํ์ธ)
echo "show table security_front" | sudo socat stdio /run/haproxy/admin.sock
```
---
## 9. ์ค๋ฌด ํธ๋ฌ๋ธ์ํ
& ํ๋ก๋์
์ด์ ์ฒดํฌ๋ฆฌ์คํธ (Best Practices)
### โ ๏ธ ์ด๋ณด์๊ฐ ๊ฐ์ฅ ํํ ๊ฒช๋ 5๋ ์ค์ ๋ฐ ํด๊ฒฐ์ฑ
| ์ฆ์ ๋ฐ ๋ฌธ์ ์ | ์์ธ ๋ถ์ | ํ์คํ ํด๊ฒฐ ๊ฐ์ด๋ |
| :--- | :--- | :--- |
| **`503 Service Unavailable: No server is available`** | ๋ชจ๋ ๋ฐฑ์๋ ์๋ฒ๊ฐ ํฌ์ค์ฒดํฌ ์คํจ(DOWN)๋ก ํ์ ๋จ | `check` ํฌํธ ๋ฒํธ, ๋ฐฉํ๋ฒฝ ์ฐจ๋จ ์ฌ๋ถ, `option httpchk`์ ์ง์ ๋ `/health` URI๊ฐ 200์ ๋ฐํํ๋์ง `curl`๋ก ์ง์ ํ
์คํธ |
| **`Connection refused` ๋๋ ๊ฐํ์ ๋ฉ์ถค** | ์์คํ
์คํ ํ์ผ ๋์คํฌ๋ฆฝํฐ ํ๊ณ ๋ฐ `maxconn` ๋ถ์กฑ | `/etc/security/limits.conf`์ `haproxy nofile 100000` ์ค์ ํ `global` ์น์
์ `maxconn 50000` ์ํฅ ์กฐ์ |
| **๋ฐฑ์๋ ๋ก๊ทธ์ ํด๋ผ์ด์ธํธ IP๊ฐ 127.0.0.1๋ก ๊ธฐ๋ก๋จ** | ํ๋ก์๊ฐ ์ค์ ํด๋ผ์ด์ธํธ IP ํค๋๋ฅผ ์ฃผ์
ํ์ง ์์ | `frontend`์ `option forwardfor` ๋๋ ํฐ๋ธ ์ถ๊ฐ ํ ๋ฐฑ์๋(Nginx/Tomcat)์์ `X-Forwarded-For` ํค๋๋ฅผ ์ฝ๋๋ก ์ค์ |
| **`cannot load certificate ... no private key found`** | HAProxy๋ ๋จ์ผ PEM ํ์ผ ์์ ์ธ์ฆ์์ ํค๊ฐ ํจ๊ป ์์ด์ผ ํจ | `cat cert.pem key.pem > combined.pem` ๋ช
๋ น์ผ๋ก ํฉ๋ณธ ํ์ผ์ ์์ฑํ์ฌ `crt` ๊ฒฝ๋ก์ ์ง์ |
| **L4 DB ํ๋ก์ ํต์ ์คํจ (`Invalid HTTP request`)** | TCP ํ๋กํ ์ฝ ๋์์ `mode http`๊ฐ ์์๋์ด ์์ | ํด๋น `listen` ๋๋ `frontend/backend` ๋ธ๋ก ์ต์๋จ์ `mode tcp`๋ฅผ ๋ช
์ํ์ฌ L7 ํ์ฑ ์ค๋จ |
---
### ๐ ๋ฆฌ๋
์ค ์ปค๋ ํ๋ผ๋ฏธํฐ ๊ณ ์ฑ๋ฅ ์ต์ ํ (`/etc/sysctl.conf`)
์ด๋น ์๋ง ๊ฑด์ ์ฐ๊ฒฐ์ ์์ ์ ์ผ๋ก ์ฒ๋ฆฌํ๊ธฐ ์ํ OS ์ปค๋ ํ๋ ๊ฐ์
๋๋ค:
```ini
# /etc/sysctl.d/99-haproxy.conf
# 1. ํฌํธ ๊ณ ๊ฐ ๋ฐฉ์ง๋ฅผ ์ํ ๋ก์ปฌ ํฌํธ ๋ฒ์ ํ๋
net.ipv4.ip_local_port_range = 1024 65535
# 2. ์์ผ ๋๊ธฐ ํ ํฌ๊ธฐ ์ฆ๊ฐ (์ฐ๊ฒฐ ํญ์ฃผ ์ ํจํท ๋๋กญ ๋ฐฉ์ง)
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
# 3. TIME_WAIT ์ํ์ ์์ผ์ ์ ์ฐ๊ฒฐ์ ์์ ํ๊ฒ ์ฌ์ฌ์ฉ
net.ipv4.tcp_tw_reuse = 1
# 4. FIN ๋จ๊ณ ํ์์์ ๋จ์ถ (์์ผ ์์ ๋น ๋ฅธ ๋ฐํ)
net.ipv4.tcp_fin_timeout = 15
# 5. ๋น๋ก์ปฌ IP ๋ฐ์ธ๋ฉ ํ์ฉ (Keepalived Active-Passive VIP ์ด์คํ ํ์ ์ค์ )
net.ipv4.ip_nonlocal_bind = 1
```
์ค์ ๋ฐ์ ๋ช
๋ น์ด: `sudo sysctl -p /etc/sysctl.d/99-haproxy.conf`
---
### ๐ก๏ธ Keepalived ์ฐ๋ Active-Passive VIP ์ด์คํ ๊ตฌ์กฐ
๋จ์ผ HAProxy ์๋ฒ๊ฐ ๋ฌผ๋ฆฌ์ ์ผ๋ก ๊ณ ์ฅ ๋๋ SPOF(Single Point of Failure)๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด 2๋์ HAProxy ๋
ธ๋๋ฅผ ๊ฐ์ IP(VIP)๋ก ๋ฌถ๋ ๊ถ์ฅ ๊ตฌ์ฑ์
๋๋ค.
```text
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Virtual IP (192.168.1.10) โ
โโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโ
โ VRRP Protocol
โโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโ
โผ โผ
[ Master HAProxy ] [ Backup HAProxy ]
(192.168.1.11 - Active) (192.168.1.12 - Standby)
โ โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโ
โผ
[ Backend Web Server Pool ]
```
1. ๋ง์คํฐ HAProxy๊ฐ ์ด์์์ ๋๋ 192.168.1.11์ด VIP(192.168.1.10)๋ฅผ ์์ ํฉ๋๋ค.
2. ๋ง์คํฐ ํ๋ก์ธ์ค ๋ค์ด ์ Keepalived์ VRRP ํฌ์ค์ฒดํฌ ์คํฌ๋ฆฝํธ(`killall -0 haproxy`)๊ฐ ์ด๋ฅผ ๊ฐ์งํ๊ณ 1์ด ์ด๋ด์ ๋ฐฑ์
๋
ธ๋(192.168.1.12)๋ก VIP๋ฅผ ์๋ ์ธ๊ณ(Failover)ํฉ๋๋ค.
์๊ฒฌ ๋ฐ ์ง๋ฌธ
0์์ง ๋ฑ๋ก๋ ์๊ฒฌ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋๊ธ์ ๋จ๊ฒจ๋ณด์ธ์!
๋๊ธ ์์
๋๊ธ ์ญ์