[toto]Laravelにおける多言語サポートの構築:テンプレートベースと変数ベースのアプローチの比較



はじめに

こんにちは、Racing & Lottery開発部(RLD)でアプリケーションエンジニアを務めるJuliusです。本記事では、Laravelを使用した、Webアプリケーションの多言語対応システムを実装した際の私たちの経験を共有したいと思います。

国際化されたアプリケーションを構築する際は、適切なローカライゼーション戦略を選択することが非常に重要です。それは、ユーザーエクスペリエンスだけでなく、開発効率、保守コスト、セキュリティ上の考慮事項にも影響を及ぼすからです。私たちのサービスは、デスクトップとモバイルどちらのインターフェースにおいても、日本語や英語など、複数の言語をサポートする必要がありました。

実装の過程で、主に以下の2つのアプローチを検討しました。

・Laravelの標準的な変数ベースのローカライゼーション

・完全なHTMLファイルを言語ごとに分離するテンプレートベースのアプローチ

2つの戦略の内容、そのトレードオフ、多言語コンテンツを扱う際の重要なセキュリティ上の考慮事項について、私たちが学んだことをご紹介します。

 

言語検出戦略

多言語アプリケーションにおける最初の課題の一つは、ユーザーに対して表示する言語を決定することです。私たちは、複数のフォールバック層を持つ優先順位ベースの検出メカニズムを実装しました。

優先順位:

  1. URLパラメータ - ?locale=enによる明示的なユーザー設定
  2. Cookie - 前回のセッションから保持されたユーザー設定
  3. Accept-Languageヘッダー - ブラウザの言語設定
  4. デフォルト設定 - アプリケーションのデフォルト(通常は日本語)

 

言語検出フロー

 

コード実装

 

セキュリティ上の考慮事項

  • 入力検証:インジェクション攻撃を防ぐため、常にlocale値を許可されたホワイトリスト(enum)に対して検証する
  • Cookieセキュリティ:locale Cookieが適切にスコープされ、検証なしでユーザー制御データを含まないようにする
  • パストラバーサル:検証なしでファイルパスに生のlocale値を使用しない - enumまたは厳密なホワイトリストを使用する

 

テンプレートベースのローカライゼーションアーキテクチャ

テンプレートベースのアプローチでは、各言語ごとに個別のビューファイルを維持します。システムは、フォールバック階層を使用して、ユーザーのロケールに基づいて適切なテンプレートを自動的にロードします。

ビューディレクトリ構造

 

ビュー解決とフォールバック階層

モバイルデバイスの場合:

 

デスクトップの場合:

 

コード実装

セキュリティノート:ロケール値にenumを使用することで、ディレクトリトラバーサル攻撃を防ぎます。生のユーザー入力をファイルパスに連結しないでください。

 

Laravelの標準ローカライゼーションアプローチ

Laravelの組み込みローカライゼーションは、翻訳文字列がHTML構造とは別に保存される変数ベースのシステムを使用します:

参照: https://laravel.com/docs/12.x/localization

 

翻訳ファイル

ビューでの使用方法

 

比較:テンプレートベース vs 変数ベースのローカライゼーション

側面

テンプレートベース(個別のHTMLファイル)

変数ベース(Laravel標準)

HTML最適化

容易 - 各言語が独自のHTML構造を持つ

困難 - 単一の構造がすべての言語に適合する必要がある

メンテナンスファイル

言語数で乗算される

単一のビューファイルセット

フォールバック動作

デフォルト言語テンプレートにフォールバック(グレースフル)

変数ごとのフォールバック(言語が混在する可能性)

デザイナーワークフロー

デザイナーが独立して完全なページを作成可能

デザイナーは開発者と協力して構造/コンテンツを分離する必要がある

QAコスト

すべての言語バージョンを徹底的にテストする必要がある

一部の言語の組み合わせをスキップする可能性

セキュリティリスク

低 - 言語ごとの静的コンテンツ

高 - XSSを防ぐためにすべての翻訳文字列をサニタイズする必要がある

コンテンツ長の柔軟性

高 - 各言語が異なるレイアウトを持つことができる

低 - 最も長い翻訳に対応する必要がある

翻訳の欠落

デフォルト言語ページを表示(完全なUX)

翻訳キーまたは混在コンテンツを表示(壊れたUX)

 

多言語アプリケーションにおけるセキュリティ上の考慮事項

1. クロスサイトスクリプティング(XSS)の防止

変数ベースのローカライゼーションでは、ユーザー生成コンテンツが言語ファイルに保存されている場合、翻訳文字列がXSS攻撃のベクターになる可能性があります:

 

2. ロケール検証とセキュリティフロー

コード実装サンプル

3. コンテンツセキュリティポリシー(CSP)

多言語コンテンツを提供する場合、CSPヘッダーが適切に設定されていることを確認してください:

4. 文字エンコーディング

エンコーディングベースの攻撃を防ぐため、常に適切な文字セットを設定してください:

 

所感と学んだこと

このローカライゼーションシステムの実装から、いくつかの貴重な教訓を学びました:

  1. グレースフルデグラデーション:フォールバック階層により、特定の言語テンプレートが欠落している場合でも、ユーザーは常に完全で機能的なページを見ることができます。
  2. チームコラボレーション:テンプレートベースのアプローチにより、デザインチームが独立して作業できるようになり、開発速度が大幅に向上しました。
  3. メンテナンスのトレードオフ:より多くのファイルを維持していますが、明確な分離により、変更が特定の言語/デバイスの組み合わせに分離されるため、実際には更新が容易になります。
  4. パフォーマンスの考慮事項:ビューパス解決は最小限のオーバーヘッドを追加し、Laravelのビューキャッシュにより、本番環境でのパフォーマンスへの影響は軽減されます。

セキュリティファースト:最初からロケール入力を検証することで、潜在的な脆弱性を防ぎました。enumの使用は、型安全性とセキュリティにとって重要でした。

 

結論

Webアプリケーションの多言語対応を構築するには、ユーザーエクスペリエンス、開発ワークフロー、保守コスト、セキュリティなど、多くの要素を慎重に検討する必要があります。

  Laravelの標準的なローカライゼーションは多くのユースケースで優れていますが、私たちは、ユーザーに提供する複雑なコンテンツが豊富なアプリケーションを扱う際に、テンプレートベースのアプローチが独自の利点を提供すると判断しました。

  本記事が、他のチームがローカライゼーション戦略を検討する際の参考になれば幸いです。

すべてのアプリケーションには独自の要件があります。しかし、トレードオフを理解することで、安全で保守可能なコードを維持しながら、正しい判断が可能となり、ユーザーに適切なサービスを提供できるようになります。

 

※掲載内容は2025年12月25日時点のものです。

私たちと一緒に働きませんか?

コマース&マーケティングカンパニーでは、新たなサービス開発から日々の運用・改善まで、一緒に働く仲間を募集中!エンジニアやプロダクトマネージャーなど、幅広い職種で募集しています。ご応募をお待ちしております。

 →採用情報はこちら

「楽天 GORA」 混乱を乗り越えシステムへ:GRXデザインシステム構築を実現するまで


皆さん、こんにちは!ゴルフ場の検索や予約サービスである「楽天 GORA」の UIチームです。

今回は、私たちがゼロから構築したデザインシステム「GRX(Gora ReX - Rakuten Experienceより)」の開発により、学び、達成したことをお話しします。

 

デザインシステムとは?

デザインシステムとは、企業のすべてのインターフェースのデザインと機能を定義する標準やコンポーネント、共通言語のことです。

ボタン、フォーム、テーブルなど、マージンのデザインを毎回決めるのではなく、明確な使い方の説明書のある既製の「レゴブロック」があると考えていただくとよいでしょう。

具体的には、デザインシステムとは、以下になります。

  • デザイントークン: すべての製品で共通する基本プロパティ(色、フォント、スペーシングなど)のセット
  • UIコンポーネント: ボタン、フォーム、テーブル、モーダルウィンドウなど、ユーザーインターフェース (UI) を構成する再利用可能な個々の部品
  • パターンとルール: コンポーネントの連携方法や、各コンポーネントの使用方法
  • ドキュメント: 新しくメンバーに加わった人でも、容易に仕組みを理解するための記録

 

デザインシステムの意義

開発スピード

デザインシステムによって、コンポーネントの準備が完了してテストを行う際に、既存のブロックから追加するだけですみます。そのため、ゼロからコードを書く必要がなくなります。

