メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックカンパニーのエンジニアブログです。読者に有用な情報発信ができるよう心がけたいので応援のほどよろしくお願いします。

第二創業期のメドピアが定義する、次世代エンジニアの「三つの責任」

こんにちは。三村(@t_mimura39)です。

メドピアはいま、大きな変革の真っ只中にいます。 2024年の創業20周年や代表交代、そして2025年のMBO。こうした大きな節目を経て、自らを「第二創業期」の只中にあると定義しています。 そんな中で私は、プリンシパルエンジニアの一人として「プロダクト開発組織の未来図」を描き、その実現をリードしています。評価制度や開発フロー・ルールの刷新など、あらゆる変革の根底にあるのは、一つの極めてシンプルな問いです。

「AIが実装を担う未来において、エンジニアは何に責任を持つべきか?」

今回はその問いに対しての一つの答えを書き記したいと思います。

目次


1. はじめに「なぜ今、この責任を定義するのか」

生成AIの普及により、コードを「書く」という行為は急速に自動化されつつあります。これからのエンジニアの存在意義は、実装という作業そのものではなく、「技術によって、託されたプロダクトの品質を保証すること」に一本化されていきます。

事業の推進者がエンジニアを信頼し、プロダクトという事業の心臓部を託すための共通言語として、「プロダクト開発」を主眼に置くエンジニアが果たすべき「三つの責任」を定義します。


2. 三つの責任

① 構造の責任

「正しく、AIや人間が文脈を理解しやすい秩序を保つ責任」

  • 責任の本質
    • 変化に強い骨格を設計し、AIが生成する膨大なコードによってシステムの秩序が失われるのを防ぐ。状況に応じて最適な結合度を選択し、開発速度と保守性のバランスを制御する。
  • 必要なスキル例
    • ドメインモデリング: ビジネスの本質を、シンプルで強固なデータ構造に落とし込む力。
    • アーキテクチャ設計: 規模やフェーズに応じ、構成の分割単位と依存関係を定義する力。保守性と速度を天秤にかけ、チームの認知負荷に適した「最適な複雑さ」を選択する。
    • 技術的ガバナンス: 命名規則や設計思想の一貫性を守り、AIや人間が迷わず文脈を理解できる「ノイズのない場」を維持する力。
  • 具体的なアクション例
    1. 解決するべき複雑な事象を整理し、単なるデータの保存ではない「ビジネスルールを表現する」強固なデータ構造を定義することで、システムの整合性を担保する。
    2. 「疎結合」を目的化せず、チームの状況を考慮して、あえてシンプルさを優先するか、抽象化を導入して独立性を高めるかの損益分岐点を見極め、構成を決定する。
    3. 設計思想と実装の乖離を埋めるリファクタリングを主導し、AIや後続のエンジニアが「推測」なしに改修できる透明性の高いコードベースを維持し続ける。

② 価値成立の責任

「技術をプロダクトの価値へ翻訳し、確実な形として具現化する責任」

  • 責任の本質
    • 「仕様書通り」の遂行に留まらず、技術の力で事業の可能性を拡張する。ビジネスの意図を咀嚼した上で、プロダクトが進むべき理想の姿を自ら定義し、最短・最良の形で価値へと昇華させる。
  • 必要なスキル例
    • 技術要素の統合力: 自身の専門を軸としつつ、インフラからフロントエンドまでを地続きに捉え、ユーザー体験を成立させるために必要な要素を繋ぎ合わせる力。
    • プロダクト価値の定義: 要求の背景にある課題を技術的に解釈し、プロダクトとしての「あるべき品質や機能」を逆提案する力。
    • 仮説検証の設計力: 最初から完璧を目指すのではなく、ビジネスの仮説を最速で検証するための「価値の核」を特定し、素早いフィードバックループを回すための構成を提案する力。
  • 具体的なアクション例
    1. 自身の担当領域を仕上げた上で、APIの挙動や画面の操作感に矛盾がないかE2Eで点検し、必要であれば領域外の修正まで踏み込んで「体験」を完成させる。
    2. 不確実性の高い新機能において、フルスペックの開発に入る前に「価値の核」を確認するための最小実装(プロトタイプ)を提案・構築し、実戦での検証結果をもとに素早く軌道修正を行う。
    3. 実装段階で仕様の不備(あるべきプロダクト価値との乖離など)に気付いた際、専門家として代替案を提案・議論し、最適化する。

③ 継続性の責任

「プロダクトが社会的に生存し続け、信頼を維持するための責任」

  • 責任の本質
    • セキュリティや信頼性、そして経済的合理性を後回しにせず、プロダクトが持続するための絶対条件として維持する。
  • 必要なスキル例
    • 経済的最適化: インフラ構成やリソース消費をモニタリングし、パフォーマンスを維持しつつコストを最小化する力。
    • レジリエンスと可観測性: 異常の予兆を検知し、復旧プロセスをエンジニアリングで自動化する力。
    • セキュリティ・バイ・デザイン: 扱うデータの重要性やリスクを予見し、設計段階から堅牢な保護の仕組みを組み込む力。
    • ライフサイクル管理: 技術負債を適切に管理し、計画的なアップデートによってシステムの陳腐化を防ぐ力。
  • 具体的なアクション例
    1. リソースの利用効率を追求し、モデルの選定やキャッシュ戦略の最適化を通じて、事業利益を圧迫しない技術構成を実現する。
    2. SLO/SLIを定義し、「人が介入すべき真の異常」のみを抽出するアラート設計と、復旧手順の自動化を推進する。
    3. 認証基盤やデータ保護など、社会的な信頼に応えるためのセキュリティ基準を満たすアーキテクチャへの継続的な投資と改善を行う。

3. 実装者から「価値の設計者」へ

これらの責任は、生成AIが登場する以前からエンジニアリングの根幹を成すものでした。しかし、AIがコード生成の大部分を肩代わりする現在、その重要性はかつてないほど高まっています。

これからのエンジニアは、コードを書くという「手段」の習熟から解放され、エンジニアリングの「本質」に対してのみ注力する時代へと突入します。

  • 「作る」から「成立させる」へ

要件をコードへ翻訳するだけの段階は終わりました。これからのエンジニアリングとは、実装の先にある「構造の妥当性」や「経済的な持続性」を束ね、プロダクトを事業として成立させることそのものを指します。実装は責任を果たす過程で生まれる成果物であり、目的そのものではありません。

  • 「何を託すか」から「誰に託すか」へ

AIがコードを書ける時代だからこそ、事業推進者が抱く問いは「何ができるか」から「誰にこの事業の心臓部を託すべきか」へと変わります。三つの責任を引き受け、プロダクトの命運を担える「設計者」であること。それこそが、変化の時代において私たちが信頼され続けるための確かな指針です。


4. 専門性を起点とした、境界なきオーナーシップ

私たちは、個々のエンジニアが持つ深い専門性(モバイル、バックエンド、SREなど)を、三つの責任を果たすための強力な「武器」であると定義します。しかし、専門性を磨くことは、自らの貢献を特定の領域に閉じ込めることではありません。

  • 専門性は「目的を達成するための起点」

自身の得意とする領域において、卓越した品質で構造を保ち、価値を成立させ、継続性を守ることはエンジニアとしての「ベースライン」です。しかし、私たちの真の目的は技術の出力そのものではなく、プロダクト価値の完遂にあります。

  • 「価値の完遂」が行動の射程を決める

