Tech 4 Mine :-) 忘れぬ先のテックメモ

ゆるエンジニアな私が、多少なり役に立ったひらめきやらTipsを忘れる前に書いていくブログです。

traefikでリバースプロキシ ON Docker With TLS

あうとらいん

コンテナはなーんにも考えずにサクッと作ってデプロイしたい・・・ってことでリバプロにまるっと任せたい!
ということでDocker上で簡単に構築できて、コンテナの追加にも影響がないTraefikを使ってみました。

環境

traefik v2.5.2
Docker(ce) 20.10.3
docker-compose 1.28.2
DNSプロバイダ ConoHa
サーバ環境ConoHa VPS(Ubuntu18.4)
証明書発行 Let's Encrypt

Docker-compose での基本設定

Composeの共通的な部分はここでは触れません。
特殊な理由がなければ好みの問題かと思いますが、traefikの設定は複数の書き方があります。
公式ドキュメントにある通りDockerだけでもyaml、toml、cliで記述出来ます。
今回はcomposeファイルに記載したいので、cliの書き方をベースにTraefikコンテナの起動コマンド設定を書いていきます。

Traefik特有な部分を以下に見ていきます、ということでyamlファイルどん。

version: "3.3"

services:
  traefik:
    image: "traefik:v2.5.2"
    container_name: "traefik"
    restart: always
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.websecure.http.tls=true"
      - "--entrypoints.websecure.http.tls.certResolver=myresolver"
      - "--entrypoints.websecure.http.tls.domains[0].main=yourdomain.com"
      - "--entrypoints.websecure.http.tls.domains[0].sans=*.yourdomain.com"
      - "--certificatesresolvers.myresolver.acme.dnschallenge=true"
      - "--certificatesresolvers.myresolver.acme.dnschallenge.provider=conoha"
      # - "--certificatesresolvers.myresolver.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53"
      # - "--certificatesresolvers.myresolver.acme.dnschallenge.delaybeforecheck=5"
      # - "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
      - "--certificatesresolvers.myresolver.acme.email=yoursubmitmail@email.com"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    environment:
      - "CONOHA_REGION=tyoX"
      - "CONOHA_TENANT_ID=xxxxxxxxxxxxxxxxxxx"
      - "CONOHA_API_USERNAME=yourusername"
      - "CONOHA_API_PASSWORD=yourpassword"
      - "CONOHA_POLLING_INTERVAL=30"
      - "CONOHA_PROPAGATION_TIMEOUT=3600"
      - "CONOHA_TTL=3600"
    volumes:
      - "./letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./logs/:/var/log/traefik/"
    networks:
      - proxy
networks:
  proxy:
    driver: bridge
    ipam:
      driver: default

解説

