Low Level Lovers

The stories of low layer programming and developments.

Traefikを試した

自宅DC(かっこいい)でぁゃしぃWebアプリケーションをdocker下で動かしている。同じhttpsポートで外部公開したいコンテナが多数ぶら下がっているので、これをHostヘッダー等で振り分けるリバースプロキシが必要だ。httpsなのでついでにLet's Encryptから証明書の取得や設定もしたい。

そんな欲張りな要求を叶える2つの方法とそれらのdocker-compose.ymlファイルを紹介する。

旧来の定番

少し前まで広く使われていたのが nginx-proxy + letsencrypt-nginx-proxy-companion を使う手法。

このリバースプロキシにぶら下がるコンテナ全体にBASIC認証(ID:"a", パスワード:"a")を掛けている。また、別途 shared という名称のネットワークを作成しておく必要がある。

  • リバースプロキシ部
version: "2.4"

services:
  nginx-proxy:
    image: jwilder/nginx-proxy:alpine
    ports:
      - "80:80"
      - "443:443"
    restart: unless-stopped
    environment:
      - DHPARAM_GENERATION=false
    labels:
      - "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy=true"
    volumes:
      - certs:/etc/nginx/certs:ro
      - vhost:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
      - dhparam:/etc/nginx/dhparam
      - /var/run/docker.sock:/tmp/docker.sock:ro
    command: >
      /bin/sh -c "
      {
         echo 'a:$apr1$wmiqdea7$6.7AHQ8V2YnWHA.g5/Bp8.';
      } > /etc/nginx/conf.d/htpasswd
      && {
         echo 'server_tokens off;';
         echo 'auth_basic Restricted;';
         echo 'auth_basic_user_file /etc/nginx/conf.d/htpasswd;';
      } > /etc/nginx/conf.d/auth_basic.conf
      && forego start -r"

  letsencrypt:
    image: jrcs/letsencrypt-nginx-proxy-companion
    restart: unless-stopped
    volumes:
      - certs:/etc/nginx/certs:rw
      - vhost:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
      - /var/run/docker.sock:/var/run/docker.sock:ro
    depends_on:
      - "nginx-proxy"

volumes:
  certs:
  vhost:
  html:
  dhparam:

networks:
  default:
    external:
      name: shared


環境変数にバーチャルホスト名などを設定する。サブディレクトリでの振り分けをしたい場合はリバースプロキシへ独自のnginx.confを仕込むか、さらにもう一つリバースプロキシを挟む。

version: '2.4'

services:
  www:
    image: httpd:2.4-alpine
    restart: unless-stopped
    environment:
      - VIRTUAL_HOST=***.local
      - LETSENCRYPT_HOST=***.local
      - LETSENCRYPT_EMAIL=***@***.***

networks:
  default:
    external:
      name: shared

今時の構成

エッジルーターのTraefikを使うと上記とほぼ同じことができる。

github.com

  • リバースプロキシ部

本記事執筆時点の最新版v2.2に必要な設定はv1系と大きく異なる。ググるとv1系の記事がけっこう出てくるので気をつけよう。

Let's Encrypt周りの設定が少しややこしい。しかし、証明書発行時に使える認証方式がletsencrypt-nginx-proxy-companionはhttp-01のみに対し、Traefikならhttp-01, dns-01, tls-alpn-01と幅広い。http-01は80番ポートの開放が必要だが、tls-alpn-01だとhttpsで利用する443番ポートのみの開放で認証できる。

version: "2.4"

services:
  traefik:
    image: traefik
    restart: unless-stopped
    ports:
      - "443:443"
      - "127.0.0.1:8080:8080"
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.websecure.forwardedHeaders.trustedIPs=127.0.0.0/8,172.16.0.0/12"
      - "--certificatesresolvers.myresolver.acme.tlschallenge=true"
      - "--certificatesresolvers.myresolver.acme.email=***@***.***"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
    volumes:
      - letsencrypt:/letsencrypt
      - /var/run/docker.sock:/var/run/docker.sock:ro

volumes:
  letsencrypt:

networks:
  default:
    external:
      name: shared


環境変数の代わりにラベルでルーティングルールなどを記述する。ルーティングルールにサブディレクトリも使えるが、ウェブアプリケーションの出力したHTMLに埋め込まれているディレクトリパスを書き換えてくれるわけではないので、ウェブアプリケーションの実装がサブディレクトリの運用を想定したものになっていないといけない。

上のリバースプロキシの設定で配下の全コンテナを対象とするBASIC認証の設定をどう書くか不明だったので、仕方なくここに書いた。(v1系では可能だったという話がググったらでてきたし、v2系でもグローバルな設定を可能にしてほしいという要望はあるみたい)

version: "2.4"

services:
  www:
    image: httpd:2.4-alpine
    restart: unless-stopped
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.www.rule=Host(`***.local`)"
      - "traefik.http.routers.www.entrypoints=websecure"
      - "traefik.http.routers.www.tls.certresolver=myresolver"
      - "traefik.http.middlewares.defaultauth.basicauth.users=a:$apr1$wmiqdea7$6.7AHQ8V2YnWHA.g5/Bp8."
      - "traefik.http.routers.www.middlewares=defaultauth"

networks:
  default:
    external:
      name: shared

どっちが優れている?

Traefikのほうがシンプルな印象。だが、IPアドレスベースのアクセス制限を課すとき、IPアドレスが制限の対象となるかを判断するウェブアプリケーションをわざわざ別のコンテナとして起動したうえでTraefik側からForwardAuthミドルウェアを使って連係しなければならない点が実運用上の問題になった。さらに、POSTメソッドの場合だけ制限したいなどの細かい設定をしたければさらに複雑な構成となってしまう。