ツナワタリマイライフ

日常ネタから技術ネタ、音楽ネタまで何でも書きます。

capistranoでリモートサーバへのコマンド実行を自動化する

はじめに

仕事でえらいひとから「ちょっと話いいかな^^」で新しい仕事が振られました。

「あるシステムのサーバ群をメンテナンスするときの手順を自動化して欲しいって言われてるのね、それにはcapistranoを使ってるらしいの。だいたいこういうイメージ。じゃ、よろしく^^」

えっ?きゃぴ?何?

capistrano

公式ドキュメント爆読みしてググりまくって使ってみてプロトタイプ作成まで一週間。

A remote server automation and deployment tool written in Ruby.

元々railsプロジェクトを複数のサーバ(productionとstaging)に一気にデプロイをするためのツールです。バージョンが3系からrailsから独立したんですね。

ただこれ考えようによってはリモートサーバにシェルコマンドを送り込むツールとして使うこともできて、今回はその使い方で手順を自動化しようと言う話です。

ちなみに実際の手順書は数十ページに及ぶわ、対象サーバは10種類以上あるわ、1台やるのに30分〜1時間かかるわで、自動化の価値はあるんですが、その分自動化するのも大変でした。。。

capistranoの導入

gem install capistrano

バージョン指定しなかったので3.4が入ったはずです。

そのあと、任意のディレクトリを作って、そこにプロジェクトを展開するイメージ。

cap install

config/deploy/以下にはデプロイ先のサーバの情報を記述します。ここにstaging.rb、production.rbとしておいて、デプロイ先のサーバのipやパスワードを入力します。

ローカルだとこうなりますね。

server 'localhost', user: 'root', password: 'hogehoge', roles: %w{web}

ここでroleが出てきますが、サーバごとにroleを定義できます。webサーバやdbサーバなど、種類別にデプロイができます。

capistranoの基本的の書き方

コマンド実行

execute、capture、testをよく使います。

executeはコマンドを実行します。captureはコマンドの実行結果(標準出力)を取得します。これを変数にいれてごにょごにょできます。testは実行結果の終了コードを見てtrueかfalseかを返すので、その時点でdeploy失敗にはなりません。微妙に使いドコロが違います。

私はリモートサーバで手順を実行するんですが、以下のような書き方をしています。

if test 'command'
  info 'ok!'
else
  raise 'no-no-'
end

ただあんまり意味がない気が今更してきたな。executeで良いような。raiseのコメントを自分で書けるぐらいか。raiseするとその時点でdeployは終了します。

role制御

roleを制御するのには on roles(:web)といった記述をすることで、webサーバにだけ適用するよう記述できます。

今回はon rolesも使いましたが、一部コマンドだけこのroleでは必要で、あとは共通、って処理を書くのにうまく書けなかったので、以下の様な書き方をしました。

on roles(:all) |host| do
  host.roles.each do |role|
    if role == "web"
      # webロールに対する操作
    elsif role == "db"
      # dbロールに対する操作
    else
      raise 'invalid role #{role}
    end
  end
end

on rolesで対象オブジェクトを展開します。これのrolesで、rubyのsetクラスを返します。roleは複数定義可能で、順番は問わないのでSetなのでしょう。Setはじめて触りました。今自分の使っているツールでは基本1ホスト1ロールでやっているのでeachする必要はないんですが。。。

library set (Ruby 2.2.0)

taskのモジュール化

taskは複数定義でき、task内でtaskを呼ぶことができます。共通コマンドをメソッドに切り出すイメージですね。

invoke "util:service"

これはutilというnamespaceのserviceタスクを呼んでいます。invokeするにはnamespaceが必須です。

以下はリモートサーバにhttpdのstatusを確認するコマンドの例です。

namespace :util do
  task :service do
    execute 'service httpd status'
  end
end  

最初の方は良いんですが、かなり1ファイルあたりのコードが多くなってくると、内容や対象サーバごとにファイルをわけ、namespaceもわけています。共通化はなかなか難しかった。リファクタリングですね。

taskに引数を渡す

taskには引数を渡すことができます。

namespace :util do
  task :service, :command do |task, arg|
    execute 'service httpd #{arg[:command]}'
  end
end  

cap staging util:service[start]

これでserviceコマンドでstart、status、stopを使い分けることができます。複数取るときは以下のようにすれば良いです。

namespace :util do
  task :service, name, :command do |task, arg|
    execute 'service #{arg[:name] #{arg[:command]}'
  end
end  

cap staging util:service[httpd,start]

こうすればサービス名も入力させることができます。

sudo

set :pty, true

を記述してください。

capistranoの実行方法

cap <実行環境> :<task名>

HOSTFILTERを使えば、例えばたくさんあるwebサーバのうち2つのサーバに対してだけ実行することができます。1つでも良いです。

cap staging util:service[httpd,status] HOSTS='192.168.1.10,192.178.1.11'

まとめと今後

コマンドを送り込むツールとしてはかなり優秀です。rubyで制御できるのがかなりやりやすいですね。今後ですが

  • ansibleやchefとの比較

エージェントレスという点ではansibleと近いです。ただコマンドを投げてその結果でどうこうするとかは、ansibleやchefのレシピを書いてデプロイするツールとはちょっと違う気がします。ansibleはインフラ配備ツールで、capistranoは元々コードを複数環境にデプロイして、バージョン管理に基づいて戻したりできるっていうのが設計思想にありそうで、インフラの構築に使うべきではないでしょうね。冪等性は担保しませんから。単にコマンドをリモートに実行するツールとしては使い勝手がいいです。

  • 内部コードの読み込み

特にsshkitという部分をしっかり読んでおきたいです。まだ使い方を調べて書いてるだけで、もっといい使い方があるのかもしれない。そのために内部実装をちゃんと読みたい。sshkitはcapistrano内部のssh実行部分で、on ~あたりはここに実装されてるらしい。

  • 内部で複数サーバへの実行

今回仕事で要求されている手順で、サーバ1で作業して、サーバ2で作業して、またサーバ3で作業、というものがありました。これをcapistranoで実行するためにはサーバごとにtaskを実行してやらないといけないと思っています。(というのも、ロールごとにわけたとしても、同時に実行されてしまい、順番が保証されない)このいい解決策がないか、ということを探るためにもsshkitを読み込みたい。多分sshkitを単独で使えばいけるんだろうな。

おまけ

会社でやってるので上記のコード例は全部ソラで書きました。間違ってたらごめんね。

ちなみに作成したコードは0.8kstepにおよびました。仕事でこんなにコード書いたこと無い。今週テストです。こわい。

家で技術勉強はしない分会社で勉強出来てるのはいいことだ。