command部分の各設定をみていきます。今回はあくまでTLS化することを主体としているため、「とりあえず動かす」ためには不要な箇所も多いです。

      ## ロバイダ指定、trueにすることでDockerコンテナを能動的に取得、プロキシ下に置く
      - "--providers.docker=true"
      ## デフォルトでコンテナを管理化に収めるかのフラグ、falseにした場合、対象コンテナに traefik.enable=true の設定が必要
      - "--providers.docker.exposedbydefault=false"
      ## Webアクセスをどこで許可するかの指定、webはアクセスポイント(AP)名で任意に設定する。80番ポートで受けるリクエストはWebというAPとして扱う
      - "--entrypoints.web.address=:80"
      ## リダイレクト先のAP名を指定する。websecureというapにリダイレクトする設定
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      ## リダイレクト先のAPがtls化されている場合はスキーマがhttpからhttpsへ変わるためhttpsを指定
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      ## こちらはtls用のap設定、443でtlsを受けるよう定義
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.websecure.http.tls=true"
      ## TLS化のために後述の証明書リザーバーを指定する
      - "--entrypoints.websecure.http.tls.certResolver=myresolver"
      ## 取得したいドメイン名を指定、ワイルドカード証明書はDNSチャレンジで証明書を取得する場合のみ有効
      - "--entrypoints.websecure.http.tls.domains[0].main=yourdomain.com"
      ## 子の証明書にワイルドカード証明書を指定
      - "--entrypoints.websecure.http.tls.domains[0].sans=*.yourdomain.com"
      ## DNSチャレンジで証明書を取得するためtrueに設定
      - "--certificatesresolvers.myresolver.acme.dnschallenge=true"
      ## プロバイダ指定、プロバイダ名はTraefik公式ドキュメントを参照のこと
      - "--certificatesresolvers.myresolver.acme.dnschallenge.provider=conoha"
      ### コメントアウト部分はLet's Encryptのテスト用サーバ設定とdnsチャレンジがうまく行かなかった場合のディレイ設定
      # - "--certificatesresolvers.myresolver.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53"
      # - "--certificatesresolvers.myresolver.acme.dnschallenge.delaybeforecheck=5"
      # - "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
      ## 証明書の発行等の通知を受けるメールアドレス
      - "--certificatesresolvers.myresolver.acme.email=yourmail@email.com"
      ## acmeファイルの格納先、後続の関係で固定推奨
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"

この辺の詳細や追加情報はTraefik let's Encrypt等で検索すると公式ドキュメントがあります。
今回はエントリーポイントにwebとwebsecureを作り、80番ポートを443番ポートにリダイレクト、http → https にリダイレクトする形の設定をしています。

Port部分は特に触れませんが、80番と443番をマッピングしています。
次はenvironment部分です。
commandのdnsチャレンジプロバイダによって全く異なりますが、今回どっぷりハマってしまったこともあり注意して追いかけてみます。

      ## REGIONはConoHaのAPIで表示されているものを入れましょう、tyo1、tyo2(東京リージョンの場合)などです
      - "CONOHA_REGION=tyoX"
      ## この辺りは素直にConoHaのアカウント情報を入力
      - "CONOHA_TENANT_ID=xxxxxxxxxxxxxxxxxxx"
      - "CONOHA_API_USERNAME=yourusername"
      - "CONOHA_API_PASSWORD=yourpassword"
      ## ココでハマりました、DNSの浸透速度が遅いせいか、TIMEOUT値やTTLの値がデフォルトのままだとチャレンジ失敗します
      - "CONOHA_POLLING_INTERVAL=30"
      - "CONOHA_PROPAGATION_TIMEOUT=3600"
      - "CONOHA_TTL=3600"

というわけで上記の通り、ConoHaのDNSは浸透が遅いのか、タイミング問題なのかは不明ですが、TIMEOUTとTTLの値を伸ばし、それに伴いポーリングの感覚も30秒に広げています。
これにより、デフォルト値ではacmeレコードをDNSに登録したところで、浸透するより早くTIMEOUT扱いになることがなくなりました。
その際のログが下記のように出ます。

traefik    | time="2021-09-02T14:30:23Z" level=error msg="Unable to obtain ACME certificate for domains \"yourdomain.com....

続いてはVolume部です、ここも定型的に入れてしまっていいでしょう。

      ## acmeファイル(証明書)の格納場所をマウントする
      - "./letsencrypt:/letsencrypt"
      ## Dockerをプロバイダとして登録するため、ソケットをマウント
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      ## traefikのログをマウント
      - "./logs/:/var/log/traefik/"

最後にネットワークについてですが、これはtraefik用のDocker networkを作っているだけなので割愛します。
重要なのは、ここで作成したTraefik用ネットワークを、管理対象のコンテナにも反映することです(external利用)。

コンテナ側のdocker-compose

version: "3.3"

services:
  web:
    image: webapps image
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.web_router.rule=Host(`web.yourdomain.com`)"
      - "traefik.http.routers.web_router.entrypoints=websecure"
    expose:
      - "3000"

