リバースプロキシとしての Traefik

## リバースプロキシとしての NGINX の代替を探していた

業務で NGINX をリバースプロキシとして利用しているのですが、Active Health Check をしたいという要求が出てきました。
有償版の NGINX Plus であれば Active Health Check が使えるのですが、OSS 版にはありません。
plugin を導入する・自作するなども選択肢としてありますが、基本機能で担保してもらえるに越したことはありません。
代替候補として、HAProxy や Envoy Proxy などもあるなか、今回は設定の簡易さ(あくまで主観)ときめ細やかな HTTP Health Check 設定ができる [Traefik](https://traefik.io/traefik/) を採択しました。

Traefik は L7 HTTP のリバースプロキシに加え、L4 TCP/UDP プロキシとしても機能しますが、今回は L7 HTTP のリバースプロキシを紹介します。

## 今回試した構成

今回は以下のシンプルな構成でリバースプロキシを検証しています。

```mermaid
graph TD;
    Z(Client)-- http:80 -->A
    A-- http:8001 -->B(httpd);
    A(Traefik)-- http:8002 -->C(nginx);
```

## セットアップ

### 設定ファイル

設定ファイルは2種類あります。
起動時に読みこむ静的な設定ファイルと、動的に経路情報を反映可能な設定ファイルです。
動的な設定ファイルは、静的な設定ファイルで事前に Watch 対象として指定しておく必要があります。

```yaml:静的設定ファイル traefik.yml
# こちらの設定は最新版のチェックや統計情報を送信するかどうかの設定です
global:
  checkNewVersion: false
  sendAnonymousUsage: false

# ポート 80 で web という名前のエントリーポイントを定義
entryPoints:
  web:
    address: ":80"

providers:
  # 動的に読み込む設定ファイル/ディレクトリを指定します
  # この他にも動的に設定を反映する方法として、Docker, Kubernetes, ECS, Consul, Etcd など外部システムと連携できます
  file:
    directory: /etc/traefik/dynamic/
    # watch を指定することで変更があった時に、動的に設定が反映されるようになります
    watch: true

```

```yaml:動的設定ファイル web.yml
http:
  routers:
    proxy:
      # 静的設定ファイルで定義した entorypoint web を指定
      entryPoints:
        - "web"
      # Proxy 先情報やヘルスチェック情報を記載した Service proxy を指定
      service: "proxy"
      # 他にも Match Path や Host 指定など色々設定可能
      rule: "Path(`/`)"

  services:
    # Service proxy を定義
    proxy:
      loadBalancer:
        # Proxy 先を指定
        servers:
          - url: "http://<IP or domain>:8001"
          - url: "http://<IP or domain>:8002"
        passHostHeader: true
        healthCheck:
           path: /
           scheme: http
```

今回 `<IP or domain>` には、ホストの IP アドレスを指定しています。
Health Check は、Interval の調整はもちろん、HTTPメソッド、カスタムヘッダーの指定など細かな設定を行うことが可能です。
https://doc.traefik.io/traefik/routing/services/#health-check

### Traefik とプロキシ先 Web サーバ Apache と NGINX の起動

Traefik は バイナリ, Docker, Helm Chart など様々な起動方法があります。
https://doc.traefik.io/traefik/getting-started/install-traefik/

今回は Docker で起動します。

```bash
$ mkdir dynamic
$ ls -1
dynamic
traefik.yml
web.yml

$ docker run --name traefik --rm -p 80:80 \
    -v $PWD/traefik.yml:/etc/traefik/ \
    -v $PWD/dynamic:/etc/traefik/dynamic \
    traefik:v3.0
```
この例では ポート 80 で起動し、静的設定ファイルと、この時点では空の `dynamic`ディレクトリをマウントしています。

>:pencil: 記事執筆時点で Traefik v3 はまだベータです。今回の記事内容は v2 の latest でも問題なく動作します。

今回は、ローカルに Apache, NGINX を立てます。

```bash
$ docker run -d --name httpd --rm -p 8001:80 httpd
$ docker run -d --name nginx --rm -p 8002:80 nginx
```

まだ、プロキシ先の設定ファイル `web.yml` を dynamic ディレクトリに移動していないので、traefik にアクセスするとプロキシ先がないので、404 になります。

```bash
$ curl http:127.0.0.1
404 page not found
```

### 動的ルーティング情報の反映

`web.yml` を Watch 対象のディレクトリ dynamic に移動します。

```bash
$ mv web.yml dynamic
```

これで経路情報が反映されます。
何度かリクエストをすると、nginx と apache のレスポンスに切り替わっているのがわかります。

```bash
$ curl http://127.0.0.1
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

$ curl http://127.0.0.1
<html><body><h1>It works!</h1></body></html>
```

動的に設定ファイルを読み込むので、ファイルを追加したり、更新するだけです。
プロセス再起動も、SIGHUP を送る必要もなく、楽ちんです。

## 後記

Traefik で Web 検索をすると、docker-compose でリバースプロキシをしている事例がよく見つかりますが、単にサーバにプロキシする記事があまりなかったので書きました。
社内では、OpenStack で作成した VM へのリバースプロキシ用途に Traefik を使っています。
Traefik には OpenStack に対応した Service Discovery の機能はないため、定期的に OpenStack API にアクセスし、動的設定ファイルを作成するスクリプトを作り、VM の経路情報の更新を行っています。
今回紹介した機能以外にも、Traefik にはダッシュボードや API、TCP/UDP Proxy の機能も充実しているので、色々触ってみると面白いかもしれません。