【2020年1月版】Traefik 2.0 で Docker-Compose の"80"ポートを交通整理

# ホストのポート番号、足りていますか？！

docker-compose でバンバンサービスを立ち上げて監視しつつ、開発しつつ、実験などやっていると、`3000`とか`8080`とかぶつかって`8081`とか`8180`とか、ポート番号をずらすのが大変になってきますよね？
ちょっと違いますが僕は先日、`k3s`上の何かのサービスのhttps用ポートで`8443`がぶつかってしまったので`6443`に変更し、 **k3sのクラスタを丸ごと喪失**させました。k3sが`6443`使ってるんだよね！

Dockerで多数のサービスがサクサク動かせるのは良いことなのですがホストのポート番号が**マヂでヤヴァイ**のであります。

そんなあなたに！今回は Traefik と `nip.io` のワイルドカードDNS を使って**ポート番号の衝突から解放される**手順をご紹介します!

## 対象環境

OS: Ubuntu 18.04.3 LTS (Bionic Beaver)
docker: 19.03.5
docker-compose: v1.24.0
ip アドレス: 192.168.1.50

今回は ipアドレス `192.168.1.50` の Ubuntu に docker-compose でモリモリサービスを立ち上げてみます。

## 何をするのか

以下の手順で行う内容ですが、先にタネを明かしてしまいますと、`nip.io` のワイルドカードDNSを使って `xxxxx.192.168.1.50.nip.io` や`yyyyy.192.168.1.50.nip.io`などをすべて `192.168.1.50` に解決させてしまいます。
これでホスト名が変わっても`192.168.1.50`にすべてリクエストが行きますが、Traefikで **"サブドメイン"でルーティングさせ** て対応する Docker コンテナにリクエストを転送させるようにします。

また、**Traefikの設定をコンテナ側で行う**ことで、**コンテナをデプロイするだけで Traefik にサービスが自動的に登録される**ようになります。

これによって Traefik コンテナだけで `80` 番を待ち受けていれば、**好きなコンテナを起動するだけでホスト名でのルーティングができる**ようになっている、ということが実現できます。

この手順を以下で紹介いたします！

## 1. Traefik の準備

まずは以下のような Traefik 起動用の `docker-compose.yml` を、`traefik`フォルダに作成いたします。

```yml:docker-compose.yml
version: '3'
networks:
  web:
    driver: bridge

services:
  reverse-proxy:
    image: traefik:v2.1 # The official Traefik docker image
    ports:
      - "80:80"     # The HTTP port
      - "8080:8080" # The Web UI (enabled by --api)
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock # So that Traefik can listen to the Docker events
      - ./traefik.yml:/etc/traefik/traefik.yml
    networks:
      - web
```

まずは "network" に `web` というネットワークを定義しております。
後ほど解説いたしますがここでは単に、traefik 経由で外に出たいコンテナは`web`ネットワークに入ってね、ということだけ覚えておけばOKです。
また他の`docker-compose.yml`からこの`web`ネットワークを参照しますが、docker-composeの作成するネットワークは**"フォルダ名_ネットワーク名"**になるます。
今回は `traefik`フォルダでdocker-compsoeを起動しますので、他から参照する時は`traefik_web`というネットワーク名となります。

続いて Traefik のポートの設定ですが、`80:80` と `8080:8080` の設定で80番と8080番はホストとバインドさせて、Traefik に入るようにいたします。このマシンで使用する80番と8080番はTraefik専用、といたします。

続いて `volumes` で指定している `./traefik.yml:/etc/traefik/traefik.yml` ですがこれに対応するように以下の設定ファイルを作成いたします。

