事例が何かの参考になればと思ったので記事にしました。
3行でまとめると
- Zenrei (zenrei.nyamikan.net) の Web サーバ・ API サーバ・ DB サーバすべてを Docker 化した
- SteveLTN/https-portal という Let's Encrypt の証明書取得を自動化した nginx のリバースプロキシコンテナを利用した
- プルリクエストを出して https-portal を IPv6 対応にした
すべて Docker 化
個人開発のサービスを省力運用にするべく、 Zenrei (zenrei.nyamikan.net) をすべて Docker コンテナ化しました。
docker-compose.yml とそれぞれの Dockerfile はリポジトリに公開しています。
nginx (host network)
nginx (bridge network)
フロントエンドの静的ファイル配信と、サーバサイドアプリケーションへのリバースプロキシを担います。マルチステージビルドを使用し、 Vue.js の生成物のみを nginx に持ってきてイメージサイズを 23MB と小さくしています。
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 のデータがどうしても取得できなかった)。個人開発は動けばよかろうの精神であまり深追いしていません。
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 コンテナです。
通常は 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 でリッスンするようプルリクエストを出しました(マージ済み🎉)。
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
もう一度まとめると
- Zenrei (zenrei.nyamikan.net) の Web サーバ・ API サーバ・ DB サーバすべてを Docker 化した
- SteveLTN/https-portal という Let's Encrypt の証明書取得を自動化した nginx のリバースプロキシコンテナを利用した
- プルリクエストを出して https-portal を IPv6 対応にした
参考