マネーフォワードのビルド・リリース改善

こんにちは。
インフラエンジニアの村上です。

マネーフォワードのインフラチームは、サービスに関わるインフラから、自社の作業環境、開発環境、さらにはサービスのインフラの中でも物理的なものからOS・ミドルウェア・アプリケーションのメンテナンス・ビルド・リリース・運用まで幅広く関与しています。

その中で現在取り組んでいるビルド・リリースの改善をご紹介したいと思います。
 

何をしたか?

■ビルド・リリースのツールをshellからcapistranoへ乗せ換えをした。
■ビルド・リリースフローを見直した。
 

現状の問題点

マネーフォワードの家計簿サービスがサービスインしてから、早3年程経っており、当時は小さかったサービスもお陰さまで大きくなってきました。
サービスも複数になり、少しづつ機能を追加/改修されてきました。
そして、スクリプトも複雑になるにつれて以下の様な問題点が出てくる様になりました。
■リリースが遅い(1アプリケーション 5~10分程度)
■サービスが増えた時に対応しずらい(改修しづらい)
■リリース作業のステップ数が多い

今回の改修においては、以下を重要点として進める事にしました。
■スピード感のあるリリースが出来る事。
■カスタマイズ性の向上/フレームワーク化を進める事。
 

実現の為にやったこと

まずは、ビルド/リリースのフローを見直しました。

今までのフロー

今まではMergeしてから実際にリリース完了までが長く、開発が進むに連れてビルド時間も伸び、エンジニアがチェックを完了するまでの時間を長く拘束する事がありました。
エンジニアは、MergeしてJenkinsを実行してから完了まで全てにおいてリリースを頭の隅においておく必要がありました。
buildflow-before

新しいフロー

新リリースフローでは、Mergeした時点でビルドが自動で開始され、アーカイブファイルが各サーバーで展開される所まで勝手に行われます。
エンジニアは、準備が完了した時点で通知を受けるので、それに対してSwitchの指示をする事で15秒程度でリリースが終わる様になります。

この様にした理由は、ビルド/リリースの処理時間のチューニングの頭打ちが見えていた為です。
処理時間が減らせないのであれば、フローを変える事でスピード感を維持する事にしました。

さらにStaging環境と同じアーカイブファイルを利用する様にすれば、ステージング確認中に本番での配置まで終わるので、Stagingでの動作確認完了と同時に本番もSwitchという様な事が出来る様になります。
ここまでくればリリースの為に何かを待つという事はなくなり、スピード感があるリリースが出来る様になります。
buildflow-after

ツールの開発

リリースツールに使う言語・ツールは数多ありますが、弊社の開発体制/使用言語を考慮しRubyベースのCapistranoを採用しました。
Capistranoデフォルトのリリースフレームワークは各サーバーでcloneやらbundle installを行うので、NWコストが高くスケールしないのと、継続的Deploymentの鉄則であるテストと同じモジュールを使う。という思想からはズレてしまうので今回はタスクを新規に実装しなおしています。
主にビルド・パッケージ・Bundle InstallはJenkins上で担い、各サーバーにはリポジトリサーバーを経由して同じものを配布する様にしています。
 

ツールの構成

開発にあたってCapistranoの構成は以下の様にしました。

Capfileは全アプリケーションで1つにしています。
Gemfileを置いておくことでCapistranoの実行環境をどのサーバでも作れる様にしました。
config配下は一般的にはdeploy.rbとdeploy/<#environment>.rb を準備しますが、弊社の場合はアプリケーション毎に<#appname>_deploy.rbを作成しています。
各アプリケーションのフォルダにdeploy.rbを配置する事で、lib/tasks配下のロジックを共通で使える様にしました。

実行時のコマンドイメージは以下になります。

cap <#taskname> ROLES=<#appname> app=<#appname> [branch_name=<#branch_name]
※ []は任意オプション
※ ROLESはアプリケーション毎に配布先が違う場合に使います。
<#environment>.rbでサーバーには<#appname>をroleとして設定しておく事で、実行時にROLES=を指定すれば自動的に配布先が確定します。

Capfile               # 引数チェック等をここでやっています。
Gemfile
Gemfile.lock
config
-- app01_deploy.rb
-- app02_deploy.rb
-- common_deploy.rb
-- deploy             # サーバーの定義、環境毎のフォルダパス等を記載
    -- dev.rb 
    -- prod.rb
    -- stg.rb
lib
-- capistrano
    -- tasks          # 自前で実装したロジックを配置
       -- build.rake
       -- cleanup.rake
       -- distribute.rake
       -- git.rake
       -- help.rake
       -- notice.rake
       -- switch.rake

<#appname>_deploy.rbで実装するタスクは以下の様にしています。
どのアプリケーションにおいても、この名前のタスクを作ってインタフェースとしておく事で
呼び出す側がAPP毎の処理の違いを意識しない様にしています。