例えば「② 価値成立」の責任を果たす上で、自身のメイン領域の修正だけでなく、APIの仕様変更やデータベースの調整、あるいはインフラの構成変更が必要であれば、技術スタックの境界を自ら越えて課題解決に当たることを求めます。

  • 技術領域に安住しない

「ここから先は自分の領域ではない」という心理的な境界線を設けず、「プロダクトを成功させるために、今、解決すべき課題は何か」を最優先する姿勢が重視されます。専門性は、自身の貢献を規定するための枠組みではなく、より広範な課題を解決するための確固たる足場として機能させます。


5. 結びに「なぜこれらの責任が信託の基礎となるのか」

事業の推進者はエンジニアに「コード」を頼むのではありません。「技術を通じて、この事業を成功させてほしい」という願いを託します。

  • 構造があるから、明日も加速できる。
  • 価値成立があるから、ユーザーに届く。
  • 継続性があるから、安心して使い続けられ、事業が成り立つ。

この三つの責任を果たすこと。それこそが、私たちが専門家として信頼を受け、プロダクトに命を吹き込むための証明です。コードの先にある、事業の未来を共に創りましょう。


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。
ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

Rails8.0にアップグレードしたらinsert_allの振る舞いが変わった

こんにちは。メドピアのお手伝いをしている@willnetです。最近寒くなってきましたね。毎年この時期に風邪をひくので最近は手洗いうがいを怠らないようにしています。このたび久しぶりにテックブログに寄稿します。

insert_allの仕様変更に関するPR

メドピア内のとあるRailsアプリケーションのバージョンを7.2から8.0にアップグレードしたときにinsert_allの仕様変更に気づいた話をします。 insert_allの仕様変更に該当するのは次のPRです。

Fix active record insert values of type cast and serialize by OuYangJinTing · Pull Request #48139 · rails/rails

上記PRではsaveメソッドとinsert_all(及びinsertinsert_all!)メソッドで値の変換方法が一致していないのを修正しています。PRではString型のカラムにArrayやHashをアサインして変更内容を検証していますが、この変更でDateTime型やTime型のカラムの振る舞いも変わることがわかりました。

変更内容

前提として、次のようにRailsのタイムゾーンを"Tokyo"にしています。

class TestApp < Rails::Application  
  config.time_zone = "Tokyo"
end

そのうえで次のようにstart_timeカラム(Time型)、start_atカラム(DateTime型)に値をいれます。どちらも文字列を利用しているのがポイントです。

Post.insert_all([{ start_time: "12:00:00", start_at: "2025-10-29 12:00:00" }])

するとRails7.2では次のようなクエリが発行されます

INSERT INTO "posts" ("start_time","start_at") VALUES ('2000-01-01 12:00:00', '2025-10-29 12:00:00') ON CONFLICT  DO NOTHING RETURNING "id"

Rails8.0ではこうです。

INSERT INTO "posts" ("start_time","start_at") VALUES ('2000-01-01 03:00:00', '2025-10-29 03:00:00') ON CONFLICT  DO NOTHING RETURNING "id"

INSERTする時刻が9時間ズレてしまいました。

もともとRailsはsave時にタイムゾーン設定を考慮して、DBにはUTCに変換した時刻を入れるようになっています。しかしinsert_allを利用したときはsaveと振る舞いが違っていました。ActiveSupport::TimeWithZoneオブジェクトがアサインされたときはUTCへの変換が行われますが、文字列がアサインされたときにはタイムゾーン変換を行わずにクエリを発行しています。Rails8.0ではこの問題が解消されてinsert_allsaveが同じように振る舞います。

感想

これはバグフィックスとしては妥当な変更だと思いますが、結果として発行するクエリが変わるので、アップグレードガイドなどに変更する旨を記載するか設定で振る舞いを切り替えられるとより良かったように思えます。Rails8.0がリリースされる前にこの仕様変更に気づいてPRを出すべきだったので、やっぱり「定期的にRailsのedgeでCIを実行する」は実施すべきプラクティスだよな〜という気持ちを新たにしました。

この記事が今後Rails8.0にアップグレードする方の参考になれば幸いです。


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。
ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

Vue Fes Japan 2025 に参加してきました!

こんにちは、エンジニアの野里です。 今回は 2025年10月25日に開催された Vue Fes Japan 2025 に参加してきたレポートになります。

Vue Fes Japan 2025 のパネル前での集合写真

会場の雰囲気

私が参加してきたイベントの中では参加者が一番多く、「盛り上がってるなー」と感じました! あと小物なんかがとてもオシャレ!

広い会場でしたが休憩スペースがあったり、飲み物も提供されていたのでとても快適に過ごせました。

ブース出展

今年も弊社はブース出展させていただきました。

メドピア出展ブース前での集合写真

Vue.js、React、Svelte に関するアンケートを実施。 たくさんの方にご回答いただけました!参加していただいた皆様ありがとうございます!

アンケートの写真

「その他・該当なし」では Astro や SolidJS の名を挙げる方もおり、話が盛り上がりました。

参加したセッション等の感想(一部)

オープニング

ムービーがカッコイイ…引き込まれました。

youtu.be

Vue Fes Japan のロゴが変更され、Vue だけでなく Vite などエコシステム全体を表現するデザインになったことなどお話しされていました。 そういったところでも進化しているんだなと感じました。

さらに日英同時翻訳が表示されていて、参加する人が言語関係無くお話し聞けるようになっていたのもすごかったです。

キーノート

Evan You 氏によるキーノートの様子

Evan You さんによるキーノート。

Vue.js の成長、新機能や VoidZero のお話の中でも、少し前に発表されたフロントエンドのツールチェーン統合の Vite+ の話がやはり印象に残りました。 Rolldown や Oxc は触ったことが無いので少しづつでも試していきたいと思います。

生成AI時代のWebアプリケーションアクセシビリティ改善

個人的に大変興味があったやまのくさんのセッション。 AI に指示をしない方がアクセシブルなコードというのが衝撃的でした。

やまのくさんが MCP を作成してくれたりして使ってみたいと思ったのと、自分も勉強していかなくてはと改めて感じました。

フロントエンドの未来を語る ─ React/Vue.js/Svelte が見据える次の 10 年

「フロントエンドの未来を語る ─ React/Vue.js/Svelte が見据える次の 10 年」の様子

今回一番聞きたかったのがこちら。 Evan You さん、Dan Abramov さん、dominikg さんという、なんとも豪華なパネルセッションでした。

注目度も高く、会場は立ち見の方も含めてかなりの人数が参加されていました。

AIによるコーディングが可能になる時代においても、それを行うための知識は必要であり、引き続き学習が重要!これからもやっていくぞ!!と気合が入りました。

終わりに

セッションはもちろん参加している方と会話も出来、とても刺激を受けた一日でした。 Vue.js はもちろん、VoidZero にも注目していきたいです!

運営の皆様、本当にありがとうございました!

パネルにたくさんの寄せ書き


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

GitHub ActionsでTerraformを実行する

こんにちは、SREの侘美です。

今回はGitHub Actions上でTerraformを実行する環境を構築したので紹介させていただきます。 もともと利用していた Terraform Cloud から使い勝手や機能を悪化させることなく移行を目指し、様々な工夫を行ったのでそれらも合わせて紹介いたします。

