Fabric を使ったデプロイフレームワークの開発

<!-- Fabric を使ったデプロイフレームワークの開発 -->

# Fabric とは

Fabric はリモートマシンでのシェルコマンド実行を Python で実現するためのライブラリです。

## 背景

ナビタイムではリモートマシンでシェルコマンドを実行する時はシェルスクリプト上で `ssh` を使用していました。しかしデプロイを自動化するという上で下記のような要件が出てきたときにシェルスクリプトでは実現が難しいと感じるようになりました。

- 複数マシンに同時にデプロイしたい
- デプロイ先のマシンごとに設定を変えたい
- コードを適切にモジュール分割したい
- オブジェクト指向に実装したい
- デバッグ実行したい

シェルスクリプトでこれらを実現するには機能が不足していたため、代わりに Python を使って実現できないかと考えるようになりました。こういうときに Fabric を使うと Python コードで上記のような要件を満たした上でシェルコマンドの実行ができるようになります。

## 試した環境

- CentOS 7
- Fabric 2.3.1

### 注意

Fabric のバージョンには次の通り複数あります。

| バージョン  | 対応 Python | 説明                  |
|-------------|-------------|-----------------------|
| Fabric 1 系 | Python 2    | 公式バージョン        |
| Fabric 2 系 | Python 2/3  | 公式バージョン        |
| Fabric3     | Python 2/3  | Fabric 1 系の fork 版 |

Fabric 1 系がいつまでも Python 3 に対応しないことで Fabric3 という Python 3 に対応した fork 版が登場しました。その後、 Python 3 にも対応した Fabric 2 系が公式にリリースされました。

Fabric 1 系と 2 系には互換性がありませんが、 2 系のほうが利便性がかなり向上しているため 2 系を使用することをおすすめします。

## インストール

`pip` でインストールできますが、最近は [pipenv] を使うのが主流になりつつあるので `pipenv` でインストールします。

```bash
$ pipenv install fabric
```

[pipenv]: https://pipenv.readthedocs.io/en/latest/

## 使い方

公式サイトの [チュートリアル] のコードを実行してみます。

```python
>>> from fabric import Connection
>>> c = Connection('web1')         # ホスト名が web1 のサーバに接続する
>>> result = c.run('uname -s')     # web1 上で 'uname -s' コマンドを実行する
Linux
>>> result.extied                  # コマンドの終了コード
0
>>> result.ok                      # コマンドが正しく終了したかどうか (exited == 0 と同じ)
True
>>> result.command                 # 実行したコマンド
'uname -s'
>>> result.connection              # Connection インスタンスの情報
<Connection host=web1>
>>> result.connection.host         # 接続先のホスト
'web1'
```

リモート接続からシェルコマンドの実行に関する情報がオブジェクトによって管理されていることが分かります。

[チュートリアル]: http://docs.fabfile.org/en/2.4/getting-started.html

## デプロイをフレームワーク化する

Fabric の機能自体は [使い方] で示したとおり至ってシンプルです。シェルコマンドが Python 経由で実行できるだけで、シェルスクリプトでは難しかったデバッグやオブジェクト指向による開発が可能になります。

しかし、デプロイの手順を Fabric で実現する場合これだけでは不十分です。どんなモジュールであれ、デプロイの手順はある程度統一でき、その同じような手順の実装を各モジュールの担当者に任せてしまうと似たようなコードが増えてしまいメンテ効率が下がってしまうためです。

そこでナビタイムではデプロイを実現するためのフレームワークを Fabric を用いて開発しました。開発時には Ruby 製のデプロイフレームワークである [Capistrano] を参考にしました。

### フレームワークのタスクフロー

フレームワークではデプロイの手順を次のように細分化し、それぞれのタスク間で個別のフックを実行できるようにしています。

```
- starting:   デプロイ先のディレクトリを作成
- started:    starting 後のフック
- updating:   ファイルをアップロードする
- updated:    updating 後のフック
- publishing: アップロードしたファイルの有効化
- published:  publishing 後のフック
- finishing:  不要なバックアップの削除
- finished:   finishing 後のフック
```

これらを実現するためのクラスを定義しておきます。デプロイ時にはこれらのタスクが順に実行されるようになっており、特別な処理をしたいときにはクラスを継承してフック関数を実装すれば良いというような構造になっています。

### デプロイ先のディレクトリ構成

デプロイ先のディレクトリはデプロイした時間ごとにファイルを管理するように作っているため、デプロイしたファイルがどんなものだったのかを後で確認しやすい構成になっています。

```
<デプロイ先ディレクトリ>
├── current -> releases/20171201125925  # 最新のファイルを指すシンボリックリンク
├── releases                            # デプロイしたファイルが日付ごとに管理される
│   ├── 20171201124246
│   ├── 20171201125802
│   ├── 20171201125844
│   └── 20171201125925
└── shared                              # 設定ファイルなど毎回共通して使うファイル
```

[使い方]: #使い方
[Capistrano]: https://capistranorb.com/

こうしてコードの共通化ができたことで、個別のデプロイコードはだいぶ軽量になりました。

## まとめ

シェルスクリプトで行っていた処理が Python になるとだいぶ柔軟性が増すので、ぜひ Fabric をお試し下さい。

## 参考

- [Fabric] - 公式

[Fabric]: http://www.fabfile.org/