コンテナ化したVarnish Cache(Vinyl Cache)のメトリックをMackerelで可視化する

こんにちは、Androidアプリケーションエンジニアのid:rokouchaです。

皆さんはVarnish Cache改めVinyl Cache1(以下Varnish)を使っていますか、コンテナ化していますか、メトリックを取得できていますか?

オンプレミスなサーバやEC2など仮想マシン上で動かしている時は、サーバーで動いているエージェントでmackerel-plugin-varnishを有効化するだけでメトリックを取得できていました。

しかしメトリックプラグイン一覧にも記載されているように、container-agentにはVarnish用のプラグインが提供されていません。

そこで本記事では、コンテナ化したVarnishでもMackerelでメトリックを可視化できるようにするための手法をご紹介します。

というわけで、この記事はMackerel Advent Calendar 2025の18日目です。


mackerel-plugin-varnishがやっていること

まずはmackerel-plugin-varnishがどのようにVarnishのメトリックを取得しているか確認しておきます。

mackerel-agent-plugins/mackerel-plugin-varnish/lib/varnish.go at d13dd0608c534534d96f46211915196e9c3666bf · mackerelio/mackerel-agent-plugins · GitHub

if m.VarnishName == "" {
    out, err = exec.Command(m.VarnishStatPath, "-1").CombinedOutput()
} else {
    out, err = exec.Command(m.VarnishStatPath, "-1", "-n", m.VarnishName).CombinedOutput()
}

VarnishNameによる分岐がありますが、どちらの場合でもexec.Command()VarnishStatPathを実行しています。

VarnishStatPathの実態はvarnishstat2というコマンドで、Varnishに付属するユーティリティです。 このコマンドは、実行中のvarnishdデーモンに関するメトリックを取得して出力してくれます。

このように、mackerel-plugin-varnishではvarnishstatコマンドを実行し、出力結果を整形してカスタムメトリックとしてMackerelに送信しています。 ではcontainer-agentでもmackerel-plugin-varnishのようにvarnishstatを動かせばメトリックが取れるのでしょうか。

結論から言えば、メトリックは取れません。

Varnishの実装起因による制限

コマンドのヘルプを見て、あれ?と思われたかもしれませんが、varnishstatコマンドにはどのホストに接続するのかを変更するオプションが存在しません。

というのも、VarnishのメトリックはVarnish Shared Memory3という共有メモリから取得しているため、同じホスト内からしか取得できません。

実際にvarnishstatのコードを確認すると、VSC_Iterを呼び出して共有メモリのデータを順番に取得しています。

varnish-cache/bin/varnishstat/varnishstat.c at 63806461a205a11da12deb21051f654e35acee9e · varnishcache/varnish-cache · GitHub

static void
do_once(struct vsm *vsm, struct vsc *vsc)
{
    struct vsc *vsconce = VSC_New();
    struct once_priv op;

    AN(vsconce);
    AN(VSC_Arg(vsconce, 'f', "MAIN.uptime"));

    memset(&op, 0, sizeof op);
    op.pad = 18;

    (void)VSC_Iter(vsconce, vsm, do_once_cb_first, &op);
    VSC_Destroy(&vsconce, vsm);
    (void)VSC_Iter(vsc, vsm, do_once_cb, &op);
}

では、コンテナ化したVarnishのメトリックを取得することは不可能なのでしょうか。

それでもメトリックを取得したい

ここまでの調査で、共有メモリによる制限のために別コンテナからのメトリック取得ができない事が分かっています。 それでもメトリックを取得するためにはどうすればいいでしょうか。

実は簡単な解法が1つあります。Varnishと同じコンテナ内でメトリックを取得すればいいのです。

同じコンテナ内であれば共有メモリにそのままアクセスできるので、何も難しい事を考えずにメトリックを取得できます。 しかし、流石にcontainer-agentがあるのに同じコンテナ内でVarnish用のエージェントを動かしたくはないので、別なツールを使ってメトリックを収集しましょう。

prometheus_varnish_exporterを使ってメトリックを取得する

Varnishからメトリックを取得して外へ送る方法は何種類かありますが、今回はprometheus_varnish_exporterを使ってメトリックを取得します。

GitHub - jonnenauha/prometheus_varnish_exporter: Varnish exporter for Prometheus

取得したメトリックはMackerelで見たいので、Varnish→prometheus_varnish_exporter→OpenTelemetry Collector→Mackerelの順に流れるように設定します。