networks:
    default:
        external:
            name: traefik_proxy

実際に動かすimageや起動コマンドは各個の環境に合わせてください。(ココでは開設に必要な部分のみ記載しています)

特にtraefik経由でリバプロさせるために重要な部分はlabels部分ですね。
ここにコンテナ別の必要な設定を記載していきます。
traefik.enable=true ・・・traefikの管理対象であること明示するフラグ、必須
traefik.http.routers.web_router.rule ・・・HostやPathでルーティング先を選別するための設定、上記ではドメインの先頭にweb~とついていたらこのコンテナにルーティングするように定義している traefik.http.routers.web_router.entrypoints ・・・どのAPからのアクセスを対象とするかを指定、websecureとしているがここはtraefik側の設定で記載が必要(※)
※ コンテナごとに設定かけなくもないがひっじょーうに長くなりかねないのでtraefikに巻いてしまうことをおすすめする。

このWebアプリケーションがデフォルト3000番で待受ていた場合、exposeで3000番ポートを内部公開する。
Traefikでは内部公開されているポートを自動的に拾い、リダイレクトする際に対象のポートを使ってくれるため、ホスト側のポートをマウントしないexposeで事足りる。

最後にnetworksの定義だが、externalをつけてtraefikを起動した際に生成されたネットワークに参加させる必要がある。
ここではtraefikフォルダでtraefikのdocker-composeを起動したので、traefik_proxyが対象となる。

あとがき

最近筆が止まっていたので(色々とお仕事頂いていたのと、子どもをひたすら愛でていた)あとから追記編集するつもりでバリバリと書きなぐっていくことにしました。
肝心のTraefikですが、非常に使いやすいですね。日本語の資料が少ないですが、公式のドキュメントが手厚いのでなんとか使えています。
ロゴがGolangのキャラクターをパロってるので、なかなかお硬い現場では気を使いますね!
Traefikは何よりもコンテナを上げ下げしたときに動的にルーティングの追加・削除をしてくれるところが素晴らしいです。
軽量なリバプロということでPoundを使っていたこともありましたが、使いやすさや機能面含め、シンプルな環境ではリバプロのベストプラクティスになりうるのでは、と感じています。
以下その他の役に立った気がするTipsを載せておきます。(適宜追記予定)
それでは良いコンテナライフを・・・!

その他Tips

ダッシュボードをセキュアにする

command:
      - "--api.dashboard=true"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.myresolver.acme.dnschallenge=true"
      - "--certificatesresolvers.myresolver.acme.dnschallenge.provider=yourprovider"
      - "--certificatesresolvers.myresolver.acme.email=your.address@email.com"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
labels:
      - "traefik.enable=true"
      - "traefik.http.routers.dashboard.rule=Host(`your.domain.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.middlewares.dashboard-auth.digestauth.realm=realmword"
      - "traefik.http.middlewares.dashboard-auth.digestauth.users=account:realmword:abcdefgh1234567890xxxxxxxxxxxxxx"
      - "traefik.http.routers.dashboard.entrypoints=websecure"
      - "traefik.http.routers.dashboard.middlewares=dashboard-auth"
      - "traefik.http.routers.dashboard.tls.certresolver=myresolver"
      - "traefik.http.routers.dashboard.tls.domains[0].main=your.domain.com"

URLから指定のディレクトリを削除する

以下をリバプロ内のアプリケーションのcomposeファイルに記述する

labels:
      ## yourdomain.com/sample/app1 -> yourdomain.com/app1 となる
      - "traefik.http.middlewares.samplemiddle.stripprefix.prefixes=/sample"
      - "traefik.http.routers.samplerouter.middlewares=samplemiddle@docker"

Headerにパスを追加する

labels:
      - "traefik.http.middlewares.testHeader.headers.customrequestheaders.X-Script-Name=test"
      - "traefik.http.middlewares.testHeader.headers.customresponseheaders.X-Custom-Response-Header=value"