移行の理由

弊社では2020年頃からTerraformの統一的な実行環境としてTerraform Cloudを採用していました。 Terraform CloudはTerraformのplan/applyの実行、チームとアクセス権の管理、モジュールレジストリ機能などTerraformに必要な様々な機能を備えているTerraform開発元であるHashiCorp製のSaaSです。

特に機能面で大きな不満なく利用できていたのですが、2023年5月にTerraform Cloudの料金体系の変更が発表されました。

https://www.hashicorp.com/en/blog/terraform-cloud-updates-plans-with-an-enhanced-free-tier-and-more-flexibility

端的に言うとユーザー数での課金から管理するリソース数での課金に変わるという内容でした。 この発表を受け直近の管理リソース数からコストを試算したところ、新料金プランでは従来の約12倍のコストとなることが判明しました。

ここまでの値上がり幅となってしまったのは、少人数のSREで効率化しながら多数のAWSアカウントを管理できるよう努力してきた成果の表れとも言えます。

弊社では契約タイミングの都合で新料金体系に切り替わるのは2025年10月と比較的猶予がある状況でした。そこで、これを機に別の実行環境への移行を検討し2025年9月に晴れてGitHub Actionsへの移行を完了させました。

前提

先に以下の説明に関連する範囲で弊社のTerraformの運用フローやリポジトリ構成を説明しておきます。

  • リポジトリはサービス単位(約50リポジトリ)
  • リポジトリ内に複数のTerraform Workspace(stg, prd, datadog, etc...)
  • PR上で terraform plan を実行
  • デフォルトブランチにマージ後、 terraform apply を実行

構成

まずは移行前のTerraform Cloudを採用した構成と、移行後である現在のGitHub Actionsを採用した構成をご紹介します。

Before

Terraform CloudでTerraformを実行

特に特殊な使い方をしているわけではなく、一般的なTerraform Cloudの利用方法をしていました。 リポジトリ内で envs/stg envs/prd のようにディレクトリを分けて、それぞれのディレクトリをTerraform Cloudのworkspaceに対応させる形で利用していました。

After

GitHub ActionsでTerraformを実行

移行後もTerraform Cloudのworkspaceがそのまま同リポジトリ内のActionsのジョブに置き換わった形になります。 なお、Terraform Cloudはモジュールのレジストリ機能のみ今後も継続して利用するため残しています。

工夫点

ここからは移行に際して、使い勝手を落とさないため/既存の課題を解決するために行った工夫点をいくつか紹介させていただきます。

PR上にplan結果のコメント

plan結果をPull Requestのレビュー時に確認するのは必須となっています。

Before

PR上のChecks

Terraform Cloudの場合、外部のCIなのでPR上のChecksに結果へのリンクが掲載される仕組みでした。 リンクを開いて(未ログインならTerraform Cloudへログインして)plan結果を閲覧するという流れです。

After

PR上のコメント(トグルを開くと詳細なplan差分を確認可能)

Actionsで実行したplan結果は、何も追加の実装を行わなければActionsのログで閲覧するしかありません。 そこで、 suzuki-shunsuke/tfcmt を使いActionsで実行したplan結果をPR上にコメントとして記載するようにしました。

「リンクを開かずともサッとplan差分を見られるようになった」「Terraform Cloudへのログインが不要になった」と開発者からの評判は上々です。

※ PRのコメントなので極端に長いplan差分は一部省略されてしまいますが、レアケースですしActionsのログを確認すれば閲覧可能なのでOKとしました。

applyのレビュー待ちと通知

Terraform Cloudにはapply前に最終plan結果を確認し、そのplan結果でapplyを行ってよいかの確認をはさむConfirmという機能があります。

Terraform CloudのapplyのConfirm画面

PR上のplanが実行された後、別PRまたは手動でAWS上でリソースが変更される可能性はゼロではありません。こういった意図しない変更が入った状態でPRマージ後にapplyを行うと、PR上で確認したplan結果とは異なる差分が発生する可能性があります。

本番環境など、万が一リソースを誤って変更・削除してしまうと一発で障害につながるような環境では、apply直前のplan差分のチェックが必須です。

Actions移行後もこのConfirm機能相当のフローを継続することにしました。 GitHubのEnvironments機能を利用して同等の仕組みを実現可能できそうな目処がたったので、これを採用しました。

  • リポジトリ内のTerraform Workspaceに対応するEnvironmentsを作成する
    • 例: stg, prd, datadog
  • apply前のplan結果確認が必要なEnvironmentsに対してレビュアーを指定する
    • 例: stg, datadog → レビュアーなし / prd → 開発チーム + SREチームをレビュアーに指定
  • マージ後に実行されるplan → applyを行うActionsのジョブにおいて、applyジョブに environment を指定する
jobs:
  plan:
    # 省略

  apply:
    needs: plan

    environment:
      name: prd
      
    # 省略

上記の設定を行うことで、レビュアーが指定されたEnvironmentのみマージ後のplanジョブ完了後に「DeployのApprove待ち」状態となります。

GitHub ActionsでのDeployのApprove画面

参考:

一方でTerraform Cloudの時代から、「開発者が忘れてapply前の確認待ち状態で止まっている」というケースが発生する問題がありました。 Terraform CloudからのSlack通知はあるのですが、メンションがないためその他の通知と共に埋もれてしまう点を改善することにしました。

Terraform Cloudの通知

そこでマージ後にapplyのApproveが必要なジョブに関しては、planジョブの最後でSlackにメンション付きで通知を送るようにActionsを実装しました。 通知対象はマージしたユーザーとなるように設定しています。

今回実装したActionsからの通知

Actionの共通化

弊社ではもともとメタデータ構文機能を使い、Actionsの実装の共通部分を別リポジトリ( actions リポジトリ )に切り出しています。

このリポジトリに新たに terraform-apply terraform-plan terraform-notification などを追加し、各サービスのTerraformリポジトリ上のActionsではこのリポジトリをチェックアウトして uses で指定して利用する形としました。

https://docs.github.com/en/actions/reference/workflows-and-actions/metadata-syntax

さらにactionsリポジトリ側でバージョン番号をtagで管理することで、利用側でclone時にバージョン指定して利用できるようにもしています。 このバージョン固定の仕組みを導入することで、破壊的変更を伴うアップデートもより安全に行うことができるようになりました。(常に最新のコードを参照する仕組みだと、 actionsリポジトリ側で破壊的変更を加えた際に、即座に全利用リポジトリ側を修正する必要がありました。)

      - name: Checkout Actions
        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
        with:
          repository: 'org_name/actions'
          ssh-key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
          path: 'mpg-actions'
          ref: v2.0.0

秘匿情報の管理

秘匿情報の管理に関しても、Terraform Cloud時代にあった課題を解決することができました。

Before

Terraform Cloud上のterraformで扱う秘匿情報のうち、複数のworkspaceに同じ秘匿情報を設定したいケースが存在していました。 主にAWSや各種SaaSの認証情報など、TerraformのProviderを実行するのに必要な権限がここに該当します。

これらは事前に手動でTerraform Cloud上のOrganizationのVariable Setsという形で登録し、利用するWorkspaceから参照可能に紐づけるという形で管理していました。この管理方法を採用することで、Workspaceが増えるたびに手動で設定しなければならない事態は回避できていました。

