リバースプロキシとして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入門者向け(広告)