戦略
- Railsのことは忘れる(そういうこともあるよ)
- capをサーバーにsshでコマンドを送り込むrake taskの塊と見る
- 「とある役割のサーバー」に対して「とある処理」を実行して回りたい、という場合を想定
- 単にrestartだけ切り出して実行するとか、あるコマンドの結果だけ手に入れるとか
- 個人的にはansibleでもできると思ったけどrubyメンバーに強要できないし、小回り効かせたい
準備
$ bundle init
$ echo gem "capistrano" >> Gemfile
$ bundle install
$ cap install
ちなみにversionは、
$ cap -V
Capistrano Version: 3.2.1 (Rake Version: 10.3.2)
ここまでやると以下のようなディレクトリ構成になる
$ tree
.
├── Capfile
├── Gemfile
├── Gemfile.lock
├── config
│ ├── deploy
│ │ ├── production.rb
│ │ └── staging.rb
│ └── deploy.rb
└── lib
└── capistrano
└── tasks
ちょっとした解説
- Capfileはrequireなどの設定とlib以下のtaskの読み込みに使う
- config/deploy.rbには全ステージ共通の設定を書く
- production.rbとかstating.rbに各ステージの設定を書く
- サーバーのIPや、専用taskとか
- lib/capistrano/tasksの下にはオリジナルのtaskを追加したいときに使う
deploy.rbを最低限の設定にする
lock '3.2.1'
# set :pty, true
本当に全部消してとりまこれだけにしてみた。
※後述するけどsudo使いたいならptyを有効にする必要がある
ステージを増やす
今回試す場所が開発サーバーなので、dev.rbをはやしてみる。developmentでもいいと思う。
└─- config
└── deploy
├── production.rb
├── staging.rb
└── dev.rb
role :app, %w{hoge@example.com}
これは以下のようにステージを指定できることを意味する。
$ cap dev YOUR_TASK_NAME
オリジナルタスクを追加してみる
ミニマムで試すなら、そのままdev.rbに書いてしまう。
role :app, %w{hoge@example.com}
task :ls do
on roles(:app) do
execute "ls"
end
end
これで、
-
hoge@example.comでログインできるappという役割のサーバーを定義
- (複数書くならスペース区切りで増やせばいい)
- appに対してのみlsを実行する
:ls
というタスクを定義
したことになる。
実行してみる。
$ bundle exec cap dev ls
INFO[6a60919c] Running /usr/bin/env ls on example.com
DEBUG[6a60919c] Command: /usr/bin/env ls
DEBUG[6a60919c] <<ここにファイル一覧が表示される>>
INFO[6a60919c] Finished in 1.851 seconds with exit status 0 (successful).
タスクを増やすならnamespaceを付けた方があとあと見通しが良くなる。
namespace :utils do
task :ls do
on roles(:app) do
execute "ls"
end
end
task :perl_version do
on roles(:app) do
execute %[perl -v]
end
end
end
実行してみる。namespace:task_name
というように記述する。
$ bundle exec cap dev utils:perl_version
INFO[444094bb] Running /usr/bin/env perl -v on +HFOWFLWKEJF+WLEHF
DEBUG[444094bb] Command: perl -v
DEBUG[444094bb]
DEBUG[444094bb] This is perl, v5.10.1 (*) built for x86_64-linux-thread-multi
DEBUG[444094bb]
DEBUG[444094bb] Copyright 1987-2009, Larry Wall
DEBUG[444094bb]
DEBUG[444094bb] Perl may be copied only under the terms of either the Artistic License or the
DEBUG[444094bb] GNU General Public License, which may be found in the Perl 5 source kit.
DEBUG[444094bb]
DEBUG[444094bb] Complete documentation for Perl, including FAQ lists, should be found on
DEBUG[444094bb] this system using "man perl" or "perldoc perl". If you have access to the
DEBUG[444094bb] Internet, point your browser at http://www.perl.org/, the Perl Home Page.
DEBUG[444094bb]
INFO[444094bb] Finished in 1.874 seconds with exit status 0 (successful).
決まった塊でlib以下に移す
大きくなったらlib/capistrano/tasksに移せばいい
lib/capistrano/tasks/utils.rake
というのをつくってみる。
namespace :utils do
task :ls do
on roles(:app) do
execute "ls"
end
end
task :perl_version do
on roles(:app) do
execute %[perl -v]
end
end
end
namespaceのブロックをまるごとコピペでいい。実行のやりかたも変わらない。
$ bundle exec cap dev utils:perl_version
INFO[444094bb] Running /usr/bin/env perl -v on LHFWLKEFJWEFJ
DEBUG[444094bb] Command: perl -v
DEBUG[444094bb]
DEBUG[444094bb] This is perl, v5.10.1 (*) built for x86_64-linux-thread-multi
DEBUG[444094bb]
DEBUG[444094bb] Copyright 1987-2009, Larry Wall
DEBUG[444094bb]
DEBUG[444094bb] Perl may be copied only under the terms of either the Artistic License or the
DEBUG[444094bb] GNU General Public License, which may be found in the Perl 5 source kit.
DEBUG[444094bb]
DEBUG[444094bb] Complete documentation for Perl, including FAQ lists, should be found on
DEBUG[444094bb] this system using "man perl" or "perldoc perl". If you have access to the
DEBUG[444094bb] Internet, point your browser at http://www.perl.org/, the Perl Home Page.
DEBUG[444094bb]
INFO[444094bb] Finished in 1.874 seconds with exit status 0 (successful).
これでdev.rbの中にはサーバーの情報だけ記述されるだけになるので、シンプルになる。
環境変数を渡してコマンドを実行する
bundle exec cap dev utils:echo user_name=kazuph
とかやって、外から自由に値を渡せる。
...
task :echo do
set :user_name, ENV["user_name"] || raise("ユーザー名必須")
on roles(:app) do
execute "echo #{fetch(:user_name)}"
end
end
ENVで受け取って、setすればfetchで取れる(使わないでそのままENVつっこんでもいい)。
実行時にユーザーに入力させる
対話式で入力させてもいい。
...
task :echo do
ask(:user_name, nil) ## ここで聞いてる
on roles(:app) do
execute "echo #{fetch(:user_name) || raise("ユーザー名は必須だよん☆")}"
end
end
実行する。
$ bundle exec cap dev utils:echo
Please enter user_name (): kazuph
INFO[62046886] Running /usr/bin/env echo kazuph on example.com
DEBUG[62046886] Command: echo kazuph
INFO[62046886] Finished in 2.086 seconds with exit status 0 (successful).
DEBUG[62046886] kazuph
INFO[62046886] Finished in 2.086 seconds with exit status 0 (successful).
askのデフォ値を第二引数でnilにすることによって、fetchの返り値がnilなので、raiseするという仕組み。もっといい方法があったら教えてください。
他のタスクをまとめて実行する
invokeでできる。namespaceは必須。
restartとかがわかりやすいですかね。
...
task :restart do
on roles(:app) do
invoke "utils:restart_nginx"
invoke "utils:restart_app"
invoke "utils:restart_mysql"
end
end
task :restart_nginx do
on roles(:app) do
execute "sudo /etc/init.d/nginx restart"
end
end
task :restart_app do
on roles(:app) do
execute "sudo supervisorctl restart all"
end
end
task :restart_mysql do
on roles(:app) do
execute "sudo /etc/init.d/mysql restart"
end
end
※ちなみにsudoが失敗するときのことは後述
連鎖的にタスクを実行する
◯◯の処理のあとに◯◯、みたいなとき。
:add_user
のあとに:enable_ssh_login
をやってるけど、:enable_ssh_login
も単体で実行することを考慮するとこうなる。
task :add_user do
fetch(:user_name) || ask(:user_name, nil)
on roles(:app) do
user_name = fetch(:user_name) || raise("ユーザー名は必須")
execute %[useradd {user_name}]
invoke "utils:enable_ssh_login"
end
end
task :enable_ssh_login do
fetch(:user_name) || ask(:user_name, nil)
on roles(:app) do
user_name = fetch(:user_name) || raise("ユーザー名は必須")
execute %[mkdir -p /home/#{user_name}/.ssh]
execute %[cp /root/.ssh/authorized_keys /home/#{user_name}/.ssh/]
execute %[chmod 700 /home/#{user_name}/.ssh]
execute %[chmod 600 /home/#{user_name}/.ssh/authorized_keys]
execute %[chown -R #{user_name}.#{user_name} /home/#{user_name}/.ssh]
end
end
まずfetchしてnilならaskする。これなら、invoke先のaskは実行されない仕組み。便利。
sudoが失敗する
INFO[29ab7cc8] Running /usr/bin/env sudo whoami on +LHKFlkL+FHLEWJF
DEBUG[29ab7cc8] Command: sudo whoami
DEBUG[29ab7cc8] sudo
DEBUG[29ab7cc8] :
DEBUG[29ab7cc8] sudo を実行するには tty がなければいけません。すみません
DEBUG[29ab7cc8]
cap aborted!
なんか謝られたけど、ttyがないとだめとのこと。
cap3は以下のようにすると設定できる。
lock '3.2.1'
set :pty, true
これでOK。
sshkitを使いこなす
capのDSLのonとかの部分って実はsshkitそのもの。
これがキモ。
詳しくは以下を参照とうい感じなのですが、正直これを知ると知らないとではコマンド実行のバリエーションに大きな影響が出る。
- capistrano/sshkit
- sshkit/EXAMPLES.md at master · capistrano/sshkit
- Capistrano 3への手引き - 今日のごはんは素麺です
ちょっと触ってみる。
task :other_whoami do
on roles(:app) do
execute :whoami
as :fuga do
execute :whoami
end
end
end
こうすると一つ目のexecuteだとhoge, 2つ目のexecuteでfugaと表示される。
ちなみに実際に使用されるコマンドがそれぞれ以下のようになる。
# 最初のexecute
/usr/bin/env whoami
# 2つ目のexecute
/usr/bin/env if ! sudo -u fuga whoami > /dev/null; then echo "You cannot switch to user 'fuga' using sudo, please check the sudoers file" 1>&2; false; fi on example.com
勉強になりますね。
あと実行するディレクトリを限定するwithinとか、複数ホストパラレル実行するか、シーケンシャルに実行するか、間隔を何秒あけて実行するとか、execute時のコマンドを独自に定義するとかエロエロできるので試してみるといいです。
download & upload
実はこれもsshkit自体の機能で実現できる。
まずはdownload。
maillogをdownloadしたくて、一度ifでファイルがあるかどうか確認してから実行してる。この例だとカレントディレクトリにそのまま落としてきてる。
task :download_maillog do
on roles(:app) do
file_name = "/var/log/maillog"
if test "[ -f #{file_name} ]"
download! file_name, '.'
end
end
end
次はupload。
task :upload_dir do
on roles(:app) do
execute :mkdir, '-p', '/home/hoge/cap_sample'
upload! "./Gemfile", '/home/hoge/cap_sample/Gemfile'
execute :ls, '/home/hoge/cap_sample'
end
end
ディレクトリごとuploadもできる。
task :upload_dir_all do
on roles(:app) do
execute :mkdir, '-p', '/home/hoge/cap_sample'
upload! '.', '/home/hoge/cap_sample', recursive: true
execute :tree, '-L 2 /home/hoge/cap_sample'
end
end
楽ちん♪
まとめ
開発で一時的に使うサーバーの場合は、プロビジョニングするつもりまではないが、ふとしたタイミングで入れたいツールとか、一度だけ実行したいコマンドがあるとか、そういうことはたくさんありそう。
ansibleなどのツールを使ってもいいけど、そのためにロールを書くのもだるい。手元にRubyの環境があればいいだけなので、デプロイまでいかずともshellでやりたくもないことを記述するならcapはちょうどいいDSLかもしれない。
(あ、でもansibleのinlinefileみたいなやつ超便利だなぁ…)