MVPを素早くローンチしたり、アイデアをテストする際には特に有用です。より完成度の高い製品開発が可能となるからです。

 

一貫性 = 認知度

一つの製品を利用したユーザーなら、別の製品の使い方も理解しやすくなります。
これにより、以下の効果が期待できます。

・UXの向上
・参入障壁の低減
・問い合わせの減少
・ブランド認知の強化

 

スケーラビリティ

デザインシステムによって、新しいプロジェクトであっても、ゼロからスタートする必要はなくなります。チームに新しい開発者やデザイナーが参加する際も、共通のシステムがあることで、彼らは各プロジェクトの独自のスタイルを学ぶ必要はなくなります。

 

負担の削減

プロジェクト毎のコンポーネント管理だったものから、コンポーネントが統一されることで、プロジェクトごとにデザインの更新は不要となり、一ヶ所を変更することで、すべてに反映されます。

 

品質

各プロジェクト毎にテストされていたコンポーネントが、一度のテストで済むため、バグが出にくく、整合性が高くなります。その結果、より品質の高い製品開発をより速く実施できます。

 

GRXの開発を実施した理由とその方法

独自のデザインシステムを開発するに至った要因は以下の2点です。

・チームの負担を軽減するため

・チームに一貫性のある成熟したプロセスを実装する必要性があったためデザインシステムは海外ではすでに標準であり、スタートアップではありません。楽天もグローバル企業として、デザインシステムを導入するタイミングがきていました。

 

初日から製品構築を実施

本プロジェクトでは、プロトタイプではなく、初日から、製品構築を実施することにしました。シンプルなボタンや入力フィールドから、カレンダーやデータテーブルのようなより複雑な要素まで、コンポーネントの完全なセットを計画しました。

GRXはVue 3 + TypeScriptで構築されています。最初から、Storybookでのドキュメント、Vitestでのユニットテスト、そしてビジュアルリグレッションテストなどの完全なインフラを整えました。

これほど本格的なアプローチを行った理由は、デザインシステムは3〜4個程度のコンポーネントのセットでは機能しないからです。少なくとも基本的な要素がカバーされていなければ、インターフェースを構築できず、製品は従来のスタイルのままになり、効果がないのです。

 

: Storybookのコンポーネントページの例。開発者とデザイナーがすべてのpropsを確認し、コンポーネントがどのように機能するかの例を見られる、唯一の信頼できる情報源

 

開発の状況

当初GRXに取り組んでいた開発者は一人だけでした。そのため、基本的なコンポーネントを含むベータ版がリリースされたのは6ヶ月後でした。モーダル、カレンダー、コンボボックスを含むより完全なシステムの完成は1年後です。

初期バージョンが完成し、概念実証を実施した後、より早く開発することになり、まず開発ローテーションで実装を行いました。各チームメンバーが1〜2ヶ月間手伝うことになり、内部オープンソースプロジェクトのように、少なくともそれぞれ一つのタスクを担当しました。

しかし、この試みでは、期待ほど開発のスピードアップにはつながりませんでした。

それは、メンバーの経験知が異なっていたこと、主要プロジェクトに加えた作業が負担になったこと、新しい製品であるGRXに深く関わることの難しさなどが理由として挙げられました。最終的に、私たちは2人目の専任の担当者を割り当て、問題を解決しました。

この経験から、デザインシステムは専門的な技術を持つ、専任のチームがあたるべきだと考えます。そうなれば、デザインシステムの安定性が担保され、他のプロジェクトの影響を受けずにすむでしょう。一般的に、専任のデザインシステムがあることが、グローバルでは標準なので、当社でもいずれそうなることを願っています。

 

デザインプロセスの構築

デザインシステムは開発者だけではなく、デザイナーも同じくらい重要な役割を果たすため、この点で新たな課題が生じました。

1年間で、デザイナーが3回交代しましたが、全員がデザインシステムの経験があったわけではなく、徐々に原則を理解し、熟練していく必要がありました。

デザイナーの最大の課題は、従来の思考を変えることです。今まで、デザイナーは特定のタスクのために、任意のコンポーネントを作成してきましたが、今回はシステム的に考える必要があります。既存のソリューションを使用するか、新しいものが必要な理由を説明するかです。

もう一つの課題はFigmaのインターフェースです。Figmaのインターフェースは、システムロジックを変更する必要がある場合でも、通常のプロパティのようにコンポーネントトークンを変更することが可能です。そのため、デザインシステムに慣れていないデザイナーはコンポーネントのpropと同じくらい簡単に変更しようと考えてしまいます。しかし、そうすることで一貫性がなくなるため、コードは機能しなくなります。そのため、モックアップに戻ってやり直さざるを得なくなるでしょう。

これらのデザインの問題点に対して、7年のデザイン経験とデザインシステムでの作業経験を持つ、メインGRX開発者が大きな助けとなりました。彼はデザイナーに技術的な制限を説明し、開発者とデザイナーとの橋渡し役となりました。この働きは、基本的なプロセスの構築と、デザインシステムの理解の確立に非常に役立ちました。

今後、同様の不備を防ぐために、デザインシステムの理解を確立するプロセスを強化し、社内でトレーニングコースを設けることを計画しています。このトレーニングは、デザイナーだけでなく、将来デザインシステム(特にGRX)の使用を検討しているチームにとっても有用です。システマティックなアプローチを簡素化し、新しいプロジェクトでの実装をスムーズにすることがこのトレーニングの目的です。トレーニングに興味がある方は、ぜひお問い合わせください。

 

: Figmaのコンポーネントページの例。デザイナーはすべての制限とガイドラインを文書化し、すべてのコンポーネントバリアントを組み立てます。このドキュメントはデザイナーが準備します。

 

意識改革:GRXはツールではなく、製品である

最も予想外の課題は、技術的なものではなく、私たちの意識でした。

当初は、私たちはGRXを通常のツールであり、各特定のプロジェクトのニーズに合わせて調整できるものと考えていました。例えば、「ここの角を少し変えましょう」とか「このケースでは、このコンポーネントを少し違うものにできますか?」といった感じにです。

しかし、デザインシステムは製品であるため、独自のユーザー(デザイナーと開発者)、独自のロジック、独自の制限とルールがあります。また、その完全性を壊さずに「フィットさせる」ことはできません。ツールはタスクに適応しますが、製品は使用のルールを設定するものだからです。

各チームがニーズに合わせてコンポーネントを修正し始めたら、以前の一貫性のない状態に戻ってしまいます。デザインシステムは、みんなが同じルールで運用されているときにのみ機能します。

意識改革は簡単ではありませんでしたが、GRXを製品として扱い始めたとき(独自のドキュメント、ロードマップ、機能リクエストプロセス、バグ処理を含む)、うまく機能し始めました。もちろん、まだすべての場所で機能するには、さらに時間が必要です。

 

開発者からのフィードバック

GRXデザインシステムを組み込むことにより、以下の効果が得られました。

・EOLプロジェクトで時間短縮が可能となる

・チームがビジネスロジックに集中できる

 

しかし、もちろん課題もありました。

初期段階の実装であったため、一部のGRXコンポーネントにバグがあり、開発またはQAフェーズ中に修正する必要がありました。

これは、初期段階で準備しておく必要がある事象です。いくつかの複雑なコンポーネント(Combobox、Calendar、Datepicker)は、プロジェクトと並行して、厳しい締め切りの中で作成されました。そのため、この場で修正しなければならないバグや、不十分な機能が生じてしまいました。理想的ではありませんが、スピード感のある開発を実施するためのトレードオフだと考えています。

また、デザイナーとエンジニアリングチームが、コンポーネントの使い方について見解が違うことがあります。それは、役職によって、コンポーネントを異なる視点で見ているので、当たり前のことです。初期段階でしっかりとコミュニケーションをとり、複雑なコンポーネントの議論にすべての役職(プロダクトマネージャーを含む)を関与させることが解決策となります。

 

経験を積むことで成長できる

GRXを使用するプロジェクトは初めて実施するので、課題をクリアする経験を積むことで、GRXをより安定的に扱うことができるようになります。

私達は課題を解決することを投資と考えています。最初のプロジェクトは困難が多く、時間がかかりますが、次のプロジェクトは、その分時間も短縮できます。

GRX開発者が、システムを正しく発展させるためには、デザイナーの構造化されたフィードバックが重要です。また、ユニットテストとVRTだけでは不十分でした。そのため、本番環境の前にバグをキャッチできるように統合テストとe2eテストを実施する必要が明らかになりました。

 

