Dockerで公開しているWebサイトをLet's Encryptを使ってSSL対応する

無料でSSLを導入できるLet's Encryptを使って、Dockerコンテナで立ち上げているサイトのSSL対応について簡単に説明します。環境はCentOS7です。

アプリケーションの起動

適当なコンテナを立ち上げます。特に立ち上げられるコンテナがなければ、いろいろと完成しているWordPressイメージを立ち上げるといいです。WordPressを立ち上げる場合は以下みたいにします。

docker run -e WORDPRESS_DB_HOST=192.168.0.1:3306 -e WORDPRESS_DB_USER=root -e WORDPRESS_DB_NAME=example -e WORDPRESS_DB_PASSWORD=password --name blog -d -p 8080:80 wordpress

http://example.com:8080にアクセスするとWordPressが立ちがっていると思います。

SSL証明書の取得

Let's EncryptはいつのまにかDockerで設定ができるようになっているみたいです。下記の説明がとても分かりやすく参考にさせてもらいました。

今回はざっくり下記のようなコマンドでSSL証明書の取得を取得しました。随分前にやったことを思い出しながら書いているのでうろ覚えなんですが、確かstandaloneでやれば面倒な作業なく発行できるんですが、それがオプションで指定したのか、途中の対話式の画面で選択したのか忘れてしまいました。とりあえず自然にできていた覚えがあります。

docker run -it --rm -p 443:443 --name letsencrypt -v "/etc/letsencrypt:/etc/letsencrypt" -v "/var/lib/letsencrypt:/var/lib/letsencrypt" quay.io/letsencrypt/letsencrypt:latest certonly

完了すると以下のようなメッセージが出ます

IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at
/etc/letsencrypt/live/example.com/fullchain.pem. Your cert will
expire on 2016-08-10. To obtain a new or tweaked version of this
certificate in the future, simply run certbot again. To
non-interactively renew *all* of your certificates, run "certbot
renew"
- If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
Donating to EFF:                    https://eff.org/donate-le

ファイルはここに出来ました

/etc/letsencrypt/live/example.com/fullchain.pem
/etc/letsencrypt/live/example.com/cert.pem
/etc/letsencrypt/live/example.com/chain.pem
/etc/letsencrypt/live/example.com/privkey.pem

Nginxの設定

Nginxをインストールして設定します。そのままではインストールできないのでepelを入れます。

yum -y install epel-release

インストールしたら/etc/yum.repos.d/epel.repoを編集して、epelのenabledを0にしてデフォルトを無効にしておきます。その後下記のようにしてNginx本体をインストール。

yum install --enablerepo=epel nginx

自動起動をONにしてNginxの起動を確認しておきます。

systemctl enable nginx
systemctl start nginx

続けてNginxのSSLの設定とDockerコンテナへのプロキシを行います。

  1. 今回は全ページをSSL対応するため、80番ポートへのアクセスは全てhttpsにリダイレクト
  2. httpsに来たアクセスの実体はhttp://localhost:portにプロキシすることで表側からSSL対応

その設定が下記になります。

server {
listen 80;
server_name example.com;
listen [::]:80;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
server_name example.com;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
location / {
proxy_pass http://127.0.0.1:8080;
}
}

proxy_set_headerでいろいろとやってますが、アプリケーション側が対応していればこの設定はいらなかったりします。いろいろ端折ってますが大体こんなイメージでいけます。実際の運用時にまだまだ設定しなきゃいけないことがありますが、ざっくりこんな感じ。

(追記) proxy_set_headerについて

proxy_set_header Host $host;という記述を(覚えてはいけないけど)WordPress対策のために設定したことを忘れ、この設定のままLaravelのアプリケーションを立ち上げたらエラーになりました。Dockerでアプリケーションを運用する場合はproxy_set_headerの値に注意したいです。

また、nginx自体の設定ファイルや再起動を自動化しても良いかもしれません。proxy_set_headerの値をjsonで定義しておき動的に書き換えられるようにすれば、意識付けもできますし、個人用Docker運用環境ならそれくらいで良いかもしれません。

