Webサーバの負荷分散をするBIG-IPで、同一Virtual Server (VIP)で受けるHTTPリクエストを、Refererヘッダによって分散先のPoolを切り替えられるようにする。
モチベーション
Virtual Serverを分けられず、かつ、インターネットからのアクセスでユーザの識別、振り分けが困難な環境において、流入元ごとにConnection Limit, Rate Limitを制御したい。特定流入元のキャンペーンなどで通常と異なる大量リクエストが発生する際に、それらのリクエストを制限しつつ、通常のリクエストには影響を与えないようにする。
構成イメージ
動作確認環境
BIG-IP 11.5.3 (HF1) Google Chrome 93.0.4577.63 curl 7.68.0
※本記事の設定・動作はRFCに完全準拠はしていない可能性があります。不具合時の責任は負いかねますのでご了承ください。
Virtual Server・Pool設定
ltm virtual designet.work { destination 192.168.1.10:https ip-protocol tcp mask 255.255.255.255 pool designet-pool-basic profiles { http { } designet.work { context clientside } tcp { } } source 0.0.0.0/0 vs-index 1 } ltm pool designet-pool-basic { members { server1:http { address 192.168.1.11 session monitor-enabled state up } server2:http { address 192.168.1.12 session monitor-enabled state up } } monitor tcp } ltm pool designet-pool-advanced { members { server1:http { address 192.168.1.11 connection-limit 10 session monitor-enabled state up } server2:http { address 192.168.1.12 connection-limit 10 session monitor-enabled state up } } monitor tcp queue-on-connection-limit enabled }
iRule
このような流れでHTTPリクエストを処理する。
一連のiRuleはこちら。部分ごとの解説は後述。
ltm data-group internal designet_referer_datagroup { records { https://hatenablog.com { data designet-pool-advanced } } type string } ltm rule designet_referer_irule { when HTTP_REQUEST { if {[class match [HTTP::header value Referer] starts_with designet_referer_datagroup]}{ set pool [class match -value [HTTP::header value Referer] starts_with designet_referer_datagroup] if {[catch {pool $pool}]}{ return } log local0. "Referer: [HTTP::header "Referer"]" log local0. "LB pool: [LB::server pool]" } if {[HTTP::cookie exists "pool"]}{ log local0. "Cookie: [HTTP::header "Cookie"]" log local0. "Request cookie pool: [HTTP::cookie value "pool"]" if {[catch {pool [HTTP::cookie value "pool"]}]}{ return } log local0. "LB pool: [LB::server pool]" } } when HTTP_RESPONSE { if {[LB::server pool] equals "/Common/designet-pool-advanced"}{ HTTP::cookie insert name pool value [LB::server pool] path / HTTP::cookie expires "pool" 300 log local0. "Response Set-Cookie value: [HTTP::header "Set-Cookie"]" log local0. "LB pool: [LB::server pool]" } } }
Referer振り分け
設定を汎用化するために、Data-Groupを使ってKey-Valueで動的にpoolを設定できるようにする。
ltm data-group internal designet_referer_datagroup { records { https://hatenablog.com { data designet-pool-advanced } } type string }
Refererはパスを含む場合もあるので、starts_with
で広く拾えるようにする。
ltm rule designet_referer_irule { when HTTP_REQUEST { if {[class match [HTTP::header value Referer] starts_with designet_referer_datagroup]}{ set pool [class match -value [HTTP::header value Referer] starts_with designet_referer_datagroup] if {[catch {pool $pool}]}{ return } log local0. "Referer: [HTTP::header "Referer"]" log local0. "LB pool: [LB::server pool]" } } }
設定汎用化、エラーハンドリングのcatch
句はこちらを参考にさせていただきました。ありがとうございます。
Cookie Insert
流入後のユーザ操作ではRefererが自サイトになるため、後続を含めて一連のリクエストとして扱えるよう、Set-Cookie
ヘッダでCookieを付与して識別可能とする (設定汎用化は要検討)。このとき、HTTP::cookie expires
で有効期限を設定することで、セッションが残って制限が継続し続けることを防ぐ。HTTP::cookie secure
, HTTP::cookie httponly
は用途に応じて任意で。
※Persistenceは分散先Poolが決定した後の処理となるため、今回のケースでは有効ではない。
when HTTP_RESPONSE { if {[LB::server pool] equals "/Common/designet-pool-advanced"}{ HTTP::cookie insert name pool value [LB::server pool] path / HTTP::cookie expires "pool" 300 log local0. "Response Set-Cookie value: [HTTP::header "Set-Cookie"]" log local0. "LB pool: [LB::server pool]" } }
なお、サーバでCookieを付与している場合、レスポンスにはSet-Cookie
ヘッダがふたつ存在する。ログ出力ではサーバ付与のCookieのみが表示される。
curl -kv https://test.local --- ... < Set-Cookie: KEY=VALUE; Path=/ < Set-Cookie: pool=/Common/designet-pool-advanced;expires=Sun, 12-Sep-2021 12:49:15 GMT;path=/;
Cookie振り分け
後続リクエストに付与されるpool
をキーに分散先を決定させる。
when HTTP_REQUEST { if {[HTTP::cookie exists "pool"]}{ log local0. "Request cookie pool: [HTTP::cookie value "pool"]" if {[catch {pool [HTTP::cookie value "pool"]}]}{ return } log local0. "LB pool: [LB::server pool]" } }
動作ログ
Sep 12 16:52:45 big-ip info tmm[9867]: Rule /Common/designet_referer_irule <HTTP_REQUEST>: Referer: https://hatenablog.com/ Sep 12 16:52:45 big-ip info tmm[9867]: Rule /Common/designet_referer_irule <HTTP_REQUEST>: LB pool: /Common/designet-pool-advanced Sep 12 16:52:45 big-ip info tmm[9867]: Rule /Common/designet_referer_irule <HTTP_RESPONSE>: Response Set-Cookie value: pool=/Common/designet-pool-advanced;expires=Sun, 12-Sep-2021 16:57:45 GMT;path=/; Sep 12 16:52:45 big-ip info tmm[9867]: Rule /Common/designet_referer_irule <HTTP_RESPONSE>: LB pool: /Common/designet-pool-advanced Sep 12 16:52:50 big-ip info tmm1[9867]: Rule /Common/designet_referer_irule <HTTP_REQUEST>: Cookie: pool=/Common/designet-pool-advanced Sep 12 16:52:50 big-ip info tmm1[9867]: Rule /Common/designet_referer_irule <HTTP_REQUEST>: Request cookie pool: /Common/designet-pool-advanced Sep 12 16:52:50 big-ip info tmm1[9867]: Rule /Common/designet_referer_irule <HTTP_REQUEST>: LB pool: /Common/designet-pool-advanced Sep 12 16:52:50 big-ip info tmm1[9867]: Rule /Common/designet_referer_irule <HTTP_RESPONSE>: Response Set-Cookie value: pool=/Common/designet-pool-advanced;expires=Sun, 12-Sep-2021 16:57:50 GMT;path=/; Sep 12 16:52:50 big-ip info tmm1[9867]: Rule /Common/designet_referer_irule <HTTP_RESPONSE>: LB pool: /Common/designet-pool-advanced
期待通り、初回はRefererにより振り分けられ、その後はCookieのpoolによって振り分けられている。
まとめ - BIG-IP iRuleでReferer・CookieベースでPoolを振り分ける
BIG-IP iRuleでRefererによってPoolを振り分けられるようにした。また、Cookieを付与することで、Refererの異なる後続リクエストについても同様の振り分けとすることができた。