designetwork

ネットワークを軸としたIT技術メモ

BIG-IP iRuleでReferer・CookieベースでPoolを振り分ける

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句はこちらを参考にさせていただきました。ありがとうございます。

qiita.com

流入後のユーザ操作では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でRefererCookieベースでPoolを振り分ける

BIG-IP iRuleでRefererによってPoolを振り分けられるようにした。また、Cookieを付与することで、Refererの異なる後続リクエストについても同様の振り分けとすることができた。