🚥

Docker連携リバースプロキシで複数コンテナへのアクセスを簡単にする[Traefik]

2022/11/06に公開

Reverse-ProxyのTraefikを使っています。Dockerコンテナを使った開発環境では便利!
私のローカル環境での使い方をまとめておきます。

この記事ではDnsmasqについてMacでインストールする手順を書いているだけですが、別記事でWindows用の説明もあります。
また、それ以外にも自己証明書を発行してHTTPSアクセスしたり、ダミーメールボックスのMailHogについても触れていますのでよかったらみてください。

https://zenn.dev/arkbig/articles/devbase_b8b43191f863f8024a83f824c832f8ca0e5d209254

https://zenn.dev/arkbig/books/devbase-2022_b1b24e6e8db350a1f7f379af3833e90d79ad5

Traefikとは

Traefik is an open-source Edge Router that makes publishing your services a fun and easy experience.

エッジルーターらしいです。まだクラウドとかエッジとかの知識がないのでよく分かりません。
私としてはサブドメインのページを手軽に作れるリバースプロキシーとして使用しています。

便利な点:

  • docker composeでlabelsを設定するだけで動く
    • なんならコンテナ起動するだけでいい
    • リバースプロキシーの設定が不要なので手軽
  • ポート番号をいちいち考えなくてもいい
    • リバースプロキシーがなければ、アプリごとにポート番号を割り当てる必要があり大変
  • ダッシュボードで設定が確認できる
    • どのアプリが起動しているかブラウザで一覧できる

ダッシュボードのスクリーンショット
ダッシュボード

ルーターのスクリーンショット
routers

前提

使用マシンは Mac mini (M1, 2020)で、Docker環境を Colima で構築しています。
が、他の環境でも同じでしょう。

設定

ここでは開発環境を想定した説明となります。そのためできるだけ対応が少ない方法をとっています。
ということでTraefikeのDockercompose.yamlを用意して起動します。

compose.yaml

services:
  traefik:
    image: traefik
    hostname: reverse-proxy
    restart: unless-stopped
    volumes:
      # dockerと連携するために必要
      - /var/run/docker.sock:/var/run/docker.sock:ro
    command:
      # docker連携を有効にして、デフォルトのルールを設定
      - --providers.docker.defaultRule=Host(`{{ normalize .Name }}.localhost`)
      # entrypointsで対応したいポート番号を書く
      - --entrypoints.http.address=:80
      - --entrypoints.mongodb.address=:27017
      # apiを有効にするとダッシュボードが見れる(http://localhost:8080/)
      - --api.insecure=true
    # コンテナ間をhost経由でアクセスさせるためネットワークモードを指定
    network_mode: host

これを docker compose up -d で起動すると、http://localhost:8080/でTraefikのダッシュボードが確認できます。

defaultRule.localhostを追加しています。通常だと{{ normalize .Name }}だけなのでブラウザで簡単にアクセスできません。
entrypointsにポート番号を2つ指定しています。Traefikはhttpだけでなく、tcpにも対応しています。

--entrypoints.{任意の名前}.address=:{ポート番号}でポート番号を利用する入り口がつくられます。

分かりづらいですが、api.insecureを有効にするとダッシュボードも一緒に見れるようになります。
apiが何なのかは調べてないのでわからないですが本番では注意が必要だと思います。

このように設定を全て起動引数で指定すると、設定を変更したときにdocker compose up -dを再度行うだけで反映されます。
traefik.yamlを用意してマウントする方式だと一旦止めないと設定が反映されません(docker compose restartも可)。(ファイル保存だけで反映される方法があればぜひ教えて〜)

使用

コンテナ単体起動

docker run --rm -d --name caddyTest caddy

caddyが何かはよく知らないけど、どれかのサンプルで使われてて、「おめでとう!」と言ってくれるので出来た感が嬉しい。

それはそれとして、このコマンドで起動後にダッシュボードを見るとcaddyTest.localhostが追加されてるのが確認できます。

caddyTest

Chromeでhttp://caddyTest.localhostにアクセスすると、「おめでとう!」と言ってくれます。
大文字小文字の区別はありません。http://caddytest.localhostでもOK。

NOTICE 注意
Safariだと.localhostの名前解決できません。Safariで確認が必要なら、ローカルDNSを立ち上げる必要があるようです。

ローカルDNS Dnsmasq

