はじめに
BIG-IPで、HTTPリクエストに含まれるHTTP Hostヘッダに基づいて振り分け先Poolを切り替える方法について。
簡単に言うと、下記のイメージです。
Apacheでは「名前ベースのバーチャルホスト」と言われるものでしょうか。BIG-IPでこれを実装するには、iRuleを使用する必要があります。今回、異なる3つのiRuleを考えてみました。(実際には、1→2→3の順にruleをブラッシュアップして行ったというほうが正しいです。)
- 基本的なHostヘッダーベーススイッチング
- Data Group Listを使ったHostヘッダーベーススイッチング
- Pool名を使ったHostヘッダーベーススイッチング
それぞれについて、記載したいと思います。
1. 基本的なHostヘッダーベーススイッチング用iRule
iRuleの紹介と解説
最も基本的なiRuleは、以下のようになります。
when HTTP_REQUEST
{
if { [HTTP::host] matches "www1.example.co.jp" } {
pool pool_www1
}
elseif { [HTTP::host] matches "www2.example.co.jp" } {
pool pool_www2
}
elseif { [HTTP::host] matches "www3.example.co.jp" } {
pool pool_www3
}
else {
log local0. "No matching host. SRC-IP [IP::client_addr], Host [HTTP::host]"
}
}
このiRuleは、クライアントからのHTTPリクエストを受信した時に実行されます。リクエスト内のHostヘッダが、
[HTTP::host]というコマンドで読み込まれ、if文から逐次評価されます。マッチした場合は、そのifブロック内のpoolへ通信が振り分けられます。
HTTPリクエスト内にHostヘッダ自体がない、またはどの条件文ともマッチしないHostヘッダを含んでいる場合は、最後のelseブロックにマッチし、/var/log/ltmへ、送信元とHostヘッダ内容のログを出力します。
この後、もし、Virtual ServerにDefault Poolを設定している場合は、Default Poolへ振り分けられますが、Default Poolを設定していない場合は、コネクションがリセットされます。(BIG-IPがクライアントへTCP-RSTを応答します。)
このiRuleのメリット
このiRuleは、単純(シンプルというより原始的)ですが、拡張性があります。例えば、ある特定のpoolへの振り分け時にのみ、何らかの処理を加えたい場合、該当箇所にコードを追加すれば対応できます。
このiRuleのデメリット
このVirtual ServerでサポートするHostが増える都度iRule自体を修正する必要があります。頻繁にHostが増減するような環境では、それがかなり負担になるかもしれません。
2. Data Group Listを使ったHostヘッダーベーススイッチング用iRule
iRuleの紹介と解説
次は、Data Group Listという機能を使ったiRuleです。
when HTTP_REQUEST
{
if { [class match [HTTP::host] equals example.co.jp_host_list] } {
set pool [class match -value [HTTP::host] equals example.co.jp_host_list]
pool $pool
}
else {
log local0. "No matching host. SRC-IP [IP::client_addr], Host [HTTP::host]"
}
}
最初のiRuleと比べて、かなりシンプルになりました。
Data Group Listとは
Data Group Listは、プログラムでいう連想配列のようなものです。
二つのデータ(KeyとValue)を1セットとして、BIG-IPに登録します。
例えば、以下の表ようなデータを、Data Group Listとして登録し、iRuleから扱うことができます。
String(Key) | Value |
---|---|
www1.example.co.jp | Pool_www1 |
www2.example.co.jp | Pool_www2 |
www3.example.co.jp | Pool_www3 |
・ | ・ |
このようにドメイン名と振り分け先Poolを1つのセットで入力しておけば、「HTTP::hostの文字列が、Data Group ListのKeyにマッチした場合は、そのElementのValueの名前のPoolに振り分ける」というiRuleを作成しておくことで、今後はHost名が追加されても、Data Group Listを編集するのみ(あとPoolの追加)で、iRule自体には手を加えないで済みます。
なお、一つのData Group List内で、Keyは一意である必要があります。
String(Key) | Value |
---|---|
www1.example.co.jp | Pool_www1 |
www1.example.co.jp | Pool_www2 |
↑こんなものは作れません。
String(Key) | Value |
---|---|
www1.example.co.jp | Pool_www1 |
www2.example.co.jp | Pool_www1 |
↑これは問題ありません。
Data Group Listは、GUI (Configuration Utility)の「Local Traffic -> iRules -> Data Group List」というところにあります。
新規に作成する場合は、Keyの型(Type)を選ぶところから始まります。型は「Address」「String」「Integer」から一つ、適当なものを選択します。今回はHost名が入るのでStringを使用します。そして、まずはデータが空のData Group Listを作成し、次に、そのData Group ListのRecords欄に実際のデータを入力していきます。
作成したData Group Listはこんな感じ↓になりました。
String Records表内の、「:=」の左側がKey、右側がValueです。
iRuleのclassコマンドについて
このiRuleではclassというコマンドを使用します。これはiRule内でData Group Listを扱うためのコマンドです。非常に多くの機能があるので、使う際は、Devcentralのリファレンスを参照したほうが良いと思います。
今回のiRuleで使用しているのは以下の二つのケースです。
class match 文字列 equals DataGroupList名
これは、文字列が対象DataGroupListのKeyと一致するかをチェックします。マッチした場合は1、マッチしなかったは場合は0が返ります。
class match -value 文字列 equals DataGroupList名
こちらは、文字列が対象DataGroupListのKeyに含まれる場合にそのValueが返ります。
このiRuleのメリット
Data Group Listを使うので、Hostが追加、削除されてもiRule自体を触らずに済むのは、運用上大きなメリットと思います。DevCentralで検索してみると、実際にData Group Listを使ったL7スイッチ(Hostヘッダ以外にも、URI等も対象にできます)を利用している実績は少なくないようです。
このiRuleのデメリット
通信の振り分けは行えますが、個々のPoolへの振り分け時に、個別にさらなる処理を加えることが難しくなります。iRuleに処理を加えることはできますが、全ての通信が一律して同じ処理を追加されることになります。(それもメリットと言えるかも。)
3. Pool名を使ったHostヘッダーベーススイッチング用iRule
iRuleの紹介と解説
最後のiRuleは、Hostが追加や削除されても、iRuleを変更する必要がなく、Data Group Listも使用しません。
その代わり、Host名とPool名の間に簡単な命名規則を設けます。例えば、
ドメイン名 | 振り分け先のPool名 | |
---|---|---|
www1.example.co.jp | ⇒ | Pool_www1 |
www2.example.co.jp | ⇒ | Pool_www2 |
www3.example.co.jp | ⇒ | Pool_www3 |
上記のように、「Host名の先頭」と「Pool名」を一致させるという規則があれば、次の手順で、HTTP Hostヘッダの情報から、振り分け先Poolが判定できます。
- HTTPリクエストのHostヘッダが"(hoge).example.co.jp" にマッチするかを確認する
- マッチした場合、BIG-IPに"Pool_(hoge)"という Poolがあるかを確認する
- Poolが存在する場合、そのPoolへ通信を振り分ける
前置きは以上にして、iRuleを記載します。
when RULE_INIT
{
set static::host_re {(\w+)\.example\.co\.jp}
}
when HTTP_REQUEST
{
if { [regexp $static::host_re [HTTP::host] all host] } {
if { [catch {pool "pool_$host"}] } {
log local0. "No matching host. SRC-IP [IP::client_addr], Host [HTTP::host]"
}
}
else {
log local0. "Regexp not matched. SRC-IP [IP::client_addr], Host [HTTP::host]"
}
}
このiRuleは、二つのイベント(RULE_INITとHTTP_REQUEST)に分かれているので、イベント毎に詳細を記載します。
iRuleの「RULE_INIT」イベントについて
when RULE_INIT
{
set static::host_re {(\w+)\.example\.co\.jp}
}
RULE_INITイベントは、
- そのiRuleがsaveされた時
- 機器の起動時
- ソフトウェアの再スタート時
のタイミングで一度実行されます。
RULE_INIT内で「static::定数名」で作成したグローバル定数は、他のイベント内で「$static::定数名」というフォーマットでグローバル定数として利用できます。
iRule内で常に同じ値となる定数は、RULE_INIT内に「static::定数名」で定義するのが効率的です。HTTP_REQUESTなどの一般的なイベント内で定義すると、そのiRuleが実行される都度に新しく定義し、そして通信の終了時に破棄されることになります。
HTTP_REQUEST イベント内の処理について
when HTTP_REQUEST
{
if { [regexp $static::host_re [HTTP::host] all host] } {
if { [catch {pool "pool_$host"}] } {
log local0. "No matching host. SRC-IP [IP::client_addr], Host [HTTP::host]"
}
}
else {
log local0. "Regexp not matched. SRC-IP [IP::client_addr], Host [HTTP::host]"
}
}
最初のif文
if { [regexp $static::host_re [HTTP::host] all host] }
で、正規表現を使って、Hostヘッダが、(\w+).example.co.jpにマッチしているかをチェックします。また、マッチした場合ホスト名の先頭部分がhostという変数に保存されます。
Hostヘッダがマッチした場合は、つづいて
if {[ catch {pool "pool_$host"} ]}
というif文とcatcheコマンドを使ってます。
catchコマンドは、続く引数を実行します。引数を実行してその応答が正であれば、cacheコマンドは0を応答します。つまりif文は偽になります。もし、実行中の引数がエラーを返した場合、catchコマンドは0以外を応答します。したがって、ここでは、そもそも存在するかわからないPoolに対して通信を振り分けようとしてPoolがあれば振り分けるし、なければエラーが返るという多少強引と思える方法を使ってます。これはDevCentralにも載ってるので、この使い方で問題ないかと思います。
このiRuleのメリット
振り分ける新規ホストが追加された時の手間は3つのiRuleで最小です。
命名規則に従った Poolを作成すれば良いだけです。
このiRuleのデメリット
「命名規則にマッチするかどうか」で判断するので、Pool名が制限されます。
このiRuleの対象としないPoolについても、(上記のcache文でマッチしないように)名前が制限されることになります。
最後に
メリット、デメリットを書くならば、各iRuleごとに、大量のPoolがある場合の負荷はどうなるのか、ということも考えるべきと思ったのですが、通信速度に差異が出るほどの設定量を行うのが難しく、それは未確認です。おそらくは、DataGroupListを使うパターンが一番高速で負荷が軽くなるんじゃないでしょうか。
そもそもそれほど大規模な環境なら、グローバルIPもたくさんあって、Virtual Server一つでどうにかしよう、なんてしないのではと思います。
補足事項
ここではHostヘッダを取得する[HTTP::host]を使っていますが、iRuleを使えば、他のいろいろなHTTPヘッダの情報も簡単に取得して、扱うことができます。紹介記事を書いたので、よければ見てやってください。
Qiita: iRuleでHTTPリクエストに含まれる値を取得する
参考情報
- DevCentral: Getting Started with iRules: Variables
- DevCentral: iRules 101 - #07 - Catch
- DevCentral: iRules 101 - #08 - Classes
- Tcl 8.4.1 Manual Command Reference: regexp
本ページは以上です。