Terraform Cloudでの秘匿情報の管理

一方で、この構成では手動で秘匿情報を設定する以上「誰がいつなんの値を登録したのか後から確認が困難」「再登録などのために別途秘匿情報を管理する必要がある」といった課題が残っていました。

After

弊社では、GitHub上の一部のリソース(ユーザー、チーム、Branch Protection、etc...)をTerraformで管理しています。今回の移行時にはTerraform関連のリポジトリの設定もいくつかTerraformで管理するようにしました。

GitHubのOrganizationまたはRepositoryの秘匿情報をAPIで扱うために、暗号化の仕組みが提供されています。 Organization/Repositoryから暗号化のための公開鍵を取得し、手元で秘匿情報を暗号化し、その暗号化された値をAPIで送ることでGitHub内部で復号されつつ秘匿情報が登録される仕組みです。GitHubのTerraform Providerからもこの仕組みは利用可能です。 暗号化用のスクリプトを用意し、これを利用してリポジトリに登録するための暗号化をスムーズに行えるように構築しました。

全体の流れは下記の図のようになります。

Terraformを利用したGitHubのSecretsの管理

この仕組みを構築したことで秘匿情報を暗号化してコード管理できるようになり、設定経緯・管理・属人化といった手動設定にまつわるもろもろの課題を解決できました!

参考:

workspace一覧

Terraform Cloudで地味に重宝していたのがWorkspace一覧画面でのapplyエラーの確認です。

Terraform CloudのWorkspace一覧画面

(移行済みなので若干古いUIの時の画像しか用意できませんでした、ご了承ください。)

Confirmが必要なprdなどはきちんとapplyが正常に終了するまでチェックされることが多いですが、Confirm不要でマージしたらplan→applyと自動で流れるworkspaceに関しては、たまにエラーが放置されているケースがあります。

もちろんapply状況のSlack通知を行っているためそちらで気づいて対応するケースが大半ですが、それでも開発者によって、または通知先チャンネルによっては気づかずにエラーが放置されてしまうケースは発生してしまいます。

そこで全workspaceにアクセス権のあるSREメンバーがこのworkspace一覧を見ることで、エラーになったままのworkspaceに気づく、という使い方ができ重宝していました。

さて、Actionsへ移行した場合このworkspace一覧に相当する画面を用意することは困難です。 各apply Actionはそれぞれのリポジトリ内で動作します。GitHub自体にOrganization内のActionsを横断的に表示する機能があれば良かったのですが、現時点ではそのような機能は存在しません。

そこでStatus BadgeをWikiに掲載する方法を採用しました。 幸い、弊社ではOrganization配下のTerraform系のリポジトリのコードをスキャンし、自前で構築した静的解析を行い結果をリポジトリのWikiに掲載するシステムがすでに稼働していました。そこで、この静的解析の機能を拡張し、各Terraform系リポジトリのworkflowファイルを取得→workflowファイル名からStatus BadgeのURLを組み立て→それをWikiに反映という形で実装しました。 この静的解析は定期的に実行されるので、リポジトリやworkspaceが増減しても自動でStatus Badge一覧が更新されます。 Status Badgeはその仕組み上、閲覧するタイミングで最新のジョブの終了状態が反映されるので、これでTerraform Cloud時代のworkspace一覧に相当する一覧ページを再現することができました。

applyジョブのStatus Badgeリスト(Wiki)

※ Status Badgeのリンク先は各リポジトリのActionsページにしてあるので、エラーの確認がスムーズなのも地味に便利ポイントです

移行手順

移行は概ね下記の手順で行いました。 手順をドキュメントにまとめてSREメンバーで手分けして全リポジトリをActionsに移行しました。

  • plan差分が無い状態にする
  • terraform state pull でStateをローカルに取得
  • Terraform CloudからGitHub Actionsへの実装に変更
    • バックエンドをTerraform CloudからS3へ変更
    • plan/apply等のActionsを追加
  • terraform state push で新しいバックエンドのS3にStateをpush
  • PRを出す(Terraform CloudとActions両方でplanが実行される)
  • マージ後、Terraform Cloudを管理しているリポジトリで移行完了したリポジトリのworkspaceを削除するPRを出す
  • SlackのGitHub Appで通知を設定
  • 必要に応じてRemote Stateで参照している側の実装を修正

特にミスが発生することもなくスムーズに全リポジトリで移行できました。

課題

移行に際して既存の課題はあらかた解決できたのですが、一部で新しい課題も出てきたので紹介させていただきます。

ローカルからのplan

Terraform Cloudを実行環境に利用している場合、PRやデフォルトブランチにコードをpushした際のplan/applyの流れは下記の図のようになります。

Terraform Cloudにおけるpushをトリガーとするplan/apply

一方で、ローカルで terraform plan を実行すると、ローカルのコードをTerraform Cloudに転送してplanを実行し、その結果を表示することができます。

Terraform Cloudを利用しているリポジトリでのローカルでのterraform plan

これはTerraform CloudがTerraform開発元であるHashiCorp社製のサービスであるから実現できる仕組みとなっています。terraformコマンド自体に、バックエンドがTerraform Cloudだった場合にコードを転送してplanを実行するような仕組みが組み込まれているためです。 この仕組みにより、「コードをpushすることなく迅速に」「開発者間で統一された実行環境で」 terraform plan の実行が実現されています。

Actions移行後はさすがにこれと同様の仕組みを提供することはできませんでした。

AWSを管理するworkspaceの場合、ローカルで terraform plan の実行に必要な権限をもったIAM Roleを利用可能な開発者はローカルから実行することは可能です。 しかし、この権限をもったエンジニアは一部ですし、弊社の権限設計的にもあまり多くの開発者にこの権限を付与するのは望ましくありません。 また、AWSのように個々に権限を管理する仕組みが整備されている場合は実現の目処はありますが、GitHubやSendGridやDataDogなどの各種SaaSで同様に個々の開発者がローカルから terraform plan を実行するためのAPIキーなどを発行するのはリスクが伴います。

現状は高速にplan結果を確認したいケースはそこまで頻繁に発生しないことから、いったんはplanはコードをpushして必ずActionsで実行するような運用としています。

deployのapprove時にplanを確認する手間

「applyのレビュー待ちと通知」の項にて、メンション付きでマージしたユーザーに通知を送る仕組みを紹介しました。 通知自体は問題ないのですが、このDeployのApprove画面からplan結果を確認するためにActionsのログを開くのに数回のページ遷移とスクロールを行わなければならず、若干の手間となっています。

① Actionsの画面からplanジョブを選択
② planジョブの中のplanのstepのログを展開 & スクロール
③ planの差分内容に問題のないことを確認
④ workflowのトップに戻りReviewへ遷移
⑤ チェックを入れApprove & deployを選択

PR上にコメントでplan結果を表示したように、Actionsのworkflow画面にplan結果を表示することができれば手間をもっと減らせそうですが、現状はそういった機能は提供されていません。

今後の取り組み: RenovateによるAuto Merge

今まではDependabotを利用していましたが、Renovateを導入して以下を実現したいと検証中です。

  • 同一リポジトリ内のworkspace間でのPRの統一
  • plan差分の無いPRの自動マージ

上記を実現することで、さらなるリポジトリ管理の工数削減を目指します。

まとめ

