Nginxでメンテナンスページを表示する

Nginxでメンテナンスページを表示させる仕組みを作った時の覚書.

動作環境

  • Nginx ... 1.12.2

やりたいこと

今回、メンテナンスページを表示させるに当たって、前提条件・実現したいことをまとめます.

前提条件

  • Nginxのrootに指定してあるディレクトリにmaintenance.hmtlというメンテンナンスページ用のページを用意している

実現したいこと

  • メンテナンス画面への切り替えで、Nginxの再起動を必要としない
  • メンテナンス中は、Webアプリと通信しない
  • メンテナンス中に/health_checkへのリクエストが来た場合、200で空のレスポンスを返す
  • /health_check以外へのリクエストの場合は、メンテナンスページを表示させる
  • メンテナンスページのHTTPステータスコード503にする

これらを実現したいです.

やってみる

条件が書けないですし、ifの中にifを書くことも出来ません.
どういうことかというと、メンテナンス中かつ/health_checkへのリクエストであるをifの条件式に書くことが出来ません.
そのため、無理やりですが以下のように書きました.

error_page 503 @maintenance;

# Maintenance
set $maintenance false;
set $maintenance_app_request false;
set $maintenance_health_check_request false;

if (/usr/src/app/tmp/maintenance.txt) {
    set $maintenance true;
}
if ($request_uri ~ ^/health_check) {
    set $maintenance_health_check_request $maintenance;
}
if ($request_uri !~ ^/health_check) {
    set $maintenance_app_request $maintenance;
}

if ($maintenance_app_request = true) {
    return 503;
}

location @maintenance {
    try_files /maintenance.html =404;
}

# Health Check
location /health_check {
    if ($maintenance_health_check_request = true) {
        return 200;
    }

    proxy_pass http://app;
}

どうでしょうか?
良くない書き方だと自分でも思いますが、複数条件の論理積について思い出してみましょう.
複数条件の論理積の場合は、自分以外の条件が成り立たない場合、論理式の結果としては、偽となります.
この性質を利用して書きました.
つまり、メンテナンス中かつ/health_checkへのリクエストであるという条件は、/health_checkへのリクエストであるという条件が成り立つ場合、他の条件であるメンテナンス中という条件の結果を入れれば良い.
ということになります.

/health_checkへのリクエストであるという条件が成り立ったとしても、他の条件であるメンテナンス中が成り立たない場合は、論理式としては偽となります.
これらの条件の結果を保存する変数を事前にfalseとして初期化しておき、
ある条件が成り立つ場合に条件式の結果を代入すれば論理積としては期待する結果になります.

ただ、このやり方のデメリットは、

  • 条件分の変数定義が必要になる
  • 論理和を表すことができない

あたりです.
他のやり方で、論理和などを表現することはできるのですが、あまり良い方法とは思えません.

最後に

自分で良い書き方ではないと思いますが、とりあえず自分のやりたいことを実現するにはこれで十分だったので、こうしました.
もっとマシなやり方を思いついたら、書きますね.

ちなみにifディレクティブは、公式からIf Is Evilと書かれるぐらいなので、必要がない限りは使用しない方がいいですよ.