Varnish Cache
高性能HTTPアクセラレーター。リバースプロキシキャッシュで、Webサイトのレスポンス時間を大幅短縮。VCL(Varnish Configuration Language)による柔軟設定。
キャッシュサーバー
Varnish Cache
概要
Varnish CacheはWebアプリケーション加速器として知られる高性能HTTPリバースプロキシです。HTTPを話すサーバーの前面に配置し、コンテンツをキャッシュすることで、通常300〜1000倍の配信速度向上を実現します。VCL(Varnish Configuration Language)という独自のドメイン固有言語により、リクエスト処理ポリシーを自由に記述可能。20Gbpsの配信性能を通常のハードウェアで実現し、30万リクエスト/秒の処理能力を誇ります。設定変更時はVCLをC言語にコンパイルし、共有オブジェクトとして動的ロードするため再起動不要でのポリシー更新が可能です。
詳細
Varnish Cache 2024年版は、高トラフィックWebサイトにおけるHTTPアクセラレーターの決定版として確固たる地位を維持しています。メモリベースのキャッシュ機能とリバースプロキシによる高速配信で、CDNやメディアサイトでの重要な役割を担います。VCLの強力な設定機能により、キャッシュルール、認証処理、エラーハンドリング、ESI(Edge Side Includes)処理、WebSockets対応まで幅広くカスタマイズ可能。マルチスレッド対応によりモダンなマルチコアプロセッサーを効率活用し、ネットワークバウンドな性能特性でネットワーク速度が事実上の制約となります。
主な特徴
- 超高速パフォーマンス: 通常のハードウェアで20Gbps配信可能
- VCL設定言語: 柔軟なポリシー記述とリアルタイム更新
- リバースプロキシ: HTTPリクエストの高速処理とキャッシング
- ESI対応: エッジサイドインクルードによる部分キャッシング
- WebSockets対応: アップグレードヘッダーの適切な処理
- 動的設定更新: VCL再起動なしでの設定変更
メリット・デメリット
メリット
- 極めて高いパフォーマンス(300-1000倍の速度向上)
- VCLによる高度なカスタマイズ性と柔軟性
- 再起動不要の動的設定変更機能
- 豊富なヘルスチェックとフェイルオーバー機能
- ESI、gzip圧縮、SSL終端などの統合機能
- 大規模サイトでの実績豊富なエンタープライズ採用
デメリット
- VCL習得に専門知識が必要で学習コストが高い
- メモリ使用量が大きく適切なサイジングが必要
- 複雑な設定でのデバッグとトラブルシューティングが困難
- SSL終端時のCPU負荷増大
- キャッシュ無効化の制御が複雑
- 設定ミスによるサービス影響のリスク
参考ページ
書き方の例
インストールと基本セットアップ
# Ubuntu/Debianでのインストール
sudo apt update
sudo apt install varnish
# CentOS/RHEL/Fedoraでのインストール
sudo yum install varnish
# macOS (Homebrew)
brew install varnish
# ソースからのビルド
curl -s https://packagecloud.io/install/repositories/varnishcache/varnish70/script.deb.sh | sudo bash
sudo apt install varnish
# Varnishの起動
sudo systemctl start varnish
sudo systemctl enable varnish
# 設定ファイルの確認
varnishd -f /etc/varnish/default.vcl -T localhost:6082 -a :6081 -s malloc,256m
# 動作確認
curl -I http://localhost:6081
基本VCL設定(/etc/varnish/default.vcl)
vcl 4.0;
# バックエンドサーバー定義
backend default {
.host = "127.0.0.1";
.port = "8080";
.connect_timeout = 60s;
.first_byte_timeout = 60s;
.between_bytes_timeout = 60s;
}
# 追加バックエンド例
backend webserver1 {
.host = "192.168.1.10";
.port = "80";
.probe = {
.url = "/health";
.timeout = 1s;
.interval = 5s;
.window = 5;
.threshold = 3;
}
}
backend webserver2 {
.host = "192.168.1.11";
.port = "80";
.probe = {
.url = "/health";
.timeout = 1s;
.interval = 5s;
.window = 5;
.threshold = 3;
}
}
# ロードバランサーの設定
import directors;
sub vcl_init {
new cluster = directors.round_robin();
cluster.add_backend(webserver1);
cluster.add_backend(webserver2);
}
# リクエスト受信処理
sub vcl_recv {
# バックエンドサーバーの選択
if (req.url ~ "^/api/") {
set req.backend_hint = cluster.backend();
} else {
set req.backend_hint = default;
}
# 静的ファイルのキャッシュ対象
if (req.url ~ "\.(jpg|jpeg|png|gif|ico|css|js|pdf|txt)$") {
unset req.http.Cookie;
return(hash);
}
# POST/PUT/DELETEリクエストはキャッシュしない
if (req.method != "GET" && req.method != "HEAD") {
return(pass);
}
# 管理ページはキャッシュしない
if (req.url ~ "^/admin") {
return(pass);
}
# 認証が必要なページはキャッシュしない
if (req.http.Authorization) {
return(pass);
}
# Cookieを持つリクエストの処理
if (req.http.Cookie) {
# セッションCookieがある場合はパス
if (req.http.Cookie ~ "session_id") {
return(pass);
}
# その他のCookieは削除
unset req.http.Cookie;
}
return(hash);
}
# バックエンド応答処理
sub vcl_backend_response {
# キャッシュ時間の設定
if (bereq.url ~ "\.(jpg|jpeg|png|gif|ico)$") {
set beresp.ttl = 1h;
set beresp.grace = 1m;
} elseif (bereq.url ~ "\.(css|js)$") {
set beresp.ttl = 1h;
set beresp.grace = 1m;
} elseif (bereq.url ~ "^/api/") {
set beresp.ttl = 5m;
set beresp.grace = 30s;
} else {
set beresp.ttl = 10m;
set beresp.grace = 1m;
}
# ESI処理の有効化
if (bereq.url == "/dynamic-page") {
set beresp.do_esi = true;
set beresp.ttl = 1h;
}
# gzip圧縮の有効化
if (beresp.http.content-type ~ "text|javascript|json|xml") {
set beresp.do_gzip = true;
}
# キャッシュしない条件
if (beresp.status >= 400) {
set beresp.ttl = 0s;
}
# セットクッキーヘッダーがある場合はキャッシュしない
if (beresp.http.Set-Cookie) {
set beresp.ttl = 0s;
}
return(deliver);
}
# 配信前処理
sub vcl_deliver {
# キャッシュヒット/ミスの情報を追加
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
set resp.http.X-Cache-Hits = obj.hits;
} else {
set resp.http.X-Cache = "MISS";
}
# デバッグ情報の追加(本番では削除)
set resp.http.X-Served-By = "Varnish";
return(deliver);
}
# エラーページのカスタマイズ
sub vcl_backend_error {
if (beresp.status == 503 && bereq.retries < 3) {
return(retry);
}
set beresp.http.Content-Type = "text/html; charset=utf-8";
synthetic({"
<html>
<head><title>Service Temporarily Unavailable</title></head>
<body>
<h1>Service Temporarily Unavailable</h1>
<p>The server is temporarily unable to service your request.</p>
</body>
</html>
"});
return(deliver);
}
VCL管理操作(varnishadm)
# varnishadmでの接続
sudo varnishadm
# VCL設定の管理
varnishadm> vcl.list
# ロードされているVCL一覧表示
varnishadm> vcl.load new_config /etc/varnish/new.vcl
# 新しいVCL設定をロード
varnishadm> vcl.use new_config
# 新しい設定をアクティブ化
varnishadm> vcl.discard old_config
# 古い設定を削除
# キャッシュ管理
varnishadm> ban req.url ~ "^/api/"
# 特定URLパターンのキャッシュを無効化
varnishadm> ban req.http.host == "example.com"
# 特定ホストのキャッシュを無効化
varnishadm> ban.list
# バン(無効化)ルールの一覧表示
# 統計情報の確認
varnishadm> stats
# Varnish統計情報表示
varnishadm> param.show
# パラメータ一覧表示
varnishadm> param.set default_ttl 300
# デフォルトTTLを5分に設定
# バックエンドヘルスチェック状況
varnishadm> backend.list
# バックエンドサーバーの状態確認
# ログ出力制御
varnishadm> param.set vsl_mask +VCL_call,-Hit
# VCLコール有効化、ヒット情報無効化
ヘルスチェックとフェイルオーバー設定
vcl 4.0;
# 詳細なヘルスプローブ設定
probe healthcheck {
.url = "/health";
.request =
"GET /health HTTP/1.1"
"Host: example.com"
"Connection: close"
"User-Agent: Varnish Health Probe";
.timeout = 2s;
.interval = 5s;
.window = 5;
.threshold = 3;
.initial = 3;
}
# プライマリバックエンド
backend primary {
.host = "192.168.1.10";
.port = "80";
.probe = healthcheck;
.connect_timeout = 5s;
.first_byte_timeout = 10s;
.between_bytes_timeout = 2s;
}
# セカンダリバックエンド
backend secondary {
.host = "192.168.1.11";
.port = "80";
.probe = healthcheck;
.connect_timeout = 5s;
.first_byte_timeout = 10s;
.between_bytes_timeout = 2s;
}
# フェイルオーバーディレクター
import directors;
sub vcl_init {
new failover = directors.fallback();
failover.add_backend(primary);
failover.add_backend(secondary);
}
sub vcl_recv {
set req.backend_hint = failover.backend();
return(hash);
}
# バックエンドエラー時のリトライ処理
sub vcl_backend_error {
if (beresp.status == 503 && bereq.retries < 2) {
return(retry);
}
# カスタムエラーページ
set beresp.http.Content-Type = "text/html; charset=utf-8";
set beresp.status = 503;
synthetic({"
<!DOCTYPE html>
<html>
<head>
<title>Service Unavailable</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
.error { color: #cc0000; }
</style>
</head>
<body>
<h1 class="error">Service Temporarily Unavailable</h1>
<p>申し訳ございませんが、一時的にサービスをご利用いただけません。</p>
<p>しばらく経ってから再度アクセスしてください。</p>
<p><a href="javascript:history.back()">戻る</a></p>
</body>
</html>
"});
return(deliver);
}
ESI(Edge Side Includes)設定例
# ESI用バックエンド設定
sub vcl_backend_response {
# ESI処理対象ページの設定
if (bereq.url ~ "^/dynamic/") {
set beresp.do_esi = true;
set beresp.ttl = 1h; # メインコンテンツのTTL
}
# ESIフラグメントの設定
if (bereq.url ~ "^/fragments/") {
set beresp.ttl = 5m; # フラグメントのTTL
}
return(deliver);
}
HTMLテンプレートでのESI使用例:
<!DOCTYPE html>
<html>
<head>
<title>Dynamic Page with ESI</title>
</head>
<body>
<h1>メインコンテンツ</h1>
<p>このページは1時間キャッシュされます。</p>
<!-- ユーザー固有情報(5分キャッシュ) -->
<esi:include src="/fragments/user-info?user=123" />
<!-- 新着記事(1分キャッシュ) -->
<esi:include src="/fragments/latest-news" ttl="60" />
<!-- 天気情報(キャッシュなし) -->
<esi:include src="/fragments/weather" ttl="0" />
<!-- エラー時のフォールバック -->
<esi:include src="/fragments/ads">
<esi:alt>
<p>広告を読み込めませんでした。</p>
</esi:alt>
</esi:include>
</body>
</html>
WebSockets対応設定
# WebSocket接続の処理
sub vcl_recv {
# WebSocketアップグレード要求をパイプ経由で転送
if (req.http.upgrade ~ "(?i)websocket") {
return(pipe);
}
}
sub vcl_pipe {
# アップグレードヘッダーの転送
if (req.http.upgrade) {
set bereq.http.upgrade = req.http.upgrade;
set bereq.http.connection = req.http.connection;
}
}
SSL/TLS終端とHTTPS設定
# Nginxとの組み合わせ例(SSL終端)
# /etc/nginx/sites-available/varnish-ssl
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
location / {
proxy_pass http://127.0.0.1:6081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
VCLでのHTTPS処理:
sub vcl_recv {
# HTTPSリダイレクト処理
if (req.http.X-Forwarded-Proto != "https") {
return(synth(301, "https://" + req.http.host + req.url));
}
# セキュリティヘッダーの設定
if (req.http.host == "example.com") {
set req.http.X-Forwarded-Proto = "https";
}
}
sub vcl_deliver {
# セキュリティヘッダーの追加
set resp.http.Strict-Transport-Security = "max-age=31536000; includeSubDomains";
set resp.http.X-Content-Type-Options = "nosniff";
set resp.http.X-Frame-Options = "DENY";
set resp.http.X-XSS-Protection = "1; mode=block";
return(deliver);
}
監視とロギング設定
# varnishlogによるアクセスログ
sudo varnishlog -w /var/log/varnish/access.log
# 特定条件でのフィルタリング
sudo varnishlog -q "ReqURL ~ '^/api/'" -w /var/log/varnish/api.log
# リアルタイム監視
sudo varnishlog -q "RespStatus >= 400"
# 統計情報の取得
varnishstat -1
# Prometheusメトリクス出力
varnish_exporter --varnish.instance=""