Dockerで各種WebサービスをつくりProxy経由Let’s Encrypt SSLアクセス:黎明編(nginx-proxy,letsencrypt,wordpress)

投稿者: | 2021年7月18日
docker

長らくVMどまりだったおっさんSEが、今更だがコンテナをやってみる。
というのも、この blog を動かしているプラットフォームがレガシー化して久しく、そろそろ OS ごと入れ直しが必要ということで、折角なのでコンテナ化しようと。

とりあえずは WordPress が動けば良いのだが、今後 *.ookawara.com で色々なサービスを動かして遊びたい、ということでリバースプロキシをたててバックエンドサービスとして WordPress を動かす。考えた構成はこんな感じ。(考えたってほどの構成ではない。誰がやってもこうなる系)

今までは CentOS を使っていたのだが、なんとなく最近の赤帽帝国の不穏な雰囲気を感じているため、新たな OS は Ubuntu 20.04 を採用。

Ubuntu 20.04 および Docker/Docker Compose のインストールは省略。

リバースプロキシ&Let’s Encrypt SSL

まずはリバースプロキシ&Let’s Encrypt SSL証明書自動発行のコンテナ構築から。
構成ディレクトリ&ファイルは以下。

docker-compose.yml
nginx
├certs
├log
└vhost.d
 └default

nginx/certs ディレクトリには Let’s Encrypt で発行した証明書関連ファイルができる。
nginx/log はアクセスログ、エラーログを出力するところ。
nginx/vhost.d は Virtual Host単位で nginx の独自設定(例えばBASIC認証とか、リダイレクト系の設定など)を置くところ。
nginx/vhost.d 配下には default というファイルがあるが、これは Virtual Host 単位の独自設定(独自設定ファイル名=FQDN)がない場合に適用される。とりあえず “client_max_body_size 500M;” とか書いて POST できるデータサイズを緩和しておく。

docker-compose.yml はこんな感じ。

version: "3.7"

services:

    proxy:
        image: jwilder/nginx-proxy
        container_name: proxy
        environment:
            TZ: Asia/Tokyo
            DEFAULT_HOST: ${PROXY_DEFAULT_HOST}
            DHPARAM_GENERATION: "false"
        ports:
            - 80:80
            - 443:443
        networks:
            - proxy-link
        volumes:
            - /var/run/docker.sock:/tmp/docker.sock:ro
            - ./nginx/certs:/etc/nginx/certs:ro
            - ./nginx/vhost.d:/etc/nginx/vhost.d
            - ./nginx/log:/var/log/nginx
            - proxy-dhparam:/etc/nginx/dhparam
            - proxy-html:/usr/share/nginx/html
        restart: always

    letsencrypt:
        image: jrcs/letsencrypt-nginx-proxy-companion
        container_name: letsencrypt
        environment:
            TZ: Asia/Tokyo
            NGINX_PROXY_CONTAINER: proxy
            DEFAULT_EMAIL: ${LETSENCRYPT_EMAIL}
        networks:
            - proxy-link
        volumes:
            - /var/run/docker.sock:/var/run/docker.sock:ro
            - ./nginx/certs:/etc/nginx/certs
            - ./nginx/vhost.d:/etc/nginx/vhost.d
            - proxy-acme:/etc/acme.sh
            - proxy-html:/usr/share/nginx/html
        depends_on:
            - proxy
        restart: always

networks:
    default:
        external:
            name: bridge
    proxy-link:
        name: proxy.internal

volumes:
    proxy-dhparam:
    proxy-html:
    proxy-acme:

docker-compose.yml にて使用している環境変数指定用ファイルを別途作成。このあたりは複数環境でテストしたりしないなら docker-compose.yml に直接書けばいいかもね。

# Proxy
PROXY_DEFAULT_HOST=proxy.domain.example

# Let's Encrypt
LETSENCRYPT_EMAIL=foo@domain.example

注意すべきところは以下2点。
間違えやすいとか、ネット上に新しい情報があんまりなさそうなところだね。

  • ボリューム “docker.sock” のところ。nginx-proxy 側は”/tmp/docker.sock” 、letsencrypt 側は “/var/run/docker.sock” とパスが微妙に異なる。うっかりコピペ放置注意。
  • 環境変数 NGINX_PROXY_CONTAINER にプロキシーコンテナ名称を指定する。

本家ドキュメントも結構突き放し系。
https://hub.docker.com/r/jwilder/nginx-proxy
https://hub.docker.com/r/jrcs/letsencrypt-nginx-proxy-companion/

WordPress

次に、WordPress。
1コンテナで完結する All in one もあるようだが、自分は無駄にこだわる 3コンテナ構成。

docker-compose.yml
nginx
├nginx.conf
└conf.d
 └default.conf
wordpress
├Dockerfile
├msmtprc
└uploads.ini

nginx.conf はこんな感じ。正直どっかからのコピペで、あまり煮詰めてはいない。

