VPSなどの素のサーバーでWebアプリを公開するとき、必ず引っかかるのがSSLをどうするかという問題です。マネージドサービスなら当たり前に付いてくるものを、自分で用意しなければなりません。
弊社(アイデアマンズ)ではいろいろ試した末、次の組み合わせに落ち着きました。
- ドメイン管理: Amazon Route 53
- Let's Encryptクライアント: lego(Go製のシングルバイナリで取り回しが楽)
- リバースプロキシ: nginxのDockerコンテナ
- ログローテーション: 1日1回
ソースコード一式は公開しています。cloneして自分のアプリを記述すれば、そのまま使えます。
構成
Route 53を使っているのは、legoがDNS認証にAPIを備えたDNSを必要とするためです。手元で試した範囲ではRoute 53が最も手軽でした。
代替案としてhttps-portalもおすすめできます。こちらのほうが手軽で任意のDNSでも使えますが、ワイルドカード証明書(*.example.com)には対応していません。以前はよく使っていたものの、ワイルドカードの便利さを取ってこの構成に移りました。
手順1: Route 53を操作するIAMユーザーを用意する
次のポリシーを参考にIAMユーザーを作成します。<INSERT_YOUR_HOSTED_ZONE_ID_HERE> は対象ドメインのホストゾーンIDに置き換えてください。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"route53:GetChange",
"route53:ListHostedZonesByName",
"route53:ListResourceRecordSets"
],
"Resource": ["*"]
},
{
"Effect": "Allow",
"Action": ["route53:ChangeResourceRecordSets"],
"Resource": ["arn:aws:route53:::hostedzone/<INSERT_YOUR_HOSTED_ZONE_ID_HERE>"]
}
]
}
手順2: .env に認証情報を書く
example.env を .env としてコピーし、4項目を設定します。
LEGO_EMAIL=you@example.com # メールアドレス
LEGO_DOMAIN=*.example.com # 証明書を取得するドメイン(ワイルドカード可)
AWS_REGION=ap-northeast-1
AWS_ACCESS_KEY_ID= # IAMユーザーのアクセスキーID
AWS_SECRET_ACCESS_KEY= # IAMユーザーのシークレットアクセスキー
手順3: nginxの設定を書き換える
nginx/default.conf のドメインを、利用するドメインに変更します。
ここに1つハマりどころがあります。ワイルドカード記号 * は、証明書のファイルパス上では _ に置換されます。
ドメインが www.example.com、証明書を *.example.com で取得した場合は次のようになります。
server_name www.example.com;
ssl_certificate /var/lib/lego/certificates/_.example.com.crt;
ssl_certificate_key /var/lib/lego/certificates/_.example.com.key;
手順4: アプリを差し替えて起動する
compose.yml の app サービスを、公開したいWebサービスに変更します。
app: # ダミーアプリケーション
image: httpd:2.4
あとは起動するだけです。
docker compose up --build
初回はLet's Encryptで証明書を取得するため数分かかります。ブラウザでアクセスして表示されれば完了です。
仕組み1: シェルスクリプトのサイドカー
nginxに補助的な処理を足したいとき、シェルスクリプトをよく使います。
シェルスクリプトの関数は & を付けて実行すると子プロセスとして並行処理できます。これを使い、素のnginxイメージに証明書の取得・更新とログローテーションを足しています。
#!/bin/bash
sidecar() {
# 補助的な処理
}
sidecar &
# nginx本体を起動
exec nginx -g "daemon off;"
実装は nginx/entrypoint.sh にあります。
仕組み2: cronを使わない定期実行
定期処理といえばcronですが、Dockerコンテナでcronを動かすのは何かと面倒です。
そこで while の無限ループと date・sleep を組み合わせ、1日1回の処理をスケジューリングしています。この小技については以前Qiitaにも書きました。
1コンテナ1ロールについて
Dockerには1つのコンテナに1つの役割という原則があります。この点、上記のやり方はかなり横着しています。
原則自体には賛同していますが、ここはケースバイケースでよいと考えました。SSLゲートウェイはWebアプリの脇役に過ぎず、この構成にしておくと記述するファイル数もコンテナの相互関係もシンプルに保てます。
VPSを選ぶ理由は、月500円程度から使えて費用が急に跳ね上がらないこと、開発言語を選ばないこと、ロックインされないことです。安く抑える工夫自体が勉強になる、という側面もあります。
副産物のようなプロジェクトですが、汎用的に使えるはずです。一度慣れておくとWebアプリの公開が気楽になります。
さらに詳しくは次の記事で解説しています。
