dely Tech Blog

クラシル・TRILLを運営するdely株式会社の開発ブログです

小さいチームで実践する!開発速度・信頼性向上のためにやってよかったシステム改善3選

こんにちは、クラシルリワードのSRE担当のjoooee0000です。

私はクラシルリワードのサービスローンチの約3ヶ月後にサーバー兼インフラエンジニアとしてjoinし、サービスの成長と共に、開発速度とシステムの信頼性の向上を目指してシステムの改善を行ってきました。

その中で、特に開発速度と信頼性向上に寄与したと思う3つの改善を紹介したいと思います。 改善を行う際に「コスト(金額、導入共に)と効果のバランス」「運用しやすいシンプルな設計」に特に気をつけたので、参考なると幸いです。

(話すこと: 取り組んだ施策の概要紹介、 話さないこと: 細かいhow to)

やってよかったこと3選

  1. ブランチ戦略の見直しとシンプルな検証環境の維持
  2. ログの構造化とNewRelicログUIの導入
  3. インシデント管理ツールの導入

それぞれ、改善前にどのような課題があったか、改善後のメリットなどを紹介していきます。

1. ブランチ戦略の見直しとシンプルな検証環境の維持

ここ1年間、チームの成長に合わせてブランチ戦略や検証環境が今の開発スタイルに合っているかを都度検討しながら、速度を落とさずに開発を進めてきました。そこで、初期から現在までのブランチ戦略の改善や検証環境をどのように運用しているかを紹介します。

チームが始まった当初のサーバーサイドエンジニアが1 ~ 3人くらいだった頃の話になりますが、下記の図のように、main / develop / stagingとfeatureブランチの4種類のブランチで運用されていました。

当時のブランチ戦略

また、main / develop / stagingそれぞれのブランチをベースにしたPullRequestをマージすると各環境にデプロイされる、というデプロイ戦略でした。

当時のブランチ戦略は、GitFlowなどのブランチ戦略と違い、developブランチを経由してmainブランチにリリースされるといった開発フローではなく、開発フロー中にdevelopブランチとmainブランチが合流するポイントがありませんでした。

それゆえ、mainブランチからdevelopブランチを一回切ったあとはdevelopブランチとmainブランチが徐々に乖離していくという課題があり、featureブランチをマージする際に複雑なコンフリクトやforce pushが必要になることが頻繁に発生し、開発速度が低下していました。stagingに関しては、利用頻度が活発ではなかったためデプロイの内容が徐々に古くなっていくという状況でした。

そこでブランチ戦略をmainとfeatureブランチだけのGithubFlowのブランチ戦略に変更し、developブランチの廃止に伴い、検証環境には個々のfeatureブランチをGitHub Actionsのworkflow dispatchをhookにブランチを選んでデプロイできるようにしました。ステージング環境にはfeatureブランチからmainブランチへのマージをhookに本番と一緒にデプロイされるようにしました。

デプロイフロー

また、そのタイミングで検証環境を開発者個々人に用意するかどうかが議論に上がりました。しかし、個々人用に検証環境を作ることで検証環境独自の運用が発生することを回避するために、本番環境と全く同じ構成の1台で運用することで検証環境の運用コストを下げることを選択しました。そして、検証環境は最終的な確認のみに使い、local環境での開発をメインにするようにしました。ブランチ戦略とデプロイを改善したことで、検証環境が1台でも問題なく開発ができるようになりました。また、少なくともここ1年間はほとんど検証環境の運用作業が発生しておらず、シンプルな構成の恩恵を受けています。

開発者のフィードバック

現在、サーバーサイドエンジニアが6名+web開発の1名になり、ブランチ戦略を整理した当時の倍以上になったことで複数人が同時に検証環境を利用したいシーンが増えてきました。個人のfeatureブランチをデプロイする方法のみだと、検証に待ちが発生する状態になりました。

そこで、検証環境を複数台に増やす検討の前に、まずはブランチ戦略やデプロイの改善をしました。

