dockerコンテナ上で利用しているLet's Encryptを自動更新する

4月から転職しまして、自社設備の右も左もわからない状態で
「Let's Encryptがそろそろ更新だからやっといて〜」
とのタスクをいただいたのでついでに自動化をば。

dockerでアプリを実装していますが、マウントしているのは証明書だけだったのでホストのcronで回してます。

## 前提情報　

- ホスト
    - Amazon Linux
- コンテナ
    - nginx

- 証明書保存先

```shell
$ ls -al foo/var/certs/
合計 28
drwxrwxr-x  2 ec2-user ec2-user 4096  3月  8 03:57 .
drwxrwxr-x 16 ec2-user ec2-user 4096  4月  8 02:32 ..
-rw-r--r--  1 root     root      692  1月 22 09:15 README
-rw-rw-r--  1 ec2-user ec2-user 1927  3月 26 00:47 cert.pem
-rw-r--r--  1 root     root     1647  1月 22 09:15 chain.pem
-rw-rw-r--  1 ec2-user ec2-user 3574  3月 26 00:47 fullchain.pem
-rw-rw-r--  1 ec2-user ec2-user 1704  3月 26 00:47 privkey.pem
```

## 1. どこでLet's Encryptが使われてるか確認

そうです、探すとこからです。

- とりあえずpstree

```
$ pstree
略
    ├─dockerd─┬─docker-containe─┬─docker-containe─┬─mysqld───28*[{mysqld}]
     │         │                 │                 └─9*[{docker-containe}]
     │         │                 ├─docker-containe─┬─mysql
     │         │                 │                 ├─php-fpm───3*[php-fpm───3*[{php-fpm}]]
     │         │                 │                 └─9*[{docker-containe}]
     │         │                 ├─docker-containe─┬─nginx───nginx
     │         │                 │                 └─9*[{docker-containe}]
     │         │                 ├─docker-containe─┬─redis-server───2*[{redis-server}]
     │         │                 │                 └─9*[{docker-containe}]
     │         │                 ├─docker-containe─┬─celery───3*[celery]
     │         │                 │                 └─9*[{docker-containe}]
     │         │                 ├─docker-containe─┬─postgres───10*[postgres]
     │         │                 │                 └─9*[{docker-containe}]
     │         │                 ├─docker-containe─┬─gunicorn───4*[gunicorn]
     │         │                 │                 └─9*[{docker-containe}]
     │         │                 └─19*[{docker-containe}]
     │         ├─docker-proxy───7*[{docker-proxy}]
     │         ├─2*[docker-proxy───5*[{docker-proxy}]]
     │         ├─docker-proxy───6*[{docker-proxy}]
     │         └─21*[{dockerd}]

```
あ、dockerなんですね。

- docker-compose情報

```
$ cat docker-compose.yml
version: '2'
services:
  app:
    build: ./foo/app
    volumes:
      - ./:/var/www
    links:
      - mysql
  web:
    build: ./bar/web
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./:/var/www
      - ./certs/:/etc/pki/certs
    links:
      - app
  mysql:
    build: ./nyaa/mysql
    ports:
      - "3306:3306"
ry

```
とりあえず./bar/web/以下のwebコンテナのみ証明書を使用していると判断

```
$ ls -al ./bar/web/
合計 20
drwxrwxr-x 2 ec2-user ec2-user 4096  3月 14 07:57 .
drwxrwxr-x 5 ec2-user ec2-user 4096  1月 22 08:58 ..
-rw-rw-r-- 1 ec2-user ec2-user  298  1月 22 08:58 Dockerfile
-rw-rw-r-- 1 ec2-user ec2-user  671  1月 22 08:58 nginx.conf
-rw-rw-r-- 1 ec2-user ec2-user  650  3月 14 07:57 vhost.conf
```

```shell:vhost.conf
ry
    ssl on;
    server_name your_domain.jp;
    ssl_certificate /etc/pki/certs/fullchain.pem;
    ssl_certificate_key /etc/pki/certs/privkey.pem;
ry
```
webコンテナのnginxで使用していることが判明　　
（このタイミングで確認しましたがやはり使用しているのはnginxだけでした。）


## 2. 更新作業

1. 証明書の更新
2. dockerコンテナ内nginxのreload

上記手順を自動化します。

### 証明書の更新

とりあえず更新してみる。

- 設定ファイル