Dnsmasqは次の手順で動きます。

  1. brew install dnsmasqでインストール

  2. /opt/homebrew/etc/dnsmasq.confに追記(/otp/homebrewはhomebrew --prefixで確認可能)

    # localhostを127.0.0.1に解決
    address=/.localhost/127.0.0.1
    
  3. sudo brew services start dnsmasqで起動

  4. sudo mkdir /etc/resolverでDNS指定用のディレクトリ作成

  5. sudo vi /etc/resolver/localhostでlocalhostファイルを作る(ファイル名がドメイン名)

    nameserver 127.0.0.1
    options timeout:1
    
  6. ping -c 1 hoge.localhostで動作確認

  7. Safariでhttp://caddyTest.localhostにアクセスすると、「おめでとう!」と言ってくれます。

compose起動

caddyさんには退場してもらいましょう。(docker stop caddyTest)

普段はオプションが面倒なのでcompose.yamlを利用しています。例えばMongoDBを例にしてみましょう。

name: my-proj

services:
  mongo:
    image: mongo
    hostname: mongo
    restart: unless-stopped
    ports:
      - 127.0.0.1::27017
    volumes:
      - mongodb-data:/data/db
      - mongodb-config:/data/configdb
    labels:
      # traefikの設定
      traefik.tcp.routers.mongo.entrypoints: mongodb
      traefik.tcp.routers.mongo.rule: HostSNI(`*`)
  mongo-express:
    image: mongo-express
    hostname: mongo-express
    restart: unless-stopped
    depends_on:
      - mongo
    ports:
      - 127.0.0.1::8081
    environment:
      ME_CONFIG_MONGODB_SERVER: host.docker.internal

volumes:
  mongodb-data:
  mongodb-config:

これでhttp://mongo-express-my-proj.localhostでMongo Expressにアクセスできます。
これは、traefikのルールの.Nameがservice名(mongo-express)-COMPOSE_PROJECT_NAME(my-proj)となるためです。

MongoDBの方はというと、labels:の指定があります。
というのもデフォルトではHTTPプロトコルとして登録されるためで、TCP通信の場合は指定が必要です。
traefik.tcp.{任意の名前}.entrypointsで使用するポート番号を指定し(traefikの起動コマンドで書いたもの)、
traefik.tcp.{同上}.ruleで条件を指定します。TCPのルールはほとんど書けません。(TLSならドメイン名で分岐ができるらしい)

このサンプルではmongo-expressからmongoへのアクセスを無理やりホスト経由(host.docker.internal)にしています。
ホスト側を127.0.0.1の自動で割り振りのポート番号を利用している127.0.0.1::8081)ので、本来はdocker compose psで確認できる大きい数字のポート番号でアクセスする必要があります。
間にTraefikが入ってくれて、いい感じにコンテナに転送してくれています。

ここでTraefikのダッシュボードを見てください。
TCPの方はいい感じに登録されています。
HTTPの方はEntrypointsにhttpとmongodbの両方が登録されてしまっています。
これはTraefikに登録しているentrypointsが全て割り当てられるためで、実はhttp://mongo-express-my-proj.localhost:27017でアクセスできる時もあります。(MongoDBが立ち上がってなくて、Mongo Expressが他のDBに繋がってる時)
Version 1時代はdefaultEntrypointsで指定できたみたいですが、今のVersion 2だと無理みたいです。
ここでは開発用なので気にしないですが、本番ではちゃんと設定したほうがいいでしょう。

本番運用に向けて

本番(と言ってもアクセスが限定される社内)ではもう少し設定を書いて運用してます。

  • Traefikの設定をtraefik.yamlに書いて、/etc/traefik/traefik.yamlにマウント
  • providers.docker.exposedByDefault=falseにして、余計なものが公開されないようにする
    • compose.yamlのlabelsにエントリーポイントやルールを明記する
  • ルールにパスを見たり、クエリを見たり、ミドルウェアを利用してパスを変換したり(/apiを消すとか)、リダイレクトしたり、SSL利用したり使用方法はいろいろあります

まとめ

ローカルで複数のコンテナを起動する場合はTraefikがあるとかなり便利ですのでおすすめです。
とりあえず使う分には手軽ですので試してみると幸せになれるかもしれません。

Traefikを含めたDocker開発環境基盤を組み立てたい人は、下記の記事、本も参考にしてください。自己証明書を発行してHTTPSアクセスしたり、ダミーメールボックスのMailHogについても触れています。

https://zenn.dev/arkbig/articles/devbase_b8b43191f863f8024a83f824c832f8ca0e5d209254

https://zenn.dev/arkbig/books/devbase-2022_b1b24e6e8db350a1f7f379af3833e90d79ad5

Discussion