「HRMOS」シリーズでは多数存在する機能間での体験を統一するための様々な取り組みを続けています。
今回、HRMOSプロダクト本部プラットフォーム開発部では、ユーザー体験の向上とアーキテクチャ改善を目的として 「HRMOSタレントマネジメント」 内に複数あった認証基盤を統合しました。
本記事では、認証基盤統合に至った背景と、それをスピーディかつ安全に進めるために行ったプロセスや工夫をご紹介します。 構想から完遂までの約6ヶ月間の内、設計およびプロトタイプ作成に要した前半3ヶ月、実際に本番システムに段階的に変更を加えていった後半3ヶ月という大まかな流れに沿って説明します。
統合前の課題
「HRMOSタレントマネジメント」は内部的に「従業員データベース機能」を提供するモジュールと「評価管理」などの機能を提供するモジュールに分かれています。実はこれらのモジュールはそれぞれ個別の製品として同時期に開発をスタートしたのですが、開発途中でひとつの製品に統合した経緯があります。
この際にログインIDは統合され OpenID Connect で各モジュールへログインできるようになったものの、ログイン状態については各モジュールのセッションストアでの管理がその後も続いていました。このため「従業員データベース機能」にはログインできているのに「評価管理」には未ログイン状態、といったチグハグな状況を生んでいました。
認証統合のためのアーキテクチャ
認証統合にあたっては、いくつかのアーキテクチャを検討しました。最終的には以下の記事で 「JWT 利用 + API Gateway パターン」 として紹介されているパターンを採用しました。
Microservices における認証と認可の設計パターン - Please Sleep
API Gateway で Cookie 内の SessionID をセッションストアに照合し、有効なセッションであれば短命な(数分程度)JWTをヘッダーに含めてバックエンドサービスにリクエストを転送します。バックエンドサービスは予め取得しておいた公開鍵でJWTを検証することで認証します。
我々がこのアーキテクチャを採用した理由は大きく3つあります。
- フロントエンド側の認証が従来通りのステートフルな認証方式
- ユーザー情報を含むトークンをクライアントサイドで保持するステートレス認証を新たに採用するのは、追加の検討コストを要するため避けたい
- バックエンド側の認証はステートレス
- API Gateway からリクエストを受けたバックエンドサービスが、さらに別のサービスを呼び出す場合に有利
- バックエンドサービス間でホップ毎の認証のための通信が発生せず、これに起因する通信オーバーヘッドやネットワークエラーを無くすことができる
- 横断的な関心事をオフロードするための機構
- API Gateway を拡張可能なコンポーネントとして設計・導入したい
- 認証以外にも存在する、モジュール毎に重複した機能実装やUXの分断などの課題解決に活かしたい
- 具体的には、IP制限や Rate Limit などの機能をもたせる想定
一方でこのアーキテクチャでは API Gateway に高い性能と可用性を求められるのに加え、システム全体の複雑度が高まるリスクがあります。 安全に認証基盤を統合しリリースするため、これらのリスクを最小化する様々な工夫が求められました。
認証統合までの大まかな流れ
認証基盤の統合にあたっては「HRMOS」シリーズの各モジュールに影響が及ぶので、エンドユーザーへの影響を最小限にするため各モジュールを段階的にリリースする方法を採用しました。
認証基盤のアーキテクチャの検討から実際の導入までのプロセスの中でも、特に力を入れた部分である取り組みを2点紹介します。
1. ドキュメントとプロトタイプを用いた複数チームとのコラボレーション
冒頭でも記載したとおり認証基盤の統合にあたっては、「HRMOS」シリーズの各モジュールに影響があり、QA・SRE・セキュリティ・モジュール開発チームなどあらゆるチームの協力が不可欠でした。 そのため、関わり方や関わるタイミングが異なるチームのコラボレーションを進めるにあたり、参加者全員がプロジェクトの概要を理解できる設計ドキュメントを作ることに力を入れました。 また、ドキュメントの内容を補完するものとして、プロトタイプを用意し、設計と実装の抽象度のバランスを保ちながらプロジェクトをすすめました。
1-1. 設計ドキュメントについて
複数のチームのコラボレーションの媒体となる設計ドキュメントを作成しました。 設計ドキュメントは大きく4つのパートで構成しました。
背景
背景は、組織内で一定の共通認識が取れていましたが、今まで発生した事実ベースの課題を記載することでソリューションありきにならないようにしました。
プロジェクトゴール
プロジェクトゴールは、主にスケジュールの観点で、システムがいつまでにどういう状態になっているかについて記載し、各チームがいつなにをやるべきか分かるようにしました。
解決方法
解決方法は、全体構成図を図示して概要がわかるようにしつつ、ルーティングや内部の認証方式など、変更がある部分については具体的な設計にも踏み込み、システムの具体的な変更点が分かるようにしました。
検証・検討内容
検証・検討内容は、なぜ今回の解決方法を選んだかの意思決定の過程を記録し、採用された背景を後から分かるようにしました。
また、関わる関係者に比例してドキュメントに求められる観点が膨大になり、設計がどこまでも広がっていくことになるため、ドキュメントでは「やること・やらないこと」を明文化しながらスコープの調整をして対応しました。
チーム全員でドキュメントを作成する方針を採用したことで、 抽象度の高いプロジェクトに対して複数人で多角的に観点を考慮できたことや、チーム内で理解度に偏りが発生しないという恩恵がありました。 また、ドキュメントに何を記載するかについてはチーム全員で目次レベルで検討し、それぞれの項目ごとに担当を割り当てて進めました。 作業は非同期で進めつつも週次で進捗のシェア・レビュー・困ったことがあればその都度解消し、プロジェクトに対するインプットとアウトプットを並行して進めることができました。
設計ドキュメントをSREチームに共有できたので、このあとに実施するプロトタイプを用いた性能検証に必要なインフラの用意にむけてSREチームに事前に動き出してもらうことができました。
1-2. プロトタイプについて
ドキュメントに記載された内容について、機能検証・性能検証・セキュリティ診断の3つの観点を検証するためにプロトタイプを開発しました。 それぞれの観点について具体的に何をしたのか、他チームとどのようなコラボレーションをしたのかについて紹介します。
機能検証
既存機能の振る舞いを損なわずに、認証方法の変更にともなうAPI Gatewayの新設が可能かどうかを調査する目的で実施しました。
開発チームやQAチームと検証観点を洗い出し、実際にプロトタイプを動かすことで、 当初想定していなかった下記のような特殊なエンドポイントの振る舞いを発見することができました。
- 認証処理があってもなくても通過させたい処理
- IP制限のためのヘッダー転送
- エンドポイントの種別(SSR/API)に応じたエラー処理
性能検証
「HRMOS」シリーズのほぼすべてのトラフィックをさばくAPI Gatewayを新設するため、性能的な劣化がないこと、将来的なユーザー増加に対応できることを検証する目的で実施しました。
性能検証ツールの k6 で言及されている Test Types の観点を参考に実施したのは以下の3点です。
- 既存のシステム利用状況を再現して性能劣化がないことのテスト (Load Testing)
- ピーク時のアクセスやサービス成長による将来的なユーザー増に対するテスト (Stress Testing / Spike Testing)
- 長時間稼働によるメモリリークなどの問題はないかのテスト (Soak Testing)
観点設計、性能検証をするための環境構築、それに伴うインフラコストの見積りなど、 性能検証にともなう準備をSREチームが担ってくれたため、 自チームは性能検証のためのツール選定・シナリオ作成に専念できました。
Load Testingでは、ブラウザベースのplaywrightを使って、実際のユースケースにそった負荷を再現できました。
セキュリティ診断
認証に関わる機能改修にあたるため、セキュリティ診断が必要でした。 設計と実装の2段階でセキュリティチームに確認を依頼しました。
設計レベルのセキュリティ診断のために、全体構成図(コンポーネント図)、シーケンス図、各エンドポイント同士でどんな情報をやりとりしているかの情報を設計ドキュメントに追加しました。
セキュリティチームからのレビューをもとに、設計上のセキュリティ懸念がなくなるまでドキュメントをアップデートし、プロトタイプを開発して、セキュリティ診断を実施しました。
機能検証/性能検証/セキュリティ診断を通じて複数のチームとコラボレーションするうえでは、設計ドキュメントとプロトタイプがチーム間での共通認識を取るための心強いツールとなりました。
2. 段階適用によるダウンタイムゼロに向けて
プロトタイプの検証をしたことで、機能要件・非機能要件をみたし、本番に適用できる仕組みになっていることがわかりました。 しかし、プロトタイプではシステムの最終的な状態を実装して検証したため、これをそのまま本番に適用する場合、システムを停止してメンテナンスする必要があることも分かりました。
システム停止による入れ替えでは、手順や実装が単純になるメリットがある一方で、 ユーザーが利用できない時間ができること、 不具合や障害のリスクが高い・影響範囲が広いこと、複数の開発チームがメンテナンス時に待機する必要があることなど、マイナスの面も多くあります。
特にユーザーが利用できない時間が発生する方法は避けたいと考え、システムに段階的に変更を入れていき、最終的な完成状態まで、ダウンタイムゼロで反映する方法を採用することにしました。
ダウンタイムゼロで本番に反映するために取り組んだことを2つ紹介します。
2-1. ロールバック可能性を担保しながら段階的にリリース
1つ目は、API Gatewayを通る新しいリクエストと通らない既存のリクエストの両方で動作するアプリケーションに変更しました。 実装は複雑になってしまいますが、一部の環境やユーザーのみがAPI Gatewayのテストができるようになりました。
今回はリクエストヘッダーなどに特別な値をセットすることで、開発チームのみがAPI Gatewayを通るリクエストを発生させることができるようになり、本番環境であっても先行してAPI Gatewayのテストができました。
さらに、ユーザー影響が少ないモジュールからAPI Gateway経由に切り替えることでリスクを減らし、問題が発生した場合も一瞬でロールバックできる状態にしておきました。
2-2. 計画の全体のタイムラインを作成し他チームと調整
段階的に適用する・ロールバックできるようにする仕組みを選択した結果、複数の関連モジュールのリリースタイミングや、 それらの開発・QA・セキュリティ診断などの日程の複雑度が高まっため、事前にタイムラインを作成し、他チームと適宜共有をしながら進めました。
段階的にモジュールの状態を変えていくため、プロトタイプでは検証できていない、いくつかの状態を経由することになります。 それにより必要になるQAやセキュリティ診断などを事前に見つけることができ、より安全なリリース計画にできました。
おわりに
システム全体的かつ組織横断的な変更をスムーズに進めるために活用したプラクティスを紹介しました。
- 設計ドキュメントとプロトタイプを事前に用意したことで、チーム間のコラボレーションが円滑になり、手戻りを最小限に抑えられた
- 段階的に適用することで、ユーザーが利用できない時間を発生させずダウンタイムゼロでリリースできた
- システム停止を伴うリリースでは日程を変更するのが難しいためバッファをとった計画になってしまう一方、比較的柔軟にスケジュールできるリリース方式にしたことで当初の想定よりも早く認証基盤を統合できた
一方でいくつかの課題も見えてきました。本プロジェクトの完遂後に API Gateway への変更により障害を発生させてしまいました。影響範囲の大きさと要求される性能水準の高さを鑑みると、リグレッションテストの整備や性能監視の体制の必要性は明らかであり、現在絶賛改善中です。
またシステム間通信のためのネットワーク構築が煩雑だったり、部分的に分散トレーシングがうまく機能していない箇所が残るなど、分散システムを支える基盤の強化に取り組む必要性も感じています。
次回の記事では、今回あまり触れられなかった実装の詳細に踏み込んで、どのように API Gateway を構築したか紹介します。