(追記) 定期更新について

Let's Encryptには有効期限があります。上記の手順で再度ファイルを作成します。ファイルの作成先は同様の場所になるのですが、別のディレクトリになることがありますので、古いものを削除して同様の場所に配置してあげれば良いです。Nginxを起動していると443番ポートで更新用のコンテナが立ち上がらないので一度Nginxを停止して上げる必要があります。どこか別の場所で生成して持ってきても良いかもしれない。

(追記) 定期更新について2

あれから定期更新や新規サービスの追加毎にSSL証明書の再発行を行っています。毎度作成する時にはよくわからない場所に証明書が設定されて、シンボリックリンクが更新されないという問題にぶち当たり、Nginx側から調査してしまって時間をかけるという悪循環なので自分が忘れないように、作成日の時間を確認しながら最新ファイルの実態がどこに作成されたか確認をしてシンボリック先を変更するの忘れないようにという気持ちを込めてもう一度書いておきました。

参考
- http://qiita.com/ywatai@github/items/a179186a458a42b3c7f0
- http://kido0617.github.io/wordpress/2014-11-03-wordpress-redirect-loop/
- http://postd.cc/secure-web-deployment-with-lets-encrypt-and-nginx/
- http://qiita.com/sawanoboly/items/9fdde1707de5e975dd15

docker-composeでbuildする時にcacheを使わない

f:id:setouchinatsu:20190403180056p:plain:w200

docker buildと同じようにdocker-composeでもキャッシュを使わないよう指定することができる

docker-compose build --no-cache

開発中にdocker-compose buildしても内容が変化しない時はキャッシュが効いている事が多いのでたまに使う。nginx.confファイルを編集して内容が反映されなくて、キャッシュが効いてるということに気付かないケースが多い。。。

また環境にもよると思うがdocker-compose upで何度も失敗してしまう場合はbuildでおかしなことになっていることが多く、この場合はdocker-compose buildとビルドだけを指定して何度も実行してるとうまくいくことが多い。ノートPCでやってる時に頻繁に起きるのでネットワークの遅さなのか、マシンが貧弱なのか。

Dockerコンテナがiptablesのエラーで立ち上がらなくなった

サーバーで色々と作業してrebootしてdockerコンテナを立ち上げようとした所以下のエラーが発生した

Error response from daemon: Cannot start container ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb: failed to create endpoint blog on network bridge: COMMAND_FAILED: '/sbin/iptables -w2 -t nat -A DOCKER -p tcp -d 0/0 --dport 8080 -j DNAT --to-destination 172.17.0.2:80 ! -i docker0' failed: iptables: No chain/target/match by that name.

単にdockerデーモンを再起動したら治った

systemctl restart docker

他にもnginxのproxy経由では502 Bad Gatewayが出て解決できないけど、自分自身では見えたり。特定ポートではコンテナが立ち上がらなかったり。作業中にもしやとSELinuxを停止したり色々していたら動いたけど、逐一確認作業をしなかったので証拠がない。多分SELinuxだったんじゃないかと思う。。。

参考
http://qiita.com/witamo/items/9770a2a757d3f5e369a4

JavaScriptで配列内の文字列から最も文字数の多い要素の文字数を得る

var arr = ['aaa', 'bb', 'cccc', 'dd'];
console.log(Math.max.apply(null, arr.map(function(str) { return str.length; })));

(訂正) 当初、「JavaScriptで配列内の文字列から最も文字数の多い要素を得る」と書いてしまっていましたが、正しくは「JavaScriptで配列内の文字列から最も文字数の多い要素の文字数を得る」というタイトルでした。失礼しました。

jQueryのeachでbreakとcontinueをする

continue

ループ中にreturn true;continueと同じ処理になる

$('a').each(function() {
var url = $(this).attr('href');
if (/hoge/.test(url)) {
return true;
}
});

break

ループ中にreturn false;breakと同じ処理になる

$('a').each(function() {
var url = $(this).attr('href');
if (/hoge/.test(url)) {
return false;
}
});