Raccoon Tech Blog [株式会社ラクーン 技術戦略部ブログ]

株式会社ラクーン 技術戦略部より、tipsやノウハウなど技術的な話題を発信いたします。

2013年05月

ラクーンのCI/CDへの取組みの現状とこれから(2)

こんにちは。たむらです。

今回は前回に引き続き「ラクーンのCI/CDへの取組みの現状とこれから」と題した第2回目です。第1回は「Capistrano」によるデプロイの効率向上のお話でした。第2回目は「Selenium」によるテスト自動化の話とシステムとツールを統合管理してくれるCIツール「Jenkins」のお話です。それではいってみましょう!

サイト監視ってカッタルイ ~「Selenium」登場~

弊社はサイトを通してビジネスをしているのでサイトがダウンするとビジネスが成り立たないことになってしまいます。その為、当然のことですがサーバの死活監視、プロセス監視、HTTP通信監視等色々な監視を複合で行なっています。その監視の1つとして、「お客様が実際に利用するメインの導線となる機能が正しく機能しているかを確認する」監視という結構泥臭いことを行なっています。例えばスーパーデリバリーで言えば、以下の様なフローを実施することで確認をしています。

1. 小売店様のスーパーデリバリーサイトへのログイン
2. 商品検索~閲覧
3. 商品Aの注文
4. 出展企業様の管理画面へのログイン
5. 商品Aの受注処理~出荷処理
6. 商品Aの返品・返金処理

さてこの監視ですが、以前はその他の監視業務等と併せて外部委託していましたが、現在はSeleniumを利用して行なっています。
SeleniumはWebブラウザを使ってWebアプリケーションをテストするツールとしてこれまたメジャーなツールです。FireFoxアドオンのSeleniumIDEにて簡単にテストシナリオを作成することができます。また、Selenium2(WebDriver)となってからは各種ブラウザをネイティブに操作できる様な作りとなりだいぶ様変わりしました。
正直、2008年頃は挙動が安定しなかったり動的なページのテストが上手くできなかったりして余り使えてなかったのですが、Selenium2が発表された頃に再度利用してみて「これはイケそう!」とまた使ってみる気になった経緯があります。
 
現時点ではまだSelenium1系のSeleniumRCを使ってテストを運用しています。その為、テストシナリオはSeleniumIDEを利用してベースを作成した後、記述を見直した上でテストケースとしています。というのは、利用したことがある人はご存知だと思いますが、SeleniumIDEの記録するシナリオはロケーションの指定の仕方が微妙なものがある為です。また、表示の度にidが変わる様なものもSeleniumIDEだとほぼ確実にidでロケーションを指定してしまうため、それらは汎用的な形に指定を変更してあげる必要があります。

  ■TestCaseの例(上記「3.商品Aの注文」の一部)■