弊社ではTerraform Cloudの新料金体系への移行タイミングを契機に、より安価でカスタマイズ性の高いGitHub ActionsへTerraformの実行環境を移しました。 Terraform Cloud相当の使い勝手を維持しつつ、既存の課題をいくつか解決するなどしてかなり使い勝手の良い実行環境を整備することができました。 今後Terraformを自動実行する環境を構築する方の参考になれば幸いです。


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

Kaigi on Rails 2025に参加してきました! #kaigionrails

はじめに

こんにちは。エンジニアの福田です。Kaigi on Railsお疲れ様でした!

今回はスポンサーとしてブースに立ちながら、参加者として複数のセッションを聴講しました。

この記事では、ブースでの交流から見えた肌感と、セッションで得た学びを、明日からの開発に活かしやすい形で書いていきます。

ジョブボードに名前を書きました!

スポンサーブース出展しました!

Day1

一日目はAIについてのパネルアンケートを実施しました。

多くの来訪者の方々とツール選定や使い心地について話しました。開発支援ツールはClaude CodeとCursorの利用されている方が多い印象で、生成AIを使った作業がすでに日常的に活用されていると改めて感じました!

また、弊社でも「AIファーストカンパニー」として導入と運用を推進しており、現場の現実感と重なる点が少なくありませんでした。

意外にもQ3の「生成AIの待ち値時間に何をしているか?」という問いには、「祈る」と答える方が想像以上に多かったです。皆さんはどれを選びますか?

Day1のパネルアンケート

Day2

二日目は今年一月にリリースした「ClinPeer」の技術選定ブログClinPeer Railsプロジェクトの技術選定(2025年版) - メドピア開発者ブログを題材にクイズを実施し、来訪者の方々と交流できました!

ブースを担当していく中で私自身まだ知らなかったGemがあったので、とても刺激になりました。新しくリリースした「ClinPeer」を含め、弊社を知っていただく良い機会になったと思います!

ぜひ、一度クイズの答えを考えてみて上記のブログを参照していただけると幸いです!

Day2のパネルクイズ

聴講したセッション紹介

私が聴講したセッションから一部感想を書かせていただきます。

5年間のFintech × Rails実践に学ぶ - 基本に忠実な運用で築く高信頼性システム

kaigionrails.org

運用とは「ビジネスを可能にすること」——まさにその通りだと感じました。指揮・連絡・一次調査といったガイドラインの制定、規制対象の機能を本体から分離するという「Citadel」の考え方も大きなヒントになりそうです。

また、アラートやエラー通知の量を減らし、トリアージのガイドラインを整えるのはとても有効だと思いました。あわせて、アラート通知に対して過去の対応手順書から類似内容を提案する仕組みもぜひ試してみたいです。これならドメイン知識が少なくても一定の時短につながりそうです。まずは「Runbook(ランブック)」としてドキュメントを残すことを意識します。

ビジネスを可能にするためには、運用の積み重ねが重要だと改めて実感したセッションでした。

Railsアプリケーション開発者のためのブックガイド

kaigionrails.org

技術を学ぶときに、どんな書籍を買えばよいか、学生のときから今でも迷います。この発表は、技術情報の紹介も含めてとても参考になりました。

セッションからは外れますが、実際に本屋さんでお話しさせてもらい、2冊ほど買わせていただきました。説明を受けながら本を買える体験は、なかなかないのではないかと思います。貴重な体験でした。

どんどん積読していきます。

2分台で1500 examples完走!爆速CIを支える環境構築術

kaigionrails.org

こちらは30分以上かかっていたCIを2分台までに短縮するお話でした。私個人はなるべくパフォーマンスを上げるようにコードを書くのかなと思っていました。ですが、並列実行で分散しつつ、時間のばらつきを平準化して最長区間を短縮できるものでした。そこまで時短できるのかという驚きました。

そこから物理マシンを強化して一気に詰めるアプローチはとても大事かもですね。個人的に試してみたいですね。 まずは計測から始めていこうと思います。

rails g authenticationから学ぶ Rails 8.0 時代の認証

kaigionrails.org

弊社技術顧問の前島さんのセッションです。

最近プライベートで認証機能を実装する際に認証ジェネレータを利用しましたが、理解できたとは言えませんでした。 ですが、セッションにある通りコードを読んでいくことで認証機能のベースを理解する良い機会です。

聴講前は、Sessionモデルを定義する理由が不明でした。ここでは「ユーザーが乗っ取られた時に、ログインセッションを無効化する」が一番大きい理由だと知りました。

has_secure_passwordを宣言するとpssword_reset_tokenが使えるのはRails 8 からなのを初めて知りました。自動で有効期限がつくのは便利ですね。

認証機能はセキュリティに直結することなので、魔法だと思わずに情報のキャッチアップを続けていきます。

さいごに

私自身、初参加でしたが、想定以上の刺激を受けました。新しく学んだことをアウトプットしていきます。来年もぜひ参加したいです。


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

MedPeer で負荷試験基盤を構築しました - 定量的な計測のための精度の担保と自動化のコツ

はじめに

こんにちは。サーバーサイドエンジニアの近藤です。

先日、私が担当している MedPeer (医師・医学生向け医療情報サイト) にて負荷試験環境を構築しました。

本記事ではその取り組みについてご紹介します。

背景

MedPeer ではユーザーである医師会員向けに、薬剤情報などの講演会を生放送で配信するための、Web講演会というサービスを提供しています。

このWeb講演会サービスでは、講演会の終了時刻付近に講演会のアンケートフォームが開くことで、総リクエスト数が急増する傾向があります。

大量アクセスによるシステムの負荷上昇が懸念され、同時間帯に開催できる講演会数に制限があることがビジネス上の課題でした。

そこで、許容できるWeb講演会の開催規模を定量的に計測し、システム改修によってその上限を引き上げることを目的に、このたび負荷試験環境を構築する運びとなりました。

また、将来的に機能やインフラの追加・改修を行う際に、性能への影響を事前に評価できる環境を整えたいという目的もありました。

開発の指針

負荷試験環境の開発にあたり、下記の3つの指針を定めて開発を進めていました。

  1. 誰でも安全に負荷試験をできる環境が用意されていること
    • 負荷試験の実施において複雑な手順やインフラの専門的な知識を要求されず、メンバーの誰もが負荷試験を実施できる環境を目指しました
  2. 計測したい負荷 (Web講演会の同時開催時の大量アクセス) を正しくかけることができ、その負荷を計測できるようになっていること
    • 試験結果の信頼性を担保するため、Web講演会のピーク時に発生する複雑なアクセスパターンを忠実に再現できるよう、実装における工夫を取り入れました
  3. まずは最低限動く状態を目指して作ること
    • 最初から完璧な環境を目指すのではなく、まずは主要な課題を解決できる最小限の構成を目指しました

開発において判断に迷った際はこの指針を基に意思決定を行いました。

システムの概要

全体像

今回の負荷試験は、既存の Staging 環境を「試験中だけ本番環境相当にする」というコンセプトで行います。

具体的には、試験の実行中のみインフラの性能を本番環境と同等まで一時的に引き上げ、その環境に対して大量のリクエストを送信することで負荷試験を行います。

構成図