user nginx;
worker_processes auto;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include /etc/nginx/mime.types;
    default_type  application/octet-stream;

    include /etc/nginx/conf.d/*.conf;
}

default.conf も同じく動けばいいんだよ系。

server {
    listen 80 default_server;
    server_name _;
    server_tokens off;
    root /var/www/html;
    index index.php index.html;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }
    location ~ .php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+.php)(.+)$;
        fastcgi_pass wordpress:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
    client_max_body_size 500M;
}

WordPress コンテナにメール送信できるソフトウェアが入っていないため、msmtp を別途インストールするためビルドする。Dockerfile はこんな感じ。

FROM wordpress:php8.0-fpm
RUN apt-get update \
 && apt-get install -y msmtp msmtp-mta \
 && apt-get clean

COPY ./msmtprc /etc/msmtprc
RUN chown www-data.www-data /etc/msmtprc
RUN chmod 600 /etc/msmtprc

COPY ./uploads.ini /usr/local/etc/php/conf.d
RUN chown www-data.www-data /usr/local/etc/php/conf.d/uploads.ini

RUN echo 'sendmail_path = "/usr/sbin/sendmail -t"' > /usr/local/etc/php/conf.d/mail.ini

msmtprc … msmtp でメール送信する際の設定ファイル。イマドキは必ず送信認証があるだろうからアカウント設定が必要。

# Set default values for all following accounts.
defaults
auth on
tls on
tls_starttls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
aliases /etc/aliases
logfile -

# Gmail
account yok
host mail.domain.example
port 587
from noreply@domain.example
user noreply
password hogehoge

# set a default account
account default : yok

uploads.ini … PHP の追加設定ファイル。でかいファイル Upload したい人向けの変更など。

memory_limit = 512M
upload_max_filesize = 500M
post_max_size = 500M
max_execution_time = 600

上記各ファイルを前提とした、docker-compose.yml はこちら。

version: "3.7"

services:

    wordpress-db:
        image: mariadb
        container_name: wordpress-db
        environment:
            TZ: Asia/Tokyo
            MYSQL_ROOT_PASSWORD: ${WORDPRESS_MYSQL_ROOT_PASSWORD}
            MYSQL_DATABASE: ${WORDPRESS_MYSQL_DATABASE}
            MYSQL_USER: ${WORDPRESS_MYSQL_USER}
            MYSQL_PASSWORD: ${WORDPRESS_MYSQL_PASSWORD}
        networks:
            - wordpress-link
        volumes:
            - wordpress-db-data:/var/lib/mysql
        command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
        restart: always

    wordpress:
        build: ./wordpress
        container_name: wordpress
        environment:
            TZ: Asia/Tokyo
            WORDPRESS_DB_HOST: wordpress-db
            WORDPRESS_DB_NAME: ${WORDPRESS_MYSQL_DATABASE}
            WORDPRESS_DB_USER: ${WORDPRESS_MYSQL_USER}
            WORDPRESS_DB_PASSWORD: ${WORDPRESS_MYSQL_PASSWORD}
        networks:
            - wordpress-link
        volumes:
            - wordpress-www-data:/var/www/html
        depends_on:
            - wordpress-db
        restart: always

    wordpress-web:
        image: nginx
        container_name: wordpress-web
        environment:
            TZ: Asia/Tokyo
            VIRTUAL_HOST: ${WORDPRESS_VIRTUAL_HOST}
            VIRTUAL_PORT: ${WORDPRESS_VIRTUAL_PORT}
            LETSENCRYPT_HOST: ${WORDPRESS_VIRTUAL_HOST}
            LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}
            LETSENCRYPT_TEST: ${WORDPRESS_LETSENCRYPT_TEST}
            CERT_NAME: ${WORDPRESS_CERT_NAME}
        networks:
            - proxy-link
            - wordpress-link
        volumes:
            - ./nginx/nginx.conf:/etc/nginx/nginx.conf
            - ./nginx/conf.d:/etc/nginx/conf.d
            - wordpress-www-data:/var/www/html
        depends_on:
            - wordpress
        restart: always

networks:
    default:
        external:
            name: bridge
    proxy-link:
        external:
            name: proxy.internal
    wordpress-link:
        name: wordpress.internal

volumes:
    wordpress-www-data:
    wordpress-db-data:

環境設定。

# WordPress MariaDB
WORDPRESS_MYSQL_ROOT_PASSWORD=hogehoge
WORDPRESS_MYSQL_USER=root
WORDPRESS_MYSQL_PASSWORD=hogehoge
WORDPRESS_MYSQL_DATABASE=wordpress

# WordPress nginx
WORDPRESS_VIRTUAL_HOST=www.domain.example
WORDPRESS_VIRTUAL_PORT=80
WORDPRESS_LETSENCRYPT_TEST=false
WORDPRESS_CERT_NAME=www.domain.example

ここで、WORDPRESS_LETSENCRYPT_TEST を true にして WORDPRESS_CERT_NAME を default としておくとテスト証明書発行になる。本物の発行は何回か失敗すると暫く発行できなくなったりするので、最初はテスト証明書で確認したほうが無難。

nginx-proxy および letsencrypt が wordpress コンテナ起動を検知して、VIRTUAL_HOST VIRTUAL_PORT LETSENCRYPT_HOST LETSENCRYPT_EMAIL LETSENCRYPT_TEST CERT_NAME 各変数の値をもとにリバースプロキシの設定や証明書自動発行(更新チェック)などを行ってくれる。便利。素晴らしい。神。

本格的に運用するとなると、各コンテナの各種ログを集約管理するとか、各ボリュームの定期バックアップとか、いろいろあるが、とりあえず別の記事としていずれ。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

1 × three =