BIG-IPでSorryページを応答する（iFile使用版)

# はじめに
BIG-IPでSorryページを応答させるにはiRuleを使う必要があります。
これは一般的に需要が多い機能であり、ググれば、様々なサンプルiRuleが見つかります。

ただ、検索して見つかるiRuleは古いものが多く、一番閲覧されるであろう[F5のなかのひとが書いた日本語記事](https://devcentral.f5.com/articles/big-ip%E3%82%92sorry-server%E3%81%A8%E3%81%97%E3%81%A6%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8Birule)は、2010年の記事で、コンテンツをBase64にエンコードしたものを用意する必要があったり、そのファイルをBIG-IPに保存した上で、Data Group Listでファイルパスを登録するといった婉曲的な手段をとらざるを得ないものとなってます。

そのような時代もあったわけですが、最近のBIG-IPでは、[iFile](https://support.f5.com/kb/en-us/products/big-ip_ltm/manuals/product/bigip-system-irules-concepts-11-6-0/7.html)という機能により、外部のファイルをBIG-IPにインポートして、iRuleでそれらを取り扱えるようになりました。そのため、sorryページを応答するiRuleは、従来よりもずっとシンプルで、またファイルの更新も簡単なものとなっています。

# 参考情報
- [DevCentral: BIG-IPをSorry Serverとして利用するiRule ](https://devcentral.f5.com/articles/big-ip%E3%82%92sorry-server%E3%81%A8%E3%81%97%E3%81%A6%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8Birule)
- [DevCentral: 20 Lines or Less #68: Simplifying the Complexified Simplicity](https://devcentral.f5.com/articles/20-lines-or-less-68-simplifying-the-complexified-simplicity)
- [DevCentral: v11.1–External File Access from iRules via iFiles](https://devcentral.f5.com/articles/v111ndashexternal-file-access-from-irules-via-ifiles)
- [BIG-IP LTM Manual: BIG-IP System: iRules Concepts iFiles](https://support.f5.com/kb/en-us/products/big-ip_ltm/manuals/product/bigip-system-irules-concepts-11-6-0/7.html)
- [DevCentral: iRues API: LB_FAILED](https://devcentral.f5.com/wiki/iRules.LB_FAILED.ashx)
- [DevCentral: iRues API: ifile](https://devcentral.f5.com/wiki/irules.ifile.ashx)

# 環境
このページに記載のiRuleはv12.1.2で動作を確認してます。

# 手順
## Sorryページコンテンツの用意
まず、Sorryページとなるコンテンツ（HTMLファイルや画像ファイルなど）を用意します。
今回は、簡単にこんな感じのサンプルページを作ってみます。
![sorry_sample_capture_50p.png](https://qiita-image-store.s3.amazonaws.com/0/157891/8b33bfdd-da9a-c682-8b8d-345e72522b59.png)

このページを構成するファイルは、次の二つです。

|ファイル名|役割|
|:-:|:-:|
|sorry.html | sorryページとして応答するHTMLファイル |
|message_gomennasai.png | sorry.html内で、imgタグで呼び出される画像ファイル |

 画像ファイルは「[いらすとや](http://www.irasutoya.com/2013/04/blog-post_2744.html)」さんからいただきました。

sorry.htmlは次のような感じです。

```html
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>sorry page</title>
    </head>
    <body>
        <center>
        <img width="300px" src="message_gomennasai.png" />
        <h1>ごめんなさい。システムエラー中です。</h1>
        </center>
    </body>
</html>
```

## iFileへの登録
まずは、コンテンツとなる2つのファイルをBIG-IPにアップロード（iFileとして登録）します。

1. Web Configuration Utilityの**System >> File Management >> iFile List** を開く。
![sorry_pros_1.png](https://qiita-image-store.s3.amazonaws.com/0/157891/4cdcb1ed-5e07-c21b-8158-b2ba8768025e.png)
  
2. **Import** を押して、対象のファイルを選択する。
NameはBIG-IP上でのそのファイルの名称です。元のファイル名と同じでも、異なっても、どちらでも構いません。
![sorry_pros_2.png](https://qiita-image-store.s3.amazonaws.com/0/157891/942f679a-a88f-acfd-6ea5-ef639e03466f.png)
  
3. 登録したファイルは、iFileとしてリスト表示されます。
 ![sorry_pros_3.png](https://qiita-image-store.s3.amazonaws.com/0/157891/d214656a-1762-44c4-4553-b53a15313c11.png)

これで、BIG-IPへのファイルのアップロードが完了しました。

しかし、iRuleでiFileを呼び出すには、もう一つ手順が必要です。iRuleの設定側にもiFileの項目があり、先ほどFile Managementにアップロード（登録）したiFileを、iRule側のiFile設定と紐付けなければなりません。（正直、これは二度手間で、何故こんな面倒な設定になってるのか疑問です。）

4. **Local Traffic >> iRules >> iFile List** を開く。
![sorry_pros_4.png](https://qiita-image-store.s3.amazonaws.com/0/157891/fb61036c-fd91-a4f1-463b-f8826bdf508e.png)
  
5. **Create**を押し、iFileの名前を指定、呼び出すiFileを選択する。
![sorry_pros_5.png](https://qiita-image-store.s3.amazonaws.com/0/157891/9df7deef-3f2c-85f6-b0eb-ae13949c2a91.png)
  
6. 登録したファイルがリスト表示されます。
![sorry_pros_6.png](https://qiita-image-store.s3.amazonaws.com/0/157891/53973017-c19b-06ab-79e2-4091d488fa58.png)

iRuleでiFileを呼び出す準備が整ったので、次はiRuleを作成します。

## Sorryページを応答するiRule

```tcl
when LB_FAILED {
    if { [active_members [LB::server pool]] < 1} {
        switch [string tolower [HTTP::uri]] {
            "/sorry.html" {
                HTTP::respond 200 content [ifile get sorry.html] "Content-Type" "text/html"
            }
            "/message_gomennasai.png" {
                HTTP::respond 200 content [ifile get message_gomennasai.png] "Content-Type" "image/png"
            }
            default {
                HTTP::redirect "http://example.com/sorry.html"
            }
        }
    }
}
```
このiRuleを、利用したいVirtual Serverに紐づければ完了です。

#解説

## このiRuleについて

**LB_FAILED イベント** は、BIG-IPが通信をPool memberに振り分けようとして振り分けできなかった際にトリガされます。（多くのsorryページ用iruleでは、HTTP_REQUESTをトリガにしていますが、LB_FAILEDのほうがイベント自体が発動するケースが少ないはずで、処理は軽いと思います。）

**[active_members [LB::server pool]] < 1 { }** は、Virtual Serverに紐づいているPoolの、activeなmember数が1未満、つまり全Pool MemberがDownとなっている場合にifブロック内が実行されます。
LB_FAILEDはPool memberが全てDownしているという場合以外にも発動するケースがあります。そのため、さらにPool memberが死活監視でDownと判定されているケースのみを対象とします。

**active memberを基準とする場合の注意点**
BIG-IPに限らず一般的な死活監視では、ヘルスチェック対象（例えばPool member）がヘルスチェックに応答できなくなってから、それをヘルスチェック元（例えばBIG-IP）がその対象をDownと判定するまでには一定の間隔があります。（ヘルスチェックモニタのTimeout時間）。そのため、Pool memberのactive数を基準としたこのiRuleでは、Pool memberがヘルスチェックに応答できなくなって一定時間は、まだsorryページは応答しない事に注意が必要です。

**switch [string tolower [HTTP::uri]] { }** は、リクエストされたURIに基づいて応答を切り替えます。sorryページの為に複数のコンテンツがあるということは、if文にマッチした後、複数の異なるURLに対してリクエストを受けることになります。そのため、まずdefaultでsorryページ用のURIを用意しておき、"sorryページ用コンテンツのURI以外へのリクエストは、sorryページにリダイレクトする"という仕掛けを入れておく必要があります。

**sorryページが応答する際の一連の流れ**

1. 振り分けに失敗した際に、LB_FAILEDが発動する
2. pool memberが全Downしている場合に ifブロック内を実行
3. 一旦"http://(サイトのFQDN)/sorry.html" にリダイレクト
4. リダイレクトを受けて、新しいリクエストが"/sorry.html" 宛に飛んでくる。同様に1〜3のステップを踏み、switch文の先頭にマッチしてコンテンツを応答
5. "/sorry.html"内に記述されているコンテンツ（message_gomennasai.png)についてもリクエストが飛んでくるので、それについても同様にswitch文にマッチしてコンテンツを応答

**リダイレクトループに注意**
このiRuleでは、switchのdefaultを使い、マッチしないURIに対するリクエストを全てリダイレクトします。sorry.html内にマッチしないファイルの読み込みが含まれていると、リダイレクトループするので注意が必要ですね。（試してみたのですが残念ながら無限ループは発生しませんでした。ブラウザが2回目以降のリクエストを送らないようです。しかし、ブラウザ側の安全設計に頼るのは良くないと思います。）

# その他の事項
## iFileの更新について
iFileに登録したファイルを更新したい場合、**System >> File Management >> iFile List** で、importする際に、すでに登録済みのiFileを選択するだけでOKです。iFileはファイルごとにバージョン情報を持っていて、ファイルの更新毎にバージョンの値が+1されます。

sorry.htmlを更新した例。
![sorry_update_1.png](https://qiita-image-store.s3.amazonaws.com/0/157891/3f5712b6-872e-3b6e-5891-9daa714120ba.png)

## 多数のiFileを扱う場合に考慮すべき点
iFileでは、ディレクトリ構造を取ることはできません。また、登録名にスラッシュ（/）を含めることはできません。多数のファイルをiFileに登録するときは（Sorryコンテンツ自体がディレクトリ構成となっている場合は特に）命名にルールを設けることが良さそうです。

また、今回は2つしかファイルがないので直接iRule内に明記してますが、もしファイルが多数ある場合、switch文が非常に長くなります。**[ifile listall]** などを使って、"リクエストされたファイル名がiFileに一致する場合はiFileのコンテンツを応答して、それ以外はリダイレクトする"コードにした方が便利ですね。

## レスポンスコードについて
sorryページは、レスポンスコードを200ではなく503 で応答すべきかもしれません。その場合 **HTTP::respond 200** の部分を**HTTP::respond 503** に変更すれば、レスポンスコードを503にして応答できます。

# 免責事項
本ページの内容に誤り等があり、参考にされた方がなんらかの損害を被った場合、一切の責任は負いません。