Traefikを使ってLet’s Encryptの証明書を得る

2022年9月30日

Traefikを使ってLet’s Encryptの証明書を得て、その証明書でウェブサイトを保護する。これをやってみるのだが、あちこちのウェブサイトにあるサンプルを試してみたものの、それらはバージョンが古いのか何なのか、どうにもうまく動かない。結局本家のサンプルが最もうまく行った。

サンプルは以下の二つで、現時点では2.8用である(今後更新されるかもしれない)。

これを見ていくが、それ以前に前提条件がある。

  • docker, docker-composeがインストールされていること
  • 自身のドメインを持っており、今回のテスト用にDNSがセットアップされていること
  • 自身のメールアドレスがあること。

以下では、オリジナルのサンプルとは異なり、ドメインを「fortest.example.com」、メアドを「me@example.com」としてある。

基本的なサンプル

適当なフォルダを作成して、その中にdocker-compose.ymlとして以下を記述する。ただし、fortest.example.comの部分は自分の保持するドメイン(あるいはサブドメイン)にする。なお、fortest.example.comを囲んでいるのはシングルクォート(‘)ではなく、バッククォート(`)であることに注意。

version: "3.3"

services:

  traefik:
    image: "traefik:v2.8"
    container_name: "traefik"
    command:
      #- "--log.level=DEBUG"
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"
      - "8080:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

  whoami:
    image: "traefik/whoami"
    container_name: "simple-service"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`fortest.example.com`)"
      - "traefik.http.routers.whoami.entrypoints=web"

上を記述したら、そのフォルダの中で「docker-compose up -d」を行い、http://fortest.example.comにアクセスする。

以下のような表示がされるはずだ。

Hostname: 3adb4ff***
IP: 127.0.0.1
IP: 172.27.0.2
RemoteAddr: 172.27.0.3:50894
GET / HTTP/1.1
Host: fortest.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: ja,en-US;q=0.7,en;q=0.3
Dnt: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Te: trailers
Upgrade-Insecure-Requests: 1
X-Forwarded-For: ****
X-Forwarded-Host: fortest.example.com
X-Forwarded-Port: 443
X-Forwarded-Proto: https
X-Forwarded-Server: 0b98bcc****
X-Real-Ip: ****

Let’ Encryptの証明書を取得する

前のサンプルは「docker-compose down」としてコンテナを停止しておく。

docker-compose.ymlを以下に書き換える。fortest.example.comは自身のドメインに書き換える。me@example.comは自身のメアドに書き換える。

version: "3.3"

services:

  traefik:
    image: "traefik:v2.8"
    container_name: "traefik"
    command:
      #- "--log.level=DEBUG"
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.myresolver.acme.httpchallenge=true"
      - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
      #- "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
      - "--certificatesresolvers.myresolver.acme.email=me@example.com"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
    volumes:
      - "./letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

  whoami:
    image: "traefik/whoami"
    container_name: "simple-service"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`fortest.example.com`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      - "traefik.http.routers.whoami.tls.certresolver=myresolver"

これで「docker-compose up -d」を行い、(この例だと)https://fortest.example.comにアクセスする。すると、Let’s Encryptの証明書で保護されていることがわかる。

Let’s Encryptの証明書は、docker-compose.ymlの下のフォルダletsencryptのacme.jsonに保持されている。このファイルはただのテキストファイルなので、単純に表示すれば、保持されている様子がわかる。

別コンテナに適用するには?

docker-composeに慣れている人なら言わずもがなだろうが、これを別コンテナに適用するにはどうするか?

別々のフォルダで、それぞれ次のようなdocker-compose.ymlを書き、それぞれ起動する。

ネットワークを合わせないといけないので、common-networkを使用している。あらかじめ、「docker network create common-network」として作成しなければならない。

version: "3.3"

services:

  traefik:
    image: "traefik:v2.8"
    container_name: "traefik"
    command:
      #- "--log.level=DEBUG"
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.myresolver.acme.httpchallenge=true"
      - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
      #- "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
      - "--certificatesresolvers.myresolver.acme.email=me@example.com"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
    volumes:
      - "./letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
networks:
  default:
    external:
      name: common-network

version: "3.3"

services:

  whoami:
    image: "traefik/whoami"
    container_name: "simple-service"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`fortest.example.com`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      - "traefik.http.routers.whoami.tls.certresolver=myresolver"
networks:
  default:
    external:
      name: common-network

つまり、任意のサービスについて、labels以下の文言を書けば、簡単にLet’s Encryptでの保護ができるということ。ただし、networkを合わせること。

ただし、fortest.example.comは変更しないといけない。また、”traefik.http.routers.whoamiのwhoami部分は任意であり、他コンテナと競合しなければ何でもよい。

80以外のポートの場合

whoamiは80以外のポートも指定できる。この場合をみてみる。

以下では、whoamiは2001番で待ち受けているが、それを示すことにより、traefikは、80で受けたリクエストをwhoamiの2001番に受け渡す。

version: "3.3"

services:

  whoami:
    image: "traefik/whoami"
    container_name: "whoami"
    command:
       # It tells whoami to start listening on 2001 instead of 80
       - "--port=2001"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`fortest.example.com`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      - "traefik.http.routers.whoami.tls.certresolver=myresolver"
      - "traefik.http.services.whoami.loadbalancer.server.port=2001"

networks:
  default:
    external:
      name: common-network