作業ログ メンテナンスページがgzip圧縮されない問題を修正 2019/09/07

※この記事に登場するソースコードなどは僕個人が開発しているもので仕事とは無関係です

ざっくり状況説明

リバースプロキシにnginx、アプリケーションはRailsで動いています。

capistrano-maintenanceというGemを使ってメンテナンスページのオンオフを切り替えるように作ったところ、メンテナンスページがgzip圧縮されずにブラウザに送信されてきていることに気付きました。

f:id:Kenta-s:20190906200329p:plain

900KB超えてます。

このままではユーザーに申し訳なさすぎてメンテナンス画面を出せません。

faviconがこんなに大きい理由は後述します。

capistrano-maintenanceについてざっくり説明

まずはnginxに例えば以下のような記述を追加します

error_page 503 @503;

# Return a 503 error if the maintenance page exists.
if (-f /var/www/domain.com/shared/public/system/maintenance.html) {
  return 503;
}

location @503 {
  # Serve static assets if found.
  if (-f $request_filename) {
    break;
  }

  # Set root to the shared directory.
  root /var/www/domain.com/shared/public;
  rewrite ^(.*)$ /system/maintenance.html break;
}

/var/www/domain.com/shared/public/system/maintenance.html が存在する場合は location @503 のブロックが評価されるようになります。

そして、 cap production mainntenance:enable とコマンドを叩くと設定ファイルで指定したhtmlが /var/www/domain.com/shared/public/system/maintenance.html としてアップロードされ、nginxが503を返すようになり、 メンテナンスを解除したいときは cap production maintenance:disable と叩くと例のhtmlが単純に削除されます。

シンプルで良いですね。

github.com

現在の設定

503のブロック

  # Return a 503 error if the maintenance page exists.
  if (-f /xxx/xxx/xxx/xxx/public/system/maintenance.html) {
    return 503;
  }
  location @503 {
    # Serve static assets if found.
    if (-f $request_filename) {
      break;
    }
    # Set root to the shared directory.
    root /xxx/xxx/xxx/xxx/public;
    rewrite ^(.*)$ /system/maintenance.html break;
  }

hthpブロックには

gzip on; が記述してあります。

この時点で動いてくれておかしくなさそうなんですが、、うーん、、、

実はこの問題は一週間ほどまえに気付いていたんですが、やみくもに着手しても解決できる問題だとは思えなかったので(というのもgzip on;などの必要な設定は記述しているし、第一text/htmlはデフォルトでgzip圧縮して転送してくれるはずなので皆目見当つきませんでした)、ここのところ隙間時間でnginxのドキュメントを読んでいました。

それでも原因が見当たらず、こうなるともうあとはnginxのソースコードを読むくらいしか思いつかないんですが、そのまえにgzip_staticを試したいと思います。

最初からgzip圧縮されているものをぶん投げれば、いくらなんでも勝手に解凍して送るようなことはないだろうと考えたからです。

gzip_static 作戦

まずはcapistrano-maintenanceの設定からいじります。アップロードするテンプレートの指定です (※あとで書きますがこれは動きません)

- set :maintenance_template_path, File.expand_path("../../public/503.html", __FILE__)
+ set :maintenance_template_path, File.expand_path("../../public/503.html.gz", __FILE__)

とりあえずは動作確認が目的なので素手gzip圧縮します

$ gzip public/503.html

nginxのほうは gzip_static on; と、念のためgzipに対応できていないブラウザのために gunzip on; も追加しておきます。

ところでIE 11はgzipだめみたいですね。想定ユーザーがあまり高リテラシーではないと考えられるのでIE切れませんでした。

  # Return a 503 error if the maintenance page exists.
  if (-f /home/xxx/xxx/current/public/system/maintenance.html.gz) {
    return 503;
  }
  location @503 {
    # Serve static assets if found.
    if (-f $request_filename) {
      break;
    }
    # Set root to the shared directory.
    root /home/xxx/xxx/shared/public;
    gzip_static on;    
    gunzip on;
    rewrite ^(.*)$ /system/maintenance.html break;
  }

準備できました。

トレース・オン!

$ bundle exec cap production maintenance:enable
Enter passphrase for /home/kenta-s/.ssh/google_compute_engine:
00:00 maintenance:enable
      Uploading /home/xxx/xxx/xxx/public/system/maintenance.html 18.62%
      Uploading /home/xxx/xxx/xxx/public/system/maintenance.html 37.24%
      Uploading /home/xxx/xxx/xxx/public/system/maintenance.html 55.86%
      Uploading /home/xxx/xxx/xxx/public/system/maintenance.html 74.48%
      Uploading /home/xxx/xxx/xxx/public/system/maintenance.html 93.1%
      Uploading /home/xxx/xxx/xxx/public/system/maintenance.html 100.0%
      01 chmod 644 /home/xxx/xxx/xxx/public/system/maintenance.html
    ✔ 01 oak-production 0.068s

え、maintenance.html...?? maintenance.html.gzのはずでは??? と思ってアップされたファイルを確認してみると、モノはgzだったんですが、ファイル名がmaintenance.htmlになっていて、nginxも単なるhtmlファイルとして解釈してしまいました。

maintenance.html.gzは存在しないのでメンテナンス画面はもちろん出ません。

改めてcapistrano-maintenanceのREADMEを見てみると、

The template file should either be a plaintext or an erb file.

だそうです。

仕方ないのでscpでアップロードしました。

こんどは期待通りメンテナンスページが表示されました。

gzip圧縮も効いています。

f:id:Kenta-s:20190906211657p:plain

f:id:Kenta-s:20190906211839p:plain

10分の1以下になりました。gzipはすごいですね(こなみかん)

capistrano-maintenanceを使うのはやめて、代わりにメンテナンスon/off手順をドキュメントに書いておくことにします。

そもそもcapistrano-maintenanceシンプルすぎて最初からいらなかった説あります。

補足

ファビコンがやたら大きい理由

faviconファイルを置いていないので以下条件に一致せず、ファビコンのリクエストにmaintenance.htmlを返しているためです。

   # Serve static assets if found.
   if (-f $request_filename) {
     break;
   }

これは今回とは別の話なので別の機会で解決します。

maintenance.htmlがそこそこ大きい理由

webpackでスタイルシートもjsもまるごと全部ひとつのhtmlに吐き出すようにしていて、このhtml一枚で23000行くらいあります。

なぜはじめの設定で動かなかったのか

迷宮入りしそうです。 もう一回ドキュメントを頭から読んでみます。ピンと来る方、いたら教えてくださいm(__)m