```yml:traefik.yml
## traefik.yml

# https://docs.traefik.io/

# Docker configuration backend
providers:
  docker:
    exposedByDefault: false
    defaultRule: "Host(`{{ index .Labels \"traefik.host\" }}.192.168.1.50.nip.io`)"

entryPoints:
  http:
    address: ":80"
  # smtp:
  #   address: ":25"
  # pop3:
  #   address: ":110"
  # imap:
  #   address: ":143"
  # mysql:
  #   address: ":3306"
  # psql:
  #   address: ":5432"
  # mongo:
  #   address: ":27017"

# API and dashboard configuration
api:
  insecure: true
```
`defaultRule` で "Host(`{{ index .Labels \"traefik.host\" }}.192.168.1.50.nip.io`)" を指定しております。
この "Host(`{{ index .Labels \"traefik.host\" }}.192.168.1.50.nip.io`)" がキモの一つです。

以下の設定サンプルにある通り、コンテナにつけた名前と"ラベル"がルールに使用できます。

- [defaultRule | Traefik & Docker](https://docs.traefik.io/providers/docker/#defaultrule)

今回はコンテナ名とは別に**`traefik.host` ラベルで指定した文字列がサブドメインになる**ように指定いたしました。

続いて`entryPoints`の設定ですが http を対象としますので、`:80`のエントリーポイントを定義します。
また、例えばmongodbであれば `27017` を指定しておきます。ここで開けたポートは `docker-compose.yml`の`ports`でも忘れなく指定しておきましょう。
このように使いたいポートを"エントリーポイント"としてあらかじめ定義しておきます。

## 2. Traefik の起動

準備が整ったところで以下のコマンドで Traefikを起動します。

```shell
$ docker-compose up -d
...
```
しばらくすると `http://192.168.1.50.nip.io:8080` にてTraefikのダッシュボードが立ち上がると思います。
立ち上がらなければ設定にミスなどある可能性大なので `docker-compose logs reverse-proxy` などでログを確認しましょう。

これでルーティングの準備は完了です。

## 3. サービスの起動

以下でいくつかのサンプルを上げてみましょう。

### 3-1. 例えば KeyCloak

それではサンプルとして最近お世話になっている `KeyCloak` を立ち上げてみましょうか。
いつもの docker-compose.yml に Traefik 用の設定を追加します。

```yml:docker-compose.yml
version : "3"
volumes:
  kc_postgres_data:
      driver: local

networks:
  web:
    external:
      name: traefik_web

services:
  kc-postgres:
    image: postgres
    volumes:
      - kc_postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: password
  keycloak:
    image: jboss/keycloak:7.0.1
    container_name: keycloak
    restart: always
    expose:
      - 8180
    environment:
      DB_VENDOR: POSTGRES
      DB_ADDR: kc-postgres
      DB_DATABASE: keycloak
      DB_USER: keycloak
      DB_SCHEMA: public
      DB_PASSWORD: password
      KEYCLOAK_USER: admin
      KEYCLOAK_PASSWORD: admin
    networks:
      - web
    command: |
      -Djboss.socket.binding.port-offset=100
    depends_on:
      - kc-postgres
    tty: true
    labels:
      traefik.enable: true
      traefik.host: keycloak
      traefik.http.routers.keycloak.entrypoints: http
      traefik.http.services.keycloak.loadbalancer.server.port: 8180
      traefik.docker.network: traefik_web
```
早速ですが、

```
networks:
  web:
    external:
      name: traefik_web
```
で`traefik`の`web`ネットワークを`web`という名前で参照いたします。この外部ネットワーク参照については[過去の記事も読んでいただけると幸いです](https://qiita.com/koinori/items/c318256e660cd68f3bf5)。

`kc-postgres` は"keycloak用のPostgreSQL"なのでここはスルーいたします。

さて本命の`keycloak`です。
肝心なのは `networks` で `web` に参加していることと、`labels` の設定です。
以下に列挙します。

- `traefik.enable`: このスイッチにより traefik でのルーティング対象とします。(traefik は基本的に全コンテナの起動と終了を監視しています。)
- `traefik.host`: このラベルがサブドメイン名となります。ここでは `keycloak.19.168.1.50.nip.io` だったらこのコンテナが呼ばれる、ということになります。
- `traefik.http.routers.keycloak.entrypoints`: 冒頭の `traefik.yml`で指定したエントリポイントを選択します。ここでは `80`のhttpを使用します。
- `traefik.http.services.keycloak.loadbalancer.server.port`： 上記のhttpにマッピングされるこのコンテナのポートを指定します。keycloakが待ち受ける `8180`を指定します。
- `traefik.docker.network`: treafik 側からこのコンテナを見るときのネットワークを指定します。これがないとtraefikは `default`ネットワーク経由でこのコンテナのIPを参照してしまうようです。

`traefik.docker.network`の指定が若干、残念ですがネットワークはいくらでも定義できるのでtraefik側から判断するのは困難なのでしょう。
実は、`traefik_web` のネットワークに参加しなければならない大きな理由は、この**treafik側から見えるかどうか**という問題です。先に立ち上がっているtraefikから、後で立ち上げたコンテナの名前を解決させるために同じネットワークに入れておく必要がある、ということなわけです。~~(結局、これも過去の記事と同じテーマなのです。。。)~~

これでブラウザから `http://keycloak.192.168.1.50.nip.io`を開くと無事に Keycloakにアクセスできると思います！

### 3-2. 例えば Adminer

RDBに簡単につなぎに行けるWebクライアントとしては Adminerがもっともお手軽で強力だと思います。
docker-compose.ymlの追加部分はkeycloakと変わりませんのでサクッと立ち上げてしまいましょう。

```yml:docker-compose.yml
version: '3'

networks:
  web:
    external:
      name: traefik_web

services:
  adminer:
    image: adminer
    container_name: adminer
    restart: always
    ports:
      - 8080
    networks:
      - web
      - db-admin
    labels:
      traefik.enable: true
      traefik.host: adminer
      traefik.http.routers.adminer.entrypoints: http
      traefik.http.services.adminer.loadbalancer.server.port: 8080
      traefik.docker.network: traefik_web
```

設定内容は上記と同じです。ただし、`routers`と`services`の名前は変えておく必要があります。
前回は `traefik.http.routers.keycloak.entrypoints` でしたが、今回は `traefik.http.routers.adminer.entrypoints` です。

この設定で `http://adminer.192.168.1.50.nip.io` で Adminer にアクセスできるようになります！
Keycloakよりシンプルだから先にこっちを挙げておけばよかったか。。。

あとはお好きなサービスでどうぞ！

## トラブルシューティング

traefik のダッシュボードではエラーメッセージは一切出ないのが難点です。
コンテナ起動してもダッシュボードに反応がなければ `docker-compose logs -f reverse-proxy` で treafik のログを参照するしかないです。

traefik のエラーログでは**goのエラーメッセージ**が出てしまうのでちょっとわかりにくいのがさらに厳しいです。
ソースコード見ても落ちている箇所はわかるけどどう設定すればいいの？みたいな場合が多々あります。
ここはダッシュボードで優しく教えてくれないかな・・・

## まとめ

本家サイトのガイドではDockerを使った場合も設定ファイルにガリガリ記述させるので、コンテナ立ち上げるたびに設定直してたらキリがないな…とあまり興味がなかったのですが、以下のしれっとした記述でラベルで設定できるのを見つけたので、これ幸いと traefik を導入致しました。

- [Routing Configuration with Labels | Traefik & Docker](https://docs.traefik.io/providers/docker/#routing-configuration-with-labels)

いや、これあっさりしすぎでしょう。この機能、超重要ですよ？

とにかく、このようにワイルドカードDNSとTraefikを組み合わせることで、社内からアクセスできるサービスをdocker-composeで**ポート番号を気にせずに**バンバン立ち上げることができました。

また、ワイルドカードDNSは`localhost`ではないちゃんとした**ホスト名**なので KeycloakでのOAuth設定もばっちりです。**社内サービスのSSO化**もバリバリ捗ります。

いや～素晴らしい！

本日は以上といたします。