デザイナーでもわかるrsync

## はじめに

この記事は、Webサーバーへのデプロイにrsyncを導入、利用するための手順を共有するためのものです。

rsyncを導入しWebサーバーへのデプロイに利用してみました。
その際Web上で集められた情報は「どうやってrsyncを使うのか」が大半でした。「なぜrsyncを使うのか」という視点の記事があまりありませんでした。

そのため、この記事では

* rsyncのメリットとデメリットを比較し、どのような場合に導入すべきなのか
* rsyncの具体的な導入の手順の解説

の2点を取り上げます。

### 想定する読者

この記事は以下の読者を想定して書かれています。

* macOSユーザー
* デザイナー
* ターミナルが何かはわかる。node.js関係で使うことがあるが習熟してはいない。
* Webサーバーへのデプロイ手段として、rsyncの利用を検討している。

つまり私のようなデザイナーでも、この記事を読めばrsyncの導入ができることを目指しています。

### 環境

この記事は以下の環境を前提に書かれています。

* macOS 10.14.2
* OpenSSH 7.9p1
* Homebrew 1.9.2
* rsync 3.1.3

バージョンやOSが異なる場合、この記事の手順は適用できない場合があります。ご注意ください。

## rsyncとは

[rsync](https://rsync.samba.org/)とはファイルの同期を目的としたユーティリティです。PC上のローカルのファイル同士を同期したり、遠隔地でのファイル同期ができます。
アプリケーションをインストールすることで、さまざまなプラットフォームでrsyncが利用可能です。
遠隔地とローカルのPCの接続はRSHやSSHなどを利用します。

### メリット

#### 高速

ファイル同士の比較と、差分の転送が高速です。

#### セキュア

SSH接続を前提とするので、転送中のファイルは安全に保たれます。

#### 厳密

オプションの指定によって、ファイルのタイムスタンプやパーミッションなどを維持したままの厳密なデプロイができます。

#### 繰り返しの処理に強い

rsyncはコマンドラインで操作をすることが前提となります。
そのため一度コマンドを確定してしまえば、デプロイ時のヒューマンエラーが排除できます。
（rsyncを扱うGUIアプリケーションもありますが、この記事では対象としません。）

### デメリット

#### 導入が難しい

rsyncで遠隔地のWebサーバーへのデプロイを行う場合、前提として

* SSH接続をする
* rsyncのインストール
* rsyncコマンドの試行錯誤

という手順が必要になります。
一度運用方法を確立したあとのランニングコストは低くなりますが、導入コストは高くなります。

#### 運用に知識が必要

rsyncのコマンドは、一部のオプションで破壊的な操作を行います。コマンドを変更し、動作を確認しないで実行するとサーバー側のファイルを消去する可能性があります。
また、ディレクトリ指定の方法にも注意が必要です。たとえばパスの末尾がスラッシュか否かで挙動が変わります。
こうした理由から、コマンドを変更する場合は一定の確認手順が必要です。

### メリットとデメリットの比較

以上のメリットとデメリットを比較すると、rsyncをWebサーバーへのデプロイ手段として導入するべきプロジェクトは、以下のようになります。

* 繰り返しデプロイする必要がある。
* デプロイの頻度が高い。
* 対象ファイルの数が多い。
* ファイルのパーミッションなど詳細なファイル設定を維持したい。

数個のファイルやディレクトリを数回デプロイする程度であれば、GUIクライアントを利用してデプロイしたほうが総工数を抑えられる可能性があります。また、各種タスクランナーにもデプロイ用のプラグインがありますので、そちらを利用することも検討してください。

## 導入

ここからは具体的な導入方法の解説となります。

ここからはターミナルのコマンドを解説する場合、

```bash
コマンド名 [オプション]
```

と[]で括った箇所がオプション指定となります。

### SSH

rsyncを使うために、サーバーとのSSH接続が可能な状態にします。

目標はターミナルで

```bash
ssh [~/.ssh/configファイルで指定したHost名]
```

の指定でサーバーと接続できる状態にすることです。

参考記事
[Connecting to GitHub with SSH](https://help.github.com/articles/connecting-to-github-with-ssh/)

#### 鍵ファイルが存在するかチェック

macOSでは、SSHで利用する鍵ファイルをホームディレクトリの``.ssh``という隠しフォルダーに格納しています。この鍵ファイルがあるかどうかを最初に確認します。

##### コマンドラインで確認

すでに鍵ファイルが存在するかは、ターミナルに以下のコマンドを入力することで確認できます。

```bash
ls -al ~/.ssh
```

`~`はパスの省略記法で、現在ログインしているユーザーのホームディレクトリを指します。たとえば

```bash
~/.ssh
```

は

```bash
/Users/[ログインしているユーザー名]/.ssh
```

と同じディレクトリを指します。

このコマンドを実行すると以下のようなファイルリストが表示されます。

```console
-rw------- 1 username  admin  1743  1 13 17:52 id_rsa
-rw-r--r-- 1 username  admin   381  1 13 17:52 id_rsa.pub
```

``id_rsa``と``id_rsa.pub``が存在する場合は、すでに鍵ファイルが生成されています。

##### Finderで確認

lsコマンドと同様の操作は、Finderでも行えます。
Finderの移動メニューから「フォルダへ移動…」を選択します。

![](https://www.evernote.com/l/AAnnf8R2nbRD36bFuQ8AOQXF2oKLvh3em94B/image.png)

次に移動先を聞かれるので、lsコマンド同様``~/.ssh/``を指定します。

![](https://www.evernote.com/l/AAmsJsz6kE1AXL5zuJ7Z3sLBj1xHz45DZUwB/image.png)

Finderのウィンドウが開き、フォルダーが表示されます。このフォルダーの中に``id_rsa``と``id_rsa.pub``が存在するかどうかを確認します。

``id_rsa``と``id_rsa.pub``が存在する場合は、ssh-keygenでの鍵ファイル生成は必要ありませんので、次の手順はスキップしてください。

#### ssh-keygenで鍵ファイルを生成する

以下のコマンドを実行すると、鍵ファイルの生成が始まります。

```bash
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
```

``-C``オプションはコメントです。GitHubでは``-C``に登録時のメールアドレスを記載することを推奨しています。このオプションは省略が可能です。

次に鍵ファイルの保存先を聞かれます。

```bash
Enter a file in which to save the key (/Users/you/.ssh/id_rsa): [Press enter]
```

Enterを押してデフォルトの``~/.ssh/id_rsa``に保存します。

次にパスフレーズを入力します。

```bash
Enter passphrase (empty for no passphrase): [Type a passphrase]
Enter same passphrase again: [Type passphrase again]
```

パスフレーズの入力中はタイプ結果がターミナルに表示されません。入力は受け付けられていますので気にせずタイプしてください。

以上で``~/.ssh/id_rsa``と``~/.ssh/id_rsa.pub``が生成されます。

#### サーバーに公開鍵を登録する

次に生成した公開鍵``~/.ssh/id_rsa.pub``の情報を登録します。

```bash
pbcopy < ~/.ssh/id_rsa.pub
```
これでクリップボードに公開鍵``~/.ssh/id_rsa.pub``の内容がコピーされます。
テキストエディットで公開鍵``~/.ssh/id_rsa.pub``を開いて、コピーすることもできます。その際は操作ミスで内容を書き換えてしまわないように注意してください。

サーバー側の公開鍵の登録方法は、サービスによって変わります。ターミナルで接続して公開鍵ファイルを転送する場合もあれば、ブラウザからGUI経由で設定する場合もあります。ご利用中のサーバーの登録方法を確認してください。以下に例としてさくらのVPS、XServerとGitHubの登録方法を挙げます。

- [さくらのVPS SSH接続の設定変更方法](https://help.sakura.ad.jp/hc/ja/articles/206208161-SSH%E6%8E%A5%E7%B6%9A%E3%81%AE%E8%A8%AD%E5%AE%9A%E5%A4%89%E6%9B%B4%E6%96%B9%E6%B3%95)
- [XServer SSH設定](https://www.xserver.ne.jp/manual/man_server_ssh.php#link-c02)
- [Adding a new SSH key to your GitHub account](https://help.github.com/articles/adding-a-new-ssh-key-to-your-github-account/)

公開鍵は複数登録できます。複数人で別々の鍵を持ち、同一のサーバーにそれぞれの鍵で接続することも可能です。公開鍵の追加はGUIで操作が可能なサービスもあれば、ターミナルから追加する場合もあります。ターミナルからの追加方法はこちらの記事をご参照ください。

[Linuxユーザーの追加〜SSH公開鍵登録まで](https://qiita.com/soushiy/items/fabe64ec382a007a753c#31-authorized_keys%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E4%BD%9C%E6%88%90%E3%81%97%E9%96%8B%E3%81%8D%E3%81%BE%E3%81%99)

#### SSH接続のテスト

この状態で、最低限のSSH接続が可能になりました。

```bash
ssh -l [ユーザー名] -p [ポート番号] -i ~/.ssh/id_rsa [サーバーのドメイン]
```
-iオプションは接続に利用する鍵ファイルを指定するオプションです。デフォルトで``~/.ssh/id_rsa``を指しますので、今回の例では省略可能です。

これで接続に成功すると、初回接続時はフィンガープリントの確認が行われます。

```console
The authenticity of host 'github.com (207.97.227.239)' can't be established.
# RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48.
# Are you sure you want to continue connecting (yes/no)?
```

表示されたフィンガープリントが一致するか、サーバーが一般公開しているフィンガープリントと比較してください。例として[GitHubのフィンガープリント](https://help.github.com/articles/github-s-ssh-key-fingerprints/)はこのように公開されています。
一致した場合は正しいサーバーに接続していますので、yesをタイプしてください。
2回目以降にはこの確認は表示されません。

これでSSH接続が可能になりましたが、毎回接続情報をタイプするのは非効率ですし、ミスの原因となります。次に接続情報をOSに覚えさせます。

##### 接続失敗した場合

SSH接続時に、以下のエラーが出たら秘密鍵ファイルのパーミッションを確認してください。

```console
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '~/.ssh/id_rsa' are too open.
It is recommended that your private key files are NOT accessible by others.
This private key will be ignored.
bad permissions: ignore key: ~/.ssh/id_rsa
Permission denied (publickey).
fatal: The remote end hung up unexpectedly
```

このエラーは、秘密鍵ファイルのパーミッションが過剰な場合に起こります。
秘密鍵ファイルは広範囲の権限があると操作ミスによって破壊されたり、攻撃によって改ざんされてしまう恐れがあります。
そのためid_rsaのパーミッションを

* 400（所有者のみ読み取り専用）
* 600（所有者のみ読み書き）

のどちらかに変更すれば接続に成功します。

```bash
chmod 600 ~/.ssh/id_rsa
```

このエラーはssh-keygenで鍵ファイルを生成した場合は起きません。
次のような理由で、鍵ファイルをコピーした場合にパーミッションが変化してしまうのが原因です。

* 旧マシンから鍵ファイルを取り出してコピーした。
* WEBサービス側で鍵ファイルを生成してダウンロードした。

参考記事
[ssh “permissions are too open” error](https://stackoverflow.com/questions/9270734/ssh-permissions-are-too-open-error)

#### ~/.ssh/configファイルの作成と編集

SSHコマンドは設定ファイル``~/.ssh/config``を参照します。このファイルに記述がある場合、SSHコマンドのオプションを省略できます。このconfigファイルの作成と編集を行います。

```bash
ls -al ~/.ssh

...
-rw-r--r--@  1 username  admin   247  1 13 17:52 config
```

最初に``~/.ssh``ディレクトリのファイルリストを確認します。上記の例ではlsコマンドでファイルの確認をしていますが、Finderで確認することもできます。configファイルが存在する場合はそれを編集します。ない場合はテキストファイルを新規に作成します。
configファイルの形式はプレーンテキストです。macOSの場合はテキストエディットで作成と編集が可能です。

configファイルの内容は以下のようになります。

```console
Host *
 AddKeysToAgent yes
 UseKeychain yes
 IdentityFile ~/.ssh/id_rsa
 
Host [任意の名前]
 HostName      [サーバーのIPアドレスかドメイン]
 IdentityFile  [デフォルト以外の鍵ファイルを利用する場合はファイルパス 省略可]
 User [ユーザー名]
 Port [デフォルトの22以外のポートを利用する場合に記述 省略可]
```

``Host *``には共通で利用される設定を記述します。``AddKeysToAgent``と``UseKeychain``オプションを利用するとmacOSのキーチェーンをSSH接続に利用できます。
``Host [任意の名前]``には個別のサーバーの設定を記述します。SSHコマンドのオプションとして設定していた項目をconfigファイル内に記述でき、任意のHost名で呼び出すことができます。

この設定が完了すると

```bash
ssh [~/.ssh/configファイルで指定したHost名]
```
のみでssh接続ができるようになります。

### rsync

デプロイ先のサーバーとSSH接続が可能になりましたので、ここからrsyncの導入を行なっていきます。

#### インストール

macOSにはrsyncが標準搭載されていますが、最新版ではありません。利用開始前にrsyncのアップグレードを行います。

##### Homebrewのインストール

rsyncの最新版をインストールするために、macOS用のパッケージマネージャー[Homebrew](https://brew.sh/index_ja)を導入します。

インストールスクリプトを実行します。

```bash
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
```

次にインストールが成功したか確認します。

```bash
brew -v
```

バージョン番号が表示されれば成功です。

```bash
Homebrew 1.9.2
Homebrew/homebrew-core (git revision 78493; last commit 2019-01-14)
```

##### rsync最新版のインストール

Homebrewを使って最新版のrsyncをインストールします。

```bash
brew install rsync
```

この状態ではインストールが行われただけで、まだ最新版のrsyncが動いていません。

##### ターミナルを再起動

インストールを行ったら、ターミナルをいったん終了して再起動します。
再起動後、バージョン確認のコマンドを入力し

```bash
rsync --version
```

最新バージョンになっていればインストールは成功です。

```bash
rsync  version 3.1.3  protocol version 31
```

### コマンドの作成と検討

これでrsyncの利用ができるようになりました。次にrsyncコマンドのオプションを検討します。

#### なにはなくとも -nv

まず、rsyncを利用する前にこのオプションを覚えてください。

```bash
rsync -nv
```

- ``-n``(--dry-run)オプションは実際に処理を行わず、テスト結果だけを出力するオプションです。
- ``-v``(--verbose)オプションは処理中のログをターミナルに表示するオプションです。
- ``-``オプションは繋げて記述できるので、この2つを合わせて``-nv``としています。

この2つのオプションを有効にすることで、実際に処理を行わずにテスト結果だけを表示できます。
この状態でコマンドの検討を行なっていきます。

#### ソースと同期先の設定

ターミナルでデプロイするプロジェクトのルートディレクトリに移動します。
rsyncのオプションは、ターミナルのカレントディレクトリを基準にして動きます。

```bash
cd [プロジェクトのディレクトリ]
```

rsyncのソース、同期先指定は以下のような書式で指定します。

```bash
rsync -nv [ソース] [~/.ssh/configファイルで指定したHost名]:[同期先ディレクトリ]"
```

ここで十分なテストを行い、期待通りの動作をすることを確認してくだい。

##### ソースパスの末尾スラッシュ

ここで重要なのが、ソースパスの末尾のスラッシュの有無です。rsyncではこの**末尾スラッシュの有無で挙動が変化**します。

[rsync --delete で泣かないために](https://qiita.com/QUANON/items/2953c52df7f65f2ecee5#%E3%81%AA%E3%81%9C%E3%83%9E%E3%83%9F%E3%81%95%E3%82%93%E3%81%AF%E6%AD%BB%E3%82%93%E3%81%A0%E3%81%AE%E3%81%8B)

同期先ディレクトリのパスの末尾のスラッシュは挙動に影響を与えません。

具体的には

* スラッシュがある : 同期対象はディレクトリの内部のファイル一式
* スラッシュがない : 同期対象はディレクトリそのもの

となります。

つまり
`./path/to/src/` = `./path/to/src/*`
という関係になります。

個人的には、この違いは直感的に理解しづらいため、ソースパス側の末尾は必ず`/*`で終わらせています。明示的にアスタリスクをつけることで、対象がファイルだとわかりやすくなるためです。皆様もどのようなルールにするのかご検討ください。

#### オプションの設定

パスが確定したら、次はオプションを追加します。

参考記事
[rsyncのサンプル例集](https://qiita.com/nagais/items/7403411b4aa193d0fa0d)

Webサーバーへのデプロイに必要なオプションは

* ``-a`` アーカイブモード。さまざまなオプションをまとめて有効化し、厳密に同期させます。
* ``-r`` ソース以下のディレクトリも再帰的に同期します。
* ``-p`` パーミッション情報を維持します。

くらいです。
``-va``か``-vr``を指定すれば大半のデプロイの要件を満たせます。
`-r`および`-p`オプションは、`-a`オプションに含まれます

パスとオプションを設定したコマンドは、例として以下のような形になります。

```bash
rsync -vr ./src/* HostName:path/to/dist/
```

### npm scriptsからの呼び出し

ここまででrsyncのコマンドのテストが終わりました。
このコマンドを繰り返し実行するために、ファイルに記録しておくことをオススメします。
Web開発を行うならnpmのpackage.jsonに記録しておくのが簡単です。

*▼package.json*

```json
  "scripts": {
    "rsync": "rsync -vr ./src/* HostName:path/to/dist/",
  }
```

この状態で

```bash
npm run rsync
```

とスクリプトを実行すれば人為的なミスを減らすことができます。

認証情報は``~/.ssh/config``とその中に設定されている鍵ファイルに隔離されています。
そのためpackage.jsonを開発メンバーで共有しても漏洩の心配はありません。また、``~/.ssh/config``をそれぞれのメンバーが書き換えることで個別の設定を持たせることも容易です。

以上、ありがとうございました。