nginx-proxyからの卒業!Traefikで実現する、よりスマートなリバースプロキシ環境


コンテナ環境でWebサービスを公開する際、リバースプロキシは不可欠な存在です。

これまで多くの方がnginx-proxyとそのコンパニオンであるacme-companionを利用して、手軽にSSL証明書の自動取得・更新を実現していることでしょう。

しかし、サービスの規模が大きくなるにつれて、より動的で高機能なリバースプロキシが欲しくなる場面も増えてきます。

そこでおすすめしたいのが Traefik です。

この記事では、既存のnginx-proxy環境からTraefikへ移行するメリットと、その具体的な設定方法をコードを交えて解説します。

なぜTraefikへ?移行のメリット

nginx-proxyも素晴らしいツールですが、Traefikはクラウドネイティブ時代のリバースプロキシとして、さらに多くの利点を提供します。

  • 設定の自動検出と動的更新: TraefikはDockerやKubernetesなどのコンテナオーケストレーターと連携し、コンテナの起動・停止を検知してルーティング設定を自動で更新します。設定ファイルを手動で再読み込みする必要はありません。
  • ラベルベースの直感的な設定: docker-compose.yml内にサービスのルーティング情報を「ラベル」として記述するだけで設定が完了します。どのコンテナがどのドメインで公開されているかが一目瞭然です。
  • 強力な証明書管理 (ACME): Let's Encryptを利用したSSL証明書の取得・更新が非常に簡単です。
    特に、CloudflareなどのDNSプロバイダと連携するDNS-01チャレンジに標準で対応しており、ワイルドカード証明書の取得や、ポート80を外部に公開する必要がないといった利点があります。
    この記事ではCloudflareを利用した内容となっています。
  • 豊富なミドルウェア: 認証、リダイレクト、ヘッダー書き換え、IPホワイトリストなど、豊富な機能が「ミドルウェア」として提供されており、必要に応じて柔軟に組み合わせることができます。
  • 美しいダッシュボード: 現在のルーティング設定、各サービスの状態、ミドルウェアの適用状況などを視覚的に確認できるダッシュボードが標準で付属しています。

既存環境(nginx-proxy)の確認

これまで稼働してきたnginx-proxyacme-companionを使ったdocker-compose.ymlを見てみましょう。

shared/docker-compose.yml

version: "2"
services:
  proxy:
    image: nginxproxy/nginx-proxy
    container_name: proxy
    privileged: true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - /etc/localtime:/etc/localtime:ro
      - ./certs:/etc/nginx/certs:ro
      - vhost:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
      - ./htpasswd:/etc/nginx/htpasswd
      - ./proxy-settings.conf:/etc/nginx/conf.d/proxy-settings.conf
    restart: always
    logging:
      options:
        max-size: 5m
        max-file: "10"

  acme-companion:
    image: nginxproxy/acme-companion
    container_name: nginx-proxy-acme
    privileged: true
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./certs:/etc/nginx/certs:rw
      - vhost:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
      - acme:/etc/acme.sh
    volumes_from:
      - proxy
    restart: always

volumes:
  html:
  vhost:
  acme:

networks:
  default:
    external:
      name: shared

この構成では、proxyサービスがリバースプロキシとして機能し、acme-companionがSSL証明書の管理を行っています。

新しいサービスを公開するには、そのサービスのdocker-compose.ymlVIRTUAL_HOSTLETSENCRYPT_HOSTといった環境変数を設定する必要がありました。

Traefik環境の構築

次に、Traefikを使った新しいリバースプロキシ環境を構築します。

ファイルはdocker-compose.ymltraefik.ymlの2つです。

1. traefik/docker-compose.yml

Traefik自身をコンテナとして起動するためのファイルです。

