Shipitではじめる、お手軽デプロイ



[Shipit](https://github.com/shipitjs/shipit)はgulp/Gruntの影響を強く受けたデプロイツールで、なかなか便利なんですが、ドキュメントが貧弱すぎて、手を出しにくいのが玉に瑕。日本語の記事はもしかすると初かも? ここでは、最近触ってみた範囲でまとめてみたいと思います。([Ghost](https://ghost.org/)が社内用ツールとして使っているという噂ですが、情報求む)

- 2015/6/22: shipit-deployについて追記して、章立てを調整

![SHIPITロゴ](https://qiita-image-store.s3.amazonaws.com/0/1858/cdc5a39e-86e2-5ac9-eb2a-632aff2b0c38.png "81df9f46-182e-11e4-9da6-b2c7a6b84136.png")

## はじめに

各言語デプロイツールが出揃った感がありますが、JavaScriptにはまだ定番と言えるほどのツールは登場していません。[Flightplan](https://github.com/pstadler/flightplan)か、このShipitを気にしつつ、業務ではCapistranoかFabricという方が多いんじゃないでしょうか?

- [Capistrano](http://capistranorb.com/) (Ruby)
- [Fabric](http://www.fabfile.org/) (Python)
- [Rocketeer](http://rocketeer.autopergamene.eu/) (PHP)

とは言っても、デプロイのためだけに他言語を使うのも気が重い話。個人的には、GitHub Pages程度であれば[gulp](http://gulpjs.com/)でデプロイしてしまったり、[Ansible](http://www.ansible.com/home)でゴニョゴニョというケースもありますが、その辺をShipitに置き換えていきたい所存です。

### デプロイツールの基本は何か?

実運用上はいろいろありますが、一番の基本は「サーバサイドでのコマンド実行の自動化」です。というか、Shipitの基本機能としては、次のふたつしかできることはありません。

- リモートにSSHで接続してコマンド実行
- ローカルからリモートへのファイルコピー

ここに、プラグインを追加していくことで、各種作業を自動化する、という構成になっています。今のところ、`shipit-deploy`だけが公式で、ほかのプラグインはまだ実験的な段階のように見えます。(このあたりが充実して来ると、エコシステムができそうですが...)

- [shipit-deploy](https://github.com/shipitjs/shipit-deploy) (公式)
- [shipit-db](https://github.com/timkelty/shipit-db): 今のところMySQLのみ対応
- [shipit-shared](https://github.com/timkelty/shipit-shared): Capistranoでいうところの、linked_files/linked_dirs
- [shipit-npm](https://github.com/callerc1/shipit-npm): リモートでのnpmの実行
- [shipit-assets](https://github.com/timkelty/shipit-assets): アセットの同期

### コアはgulpと同じ

Shipitのタスクランナー部分は、gulpのコアと同じ[orchestrator](https://github.com/orchestrator/orchestrator)なので、書き方もかなりgulpに近いものになっています。(↓タスク定義の例)

```javascript
shipit.task('hello', function(){
  console.log('Hello!')
})
```

### Coffeeでも書ける

公式に何も記載がないですが、[Liftoff](https://github.com/tkellen/js-liftoff)が使われているため、CoffeeほかAltJSで書けます。設定ファイル的なものは、Coffeeが書きやすいので(主観)、以下Coffeeで行きます。

```coffeescript
shipit.task 'hello', ->
  console.log 'Hello!'	
```

### サーバサイドのインストール不要

サーバサイドで実行しているのは、ただのシェルコマンドなので、Nodeのインストール前でも使えます。

## Shipitを使ってみる

### shipitfile

設定ファイルは、gulpと同様にスクリプトファイルですが、CommonJSでエクスポートしている点や、`shipit.initConfig`で設定の初期化をするなど、Shipit独自の部分もあります。ファイル名は、shipitfile + 拡張子です。

- JavaScriptの場合: `shipitfile.js`
- CoffeeScriptの場合: `shipitfile.coffee`

```coffeescript
# shipitfile.coffee
module.exports = (shipit) ->
  shipit.initConfig
    staging:
    	servers: 'username@192.168.0.100'
    production:
    	servers: 'username@192.168.0.200'
    	key:     'path/to/ssh_key'
    
  shipit.task 'pwd', ->
    shipit.remote 'pwd'
```

### コマンドの実行

Shipitのタスクは、コマンドラインから実行します。次のコマンドで、グローバルに`shipit-cli`をインストールしておきましょう。(グローバルに入れない方法は後述)

```bash
$ npm install --global shipit-cli
```

サーバの接続情報が正しければ、次のようなコマンドでタスクを実行できるようになります。

```bash
$ shipit staging pwd
```

実際に実行すると、こんな結果が表示されます。gulpっぽいですね。(こればっか w)

```
Running 'pwd' task...
Running "pwd" on host "192.168.0.100".
@192.168.0.100 /home/username
Finished 'pwd' after 811 ms
```

### デプロイ先の設定

上述のshipitfileのように、`shipit.initConfig`メソッドで設定します。任意の名前で、設定情報を記述することができ、例えば`staging`と`production`サーバがあるなら、前述のようになるでしょう。最低限必要なのは、`servers`プロパティで、ここに接続先情報を記載します。以下、接続情報の例。

- `username@192.168.0.100`: サーバのIPアドレスが`192.168.0.100`で、接続ユーザは`username`
- `192.168.0.100`: サーバのIPアドレスが`192.168.0.100`で、接続ユーザはデフォルトの`deploy`
- `username@myserver.dev`: サーバのドメインが`myserver.dev`で、接続ユーザは`username`

#### パスワード設定は?

ないです、そんなの (爆)。パスワード認証でサーバ管理とかしちゃダメですってことですね。デフォルトでは、手元のシェルの鍵が使われます。プロダクションサーバなどで、普段の鍵と違うものを使う必要がある場合は、`key`プロパティを使います。

#### default

複数のサーバで共通のプロパティを設定する場合は`default`が使えます。shipit-deployプラグインなどを使う場合に役に立ちます。

```coffeescript
shipit.initConfig
  default:
    something: 'something'
    other: 'other'
  staging:
    servers: 'username@192.168.0.100'
```

### タスクの定義

だいたい、gulpだと思ってください。`shipit.task`メソッドでタスクを登録します。Promiseを返すか、コールバックを指定できます。

```coffeescript
shipit.task 'task_with_promise', ->
  new Promise (resolve, reject) ->
  	# なにかの作業
  	
shipit.task 'task_with_callback', (callback) ->
  # なにかの作業
  callback()
```

### 設定情報へのアクセス

`shipit.config`でアクセスできます。実行中のサーバ情報が欲しければ、次のように`shipit.config.servers`を参照します。

```coffeescript
shipit.task 'server', ->
  console.log shipit.config.servers
```

## ビルトイン機能

### ローカルでのコマンド実行: shipit.local(command[, options])

ローカルのシェルでコマンドを実行する際、gulpだと[gulp-shell](https://github.com/sun-zheng-an/gulp-shell)などを使うのが一般的ですが、Shipitにはそのための機能があらかじめ用意されています。サーバサイドでの実行と同じ書き方ができるので、コードが読みやすくなるのはメリットです。

#### options

- cwd: 'path/to/dir' # カレントディレクトリの指定


### サーバサイドでのコマンド実行: shipit.remote(command[, options])

`shipit.remote`はPromiseを返します。つまりthenableなので、`then`でシリアルな処理をつなげていくことができます。

```coffeescript
shipit.task 'install', ->
  shipit.remote 'sudo yum -y update'
  .then -> shipit.remote 'sudo yum install gcc-c++ make'
```

これだけであればコマンドを`&&`でつなげたのとあまり変わりありませんが、間にログを出したりとかもできます。

```coffeescript
shipit.task 'install', ->
  shipit.remote 'sudo yum -y update'
  .then (res) -> console.log 'YUMの更新完了!'
  .then -> shipit.remote 'sudo yum install gcc-c++ make'
  .then (res) -> console.log 'パッケージインストールDONE!'
```

`res`には実行結果のオブジェクトが渡されるので、その内容に応じて処理を変えるのもありです。

一点、注意事項として、ローカルでは使える`cwd`オプションが、リモートでは使えません。`cd /path/to/dir && your_command`する必要があります。(ver 2.0では実装予定らしい)


### ファイルコピー: shipit.remoteCopy

ローカルからリモートへファイルをコピーします。`git`などを絡めないなら、これが一番簡単なデプロイ方法です。裏側では、`rsync`が実行されています。

```coffeescript
shipit.task 'copy', ->
  shipit.remoteCopy '/local/workspace', '/remote/myapp'
  .then ->
  	# コピー完了後に実行する処理
```

## shipit-deployを使う

デプロイ作業(git/rsync)を自動化する公式プラグインです。npmからインストールしましょう。

```bash
$ npm install --save-dev shipit-cli
```

設定に次のように書き足しておきます。

```coffeescript
shipit.initConfig
  default:
    workspace:     '/tmp/github-monitor'
    deployTo:      '/var/www'
    repositoryUrl: 'https://github.com/username/something.git'
    branch:        'master'
    ignores:       ['.git', 'node_modules']
    rsync:         ['--del']
    keepReleases:  5
    shallowClone:  true
  staging:
    servers: 'username@192.168.0.100'
```

### デプロイ実行

ここで、次のコマンドを発行すると、一連のコマンドが実行されます。

```bash
$ shipit staging deploy
```

サブタスク | 詳細 | 完了イベント
:-- | :-- | :--
`deploy:init` | - | `deploy` 
`deploy:fetch` | ローカルの作業領域の確保、リポジトリ初期化、ソースを指定ブランチから取得 | `fetched`
`deploy:update` | リモートに領域を確保、ファイルをローカルからコピー | `updated`
`deploy:publish` | `current`のsymlinkを更新| `published`
`deploy:clean` | 古いリリースを削除 | `cleaned`

### イベントを検知して、処理を実行

例として、リモートでビルドコマンドを走らせるなら、次のように書けます。

```coffeescript
shipit.on 'updated', ->
  dir = path.join shipit.releasesPath, shipit.releaseDirname
  shipit.remote "cd #{ dir } && npm i && npm run build"
```

### ロールバック

ひとつ前のリリースに戻すには、`rollback`タスクが使えます。

```bash
$ shipit staging rollback
```

## npmタスクとして実行

ここまでの説明は、`shipit-cli`をグローバルに入れた前提で説明していましたが、

- `npm`の`scripts`に登録したり
- NodeのAPIから使う

のであれば、グローバルインストールは不要です。その場合、

```bash
$ npm install --save-dev shipit-cli
```

した上で、`package.json`内に次のような記述をすれば、`npm run deploy`で実行できるようになります。チームのメンバーと共有する場合には、こちらの方が勝手が良いかもしれません。

```json
"scripts": {
  "deploy": "shiit staging deploy"
}
```


## まとめ

簡単な手順であれば、Shipitが使えるシーンは多いでしょう。もちろん、ChefやAnsibleのように冪等性を担保したりとかはないので、シェルでごりごり書くのとあまり変わりありませんが、JavaScriptだけで完結する点、`gulp`的にかける点は魅力です。

Flightplanが「単体で何でもやる」設計になっているのに対して、Shipitはコア機能は極力減らしてデプロイ部分をプラグイン化しているのが特徴です。ちなみに、Gruntのプラグインとしても動作するほか、タスク自体はgulpと共通なので、gulpの中から呼び出すことも簡単そうです。(まだ試してないですが)

[Flightplanとの比較記事](http://johnmunsch.com/2015/03/08/shipit-vs-flightplan-for-automated-administration/)に載っていた[レシピ](https://github.com/JohnMunsch/PaperQuik/blob/master/shipitfile.js)も参考になります。