はじめに
「サーバー1台で限界が来た」
この瞬間、ロードバランサーが必要になる。
でも、なんとなくALBを置いて終わりにしていないだろうか。
- なぜL4とL7があるのか
- ラウンドロビンで本当にいいのか
- スティッキーセッションの罠
- ヘルスチェックの設計
ロードバランサーは「置くだけ」では不十分だ。
この記事では、ロードバランサーの本質から、実務で必要な設計・実装パターンまでを解説する。
なぜロードバランサーが必要か
単一サーバーの限界
flowchart TB
Client["Client"]
Server["Server<br/>(1台)<br/>← 全リクエストがここに集中"]
Client --> Server
style Client fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style Server fill:#ffebee,stroke:#c62828,stroke-width:2px
問題点:
- 処理能力の限界(CPU、メモリ、I/O)
- 単一障害点(落ちたら全停止)
- メンテナンス時にサービス停止
ロードバランサーによる解決
flowchart TB
Client["Client"]
LB["Load<br/>Balancer"]
S1["Server1"]
S2["Server2"]
S3["Server3"]
Client --> LB
LB --> S1
LB --> S2
LB --> S3
style Client fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style LB fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style S1 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style S2 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style S3 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
メリット:
| メリット | 説明 |
|---|---|
| スケーラビリティ | サーバーを増やせば処理能力が上がる |
| 可用性 | 1台落ちても他が処理を継続 |
| メンテナンス性 | ローリングアップデートが可能 |
| 柔軟性 | サーバーの追加・削除が容易 |
L4 vs L7:どの層で分散するか
OSI参照モデルのおさらい
Layer 7: アプリケーション層(HTTP, HTTPS, WebSocket)
Layer 6: プレゼンテーション層
Layer 5: セッション層
Layer 4: トランスポート層(TCP, UDP)
Layer 3: ネットワーク層(IP)
Layer 2: データリンク層
Layer 1: 物理層
L4ロードバランサー
トランスポート層(TCP/UDP)で動作
クライアント → [L4 LB] → サーバー
IPとポートを見て振り分け
特徴:
- 高速(パケットの中身を見ない)
- シンプル
- SSL終端はサーバー側で行う
- HTTPの内容(URL、ヘッダー)は見えない
ユースケース:
- 高スループットが必要
- TCP/UDPレベルの分散で十分
- データベース接続の分散
L7ロードバランサー
アプリケーション層(HTTP/HTTPS)で動作
クライアント → [L7 LB] → サーバー
HTTPの内容を見て振り分け
特徴:
- HTTPヘッダー、URL、Cookie等を見て振り分けできる
- SSL終端をLBで行える
- コンテンツベースのルーティング
- L4より遅い(パケットを解析するため)
ユースケース:
- URLパスで振り分け(/api/* → APIサーバー)
- ホスト名で振り分け(api.example.com → APIサーバー)
- 認証・認可をLBで行う
- A/Bテスト
L4 vs L7 比較
| 項目 | L4 | L7 |
|---|---|---|
| 処理速度 | ✅ 高速 | ⚪ 普通 |
| 機能 | ⚪ シンプル | ✅ 高機能 |
| SSL終端 | サーバー側 | LBで可能 |
| コンテンツルーティング | ❌ 不可 | ✅ 可能 |
| WebSocket | ✅ そのまま通る | ⚪ 設定必要 |
| 料金(AWS) | NLB: 安い | ALB: やや高い |
実務での選択
flowchart TD
Q1["URLやヘッダーで振り分けが必要?"]
Q2["高スループットが必要?"]
A1["L7<br/>ALB, Nginx"]
A2["L4<br/>NLB, HAProxy"]
A3["L7<br/>機能が多いので便利"]
Q1 -->|"Yes"| A1
Q1 -->|"No"| Q2
Q2 -->|"Yes"| A2
Q2 -->|"No"| A3
style Q1 fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style Q2 fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style A1 fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style A2 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style A3 fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
ロードバランシングアルゴリズム
1. ラウンドロビン(Round Robin)
flowchart LR
R1["リクエスト1"]
R2["リクエスト2"]
R3["リクエスト3"]
R4["リクエスト4<br/>(最初に戻る)"]
S1["Server1"]
S2["Server2"]
S3["Server3"]
R1 --> S1
R2 --> S2
R3 --> S3
R4 --> S1
style R1 fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style R2 fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style R3 fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style R4 fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style S1 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style S2 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style S3 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
特徴:
- シンプル
- サーバーの性能が均一なら公平
- サーバーの負荷状況を考慮しない
設定例(Nginx):
upstream backend {
server server1.example.com;
server server2.example.com;
server server3.example.com;
# デフォルトでラウンドロビン
}
2. 重み付きラウンドロビン(Weighted Round Robin)
flowchart TB
subgraph Weight["重みに応じて振り分け"]
W1["weight=3: Server1"]
W2["weight=2: Server2"]
W3["weight=1: Server3"]
end
Dist["リクエストの分配<br/>━━━━━━<br/>Server1 → Server1 → Server1 →<br/>Server2 → Server2 →<br/>Server3 →<br/>(繰り返し)"]
Weight --> Dist
style W1 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style W2 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style W3 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style Dist fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
特徴:
- サーバー性能の違いを考慮できる
- 高性能サーバーに多くリクエストを振る
設定例(Nginx):
upstream backend {
server server1.example.com weight=3; # 3倍のリクエスト
server server2.example.com weight=2; # 2倍のリクエスト
server server3.example.com weight=1; # 基準
}
3. 最小接続数(Least Connections)
flowchart LR
S1["Server1<br/>10接続中"]
S2["Server2<br/>5接続中<br/>← 次のリクエストはここ"]
S3["Server3<br/>8接続中"]
Next["次のリクエスト"] --> S2
style S1 fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style S2 fill:#e8f5e9,stroke:#4caf50,stroke-width:3px
style S3 fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style Next fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
特徴:
- 現在の接続数が少ないサーバーに振る
- 処理時間にばらつきがある場合に有効
- 実際の負荷を考慮できる
設定例(Nginx):
upstream backend {
least_conn;
server server1.example.com;
server server2.example.com;
server server3.example.com;
}
4. IPハッシュ(IP Hash)
flowchart LR
Client["クライアントIP<br/>192.168.1.100"]
Hash["hash(192.168.1.100) % 3 = 1"]
S2["Server2<br/>← 常にここへ"]
Client --> Hash --> S2
style Client fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style Hash fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style S2 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
特徴:
- 同じクライアントは常に同じサーバーへ
- セッション維持が必要な場合に使用
- サーバー台数が変わると再配分される
設定例(Nginx):
upstream backend {
ip_hash;
server server1.example.com;
server server2.example.com;
server server3.example.com;
}
5. 一貫性ハッシュ(Consistent Hashing)
flowchart TB
subgraph Ring["Hash Ring"]
direction LR
S1["S1"] --> S2["S2"]
S2 --> S3["S3"]
S3 -.->|"リング状"| S1
end
Note["Client群がリング上の位置で振り分け"]
Ring --> Note
style S1 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style S2 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style S3 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style Note fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
特徴:
- サーバー追加・削除時の影響が最小限
- キャッシュサーバーの分散に最適
- 再配分されるキーが少ない
通常のハッシュとの違い:
# 通常のハッシュ:サーバー3台→2台になった場合
hash(key) % 3 → hash(key) % 2
→ ほとんどのキーが再配分される
# 一貫性ハッシュ:サーバー3台→2台になった場合
→ 削除されたサーバーのキーだけが再配分される
6. 最小応答時間(Least Response Time)
Server1: 平均応答50ms
Server2: 平均応答30ms ← 次のリクエストはここ
Server3: 平均応答45ms
特徴:
- 最も応答が速いサーバーに振る
- 実際のパフォーマンスを考慮
- 動的な負荷分散
設定例(Nginx Plus):
upstream backend {
least_time header; # 最初のバイトまでの時間
server server1.example.com;
server server2.example.com;
}
アルゴリズムの選択指針
flowchart TD
Q1["セッション維持が必要?"]
Q2["サーバー性能は均一?"]
Q3["処理時間は一定?"]
A1["IP Hash or<br/>Sticky Session"]
A2["Round Robin"]
A3["Least Connections"]
A4["Weighted<br/>Round Robin"]
Q1 -->|Yes| A1
Q1 -->|No| Q2
Q2 -->|Yes| Q3
Q2 -->|No| A4
Q3 -->|Yes| A2
Q3 -->|No| A3
style Q1 fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style Q2 fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style Q3 fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style A1 fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style A2 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style A3 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style A4 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
【実装】Nginx
基本設定
http {
# アップストリーム定義
upstream backend {
least_conn;
server 10.0.0.1:8080 weight=3;
server 10.0.0.2:8080 weight=2;
server 10.0.0.3:8080 weight=1 backup; # バックアップサーバー
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend;
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;
}
}
}
SSL終端
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://backend; # バックエンドはHTTP
}
}
パスベースルーティング
upstream api_servers {
server api1.internal:8080;
server api2.internal:8080;
}
upstream web_servers {
server web1.internal:3000;
server web2.internal:3000;
}
server {
listen 80;
# /api/* は API サーバーへ
location /api/ {
proxy_pass http://api_servers;
}
# それ以外は Web サーバーへ
location / {
proxy_pass http://web_servers;
}
}
ヘルスチェック
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
# パッシブヘルスチェック
# 3回失敗したら30秒間除外
server 10.0.0.1:8080 max_fails=3 fail_timeout=30s;
}
# Nginx Plus ではアクティブヘルスチェックが可能
upstream backend {
zone backend 64k;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
health_check interval=5s fails=3 passes=2;
}
WebSocket対応
location /ws/ {
proxy_pass http://websocket_servers;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
# タイムアウト設定(長めに)
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
【実装】HAProxy
基本設定
global
log stdout format raw local0
maxconn 4096
defaults
mode http
log global
option httplog
option dontlognull
timeout connect 5s
timeout client 50s
timeout server 50s
frontend http_front
bind *:80
default_backend http_back
backend http_back
balance roundrobin
option httpchk GET /health
http-check expect status 200
server server1 10.0.0.1:8080 check weight 3
server server2 10.0.0.2:8080 check weight 2
server server3 10.0.0.3:8080 check weight 1 backup
# 統計画面
listen stats
bind *:8404
stats enable
stats uri /stats
stats refresh 10s
L4モード(TCP)
defaults
mode tcp
timeout connect 5s
timeout client 30s
timeout server 30s
frontend mysql_front
bind *:3306
default_backend mysql_back
backend mysql_back
balance leastconn
option mysql-check user haproxy
server mysql1 10.0.0.1:3306 check
server mysql2 10.0.0.2:3306 check backup
ACL(アクセス制御リスト)によるルーティング
frontend http_front
bind *:80
# ACL定義
acl is_api path_beg /api
acl is_websocket hdr(Upgrade) -i websocket
acl is_admin path_beg /admin
acl is_internal src 10.0.0.0/8
# ルーティング
use_backend api_servers if is_api
use_backend websocket_servers if is_websocket
use_backend admin_servers if is_admin is_internal
default_backend web_servers
backend api_servers
balance roundrobin
server api1 10.0.0.1:8080 check
server api2 10.0.0.2:8080 check
backend websocket_servers
balance source
timeout tunnel 1h
server ws1 10.0.0.3:8080 check
server ws2 10.0.0.4:8080 check
backend web_servers
balance roundrobin
server web1 10.0.0.5:3000 check
server web2 10.0.0.6:3000 check
【実装】AWS ALB/NLB
ALB(Application Load Balancer)
# Terraform
# ALB作成
resource "aws_lb" "main" {
name = "my-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = var.public_subnets
enable_deletion_protection = true
tags = {
Environment = "production"
}
}
# ターゲットグループ
resource "aws_lb_target_group" "main" {
name = "my-target-group"
port = 8080
protocol = "HTTP"
vpc_id = var.vpc_id
health_check {
enabled = true
healthy_threshold = 2
unhealthy_threshold = 3
timeout = 5
interval = 30
path = "/health"
matcher = "200"
}
stickiness {
type = "lb_cookie"
cookie_duration = 86400
enabled = true
}
}
# リスナー
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.main.arn
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS-1-2-2017-01"
certificate_arn = var.certificate_arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.main.arn
}
}
# パスベースルーティング
resource "aws_lb_listener_rule" "api" {
listener_arn = aws_lb_listener.https.arn
priority = 100
action {
type = "forward"
target_group_arn = aws_lb_target_group.api.arn
}
condition {
path_pattern {
values = ["/api/*"]
}
}
}
NLB(Network Load Balancer)
# NLB作成
resource "aws_lb" "nlb" {
name = "my-nlb"
internal = false
load_balancer_type = "network"
subnets = var.public_subnets
enable_cross_zone_load_balancing = true
}
# ターゲットグループ(TCP)
resource "aws_lb_target_group" "tcp" {
name = "my-nlb-target"
port = 8080
protocol = "TCP"
vpc_id = var.vpc_id
health_check {
enabled = true
healthy_threshold = 2
unhealthy_threshold = 2
interval = 10
protocol = "TCP"
}
}
# リスナー(TCP)
resource "aws_lb_listener" "tcp" {
load_balancer_arn = aws_lb.nlb.arn
port = "443"
protocol = "TCP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.tcp.arn
}
}
ALB vs NLB 比較
| 項目 | ALB | NLB |
|---|---|---|
| レイヤー | L7 | L4 |
| プロトコル | HTTP/HTTPS/gRPC | TCP/UDP/TLS |
| 性能 | 良い | 非常に高い |
| 固定IP | ❌ | ✅ |
| WebSocket | ✅(設定必要) | ✅(そのまま) |
| パスルーティング | ✅ | ❌ |
| 料金 | やや高い | 安い |
【実務】ヘルスチェックの設計
ヘルスチェックの種類
1. TCP接続チェック
LB → TCP SYN → Server
LB ← TCP ACK ← Server
→ ポートが開いていればOK
メリット: 軽量、シンプル デメリット: アプリケーションの状態はわからない
2. HTTPチェック
LB → GET /health → Server
LB ← 200 OK ← Server
→ 200が返ればOK
メリット: アプリケーションの状態がわかる デメリット: オーバーヘッドがある
3. カスタムチェック
# /health エンドポイントの実装
@app.route('/health')
def health():
checks = {
'database': check_database(),
'redis': check_redis(),
'disk': check_disk_space(),
}
if all(checks.values()):
return jsonify({'status': 'healthy', 'checks': checks}), 200
else:
return jsonify({'status': 'unhealthy', 'checks': checks}), 503
ヘルスチェックの設計ポイント
1. 適切な間隔
interval = 10s # チェック間隔
timeout = 5s # タイムアウト
unhealthy = 3 # 何回失敗で除外
healthy = 2 # 何回成功で復帰
検出時間の計算:
最大検出時間 = interval × unhealthy = 10 × 3 = 30秒
間隔を短くすると検出が早くなるが、負荷が増える。
2. 深さの選択
# Shallow(浅い): プロセスが動いているか
@app.route('/health/live')
def liveness():
return 'OK', 200
# Deep(深い): 依存サービスも含めて正常か
@app.route('/health/ready')
def readiness():
if not database.is_connected():
return 'Database unavailable', 503
if not redis.is_connected():
return 'Redis unavailable', 503
return 'OK', 200
LBには Shallow チェックを使うことが多い。
Deep チェックだと、DBが落ちたら全サーバーが unhealthy になり、全滅する。
3. グレースフルシャットダウン
import signal
# シャットダウンシグナルを受けたら
is_shutting_down = False
def shutdown_handler(signum, frame):
global is_shutting_down
is_shutting_down = True
# 既存リクエストの処理完了を待つ
time.sleep(30)
sys.exit(0)
signal.signal(signal.SIGTERM, shutdown_handler)
@app.route('/health')
def health():
if is_shutting_down:
return 'Shutting down', 503 # LBから除外される
return 'OK', 200
【実務】セッション管理
問題:セッションの不整合
flowchart LR
R1["リクエスト1"]
R2["リクエスト2"]
S1["Server1<br/>(セッション作成)"]
S2["Server2<br/>(セッションがない!)"]
R1 --> S1
R2 --> S2
style R1 fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style R2 fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style S1 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style S2 fill:#ffebee,stroke:#c62828,stroke-width:2px
解決策1: スティッキーセッション
upstream backend {
ip_hash; # 同じIPは同じサーバーへ
server server1.example.com;
server server2.example.com;
}
または Cookie ベース:
upstream backend {
server server1.example.com;
server server2.example.com;
sticky cookie srv_id expires=1h;
}
メリット: 実装が簡単 デメリット:
- サーバーがダウンするとセッションが消える
- 負荷が偏る可能性
- スケールしにくい
解決策2: 外部セッションストア
flowchart TB
LB["Load Balancer"]
S1["Server1"]
S2["Server2"]
S3["Server3"]
Redis["Redis<br/>← セッションを集中管理"]
LB --> S1
LB --> S2
LB --> S3
S1 --> Redis
S2 --> Redis
S3 --> Redis
style LB fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style S1 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style S2 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style S3 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style Redis fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
# Python + Flask + Redis Sessions
from flask import Flask
from flask_session import Session
import redis
app = Flask(__name__)
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = redis.from_url('redis://localhost:6379')
Session(app)
# どのサーバーでも同じセッションにアクセス可能
@app.route('/login', methods=['POST'])
def login():
session['user_id'] = request.form['user_id']
return 'Logged in'
メリット: サーバー間でセッション共有、スケールしやすい デメリット: Redisが単一障害点になりうる(クラスタ化で対応)
解決策3: ステートレス設計(JWT)
クライアント → [JWT in Header] → サーバー
↑
署名を検証するだけ
セッションストア不要
import jwt
# ログイン時にJWTを発行
@app.route('/login', methods=['POST'])
def login():
user = authenticate(request.form)
token = jwt.encode(
{'user_id': user.id, 'exp': datetime.utcnow() + timedelta(hours=24)},
SECRET_KEY,
algorithm='HS256'
)
return jsonify({'token': token})
# リクエスト時にJWTを検証
@app.before_request
def verify_token():
token = request.headers.get('Authorization', '').replace('Bearer ', '')
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
g.user_id = payload['user_id']
except jwt.InvalidTokenError:
return 'Unauthorized', 401
メリット: 完全にステートレス、セッションストア不要 デメリット: トークンの無効化が難しい
【実務】高可用性構成
LBの冗長化
flowchart TB
VIP["Virtual IP<br/>(192.168.1.100)"]
LB1["LB1<br/>(Active)"]
LB2["LB2<br/>(Standby)"]
Backend["Backend Servers"]
VIP --> LB1
VIP --> LB2
LB1 <-->|Heartbeat| LB2
LB1 --> Backend
LB2 --> Backend
style VIP fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style LB1 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style LB2 fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style Backend fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
Keepalivedによる冗長化
# /etc/keepalived/keepalived.conf (Master)
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass secret
}
virtual_ipaddress {
192.168.1.100
}
track_script {
chk_nginx
}
}
vrrp_script chk_nginx {
script "/usr/bin/pgrep nginx"
interval 2
weight 2
}
# /etc/keepalived/keepalived.conf (Backup)
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 99 # Masterより低い
advert_int 1
authentication {
auth_type PASS
auth_pass secret
}
virtual_ipaddress {
192.168.1.100
}
}
AWSでの高可用性
flowchart TB
R53["Route 53<br/>(DNS Failover)"]
LB["ALB / NLB<br/>(Multi-AZ, 自動フェイルオーバー)"]
AZa["AZ-a<br/>Servers"]
AZc["AZ-c<br/>Servers"]
R53 --> LB
LB --> AZa
LB --> AZc
style R53 fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style LB fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style AZa fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style AZc fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
AWSのALB/NLBは、複数AZにまたがって自動的に冗長化される。
実務チェックリスト
設計時
- L4とL7、どちらが適切か検討したか
- アルゴリズムの選択は適切か
- セッション管理の方法は決まっているか
- LBの冗長化は考慮されているか
ヘルスチェック
- ヘルスチェックエンドポイントは実装されているか
- チェック間隔とタイムアウトは適切か
- グレースフルシャットダウンは実装されているか
- Deep/Shallowの使い分けは適切か
セキュリティ
- SSL終端の場所は適切か
- セキュリティグループ/ファイアウォールは設定されているか
- アクセスログは取れているか
運用
- メトリクス(接続数、レイテンシ、エラー率)は監視されているか
- アラートは設定されているか
- 負荷テストは実施したか
まとめ
ロードバランサーの本質は、トラフィックの分散と可用性の向上だ。
使い分け
| 要件 | 選択 |
|---|---|
| 高スループット、シンプル | L4(NLB, HAProxy TCP mode) |
| パスルーティング、SSL終端 | L7(ALB, Nginx, HAProxy HTTP mode) |
アルゴリズム
| 要件 | アルゴリズム |
|---|---|
| シンプル、均一性能 | Round Robin |
| 性能差あり | Weighted Round Robin |
| 処理時間にばらつき | Least Connections |
| セッション維持 | IP Hash / Sticky Session |
セッション管理
| 要件 | 方法 |
|---|---|
| シンプル | Sticky Session |
| スケーラブル | 外部セッションストア(Redis) |
| 完全ステートレス | JWT |
「とりあえずALB」ではなく、要件に合った選択をしよう。
それが、スケールする設計の第一歩だ。