各要素の役割

  • 負荷生成 (k6 サーバー)
    • 負荷生成ツールには k6 を採用しました。専用サーバー上で k6 を実行し、シナリオファイル(リクエスト内容を定義したファイル)に基づいて試験対象へリクエストを送信します
  • 自動化 (GitHub Actions)
    • 試験の準備から実行、後片付けまでの一連のフローは GitHub Actions Workflow で自動化し、誰でも手軽に試験を実施できる体制を整えました
  • モニタリング (Datadog)
    • 試験中の主要なメトリクスは Datadog のダッシュボードでリアルタイムに可視化し、試験結果をすぐに確認できるようにしています
  • ダミーデータの準備
    • より本番に近い状態で試験を行うため、インフラ性能だけでなくデータベースの状態も重要です。システム構成図には記載がありませんが、事前に Staging 環境のデータベースへ大量のダミーデータを投入し、本番環境のデータ規模を再現しました

各システムの構成

ここからは、負荷試験環境を構成する各要素について、その詳細や採用理由などを解説します。

負荷試験環境のインフラ構成

スケールアップ対象の要素

負荷試験の実施中は、主に以下の要素を本番環境と同等の設定値へ変更します。

  • アプリケーションサーバー (Amazon ECS)
    • タスク数
    • CPU・メモリの割り当て量
  • Web サーバー (Unicorn)
    • ワーカー数
  • データベース (Amazon RDS)
    • インスタンスサイズ

実装方式

この動的な環境変更は、Terraform の Input変数機能を使って実現しました。

インフラ構成に load_test_mode という真偽値 (boolean) の変数を定義しました。この変数を true にすると、Terraformの三項演算子によって各リソースが本番環境相当の設定値で再構築される仕組みです。

