๋ก๋ ๋ฐธ๋ฐ์ฑ (Load Balancing)
๊ฐ์
๋ก๋ ๋ฐธ๋ฐ์ฑ์ด๋ ํด๋ผ์ด์ธํธ ์์ฒญ์ ์ฌ๋ฌ ๋์ ์๋ฒ์ ๋ถ์ฐ์์ผ ํ ์๋ฒ๊ฐ ๊ณผ๋ถํ๋์ง ์๋๋ก ์กฐ์ ํ๋ ๊ธฐ์ ์ด๋ค.
์ฝ๊ฒ ๋งํด "์ค ์ธ์ฐ๊ธฐ ๋์ฐ๋ฏธ"์์!
๋งํธ์ ๊ณ์ฐ๋๊ฐ 1๊ฐ๋ผ๋ฉด ์๋ฌด๋ฆฌ ์ง์์ด ๋น ๋ฅด๊ฒ ์ผํด๋ ์๋์ด ๋ชฐ๋ฆฌ๋ฉด ์ค์ด ์์ฒญ ๊ธธ์ด์ง์ฃ . ๊ทธ๋ฐ๋ฐ ๊ณ์ฐ๋๋ฅผ 3~4๊ฐ๋ก ๋๋ฆฌ๊ณ "์๋ ์ฌ๊ธฐ๋ก ์ค์ธ์, ์ ์ชฝ์ผ๋ก ์ค์ธ์" ํ๊ณ ์๋ดํด์ฃผ๋ ์ง์์ด ์๋ค๋ฉด? ํจ์ฌ ๋น ๋ฅด๊ฒ ์ฒ๋ฆฌํ ์ ์์ด์!
Nginx๊ฐ ๋ฐ๋ก ๊ทธ ์๋ด ์ง์ ์ญํ ์ ํ๋ ๊ฑฐ์์. ์์ฒญ์ด ๋ค์ด์ค๋ฉด ์ฌ๋ฌ ๋ฐฑ์๋ ์๋ฒ ์ค ์ ์ ํ ๊ณณ์ผ๋ก ๋ถ์ฐ์์ผ ์ฃผ๋ ๊ฑฐ์ฃ .
๋ก๋ ๋ฐธ๋ฐ์ฑ์ ์ฐ๋ฉด:
โข
โข
โข
โข
๋ก๋ ๋ฐธ๋ฐ์ฑ ์ํคํ
์ฒ
graph TD
A["๐ค ํด๋ผ์ด์ธํธ 1"] -->|"์์ฒญ"| LB
B["๐ค ํด๋ผ์ด์ธํธ 2"] -->|"์์ฒญ"| LB
C["๐ค ํด๋ผ์ด์ธํธ 3"] -->|"์์ฒญ"| LB
D["๐ค ํด๋ผ์ด์ธํธ 4"] -->|"์์ฒญ"| LB
LB["โ๏ธ Nginx<br/>๋ก๋ ๋ฐธ๋ฐ์<br/>:80 / :443"]
LB -->|"์์ฒญ 1"| S1["๐ฅ๏ธ Server 1<br/>:8080"]
LB -->|"์์ฒญ 2"| S2["๐ฅ๏ธ Server 2<br/>:8081"]
LB -->|"์์ฒญ 3"| S3["๐ฅ๏ธ Server 3<br/>:8082"]
style LB fill:#FFD700
style S1 fill:#87CEEB
style S2 fill:#87CEEB
style S3 fill:#87CEEBMermaid
๋ณต์ฌ
๋ก๋ ๋ฐธ๋ฐ์ฑ ์๊ณ ๋ฆฌ์ฆ ๋น๊ต
์๊ณ ๋ฆฌ์ฆ | Nginx ์ค์ | ๋ถ๋ฐฐ ๋ฐฉ์ | ์ ํฉํ ์ํฉ |
Round Robin | (๊ธฐ๋ณธ๊ฐ) | ์์๋๋ก ๋์๊ฐ๋ฉฐ | ์๋ฒ ์ฑ๋ฅ์ด ๋์ผํ ๋ |
Least Connections | least_conn; | ์ฐ๊ฒฐ ์ ์ ์ ์๋ฒ ์ฐ์ | ์ฒ๋ฆฌ ์๊ฐ์ด ๋ค์ํ ๋ |
IP Hash | ip_hash; | ํด๋ผ์ด์ธํธ IP ๊ธฐ์ค ๊ณ ์ | ์ธ์
์ ์ง๊ฐ ํ์ํ ๋ |
Weighted | weight=N; | ๋น์จ์ ๋ฐ๋ผ ๋ถ๋ฐฐ | ์๋ฒ ์ฑ๋ฅ์ด ๋ค๋ฅผ ๋ |
Random | random; | ๋ฌด์์ ๋ถ๋ฐฐ | ๊ฐ๋จํ ๋ถ์ฐ์ด ํ์ํ ๋ |
Hash | hash $๋ณ์; | ํน์ ๊ฐ ๊ธฐ์ค ๊ณ ์ | URL ์บ์ฑ ํจ์จํ |
upstream ๋ธ๋ก โ ํต์ฌ ์ค์
๋ชจ๋ ๋ก๋ ๋ฐธ๋ฐ์ฑ์ ์์์ upstream ๋ธ๋ก์ด์์!
# upstream = ๋ฐฑ์๋ ์๋ฒ ๊ทธ๋ฃน์ ์ ์ํ๋ ๋ธ๋ก
upstream backend {
server localhost:8080;
server localhost:8081;
server localhost:8082;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass <http://backend>; # upstream ์ด๋ฆ์ ๊ทธ๋๋ก ์ฌ์ฉ
}
}
Plain Text
๋ณต์ฌ
upstream ๋ธ๋ก์ ์๋ฒ ์ฃผ์๋ฅผ ๋์ดํ๋ฉด, Nginx๊ฐ ์์์ ๋ถ์ฐํด์ค์!
์๊ณ ๋ฆฌ์ฆ๋ณ ์์ธ ์ค์
1. Round Robin (๋ผ์ด๋ ๋ก๋น) โ ๊ธฐ๋ณธ๊ฐ
sequenceDiagram
participant LB as โ๏ธ Nginx
participant S1 as ๐ฅ๏ธ Server 1
participant S2 as ๐ฅ๏ธ Server 2
participant S3 as ๐ฅ๏ธ Server 3
LB->>S1: ์์ฒญ 1
LB->>S2: ์์ฒญ 2
LB->>S3: ์์ฒญ 3
LB->>S1: ์์ฒญ 4 (๋ค์ ์ฒ์์ผ๋ก)
LB->>S2: ์์ฒญ 5Mermaid
๋ณต์ฌ
upstream backend {
# ๋ณ๋ ์ค์ ์์ผ๋ฉด ์๋์ผ๋ก ๋ผ์ด๋ ๋ก๋น
server localhost:8080;
server localhost:8081;
server localhost:8082;
}
Plain Text
๋ณต์ฌ
ํน์ง: ๊ฐ์ฅ ๋จ์ํ๊ณ ๊ณตํํ๊ฒ ๋ถ๋ฐฐ. ์๋ฒ ์ฑ๋ฅ์ด ๋์ผํ ๊ฒฝ์ฐ ์ต์ ์ด์์.
2. Least Connections (์ต์ ์ฐ๊ฒฐ) โ ์ค๋ฌด ์ถ์ฒ
graph LR
LB["โ๏ธ Nginx"] -->|"๋ค์ ์์ฒญ"| S2
S1["๐ฅ๏ธ Server 1<br/>ํ์ฌ ์ฐ๊ฒฐ: 10๊ฐ"] -.->|"๋ฐ์จ"| LB
S2["๐ฅ๏ธ Server 2<br/>ํ์ฌ ์ฐ๊ฒฐ: 2๊ฐ โญ"] -.->|"์ฌ์ ์์"| LB
S3["๐ฅ๏ธ Server 3<br/>ํ์ฌ ์ฐ๊ฒฐ: 7๊ฐ"] -.->|"๋ณดํต"| LB
style S2 fill:#90EE90Mermaid
๋ณต์ฌ
upstream backend {
least_conn; # ์ฐ๊ฒฐ ์๊ฐ ๊ฐ์ฅ ์ ์ ์๋ฒ๋ก ์์ฒญ ์ ๋ฌ
server localhost:8080;
server localhost:8081;
server localhost:8082;
}
Plain Text
๋ณต์ฌ
ํน์ง: ์ฒ๋ฆฌ ์๊ฐ์ด ๋ค์ฅ๋ ์ฅํ API ์๋ฒ์ ๋ฑ! ํ์ผ ์
๋ก๋, ๋์์ ์ฒ๋ฆฌ ๋ฑ์ ์ ๋ฆฌํด์.
3. IP Hash โ ์ธ์ ๊ณ ์
graph LR
C1["๐ค IP: 1.1.1.1"] -->|"ํญ์"| S1["๐ฅ๏ธ Server 1"]
C2["๐ค IP: 2.2.2.2"] -->|"ํญ์"| S2["๐ฅ๏ธ Server 2"]
C3["๐ค IP: 3.3.3.3"] -->|"ํญ์"| S1["๐ฅ๏ธ Server 1"]
C4["๐ค IP: 4.4.4.4"] -->|"ํญ์"| S3["๐ฅ๏ธ Server 3"]
LB["โ๏ธ Nginx<br/>(ip_hash)"] --- C1
LB --- C2
LB --- C3
LB --- C4
style LB fill:#FFD700Mermaid
๋ณต์ฌ
upstream backend {
ip_hash; # ํด๋ผ์ด์ธํธ IP๋ฅผ ํด์ํด์ ํญ์ ๊ฐ์ ์๋ฒ๋ก
server localhost:8080;
server localhost:8081;
server localhost:8082;
}
Plain Text
๋ณต์ฌ
ํน์ง: ๊ฐ์ ์ฌ์ฉ์๋ ํญ์ ๊ฐ์ ์๋ฒ์ ์ ์ํด์. ๋ก๊ทธ์ธ ์ธ์
์ด ์๋ฒ์ ์ ์ฅ๋ ๊ฒฝ์ฐ ํ์! ๋จ, ์๋ฒ๊ฐ ๋ค์ด๋๋ฉด ์ธ์
์ด ๋ ์๊ฐ ์ ์์ด์.
4. Weighted (๊ฐ์ค์น)
graph LR
LB["โ๏ธ Nginx"] -->|"6๋ฒ ์ค 3๋ฒ"| S1["๐ฅ๏ธ Server 1<br/>weight=3<br/>(๊ณ ์ฑ๋ฅ ์๋ฒ)"]
LB -->|"6๋ฒ ์ค 2๋ฒ"| S2["๐ฅ๏ธ Server 2<br/>weight=2<br/>(์ค๊ฐ ์๋ฒ)"]
LB -->|"6๋ฒ ์ค 1๋ฒ"| S3["๐ฅ๏ธ Server 3<br/>weight=1<br/>(์ ์ฑ๋ฅ ์๋ฒ)"]
style S1 fill:#90EE90
style S2 fill:#FFD700
style S3 fill:#FFB366Mermaid
๋ณต์ฌ
upstream backend {
server localhost:8080 weight=3; # 3/6 = 50% ํธ๋ํฝ
server localhost:8081 weight=2; # 2/6 = 33% ํธ๋ํฝ
server localhost:8082 weight=1; # 1/6 = 17% ํธ๋ํฝ
}
Plain Text
๋ณต์ฌ
ํน์ง: ์๋ฒ ์ฌ์์ด ๋ค๋ฅผ ๋ ์ ์ฉํด์. ๊ณ ์ฑ๋ฅ ์๋ฒ์ ๋ ๋ง์ ์์ฒญ์ ๋ณด๋ผ ์ ์์ด์.
์๋ฒ ์ํ ์ค์
์๋ฒ๋ง๋ค ํน์ ์ํ๋ฅผ ์ง์ ํ ์ ์์ด์!
upstream backend {
server localhost:8080; # ์ ์ ์๋ฒ
server localhost:8081 down; # ์์ ๋นํ์ฑํ (์ ์ง๋ณด์ ์ค)
server localhost:8082 backup; # ํ์์ ์ ์ฐ๊ณ ๋ค๋ฅธ ์๋ฒ ๋ชจ๋ ๋ค์ด ์ ์ฌ์ฉ
server localhost:8083 max_fails=3 # 3๋ฒ ์คํจํ๋ฉด
fail_timeout=30s; # 30์ด๊ฐ ์ ์ธ
}
Plain Text
๋ณต์ฌ
์ต์
| ์ค๋ช
| ์ฌ์ฉ ์ํฉ |
weight=N | ๋ถ๋ฐฐ ๋น์จ ์กฐ์ | ์๋ฒ ์ฑ๋ฅ ์ฐจ์ด๊ฐ ์์ ๋ |
max_fails=N | N๋ฒ ์คํจ ์ ๋นํ์ฑํ | ์๋ ์ฅ์ ๊ฐ์ง |
fail_timeout=Ns | N์ด ํ ์ฌ์๋ | max_fails์ ํจ๊ป ์ฌ์ฉ |
down | ์๋ฒ ์์ ๋นํ์ฑํ | ์ ์ง๋ณด์ |
backup | ์๋น ์๋ฒ | ๋ชจ๋ ์๋ฒ ๋ค์ด ์ ํฌ์
|
max_conns=N | ์ต๋ ๋์ ์ฐ๊ฒฐ ์ ์ ํ | ์๋ฒ ๋ณดํธ |
Health Check (ํฌ์ค ์ฒดํฌ)
์๋ฒ๊ฐ ์ด์์๋์ง ์ฃผ๊ธฐ์ ์ผ๋ก ํ์ธ!
stateDiagram-v2
[*] --> Active: ์๋ฒ ์ ์
Active --> Inactive: ์์ฒญ ์คํจ (max_fails ์ด๊ณผ)
Inactive --> Active: fail_timeout ํ ์ฌ์๋ ์ฑ๊ณต
Inactive --> Inactive: ์ฌ์๋ ์คํจMermaid
๋ณต์ฌ
ํจ์๋ธ ํฌ์ค ์ฒดํฌ (Nginx ๋ฌด๋ฃ)
์์ฒญ์ด ์คํจํ๋ฉด ์๋์ผ๋ก ์๋ฒ๋ฅผ ๋นํ์ฑํํด์:
upstream backend {
server localhost:8080 max_fails=3 fail_timeout=30s;
server localhost:8081 max_fails=3 fail_timeout=30s;
}
server {
location / {
proxy_pass <http://backend>;
# ์คํจ ์ ๋ค์ ์๋ฒ๋ก ์๋ ์ฌ์๋
proxy_next_upstream error timeout http_500 http_502 http_503;
proxy_next_upstream_tries 2;
}
}
Plain Text
๋ณต์ฌ
์กํฐ๋ธ ํฌ์ค ์ฒดํฌ (Nginx Plus ์ ๋ฃ)
upstream backend {
server localhost:8080;
server localhost:8081;
}
server {
location / {
proxy_pass <http://backend>;
health_check interval=5s fails=2 passes=2 uri=/health;
}
}
Plain Text
๋ณต์ฌ
๋ฌด๋ฃ ๋ฒ์ ์์ ํฌ์ค ์ฒดํฌ๊ฐ ํ์ํ๋ค๋ฉด nginx_upstream_check_module ๊ฐ์ ์จ๋ํํฐ ๋ชจ๋์ ์ฌ์ฉํ๋ฉด ๋ผ์.
Keepalive ์ฐ๊ฒฐ ์ต์ ํ
๋ฐฑ์๋ ์๋ฒ์ ์ฐ๊ฒฐ์ ์ฌ์ฌ์ฉํด์ ์ฑ๋ฅ์ ๋์ฌ์!
upstream backend {
server localhost:8080;
server localhost:8081;
keepalive 32; # ์ต๋ 32๊ฐ ์ฐ๊ฒฐ ์ฌ์ฌ์ฉ (์ปค๋ฅ์
ํ)
}
server {
location /api/ {
proxy_pass <http://backend>;
# keepalive๋ฅผ ์ฐ๋ ค๋ฉด ์๋ ๋ ์ค์ด ๋ฐ๋์ ํ์ํด์!
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
Plain Text
๋ณต์ฌ
keepalive ์์ด: ์์ฒญํ ๋๋ง๋ค TCP ์ฐ๊ฒฐ ์๋ก ๋งบ์ โ ๋๋ฆผ
keepalive ์์ด: ๊ธฐ์กด ์ฐ๊ฒฐ ์ฌ์ฌ์ฉ โ ๋น ๋ฆ, ์๋ฒ ๋ฆฌ์์ค ์ ์ฝ
์ค์ ์ค์ ์์
Spring Boot ๊ณ ๊ฐ์ฉ์ฑ ๊ตฌ์ฑ
upstream spring_backend {
least_conn;
server 10.0.0.10:8080 weight=2 max_fails=3 fail_timeout=30s;
server 10.0.0.11:8080 weight=2 max_fails=3 fail_timeout=30s;
server 10.0.0.12:8080 weight=1 max_fails=3 fail_timeout=30s backup;
keepalive 64;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://spring_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
proxy_next_upstream error timeout http_500 http_502 http_503;
proxy_next_upstream_tries 2;
}
}
Plain Text
๋ณต์ฌ
๋ฌด์ค๋จ ๋ฐฐํฌ ํ๋ก์ฐ
sequenceDiagram
participant Dev as ๐จโ๐ป ๊ฐ๋ฐ์
participant LB as โ๏ธ Nginx
participant S1 as ๐ฅ๏ธ Server 1
participant S2 as ๐ฅ๏ธ Server 2
Note over S1,S2: ํ์์ โ ๋ ์๋ฒ ๋ชจ๋ ํธ๋ํฝ ์ฒ๋ฆฌ
Dev->>S1: ๋ฐฐํฌ ์์ (S1 ์ข
๋ฃ)
Note over LB: S1 max_fails ๊ฐ์ง โ ์๋ ์ ์ธ
LB->>S2: S2๋ก๋ง ์์ฒญ ์ ๋ฌ
Dev->>S1: ์ ๋ฒ์ ๋ฐฐํฌ ํ ๊ธฐ๋
Note over LB: S1 ๋ณต๊ตฌ ๊ฐ์ง โ ์๋ ํฌํจ
LB->>S1: S1, S2 ์์ชฝ์ผ๋ก ๋ถ๋ฐฐ ์ฌ๊ฐ
Dev->>S2: S2๋ ๊ฐ์ ๋ฐฉ์์ผ๋ก ๋ฐฐํฌ
Note over LB: S2 max_fails ๊ฐ์ง โ ์๋ ์ ์ธ
LB->>S1: S1์ผ๋ก๋ง ์์ฒญ ์ ๋ฌ
Dev->>S2: ์ ๋ฒ์ ๋ฐฐํฌ ํ ๊ธฐ๋
Note over S1,S2: ๋ฐฐํฌ ์๋ฃ โ ๋ ์๋ฒ ๋ชจ๋ ์ต์ ๋ฒ์ Mermaid
๋ณต์ฌ
์ฑ๋ฅ ๋น๊ต
๊ตฌ์ฑ | ์ด๋น ์ฒ๋ฆฌ ์์ฒญ | ์ฅ์ ๋ณต๊ตฌ | ๋ฐฐํฌ ๋ฐฉ์ |
์๋ฒ 1๋ | 1,000 RPS | ์๋น์ค ์ค๋จ | |
์๋ฒ 2๋ (๋ก๋๋ฐธ๋ฐ์ฑ) | 2,000 RPS | ๋ฌด์ค๋จ | |
์๋ฒ 4๋ (๋ก๋๋ฐธ๋ฐ์ฑ) | 4,000 RPS | ๋ฌด์ค๋จ |
RPS(Requests Per Second) โ ์ด๋น ์ฒ๋ฆฌ ๊ฐ๋ฅํ ์์ฒญ ์. ์๋ฒ๋ฅผ N๋ฐฐ ๋๋ฆฌ๋ฉด ์ฒ๋ฆฌ๋๋ ๊ฑฐ์ N๋ฐฐ!
๋ก๋ ๋ฐธ๋ฐ์ฑ ์ํ ํ์ธ
# Nginx ์ค์ ํ
์คํธ
nginx -t
# ์ค์ ์ ์ฉ
systemctl reload nginx
# ์๋ฌ ๋ก๊ทธ ์ค์๊ฐ ํ์ธ
tail -f /var/log/nginx/error.log
# ์ฐ๊ฒฐ ์ํ ํ์ธ
ss -tnp | grep nginx
# ๊ฐ upstream ์๋ฒ ์๋ต ํ
์คํธ
curl -H "Host: api.example.com" <http://localhost>
Bash
๋ณต์ฌ
์ฒดํฌ๋ฆฌ์คํธ
upstream ๋ธ๋ก์ ๋ฐฑ์๋ ์๋ฒ ์ฃผ์ ๋ชจ๋ ๋ฑ๋ก?
๋ก๋ ๋ฐธ๋ฐ์ฑ ์๊ณ ๋ฆฌ์ฆ ์ ํ ์๋ฃ? (round robin / least_conn / ip_hash)
max_fails, fail_timeout ์ค์ ์ผ๋ก ์๋ ์ฅ์ ๊ฐ์ง?
keepalive ์ค์ ์ผ๋ก ์ฐ๊ฒฐ ์ฌ์ฌ์ฉ ์ต์ ํ?
proxy_next_upstream์ผ๋ก ์คํจ ์ ์๋ ์ฌ์๋?
backup ์๋ฒ ์ง์ ์๋ฃ?
nginx -t ์ค์ ํ
์คํธ ํต๊ณผ?
๊ฐ ๋ฐฑ์๋ ์๋ฒ ์ง์ ์ ์ ํ
์คํธ ์๋ฃ?
๋ฌด์ค๋จ ๋ฐฐํฌ ํ๋ฆ ๊ฒ์ฆ ์๋ฃ?
ํฌ์ค ์ฒดํฌ ์๋ํฌ์ธํธ(/health) ๊ตฌ์ฑ ์๋ฃ?
ํต์ฌ ์ ๋ฆฌ
๋ก๋ ๋ฐธ๋ฐ์ฑ = ์ฌ๋ฌ ์๋ฒ์ ์์ฒญ์ ๋ถ์ฐํด ์ฑ๋ฅ๊ณผ ์์ ์ฑ์ ๋์์ ํ๋ณด
upstream ๋ธ๋ก = ๋ฐฑ์๋ ์๋ฒ ๊ทธ๋ฃน ์ ์ โ proxy_pass๋ก ์ฐ๊ฒฐ
์๊ณ ๋ฆฌ์ฆ ์ ํ:
๊ธฐ๋ณธ์ Round Robin, ์ฒ๋ฆฌ ์๊ฐ ๋ค์ํ๋ฉด Least Conn, ์ธ์
์ ์ง ํ์ํ๋ฉด IP Hash
max_fails + fail_timeout = ์๋ ์ฅ์ ๊ฐ์ง ๋ฐ ๋ณต๊ตฌ (ํจ์๋ธ ํฌ์ค ์ฒดํฌ)
keepalive = ์ฐ๊ฒฐ ์ฌ์ฌ์ฉ์ผ๋ก ์ฑ๋ฅ ํฅ์ (proxy_http_version 1.1 + Connection "" ํ์)
backup ์๋ฒ = ๋ชจ๋ ์๋ฒ ๋ค์ด ์ ์ตํ์ ๋ณด๋ฃจ
๋ฌด์ค๋จ ๋ฐฐํฌ = ์๋ฒ ํ๋์ฉ ๋ด๋ ธ๋ค ์ฌ๋ฆฌ๋ฉฐ ์๋น์ค ์ค๋จ ์์ด ์
๋ฐ์ดํธ ๊ฐ๋ฅ



