リバースプロキシとしてtraefikを使うためのメモ。(Docker使用)
目的
今まで使っていたjwilder/nginx-proxyは有用だが、ログ出力にバグがあるようでログが若干扱いにくかった。
具体的にはこのようなログになってしまっていた:
Aug 15 05:12:31 <hostname> <tag>: #033[0m#033[0;36;1mdockergen.1 | #033[0;31;1m2020/08/15 05:12:31 Running 'nginx -s reload'
……これは残念。修正済みのforkedなイメージもあるようだが(参考に記載)、traefikが気になっていたので、これを機に導入してみることにした。
traefikはLet's Encryptも扱えて、ワイルドカード証明書も発行できるので…と思ったらこの設定を忘れていることに気づいた。この設定はまた今度にしよう。とりあえず、ワイルドカードでなくてもDNS-01 challengeで使えている。
環境
dockerのtraefikイメージを使った。
- traefik v2.3
traefik側で設定すること
traefik.ymlを作成して、コンテナ内の/etc/traefik/に配置する。
- accesslogの記録
- httpをhttpsへリダイレクト
Let's Encryptの静的設定(certresolverを定義)- ログ出力はすべてjson形式に
- サービスの自動発見を無効化
基本的にはこれらの設定を行う。
webapp側で設定すること
webappはtraefikで言う所のサービス。traefikはdockerのポートがEXPOSEされているコンテナを自動発見し、サービスとみなすが、DBなどはtraefikで制御される必要がないので前項で述べたように無効にする。
webappではdocker-compose.ymlなどのファイルにlabels:に設定を行う。
- ホスト名(FQDN)の設定
- traefikに発見されるようにする
- tlsを有効化
- httpsのみルーティング
- certresolverを設定
今回はサブドメインで区別するが、パスでルーティングを行うこともできる。||や&&を使うことで色々できる。
また、サブドメインなしでのURL指定をサブドメインを付加してリダイレクトするようにする。これは動的設定として行うので、対象のwebappについては追加の設定が必要になる。
traefikの実装
一通りの説明が終わったので具体的な構成・中身に入る。まずはtraefik側の設定から。
フォルダ構造
.
├── docker-compose.yml
└── my-traefik
├── Dockerfile
└── traefik.yml
my-traefikディレクトリを作り、その中にDockerfileとtraefik.ymlを置く。
ルートディレクトリの直下にはdocker-compose.ymlを置く。
それぞれの中身については以降に示す。
my-traefik/Dockerfile
traefikはイメージサイズが小さい。やることはtraefik.ymlを移すことだけ。
FROM traefik:v2.3
COPY ./traefik.yml /etc/traefik/
my-traefik/traefik.yml
accesslog:
format: json
providers:
docker:
exposedByDefault: false
entrypoints:
web:
address: ":80"
http:
# httpをhttpsにリダイレクトする。
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
certificatesresolvers:
myresolver:
acme:
dnschallenge:
provider: "conoha"
# ステージング用設定。本番では使わない。
caserver: "https://acme-staging-v02.api.letsencrypt.org/directory"
email: "<email@address.here>"
storage: "/letsencrypt/acme.json"
# DON'T use webui in production environment.
# api:
# insecure: true
log:
level: INFO
format: json
DNS-01 challengeを利用する。providerはconohaを使用なのでそれに。環境変数も必要になる。providerについては参考にて。
caserver: "https://acme-staging-v02.api.letsencrypt.org/directory"はテスト用のCAサーバ。証明書発行制限が別枠だったと思う。
docker-compose.yml
version: "3.8"
services:
reverse-proxy:
build: ./my-traefik
ports:
- "80:80"
- "443:443"
environment:
- "CONOHA_TENANT_ID=<here>"
- "CONOHA_API_USERNAME=<here>"
- "CONOHA_API_PASSWORD=<here>"
volumes:
- "certs:/letsencrypt"
# So that Traefik can listen to the Docker events
- /var/run/docker.sock:/var/run/docker.sock
networks:
- traefik_network
networks:
traefik_network:
external: true
volumes:
certs:
特筆すべきなのはprovider用の環境変数と証明書格納用のボリュームを作成するぐらい。conohaの場合は、conohaの管理ページのAPIのところから値を確認できる。
あとは、networksのtraefik_networkをexternalにしていることか。外部のcomposeなどのコンテナを発見・ルーティングをできるようにするため。
webappの実装
次はwebapp側の設定。
docker-compose.ymlの設定
主にlabelsに追記するだけでいい。サブドメインなしをリダイレクトするかどうかで少し違う設定になる。nginxの使用を想定している。
version: "3"
services:
nginx:
build: ./nginx
environment:
# templateでnginxのconfファイルに埋め込む
NGINX_HOST: aaa.example.com
labels:
# - "traefik.http.routers.aaa-service.rule=Host(`aaa.example.com`)"
# サブドメインなしをリダイレクトするなら下のruleを使う。
- "traefik.http.routers.aaa-service.rule=Host(`aaa.example.com`)||Host(`example.com`)"
- "traefik.enable=true"
- "traefik.docker.network=traefik_network"
- "traefik.http.routers.aaa-service.tls=true"
- "traefik.http.routers.aaa-service.entrypoints=websecure"
- "traefik.http.routers.aaa-service.tls.certresolver=myresolver"
# サブドメインなしをリダイレクトするなら下の3行が必要。
- "traefik.http.middlewares.subdomain-redirect.redirectRegex.regex=^https://example.com/(.*)"
- "traefik.http.middlewares.subdomain-redirect.redirectRegex.replacement=https://aaa.example.com/$${1}"
- "traefik.http.routers.aaa-service.middlewares=subdomain-redirect@docker"
networks:
traefik_network:
external: true
traefik.docker.networkに指定したnetworkに属するコンテナをサービスとしてくれる。なのでここでは先のtraefik_networkを指定しておく。
routerのruleで||を指定すればorマッチできる。これを利用して、サブドメインなしのトラフィックを導き、traefikのmiddlewareの仕組みを使ってリダイレクトを行なっている。
一時的リダイレクトとして扱うつもりなので、traefikの動的設定としてサービス側で定義することにした。恒久的なリダイレクトにするなら、traefikの静的設定としてtraefik側でymlファイルに記述した方がいいだろう。
nginxの設定
nginx-proxyと違って、nginxのconfファイルが必要になる。Djangoなどはnginxもcomposeでupなどしないと機能が果たせない。
新しめなnginxイメージならテンプレートを利用して環境変数をconfファイルに埋め込むことができる。そのDockerfileは次のようにするだけ:
FROM nginx:alpine
COPY nginx.conf.template /etc/nginx/templates/
gunicornなら次のようなnginxのconfファイルでOK。
upstream app_server {
; webappコンテナがEXPOSEしているポートを指定
server webapp:8000;
}
server {
listen 80;
; ${variable}で埋め込まれる
server_name ${NGINX_HOSTNAME};
charset utf-8;
root /var/www;
location / {
try_files $uri @proxy_to_app;
}
location @proxy_to_app {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
# we don't want nginx trying to do something clever with
# redirects, we set the Host: header above already.
proxy_redirect off;
proxy_pass http://app_server;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
server_tokens off;
tlsはtraefikで終端されるので、80ポートだけ見ればいい。
nginxイメージが古い場合はテンプレートに埋め込む機能がないので、イメージをpullしておくのを忘れないようにする必要がある。
また。nginx起動時にwebappを見つけられないと落ちてしまうので、restart: alwaysなどをつける必要がある。(webappの起動よりnginxの起動が早いのが原因だと思う。他のオプションでもいいはず。)
確認
traefikとwebappをそれぞれupして確認する。ブラウザでも確認できるし、curlでもできる。curlだと手軽なのでそちらでの確認をしてみる。
$ curl -I -L -k example.com
HTTP/1.1 308 Permanent Redirect
Location: https://example.com/
Date: Sun, 08 Nov 2020 17:05:00 GMT
Content-Length: 18
Content-Type: text/plain; charset=utf-8
HTTP/2 307
location: https://aaa.example.com/
content-type: text/plain; charset=utf-8
content-length: 18
date: Sun, 08 Nov 2020 17:05:00 GMT
HTTP/2 200
content-type: text/html; charset=UTF-8
date: Sun, 08 Nov 2020 17:05:00 GMT
...
オプションは、-I、-L、-kの3つでいいだろう。それぞれ順にHEADメソッドのリクエスト、リダイレクトにしたがって再リクエスト、証明書を信用する(caserverをステージングのものにしている場合必要)、の3つ。
リダイレクトとステータスコードが想定通りであることを確認できればOK。 これで完了。
nginx-proxyとの比較
traefikに置換したことで、役割の明確化ができたと思う。nginx-proxyでは静的ファイルの配信は兼用で行っていたため、静的ファイルの配信用にexternalなvolumeを繋いでいたが不要にすることができ、サービス内部のnginxに任せることができる。
ログがjson形式で出力できるのもメリット。
nginxの分だけサイズが大きくなるのは欠点。nginx-proxyも軽量だった。
nginxのconfファイルを作成する手間が増えたのもデメリットだが、構成が似ていれば流用が効くのであまり気にはならない。
参考
traefikのproviderのリスト: Let's Encrypt - Traefik80 => 443のリダイレクト: EntryPoints - Traefiktraefikのmiddleware: Overview - Traefikgunicorn向けnginxのconfファイル: Deploying Gunicorn — Gunicorn 20.0.4 documentationnginx-proxyのバグについて: Remove colour codes from syslog messages · Issue #1281 · nginx-proxy/nginx-proxy · GitHub- ↑のforkedなイメージ(READMEのイメージ名が修正されてないので注意): binfalse/nginx-proxy - Docker Hub
おわり
ログがずっと気になっていたので修正できてめでたしめでたし。それぞれのコンテナの管理範囲も明確になったし見渡しやすくなった。
実はまだexternalなvolumeのままなので、そちらは早めに修正しておきたい…
traefikの日本語資料が少ない気がする。公式ドキュメントを見れば大体わかるのもあるが、具体例が少なく感じた。
とりあえず実装は完了したのでよしとする。
以上です。
Docker入門者向け(広告)


