Apacheのmod_proxy_balancerのコードを読んでみる


# はじめに

mod_proxy、mod_proxy_wstunnelに続き、mod_proxyの後段のモジュールであるmod_proxy_balancerについて読んでみたので記事にします。

- mod_proxyの記事：[Apacheのmod_proxyのコードを読んでみる(3)](https://qiita.com/tomluck/items/ca53bbf8fe0e7e2b72a5)
- mod_proxy_wstunnelの記事：[Apacheのmod_proxy_wstunnelのコードを読んでみる](https://qiita.com/tomluck/items/04bef3f34c2b4203d6fd)

実行環境や実装のバージョンはmod_proxyの時と同様にApache v2.4.23です。

いつものように我流で読み進めているので、理解内容に間違いなどありましたらご指摘ください。

# mod_proxy_balancerの概要

## AP_DECLARE_MODULE(proxy_balancer) 

apache標準のhook定義関数です。

mod_proxy_wstunnelと同様に基本的に前段のmod_proxyで登録されているため、hook関数の登録しかしていません。

```c
AP_DECLARE_MODULE(proxy_balancer) = {
    STANDARD20_MODULE_STUFF,
    NULL,       /* create per-directory config structure */
    NULL,       /* merge per-directory config structures */
    NULL,       /* create per-server config structure */
    NULL,       /* merge per-server config structures */
    NULL,       /* command apr_table_t */
    ap_proxy_balancer_register_hook /* register hooks */
};
```

## register_hooks

hook登録関数で、AP_DECLARE_MODULEの最後に指定されている関数です。

```c
static void ap_proxy_balancer_register_hook(apr_pool_t *p)
{
    /* Only the mpm_winnt has child init hook handler.
     * make sure that we are called after the mpm
     * initializes
     */
    static const char *const aszPred[] = { "mpm_winnt.c", "mod_slotmem_shm.c", NULL};
    static const char *const aszPred2[] = { "mod_proxy.c", NULL};
     /* manager handler */
    ap_hook_post_config(balancer_post_config, aszPred2, NULL, APR_HOOK_MIDDLE);
    ap_hook_pre_config(balancer_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_handler(balancer_handler, NULL, NULL, APR_HOOK_FIRST);
    ap_hook_child_init(balancer_child_init, aszPred, NULL, APR_HOOK_MIDDLE);
    proxy_hook_pre_request(proxy_balancer_pre_request, NULL, NULL, APR_HOOK_FIRST);
    proxy_hook_post_request(proxy_balancer_post_request, NULL, NULL, APR_HOOK_FIRST);
    proxy_hook_canon_handler(proxy_balancer_canon, NULL, NULL, APR_HOOK_FIRST);
}
```

Apache標準のhookを4個とmod_proxyから呼ばれるhookを3個登録しています。
この中でap_hook_post_configに関しては、第2引数でaszPred2としてmod_proxyが指定されているので、mod_proxyのhookが呼ばれた後のmod_proxy_balancerのhookが呼ばれることになります。

mod_proxyから呼ばれるhookの呼び出し元は次のようになっています。

- proxy_hook_pre_request、proxy_hook_post_request：mod_proxyのproxy_handlerからフックされます
- proxy_hook_canon_handler：mod_proxyのproxy_fixupからフックされます

# mod_proxy_balancerの詳細

## balancer_pre_config

ap_hook_pre_configのhandlerです。
confファイルから必要な情報を取得するぐらいしかやっていません。

## balancer_post_config

ap_hook_post_configのhandlerです。

```c
    /* balancer_post_config() will be called twice during startup.  So, don't
     * set up the static data the 1st time through. */
    if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) {
        return OK;
    }
```

参考情報を見ていただければ分かるのですが、ap_hook_pre_configとap_hook_post_configは2回呼び出されます。
2回目の呼び出された時に不整合が起こらないように、1回目の呼び出しではOKをreturnしています。

2回目の呼び出しでは、最初にメモリを確保します。(slotmemへshmをprovider機構で設定し各workerごとにshmを確保する)

```c
    /*
     * Get slotmem setups
     */
    storage = ap_lookup_provider(AP_SLOTMEM_PROVIDER_GROUP, "shm",
                                 AP_SLOTMEM_PROVIDER_VERSION);

```

その後、whileループに入り処理をします。
while文の中身はそれほど読めていませんが、関数名の通りの処理で、mod_proxyが管理する構造体proxy_server_confへ値の設定などをする実装になっていると思います。


### 参考情報

- [フックから Apache の全体像を追う](http://d.hatena.ne.jp/dayflower/20081029/1225266220)

- [Apache の provider 機構 - 他モジュールに移譲するしくみ](http://d.hatena.ne.jp/dayflower/20081112/1226468918)

## balancer_child_init

ap_hook_child_initのhandlerです。
内部変数の初期化、共有メモリの取得などをしています。

## proxy_balancer_canon

- フックされた時点でurlに"balancer"を含んでいないとDECLINEDになり終了です

- リクエストに問題がなければ、request_rec構造体のfilenameメンバへ整形したurlを設定し、path_infoへpath情報を設定してOKを返します。

```c
    r->filename = apr_pstrcat(r->pool, "proxy:" BALANCER_PREFIX, host,
            "/", path, (search) ? "?" : "", (search) ? search : "", NULL);

    r->path_info = apr_pstrcat(r->pool, "/", path, NULL);

    return OK;
```

## proxy_balancer_pre_request

proxy_hook_pre_requestのhandlerです。
mod_proxyのproxy_handlerからフックされます。
mod_proxy_balancerの肝とも言えるworkerを選定するところです。

### 前準備

1. URLがmod_proxy_balancer用であるか('balancer：//'で始まるか)を確認します。バランサがすでに提供されている場合は、DECLINEDで終了(フェールオーバーの試行であるため)

2. ロードバランサをロックします。

3. workerを強制的にリカバリーして、balancerのメンバーリストを更新します。

4. そのセッションのルートを見つけます。

### find_session_route(セッションと紐づくルートの探索)

```c
static proxy_worker *find_session_route(proxy_balancer *balancer,
                                        request_rec *r,
                                        char **route,
                                        const char **sticky_used,
                                        char **url)
```

stickysessionはURLまたはcookieで指定することができます。

stickysessionが未指定だった場合は、セッションとrouteを紐づける必要がないためNULLを返します。

stickysessionが指定されていた場合、最初にURLの内容を確認し、未指定であればcookieの内容を確認します。

```c
    /* Try to find the sticky route inside url */
    *route = get_path_param(r->pool, *url, balancer->s->sticky_path, balancer->s->scolonsep);
    if (*route) {
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01159)
                     "Found value %s for stickysession %s",
                     *route, balancer->s->sticky_path);
        *sticky_used =  balancer->s->sticky_path;
    }
    else {
        *route = get_cookie_param(r, balancer->s->sticky);
        if (*route) {
            *sticky_used =  balancer->s->sticky;
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01160)
                         "Found value %s for stickysession %s",
                         *route, balancer->s->sticky);
        }
    }
```

普通に考えるとcookieを先に見た方が良さそうですが、最初にURLを確認しています。
理由を想像すると、携帯電話などでcookieを使用できないものからアクセスされたケースに備えるためだと思います。

stickysessionに該当する値が取得できた場合、対応するrouteを指定したworkerを返します。

### find_session_route(セッションと紐づくルートの探索)結果による分岐

- セッションに紐づくworkerが存在する場合
ロードバランサのステータス(lbstatus)を更新します。

- routeが設定済み、かつsticky_forceが指定されている場合
バランサ名(balancer://の名前)と一致しないルートが用意されています。
ルートが同じバランサのメンバであるかどうかを確認し、該当する場合は503を返して終了です。(エラーケース)

- セッションに紐づくworkerが存在しない場合
find_best_worker関数を呼び出し、lbmethodのfinderが、その時点でベストなworkerを選定します。

### 後処理
次の処理をしてstatusを返して終了です。
- urlの書き換え
  - 変更前："balancer://url"
  - 変更後："worker_scheme://worker_hostname[:worker_port]/url"
- request_rec構造体に値の設定

## proxy_balancer_post_request

proxy_hook_post_requestのhandlerです。
mod_proxyのproxy_handlerからフックされます。
request後のエラー処理(errorstatusやfailontimeoutの内容による処理)を実行します。

## balancer_handler

balancer-manager(ブラウザでロードバランス設定を変更する)の機能性を実現しています。
mod_proxy_balancerのバランシング機能について調べていたので、この関数はほとんど読んでいないです。

# おわりに

これまでにmod_proxyに関係する3つのApacheモジュールについて読んだ内容を整理してみました。
- mod_proxy
- mod_proxy_wstunnel
- mod_proxy_balancer

現時点で他のモジュールを読む予定は有りませんが、参考になる方がいれば幸いです。