LoginSignup
12
14

More than 5 years have passed since last update.

BIG-IPで、Hostヘッダに基づいた振り分けを行うiRule3種

Last updated at Posted at 2017-01-15

はじめに

BIG-IPで、HTTPリクエストに含まれるHTTP Hostヘッダに基づいて振り分け先Poolを切り替える方法について。

簡単に言うと、下記のイメージです。

20170103_bigip-01.png

Apacheでは「名前ベースのバーチャルホスト」と言われるものでしょうか。BIG-IPでこれを実装するには、iRuleを使用する必要があります。今回、異なる3つのiRuleを考えてみました。(実際には、1→2→3の順にruleをブラッシュアップして行ったというほうが正しいです。)

  1. 基本的なHostヘッダーベーススイッチング
  2. Data Group Listを使ったHostヘッダーベーススイッチング
  3. Pool名を使ったHostヘッダーベーススイッチング

それぞれについて、記載したいと思います。

1. 基本的なHostヘッダーベーススイッチング用iRule

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です。

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」というところにあります。
iRule_DataGroupList_SS.png

新規に作成する場合は、Keyの型(Type)を選ぶところから始まります。型は「Address」「String」「Integer」から一つ、適当なものを選択します。今回はHost名が入るのでStringを使用します。そして、まずはデータが空のData Group Listを作成し、次に、そのData Group ListのRecords欄に実際のデータを入力していきます。

作成したData Group Listはこんな感じ↓になりました。

iRule_DataGroupList_SS_2.png

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が判定できます。

  1. HTTPリクエストのHostヘッダが"(hoge).example.co.jp" にマッチするかを確認する
  2. マッチした場合、BIG-IPに"Pool_(hoge)"という Poolがあるかを確認する
  3. Poolが存在する場合、そのPoolへ通信を振り分ける

前置きは以上にして、iRuleを記載します。

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」イベントについて

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 イベント内の処理について

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

本ページは以上です。

12
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
14