ローカル環境（WSL）とリモート環境（docker）でnginx設定ファイルを共通化

## モチベーション
### そのいち
ローカル環境で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 から張っている

## 概略
いろいろな要素が出てくるのですが、

1. nginxのconfファイルに渡すための環境変数（SSL証明書ファイルパス）の差異
1. nginx起動時の環境（docker上かそうでないか）の差異

があります。
前者は各環境にローカルなシェルスクリプトを置く+zshログイン時に環境変数とし、
後者はnginxの呼び出し方を工夫することで、できるだけ差異を少なくしました。

## １. 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するようにしました。

```sh:.zshrc
# 環境ごとにローカルな環境変数を読む
export LOCAL_SERVER_ENV='/usr/local/etc'
for i in "${LOCAL_SERVER_ENV}"/*.sh ; do
  [ -r $i ] && source $i
```

```sh:ssl_conf.ssh
# ローカル環境
export SSL_CRT_PATH='/etc/ssl/certs/localhost-crt.pem'
export SSL_KEY_PATH='/etc/ssl/certs/localhost-key.pem'
```

```sh:ssl_conf.ssh
# リモート環境
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本体のアップデートのたびにビルドしなおすことになるのでちょっと辛いかなと思います。

ということで、[こちら](https://qiita.com/takyam/items/e92e5a6ca1548cbd58db)を参考にしてenvsubstで対応することにしました。
templateファイルを作っておき、nginx起動前にdefault.confを生成＆読み込む感じです。

```conf:/etc/nginx/conf.d/default.conf.template
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ファイルとスクリプトは以下のようになりました。

```yml:docker-compose.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;'"  
```

```sh:/usr/local/sbin/local_nginx_start
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が起動します。

```shell
# ローカル環境
local_nginx_start

# リモート環境
docker-compose up
```

## その他
[WSLが完全なカーネルをサポートする](https://japanese.engadget.com/2019/05/07/windows-10-linux-wsl-2/)ようなので、WSL上でdockerが使いやすくなるのを期待したいですね！

## 参考
[Let's Encrypt で Nginx にSSLを設定する](https://qiita.com/HeRo/items/f9eb8d8a08d4d5b63ee9)
[ローカル環境用SSLサーバ証明書を簡単に発行する(mkcert)](https://qiita.com/rkunihiro/items/530b5dc685bd3bff2082)
[Docker上のNginxのconfに環境変数(env)を渡すたったひとつの全く優れてない方法（修正：+優れている方法）](https://qiita.com/takyam/items/e92e5a6ca1548cbd58db)
[えっ、nginxって$HOGEで環境変数読めないの？？](http://www.mpon.me/entry/2018/02/07/200000)