まずはVarnishとprometheus_varnish_exporterが動くコンテナイメージを用意しましょう。

# syntax=docker/dockerfile:1

FROM golang:1.25.5-trixie AS build

ENV EXPORTER_VERSION=1.6.1

RUN curl -Lo /tmp/prometheus_varnish_exporter-${EXPORTER_VERSION}.tar.gz https://github.com/jonnenauha/prometheus_varnish_exporter/archive/refs/tags/${EXPORTER_VERSION}.tar.gz \
    && tar -xzf /tmp/prometheus_varnish_exporter-${EXPORTER_VERSION}.tar.gz -C /tmp

WORKDIR /tmp/prometheus_varnish_exporter-${EXPORTER_VERSION}

RUN --mount=type=cache,target=/go/pkg/mod/,sharing=locked \
    --mount=type=cache,target=/root/.cache/go-build \
    go mod download

RUN --mount=type=cache,target=/go/pkg/mod/ \
    --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 go build -o /prometheus_varnish_exporter -ldflags "-X 'main.Version=${EXPORTER_VERSION}'"

FROM varnish:8.0.0

COPY --from=build /prometheus_varnish_exporter /usr/local/bin/prometheus_varnish_exporter

CMD ["sh", "-c", "/usr/local/bin/docker-varnish-entrypoint & /usr/local/bin/prometheus_varnish_exporter"]

prometheus_varnish_exporterはソースコードだけではなくビルド済みの実行ファイルも配布されていますが、ARM向けのビルドは用意されていないのでDockerfile内でビルドしています。 公式のスクリプトを参考にしてビルド時にバージョン情報を埋め込むようにしていますが、埋め込まなくても動作するのでお好みに合わせて調整してください。

また、このままだとprometheus_varnish_exporterのオプションが全てデフォルトのまま起動します。もしポートを変更したり接続するインスタンスを変更する場合は、適宜オプションを修正してください。

この段階でコンテナを起動してcontainer:9131/metricsにアクセスすると、Prometheusが取り込める形式でメトリックが表示されます。 ここまでくれば、あとはこのメトリックをOpenTelemetryで収集してMackerelに送るようにして、メトリックを可視化できます。

receivers:
  prometheus:
    config:
      global:
        scrape_interval: 1m
      scrape_configs:
        - job_name: prometheus-varnish-exporter
          static_configs:
            - targets:
                - container:9131

exporters:
  mackerelotlp:

service:
  pipelines:
    metrics:
      receivers: [prometheus]
      exporters: [mackerelotlp]

上記のような設定でOpenTelemetry Collectorを動かすことで、prometheus_varnish_exporterが取得したメトリックをMackerelに送信できます。 なお、まだ実験版ですが、Mackerel OTLP Exporterを使うと面倒なエンドポイントやバッチサイズの設定を端折れます。 ぜひ試してみてください。

全ての設定が完了すると、MackerelのメトリックエクスプローラーなどでVarnishのメトリックを閲覧できるようになります。

おわりに

このような手法で、Varnishをコンテナ化してもメトリックを取得し、Mackerelで可視化できました。

従来オンプレミスや仮想マシンで提供していたサービスをコンテナ化したり、通常のCDNでは実現できないような高度かつ複雑なキャッシュ制御をする時など、まだまだVarnishが欠かせないタイミングは十分ありえます。 そのような場合でも、今回の手法によってOpenTelemetryにネイティブで対応しているアプリケーションと遜色のないオブザーバビリティを確保できます。

なお余談ですが、VSMというのは共有メモリと名がついているものの、実態は単なるファイルです。ただし、通常のように読み込んでいるのではなく、Varnishの各プロセスが自分のメモリ領域にmmapをしています4。 そのため、本来であればVSMファイルを複数コンテナで共有できるように設定し、prometheus_varnish_exporterなどはサイドカーコンテナとして動かすのがベストな方法になるでしょう。

しかし、AWS ECSなどマネージドなコンテナランタイムでは、複数コンテナ間で書き込み可能なファイルを共有するというユースケースは一般的ではありません。 EFSなど外部のストレージサービスを別途用意してコンテナに対してマウントするように設定をする必要があるなど、メトリックを取得するための構成としてはオーバーエンジニアリングと感じます。

求められる性能や要件などによりますが、手軽にコンテナ化したVarnishのメトリックを可視化したい場合は、ぜひ本記事の手法を試してみてください。