デイリーでmainからその日限りのdevelopブランチを自動作成し、そのブランチに複数人がGitHub Actionsを通して自動マージ&デプロイをして検証を行う設計に変更しました。別の開発者がマージしたコードとコンフリクトした場合は自動マージできないので開発者が解消する必要がありますが、デイリーでmainからdevelopブランチを作成しているため、mainとの乖離による無駄なコンフリクトは発生することはありません。また、developブランチへのマージやブランチ作成をGitHub Actionsで自動化することで開発者の負担を下げています。

最新ブランチ戦略

このように、チームの規模や開発スタイルに合わせたブランチ戦略の見直しと、検証環境をシンプルに保つことで、開発速度の向上や運用工数の削減をすることができています。

2. ログの構造化とNewRelicログUIの導入

サービスをリリースしてから数カ月間は、CloudWatch Logsを利用しており、かつ構造化していないログで検索性がとても低い状況でした。そのため、ほとんどアプリケーションログが活用されておらず、不具合調査などの業務効率が大幅に下がっていました。

そこで、NewRelicの導入とアプリケーションログをjson形式に構造化をすることにしました。

まず、NewRelicを選定した理由として、下記があります。

  • NewRelicのデータ転送コストがCloudWatch Logsの1/3程度で済むこと
  • ログ基盤のElasticsearchの運用はSaaSのNewRelicに任せることで運用コストが削減できること
  • ログ管理UIの利便性

ログ管理をしようと思ったときに一番料金がかさむポイントとしてログ基盤へのデータ転送の料金があります。実際に、CloudWatch Logsでもデータ転送料がコストの大半を締めていました。

NewRelicのデータ転送コストはSaaSの中でもトップクラスで安く、CloudWatch Logsの1/3程度で済んだためコスト削減にも繋がりました。(実際にはつなぎ込みのためのKinesis Data FirehoseとBackup用のS3の料金があり1/3コスト減とまでは行きませんが、それでもCloudWatch Logs単体利用より安価)NewRelicはAPMのSaaSとしてよく知られていますが、2022年からはフルオブザーバビリティプラットフォームとして様々な機能が提供されています。より高度な機能の利用を検討する場合はユーザーアカウントごとの課金もありますが、ログ管理UIの利用はBasicUserという無料ユーザーでも利用可能です。

また、自社基盤でログ用のElasticsearchを運用することも考えましたが、Elasticsearchの運用はNewRelicに任せることで運用コストを削減しました。

NewRelicのログ管理UIはポチポチするだけである程度の絞り込みが行えたり、下記のようにグラフも自動で出してくれるのでCloudWatch Logsを利用していたときと比べて圧倒的に利便性が上がりました。

実際のNewRelicログUI

また、無料で使えるダッシュボード機能を使い、よく検索するログ(5xxエラーが出ている上位10エンドポイントの検索、など)をダッシュボード化して可視化することでトラブルシュートの速度が上がりました。

NewRelicDashboard

NewRelicは前に述べたように、他の機能も充実しているため拡張性もあります。実際に、SLI / SLOの可視化や外形監視など、他でも利用できるところが多々あり、サービスが大きくなってきた現在も便利に使えています。

また、アプリケーションログをjson形式に構造化することでログ検索の利便性が上がりました。

アプリケーションのサーバーサイドはRailsを利用していますが、Railsのログは、エンドポイント、リクエストパラメータ、レスポンスタイム、ヘッダーなどの情報が複数行に分かれているため、各エンドポイントにかかった時間やパラメータなどを一行で検索することができませんでした。また、例えば、CloudWatch Logsのクエリでリクエストごとのレスポンスタイムを検索しようとすると下記のような複雑なクエリを毎回書く必要があり不便でした。

fields @timestamp, @message
| filter @message like /Completed 200/
| parse @message "Completed 200 OK in *ms (Views: *| ActiveRecord: *ms" as response_time_rails, response_time_view, response_time_rds
| filter response_time_rails > 500
| sort @timestamp desc
| limit 20