cap build                         # ビルド処理を実行し、パッケージ(圧縮ファイル)を作成します。
cap distribute                    # アプリケーションを各サーバーに配布し、パッケージを解凍します。
cap switch:forward                # アプリケーションを最新に切り替えます。
cap switch:rollback               # アプリケーションをロールバックします。
cap cleanup                       # 古いアプリケーションファイルを削除します。

上記タスクを実行時の共通のコマンド体系とし、実際の処理はさらにそこから呼び出しをしています。
個別の処理(例えばconfの差替とか)がある時は、invokeで呼び出している処理を取り除いたり、足したりします。

task :build do
  invoke "build:check"        # フォルダやGitへの接続などを確認します。
  invoke "build:scm_update"   # ソースをCloneしてきます。
  invoke "build:install"      # bundle installを実行します。
  invoke "build:compile"      # asset:precompile を実行します。
  invoke "build:package"      # 成果物をtar.gzにします。
  invoke "build:push"         # ストレージに送ります。(tar.gzとbundleフォルダを転送しています。)
end

task :distribute do
  invoke "distribute:check"               # フォルダやサーバーへの接続などを確認します。
  invoke "distribute:distribute"          # 各サーバーにファイルをダウンロードして配置し、解凍します。また、bundleフォルダも同期します。
  invoke "distribute:install"             # bundle installを実行します。(ここでは新規にDLされる事はありません。)
end

task :switch_forward do
  invoke "switch:forward"   # シンボリックリンクを切り替えます。
end

desc "switch application link rollback old version"
task :switch_rollback do
  invoke "switch:rollback"    # シンボリックリンクを戻します。
end

desc "cleanup old module at package dir / distribute dir"
task :cleanup do
  invoke "cleanup:repository"       # アーカイブを貯めるストレージを掃除します。
  invoke "cleanup:distributefile"   # 配布先サーバー上の古いファイルを掃除します。
end

こうする事で、アプリケーション毎にやりたい処理/やりたくない処理を分ける事が出来、同じ処理は共通化する事が出来る様になりました。
もしアプリケーション固有のロジック、環境固有のパラメーターを作りたくなった時は、このファイルに書く事で共通化せずに実装する事が出来ます。
 

良かった所

Capistranoのフレームワークを使用する事で、Shellに比べると非常に可読性高くかつ疎結合になりました。
SSHの部分等、実装したくない所はCapistranoが担ってくれたのが大きかったと思います。
不具合/カスタマイズも非常にやりやすくなりましたし、エンジニアの作業ステップ数も減りそうです。
タスク毎にテストを書けば、本ツールのCIテストも実装出来そうです。
 

悪かった所

GUI実行を残す為、今回はJenkinsを採用しました。GithubWebhookも受けられるし、GUIも提供できる、Capistranoも呼び出せるので非常に便利ですが、Jenkins自体のメンテナンスや改修は相変わらずの手間で、そこからはぬけ出す事は叶っていません。
 

今後

このビルド・リリースのシステムは現在試験導入中であり、全てのサービス/全サーバーで導入できているわけではありません。
MigrationやChatOpsの部分は実装中の所もありますが既存サービスに影響を与えない様にしながら、順次切替を行っていく予定です。
 

参考

ビルド・リリースフローを検討するにあたり、Mavenとクックパッド社の Scalable Deployments を非常に参考にさせていただきました。
MavenはJavaのツールになりますが、ビルド・リリースのフローをフェーズ(ゴール)で分けるという考え方と実装を持ち込んだ偉大なツールです。今回もタスクの分割はMavenのフェーズを参考にいたしました。
ビルドリリースの部分では、Scalable Deploymentsを参考にさせていただきました。エンジニアはSwitchの部分だけ担い、それ以外はバックグラウンドの時間で処理を終わらせるという視点は素晴らしいと思い、採用させていただきました。
 

最後に

マネーフォワードでは、新技術や新しい考え方を取り入れながら、最高のサービスを提供する為のプラットフォームを整備するインフラエンジニアを募集しています。
ご応募お待ちしています。

【採用サイト】
マネーフォワード採用サイト
Wantedly | マネーフォワード

【公開カレンダー】
マネーフォワード公開カレンダー

【プロダクト一覧】
家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』
家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』 iPhone,iPad
家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』 Android
クラウド型会計ソフト『MFクラウド会計』
クラウド型請求書管理ソフト『MFクラウド請求書』
クラウド型給与計算ソフト『MFクラウド給与』
経費精算システム『MFクラウド経費』
消込ソフト・システム『MFクラウド消込』
マイナンバー対応『MFクラウドマイナンバー』
創業支援トータルサービス『MFクラウド創業支援サービス』
お金に関する正しい知識やお得な情報を発信するウェブメディア『マネトク!』

Pocket