: Vitepressドキュメントのコンポーネントページの例。開発者が必要とするprops、slots、events、コードスニペット付きの使用例などが含まれる。

 

制限は機能であり、バグではない

主なフィードバックとして、柔軟性が低くなり、作業が難解になったという指摘もあり、これは事実です。現在では、「好きなように」コンポーネントの新しいバージョンを作成できません。既存のものを使用するか、新しいものが必要な理由を説明しなくてはいけません。

しかし、これが重要な点で、制限は、以下のような理由から、ビジネスのために必要なことなのです。

  • 制限によって、細部についての議論に費やす時間が減る
  • 時間を節約できるため、実際のユーザーの課題解決により集中できる
  • 各ソリューションがすべての製品にスケールする
  • 基盤が準備されているため、新しいプロジェクトが速く始められる

デザインシステムの導入当初は、時間がかかります。しかし、新しいツールの使い方を学ぶときは、どんなものでも同じです。一度導入できれば、その後の開発スピードは何倍も早くなるでしょう。

 

現在の状況と今後の展望

約1年半で私たちは、50のコンポーネントを開発しました。コンポーネントは、シンプルなボタンから複雑なデータテーブル、カレンダーまでそれぞれにドキュメントとテストがあり、製品として完成されたものです。また、すでにGRXを使用した3つのプロジェクトをリリースしました。
*RBS Lesson、OB Line、Green Trimming Toolなどの社内ツール。

実際にGRXがどのように機能するか、理解していただくために、RBS Lessonのページを以下に提示します。このページはトークンとコンポーネントの両方を実装したもので、タイポグラフィ(タイトル、サブタイトル、コンテンツ)にGRXトークンを使用し、GrxButton、GrxCalendar、GrxSidebar、GrxTableなど、複数のGRXコンポーネントを使用しています。

 

: GRXで構築されたRBS Lessonのページ。

これまでのプロジェクトは、まだ始まりに過ぎません。近いうちに、さらに多くのプロジェクトへの展開を計画しています。私たちの直近の計画は以下の通りです。

 

パターン

コンポーネントに加えて、典型的なシナリオ(ログインフォーム、フィルター付きテーブル、ダッシュボード)のために、既製のソリューションであるパターンを作成します。パターンの作成によって、チームはインターフェースをさらに速く構築できるようになります。

 

他のプロジェクトやチームへの展開

私たちは、部門内だけではなく、社内の他のチームへのGRXの適応を支援することが可能です。ライブラリにアクセスできるようにするだけではなく、チームのトレーニング、プロセスの設定、テーマ設定の実装を支援しています。

 

テーマ設定

現時点のGRXは、テーマトークンの変更のみ可能です。しかし、よりスケールアップを図るには、他のチームや製品が個々で設定を変更できるようにする必要があるでしょう。そのため、Tailwind(CSS フレームワークのこと)と同様に、プロジェクトレベルで、よりグローバルなコンポーネント設定を可能にする計画です。

 

モバイルデバイス

モバイルアプリケーションへのGRXの適応を検討しています。いずれは、すべてのプラットフォームでユーザー体験を一貫させる予定です。

 

他のフレームワークへの対応

現在、GRXはVueで構築されていますが、チームによって使用する技術が異なることをよく理解しています。私たちにとって重要なのは、どのフレームワークを使用するかではありません。デザインの一貫性を保ち、情報源を一つにまとめることです。

デザインの一貫性を保つための方法は、以下の2つが挙げられます。1つは、ReactやSvelteのような各チームのフレームワークに向けて、GRXを適応させるための支援を実施して、一緒に構築することです。これにより品質と一貫性は保証されますが、継続的なコラボレーションと長期的なコミットメントが必要です。専任のGRXチームが実施すれば、スムーズに実施しやすくなるでしょう。

もう1つは、フレームワークに依存しないアプローチです。デザイン・トークンとガイドラインを提供し、各チームが独自に適応できるようにします。この方法は導入しやすいですが、一貫性が失われるリスクが高く、各チームが同じ問題を何度も自分たちで解決する必要が生じるでしょう。そのため、今のところ、私たちは最初のアプローチがより良いと考えていますが、このことも引き続き議論中です。

 

お互いに経験談を共有しませんか?

私達はGRXの開発を続けており、同じ問題の解決にあたっているみなさまと経験と知見を共有したいと考えております。

私達のチームは、みなさまと以下のような情報交換を通じて、相互に貢献できると確信しています。 

  • GRXがどのように機能するかを示す
  • デザインシステムの実装へのアプローチについて議論する
  • 私たちが直面したレッスンと課題を共有する

デザインシステムは単なるコンポーネントライブラリではありません。開発のスピードをアップし、製品をより良くするシステマティックな作業の文化だと私達は考えています。

 

※掲載内容は2025年12月12日時点のものです。

 

レジャープロダクト部(LPD)のエンジニアによる以下のテック記事も併せてご覧ください。

 

私たちと一緒に働きませんか?

コマース & マーケティングカンパニー ライフ&レジャーサービス開発部(LLSD)では、新たなサービス開発から日々の運用・改善まで、一緒に働く仲間を募集中!エンジニアやプロダクトマネージャーなど、幅広い職種で人材を募集しています。ご応募をお待ちしております。

 →LLSDの採用情報はこちら

→全ての採用情報はこちら

【LPD】楽天におけるカオスエンジニアリングと入門ハンズオン

The English version of this article is here.

 

こんにちは。楽天グループのレジャープロダクト部(LPD)のソリューションエンジニアリング課に所属しているシニアシステムエンジニアのBurakです。私の役割は、楽天グループのミッションに沿って、当社のシステムを効率的かつ確実に動作させ、革新的で高品質な製品を提供することです。

サービスの向上につながる課題解決と、ソリューションの実装に注力することで、楽天グループが業界のグローバルリーダーになるという目標に貢献しています。

レジャープロダクト部(LPD)では、ダウンタイムが顧客の信頼を損ない、収益の損失につながる可能性があることを認識しています。この問題に対処するため、サイトリライアビリティエンジニアリング(SRE)の実践にカオスエンジニアリングを採用しました。テスト環境に意図的に制御された障害を導入することで、ユーザーに影響を与える前に、システムの弱点を事前に特定して対処します。このアプローチにより、当社のシステムは堅牢で信頼性が高く、お客様にシームレスな体験を提供できる状態を維持できます。

 

カオスエンジニアリングとは

カオスエンジニアリングとは、システムの弱点を特定し、信頼性を向上させるために、障害やエラーを意図的にシステムに導入することを指します。ネットワーク障害、アプリケーションエラー、突然のトラフィックスパイクなど、実際に起こり得る障害シナリオを想定した実験を、インフラストラクチャやアプリケーション構成を変更することなく設計することが含まれています。

この非侵入的な方法により、システムの元の設定との整合性を維持しながら、現実的なシナリオで回復力を計る複数の実験を、迅速かつ簡単に行えます。

たとえば、ユーザートラフィックの急増をシミュレートしてスケーラビリティの評価や、偽のデータを注入してシステムがエラーをどれだけ効果的に処理できるかのテストが可能です。これらの実験によって、システムの脆弱性が発見され、重大な問題に発展することを避けられます。これにより、ダウンタイムや利益の損失、顧客に不満を感じさせるリスクを軽減できます。結果としてサービスの信頼性向上とシームレスな顧客体験を向上させることができました。

カオスエンジニアリングの採用は、すでに楽天グループでは測定可能なメリットをもたらしています。この能動的なアプローチは、顧客の信頼を強化し、業界での競争力を維持するのに役立っています。

今日の複雑な分散システムアーキテクチャでは、カオスエンジニアリングは、システムが予期しない中断にも対処できる能力を構築するために、不可欠です。カオスエンジニアリングにより、当社のシステムの信頼性を高め、未来のオンラインサービスの課題にも対処できるようになります。

 

カオスエンジニアリングとサイトリライアビリティエンジニアリング(SRE)

サイトリライアビリティエンジニアリング(SRE)(以下、SRE)は、大規模な分散システムの信頼性を高め、それを維持し、改善することに特化した分野です。主なSREの目標には、高可用性の確保、パフォーマンスの最適化、スケーラビリティの実現などが含まれます。カオスエンジニアリングはシステムの回復力のテストや、システムを強化するための構造化されたアプローチを提供することで、SREの目標を補完します。
カオスエンジニアリングとSREを組み合わせることで、障害やトラブルに耐えられる信頼性の高いシステムを構築するための、強力なフレームワークを形成できます。