~~~
<!-- 商品リストの左上商品をクリック -->
<tr>
  <td>clickAndWait</td>
  <td>xpath=(//a[starts-with(@href, '/p/r/pd_p/')][1])</td>
  <td></td>
</tr>
<!-- ログイン済み検証 -->
<tr>
  <td>assertElementNotPresent</td>
  <td>xpath=(//img[@alt='ログイン'])</td>
  <td></td>
</tr>
<!-- 数量ボタンを押下 -->
<tr>
  <td>click</td>
  <td>id=input_1_up</td>
  <td></td>
</tr>
<!-- カートに入れるボタンを押下 -->
<tr>
  <td>click</td>
  <td>css=a.vmiddle.add-cart-item > img[value="カートに入れる"]</td>
  <td></td>
</tr>
<!-- Ajax処理表示待ち -->
<tr>
  <td>waitForVisible</td>
  <td>link=カートへ進む</td>
  <td></td>
</tr>
~~~

ちなみにSelenium2は現在まだ評価中ですが、最終的にはクロスブラウザチェック等も行なっていきたいと考えている為いずれ置換えて行く予定です。ただ、上記の例でも使っているような "waitForVisible" 等のメソッドは自作する必要があり、一通りのベースを作りこんで検証した上でと考えています。


ルーチン業務に時間を取られるのはしんどい! ~「Jenkins」登場~

さて、上述のSeleniumによる監視ですが、自動定期実行のために何かしかの仕組みが欲しいところです。勿論cronでも機能的には満たせるのですが、今後のSelenium利用シーンの拡大を考慮して、より運用や管理が行い易いものを望んでいました。
そこで Jenkins の登場です。Jenkinsは言わずと知れたオープンソースの継続的インテグレーションツールの代表格です。コミットトリガによるビルドやスケジューリング、タスクの依存関係構築等がGUI上から簡単に行うことができ、更にプラグインの追加により他にもたくさんの機能を管理対象とすることができます。

Seleniumを用いたテストの自動定期実行もプラグインを利用することで簡単に設定が可能です。手順はたったコレだけです。
(1) Jenkinsのプラグインで、「Hudson SeleniumHq plugin」をインストール
(2) 「新規ジョブ作成」からジョブを登録し、実行間隔等を定義
(3) 「ビルド手順の追加」-「SeleniumHq htmlSuite Run」を選択し実行するTestSuiteを指定

■SeleniumHQ htmlSuite Run の定義例■
WS000000
プラグインとして用意されている割には、細かな設定もかなりの範囲で行えて痒い所に手が届く作りになっています。
例えば、上記では[browser]で起動すべき実行形式の指定を変更していたり、デバッグモード設定やログ出力等の指定ができる様に[other]にパラメタを積み込んだりしています。

必要に応じてレポート出力ができる「Selenium HTML report」やエラー通知用に「Jenkins Email Extension Plugin」等のプラグインを入れるのも良いでしょう。
尚、SeleniumをJenkinsサーバ自身で動かそうとしていて、且つJenkinsサーバがLinuxでGUIが無い場合、実行にはXwindowやXvnc等の設定をしておく必要があります。

この様にJenkinsはインストール後、ほぼ選択と入力をするだけで簡単に始めることができます。基本的にはビルドを中心にしてその前後処理を肉付けしていく感じで徐々にやれる範囲を拡大して行くような流れで構成を作っていくことになると思います。

■Jenkinsの画面イメージ■
WS000002


ラクーンのCI/CDのこれから

ここ迄現時点で行なっている幾つかの取組みを書いてきましたが、ん~・・・まだまだ全然不十分ですね。ワンクリックデプロイやテスト自動化という言葉には程遠い様に見えます。Jenkinsの強みも有効に利用できているとは言えません。
ただ、第1回の冒頭でも書きましたが、既存である程度の規模のシステムができ上がっている状態からこれらを適用していくのは、一朝一夕では行えません。また、継続的インテグレーションや継続的デリバリはここ迄やれば完了というものではなく、「継続的」と付いている通り常に改善/改良を加えていくものだと認識しています。
そんな訳で、「千里の道も一歩から!」。今後も一歩ずつ着実に成果を積み上げて作り上げていこうと思っています。
ちなみに今構想しているこれからの取組みはこんなことです。

1. デプロイの完全自動化
 ・バージョン管理システムとの完全な連携
 ・ロールバックをワンクリックでできる仕組みの構築
 ・ビルド環境の確立
 ・リリース単位のバッティングによるデグレードを抑止する仕組み
2. テスト自動化
 ・クロスブラウザ検証の(ある程度の)自動化
 ・テストシナリオの充実
3. システム構成管理の簡易化(環境の構築がより簡便に実施できる様にする)
4. Jenkinsによるデプロイやルーチン業務の統合
5. QAの半自動化

今回取り上げたツールの他にも色々なCI/CDツールが選択できる状態にあり、それらも上手く取り入れていくことでどれも実現が可能なものだと思っています。
 

終わりに

CIやCDについての取組みは直接システムの利用者の満足に繋がるものではありません。ですが、開発チームのサポートとして目に見えた改善を感じることができるところですし、何より新しいツール等を駆使して、いかに自分で考えながら最適解を探すかという楽しみもあり、とてもやり甲斐を感じることができるものです。

自ら考えて構築することやCI/CDに興味をお持ちの方、是非一緒に働いてみませんか??

・・・と、最後に求人を入れつつ、以上、2回に渡ってラクーンのCI/CDについてのお話でした。

ラクーンのCI/CDへの取組みの現状とこれから(1)

こんにちは。技術戦略部で今度から部門長をやることになったたむらです。
 
「お前も技術部なんだからブログの一本や二本は書け!」との鬼編集長からのプレッシャー(?)があり、頑張ってブログを書いてみます。よろしくお願いします!

今回は「ラクーンのCI/CDへの取組みの現状とこれから」と題して2回に分けて書いてみたいと思います。
アジャイル開発の界隈で良く取り上げられ、昨今メジャーな話題となっているCI(継続的インテグレーション)CD(継続的デリバリ)という用語ですが、これらで謳われている方法論はラクーンでも採り入れるべき多くのメリットを持っています。
ただ、ラクーンのメインビジネスとなっている「スーパーデリバリー」は10年以上続いている大きなシステムであり、既存のシステムにCI/CDのポリシーを適用していくのは大きなコストと時間が掛かります。そんな意味で、今回のテーマである取組みの実際の内容の他に、ラクーンがどの様にシステムに対して改善のアプローチを図っているかという一例が紹介できればいいなと考えています。

ということで、第1回は Capistrano によるデプロイの効率向上のお話です。


デプロイって面倒くさい ~「Capistrano」登場~

まず、弊社のサービスである「スーパーデリバリー」や「Paid」は以下の様なシステム構成となっています。
 
WS000005


1系サーバグループと2系サーバグループはそれぞれ複数台のサーバから構成されていて、すべてのサーバは同じ構成になっています。そして、1系と2系のグループが正副の関係となっていて、リリース毎に稼動系サーバが切り替わるという様な運用を行なっています。
さて、この様な構成のサーバ群に対してリリースする際、1年半程前迄はリリース手順書を用意してそれに沿って手動でデプロイを行なっていました。リリースの手順は以下の様なものです。

(リリースの手順の概要) 
1. リリース物の準備/バックアップ
2. ロードバランサーの振分け状況チェック(ここで1系/2系どちらが稼動系かを確認する)
3. 稼動系サーバ群でのファイル同期チェックジョブの停止
4. ファイル共有サーバへのリリース物のアップロード
5. 待機系Webサーバ群でのリリースファイルの同期
6. 待機系Webサーバ群でのプロセスの再起動
7. 待機系Webサーバそれぞれのログ確認とプロセス稼働確認
8. ロードバランサーの切替(待機系を稼働系に)
9. 旧稼動系サーバ群でのファイル同期チェックジョブの再開
 
当然、これらのオペレーションのすべてをマニュアルで行っているとミスが発生することもあり、本番サービスがダウンすることも過去に何度か発生していました。


またデプロイには以下の様な問題が絡んで、オペレーションは属人化する傾向にありました。
(デプロイに関する問題)
a. 操作対象サーバが多く作業負荷が高い
b. プロセスの再起動等のオペレーションにより、リリースユーザ権限がほぼ管理者権限になってしまう
c. ロードバランサーの操作が別物になっており、作業負荷・リスクが高い
d. リリース手順自体が複雑

そこで採用したのがCapistranoです。Capistrano はオープンソースのソフトウェアデプロイメントツールで、デプロイツールの中では非常にメジャーなものです。複数サーバへの作業の効率化・自動化が簡単に行えます。デフォルトで一通りのデプロイタスクのひな形が用意されているのですが、ラクーンでは上記のリリース手順に沿ったものにする必要があったため独自のタスクを用意して実装することにしました。


では上記で挙げたデプロイに関する問題に対してそれぞれどんなアプローチをしたのかを説明していきましょう。
 
まず、「a. 操作対象サーバが多く作業負荷が高い」についてです。
これはCapistrano の基本的な機能を使えばそれだけで解決できます。

独自タスクはrakeタスクを書く要領で簡単に実装できます。例えばこんな感じです。
(rubyやrakeタスクについての説明はここでは省略します) 
■tomcatプロセスの再起動の例(上記「手順6」の一部)■
~ deploy.rb の抜粋 ~
set :application, "deploy"
set :user, "deployuser"

if togroup == "group1"
 # 1系統のサーバーグループを定義
  role :web, "XXX.XXX.XXX.XX1"
  role :web, "XXX.XXX.XXX.XX2"
  role :web, "XXX.XXX.XXX.XX3"
elsif togroup == "group2"
 # 2系統のサーバーグループを定義
  role :web, "YYY.YYY.YYY.YY1"
  role :web, "YYY.YYY.YYY.YY2"
  role :web, "YYY.YYY.YYY.YY3"
end

desc "cap -f deploy.rb tomcat_action -S togroup=[group1|group2] -s repos=[serviceA|serviceB]"
task :tomcat_action, :roles => [:web] do
  # パラメタで指定されたサービスの停止
  run "sudo \/etc\/init.d\/#{repos} stop"
  run "sleep 10"
  # プロセス停止を確認後・・・
  run "test `ps -ef | grep \"#{repos}\" | grep -v \"grep\" | wc -l` -eq 0"
  # 起動
  run "sudo \/etc\/init.d\/#{repos} start"
  run "sleep 15"
  # 起動ログを出力し確認
  run "tail -n 10 \/var\/log\/#{repos}.log"
  # プロセス状況を出力し確認
  run "ps -ef | grep \"#{repos}\" | grep -v \"grep\""
end

Capistrano では、リモートサーバにsshで接続しコマンドを実行します。その為、事前に Capistrano を実行するマシンとリモート実行する対象のサーバではsshで接続できる設定をしておく必要があります。

上記で定義したtomcatプロセス再起動タスク(tomcat_action)を実行する際は以下の様にコマンドラインから実行します。
$ cap -f deploy.rb tomcat_action -S togroup=group1 -s repos=serviceA
パラメタは"-S"または"-s"の後にname=valueの形で指定します。オプションの"-S"と"-s"の違いは、前者は初期化時から使用したい場合、後者はタスク内でのみ使用する場合に利用します。その為、サーバーグループの選択を行う"togroup"は"-S"、タスク内のみの"repos"は"-s"と使い分けています。
 
この様にCapistrano ではパラメタにより複数サーバグループを切替えつつ、同一の操作を実行することが簡単に実装できます。


次に、「b. プロセスの再起動等のオペレーションにより、リリースユーザ権限がほぼ管理者権限になってしまう」についてです。
これは、Capistrano 経由でデプロイのコマンドを発行するユーザを新規に作成し、そのユーザに対してsudoers のコマンドエイリアスを定義することで解消させました。

■/etc/sudoersの記載例■
Cmnd_Alias PROCCMD=/etc/init.d/serviceA,/etc/init.d/serviceB
deployuser ALL=NOPASSWD:PROCCMD
この様にすることで、管理者権限が必要な特定のコマンドのみをリリース用アカウントにノーパスワードにて実行させることができます。
直接的には Capistrano とは関わりませんが、Capistrano 実行環境の構築に合わせて環境周りも整備した結果選択できた対処となります。


続いて「c. ロードバランサーの操作が別物になっており、作業負荷・リスクが高い」についてです。
これは、利用しているロードバランサーのプロダクト「BigIP」がI/Fとして提供している bigpipe shell をCapistranoから実行することにより作業を一本化&タスク化することができました。
BigIPはGUIとCUIそれぞれI/Fを持っているのですが、CUIである bigpipe shell はその名の通りLinuxのシェルと変わらない作りの為、問題なく Capistrano から実行することができます。

■ロードバランサーの切替タスクの例(上記「手順8」の一部)■
set :application, "deploy"
set :user, "deployuser"

role :lb, "ZZZ.ZZZ.ZZZ.ZZZ"

desc "cap -f deploy_lb.rb exchange_balancing -S togroup=[group1|group2]"
task :exchange_balancing, :roles => [:lb] do
  grp = togroup == "group1" ? 1 : 2
  # 指定したプール(1系/2系に分けたサーバグループ)に本番アクセスを切替
  run "bigpipe profile httpclass httpclass_serviceA pool pool_serviceA_#{grp}"
  # 切替えられたプールを表示
  run "bigpipe profile httpclass_serviceA list | grep pool"
  # ロードバランサの待機系に設定変更を反映
  run "bigpipe config sync all"
end

尚、BigIPはv10.0.0からCUIとして bigpipe shell の他に tmsh というCisco風なシェルがサポートされています。当初ラクーンではBigIPのデフォルトシェルを tmsh にしていたのですが、Capistrano はデフォルトだと tmsh 形式のコマンド実行ができません。そこでBigIP側のデフォルトシェルを bigpipe shell に切り替えて対応することにしました。これにより、「c.」の問題も解消させることができました。


最後に、「d. リリース手順自体が複雑」についてです。
そもそも、何故リリース手順が複雑になるかというと、スーパーデリバリーではコンテンツの更新などで開発案件以外でもサイト更新が頻繁に行われている為、その運用を壊さずにリリースする必要がある事が1つの大きな理由となっています。
手順自体の簡略化はプログラム化することで概ね解消します。しかし、別のマニュアルリリース作業が一部残存してしまうことから、上で挙げたリリース手順「7」の最終的なサイト確認などは現時点では自動化するより目視により行う方が適切だという判断に至りました。
そこで、最終的にはCapistrano のタスクをシェルでラップしてCUIの対話形式ツールとし、適宜確認を行えるところ迄を一つの到達点としました。

■シェルでラップしたデプロイツールのイメージ■
WS000006


以上の様にラクーンの持っていたデプロイの問題に対して、Capistrano という強力なツールを軸にデプロイの改善を進めたのでした。
色々と不完全な部分もあり完全自動化迄は至っていませんが、これによりオペレーションミスの抑制や作業の省力化はかなり進めることができました。

今回はここ迄になります。
次回は Selenium を用いた自動テストと Capistrano や Selenium 等のツールを統合してくれる Jenkins の話題に続きます。

記事検索