そこで、 GitHub - roidrage/lograge: An attempt to tame Rails' default policy to log everything. を利用してエンドポイントやレスポンスタイムの情報、ヘッダーの情報の一部を一行で出力できるようにし、コード上で指定している ::Rails.logger で出力されるRailsのアプリケーションログも同様にjson形式に構造化しました。

また、::Rails.loggerのタグ機能でrequest_idを1行ごとに付与し、1リクエストのログを紐付けられるようにすることでリクエストごとのログが追いやすくなりました。

request_idによるリクエストごとのログ検索

Railsのログの構造化に関しては、意外と参考記事が見つからなかったので後日追ってブログにしようと思います。

NewRelicを導入したことで、開発速度・信頼性共に恩恵を受けることが多々ありましたし、わたしたちのサービスのトラフィックやログ量だと、CloudWatch LogsからNewRelicに移行することでコスト削減にも繋がりました。また、ログの構造化をすることで不具合調査や障害対応時にスムーズに原因を特定でき、役立っています。

3. インシデント管理ツールの導入

3つめは、インシデント管理ツールを導入し、MTTR(平均復旧時間)の短縮をした話です。

私達のサービスは、サービス利用ピーク時間帯が朝方です。サービス利用のピーク時間は、他の時間帯と比較して障害発生確率が高くなる傾向にあります。しかし、ピーク時間帯が朝方のため、開発メンバーが起きておらず、障害に気が付かないことでMTTR(平均復旧時間)が伸びてしまったことがありました。

アラートはSlackに流すようにしていましたが、それだけだと 「常にSlackを見ていないと障害に気づけない」、「アラート通知が他のSlack通知と一緒の音で紛れがち」という課題がありました。

そこで、まずは朝方や休日のインシデントに確実に気づけるよう、インシデント管理ツールのOpsgenieというインシデント管理ツールを導入しました。インシデント管理ツールの機能の中でも、アラート通知機能によってMTTRを短くする対策することにしました。

CloudwatchAlarmでアラートが上がったらSNS経由でOpsgenieにアラートを送信するようにします。また、自分のスマートフォンにOpsgenieのアプリをインストールすることで、アラートをスマホで受け取れるようになります。

そうすることで、障害が起きるとスマホから目覚ましのアラームのような音が鳴り(心臓に悪いという点では不評)、障害に迅速に気づくことができます。Opsgenieの導入以降は障害に気づくまでの時間が短縮され、MTTRの短縮に寄与しました。

加えて、サービスに関する重要な意思決定ができるメンバー(現EM)も一緒にアラートを受け取れるようにし、障害時の対応に迷った際、迅速に意思決定ができるようにしています。

Opsgenieは、アラート通知機能を使いたいだけであれば、利用人数5人まではフリープランで足ります。

最近チームの人数が増えたこと、過去のインシデントとアラートの紐づけのニーズが上がってきたことにより有料プランへの切り替えを予定していますが、この1年間はフリープランで十分なメリットを享受できていました。その他にも、フリープランで

  • オンコールローテーションの自動スケジューリング (メンバーとローテーション期間を指定すれば自動でスケジュールを組んでくれる)
  • エスカレーション機能 (スマホで受け取ったアラートのボタンを押せば自動でエスカレーションできる)

オンコールスケジュール画面

などが利用でき、複数人の開発者のオンコールローテーションを簡単に仕組み化することができます。

インシデント管理ツールとしてはPagerDutyが有名ですが、Opsgenieは同じような機能をPagerDutyの半額で利用できるのでコスト面も優れています。また、Atlassian の製品なので品質も特に問題なくUIも使いやすいです。

Opsgenieには様々な機能がありますが、まずはシンプルにアラート通知の機能を使うだけでもメリットがあるので、おすすめです。

また、OpsgenieはTerraformでproviderが提供されており、設定内容をTerraformで管理することができます。Terraformで管理することで、属人化や設定のブラックボックス化を防いでいます。

まとめ

開発速度・信頼性向上のための取り組みを3つ紹介しました。

運用面やコスト面を考慮して改善を行っているので、小さいチームにも参考になれば幸いです!