カオスエンジニアリングは、障害をシミュレートを実施することにより、ボトルネック、単一障害点、見過ごされがちな重要な依存関係の特定に役立ちます。たとえば、カオスエンジニアリングの実験では、データベースの停止をシミュレートして、システムが重要なコンポーネントの損失を適切に処理できるかどうかを明らかにすることがあります。この能動的なアプローチにより、チームはユーザーに影響を与える前に脆弱性を改善できます。

制御された実験では、システムがどの程度障害から適切に回復できるかを評価します。たとえば、特定のサーバーやサービスをシャットダウンして、システムがトラフィックを再ルーティングし、機能を維持できるかどうかを確認するテストなどがあります。これらの実験により、予期せぬ中断が起きた場合でも、ダウンタイムが発生せず、システムが設計通りに動作し続けられます。
カオスエンジニアリングでは、突然のトラフィックスパイクなどのシナリオをシミュレートして、システムがリソースを迅速にスケールアップし、需要の増加に対応できるかどうかをテストできます。たとえば、販売キャンペーン中のトラフィックの急増をシミュレートして、システムが負荷の高い状態でもパフォーマンスを維持できるかどうかを確認することが可能です。

 

ツールとテクニック

楽天グループは、カオスエンジニアリングを効果的に実装するために、さまざまなツールとテクニックを活用し、システムの回復力と信頼性を確保しています。

