ロードバランサとは
ロードバランサは、複数のバックエンドサーバにリクエストを分散させるための仕組み。
DNSラウンドロビン
似たような負荷分散の仕組みとしてDNSラウンドロビンがある。
これはバックエンドサーバのIPアドレスをDNSレコードとして登録し、DNSの問い合わせ時にバックエンドサーバのIPアドレスを順番に返事することで負荷分散する。
DNSラウンドロビンでは対応できないこと
- DNSの問い合わせ結果はクライアント側でキャッシュされるため
- 負荷が偏る可能性がある
- サーバダウンした時に、即座に切り替えられないクライアントがいる
- パーシステンス機能がない(クライアントごとに同一のサーバに割り振れない)
上記に対応したいときに、ロードバランサが使われる。
L7ロードバランサ
- L7レベルでロードバランシングする
- クライアントから見たときの接続先IPアドレスはLBのIPアドレスになる
- バックエンドサーバから見たときの接続元IPアドレスはLBのIPアドレスになる
- HTTPの情報を用いた、高度な制御ができる
- Cookieを利用したスティッキーセッション
- X-Forwarded-Forヘッダー を利用したクライアントのIPアドレス保持
- HTTP情報を取得するために、LBでTCPコネクションが終端する
- クライアントとLB
- LBとバックエンドサーバ
パケットの流れ
TCP コネクションが LB で終端するため、合計で 2 本のコネクションが張られる。
L4ロードバランサ
- L4以下のレベルでロードバランシングする
- IPレベルでのセッションパーシステンスができる
- 接続元IPが同じなら同じバックエンドサーバに送れる
- IPアドレス変換方式(NAT方式) と MACアドレス変換方式(DSR方式) がある
- ロードバランサでTCPコネクションは終端しない
IPアドレス変換方式(NAT方式)
- NATを利用してL4レベルでロードバランシングする
- クライアントから見たときの接続先IPアドレスはLBのIPアドレスになる
- バックエンドサーバから見たときの接続元IPアドレスは
- LBのIPアドレスになる(SNAT)
- 接続元のIPアドレスになる(NAT。この場合はリアルサーバのデフォルトゲートウェイを LB に設定するなどして LB にパケットを戻す必要がある。)
- NAT変換のために全ての通信がLBを経由する
パケットの流れ
LB で TCP コネクションが終端しないため、コネクションは 1 本しか張られない。
MACアドレス変換方式(DSR方式)
- 宛先MACアドレスを差し替えてロードバランシングする
- ロードバランサとバックエンドサーバは同一セグメントに置く必要がある
- クライアントから見たときの接続先IPアドレスはLBのIPアドレスになる
- バックエンドサーバから見たときの接続元IPアドレスはクライアントのIPアドレスになる
- バックエンドサーバの戻りパケットが、ロードバランサを経由せずにクライアントと直接通信する
- レスポンスの接続元IPアドレスをロードバランサに見せかける必要がある
- ロードバランサのIPアドレスをバックエンドサーバのループバックIPアドレスに設定する
- ループバックIPアドレスはARPに答えないように設定する
パケットの流れ
戻りパケットは LB を経由せずに処理される。
構築してみる
L7ロードバランサ
構成図は以下のとおり。
今回はLBとして、nginx を利用する。
設定
リファレンスを参考に、nginxの設定をする。
http { ... upstream myapp1 { server 10.133.1.2; server 10.133.1.3; } ... server { ... location / { proxy_pass http://myapp1; } } ... }
パケットキャプチャ
次に、バックエンドサーバでhttpdを起動し、クライアントからHTTPリクエストを投げる。
LBのeth0(クライアント側NIC)のパケットをキャプチャする。
# tcpdump port 80 -i eth0 -w eth0.pcap
LBのeth1(バックエンド側NIC)のパケットをキャプチャする。
# tcpdump port 80 -i eth1 -w eth1.pcap
上記から各ethごとにTCPハンドシェイクを行っており、TCPコネクションが2本張られていることがわかる。また、クライアントからLBにHTTPリクエストが送られた後に、LBからバックエンドサーバにTCPコネクションを張っているのがわかる。
接続元IPアドレスをバックエンドサーバに伝える
クライアントの接続元IPアドレスをバックエンドサーバが知りたい場合は、X-Forwarded-Forヘッダーをnginx側で付与すればよい。リファレンス
すると以下のように、HTTPヘッダーに接続元IPアドレスが付与される。
# tcpdump -A port 80 -i eth1 ... .*.{.). GET / HTTP/1.0 X-Forwarded-for: 192.168.11.104 Host: myapp1 Connection: close Cache-Control: max-age=0 ...
IPアドレス変換方式(NAT方式)
構成図は以下のとおり。今回は SNAT を利用する。
以下を参考に、LBとしてipvsadm を利用する。
http://dsas.blog.klab.org/archives/50664843.html
設定
LBでipvsadmの設定をする。
# ipvsadm -C # ipvsadm -A -t 192.168.11.107:80 -s lc # ipvsadm -a -t 192.168.11.107:80 -r 10.133.1.2 -m # ipvsadm -a -t 192.168.11.107:80 -r 10.133.1.3 -m # ipvsadm -Ln IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 192.168.11.107:80 lc -> 10.133.1.2:80 Masq 1 0 0 -> 10.133.1.3:80 Masq 1 0 0
次に、バックエンドサーバでhttpdを起動し、クライアントからHTTPリクエストを投げる。
パケットキャプチャ
LBのeth0(クライアント側NIC)のパケットをキャプチャする。
# tcpdump port 80 -i eth0 -w eth0.pcap
LBのeth1(バックエンド側NIC)のパケットをキャプチャする。
# tcpdump port 80 -i eth1 -w eth1.pcap
上記から、LBで接続元IPや接続先IPの変換はするものの、TCPコネクションが1本しか張られていないことがわかる。(接続元のエフェメラルポート番号がそのまま接続先に渡されており、また、時刻から、LBはパケットを横流ししているようになっている)
MACアドレス変換方式(DSR方式)
構成図は以下のとおり。
以下を参考に、LBとしてipvsadm を利用する。
http://momijiame.tumblr.com/post/71390424576/centos-65-%E3%81%A7-lvs-ipvs-%E3%81%AE-direct-server-return
設定
LBでipvsadmの設定をする。
]# iptables -L ]# ipvsadm -A -t 192.168.11.107:80 -s lc ]# ipvsadm -a -t 192.168.11.107:80 -r 192.168.11.108:80 -g ]# ipvsadm -L IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 192.168.11.107:http lc -> 192.168.11.108:http Route 1 0 0
次に、バックエンドサーバでhttpdを起動し、クライアントからHTTPリクエストを投げる。
パケットキャプチャ
LBのパケットをキャプチャする。
# tcpdump port not 22 -w lb.pcap
戻りパケットがLBを経由しないため、Wiresharkがパケットが欠けてると警告を出している。
バックエンドサーバのパケットをキャプチャする。
# tcpdump port 80 -w backend.pcap
接続先IPアドレスがLBのIPアドレスになっている。また、接続元IPアドレスがクライアントのアドレスになっている。
上記から、戻りパケットだけはLBを経由せずにクライアントにダイレクトに返っていることがわかる。まさにDirect Server Return…