にゃみかんてっくろぐ

猫か百合を見守る壁になりたい

ぜんぶ Docker コンテナにする (HTTPS+IPv6 対応 )

事例が何かの参考になればと思ったので記事にしました。

3行でまとめると

すべて Docker 化

個人開発のサービスを省力運用にするべく、 Zenrei (zenrei.nyamikan.net) をすべて Docker コンテナ化しました。

f:id:no_clock:20200308221840p:plain
構成図

docker-compose.yml とそれぞれの Dockerfile はリポジトリに公開しています。

nginx (host network)

https-portal の節で紹介します。

nginx (bridge network)

フロントエンドの静的ファイル配信と、サーバサイドアプリケーションへのリバースプロキシを担います。マルチステージビルドを使用し、 Vue.js の生成物のみを nginx に持ってきてイメージサイズを 23MB と小さくしています。

view/Dockerfile

FROM node:slim as builder
WORKDIR /app
ADD package*.json ./
RUN npm install
ADD . /app
RUN npm run build

FROM nginx:alpine
ADD nginx.conf /etc/nginx/nginx.conf
COPY --from=builder /app/dist /app

api

サーバサイドアプリケーションです。 nginx と同様にマルチステージビルドとしていますが、日本語 WordNet の辞書を用いている関係でイメージサイズは 290MB あります。

aaaton/golem 辞書 (Git LFS) を取っている箇所は妥協しています( go build で Git LFS のデータがどうしても取得できなかった)。個人開発は動けばよかろうの精神であまり深追いしていません。

api/Dockerfile

FROM golang:latest as builder
WORKDIR /go/src
RUN apt-get update && apt-get install -y git-lfs && git lfs install --skip-repo
RUN curl http://compling.hss.ntu.edu.sg/wnja/data/1.1/wnjpn.db.gz > wnjpn.db.gz && gunzip wnjpn.db.gz
RUN mkdir -p /go/src && git clone https://github.com/aaaton/golem.git /go/src/golem
ADD . /go/src
RUN go build . && ls -l /go/src

FROM debian:buster-slim
WORKDIR /go
COPY --from=builder /go/src/api /go/src/wnjpn.db ./
CMD ["./api"]

mongo

ソースコード内の変数名の使用実績などを永続化している MongoDB です。こちらは公式イメージをそのまま使用しています。

https-portal

上記コンテナ群だけでは、 HTTPS で提供することはできません。そこで登場するのが https-portal です。 https-portal は、下記の機能を持った nginx のリバースプロキシ Docker コンテナです。

  • 環境変数による複数ドメインのシンプルなリバースプロキシ
  • Let's Encrypt を用いた https 化(証明書は自動取得・自動更新)

通常は nginx や certbot や cron をゴニョゴニョしなければなりませんが、これを使うと docker-compose.yml に十数行書くだけで済みます (Quick Start 参照 ) 。他のオーケストレーションツールを追加せずに Docker / Docker Compose で完結するのも嬉しいポイントです。

version: "3"

services:
  https-portal:
    image: steveltn/https-portal:latest
    network_mode: host
    ports:
      - "80:80"
      - "443:443"
    environment:
      DOMAINS: "zenrei.nyamikan.net -> http://127.0.0.1:8080"
      STAGE: "production"
      LISTEN_IPV6: "true"

IPv6 対応

https-portal の nginx が IPv4 でしかリッスンしていなかったため、 IPv6 でリッスンするようプルリクエストを出しました(マージ済み🎉)。

github.com

IPv6 と docker-proxy

…ただし、 IPv6 でリッスンしなくても、 IPv6 で通信を受け付けること自体は可能です。ブリッジネットワークでコンテナを起動してポートを開放すると、 IPv4 は NAT で、 IPv6 は docker-proxy による IPv4 への変換で、それぞれ通信がされるようです。

$ sudo iptables -L -n
Chain DOCKER (3 references)
target     prot opt source               destination
ACCEPT     tcp  --  0.0.0.0/0            172.18.0.2           tcp dpt:3001

$ sudo netstat -anp
稼働中のインターネット接続 (サーバと確立)
Proto 受信-Q 送信-Q 内部アドレス            外部アドレス            状態        PID/Program name
tcp6       0      0 :::3001                 :::*                    LISTEN      9181/docker-proxy

この状態で IPv6 通信を行うと、ブリッジアダプタの IPv4 アドレスが通信元になってしまい、それがアクセスログに記録されてしまいます。

172.18.0.1 - - [08/Mar/2020:04:56:33 +0000] "GET /favicon.ico HTTP/2.0" 200 6183

そこで、ホストネットワークでコンテナを起動しつつ、 IPv6 で nginx をリッスンさせることで、これを解消しました。

240f:<MASKED>:7194 - - [08/Mar/2020:07:01:14 +0000] "GET /favicon.ico HTTP/2.0" 200 6183

もう一度まとめると

参考