resource "aws_db_instance" "rds" {
  instance_class = var.load_test_mode ? (本番環境の設定値) : (Staging環境の設定値)
  ...

設計上の判断と見送ったこと

開発速度とコストのバランスを考慮し、以下の2つの判断を行いました。

  1. 実装コストを考慮した初期スコープの絞り込み
    • 完全な環境再現には多大なコストがかかるため、まずはアプリケーションとデータベースといった主要な要素にスコープを絞りました。Redis などのキャッシュサーバーについては、試験結果からボトルネックとして顕在化した場合に、次のステップで対応する方針としています。
  2. 既存の Staging 環境の活用
    • 負荷試験専用の環境をゼロから構築する案もありましたが、実装工数の増大が懸念されたため見送りました。既存の Staging 環境を活用することで、迅速に試験環境を準備できました。

負荷発生サーバーのインフラ構成

負荷を発生させる k6 の実行環境は、試験対象のアプリケーションサーバーとは完全に分離された、専用の Amazon ECS タスクとして構築しました。

実行の仕組みと性能設計

負荷試験の実行フローは以下の通りです。

  1. CI/CD パイプラインで、k6 本体とシナリオファイルを含む Docker イメージを事前にビルドします。
  2. 試験実行時には、このイメージからコンテナを起動し、内部で k6 コマンドを実行する仕組みです。

サーバーのスペック(CPU, メモリ)は、k6 の公式ドキュメントを参考に決定しました。ドキュメントでは 1VU (仮想ユーザー) あたり約 1〜5MB のメモリを消費するとされており、これを基準に、想定する負荷量に対して十分なリソースを割り当てています。

Datadogとの連携

MedPeer ではモニタリングツールとして Datadog を採用しています。

ですが k6 から Datadog へのメトリクス送信は、標準ではサポートされていません。

k6 の公式ドキュメントを参考にし、StatsD プロトコルでメトリクスを送信できる拡張機能 (xk6-output-statsd) を組み込んだ k6 をビルドして使うようにしました。

JMeter・Grafana Cloud k6 との比較

k6 の他の主流な負荷試験用ツールとして、JMeter を利用する選択肢もありました。ですが JMeter はGUIのソフトウェアであることや、シナリオに XML ファイルを利用するためロジックの記述・再利用が難しそうなこと、k6 よりもメモリ使用量等のパフォーマンスが悪いことなどの理由から、k6 を選択しました。

また、k6 の実行環境として SaaS である Grafana Cloud k6 の利用も検討しましたが、実施コストを比較した結果、今回は自社環境にセルフホストする方式を選択しました。

実施コスト

負荷試験1回あたりの金銭コストとしては、模倣する本番環境やかける負荷の量次第ではありますが、今回構築した負荷試験環境・シナリオの場合、4時間の負荷試験環境の立ち上げで75$ほどのコストとなりました。Staging 環境の ECS サーバーと k6 を動かすサーバーの費用が大部分を占めているため、予めある程度はコストの予測ができると思います。

自動化された負荷試験ワークフロー

負荷試験の一貫性と再現性を担保するため、準備から片付けまでの一連のプロセスを GitHub Actions で自動化しました。これにより、誰でも安全かつ手軽に試験を実施できます。

ワークフローは、大きく分けて以下の4つのフェーズで構成されます。

1. 準備

まず、試験実行に伴う不要なアラートを防ぐため、Staging 環境の監視ツールを一時的に停止します。その後、Terraform を通じて環境を本番相当のスペックへスケールアップします。

name: Setup Load Test Environment

on:
  workflow_dispatch:

jobs:
  ...
  setup-load-test:
    steps:
      ...

      - name: terraform plan
        ...
        env:
          # STG_LOAD_TEST_MODE には true が入る
          TF_VAR_load_test_mode: ${{ vars.STG_LOAD_TEST_MODE }}

      - name: terraform apply
        ...

2. 実行

次に、負荷試験本体を実行します。このフェーズでは、純粋な負荷生成だけでなく、試験データの整合性を保つための処理も自動化しています。

  • データ準備・リセット: 試験シナリオによっては、特定のデータベースレコードの作成、更新、削除が必要です。これらの処理は Rake タスクとして定義し、k6 の実行前後で自動的に実行することで、常にクリーンな状態で試験を開始・終了できるようにしています
  • 負荷試験の実行: 事前にビルドされた Docker コンテナを起動し、内部で k6 コマンドを実行して負荷を発生させます
name: Run Load Test

on:
  workflow_dispatch:
    inputs:
      ...

jobs:
  before-load-test-task:
    steps:
      - name: Execute Before Test Rake Task
        run: |
          aws ecs run-task
            ...
                  "command": [
                "bundle",
                "exec",
                "rake",
                "load_test:'${{ github.event.inputs.test_scenario }}':before_test"
              ]
            ...

  run-load-test:
    steps:
      - name: Execute k6 Scenario on ECS Task
            run: |  
              ...
              aws ecs run-task
                ...
                "command": [
                  "k6",
                  "run",
                  ...
                  "load_test/${{ github.event.inputs.test_scenario }}.js"
                ]
                ...
        
  after-load-test-task:
    steps:
      - name: Execute After Test Rake Task
        run: |
          ...

3. 片付け

試験終了後、停止していた監視ツールを再開し、スケールアップした Staging 環境の性能を元の状態へ戻します。

name: Cleanup Load Test Environment

on:
  workflow_dispatch:

jobs:
  ...
  cleanup-load-test:
    steps:
      ...

      - name: terraform plan
        ...
        env:
          # STG_LOAD_TEST_MODE には false が入る
          TF_VAR_load_test_mode: ${{ vars.STG_LOAD_TEST_MODE }}

      - name: terraform apply
        ...

4. 確認

最後に、Datadog のダッシュボードで試験結果を評価します。

分析を効率化するため、ダッシュボードには2つの工夫を加えました。

  1. Staging/本番兼用ダッシュボード: ダッシュボードの変数機能を活用し、本番環境のモニタリングで普段から使っているダッシュボードをそのまま負荷試験にも流用しています。これにより、環境を切り替えるだけで同じ指標を比較できるようになります
  2. k6 専用ダッシュボード: 上記とは別に、負荷サーバー自体の CPU 使用率や k6 が収集したメトリクス(リクエスト数、レスポンスタイム等)を可視化する専用ダッシュボードも用意し、k6 サーバー側が過負荷に陥っていないかを手軽に確認できるようにしました

Datadog のダッシュボード

シナリオファイルの作成

負荷試験のシナリオファイルは、「どのようなリクエストを、どのくらいの量・頻度で送るか」を定義します。このセクションでは、実際のトラフィックを再現するためのシナリオ作成プロセスと、実装上の工夫について解説します。

リクエストパターンの調査

最初に、負荷試験の対象とする状況(例:講演会終了間際のピーク時)を定め、その時間帯のアクセスログを分析します。ここでの目的は、リクエストの総量だけでなく、時間経過に伴うリクエスト数の増減パターン(トラフィックの波形)を把握することです。このパターンを基にシナリオを作成します。

シナリオファイルの実装

次に、分析結果を基に k6 のスクリプトを作成します。http.getsleep を組み合わせてシナリオを記述します。

// 例
export default function () {
  http.get(`${host}/top`)

  sleep(1)

  http.get(`${host}/about`)
}

実装時には、負荷試験特有の注意点があります。

  • POST リクエストにおける CSRF トークンの処理を忘れないこと
  • 意図しない外部ドメインへのリダイレクトを防止すること

このような問題を特定しやすくするため、最初は 1 VU など、ごく少数のユーザーから試験を開始するのが有効です。

dry-run モードの導入

「ユーザーごとにリクエストタイミングをずらす」などの複雑な要件が加わると、コードから意図したリクエストパターンが生成されるかを確認するのは困難です。

この課題に対し、シナリオスクリプトに dry-run モードを導入しました。これは、HTTPリクエストや sleep を実際には実行せず、予定されていたリクエストのログを高速に出力する機能です。

  • sleep 処理の代替: dry-run モード時は、実際の待機処理をスキップし、内部で管理する論理的な時刻のみを進めます
  • HTTP リクエスト処理の代替: HTTP リクエストも実際には送信せず、予定時刻や URL といった情報を記録するのみとします
  • 結果の比較: dry-run の実行後、記録されたログを集計・グラフ化します。このグラフを、最初に調査した実際のトラフィック波形と比較することで、シナリオの妥当性を事前に検証します。

dry-run結果の出力

宣言的なシナリオ定義

複雑なリクエストパターンをループと sleep で記述する命令的な方法は、変更が全体に影響しやすく、メンテナンス性に課題がありました。

この問題に対し、より直感的にシナリオを定義できる宣言的なアプローチを導入しました。これは、再現したいトラフィックの波形を、アスキーアートで描くように定義することで、そこから各リクエストのタイミングが自動算出される仕組みです。

// アスキーアートでリクエストの分布を定義
const topPageAccessDistribution = distribution(`
  ###
  #####
  ##########
  #######
  ####
  ##
`)

実装の詳細は控えますが、このアプローチにより、複雑なシナリオの可読性とメンテナンス性が向上したと思います。

負荷試験環境の活用

負荷試験環境を利用して早速、本番環境では過去に事例が無い規模のアクセスピークを模した負荷試験を実施しました。

これにより、現行システムの性能上限と、システム増強によってどの程度性能を向上できるかの見込みを、定量的に把握できました。

また、今後は新規機能の追加やインフラ構成を変更する際に、性能への影響を事前に計測する環境としても負荷試験環境を活用できます。

この環境の構築により、システムの負荷を定量的に把握できるようになっただけでなく、試験実施にかかる検証コストも大幅に削減できました。

おわりに

これからも負荷試験の知見を活かしながら、安定したサービス提供と事業の成長の基盤となる仕組みづくりを続けていきます。

本負荷試験環境構築プロジェクトは、筆者の他にサーバーサイドエンジニア2名とSRE2名、技術顧問をしていただいた外部のアドバイザーの方1名、その他多くの方々のご協力により完了させることができました。ご協力いただいた皆様に改めて感謝いたします。

最後までお読みいただきありがとうございました。


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

OpenID Connect Authorization Server を Golang 実装から Rails 実装にリプレイスした話

皆様こんにちは、メドピアのサーバーサイドエンジニアの内藤(@naitoh)です。

社内の OpenID Connect Authorization Server を Golang 実装から Rails 実装にリプレイスしたので、技術ブログで紹介させていただきます。

前提

OpenID Connect は OAuth 2.0 認可プロセスを拡張し、認証目的で利用できるようにしたものになります。

Authorization Server は OpenID Provider (以下OP) とも呼ばれ、OpenID Connect を利用する OAuth 2.0 Client は Relying Party (以下 RP) とも呼ばれます。

OpenID Connect フロー概要は OpenID Connect Core 1.0 にある通り下記になります。

+--------+                                   +--------+
|        |                                   |        |
|        |---------(1) AuthN Request-------->|        |
|        |                                   |        |
|        |  +--------+                       |        |
|        |  |        |                       |        |
|        |  |  End-  |<--(2) AuthN & AuthZ-->|        |
|        |  |  User  |                       |        |
|   RP   |  |        |                       |   OP   |
|        |  +--------+                       |        |
|        |                                   |        |
|        |<--------(3) AuthN Response--------|        |
|        |                                   |        |
|        |---------(4) UserInfo Request----->|        |
|        |                                   |        |
|        |<--------(5) UserInfo Response-----|        |
|        |                                   |        |
+--------+                                   +--------+

背景

弊社では 過去 OP を PHP (認証エンドポイント) と Golang (トークンエンドポイント) のハイブリッド実装で提供を行なっていました。 ただ、ハイブリッド実装に伴う複雑性を解消するために 2022年に認証エンドポイントを PHP から Golang 実装にリプレイスを行い Golang 実装への統一を行いました。

これに伴い、複数言語間をまたぐ複雑性は解消されたのですが、弊社のバックエンドエンジニア採用は Rails エンジニアが主であり社内で Golang をメンテナンスできるメンバーが限られていたため、実質的に内藤一人が保守する体制になってしまいました。

サービス継続性の観点から、持続的なメンテナンスを可能にするため Rails への再リプレイスを実施したので、今回その話をさせて頂きます。

リプレイスにあたって検討した点

稼働中のサービスになるため、互換性を維持しつづ、今後のメンテナンス体制を確保するのが最大の目的です。

  • メンテナンス体制
    • 社内の Rails エンジニアリソースを活用して保守を行なっていくため、 Rails を用いた実装を行う。
    • メンテナンスを容易にするため、なるべく自前実装は避け、既存の gem ライブラリを活用する。(ただしドキュメントが少ないと引き継ぎが厳しいので、ドキュメントがあると良い。)
    • OP だけでは、動作確認が困難なため、検証用の RP も用意する。
    • 既存のモノリスに OP を統合する事で、別リポジトリでの管理を避け一体的なメンテナンスが行えるようにする。(ただし、可能ならモジュラーモノリス的な疎結合にしたい。)
  • 互換性
    • OpenID Connect として現在使用している機能が実現可能。(OpenID Connect で OPTIONAL 扱いの機能はライブラリ側で未サポートの場合があるため。)
    • スムーズに移行するため、RP とのインターフェース(リクエストパラメータ)は変更しない。 (社外の RP が存在するため弊社都合での変更はハードルが高い。)
    • RP によっては同意済ステータスを引き継ぐ必要があり、旧データベースの情報を参照する必要がある。

なお、本サービスは主に医師会員向けのサービスであり、日本国内の医師数は40万人程度なので、新たな性能面の要求はありませんでした。(=現状維持できれば良い。)

移行にあたっての gem の選定基準

主に下記が検討候補として考えれました。

  1. OAuth/OIDC Component as a Service のような BaaS を使う。
  2. openid_connect gem を用いて実装する。
  3. Rails エンジンの仕組みの doorkeeper gem (OAuth 2 provider) + doorkeeper-openid_connect gem (OpenID Connect 拡張) を用いて実装する。

このうち、1 は予算の観点から特に検討しませんでした。

2 の openid_connect gem は、社内では OP としての 利用例は無いが RP としては長年利用しており、最初に候補に上がったのですが、OP のドキュメントが will write someday.. と無かったため、導入に躊躇いがありました。

3 の doorkeeper 本体は、非常によく使われているため特に問題なさそうでしたが、 doorkeeper-openid_connect は、Looking for maintainers! とメンテナーを募集中であり、継続性に不安がありました。

ただ、doorkeeper 本体のメンテナーでもある Nikita Bulai 氏が doorkeeper-openid_connect gem のサポート(バグ修正&改善のみ、新機能の追加は対象外)を行う事になり、継続的にリリースがされているので、この点では問題は無さそうと判断しました。

また、ドキュメントの観点では、下記が非常に充実しており、リプレイスにあたり必要な機能(promptパラメータ等)がサポートされているか、カスタマイズの観点で何をすれば良いか記載が豊富なのも重要な点でした。

doorkeeper.gitbook.io

github.com

また、Rails エンジンで実装されており、既存のRails モノリス統合しても密結合を避けられるためモジュラーモノリスの観点でも良さそうでした。

OpenID Connect 検証環境の構築

上記を踏まえ、一旦 3 の doorkeeper gem + doorkeeper-openid_connect gem で検証環境を構築し、評価を進めていくことになりました。

下記の実装例を参考に、手順通り実装すれば OP を構築できた & 実際に動作させながら必要な機能が期待通り動作するか、カスタマイズが意図通り行えるかを検証できたのが大きかったです。

thinkami.hatenablog.com

※ 上記は RP の実装に omniauth-oauth2 gem を使われていますが、omniauth_openid_connect gem を使う事で、さらに実装量を減らしレビューコストを削減することができました。

評価の結果、 doorkeeper gem + doorkeeper-openid_connect gem は新規に OP を提供する場合は導入しやすい事がわかったのですが、カスタマイズの観点(特に既存DBにデータがある場合)からは課題があることが見えてきました。

データの移行方針の決定

この課題とは、doorkeeper-openid_connect のテーブル構造が既存DBのテーブル構造と異なるため、既存のリクエストフローとデータ保存のタイミングが異なるため、doorkeeper-openid_connect をカスタマイズしても既存DBをそのまま使い続ける事が困難な事がわかりました。

コードベース切り替え時に、既存DBをそのまま使い続ける事ができればシームレスなサービスリリースが可能になります。

具体的にはメンテナンス画面表示を用いたサービス停止が不要になり、仮に本番リリース時に問題があった場合でもコードを切り戻せばリリース前の状態にすぐに復帰する事が可能です。

ただ、これを実現するために既存DBのテーブルに無理やり doorkeeper-openid_connect のデータを書き込むと処理が複雑になり技術負債となってしまうため、残念ながらこの方針は断念し、既存DBテーブルのレコードのデータカラムを再構成し、新しく doorkeeper-openid_connect 用に用意したテーブルにデータマイグレーションする方針にしました。

これにより、リリース時は一旦サービスを停止し、メンテナンス画面を表示する対応となったのですが、メンテナンス表示対象を OpenID Connect を使用するサービスに限定する事ができたので、ビジネス影響を最小化する事で対処しました。

採用結果

最終的にOPの実装として、下記の点から 3 の doorkeeper gem + doorkeeper-openid_connect gemを採用することに決定しました。

  • 互換性
    • リプレイスにあたりOpenID Connect として必要な機能が実装されている。
    • RP とのインターフェースを(ルーティングパスのカスタマイズ定義は必要だが)維持できる。
    • 同意済ステータスを引き継ぐために、データマイグレーションは必要だが、マイグレーションをしてしまえば、複雑なカスタマイズは回避できる。
  • メンテナンス体制
    • 導入するにあたり、OpenID Connect 周りの処理はほぼ設定ファイルの記載で対応可能 (認可画面はデフォルトの画面が用意されているが、既存の Golang 実装の画面を erb で実装しました。)なため、メンテナンス対象のコードベースを最小化することで、OpenID Connect に詳しく無い開発者でもメンテナンスを容易にする。
    • 動作検証用に管理画面に omniauth_openid_connect gem を用いた簡易RPを用意することで、 単一リポジトリでのOPの動作確認を可能にし、開発環境構築周りのトラブルを回避する。

リリース作業

無事、開発が完了したので、下記の流れでリリース作業を実施しました。

  1. ユーザーに対しメンテナンス時間を事前告知し、不要なアクセスを回避します。
  2. CloudFront 側で、簡易的なメンテナンス画面を表示し、不要なアクセスが発生しないようにしました。
  3. maintenance_tasks gem を用いてデータマイグレーションを行いました。本番環境での手作業によるリスクを回避し、進捗状況もわかる非常に重宝している gem です。詳しくは下記をご覧ください。 tech.medpeer.co.jp
  4. Rails 単体であれば flipper 等を用いたフィーチャーフラグでの切り替えを行うのですが、今回は別言語間でリプレイスなので、この手法は使えず、CloudFront 側でルーティング対象を切り替え可能にする事で対応しました。
  5. 本番でテスト用アカウントを用いて動作検証を行い、問題無い事を確認できたので、メンテナンス表示を削除し、無事リリースできました。 🎉

結果

OpenID Connect Authorization Server を Rails に統合する事で、複数リポジトリ、複数言語間にまたがったシステムの複雑性を解消し、一つの Rails モノリスに無事統合する事ができました。

Rails エンジンを用いる事で疎結合になっているので、今後の複雑性を回避できそうなのもポイントです。

その結果、OpenID Connect 関連のその後の機能改修で内藤が担当する事なく(レビューアーとしては参加)、他のメンバーが改修を行う事ができており、チームでのメンテナンス体制の確立という目的を無事達成することができました。

振り返り

前回の移行作業では、互換性を維持しつつ技術負債の解消を第一に取り組んだのですが、入社後あまり時間が経っていない時期に移行計画を立てたため、社内のエンジニア採用計画を踏まえたメンテナンス体制の重要性まで考慮できていなかったのが一番の敗因でした。

自分だけがメンテナンスできても、それは自分自身の首を絞めるだけなので、チームとしてメンテナンスできる体制を確保できるかが重要ですね。


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp