午前2時、サーバーが重い——「どのドメインが帯域幅を食い尽くしているのか?」
本番環境がタイムアウトし始めたあの夜のことは今でも鮮明に覚えている。Grafanaを開いたが、CPUは正常、RAMも余裕あり、ディスクI/Oも特に異常なし。それなのにレスポンスタイムが8秒まで跳ね上がっていた。当時そのサーバーは1つのNginx上で5つの異なるドメインをホストしていて、眠い目をこすりながらアクセスログを1行ずつgrepしなければどのドメインが問題を引き起こしているかわかる方法がなかった。
あの夜の教訓:仮想ホストごとにトラフィックを分析する必要がある場合、システムメトリクスだけでは不十分だ。Nginxのデフォルトのstub_statusは合計コネクション数しか見せてくれない——マルチドメイン環境では意味がない。VTS Moduleが必要だ。
Nginx VTS Moduleとは?
Nginx Virtual Host Traffic Status(VTS)は、各サーバーブロックの詳細なメトリクスを提供するサードパーティモジュールだ。集計された合計ではなく、ドメインごとの内訳を確認できる:
- 仮想ホストごとのリクエストレートとレスポンスタイム
- 各ドメインの送受信帯域幅
- ホストごとのHTTPステータスコード分布(2xx、4xx、5xx)
- NginxをリバースプロキシとしているときのUpstreamレスポンスタイム
VTSはPrometheus形式でメトリクスを公開する——Prometheus + Grafanaと組み合わせることで、完全なNginx監視スタックが完成する。ちゃんとしたモニタリングを構築する前は、各サーバーにSSHして確認しなければならなかった。今はダッシュボードを開くだけですべてが見える。特にマルチドメイン環境においては、数年前に知っておきたかったツールだ。
実践:ゼロから構築する
ステップ1:VTS Moduleと一緒にNginxをコンパイルする
VTSはビルトインモジュールではないため、ソースからコンパイルする必要がある。まず現在のNginxのバージョンを確認しよう:
nginx -v
# nginx version: nginx/1.24.0
依存パッケージをインストールする:
sudo apt update
sudo apt install -y build-essential libpcre3-dev zlib1g-dev libssl-dev libgd-dev git
対応バージョンのNginxソースをダウンロードし、VTSをcloneする:
cd /tmp
wget http://nginx.org/download/nginx-1.24.0.tar.gz
tar -xzf nginx-1.24.0.tar.gz
git clone https://github.com/vozlt/nginx-module-vts.git
現在動いているNginxのconfigure argumentsを取得する——重要:既存のモジュールをそのまま保持する必要がある:
nginx -V 2>&1 | grep "configure arguments"
VTSを追加してコンパイルする(既存argumentsの末尾に--add-moduleを追加):
cd /tmp/nginx-1.24.0
./configure \
--with-compat \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_gzip_static_module \
--add-module=../nginx-module-vts
make
sudo make install
Nginxをaptでインストールしている場合は、古いバイナリをバックアップしてから置き換える:
sudo cp /usr/sbin/nginx /usr/sbin/nginx.bak
sudo cp /tmp/nginx-1.24.0/objs/nginx /usr/sbin/nginx
# モジュールが追加されていることを確認する
nginx -V 2>&1 | grep vts
ステップ2:NginxでVTSメトリクスを公開する
nginx.confのhttpブロックに以下を追加する:
http {
# VTSを有効にする——必須設定
vhost_traffic_status_zone;
# 仮想ホストごとにメトリクスを分離する
vhost_traffic_status_filter_by_host on;
server {
listen 9145; # 内部メトリクス専用ポート
server_name localhost;
location /metrics {
vhost_traffic_status_display;
vhost_traffic_status_display_format prometheus;
# Prometheusサーバーとlocalhostのみアクセスを許可する
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;
}
location /nginx_status {
vhost_traffic_status_display;
vhost_traffic_status_display_format html;
allow 127.0.0.1;
deny all;
}
}
# 通常の仮想ホスト
server {
listen 80;
server_name example.com;
# ... 通常の設定
}
server {
listen 80;
server_name blog.example.com;
# ... 通常の設定
}
}
テストしてリロードする:
sudo nginx -t
sudo systemctl reload nginx
# メトリクスが公開されているか確認する
curl http://localhost:9145/metrics | head -20
正しい出力は以下のようになる:
# HELP nginx_vts_info Nginx info
# TYPE nginx_vts_info gauge
nginx_vts_info{hostname="prod-server-01",version="1.24.0"} 1
# HELP nginx_vts_server_bytes_total The request/response bytes
# TYPE nginx_vts_server_bytes_total counter
nginx_vts_server_bytes_total{host="example.com",direction="in"} 1234567
nginx_vts_server_bytes_total{host="example.com",direction="out"} 9876543
nginx_vts_server_bytes_total{host="blog.example.com",direction="in"} 456789
各hostラベルごとにメトリクスが分かれていれば正常だ。すべてが*にまとまっている場合は、vhost_traffic_status_filter_by_host onディレクティブを確認してほしい。
ステップ3:PrometheusでVTSのスクレイプを設定する
prometheus.ymlに新しいジョブを追加する:
scrape_configs:
# ... 既存のジョブはそのまま
- job_name: 'nginx-vts'
static_configs:
- targets: ['prod-server-01:9145']
labels:
server: 'prod-server-01'
env: 'production'
metrics_path: '/metrics'
scrape_interval: 15s
再起動なしでPrometheusをリロードする:
curl -X POST http://localhost:9090/-/reload
# またはsystemdを使用する場合
sudo systemctl reload prometheus
http://prometheus:9090/targetsにアクセスして、nginx-vtsターゲットがUP状態になっていることを確認する。
ステップ4:Grafanaダッシュボードをインポートする
Grafana Dashboard ID 14824はNginx VTS向けで最も広く使われているダッシュボードだ:
- Grafana → Dashboards → Importに進む
- ID:
14824を入力 → Loadをクリック - Prometheusデータソースを選択 → Importをクリック
ダッシュボードでは以下がすぐに確認できる:
- ドメインごとのリクエスト/秒の内訳
- リアルタイムの送受信帯域幅
- ホストごとのHTTP 5xxエラーレート——アラートが来たとき最初に確認するのがここだ
- Nginxがアプリサーバーのリバースプロキシとして動いているときのUpstreamレスポンスタイム
ステップ5:ドメインごとの5xxスパイクにアラートを設定する
どのドメインでエラーが増加しているかを検出するためのPromQL——本番環境で実際に使っているクエリだ:
# ドメインごとの5xx割合(5分間)(5%超でアラート)
sum by (host) (
rate(nginx_vts_server_requests_total{code=~"5.."}[5m])
)
/
sum by (host) (
rate(nginx_vts_server_requests_total[5m])
) > 0.05
# またはシンプルに——絶対レート
rate(nginx_vts_server_requests_total{code=~"5.."}[5m]) > 0.5
これをGrafana AlertingにTelegramまたはメールの通知チャンネルとして追加しておけば、5xxスパイクが起きたとき顧客から電話が来る前にアラートを受け取れる。
よくあるトラブルシューティング
メトリクスエンドポイントが404を返す
# モジュールがコンパイルされているか確認する
nginx -V 2>&1 | grep vts
# 設定のシンタックスを確認する
sudo nginx -t
# それでも404の場合はエラーログを確認する
sudo tail -f /var/log/nginx/error.log
すべてのメトリクスがホスト「*」にまとまってしまう
httpブロックにvhost_traffic_status_filter_by_host on;が設定されていない。このディレクティブはhttpレベルに配置する必要があり、serverブロック内ではない。
Prometheusがスクレイプできない
# Prometheusサーバーからテストする
curl -v http://prod-server-01:9145/metrics
# ファイアウォールを確認する
sudo ufw status
sudo iptables -L INPUT -n | grep 9145
# 必要に応じてポートを開放する(Prometheusサーバーのみ)
sudo ufw allow from 10.0.1.50 to any port 9145
まとめ
あの試行錯誤の夜以来、VTS Moduleはマルチドメインで動く新しいNginxサーバーをセットアップするとき最初にインストールするものになった。ホストごとのトラフィック内訳によって、問題の切り分けが数時間から数分に短縮された。
Nginx VTS + Prometheus + Grafanaのスタックは、1台のサーバーで複数のサービスをホストしている場合や、トラフィックスパイクがどのドメインから来ているかを顧客に証明する必要がある場合に特に有効だ。これはアクセスログを眺めるだけでは得られないリアルタイムの可視性だ。
セキュリティ上の注意点:/metricsエンドポイントはかなり機密性の高いトラフィックパターン情報を公開する。常にIPホワイトリストで制限し、社内VPNの背後に置くことを検討してほしい——インターネットに公開するのは厳禁だ。