services:
  traefik:
    image: traefik:latest
    container_name: traefik
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - ./config:/etc/traefik/config
      - ./acme:/etc/traefik/acme
    environment:
      # CloudflareのAPI情報を環境変数として渡す
      - CLOUDFLARE_EMAIL=foo@example.com
      - CLOUDFLARE_DNS_API_TOKEN=${CF_DNS_API_TOKEN} # .envファイルなどから読み込むのが安全です
      - TZ=Asia/Tokyo
    labels:
      # --- Traefik自身のダッシュボード設定 ---
      - "traefik.enable=true"
      # ホスト名'traefik.your.domain'でアクセスできるようにする
      - "traefik.http.routers.dashboard.rule=Host(`traefik.your.domain`)"
      # Traefikの内部APIサービス(@internal)に接続
      - "traefik.http.routers.dashboard.service=api@internal"
      # TLSを有効化
      - "traefik.http.routers.dashboard.tls=true"
      # 'cloudflare'という名前のCertResolverを使用する
      - "traefik.http.routers.dashboard.tls.certresolver=cloudflare"
    networks:
      - traefik-network

networks:
  traefik-network:
    external: true

注目すべきはlabelsセクションです。nginx-proxyが環境変数で設定していたのに対し、Traefikではラベルを使ってルーティングを定義します。

ここではTraefik自身のダッシュボードをtraefik.your.domainというホスト名で公開する設定を記述しています。

Cloudflareと連携するために必要なAPIトークンは、セキュリティのため.envファイルに記述し、docker-composeがそれを読み込むように構成するのが一般的です。

traefik-networkは以下のコマンドであらかじめ作成しておく必要があります:

docker network create traefik-network

2. traefik/traefik.yml

Traefikの振る舞いを定義する中心的な設定ファイルです。

# APIとダッシュボードを有効にする
api:
  dashboard: true

global:
  sendAnonymousUsage: false

# エントリーポイント(入口)の定義
entryPoints:
  # HTTP (80番ポート)
  web:
    address: ":80"
    http:
      # HTTPへのアクセスをHTTPS(websecure)へリダイレクトする
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  # HTTPS (443番ポート)
  websecure:
    address: ":443"

# 証明書リゾルバの定義 (Let's Encrypt)
certificatesResolvers:
  # 'cloudflare'という名前のリゾルバを定義
  cloudflare:
    acme:
      email: foo@example.com
      storage: /etc/traefik/acme/acme.json
      # DNS-01チャレンジを使用する
      dnsChallenge:
        provider: cloudflare
        delayBeforeCheck: 20
        resolvers:
          - "1.1.1.1:53"
          - "1.0.0.1:53"

# プロバイダーの定義
providers:
  # Dockerをプロバイダーとして使用する
  docker:
    endpoint: "unix:///var/run/docker.sock"
    # デフォルトではコンテナを公開しない
    exposedByDefault: false
  # ファイルをプロバイダーとして使用する
  file:
    directory: /etc/traefik/config
    watch: true

# ログ設定
log:
  level: INFO # 本番環境ではINFOやWARNを推奨

accessLog:
  format: json

このファイルでは、以下の重要な設定を行っています。

  • entryPoints: Traefikへのトラフィックの入口を定義します。web(HTTP)とwebsecure(HTTPS)を定義し、HTTPからHTTPSへの常時リダイレクトを設定しています。
  • certificatesResolvers: SSL証明書をどのように取得するかを定義します。
  • ここではcloudflareという名前でリゾルバを作成し、ACMEのdnsChallenge(DNS-01チャレンジ)を利用して証明書を取得するよう設定しています。
  • これにより、サーバーが直接インターネットに80番ポートを公開していなくても証明書を取得できます。
  • providers: Traefikがどこからルーティング設定を読み込むかを定義します。
  • dockerプロバイダーを有効にすることで、コンテナのラベルを監視して動的に設定を反映させることができます。
  • exposedByDefault: falseは、明示的にラベルで許可したコンテナのみを公開するためのセキュリティ上重要な設定です。

アプリケーションをTraefik経由で公開する

設定が完了したら、あとはアプリケーションのdocker-compose.ymlTraefik用のラベルを追加するだけです。以下はwhoamiというシンプルなサービスを公開する例です。

