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


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

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

はじめに

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

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

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

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

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

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

コアはgulpと同じ

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

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

Coffeeでも書ける

公式に何も記載がないですが、Liftoffが使われているため、CoffeeほかAltJSで書けます。設定ファイル的なものは、Coffeeが書きやすいので(主観)、以下Coffeeで行きます。

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

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

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

Shipitを使ってみる

shipitfile

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

  • JavaScriptの場合: shipitfile.js
  • CoffeeScriptの場合: shipitfile.coffee
# shipitfile.coffee
module.exports = (shipit) ->
  shipit.initConfig
    staging:
        servers: '[email protected]'
    production:
        servers: '[email protected]'
        key:     'path/to/ssh_key'

  shipit.task 'pwd', ->
    shipit.remote 'pwd'

コマンドの実行

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

$ npm install --global shipit-cli

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

$ 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メソッドで設定します。任意の名前で、設定情報を記述することができ、例えばstagingproductionサーバがあるなら、前述のようになるでしょう。最低限必要なのは、serversプロパティで、ここに接続先情報を記載します。以下、接続情報の例。

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

パスワード設定は?

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

default

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

shipit.initConfig
  default:
    something: 'something'
    other: 'other'
  staging:
    servers: '[email protected]'

タスクの定義

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

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

shipit.task 'task_with_callback', (callback) ->
  # なにかの作業
  callback()

設定情報へのアクセス

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

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

ビルトイン機能

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

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

options

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

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

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

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

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

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が実行されています。

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

shipit-deployを使う

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

$ npm install --save-dev shipit-cli

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

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: '[email protected]'

デプロイ実行

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

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

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

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

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

ロールバック

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

$ shipit staging rollback

npmタスクとして実行

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

  • npmscriptsに登録したり
  • NodeのAPIから使う

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

$ npm install --save-dev shipit-cli

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

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

まとめ

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

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

Flightplanとの比較記事に載っていたレシピも参考になります。