私たちが主に使用している主要なツールの1つは、Cloud Native Computing Foundation(CNCF)によって管理されているオープンソースのカオスエンジニアリングプラットフォームであるChaos Mesh(https://chaos-mesh.org/)です。
Chaos Meshは、分散システムの障害のシミュレートを実施するための強力で柔軟性の高いフレームワークを提供し、高度なインフラストラクチャに最適です。そのKubernetesネイティブな設計と機能は、楽天グループのインフラストラクチャと運用ニーズとうまく整合しており、既存のワークフローとサービスにカオスエンジニアリングの実践をシームレスに統合できます。

Chaos Meshを使用して、潜在的な脆弱性を発見するために、さまざまなテクニックを適用しています。これには以下が含まれます。

  1. Fault Injection : 特定のサーバーまたはサービスをシャットダウンするなど、制御された障害を導入して、システムがトラフィックをどれだけ効果的に再ルーティングし、機能を維持するかをテストします。
  2. Traffic Simulation : トラフィックの急増をシミュレートしてスケーラビリティを評価し、主要な販売キャンペーンなどの需要の高いイベント中でも、システムがピーク負荷の処理が可能であることを確認します。
  3. Error Injection : 無効または破損したデータをシステムに注入して、エラー処理メカニズムをテストし、異常な条件下でのデータ整合性を確保します。

Chaos Meshとこれらのテクニックを活用することで、脆弱性を事前に特定し、システム設計を改善します。また、予期しないイベントが発生した場合でも、シームレスで信頼性の高いエクスペリエンスをお客様に提供することを可能にしています。

 

Chaos Meshを使った簡単なハンズオン

それでは、Chaos Meshについて詳しく見ていきましょう。このセクションでは、ローカル環境にChaos Meshをセットアップし、簡単なカオス実験を段階的に実行する方法について説明します。これがChaos Mesh入門のお役に立つことを願っています。

ステップ1:ローカルKubernetesクラスターのセットアップ

任意のKubernetesクラスターを使用できますが、デモンストレーションのために、ローカル環境でMinikubeを使用します。Minikubeは、ローカルのKubernetesクラスターを実行できる軽量なツールです。公式のスタートガイド(https://minikube.sigs.k8s.io/docs/start/)に従えば、簡単にインストールできます。

 

Minikube が起動すると、次のコマンドを実行してクラスターの状態を確認できます。

Will display:

 

ステップ2Chaos Meshのインストール

Chaos Meshは、KubernetesのパッケージマネージャーであるHelmを使用して簡単にインストールできます。したがって、まずHelm(https://helm.sh/)がインストールされているかを次のコマンドを実行して、確認してください。

これにより、インストールされているバージョンが表示されます(バージョンは異なる場合がありますが、3.5.4より新しい必要があります)

Helmがインストールされていない場合は、公式のインストールガイドを参考にすれば、簡単にインストールできます。
https://helm.sh/docs/intro/install/

Helmが使用できる環境であることを確認したら、公式のインストールガイドに従ってください。
https://chaos-mesh.org/docs/production-installation-using-helm/

まず、Chaos Mesh HelmリポジトリをローカルのHelm構成に追加します。

次に、chaos-meshという名前のnamespaceにChaos Meshをインストールします。 ここではdockerベースのチャートを使用していますが、公式Webサイトから別の構成も選択できます。

Chaos Meshの実行ステータスを確認するには、次のコマンドを実行します。

command

全てのPodが動いていることを確認してください。

 

ステップ3:基本的なカオス実験の実行

Chaos Meshがインストールされたら、簡単な実験を実行して、クラスター内のポッドの障害をシミュレートしましょう。 このために、deployment.yamlを作成して適用することにより、単純なnginxサーバーをデプロイします。

yaml

Nginx Podが動いていることを確認します。

 

次に、カオスPodを作成しましょう。 ポッド障害実験のためにexperiment.yamlファイルを作成します。

yaml

この実験では、ランダムに1つのNginxポッドを30秒間停止させます。

実験をクラスターに適用しましょう。

ここで、Nginxポッドのステータスを確認して実験を監視できます。エラーのステータスに注意してください。

30秒後にRunning状態に戻っています。

適用できる実験は他にもたくさんあります。 詳細については、chaos-meshドキュメント(https://chaos-mesh.org/docs/basic-features/#fault-injection)を確認してください。

完了したら、次のコマンドを実行してリソースをクリーンアップし、Minikubeクラスターを停止します。

 

結論

楽天グループでは、カオスエンジニアリングはSREの実践における重要な要素であり、潜在的なリスクを事前に発見して、事前の解決を可能にします。これにより、当社のシステムは常に高可用性を実現し、お客様にシームレスなエクスペリエンスを提供できます。

レジャープロダクト部(LPD)内では、このアプローチは楽天グループ全体の成功に対して重要な役割を担っているため、システムの信頼性、回復力、イノベーションの新たな指標を確立しつつ、リーダーシップを維持できるよう努めています。

今後、システムの信頼性をさらに高めるために、追加のツールとテクニックを検討して、カオスエンジニアリングの実践を拡大する予定です。さらに高度な障害シナリオを実験に統合することを目指しています。カオスエンジニアリングの実践を継続的に進化させることにより、イノベーションを推進し、楽天グループのシステムが将来の課題に対応できるように準備したいと考えています。

 

※掲載内容は2025年9月19日時点のものです。

私たちと一緒に働きませんか?

コマース & マーケティングカンパニー ライフ&レジャーサービス開発部(LLSD)では、新たなサービス開発から日々の運用・改善まで、一緒に働く仲間を募集中!エンジニアやプロダクトマネージャーなど、幅広い職種で募集しています。ご応募をお待ちしております。

 →LLSDの採用情報はこちら

→全ての採用情報はこちら

corp.rakuten.co.jp

【楽天ビューティー】Keycloakによる認証・認可実装

commerce-engineer.rakuten.careers

 

はじめに

こんにちは、レジャープロダクト部の楽天ビューティでアプリケーションエンジニアをしているKentaです。楽天ビューティは、ユーザーがサロンを検索・予約したり、サロンが設定や予約状況を管理したりするためのサービスです。この記事では、Keycloakを使用したOAuth2.0プロトコルに準拠した認証・認可プラットフォームを構築した経験について共有したいと思います。

 

当社には連携機能があり、サロンオーナーの同意を得て、サロンの在庫データをパートナー企業に提供しています。この連携機能は、サービス初期から運用されている比較的古い機能です。パートナー企業がサロンに代わってログインし、管理アプリケーションにアクセスして連携を実施していました。

 

近年、より現代的なアプローチや手法を取り入れるために、連携機能を更新することが議論されてきました。そして昨年、連携のためのAPIを提供し、認証と認可を管理するこの刷新に取り組むことを決定しました。

 

今回の刷新において、APIを他社に公開するのが初めてだったため、認証・認可に業界標準のアプローチをどのように実装するかが主要なポイントでした。検討の結果、Oauth2.0プロトコルを使用し、認証サーバーとしてKeycloakを使用することにしました。

 

Auth2.0Keycloakを選んだ理由

Oauth2.0は、ウェブサイトやアプリケーションが、ユーザーに代わって他のウェブアプリケーションがホストするリソースにアクセスできるように設計された標準です。2012年にOAuth 1.0に代わり、現在ではオンライン認可の事実上の業界標準となっています。一般的に、以下のフローに従います。

 

1.クライアントは、認証サーバーに認証を要求し、認証情報(クレデンシャル)を識別情報として提供します。

2.認証サーバーはクライアントを認証し、要求されたスコープが許可されていることを確認します。

3.リソースオーナーは、認証サーバーとやり取りしてアクセスを許可します。

4.認証サーバーは、グラントタイプに応じて、認証コードまたはアクセス/リフレッシュトークンをクライアントにリダイレクトします。

5.クライアントは、アクセストークンを使用して、リソースサーバーからリソースへのアクセスを要求します。

 

(参照: https://auth0.com/intro-to-iam/what-is-oauth-2 )

 

 

このプロトコルを選んだ理由は以下の通りです。

 

*安全で信頼性の高いプロトコルであり、多くのアプリケーションで広く採用されている。

*第三者アプリケーションがデータオーナーの承認を得てリソースにアクセスする必要があるという当社の要件に合致している。

*広く採用されているプロトコルであるため、両者が実装すべき内容が明確かつシンプルである。

 

Keycloakは、JavaベースのOSS製品で、IDおよびアクセス管理のためのものであり、ユーザーフェデレーション、強力な認証、ユーザー管理、きめ細かい認可などの機能を提供しています。また、RedHatの一部門であるWildFlyによって開発され、Apache License Version 2.0の下でライセンスされており、活発なオープンソースコミュニティがあります。

 

上の図は、セキュアなAPIアクセスを実現するための最終的なアーキテクチャを示しています。

 

大まかに、準備のために行った手順は以下の通りです。

 

 

このプロトコルを選んだ理由は以下の通りです。

 

*安全で信頼性の高いプロトコルであり、多くのアプリケーションで広く採用されている。

*第三者アプリケーションがデータオーナーの承認を得てリソースにアクセスする必要があるという当社の要件に合致している。

*広く採用されているプロトコルであるため、両者が実装すべき内容が明確かつシンプルである。

 

Keycloakは、JavaベースのOSS製品で、IDおよびアクセス管理のためのものであり、ユーザーフェデレーション、強力な認証、ユーザー管理、きめ細かい認可などの機能を提供しています。また、RedHatの一部門であるWildFlyによって開発され、Apache License Version 2.0の下でライセンスされており、活発なオープンソースコミュニティがあります。

 

上の図は、セキュアなAPIアクセスを実現するための最終的なアーキテクチャを示しています。

 

大まかに、準備のために行った手順は以下の通りです。

 

 

Keycloakを選んだ理由は以下の通りです。

 

・ライセンス料がかからない。

・比較的簡単に統合できるにもかかわらず、最新のセキュリティ標準をサポートおよび準拠しており、本番環境での使用に十分な機能とパフォーマンスを備えている。

・多くのカスタマイズが可能であり、特にユーザー認証やアクセス許可における当社サービス固有の仕様を満たしていた。

・大規模なオープンソースコミュニティが存在し、カスタマイズで困難に直面した場合に役立つと考えられた。

 

 

上の図は、セキュアなAPIアクセスを実現するための最終的なアーキテクチャを示しています。

 

大まかに、準備のために行った手順は以下の通りです。

 

Keycloakサーバーの準備

Keycloakイメージの選択

カスタマイズ

カスタムプロバイダーの実装

Keycloakは、ユーザーとそのアクセス権、認可などを管理するための多くの機能をサポートしています。ただし、これらの標準機能だけでは、ビジネスニーズや仕様をカバーできない場合があります。Keycloakはカスタムプロバイダーを提供しているため、独自のニーズに合わせたソリューションを実装することで機能を拡張できます。

統合方法は非常に簡単です。目的は、1つ以上のサービス実装を含むjarファイルを用意し、Keycloakが認識できる場所に配置することです。起動時に、Keycloakはクラスパスをスキャンし、標準のjava.util.ServiceLoaderメカニズムを使用して利用可能なすべてのプロバイダーを選択します。

Gradleプロジェクトの作成

Keycloakのサービスプロバイダーインターフェース(SPI)を使用して、独自の実装クラスを作成

拡張したいインターフェースにちなんで名付けられたサービス定義ファイルをプロジェクトに追加し、実装の完全修飾名を含めます。

 

 

# SPIクラスの実装

full.qualified.name.of.YourImplementation

```

Dockerfileを更新して、JARが指定されたディレクトリに配置されるようにします。

```

FROM quay.io/keycloak/keycloak:latest as builder

...

# プロバイダーJARファイルをプロバイダーディレクトリに追加

ADD --chown=keycloak:keycloak --chmod=644 myprovider.jar

/opt/keycloak/providers/myprovider.jar

# コンテキスト: ビルドコマンドを実行

RUN /opt/keycloak/bin/kc.sh build

```

構成の適用

Keycloakは、環境変数やCLIオプションを介して、ロギング、データソース設定、機能の有効/無効化などの構成のカスタマイズを提供します。

Jenkinsジョブの作成

アプリケーションのデプロイはJenkinsで管理しているため、イメージをビルドし、必要なKubernetesコンポーネントを適用するためのJenkinsジョブを作成しました。

 

トークン検証サービスの準備

oauth2.0はjwtアクセストークンで動作するため、トークンが有効で、リクエストがアクセススコープに従っている場合にリクエストを認可できるサービスを準備する必要がありました。

 

一般的に、このトークン検証はオンラインまたはオフラインのいずれかの方法で実行できます。

*オンライン:トークンの使用が試行されるたびに、Keycloakがサポートするトークン検証エンドポイントを呼び出す。

*オフライン:トークンをローカルで検証する。

 

クライアントからのリクエストがあるたびにKeycloakサーバーにアクセスすることによるオーバーヘッドが懸念されたため、オフライン方式を使用することにしました。また、ユーザーが無効になったらすぐに終了するのではなく、アクセストークンをそのTTLまで有効にする仕様としました。その他、リソースAPIへのリクエストをインターセプトし、それに応じてトークンを検証するサービスも実装しました。

 

これで実装完了です。連携機能のための認証・認可プラットフォームを構築することができました。

 

感想

Keycloakを初期実装から運用してきた中で、技術的な感想をいくつか共有しようと思います。

 

*非常に簡単かつ迅速に始めることができます。必要な構成に注意すれば、短時間でサーバーを起動できます。

*カスタムプロバイダーを使用すると、機能を簡単に拡張できますが、Keycloakのその部分がどのように機能するかをある程度学習する必要がありました。カスタマイズの複雑さと、拡張するために知っておくべきことは、仕様がより「カスタム」になるほど明らかに増加します。したがって、機能のカスタマイズの範囲が広い場合は、Spring Authorization Serverのようなフレームワークを利用して、関数に対するオーナーシップを高めることを検討するかもしれません(追加の労力を許容できる場合に限ります)。

*更新は頻繁かつタイムリーであり、セキュリティ関連のコンポーネントにとっては非常にありがたいことです。ただし、バージョン間のパッチのサポートは限られています。最新のメジャービルドのみがアクティブな開発とセキュリティ修正を受けます。最高のものを得るには、比較的短い期間でバージョン更新を処理する準備が必要です。

*高可用性アーキテクチャに関する具体的な手順はあまりないようです。コンセプトガイドと設計図は利用できるため、出発点として参照し、そこからソリューションを構築できるはずです。

 

個人的な感想として、認証・認可のソリューションに取り組むことで、当社のサービスのモダン化に大きく貢献できたと感じます。私にとっては不慣れな分野でしたが、この適応を学び、Keycloak実装プロジェクトを成功裏にやり遂げる機会を得られたことを嬉しく思っています

 

終わり

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

認証・認可方法を検討したい方、開発者が何に取り組んでいるのかを見たい方にとって、この記事が役立つことを願っています。

 

私たちと一緒に働きませんか?

コマース & マーケティングカンパニー ライフ&レジャーサービス開発部(LLSD)では、新たなサービス開発から日々の運用・改善まで、一緒に働く仲間を募集中!エンジニアやプロダクトマネージャーなど、幅広い職種で募集しています。ご応募をお待ちしております。

 →LLSDの採用情報はこちら

→全ての採用情報はこちら

corp.rakuten.co.jp

【「楽天toto」開発チーム】単体テストの並列実行の実装

はじめに

私たち、楽天グループ コマース&マーケティングカンパニー レジャープロダクト部( LPD)「楽天toto」開発チームでは、「効率的で安定したシステムを作る」ことを常に意識して開発を進めています。今回は「単体テストの並列実行により実行時間の短縮」を試みましたので、ご紹介します。

「単体テストの実行時間を短縮したい」「PHPのparatestライブラリに興味がある」という方は、参考にしてください。

 

並列実行実装の背景

私たちのチームでは、単体テストの実装を積極的に行っています。具体的には「新規で追加・変更を行った部分」「ゴールデンパス」に関してはカバレッジ(網羅率)100%にするなどのルールを設定しています。これにより問題の事前の検知が可能となり、既存の機能を担保しつつ新たな機能を開発することができるようになりました。

しかし、一方で、単体テストが増えたことにより、実行時間が1回につき約20分かかるようになってしまいました。テスト結果が出るまでに毎回20分も待ってから修正、マージをするとなると、開発に集中できず生産的とはいえません。そこで、この問題を解決するため、並列実行の実装を行いました。

 

実装アイデア

「Paratest」について

現在私たちのチームでは、PHP Unitを単体テストに使用しています。それを考慮して並列実行について調査したところ、以下の「paratest」というライブラリが使用できそうという結論になりました。

「paratest」

「paratest」のインストール、実行は以下の様に簡単なコマンドで行えます。

インストール↓

実行↓



テストスイート(testsuite)の並列実行

具体的な実装としては、各テストスイート(testsuite)を並列実行することにしました。「paratest」はparallel-suiteというオプションがあり、下記のようなコマンドを使用することで簡単にテストスイート(testsuite)を並列実行できます。

before/afterは以下の様なイメージになります。

php artisan コマンドも使用可能

Laravelのphp artisanコマンドでも、「paratest」をラップしたコマンドを使用できます。

上記のコマンドのように--parallelオプションをつけるだけで並列実行できます。

 Laravelを使用していればシンプルなコマンドで実行できるので便利なのですが、今回は不要なトラブルを避けるために、元々のライブラリのコマンドをそのまま使用することにしました。

 

問題と解決策

問題

「paratest」を実行するところまでは、スムーズに行えました。しかし、実行時にほとんどのテストケースで、失敗していることに気づきました。調べてみると、原因はDBのコンフリクトでした。

通常のPHP Unitであれば、1プロセスに対して1DBを参照するという形になります。しかし、「paratest」の実行では複数プロセスで、1つのDBを参照してしまうため、他のプロセスのテストとデータが干渉してしまい、エラーが起きていました。

 

解決策

「paratest」では、各プロセスでTEST_TOKENという環境変数が設定される仕様です。今回はこのTEST_TOKENを使用して、DBを切り替えることにしました。変更点は以下の通りです。

  1. dbname_test_1, dbname_test_2, ...のような形でテスト用のDBをプロセス数用意する。
  2. 各プロセスを実行する前に行われるPHP artisan migrateコマンドの前にTEST_TOKENを使用して、DB名を書き換える。
  3. test setup部分で、database接続のconfigをと同様の方法で書き換える。

これらの変更を行うことでDB干渉の問題は無事解決し、「paratest」を実行できました。

 

実装結果

「paratest」を実行した結果、今まで20分程度かかっていたテスト実行を約10分に短縮できました。

一番時間がかかっていたtestsuiteが10分ほどだったので、こちらも期待通りです。

 

改善案

「paratest」を実行するだけで、単体テスト実行時間を短縮できました。testsuiteの分け方をより工夫することで、さらに実行時間を短縮できると考えます。

次は各testsuiteを見直して細分化することで、より実行時間を短縮したいと思います。

私たちと一緒に働きませんか?

コマース & マーケティングカンパニー Racing & Lottery開発部(RLD)では、新たなサービス開発から日々の運用・改善まで、一緒に働く仲間を募集中!エンジニアやプロダクトマネージャーなど、幅広い職種で募集しています。ご応募をお待ちしております。

 →RLDの採用情報はこちら

→全ての採用情報はこちら

corp.rakuten.co.jp

ITサービスの高可用性と耐障害性 (HA/DR) を向上させるには ― 楽天ペイ(オンライン決済)での取り組み② ―

楽天ペイ(オンライン決済)で Tech Lead をしている Nana です。今回は私たちが運営する「楽天ペイ」での高可用性/耐障害性 (以下、HA/DR)を向上させるための具体的な取り組みを紹介していこうと思います。

前回の記事では、私たちが第一弾として「楽天ペイ」という大規模サービスを運営・管理する中で培ってきたアプローチ方法をより一般的な説明として取り上げました。興味のある方は是非合わせてご覧になってください。

HA/DRを向上させるための5ステップ

前回の記事でもHA/DRを達成していくための5つのステップについてはお伝えしていますが、再掲です。「楽天ペイ(オンライン決済)」を運営している私たちのチームでも以下の5つのステップ通りに沿って、HA/DRを向上させています。

5ステップとは?

Step 1. HA/DRにおける目標を設定:事業継続計画 (BCP) における3つの(RLO/RTO/RPO)を設定する

Step 2. Portability (移植性)開発したアプリなどの資産がどのような環境でも容易に動作し、また動作環境の再現も容易である状態を目指す

Step 3. Retry-Ability (リトライ容易性)処理フローにリトライ処理を含める、あるいはユーザー操作によるリトライ処理を可能にするなど、リトライ処理が容易に行えるシステム設計を目指す

Step 4. Redundancy (冗長性)システムを多重化することで、一部のシステムが不具合などで正常に動作しない場合でも、全体を通してシステムが正常に稼働する状態を目指す

Step 5. Resiliency(回復性)システムが完全にダウンしてしまった場合でも復旧できるような状態を目指す

詳細は、前回の記事をご覧ください。

Step 1: HA/DRにおける目標を設定する

※社内戦略や具体的な数値などが含まれてしまうため割愛します。

Step2: Portability (移植性)を向上させる

私たちの場合、機能やサービス毎にアプリケーションレベルではコンテナ化、インフラストラクチャレベルでコード化できる部分をそれぞれ整理し、移植性を担保する取り組みを行なっています。

1. アプリケーションレベルでのコンテナ化

主にアプリケーションレベルでのモジュール化とは、コンテナ化を意味します。コンテナビルダーとしては、最新のトレンドとしてはBuildpacksの利用もあり得ますが、私たちは現在Dockerfileを用いてイメージ化しています。

Dockerfileを利用する場合は自由度が高い分、セキュリティ、再現性の観点で事故が発生しやすくなります。防止策として全アプリケーションが共通して利用するベースイメージを準備しておき、それらを各アプリケーションで利用することで事故を防ぐ工夫を行っています。

2. インフラストラクチャレベルでのコード化

インフラストラクチャにおけるコード化は、Infrastructure as Code(以下、IaC )とも呼びます。IaCの取り組みを進めた上で、サービスの拡大と共に徐々にインフラ構成をモジュールとして利用できるよう準備すると良いでしょう。

私たちの場合、Terraformを利用してActive・Standby用の複数環境のインフラ構成を一括で管理しています。統一化されたモジュールを利用することで最小の設定で効率的に複数の環境を構築・管理することができます。

IaCを進めていく上で重要なことは、手作業を廃し、メンバー全員にインフラストラクチャに対する理解を深めていくよう促すことです。手作業は再現性を低めバグや想定外のエラーを発生させる原因となります。手作業を排除していくためには、全てのエンジニアのインフラストラクチャに対する苦手意識(特にNW、セキュリティ周り)を取り除き、メンバー全員がIaCへの理解を深めた状態となるのが理想です。

私たちのチームでも、約2年の歳月をかけて徐々にチームメンバーへIaCを基準とした開発体制を浸透させていく事ができました。例えば、インフラの構成変更が発生するような場合、アプリケーションエンジニアに対してインフラエンジニアがレビュアーとして協力できる体制を作る、あるいは機能開発前にアプリケーション・インフラエンジニアを交えて設計する等、小さな成果を積み重ねてきました。それでも常に改善の余地があるため、現在でもメンバー同士でさらに良いインフラ構成について考えています。

Step3: Retry-ability (リトライ容易性) を向上させる

「楽天ペイ(オンライン決済)」の場合では、エラーが発生した際でも単にデータの不整合がなく処理前の状態へリカバリさせるだけでなく、データ処理を完了させた状態へ前進させることを目標としています。

一般的にはいくつかのリトライに関わるデザインパターンがあり、例えば補償トランザクションなどではエラーが発生した際には処理のキャンセルをかけていきますが、私たちのサービスでは、なるべく決済処理を完了させられるよう一度依存サービス側のステータスを確認し、処理に進めるか戻すかを決定するようなロジックを取っています。そのため場合によっては、再度リクエストを投げることなく意図した処理が完了させられます。

また依存サービスとの通信が断続的に失敗しているケースでは、キャンセル処理の通信が失敗してしまうので補償トランザクションのみではデータの整合性を完全に担保することはできません。そういったケースを想定し、別プロセスで定期的に依存サービスとの不整合を解消するための突合処理(Reconcile Process)を非同期処理として行い、補完的にデータの整合性を担保しています。

Step4: Redundancy (冗長性) を担保する

私たちの場合、パブリッククラウドを利用しているため、インフラレイヤーでは基本的にはAvailability Zone(以下、AZ)を有効にした上で、インスタンス数を2以上にすることで冗長性を担保しています。アプリケーションレイヤーではAZにまたがるKubernetes (以下、k8s)上で動かしながら、インスタンス数を2以上にすることで冗長性を担保しています。しかし、実はこれだけではアプリケーションレイヤーの可用性は完全ではありません。

k8sでは更に可用性を上げるため、以下のような設定を行っています。

設定項目 設定で防ぎたいこと・やりたい事
PodDisruptionBudget ノード故障で再起動時したいときに、Podが同時にダウンすることを防ぐ
ResourceLimit 特定のアプリケーションによる負荷が上昇した場合、他のアプリケーションの性能に影響を及ぼさないように占有できるリソースを制限する
LivenessProbe 正しく動作していないPod(Broken Pod)がそのまま実行状態となりリソースを占有したままの状態となることを防ぎ、再度処理を実行させる
ReadinessProbe 処理が失敗する可能性が高いPodを安全に切り離す
NodeSelector & NodeAffinity 特定の負荷が高いバッチとエンドユーザーに影響するフロントアプリケーションを物理的に分割し、エンドユーザーの利用に影響が出ないようにする
PodAntiAffinity あるアプリケーションを稼働させるためのPodを複数Nodeをまたいでデプロイされるようにし、1つのNodeで障害が発生した場合でも、サービス全体が停止しないようリスクを分散させる

上記の設定については、以下のようなDeployment Yamlファイルにて設定が可能です。

Deployment Yaml

apiVersion: policy/v1 kind: PodDisruptionBudget ... spec: minAvailable: 50% selector: matchLabels: app: <app-name> ... apiVersion: apps/v1 kind: Deployment ... spec: replicas: 2 template: spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - <app-name> topologyKey: "kubernetes.io/hostname" nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: app-category operator: In values: - web containers: - name: <app-name> … resources: limits: memory: 1024Mi cpu: 1000m readinessProbe: ... livenessProbe: ...

Step5: Resiliency(回復性) を向上させる

私たちは現在、複数のシステムを複数用意し待機させておくActive-Standbyの構成としていますが、一般的には以下のような3つの区分に分けて環境を準備します。事前にStep1で設定した目標復旧時点(以下、RPO)や、どの程度の運用コストや復旧時間 (以下、RTO)を要するのかなどを計算し、最適な施策の組み合わせを検討した上で、実装しました。

HA/DRを進める上ではRTO/RPOと運用コストの間の費用対効果を高めるために、3つの区分をどのように組み合わせて環境構築するかがとても重要です。

区分 定義 運用コスト 復旧にかかる時間
On-Demand
(Cold Standby)
事前にバックアップ環境のコード化をしておくことで、必要に応じてTerraformでProvisioningをする。
*一般的Cold Standbyですが、クラウドでは停止=削除となる場合がある為、On-Demandと定義しています。
Warm Standby 事前にバックアップ構築をしておき、停止もしくは縮退運用にする。
Hot Standby 事前にバックアップ構築を構築し、常に起動させておくリソース。 無~小

また忘れがちですがOn-Demandで事前にバックアップ環境をコード化する際には、セキュリティ監査やNW系の設計・疎通を受けた上でリソースを削除して、再現性が高く安全にゼロから作ることを保証しましょう。

最後に本番環境と待機系の参考システム構成を図示します。特筆することとしては、ユーザからのリクエストはGSLBで常に受け付けており、また環境を速やかにスイッチできるよう本番・待機系の両環境へ事前に待機系のIPやドメインの情報を含むよう設計し、GSLB に登録させておきます。

k8sのwarm standbyについては想像がつきにくいかと思いますが、マスターノードのみを維持してアプリケーションはデプロイせずワーカーノードを0台として縮退運用をしています。もし問題が本番環境で発生した場合には、エンジニアは待機環境のリソース群の作成や、本番環境への昇格等を行った後にGSLBからの経路を変更することでシステム回復を目指します。

またいつ問題が発生した場合であっても速やかにバックアップ環境から本番環境への移行作業が行えるように、定期的にシステムの防災訓練をかねた手順の検証を行っています。

まとめ

いかがだったでしょうか。今回は、楽天ペイ(オンライン決済)で実際のインフラ構成に落とし込んだ場合での例をご紹介しました。

前回の記事と併せて、これらの記事が少しでも皆さんが実際にアクションを起こすための参考となれば幸いです。ここまで読んでくださってありがとうございました!

 

私たちと一緒に働きませんか?

コマース & マーケティングカンパニー ECインキュベーション開発部 (ECID)では、新たなサービス開発から日々の運用・改善まで、一緒に働く仲間を募集中!エンジニアやプロダクトマネージャーなど、幅広い職種で募集しています。ご応募をお待ちしております。

 →ECIDの採用情報はこちら

→全ての採用情報はこちら

corp.rakuten.co.jp

ITサービスの高可用性と耐障害性 (HA/DR) を向上させるには ― 楽天ペイ(オンライン決済)での取り組み① ―

初めまして、「楽天ペイ(オンライン決済)」で Tech Lead として働いているNanaです。今回から2記事に分けて、私たちが取り組んでいる高可用性/耐障害性 (以下、HA/DR) の仕組み作りについて紹介していきます。

今回の記事では、私たちがこれまで「楽天ペイ(オンライン決済)」という大規模サービスを運営・管理する中で培ってきたHA/DRを高めるためのアプローチ方法を説明していきます。

また次回の記事では、私たちが運営する「楽天ペイ(オンライン決済)」でのより具体的な取り組みについても紹介する予定です。サービスのHA/DRを高めたいと考えている方は、是非ご覧ください。

楽天ペイ(オンライン決済)とは

私たち「楽天ペイ(オンライン決済)」では決済サービスを楽天市場以外のECサイトに提供をしています。エンドユーザ視点では、クレジットカード情報の入力といった面倒な作業をする事無く会員情報に事前に登録されているクレジットカード情報を利用したり、楽天ポイント・楽天キャッシュの残高を利用したりして簡単に決済することができます。

コロナ禍によってECサイトを利用する人が増えた後も継続してECサイトでの買い物をして頂けるようになったこともあり、おかげさまで楽天ペイ(オンライン決済)を選んで利用して頂ける方も増加を続けております。どのようなサイトで使えるのかは、以下のリンクをご参照ください。
使えるサイトについてはこちら

なぜHA/DRの向上が私たちにとって重要なのか

さて、ここからが本編です。上記の通り「楽天ペイ(オンライン決済)」は楽天市場以外の EC サイトへも提供されています。もし私たちのサービスが停止すると、ECサイト側でも決済不可となり購入者様にご迷惑をおかけすることはもちろん、ECサイト側の売上を落とすことにも繋がってしまいます。

そのため、私たちは根幹となる「楽天ペイ(オンライン決済)」のHA/DRを高めることを重視して日々開発・運用を行っています。

HA/DRを向上させるための5ステップ

HA/DRを達成して行くためには上記のように大きく5つのステップがあります。それぞれのステップでの取るべきアクションはサービス規模や運用にかけるコスト、チーム体制などによって変わるので、一つ一つのStepを順番通りに確認してください。

Step1: HA/DRにおける目標を設定する

まず運用コストやチーム体制は事業継続計画 (以下、BCP) と密接に関わっているので、BCPで定義されている以下の3つの観点と費用について関係者と必ず認識を合わせましょう。

目標設定は現在のサービスのビジネス状況によって変化していきます。定期的に見直し、現在の立ち位置を考慮した目標を設定しましょう。

目標 目標の内容 決定のための要素
RLO (Recovery Level Objective) / 目標復旧レベル 最低限動かしたい機能は何か?
最低限どの程度の稼働率を目指すか?
エンドユーザに使ってもらう主要機能は何か?
一時的に手運用で動かせる物や復旧を待てる業務はあるか?
RTO (Recovery Time Objective) / 目標復旧時間 どの程度の時間で復旧を目指すか? 楽観的なシナリオではどのくらいの時間がかかるか?
悲観的なシナリオではどのくらいの時間がかかるか?
停止している間の機会損失・社会的インパクトはどの程度か?
財務的にかけられる運用コストは?(掛け捨て保険)
RPO (Recovery Point Objective) / 目標復旧時点 データの損失は、いつのタイミングまで許容できるか? データの更新頻度はどの程度か?
損失するデータは別の手段で復旧が可能なデータか?
データの保持に対して関連するような法令・規則等はないか?

これら上記の3つのR、RLO/RTO/RPOはシステムの複雑度と運用コストに大きな影響を及ぼします。また万が一の障害発生時には、一部機能やデータを諦めるという決断を下さなくてはいけない場合もあります。そういった有事の際を含めた想定を行いながら、目標設定を行ってみてください。

特に後述するStep5 Resiliency (回復性)を向上させる為のアーキテクチャ設計には大きく影響するため、後回しにせず事前に目標設定を完了していることが望ましいです。

Step2: Portability (移植性)を向上させる

Step2ではPortability (移植性)を向上させていきます。移植性とは、開発したアプリなどの資産がどのような環境でも容易に動作し、また動作環境の再現も容易であるかどうかの程度を指します。移植性を向上させる主な目的は、「デリバリーの高速化」です。具体的にはコンテナ化やInfrastructure as Code (以下、IaC)の取り組みが当てはまります。

HA/DRを進めて行く上では、移植性の向上はチームとして取り組むべき最初の課題であり、開発スタイルや費用対効果といった課題にぶつかる最初の壁にもなります。

コンテナ化は比較的取り組みやすいと思いますが、IaCを取り組む場合には特に管理・教育コストの面で施策の難易度が高まります。最初に取り組むことをお勧めする理由としては、以下のような理由が挙げられます。

1.後続Step3~5の取り組みは、移植性を前提として進めて行くため。
2.後続Step3~5の取り組みが不十分であったとしても、費用と時間をかければ有事の際に復旧を目指すことができるため。

Step3: Retry-Ability (リトライ容易性) を向上させる

Step3では、Retry-Ability (リトライ容易性)を向上させていきます。この用語は私たちの造語ですが、以下の2つの観点がポイントです。

1. システム面では常にリトライ処理を実装し、リクエストが正常に処理されることを担保する

可用率をどれだけ改善しても100%とはなりません。ネットワークの瞬断や通信の滞留、フェイルオーバに伴う瞬断の発生を完全に防ぐことは難しいためです。そのためI/Oが発生する箇所(通信・ファイル書込等)では必ずリトライ処理を実装すると良いでしょう。

ただリトライ処理は単純なようでいて、実はデータの不整合を引き起こす原因になり得ます。たとえばConnection Timeoutとは違い、Query (or Read) Timeoutの場合はリクエストが送信され、処理が完了している可能性があります。

場合によっては後続処理を正とする単純なリトライ処理の実装のみで、十分に整合性を担保できることもありますが、より厳密なデータの整合性が求められる場合、モノリシックサービスであれば「トランザクション管理を行う」、マイクロサービスであれば「補償トランザクション」や「TCC (Try-Confirm/Cancel)パターン」等を作り込んでいく必要があります。今回は参考として、補償トランザクションを利用したリトライ処理の場合でのシーケンス図を以下に図示します。

[補償トランザクションの実装例]

2. ユーザが簡単にリトライ処理を行えるようなシステム設計を行う

システム側でリトライ処理を実装していても、リトライをしきれないケースも発生します。そういった場合でも、後続処理がデータの不整合による影響を極力受けないようフェールセーフに配慮することが重要です。

対策の一例としては、後続処理を素早く再実行させるために利用者自身でも容易にリトライ処理を再実行できるようなシステム設計を行うなどがあります。そのためには、エラー発生時の処理フローや画面上の振る舞いと言った観点でもシステム設計を作り込んでおくと良いでしょう。

Step4: Redundancy (冗長性) を向上させる

Step4して、Redundancy(冗長性)を向上させていきます。冗長性の向上とは、システムを多重化することで、一部のシステムが不具合などで正常に動作しない場合でも、全体を通してシステムが正常に稼働する状態を目指すことです。

ただ冗長性を向上させる施策を行うことで、切り替えや切り離し処理が追加され、新たな瞬断の発生原因となる可能性があることにも注意が必要です。冗長性を向上させるための観点は以下の2点です。

1. High Availabilityを意識したインフラ構成を構築する

まずは、地道に単一障害点となる箇所をみつけ多重化・クラスタ化を進めていきましょう。
単一障害点とは、システムを構成する要素のうち、そこが停止するとシステム全体が停止してしまう部分のことです。

このとき多重化をする上での重要なポイントは、「NW・電源系統・仮想化サーバ等」が物理的に分離されていることです。これらの要素が分離されていることで、システムの可用性を担保することができるようになります。またクラスタ化を行う場合、アプリケーションのインスタンスが物理的に異なるサーバ上でも動作可能である必要があります。

サービスによっては、オンプレミスではなくパブリッククラウドなどを利用しているケースもあるかも知れません。その場合は、Availability Zoneを適切に設定することで可用性の高いインフラ構成を実現できます。

以下では、単一障害点となる可能性があるポイントをいくつかピックアップしてみました。まずは読者の皆さんも同様の観点でチェックしてみてください。


2. 冗長性を維持する運用手順を確立しよう

仮に冗長性の高い構成を組んでいても、ダウンしたサーバやアプリケーションへリクエストが送られる状態が長く続くとリクエストの成功率は低下します。そのため、素早く切り離しやフェイルオーバを行えるかといった運用手順の確立も重要なポイントです。

サービス開始時などは手動対応でも良いですが、運用を行いながら効率的な切り離しやフェイルオーバを行う手順を確立させ、最終的には運用手順を自動化させていけることが理想です。

Step5: Resiliency(回復性) を向上させる

Step5ではResiliency(回復性)を向上させていきます。回復性ではシステムが完全にダウンしてしまったときからでも復旧できるような状態を目指していきます。

これまでのStepでシステムを継続的に正常稼働させるためのポイントを幾つか紹介してきましたが、それでも常に、大規模な災害や停電などシステム全体が停止することが避けられないケースは存在します。そのような場合でも素早くサービスを復旧できるよう準備しておきましょう。

回復性を向上させる具体的な施策としては、「バックアップを確保する」「距離的に離れた地点でシステムを動かす」などが考えられます。実装する施策の選定には、施策実施時の運用費用、サービス復旧時間とサービス停止時の損失額などを試算し、施策毎の妥当性を検討する必要があるでしょう。例として、各施策実施時の運用費用とサービス復旧時間についての関係性を調査した際のグラフを以下に示します。

※データや数値はあくまで参考値です

上記のグラフからも、システムの規模やサービス停止時の損失額により、サービス毎に取るべき施策やKPIが異なってくることがわかります。復旧時間が短い強度の高い施策の場合、その分運用コストも肥大化していくため、サービスの成長・拡大のフェイズとともに徐々にActive-Activeを目指していくことが現実的な施策選定と言えそうです。

また非常時でもシステムの回復手順(フェイルオーバの手順等)を作業者が素早く実行できるよう、定期的に手順書のアップデートやITシステムの防災訓練を行うなど、日頃から非常時の準備を行うことが重要です。

まとめ

ここまで読んで下さってありがとうございました。この記事を通して、読者の皆さんがHA/DRやBCPをより深く理解し、少しでも実際のアクションに繋げて頂けたら幸いです。

次回は、ここまで紹介してきたアプローチ方法を「楽天ペイ(オンライン決済)」の開発現場で実際にどう活かしているかについて具体例を交えて紹介していきます!

 

私たちと一緒に働きませんか?

コマース & マーケティングカンパニー ECインキュベーション開発部 (ECID)では、新たなサービス開発から日々の運用・改善まで、一緒に働く仲間を募集中!エンジニアやプロダクトマネージャーなど、幅広い職種で募集しています。ご応募をお待ちしております。

 →ECIDの採用情報はこちら

→全ての採用情報はこちら

corp.rakuten.co.jp