services:
  whoami:
    image: traefik/whoami
    container_name: whoami
    restart: always
    labels:
      # --- Traefik用の設定 ---
      - "traefik.enable=true"
      # このサービスが属するネットワークを指定
      - "traefik.docker.network=traefik-network"

      # --- HTTPルーターの設定 ---
      # 'whoami-http'という名前のルーターを定義
      - "traefik.http.routers.whoami-http.entrypoints=web"
      # 'whoami.your.domain'でアクセスされたらこのルーターに流す
      - "traefik.http.routers.whoami-http.rule=Host(`whoami.your.domain`)"
      # HTTPからHTTPSへのリダイレクトミドルウェアを指定
      - "traefik.http.routers.whoami-http.middlewares=https-redirect"

      # --- HTTPSルーターの設定 ---
      # 'whoami-https'という名前のルーターを定義
      - "traefik.http.routers.whoami-https.entrypoints=websecure"
      # 'whoami.your.domain'でアクセスされたらこのルーターに流す
      - "traefik.http.routers.whoami-https.rule=Host(`whoami.your.domain`)"
      # TLSを有効にする
      - "traefik.http.routers.whoami-https.tls=true"
      # 'cloudflare'リゾルバを使って証明書を取得
      - "traefik.http.routers.whoami-https.tls.certresolver=cloudflare"

      # --- サービスの設定 ---
      # 'whoami-svc'という名前のサービスを定義
      - "traefik.http.services.whoami-svc.loadbalancer.server.port=80"

    networks:
      - traefik-network

networks:
  traefik-network:
    external: true

このコンテナを起動すると、Traefikがラベルを検知し、whoami.your.domainへのアクセスをこのコンテナのポート80に転送するよう自動で設定します。同時に、certificatesResolversで定義したcloudflareを使ってSSL証明書を申請・取得し、HTTPS通信を有効化します。

私の環境下において、nginx-proxyからtraefikへ移行した実際のサービスです。

  • トップページ用のNginx
  • Gitea
  • Vaultwarden
  • FreshRSS
  • Growi
  • WordPress

まとめ

nginx-proxyからTraefikへの移行は、最初は少し複雑に感じるかもしれません。しかし、一度環境を構築してしまえば、その後のサービス追加や設定変更は驚くほど簡単になります。

  • 設定の集約: docker-compose.ymlのラベルにルーティング情報を集約できる。
  • 運用の自動化: コンテナを起動/停止するだけで、ルーティングとSSL証明書が自動で管理される。
  • 高い拡張性: 豊富なミドルウェアで、将来の要求にも柔軟に対応できる。

いままで動作してきたサービスがtraefikで上手くリバースプロキシとして稼働させるか不安で、なかなか手をつけなかったのですが、ようやく重い腰をあげて作業してみました。

Nginxを起動するとGiteaが起動しないといったGiteaとNginxが競合したトラブルに悩まされましたが解決後は全てのサービスが快適になりました。

なお、こちらのスライドもわかりやすいのでオススメです。

dashboadをBasic認証

上記の設定ではDashboardへの接続が公開されるので、誰でも閲覧することが出来るため、セキュリティ的には好ましくありません。

そこで、DashboardのみBasic認証の機能を追加しました。

.envにBasic認証の環境変数を追加

ユーザ名とパスワードでハッシュ値を生成

htpasswd -nb username newpassword
Icon in a callout block
Docker Composeでは$が環境変数として解釈されるため、リテラルの$を使用するには$$とエスケープする必要があります。
TRAEFIK_AUTH_HASH=admin:\$\$apr1\$\$NTbuvW.w\$\$/0gNXk8Q86009XxA.K/5J1

docker-compose.ymlにlabel追加

      - "traefik.http.routers.dashboard.middlewares=dashboard-auth"
      - "traefik.http.middlewares.dashboard-auth.basicauth.users=${TRAEFIK_AUTH_HASH}"

traefikのコンテナを再起動でBasic認証が適用されます