CMSの管理サーバと静的ファイルの公開サーバを別に分ける

# はじめに
## 対象読者
* CMS を静的ファイル公開でオンプレ環境にて運用している
    * たとえば……
        * [PowerCMS](https://www.powercms.jp/) / [PowerCMS X](https://powercmsx.jp/)
        * [Movable Type](https://www.movabletype.jp/)
        * ……など
* 管理サーバと静的ファイルの公開サーバを別に分けたい

## 前提
### 管理サーバと公開サーバの環境
* Ubuntu 20.04
* nginx
* root 以外の sudo ユーザーが存在していること
    * 本記事内では username という表記のユーザー名とする

## やること概要
* 管理サーバと公開サーバとに rsync と lsyncd をインストールする
* 管理サーバから公開サーバへ、一方通行でリアルタイムにファイル／ディレクトリの同期がかかるようにする
* 管理サーバに出力される静的ファイルが格納されるディレクトリを、公開サーバへ同期する対象のディレクトリとして指定する


# 管理サーバ／公開サーバ共通でおこなうこと
## rsync
### インストール
* デフォルトでインストールされていることがほとんどと思いますけれども

```sh
apt install rsync
```


## 鍵ペア作成
* username でログインしておくこと
* 作成コマンドを打つ

```sh
ssh-keygen -b 4096
```

* 作成される鍵を置く path と filename の入力を求められるので、任意のものを入力
* passphrase は入力しないで enter キーのみで進める

```sh
Generating public/private rsa key pair.
Enter file in which to save the key (/home/username/.ssh/id_rsa): /home/username/.ssh/rsync
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/username/.ssh/rsync
Your public key has been saved in /home/username/.ssh/rsync.pub
```

## 公開鍵を置く
* 上記で作成したお互いの公開鍵を、お互いの /home/username/.ssh/ へ置く

## 公開鍵の登録として authorized_keys へ追記
* 管理サーバと公開サーバの両方でおこなう

```sh
cat /home/username/.ssh/xxxxxxxx.pub >> /home/username/.ssh/authorized_keys
```

* もし authorized_keys がここで初めて作成される場合は permission を変更しないとならない

```sh
chmod 600 /home/username/.ssh/authorized_keys
```


## 接続テスト
### コマンド
* 双方向にお互い接続し合えるか、両方からおこなってみること
    * xxxxxxxx は秘密鍵のファイル名
    * xxx.xxx.xxx.xxx は接続先の ip address で、ここは FQDN でも可

```sh
ssh -i /home/username/.ssh/xxxxxxxx username@xxx.xxx.xxx.xxx
```

* パスワードを訊かれず ssh ログインできていれば成功です


### 接続できない／パスワードを訊かれる場合の要確認箇所
* /home/username/ ディレクトリの permission が 700 になっているか
* /home/username/.ssh/ ディレクトリの permisssion が 700 になっているか
* 秘密鍵ファイルの permission が 600 になっているか
* authorized_keys の permission が 600 になっているか
* UFW の設定
* /etc/hosts.allow の設定


# 公開サーバでおこなうこと
## rsync コマンドを sudo 時にパスワードなしで実行できるようにする
* /etc/sudoers に以下を追記

```sh:/etc/sudoers
Defaults!/usr/bin/rsync !requiretty
username ALL=(ALL) NOPASSWD: /usr/bin/rsync
```


# 管理サーバでおこなうこと
## lsyncd
### インストール
```sh
apt install lsyncd
```

### 設定
* ディレクトリ作成

```sh
mkdir /etc/lsyncd/
```

* 設定ファイル作成
    * sync 内の source は、出力される静的ファイルの path ディレクトリを指定してください
        * たとえば PowerCMS X であれば /site/ ディレクトリ
    * 同 target は source の path と揃えておいた方がわかりよいと思います

```lua:/etc/lsyncd/lsyncd.conf.lua
settings {
        logfile = "/var/log/lsyncd.log",
        statusFile = "/tmp/lsyncd.stat",
        insist = 1,
        statusInterval = 10
}

sync_base = {
        default.rsync,
        delete = running,
        init = false,
        rsync = {
          archive = true,
          rsync_path = "sudo /usr/bin/rsync",
          rsh = "/usr/bin/ssh -i /home/username/.ssh/xxxxxxxx -o StrictHostKeyChecking=no"
        }
}

sync {
        sync_base,
        source="/var/www/powercmsx/site/",
        target="username@xxx.xxx.xxx.xxx:/var/www/powercmsx/site/"
}
```

### サービス再起動
```sh
systemctl restart lsyncd
```

## 動作確認
* 管理サーバ側の同期対象ディレクトリに、適当にファイルを作成するなどしてみたときに、公開サーバ側の同期対象ディレクトリに反映されるかどうか
* ログを見るのが良い
    * ログの出力先は /etc/lsyncd/lsyncd.conf.lua で自身が指定した箇所による
* 以下、成功している場合のログの一例

```sh:/var/log/lsyncd.log
Fri Jul  3 18:55:41 2020 Normal: Calling rsync with filter-list of new/modified files/dirs
/test2
/
/test
Fri Jul  3 18:55:42 2020 Normal: Finished a list after exitcode: 0
```

* 失敗している場合は、失敗理由がログに出力されているので、そこから追いましょう
* 例えば以下は username を間違えているという理由で失敗している場合のログの一例

```sh:/var/log/lsyncd.log
Fri Jul  3 18:54:21 2020 Normal: Calling rsync with filter-list of new/modified files/dirs
/test
/
Warning: Identity file /home/username/.ssh/rsync_powercms not accessible: No such file or directory.
Permission denied, please try again.
Permission denied, please try again.
username@xxx.xxx.xxx.xxx: Permission denied (publickey,password).
rsync: connection unexpectedly closed (0 bytes received so far) [sender]
rsync error: unexplained error (code 255) at io.c(235) [sender=3.1.3]
Fri Jul  3 18:54:21 2020 Normal: Retrying a list after exitcode = 255
```

* 上記のように /etc/lsyncd/lsyncd.conf.lua の設定記述が原因で失敗している場合は、書き換えた後に必ず lsyncd サービスを再起動してください。でないと、設定が再読込されません


# サイトの公開設定変更
## 注意点
* lsyncd サービスが走り始めてから以後に変更のあったものだけを検知して同期するしくみなので、それ以前のものについては別の手段でファイルをコピーしておく必要があります
* 必ず、管理サーバの /site/ ディレクトリ以下のものが全て、公開サーバ側にも同期されていることを確認しておいてください
* しばらく様子を見て、ログを確認し、特に問題ないようであれば、公開設定の変更をおこなうという手順を取るのが良いように思います

## DNS の書き換え
* DNS レコードを管理しているところで、該当する FQDN = サブドメイン の A レコードについて、管理サーバの ip address から 公開サーバのそれへと書き換えてください
* この書き換え作業をする際には、実行予定日よりも前にあらかじめ TTL を短めに設定しておくとよいでしょう

## 公開サーバ側の nginx 設定
* これまで管理サーバで受けていた nginx の設定を、公開サーバでも同じように受けられるようにしておきましょう

## 公開サーバ側の SSL 対応
* Let's Encrypt を導入する場合は A レコードが書き換わらないとインストール成功しないので、ギリギリのところを狙っておこなってください
    * 管理サーバから Let's Encrypt の証明書ファイルを持ってくることで対応するのは……避けておいた方が無難かなと……
    * 公開サーバに既に一度 Let's Encrypt を入れていて、ここではサブドメインを拡張したい場合、以下のコマンドを実行
        * -d で拡張したい数ぶんだけ対応してください

```sh
certbot --nginx --expand -d xxxxxx.xxx -d yyyyyy.yyy -d zzzzzz.zzz
```

* その他の証明書を持っていれば、事前に置いておくだけで良いので楽です
* 証明書が正しく読み込まれているか nginx をリロードしてから確認しましょう

```sh
nginx -s reload
```

## 確認
* 公開サイトが公開サーバを見るようになっていることを確認しましょう
* また、静的ファイルの公開／再構築をかけた時に、少し間を置いてから公開サイトが更新されているか、というのも確認しておきましょう
    * PowerCMS X で試してみた手元環境では、およそ 10 秒内には公開サーバに同期されて本番サイトで反映確認できました

## 事後対応
### DNS レコードの TTL 設定
* DNS レコードの TTL を短めに設定していたら、元に戻しておきましょう

### 管理サーバ側の Let' Encrypt 再発行
* Let's Encrypt を使っている場合、公開サーバへ移したドメインが証明書に残ったままになっていると、その後の自動更新でコケますので、抜いて再発行しましょう
* 一度消すには以下コマンド

```sh
certbot delete --cert-name xxx.xxx.xxx.xxx
```

# おわりに
## 感想
* rsyncd を用いた手法についての記事も多く見られるため、 rsync サービスが走っていなくてよいのだろうか……？　などと思ったりもしましたが、 sshd だけでイケました
* 一方通行の設定であるため、管理サーバ側で lsyncd がサービス停止してしまっている間に、もしディレクトリ／ファイルに変更が加わっても、同期されないもよう……？
    * その場合、再構築を再度かけ直せば良いということになるのかな
* これで運用始めてみて、何か問題が起きたりしたら本記事へ追記します。追記がなければ運用に問題がないということで……

## 関連して
* さらにこの公開サーバに同期された静的ファイルを GitHub へバックアップするようにしておくと、運用体制は盤石と思います。それについては以下記事を書きました
    * [CMSで出力された静的ファイルをcronで定期的にGitHubへバックアップ](https://qiita.com/Hi_Noguchi/items/21008313fe0acff5a561)

## 参考
* [root権限でrsyncによるファイル転送を行う](https://qiita.com/tkykmw/items/7f1216088b789d0538fb)
* [Ubuntu Server ２台間でファイル同期(lsyncd + rsync + sshd)](https://qiita.com/ohhara_shiojiri/items/128c40cf9b1489e15a0d)
* [Ubuntu: lsyncd によるディレクトリのリアルタイム同期（追記あり）](http://tkrd.hatenablog.com/entry/2018/01/13/205730)