モチベーション
そのいち
ローカル環境でWSLを使っている場合、dockerはもろもろの事情で使いづらい(volumeのパスとか)。
でもリモート開発環境ではdockerを使いたい。
また、できるだけdotfilesを使って開発環境は統一しておきたい。。。
そのに
SSLの証明書ファイルに関する設定がローカル環境とリモート環境で異なる。
これは、SSLの設定を行うために
- ローカル環境:mkcert
- リモート環境:Let's Encrypt
を利用しているため。
この差異を吸収できるnginxの設定ファイルを作りたい。。。
環境
-
ローカル環境
- Windows 10
- WSL(Ubuntu 18.04)
- nginx 1.16.0
- mkcertによるSSL
-
リモート環境
- GCE(CentOS 7.6.1810)
- docker Engine 18.09.5
- docker-compose 1.23.2
- Let's EncryptによるSSL
-
共通
- zsh
前提(構成とか)
- nginxを使って静的ファイル配信
- ローカル環境の場合、各アプリごとにconfファイルを作りそのシンボリックリンクを/etc/nginx/conf.d から張っている
概略
いろいろな要素が出てくるのですが、
- nginxのconfファイルに渡すための環境変数(SSL証明書ファイルパス)の差異
- nginx起動時の環境(docker上かそうでないか)の差異
があります。
前者は各環境にローカルなシェルスクリプトを置く+zshログイン時に環境変数とし、
後者はnginxの呼び出し方を工夫することで、できるだけ差異を少なくしました。
1. SSL証明書へのパスの違い
特に何も指定せずにmkcertとLet's Encryptで証明書を取得すると、以下のような場所に格納されると思います。
ローカル環境:
SSL_CRT_PATH='/etc/ssl/certs/localhost-crt.pem'
SSL_KEY_PATH='/etc/ssl/certs/localhost-key.pem'
リモート環境:
SSL_CRT_PATH='/etc/letsencrypt/live/domain_name/fullchain.pem'
SSL_KEY_PATH='/etc/letsencrypt/live/domain_name/privkey.pem'
これらの違いを吸収するためには、各パスを環境変数に書いておいて、それをnginxの設定ファイルに動的にセットするのがよいかなとパッと思いつきます。
ただ、.zshrcはdotfilesに入れてあるのでローカルとリモートで同一の設定になっています。
なので、.zshrcから以下のように/usr/local/etcにあるシェルスクリプト群を呼び、そのスクリプト内で環境変数をexportするようにしました。
# 環境ごとにローカルな環境変数を読む
export LOCAL_SERVER_ENV='/usr/local/etc'
for i in "${LOCAL_SERVER_ENV}"/*.sh ; do
[ -r $i ] && source $i
# ローカル環境
export SSL_CRT_PATH='/etc/ssl/certs/localhost-crt.pem'
export SSL_KEY_PATH='/etc/ssl/certs/localhost-key.pem'
# リモート環境
export SSL_CRT_PATH='/etc/letsencrypt/live/domain_name/fullchain.pem'
export SSL_KEY_PATH='/etc/letsencrypt/live/domain_name/privkey.pem'
これにより、各環境にログインした際に、環境ごとのファイルパスが環境変数として保持できることになります。
ただ、sudoをすると設定によっては環境変数を持ち越さないことがあるので、「nginx起動時はsudoではなくsuを使う」というオレオレ規約で運用しています。
2. nginx起動時の環境差異
驚くべきことにnginxは標準では環境変数の読み込みが面倒くさいです。${foo}みたいにして読み込めません。
外部モジュールを使って読み込む方法もあるようですが、nginx本体のアップデートのたびにビルドしなおすことになるのでちょっと辛いかなと思います。
ということで、こちらを参考にしてenvsubstで対応することにしました。
templateファイルを作っておき、nginx起動前にdefault.confを生成&読み込む感じです。
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name localhost;
# envsubstによって${foo}のようにして環境変数がセットできる
ssl_certificate ${SSL_CRT_PATH};
ssl_certificate_key ${SSL_KEY_PATH};
(略)
}
ただ、このとき、
- ローカル環境:dockerなしでnginx起動
- リモート環境:docker上でnginx起動
とするので、微妙に差異があります。
docker-compose.ymlはリモートでは使うけどローカルでは使わないことになりますが、confファイルは当然共通のものを利用したいわけです。
ということで、
- ローカル環境:confファイル生成とnginx起動をするラッパースクリプト
- リモート環境:docker-compose up
としてます。(また運用でカバーしてる。。)
ymlファイルとスクリプトは以下のようになりました。
version: "3.7"
services:
nginx-proxy:
image: nginx
container_name: "nginx"
ports:
- '443:443'
# 環境変数からdockerコンテナに渡すための変数にセット
environment:
- SSL_CRT_PATH="${SSL_CRT_PATH}"
- SSL_KEY_PATH="${SSL_KEY_PATH}"
volumes:
- type: bind
source: ./nginx
target: /etc/nginx/conf.d
read_only: true
- type: bind
source: ./www
target: /www/
read_only: true
- type: bind
source: /etc/letsencrypt/
target: /etc/letsencrypt/
command: /bin/sh -c "envsubst '$$SSL_CRT_PATH$$SSL_KEY_PATH' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"
envsubst '$SSL_CRT_PATH$SSL_KEY_PATH' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf;
service nginx start;
それぞれの環境で、suした後以下のコマンドを打てばnginxが起動します。
# ローカル環境
local_nginx_start
# リモート環境
docker-compose up
その他
WSLが完全なカーネルをサポートするようなので、WSL上でdockerが使いやすくなるのを期待したいですね!
参考
Let's Encrypt で Nginx にSSLを設定する
ローカル環境用SSLサーバ証明書を簡単に発行する(mkcert)
Docker上のNginxのconfに環境変数(env)を渡すたったひとつの全く優れてない方法(修正:+優れている方法)
えっ、nginxって$HOGEで環境変数読めないの??