```
$ cat /etc/letsencrypt/renewal/your.domain.conf
# renew_before_expiry = 30 days
version = 0.33.1
archive_dir = /etc/letsencrypt/archive/your.domain.jp
cert = /etc/letsencrypt/live/your.domain.jp/cert.pem
privkey = /etc/letsencrypt/live/your.domain.jp/privkey.pem
chain = /etc/letsencrypt/live/your.domain.jp/chain.pem
fullchain = /etc/letsencrypt/live/your.domain.jp/fullchain.pem

# Options used in the renewal process
[renewalparams]
authenticator = xxxxxx
account = your_account_key
server = https://acme-v02.api.letsencrypt.org/directory
[[webroot_map]]
your.domain.jp = /foo/bar/directory
```

- 更新を実行

```
$ sudo /usr/bin/certbot-auto renew
Upgrading certbot-auto 0.30.0 to 0.33.1...
Replacing certbot-auto...
Creating virtual environment...
Installing Python packages...
Installation succeeded.
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/your.domain.jp.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cert is due for renewal, auto-renewing...
Plugins selected: Authenticator xxxxxx, Installer None
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for your.domain.jp
Waiting for verification...
Cleaning up challenges

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
new certificate deployed without reload, fullchain is
/etc/letsencrypt/live/your.domain.jp/fullchain.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/your.domain.jp/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
```

- 確認

```
$ sudo ls -al /etc/letsencrypt/live/your.domain.jp
合計 12
drwxr-xr-x 2 root root 4096  4月  8 03:31 .
drwx------ 3 root root 4096  1月 22 09:12 ..
-rw-r--r-- 1 root root  692  1月 22 09:12 README
lrwxrwxrwx 1 root root   45  4月  8 03:31 cert.pem -> ../../archive/your.domain.jp/cert2.pem
lrwxrwxrwx 1 root root   46  4月  8 03:31 chain.pem -> ../../archive/your.domain.jp/chain2.pem
lrwxrwxrwx 1 root root   50  4月  8 03:31 fullchain.pem -> ../../archive/your.domain.jp/fullchain2.pem
lrwxrwxrwx 1 root root   48  4月  8 03:31 privkey.pem -> ../../archive/your.domain.jp/privkey2.pem
```

## 3. 自動化と反映

証明書はちゃんと更新できることが判明したので、自動化してdockerコンテナに反映させます。

### 更新スクリプトを作成

現在dockerにマウントさせている証明書は上記証明書のコピーとなっている。
自動更新に対応させるため、上記シンボリックリンク先の証明書をvolumeディレクトリにコピーする必要がある。

```shell:/usr/local/libexec/lets_encrypt_renew.sh

#!/bin/sh

# 証明書の更新
/usr/bin/certbot-auto renew

# 証明書のコピー（docker_mountは上記docker-compose.ymlのvolumeマウント先です）
cp /etc/letsencrypt/live/your.domain.jp/cert.pem docker_mount/certs/
cp /etc/letsencrypt/live/your.domain.jp/chain.pem docker_mount/certs/
cp /etc/letsencrypt/live/your.domain.jp/fullchain.pem docker_mount/certs/
cp /etc/letsencrypt/live/your.domain.jp/privkey.pem docker_mount/certs/

# nginxのリロード
docker exec -i container_name service nginx reload
```

### timezone設定

cron実行してテストしようとしたらtimezone設定されていなかった。(まじか)
時刻合わせを実施する（設定済みの方は飛ばしてください）

```diff
$ sudo cp -a /etc/localtime ~/org/
$ sudo ln -sf  /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

$ sudo cp -a /etc/sysconfig/clock ~/org/
$ sudo vi /etc/sysconfig/clock

$ diff -u ~/org/clock /etc/sysconfig/clock
--- /home/your_user/org/clock	2018-11-17 08:07:41.583229222 +0900
+++ /etc/sysconfig/clock	2019-04-08 14:06:53.465544040 +0900
@@ -1,2 +1,2 @@
-ZONE="UTC"
+ZONE="Asia/Tokyo"
 UTC=true
```

- 確認

```
$ date
2019年  4月  8日 月曜日 14:09:02 JST　（あってる。）
```

### cron記載

- /etc/cron.d/lets_encrypt

```
30 9 1 * * root sh /usr/local/libexec/lets_encrypt_renew.sh > /dev/null 2>&1
```
毎月1日9:30にスクリプト実行し、（デフォルトなので）有効期限が30日未満の場合に更新します。
ついでに次回更新時の確認のためにslackでリマインドしました。

### cronの時間変えて検証

ちゃんと動作することを確認してください。（cronを実際に動かさないと、パーミッションと環境変数でエラーとかあるあるですよね。）

## 完了

ブラウザから確認

**やったぜ。**

どのサーバでなにやってるかわからない中、取り急ぎ感が満載ですが無事docker上で使用しているLet's Encryptを更新自動化できました。
これからもいろいろ整備していきます。

ホストがオートスケールだったりマネージドでホストがなかったりするとcronじゃ難しいですが、とりあえず基本としてメモまで。