asoview! TECH BLOG

アソビュー株式会社のテックブログ

品質が重要なシステムと開発サイクルについて

エンジニアリングマネージャの竹内です。

アソビューでは様々なシステムが存在していますが、品質が特に重要というシステムも存在します。 今回はその中の1つである精算業務を行うシステム(販売管理システム)において、どのような品質対策を行ったかを紹介します。

アソビューの販売管理システムとは?

アソビューのビジネスはアクティビティ予約や電子チケット購入に対して、顧客(レジャー事業者)から手数料を頂くモデルです。 クレジットカードなどの事前決済もあれば現地決済も可能ですので、アソビューから顧客に対して請求することもあれば、支払うこともあります。

最初はアウトドアレジャーから始まったアソビューですが、現在ではさまざまな遊びのジャンルを取り扱っています。ジャンルや事業者によっては商習慣が異なり特殊な精算を行うケースもままあります。 通常では月末締めで精算を行うのですが、例えば

  • 販売ベースか体験(催行/チケット利用)ベースで計上するのか
  • キャンセル料金の取り扱い
  • 興行系イベントだった場合その興行が終了したら精算するため月末締めとは限らない
  • などなど

多様な精算に対応する必要があるため販売管理システムを内製しています。(システムでの対応が間に合ってなかったり、超レアケースで運用で対応している部分もあります・・)

どのような課題か?

販売管理システムはお金の計算が主たる処理であるため高い品質が求められます。しかしながらシステムの不具合によりインシデントが頻発していた時期があり、多いときには5件/月発生していました。

インシデントが発生するたびに、営業・経理・開発で金額が正しいか再計算・検算し確認 を行っていて非常に手間と時間がかかります。当然顧客からの信頼も落ちてしまいます。

このようなことが今後起きないように以下の品質改善を図りました。

対策1: QA をアサインしリグレッションテスト実施を徹底する

インシデントが発生していた当時は、システムの変更やリリースは動作確認が完了次第随時リリースを行っていましたが、テストが十分に行えていない場合もありインシデントにつながっていたケースがありました。

そこで社内の別チームにいたQAメンバーを、このチームへ異動できるよう調整しアサインしました。これによりリグレッションテストを常に実施する体制にしました。リグレッションテストは以下の観点にわけ、これをリリース前に行います

  • 集計データの確認: システム変更前後で集計結果が一致するかを確認
  • 集計業務のシナリオに沿った確認: システム変更した部分が期待通り動作するか確認

また QA はこちらの記事にもあるように Shift Left を意識して動いてくれました。

tech.asoview.co.jp

アサインされた QA はこのシステムについての知見はありませんでしたが、Shift Left に則り開発や経理のメンバーから仕様をキャッチアップしながらテスト設計を行いました。 開発と協力してデータを準備したりテストの精度を上げたりして 1 ヶ月後にはリグレッションテストとして開発サイクルに組み込まれました。 その後も効率よくテストを実施できるようにしたり、テストケースを追加したりとブラッシュアップは続いています。

対策2: 月イチリリースとする

これまでは本番リリースは五月雨に行っていましたが、月に1回としました。理由は以下です。

  • 精算業務は月次で行われている
  • リグレッションテストを効率的に行う

月イチと定めた結果タイムボックスが明確になり、

  • QA期間
  • リリース日
  • 開発期間
  • リリース判定日
  • 振り返り/次月プランニング

などのスケジュールが自然と決めていけます。 スケジュールを決めるときには、「このシステムでは品質が最重要」と位置づけ、まずQA期間をしっかり確保した上で他の期間を決めていっています。

対策3: ビジネス側とのコミュニケーションを定期的にとる

これは対策2の副産物といえるかもしれませんが、スケジュールを決める際にビジネス側との定例ミーティングも設定しました。

  • 要件や課題の確認と優先順位
  • その月のリリース予定の変更内容

前者は今後の開発に関わることなのでもともと行っていましたが、追加で後者を実施するようにしました。 これまではリリース直前にリリース内容を共有していました。 月イチリリースに変更し次月リリース予定を決めることができたため、適切なタイミングでリリース内容をビジネス側に共有できるようになりました。

対策の効果

現在ではシステム起因のインシデントは 0件/月 まできました。成果は一定出せたのではないかと考えています。 特に以下2点は成果を出すためには重要だったと思います。

  • QAというテストを専任できるメンバーを配置しリグレッションテストの計画・設計・実施を毎月確実に実行する
  • 月イチリリースに変更し、リグレッションテストを効率よく確実に実行できるようにする

すべてのシステムにQAをアサインすることは難しいかもしれませんが、品質が特に重要というシステムでは重要な存在だと改めて思いました。QAの存在はとても頼もしいです!!

今後の課題

システム起因のインシデントの発生は抑えられていますが、システムで対応できていない運用部分ではインシデントが発生したり、手間がかかっている状況です。今後はできるだけシステムでカバーできるように且つ高品質でリリースしていくことが課題です。

最後に

アソビュー!のような主力サービスはもちろん目立ちますし華やかに見えるかもしれません。しかしその裏側では地味ですが堅実な開発が求められるシステムも存在します。この記事でさまざまな特性をもつシステムがアソビュー社内に存在し、その領域で活躍している様々なエンジニアがいるということが少しでも伝わればと思います。

そしてエンジニアを絶賛募集中ですので少しでも興味があればお気軽に以下にアクセスください!よろしくお願いします! www.asoview.com

アソビュー!初のネイティブアプリリリース。機能設計でデザイナーが取り組んだ4つのこと

こんにちは!デザインリードを担当している山中です。

ついにアソビュー!のネイティブアプリがリリースされました!
私が入社(2019年の夏ごろ)してから、アプリをつくりましょう、という話は何度もでて、途中まで開発しいたもののコロナの影響で業績が低迷、アプリの開発どころではなくなり、他の開発の優先度が高くなり、アプリの開発がずっと止まっていましたが、業績がV字回復し資金調達により採用に力を入れたことで開発メンバーも増え、やれることが増えてきたので、アプリ開発が再開でき、ついにファーストリリース!
ようやくです!私もだいぶ待ちましたが、アソビュー!をご利用の皆さん、お待たせしました!そして、週末なにしよう!?と考えてしまう前に、ぜひダウンロードしておでかけ先を探してみてください!

アプリダウンロードURL
iOS   ‎アソビュー!:遊び先の検索・予約 on the App Store
Android アソビュー!: 週末の便利でお得な遊び予約アプリ - Apps on Google Play


今回は、アソビュー!ネイティブアプリのHOME画面の機能設計を決めるうえで必要になった材料や手順のお話をつぎの順番でしていこうと思います。

1.ネイティブアプリの要件を決めるうえで、どのような流れでプロジェクトを進めてみたか

弊社では何かしらの機能開発を行う際に「利用者の属性を考える」「定量的なデータを分析する」「ユーザーインタビューから得た知見を活用する」などのステップを経て、要件や設計を決めて進めていきます。ネイティブアプリ開発の進め方においても、同様の流れを踏襲してプロジェクトを進めていきました。
同時に、どこまでをネイティブアプリ化するか。すべての機能をネイティブアプリ化するには膨大な時間とコストが必要になってくる為、最低限の機能で一番利用される「HOME画面」「検索」にフォーカスしてネイティブアプリ化を進め、他の画面はweb viewになっています。(今後すべての画面をネイティブアプリ化する予定ではいます)

2.前提となる、Webとネイティブアプリの利用者の属性の違い

根本的にアプリとWebの違いは「大半の方が、自分の意思でダウンロードした」です。
なので、たまたま検索や広告経由で入ってきた未会員の方に向けての「ここはこういうところです」の説明を省いてもいいと考えます。
ほかに、アプリの場合「位置情報の許諾」が一般的な機能としてある為、Webよりも受け入れやすいと考えます。

3.要件を考えるための事前調査

利用者の属性や定量データ、利用者のインタビューなどの情報をもとに設計を進めます。

3-1.ターゲット層について

現時点では

  • 家族層

が主なターゲットです。(※家族層だけが利用しているというわけではなく、この層の利用者をもっと増やしていきます。という意味での現時点でのターゲット層です)

3-2.データでみたWeb版

Web版を利用している方々の使い方は様々ですが、データで見ると下記のようなことがいえます(※詳細な数値データは控えます)。

  • 検索機能を利用して、検索結果画面から商品を選んでいる
  • 運用側で提案した商品よりも検索機能を利用して選んでいる
  • 検索条件による人気商品を検索結果画面の上部に設置すると売れる
  • 所在地周辺(住んでいる県と隣接する県)におでかけするケースが多い
  • カルーセルUIを導入しても3件目以降あまり触らない

3-3.ユーザーインタビューから得られたこと

様々な知見を得ていますが、今回のアプリのHOME画面に導入したのは下記です。

  • 子どもがあそべるところを探すのに困っている
  • 雨でもあそべるところを探すのに困っている

4.結果として、ネイティブアプリにどんな機能を盛り込むことにしたか、Webとの違い

以上のことを考慮して、今回リリースした「HOME画面」ではアプリを開いて、すぐにおでかけ先が見つかるように、下記を配置しておでかけ先が見つかるようにしています。

  • 現在地周辺で、
  • いま人気の商品
  • いま人気のおでかけ先のジャンル
  • 「子どもとあそべる」「雨でもあそべる」などのシーン別
  • カルーセルUIは使わずに縦に商品を並べる

<現在地の人気商品を表示>

現在地の人気商品を表示

<周辺で人気のおでかけ先ジャンル、シーン別>

周辺で人気のおでかけ先ジャンル、シーン別

今回のネイティブアプリのリリースは、あくまでも最初の一歩です。
リリース後も多くの方々の 『週末のおでかけ先が見つかる』 を様々な知見から磨き続けていきます。


<さいごに>

アソビューでは「生きるに、遊びを。」をミッションに、一緒に働くメンバーを大募集しています! カジュアル面談もありますので、少しでも興味があればお気軽にご応募いただければと思います!

www.asoview.com

「Google Things to do」連携リリース - アソビューでの機能開発の流れ

こんにちは!
アソビューでバックエンド アプリケーションエンジニアをしている山野です。

アソビューは7月にGoogleとの連携機能「Google Things to do」のリリースをしました。
今回はこの機能開発について紹介したいと思います。

Google Things to doとは

Googleがアクティビティやアトラクション分野で導入した予約検索表示機能です。
これにより世界中のゲストが世界中のアクティビティやアトラクションを見つけ、 Google上で価格を比較することができます。
またゲストは体験したいアクティビティ・プランを見つけた場合は、 Google上からサービス事業者のWEBサイトへのダイレクトにアクセスでき、予約に進むことが出来ます。

google things to do:w=200
google things to do

開発体制

プロダクトオーナー・プロダクトマネージャーとエンジニア(私)の3人体制で開発を行いました。
開発要件・仕様の整理・設計の部分は私が担当し、
プロダクトオーナー・マネージャーにスケジュール調整や設計レビュー、
仕様・設計で検討が必要な部分について意思決定いただく形で進めました。

開発要件・仕様の整理と設計

今回開発した機能は弊社の商品データを指定のフォーマットでGoogleに連携する、
所謂「データフィード」機能の開発です。

この機能の開発にあたり、まず「Google Things to do」の 開発ガイドライン、ポリシー、並びにデータ連携フォーマットの資料を確認し、仕様を整理・理解するところから始めました。
(..余談ですがGoogleの資料が全て英語であったことが仕様を整理・理解する上で地味に大変な部分でした。)

developers.google.com

これを踏まえて、Googleの連携フォーマットと弊社側の商品データとのマッピング設計を開始。
Googleの仕様や制約によりデータによっては連携フォーマットに当てはめる部分が難しい部分があり、データの抽出方法やデータ加工仕様の検討・設計を繰り返し行いました。

その中の一例としてチケット料金 x 在庫を考慮した商品データの連携可否判定があります。
弊社の商品には販売枚数限定の商品(超特割)が存在します。
この商品の場合小人料金のチケット購入可能大人料金のチケット売切といったケースが考えられます。
Google things to do の仕様により商品の料金データは 大人料金のデータのみ連携が許可されているため、単純に「販売中の商品」を連携してしまうと上記のケースの場合、ゲストに購入できないチケット料金を提示しまうことになります。
よって、その商品の大人料金のチケットが購入可能かという点もチェックし、
商品データの連携可否を決定する設計しています。

設計にあたってはGoogleの方からも弊社専任の開発サポートエンジニアをアサインしてくださり、 連携方法について適宜サポートいただきながら一つ一つ解決していき作業を進めました。

この設計から最終的にデータは以下のようなI/Fで連携する形となります。
Googleの仕様によりフォーマットはJSONとなっており、
また以下の通りテキスト項目(タイトル・説明文等)は全て多言語を考慮したI/Fになっています。
(※フィールド数が多いため一部のみ掲載させていただいています。)

{
  "products": [{
    "id": "12345",
    "title": {
      "localized_texts": [{
        "text": "新宿御苑",
        "language_code": "ja"
      }]
    },
    "description": {
      "localized_texts": [{
        "text": "<h1>四季折々の自然が楽しめる都会のオアシス。日本における近代西洋庭園の名園</h1><p>約1万本の木々が茂る新宿御苑は、広さ58.3ヘクタール、周囲3.5kmに及ぶ広大な国民庭園です。風景式庭園、整形式庭園、日本庭園が巧みに組み合わせられており、日本における近代西洋庭園の名園と言われています。旧洋館御休所や旧御凉亭など歴史的建造物も多く、自然散策とともに歴史遺産をたずねることができます。</p>",
        "language_code": "ja"
      }]
    },
    "product_features": [{
      "feature_type": "TEXT_FEATURE_MUST_KNOW",
      "value": {
        "localized_texts": [{
          "text": "[営業時間] 9:00~18:30※時期により変動あり",
          "language_code": "ja"
        // :省略
        ]
      }
    }],
    "rating": {
      "average_value": 4.0,
      "rating_count": "8840"
    },
    // :省略
    "options": [{
      "id": "ticket0000003430",
      "title": {
        "localized_texts": [{
          "text": "新宿御苑 前売り電子チケット ※中学生以下無料",
          "language_code": "ja"
        }]
      },
      "description": {
        "localized_texts": [{
          "text": "<h1>四季折々の自然が楽しめる 新宿御苑 前売り電子入園チケット</h1><p>明治39年に誕生した新宿御苑は、様々な様式を巧みに組み合わせた、日本における近代西洋庭園の名園です。広さ58.3ヘクタール、周囲3.5kmの広大な敷地には、約1万本の木々が植えられており、季節によって移り替わる美しい自然を楽しむことができます。当ページでは、新宿御苑の「前売り電子入園チケット」を販売中。当日チケット売り場に並んで購入する必要がなく、スムーズにご入園いただけます。特に混雑が予想されるお花見の時期は、前売り電子チケットの購入がオススメです。<br>※小人(中学生以下)無料。高校生以上の学生、65歳以上の方は250円にてご入場いただけます(窓口で学生証または年齢の確認できる証明書の提示が必要です。)<br>※月曜日が休日の場合は、翌平日が休園日となります。<br></p>",
          "language_code": "ja"
        }]
      },
      "landing_page": {
        "url": "https://www.asoview.com/item/ticket/ticket0000003430/",
        "mobile_url": ""
      },
      // : 省略
      "price_options": [{
        "id": "12345",
        "title": "一般(大人)",
        "price": {
          "currency_code": "JPY",
          "units": "500",
          "nanos": 0
        },
  // : 省略
  }]
}

アプリケーション開発について

今回開発したアプリケーションは以下の処理を日次で実行するシンプルなバッチアプリケーションとなっています。

1. 商品データを抽出
2. 抽出したデータを指定のフォーマットでファイルに出力
3. 2のファイルをGoogleに送信

このシンプルな仕組みのバッチアプリケーションであるため、
当初は既存のバッチアプリケーション実行環境に相乗りする形を考えていました。
しかし既存の環境は古いインフラ構成で構築されている点から、
SREチームと相談の結果、 Google Things to do専用のバッチアプリケーション環境を新規に構築する方向としました。

構成は現在アソビューで主流となっている以下の構成で構築を行いました。

- アプリケーション :  EKS
- CD          :  Argo CD
- バッチ実行管理 :  Argo Workflows

この構成については過去のテックブログでSREチームのメンバーが
詳細な構成やメリットについて書いていますので、ぜひご一読ください。

tech.asoview.co.jp

アプリケーションの環境構築についてはSREチームメンバーにサポートをいただきながら挑戦させてもらいました。
実際構築を行なってみてアプリケーションの構築のしやすさを実感し、 またArgo Workflowsを利用することでダッシュボード上でバッチの操作ができるため、 テストや運用がしやすいメリットを感じました。

まとめ

冒頭で書いた通り私はアプリケーションを主としているエンジニアですが、
この開発では要件・設計からアプリケーション開発・インフラレイヤー構築と一通りを担当をしました。
これは自ら担当を希望したもので、 アソビューではこのようなエンジニア自身のキャリアプランや希望を汲み取り、 このようなエンジニアとして成長する機会を与えてくれる会社です。

エンジニアとしてより成長したい、もっと開発について聞いてみたいと言う方は、
カジュアル面談もあるのでぜひこちらもご覧ください。

www.asoview.com

ゴールデンウィーク累計2,300万PVのアクセスを捌いた負荷対策 ~ アソビュー!が行った3つの手法 ~

こんにちは! アソビュー開発チームの山内です!

先日、自転車でしまなみ海道を渡ってきました。 途中、ゲリラ豪雨に遭いながらもなんとか75km完走しました。ゴール後の温泉は最高ですね。 次は、晴れた日に渡って温泉に入ろうと思います。

はじめに

アソビュー!では、2022年のゴールデンウィークの4/29 ~ 5/8の間に約2,300万のPVがありましたが、システムダウンすることなく、多くのゲストにワクワクを届けることができました。 今回は、私達がゴールデンウィークに向けて準備した背景と、アプリケーションでの負荷対策を紹介します。

背景

アソビュー!は、全国の遊びやレジャーチケットを予約・購入して体験できるサービスになります。 そのため、サービスの特性上、行楽シーズンの大型連休にアクセスが集中しやすくなっています。

また、今回は2022年4月15日〜2022年5月5日までの期間中に関東・東海・関西・九州の地域でTVCMを放映しました。 更には、2年ぶりの制約のないゴールデンウィークということもあり、今まで体験したことのない多くのアクセスがあることが予想されました。

日本国内で待ちに待った大型連休、アソビューのシステムダウンにより、ゲスト・パートナーに迷惑をかけることは絶対に起こしてはなりません。

インフラ側での負荷対策はもちろん、アプリケーション側でも負荷対策を行いました。

負荷対策について

アプリケーション側で行ったシステム負荷対策は主に以下の3つです。

  • OLTPのパフォーマンス改善

  • 各ページのCDN配信

  • APIタイムアウト設定・サーキットブレーカー設置

それぞれを紹介していきます。

OLTPのパフォーマンス改善

概要

データ構造の更新や、追加によりOLTP(オンライントランザクション処理)性能は劣化することがあります。 性能の劣化により、SQLのレイテンシが大きくなることで、負荷を増大させシステムダウンの原因となり得ます。

主要となる導線の各ページの全てのSQLの性能を確認し、改善の余地が無いかを検討しました。

どうやって行ったのか

レイテンシが大きい各SQLを確認し、以下のようなパターンでSQLを改善していきました。

1 indexが貼られているかどうか

参照しているテーブルの適切なカラムにindexが無いと、処理速度が遅くなることがあります。 indexを確認し、適切に設定されていない場合はindexを追加していきました。

2 不要な相関サブクエリがないか

相関サブクエリは、結果行に対して全てチェックしていくため、パフォーマンスが悪いと言われています。 また、EXSITS句も相関サブクエリを利用している場合においては、パフォーマンスが悪いケースがあります。 これらは内部結合に書き換え、不要な相関サブクエリの排除を行い、再度実行計画を確認して処理の改善をしていきました。

3 ユースケースや処理の見直し

改修が重なり、仕様が変更されるにつれ、そもそも不要なデータを取得している場合や、処理自体が不要になっている場合もあります。 SQLのみに着目するのではなく、そもそもの処理の見直しをすることで改善につながったケースもありました。

結果

パフォーマンス改善の一例を紹介します。 上記の改善を行うことで、多くのSQLの処理を改善することができました。 以下の例では、レイテンシが100ms ほどでしたが、改善後は 2msほどになっています。

各ページのCDN配信

概要

以前のアーキテクチャでは、Javaアプリケーションが直接リクエストを受けて、コンテンツを返却していました。 そのため、アプリケーション及びDBに大きな負荷がかかっている状態でした。

そこで、主要なページをURLパスごとにキャッシュし、CDN配信することで負荷を軽減させました。

どうやって行ったのか

アソビューはSSRでコンテンツを表示しております。 そのため、そのままページをキャッシュしてしまうと、以下のような問題が発生します。

  • 会員情報がそのままキャッシュされてしまう
  • Cookieで判定や処理していたものが動かなくなる
  • SpringMobileでデバイス判定していたので、初回にキャッシュされたデバイス表示でコンテンツが表示されてしまう
  • ページ内容を変更した際に、瞬時に反映されない

各問題に対して、以下のように解決しました。

  • 会員情報や、Cookieの問題

会員情報を取得するAPIを作成し、フロントから取得するように変更いたしました。 また、Cookieに関してもAPIで代替できるものでしたので、APIを作成し適用していきました。

  • SpringMobileでデバイス判定

こちらは、CloudFrontのヘッダーを条件とするPC/SPの表示切り替え対応を行いました。 具体的には、SpringMobileでデバイス判定をした後に、CloudFrontで判定した結果を上書きすることで解決できました。

  • ページ内容を変更した際に、瞬時に反映されない

社内システム等でページ内容を適用すると同時に、CloudFrontのinvalidationが走るようにすることで、瞬時にコンテンツを更新することができました。

結果

主要なページをCDN配信にすることで、オリジンへのアクセスを大幅に減少することができました。 アプリケーション、DBに直接アクセスが行かないことで、負荷を減少することができています。

CDN配信前後のオリジンへのアクセス件数

APIタイムアウト設定・サーキットブレーカー設置

概要

アプリケーション間や外部連携先との通信において、高負荷状態のためにアプリケーションのスレッドが枯渇し、クライアントからアクセスができないケースや、 そのアクセスが更に高負荷状態を促進させてしまう悪循環に陥ることがあります。

この状態を解決すべく、APIタイムアウトとサーキットブレーカーの導入を行いました。

どうやって行ったのか

APIタイムアウト設定

まず、アソビューのフロントとバックエンドから呼んでいるAPIのうち、タイムアウト設定がされていないものに対して設定していきました。

タイムアウトの設定値については、直近のAPMを参考に正常時ではタイムアウトが起きない値をAPIごとに設定しました。

以下、RestTemplateでの実装イメージです。

@Bean
RestTemplate restTemplate() {
    RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
    RestTemplate restTemplate = restTemplateBuilder
            .setConnectTimeout(3000)
            .setReadTimeout(3000)
            .build();
    return restTemplate;
}

サーキットブレーカー

サーキットブレーカーとは、呼び出し先のアプリケーションが高負荷や障害等で一定のエラーやレスポンスがない際に、呼び出し元で判断して一定時間リクエストを遮断する仕組みです。 これは、障害の際に復旧するまでの期間にアクセスをしないことで、呼び出し先に不要な負荷をかけず復旧を早める狙いがあります。

アソビューから呼び出している複数のアプリケーションの呼び出し部分に対して、サーキットブレーカーを設置しました。

サーキットブレーカーは、リクエスト先アプリケーションの状態を以下3つのステータスで管理しています。

サーキットブレーカーの状態遷移

  • CLOSED

    正常な状態。直近のエラー数をカウントし、閾値を超えるとOPENに移行します。

  • OPEN

    エラーによりリクエストを遮断している状態。指定の時間を超えると、復旧の確認であるHALF-OPENに移行します。

  • HALF-OPEN

    システムが障害から復旧したかを確認している状態。少数のリクエストを行い成功すればCLOSEDに移行します。

これらのステータス変化の設定値を直近のAPMから計算して設定していきました。

以下、Resilience4jでの実装イメージです。

public CircuitBreakerConfig circuitBreakerConfig() {
    return CircuitBreakerConfig.custom()
            .failureRateThreshold(100)
            .slowCallRateThreshold(1000)
            .slowCallDurationThreshold(Duration.ofSeconds(10))
            .minimumNumberOfCalls(10)
            .slidingWindowSize(50)
            .waitDurationInOpenState(Duration.ofSeconds(60))
            .permittedNumberOfCallsInHalfOpenState(10)
            .automaticTransitionFromOpenToHalfOpenEnabled(false);
            .build();
}

結果

これらの設定の成果としては表しにくいのですが、 APIタイムアウト設定による高負荷状態の悪循環の防止や、万が一のためにサーキットブレーカー設定をしたことで、 高負荷状態の助長を防ぐことができ、大量のアクセスでもさばききれたのだと思います。

最後に

今回は、アプリケーションで行った負荷対策の一部を紹介しました。 参考になる情報があれば幸いです。

エンジニアチームとして、できることはまだまだあると思っております。 アソビュー!は、遊び、レジャーのインフラとして快適安心なサービスをゲスト・パートナーに届けれるよう、更に改善していきます!

アソビューでは「生きるに、遊びを。」をミッションに、一緒に働くメンバーを大募集しています! カジュアル面談もありますので、少しでも興味があればお気軽にご応募いただければと思います!

www.asoview.com

RDRA + JavaによるレジャーSaaSプロダクトの要件定義と実装のシームレスな接続 / JJUG CCC 2022 Spring 登壇レポート #jjug_ccc

JJUG登壇してきました。

こんにちは。アソビューCTOの江部です。 いきなり私事ですが昨日6/30は誕生日でした。ハッピーバースデー俺。 そして10年前の2012/6/30にアソビュー!はβ版をローンチしたのでした。おおきゅうなったのー。あの頃の若さを取り戻したい!!

さて、今日は6月19日に開催されたJJUG CCC 2022 Springに登壇してきましたので、そのレポートを投稿しようと思います。 登壇資料はこちらになります。

動画版はこちら https://www.dropbox.com/s/j7695fq2b2inbgp/jjugcccspring2022_ebe.mp4?dl=0

ちなみに、JJUG CCCへの登壇は初めてでして、無事発表できて良かったです。 Session自体は事前に録画して提出、当日はそれを放映し、質疑応答の部分だけリアルでリモート参加するという形でした。 録画&リモートは楽でよいのですが、個人的にはこういうイベントもそろそろオフラインでやれると参加者の方々の反応もみれるし交流がはかれて良いなーと思いました。

ざっくり概要

弊社でとりくんでいる現行システムの刷新プロジェクトを題材に、RDRAをつかった要件定義 + Javaによる実装を接続させようという試みを紹介しました。

  • プロジェクトの体制と要件定義のススメ方
  • Spreadsheet式RDRAのアウトプットイメージ
  • RDRAからJavaの3階層アーキテクチャへの実装マッピング

を紹介していますのでご覧ください。

ちなみに、JJUG当日や社内外で、以下のような質問をいただきました。

今回は既存システムの刷新とのことでしたが、完全新規のプロジェクトで今回の手法はどうでしょうか? どのようなプロジェクトでRDRAを取り入れるべきですか?

完全新規プロジェクトでも適用できると思いますが、既存システムの刷新以上に要件定義メンバーにビジネス面を含めて意思決定できる or 情報を持っている人を入れることがスムーズにすすめる上では必須だと思います。 セッション形式で進めていくので、その場で決めていけないと持ち帰りが多くなり効率が下がることになってしまうためです。 逆に、ビジネスの意思決定者を開発に巻き込むことでユビキタス言語の形成に役にたちますし、長期的に円滑なプロダクトづくりができる組織づくりができると思います。

また、何を作るのかによっても今回の手法を取り入れるべきかどうかは分かれるとおもいます。 なぜなら今回の進め方は要件定義セッションを毎回みんなでやるのでそれなりに時間とコストの初期投資がかかるからです。 よって、この投資回収が確実に見込めるかどうかが重要かとおもいます。

有効なプロジェクト

  • 一定以上の複雑なビジネスルールやユースケースの構造をもっている
  • プロダクトのニーズがすでに証明されており、ある程度成長したシステムの刷新(今回のケース)
  • 業務システムなど、既存のオペレーションを自動化などで改善することでコスト改善が見込めるプロジェクト

上記のようなプロジェクトは、構造化されたモデリングで表現することによるコミュニケーションコストの低減や手戻りリスクの削減がROIとして良いケースが多いのではと考えています。

有効ではないプロジェクト

  • 複雑なビジネスルールを持たない至ってシンプルなシステム
  • PMFにいったってない、仮設検証段階のプロダクト。スタートアップのランウェイを走り抜けている最中のプロジェクトなど

こういったプロダクトやプロジェクトでは今回のようなセッションベースの要件定義のモデリングプロセスは冗長かもしれません。 1点目は開発も単独か少人数になりますし仕様も単純なことからソースコードベースで会話することも可能だとおもいます。 また、2点目のようなプロダクトは、PMFに至った段階で一旦すべて作り直す覚悟で、シンプルな仕様から早くプロトタイプつくって市場のフィードバックを得ながら改修を重ねてグロースさせていったり、トラクションを得ることに目一杯時間やコストを掛けたほうが良いと思います。

簡単ですが以上になります。 こういった手法に興味がある、もう少し話聞きたい、という方はお気軽にご連絡ください!

Meety https://meety.net/matches/HHAUaMpxUftI

仲間も常時募集中です。 私達は「生きるに、遊びを。」をミッションに掲げ、本質的な人の幸せに貢献することを自分たちの存在意義としています。 あなたの技術力で「衣食住遊」を実現し、より遊びが充実した社会を作って行きませんか? ご応募お待ちしています! https://www.asoview.com/brand/engineer-career/

hooks APIで整理することでReduxが不要に。asoview.comにおけるReact状態管理の変遷

こんにちはアソビューのフロントエンドエンジニアの井上です。
ヌルゲーマーなので満月の女王レオナを昨晩ようやく倒せてガッツポーズです。#エルデンリング

はじめに

Reactベースのフロントエンドの状態管理、よそはどうやってるんだろう。。
気になりますよね?私も気になります。
今回はアソビューにおける状態管理の変遷と今どのように扱っているか、について紹介したいと思います。
結論から言うとReactとその周辺のライブラリの変遷に振り回されましたが、hooks APIの登場でしがらみから開放されて(Reduxを使わずに)スッキリしました。
なので最初に言っておくと、Reduxやその他状態管理ライブラリの話はあまりしないです。。それらは誰にとっても必要なものではないのでは?というスタンスです。
共感できる方は「あるある」とか「それな」とか言いながら読んでいただけると幸いです。

これまでのアソビューのReact状態管理

クラスコンポーネントとflux(〜2017年頃)

私は5年ほど前に入社したのですが、その段階だとfacebook/fluxというfacebook社(現Meta社)の提唱したfluxのリファレンス的なライブラリを導入していました。
こちらのライブラリを実際に使っているところを今まで他で聞いたことがないので渋い選択をしたんだなとは思います。(経緯は今となってはわからないのですが)
また、Reactのコンポーネントは全てクラスコンポーネントで構成されていました。

関数コンポーネント、Redux、HoC構成へ (2018年以降)

fluxの使い方も冗長になってる部分もあったことや、状態管理として一般的に普及していなかったため活用事例も多くベストプラクティスなども世に溢れているReduxに書き換えていきました。
また、当時はHoC(Higher-order component = 高階コンポーネント)が流行(?)で、また、関数コンポーネントでもクラスコンポーネントで使えたローカルstateやcomponentDidMountなどのような各関数を使えるようにするrecomposeというライブラリが出てきて使い勝手が良かったので導入し、 関数コンポーネント、HoC with Redux & recomposeという構成で整理していきました。
また、Reduxの設計パターンとしてはDucksが見通しが良かったのでそちらを使ってました。

コンテナコンポーネント(HoCとして状態管理や副作用を管理)とプレゼンテーショナルコンポーネント(コンテナから状態やアクションをpropsとして受け取って宣言的にUIの変化を記述)を分けて複雑な状態管理があるようなページやコンポーネントでも煩雑で読みづらいコードにならないようにしていました。

大体こういう見た目でした。

// SomeComponent/index.js

export default compose(
connect(
  state => ({
    ...state.someModule
  }),
  dispatch => ({
    onClickXxxxx: () => {
      dispatch(someReduxAction())
    }
  }),
  mapProps(
     // ...
  ),
  withState(
     // ...
  ),
  withStateHandler(
     // ...
  ),
  lifecycle({
    componentDidMount () {
        // ...
    }
  })
))(SomeComponent)
// SomeComponent/SomeComponent.js

const SomeComponent = (props) => {
  return (
     {/* JSX */}
  )
}

export default SomeComponent

これらの構成で機能的には事足りていましたが、やはり記述が煩雑になったり手数が多く、シンプルさ、可読性でいうとネガティブな面が目立ってきました。

良かったこと

  • recomposeで関数コンポーネントのままでクラスコンポーネントの機能が使える!
  • Presentational Componentの宣言的UIに対してAPIからのデータ加工、画面操作による状態の変化などの処理はReduxやHoCに寄せることで分離することができ可読性が上がった
  • ajaxの非同期処理をReduxのモジュールの中で完結させ、Presentational Componentには一切それらの記述を書かないようにできた(Redux thunkを使いました)
  • Redux devtoolsで状態の変化がわかったりタイムマシーン機能でデバッグがしやすかった
  • reselectも合わせて使うことでメモ化によりレンダリングコストの削減することができた

課題

  • 手数が多い。
    • ducksパターンでReduxのファイルはドメインごとに一つで全てまとめたものの、やりたいことに対して回りくどくなりがち
    • API一本からデータを取得したいだけで、Reduxモジュールを作ってアクション定義して、コンポーネント側でcomponentDidMountでアクションdispatchして結果をmapStateToProps経由でもらって、、のような。
  • どこに何を書くか、が曖昧になってしまった。
    • HoCはあらゆるコンポーネントに使っていいのか?
    • それだと複雑になりそうなので一番上のPageコンポーネントでのみconnectする形にした。
    • そうなると下位のコンポーネントへのpropsリレーが発生してしまい、特にtypescript導入前だとミスが発生してしまった。typescript導入後は型定義が面倒なことに。。
    • Presentational Componentの状態はコンポーネント付きのHoC(recomposeのwithStateHandler)で持つのかReduxで持つのか?など。
  • これはよく言われていると思いますがHoCと上のコンポーネントから渡されたpropsの重複に気づけずバグにつながったりした。
  • Reduxモジュールのtypescript型定義が煩雑になりがちでany型に逃げてしまうこともあった。

そしてhooks APIの登場(2019年以降)

ある程度それらの構成で作って行きましたが、2019年2月頃にReact 16.8で待望のhooksの正式導入されました。
これまでやってきたことが全て標準のAPIでできるしとてもシンプル、、!

※「HoCは良い設計では無かった、おすすめしない」という趣旨の事をFacebookの中の人か誰かが言っていた気がしたのですがソース見つからず。。

hooks APIが使えるようになってどうなったか

HoCが全く不要になりました。

recomposeの各関数の置き換え

  • 下記の様にrecomposeのAPIは完全にhooks APIに置き換えることができたので変更
  • mapProps -> useMemo
  • withStateHandler -> useState
  • lifecycle + componentDidMount -> useEffect

Reduxの置き換え

  • まずはRedux hooksに変更してconnectのHoCを使わないように
  • reselectのメモ化の恩恵はuseMemoでまかなえるのでそちらに移行
  • 中途半端にビュー関連の状態も持ってしまっていた(例えばモーダル開閉とか)ものをコンポーネントごとのuseStateへ
  • あれ?APIのやり取りとその結果の管理しかしてないReduxモジュールが出てきたぞ。。。↓

REST API処理もhooks化

  • 弊社ではREST APIでデータを取得するケースが多いのですが
  • 以前の記事でも書きましたがREST API用のクライアントhooksを作りました。
  • また、キャッシュ機構が協力なswrをfetch用に優先的に使ってます。
    • postでの通信が必要だったり、環境によって認証情報が違うためswrをラップしたカスタムhooksを作って対応しています。
  • APIのやり取りしかしてないようなReduxモジュールはこちらに移行していきました。

tech.asoview.co.jp

swr.vercel.app

結局Reduxが必要なくなった

  • もちろん弊社の一部のアプリケーションでは、という注意書き付きです!
  • 上手く整理するとコンポーネントに対しての状態管理をそのコンポーネント内で完結させることができた
  • コンポーネントをまたぐ状態管理が必要なケースがそれほど無かった
  • コンポーネントを分割してコンポーネントをlazyロードするようにしてもReduxのStoreは分割できないというバンドルサイズ観点でデメリットがあり、しかもasoview.comはまだspringベースのMPAが中心のため、関係ない画面のReduxモジュールの重さが全画面に影響してしまっています。
  • これらから、カスタムhooksを適切な粒度で分けて組み合わせることで状態管理だけを切り出して整理する必要性があまり感じられなくなってきたため、新規の機能ではReduxを使わなかったり既存でReduxを使用しているものも修正のタイミングで解体していきました。

カスタムhooks活用している具体例

APIのレスポンスを加工して返す

  • JSON色付け係たるフロントエンドエンジニアであれば親の顔より見てきたパターンですね
  • 弊社では基本fetchはuseSWRを使います。(REST API)
  • 一箇所でしか使わず、かつAPIから来た値をそのまま使うのであれば関数コンポーネントのボティ部分にそのまま書くこともありますが、加工したりいくつかのコンポーネントで共用するなどの場合は切り出すことが多いです。
  • 例えば2つのAPIの結果をクライアントサイドでアグリゲーションする、とかだと下記のような形でしょうか。
  • Reduxでreselectで同じことやるとselector関数2つ作ってそれに依存するselector関数をuseSelectorで呼んで、、とかになると思いますがカスタムhooksだとシンプルに済みますね。
export const useSomethingApi = () => {
  const {data: dataApi1} = useSWR('/pass/to/api1')
  const {data: dataApi2} = useSWR('/pass/to/api2')

  return useMemo(() => {
    if (!dataApi1 || !dataApi2) return null
    return /* 何かdataApi1とdataApi2組合わせて複雑な加工する処理 */
  }, [dataApi1, dataApi2])
}
const someData = useSomethingApi()

認証情報を取得した結果をuseContextで管理して全体で使う

  • ログイン後にセッションcookieを使ってAPI通信し、会員情報などを取得する処理
  • ReactのuseContextとswrを使った通信をまとめて専用のContext Providerを作りました。
  • 基本的には一回APIリクエストしてその結果を受け取るだけなのでレンダリングパフォーマンスの心配も不要かと思います。

MemberContextProvider

const MemberContext = React.createContext<CombineMemberContext>(initialContext)

export const MemberContextProvider = () => {
  // useSWRをラップしたカスタムhooks
  const { data: memberInfoData } = useSWRGateway<MemberProfile>('/[会員情報問い合せAPI]')
  // 中略
  return (
    <MemberContext.Provider
      value={{
        ...initialContext,
        ...memberInfoData,
        // 中略
      }}
    >
      {children}
    </MemberContext.Provider>
  )
}

export function useMemberContext() {
  return useContext(MemberContext)
}

使い方

<MemberContextProvider>
  <App />
</MemberContextProvider>
const {isLogin} = useMemberContext()

{isLogin && <MemberFavoritePlans />}

コンポーネントとそれを制御する関数を共通化する(例えばモーダルなど)

  • これまでReduxを使っていたモーダルについてリファクタし、モーダルのコンポーネントと外からモーダルを開く関数をカスタムhooksで返すようにしました。
  • Reduxで作ってたときは、Reduxモジュールの中も使うコンポーネント側も結構な記述量でしたがこのくらいにまとめられました。

※口コミ投稿ボタンはいろんなとこに置くことができて、押すとモーダルが開くというもの

export const useReviewModal = () => {
  const [isReviewModalOpen, setReviewModalOpen] = useState(false)
  const { isLogin, isValidating } = useMemberContext()

  // モーダル開く用の関数。レビュー投稿機能はログインしてなかった場合はログイン画面に飛ばすのでその処理含む。元々はここもReduxのアクション経由で行っていた。
  const openModal = useCallback(() => {
    if (isValidating) {
      return
    }
    if (isLogin) {
      setReviewModalOpen(true)
    } else {
      fallBackLogin()
    }
  }, [isLogin, isValidating, setReviewModalOpen])

  // 同じくモーダル閉じる用
  const closeModal = useCallback(
    () => setReviewModalOpen(false),
    [setReviewModalOpen]
  )
  const ReviewModalHooks: React.FC<ReactModalHooksProps> = (props) => {
    return (
      <Suspense fallback={null}>
        {/* 別で定義してるモーダル本体。この中身はシンプルに別で作った共通のModalコンポーネントを利用してる。 */
        <ReviewModal {...props} isOpen={isReviewModalOpen} onClose={closeModal} />
      </Suspense>
    )
  }
  return [ReviewModalHooks, openModal, closeModal]
}

使うときはこれだけ。これを色々なページに配置していきました。

const [ReviewModal, openReviewModal] = useReviewModal()

<ReviewModal />
<Button onClick={openReviewModal}>
   口コミを投稿する
</Button>

状態管理の方針

なんでフロントエンドの状態管理のツールが必要なのか

個々のReactコンポーネントにおいては宣言的UIで描画するためにコンポーネントに渡されるpropsがあり、それを何らかの操作などで制御したくなったときに持つローカルステート(クラスコンポーネントのstate、FCでhooksで扱うならuseState)ということになるでしょうか。

それを扱うときの注意点としていかに無駄な再レンダリングを防ぐかということが特にアプリケーションが大きくなったときにユーザーの操作感や表示速度にも直結するための大きな関心事であると思います。

そのpropsの変化のタイミングをReduxなどのアクションdispatchに限ったりRedux内部でselectorを使うことで着実に制御するというのが状態管理ライブラリのメリットの一つということになると思います。
また、コンポーネントを跨いで(コンポーネントツリー上で親子関係に無いコンポーネント同士)同じ状態に依存するUIがある場合に、遥か上位からpropsをリレーしたりして見通しが悪くならないようにするというのも可読性やメンテナンス性観点のメリットです。単純にグローバルstateとして例えばAPIから取得したデータを反映したり同期したりという用途でも使えます。
グローバルstateとしての使用は誤ると昔多用されていたwindow領域のグローバル変数のようになってしまいますので最小限にしたいところです。。
アプリケーションが大規模になって複雑になっていくとルールが無いと副作用を含んだ実装をされてしまいどんどん可読性が下がっていく実感はあります。

というわけで、まとめると大規模なアプリケーションにおいてパフォーマンスを落とさずに、実装ルールがつけやすくなるので、誰がやっても開発しやすく壊れにくいようにするというのが状態管理を切り出すメリットだと理解しています。

※ただ、開発しやすい、でいうとReduxは使う上でのボイラープレートが大きいというデメリットがあり、そういう観点でも今だとRecoilは最有力になるかとは思います。

asoview.comにおける状態管理

asoview.comはアクティビティやチケットの検索、予約サイトです。
サーバーとクライアントで頻繁に(例えば数秒単位やそれ以下)情報の取得、更新を行うほどのリアルタイム性のある要件がそこまで多くありません。(在庫など一部はその傾向はあるものの)

「状態」と一言で言っても解釈は多岐にわたると思いますが、asoview.comでは基本的にはページを開いたタイミングでのサーバー(DB)のデータと同期する、必要ならそのデータを加工した上で唯一の情報源として宣言的UIの関数コンポーネントに渡して表示する。

それに対しての検索や購入などの行動を完結させるためのUIのためにコンポーネントの状態が変化していくような流れであり、一連のUIの中でサーバーとの同期や更新などが必要なタイミングはそれほど多くありません。
サーバーとの同期という観点ではswrのkeyベースのキャッシュ機構が優秀でReduxでグローバルstateとして扱っていたようなAPIレスポンスの結果をswrで管理して同期を取るやり方ができています。(swrはとてもお気に入りのライブラリなのでまた別で語りたい。。)

そのため、基本的にはReduxやRecoilなどのReactの外で状態を管理するライブラリは使用せず、Reactの中でカスタムhooksを利用して管理する形を推進しています。

メモ化などで気をつければレンダリングパフォーマンスにも影響無くできると思います。(ここに関しては目を光らせてないと行けないですが)
範囲や更新回数が限定できれば、useContextを活用してコンポーネントツリーの各階層でコンテキストを共有するやり方も有効だと思います。

また、アソビューではB向け(施設パートナー向け)管理画面なども開発しておりそちらではもう少し複雑な状態が必要になる機能を作る可能性はあります。
ただ、現状HoC with Redux & recomposeになっていますがasoview.com同様そこまで状態管理を切り出すことが必要なロジックは無さそうです。

これらから基本的にはReactの標準API(useState, useEffect, useContext, useRefなど)で完結させるように必要に応じてカスタムhooksを作成して整理しています。

hooksで完結させる上で気をつけてること

  • 不必要に再レンダリングを招くstateをuseStateで作ってないか?
    • 直接UI変化に関与しないのであればuseRefを用いる
  • 関数コンポーネントでuseEffectを安易に使ってないか?
    • Reduxで整理していたときにuseEffect + useStateを組み合わせて使ってしまってdepsの副作用地獄(?)になってたことがあるので。。useStateは明示的なユーザーアクションで変化させるようにした方が良いと思います。
  • カスタムhooksの機能はI/Oは妥当か?
    • テストしやすく整理できているか?副作用とそうじゃない部分を上手く分ける。

まとめ

アソビューでは基本的にはバンドルサイズなどのことも考えてライブラリなどの導入は必要最低限にしています。

まだ、先が見越せない段階で安易にライブラリを導入を決めてしまうのは容易に引き返せない(技術的にというよりは一度作ってしまうと引っ剥がしたり別のライブラリの移行する時間や工数を捻出できないという面が多いです。)デメリットがあるので良くないと思っています。

これからも流行っているかどうか(もちろんこれも大事ではあるそうではないfacebook/fluxの扱いなどは苦労しましたので)以上に必要かどうか、他にライトな選択肢は無いかを考えながら設計していきたいと思います。


さて、アソビューではまだまだ変化の早いフロントエンド、Reactにおいて私達と一緒にあーだこーだいいながら設計、実装を進めてくれる仲間を探しております!
興味のある方、ちょっと話を聞いてみたいと思った方は、ぜひ下記ページをご覧いただきお気軽にエントリーいただければと思います。(まずはカジュアルに面談させてもらえればと思っております。)

www.asoview.com

アソビュー!でのEKSクラスターの今までとこれから

こんにちは、アソビュー!SREチームのkirimaruです。 コロナも少し落ち着き、皆さんもGWは大いに満喫していたのではないでしょうか。 残念ながら僕は本を読んで引きこもっていましたが......。

以前「アソビュー!がECSではなくEKSを選んだ理由」という内容でランサーズさんとのイベントでお話させていただきました。

speakerdeck.com

今回はこの時から約2年、アソビュー!がEKSとどう向き合ってきたのか、どう向き合っていくのかを採用している技術とともにご紹介できればと思います。


Intro

前述のイベントでは「大量のAWSアカウントとECSクラスターの管理が辛いから、整理してEKSに統合していくぞ!」とお話させていただきました。このイベントは2020年ですが、2019年に試験的にEKSを導入し、2020年に本格移行の意思決定を行っています。SREチームはまだまだ人数が少なく他の業務と平行しながらではあるものの、既存のAWSリソースの移行とECSからEKSへのアプリケーションの移行、AWSアカウントの閉鎖を現在も行っています。

ではこれが実際どうだったのかというと、現在約7割程のアプリケーションがEKSで起動するまでに至っています。また、AWSアカウントやECSクラスターも整理が順調に進んでいます。

EKSクラスターは繁忙期にはPod数は200を超え、ノードはEC2のスポットインスタンスが100を超える大きなクラスターに成長しました。EKSクラスターはマルチテナント・シングルクラスターで進めており、クラスター内にはワーカータイプやバッチ、プロキシサーバーやAPIゲートウェイ、そしてもちろんWebアプリケーションなど様々なタイプのアプリケーションが起動しています。

この過程で、最初期に構築した試験的なクラスターでは運用に耐えられなくなり、2021年の初頭に大きなクラスターの再構築を経て、現在も継続的に改善を行っています。

再構築の大きな原因はPrivate IPの枯渇でした。今では広く知られている気がしますし直近IPv6への対応*1もありましたが、当時はaws-nodeの設定変更だけでは追いつかず、かといって構築済みのEKSクラスターに新しいサブネットも割り当てることができないため、大きく構成を変える結果になりました。

ここからはいくつかのトピックに分けて、再構築時の分岐点に焦点を当てつつ、今までとこれからをご紹介します。


Infrastructure as Code

2021年のクラスターの再構築に当たり、最も変化の大きかった部分がここになると思います。

それまではEKSクラスターもそれが利用するVPC等もTerraformで構築していました。特にEKSクラスターの構築にはTerraformのモジュール*2を利用していましたが、再構築に当たり下記の理由でeksctlを利用することにしました。

  • Kubernetesバージョンアップの容易性
  • 公式ツールなので、今後の改善や知見の集約が高い確度で見込める
  • 必要な機能がTerraform moduleで実装が遅い場合がある

eksctl.io

ただし、EKSクラスターとそれ以外のVPC等のAWSリソースのライフサイクルが異なること、バージョンアップの容易性の担保やクラスターの作り直しも加味して、EKSクラスターはeksctlで管理し、VPCなどそれ以外のリソースをTerraformで管理運用していく形に決めました。

少し話が逸れますがアソビュー!は2022年7月から11期に入る、ベンチャー企業の中では歴史を持っている会社です。この歴史の中でインフラが混沌としていったのは先のイベントでも触れさせていただきました。新しい技術もその有用性があれば積極的に導入をしていっているアソビュー!ですが、混沌期の急速なサービスの成長と拡大にIaCが完全には追いついていけてなく、一部分のみの適用になっていました。そこで、今後のIaCの利用の拡大を推進するためにも、eksctlの導入と同時にTerraformとeksctlを並行して使えるような形でIaC用のリポジトリとCICDの再設計を行いました。

これらの対応により直近あったEKSのバージョンアップ対応もスムーズに行え、IaCの利用も順調に加速していっています。積み重ねが多いので100%すべてできたぞ!とはまだいきませんが、この調子で進めばそう遠い未来でもないと感じています。

CICD

IaCの項目も一部含まれますが、CICDも2021年のクラスターの再構築で手を入れました。

これまでのCICDはCircleCIでビルドからデプロイまでを一気通貫で行っていました。所謂CIOpsと呼ばれるオペレーションです。この際のKubernetesのマニフェストの管理はアプリケーションとミドルウェアで分かれており、アプリケーションはアプリケーションリポジトリ内、ミドルウェアはミドルウェアを一括でまとめたリポジトリで行っていました。これにより見に行く箇所が多く、マニフェストの変更にトリガーされてビルドとデプロイが走るなど、管理運用に課題感を持っていました。前述のIaCの見直しに合わせて、この課題の解決のためKubernetesのマニフェストの管理も整理していくことにしました。

そこで目を付けたのが Argo CD です。

argoproj.github.io

CIOpsにはシンプルで理解しやすく容易に構築できるメリットがあります。しかし、カナリアリリースなどの高度なデプロイ手法の導入や、CIツールが過剰に権限を持つことによるセキュリティリスク、冪等性担保の難しさ等を鑑み、Kubernetesとの相性も良いGitOpsの導入を考えました。また、その中から検討当時UIを持っていることでの取っ掛かりやすさや、周辺エコシステムによる拡張性の高さから、アソビュー!ではArgo CDを導入することにしました。

導入当初はミドルウェア用のリポジトリを改修してミニマムに利用を開始していましたが、現在はアプリケーションにも適用して段々とその利用を拡大させています。同時に、Kustomizeを導入することによりマニフェスト管理自体も効率化させています。

Argo CDを使ったCICD Pipelineの簡易構成図

今後はよりアプリケーションでのArgo CDの利用を拡大していくとともに、元々予定していた高度なデプロイ手法の導入や、Kustomizeのプラグインを利用した効率的なマニフェスト管理の導入を予定しています。

また、CI部分ではコンテナイメージのビルドに一部でCNBでのビルドも導入し始めました。こちらも今後利用の拡大を行っていく予定です。

ミドルウェア

最後に導入しているミドルウェアについて、一部ご紹介させてください。

Argo Workflows

argoproj.github.io

当初EKSクラスター上で起動するバッチはCronJobで定義していましたが、より複雑な条件を付けて実行する必要のある案件やPipelineの構築が必要になってきました。ワークフローエンジンはいくつか選択肢がありますが、アソビュー!ではArgo Workflowsを導入しました。

ざっくりとArgo Workflowsでできることを挙げると下記のようになります。

  • Kubernetes上にworkflow管理のためのUIとCRDを展開
  • workflowの各ステップそれぞれをKubernetesのmanifestとして定義、podで実行
  • マルチステップ、DAGなど複雑な依存を定義、UI上での操作、確認
  • EKS on Fargateでの実行

Argoのツール群のひとつであるArgo CDを導入していることもありますが、Argo Workflowsを選んだ理由はArgo Workflowsの方が開発者と管理者にとって学習・運用コストが低いと考えたことにあります。Kubernetesクラスター上で動かすだけであれば他にも選択肢はありますが、EKSを使っていく意思決定をしているため、アプリケーションと似たように設定できることや処理の全体感を確認できるUIを持っていることが導入の決め手になりました。管理しているcronの一覧が見られるだけでもワークフローエンジンは導入する価値あります。非常に便利です。

構成は下記のようになっています。

Argo Workflowsの構成

失敗したときの通知をDefault Workflow*3の機能を使ってSlack通知を行っている部分が工夫ポイントです。

今後利用を拡大させていく予定ですが、すでに既存の一部のバッチをArgo Workflowsに移行しているだけでなく、機械学習用のデータPipelineの構築も行っています。EKS on Fargateはログの扱いがやや難しいので、Firelensのこれからのアップデートにも期待したいところです。

AWS Load Balancer Controller

定番ではありますが、AWS Load Balancer Controllerも利用しています。

github.com

アソビュー!ではそれぞれのサービスと環境を分離させるため、それぞれに対して個別にNamespaceを区切って1つのクラスター内で同居させる構成をとっています。v2になってからNamespaceを跨いだ定義ができるようになったことで、非常に管理がしやすくなりました。以前は中間にNginx Ingress Controllerを挟んだ構成になっていたため特に痛感しています。

よく一緒に使われているExternalDNSですが、アソビュー!では利用していません。これはEKSクラスターとDNSのライフサイクルの差が大きいです。Load Balancerも含め、ライフサイクルを意識する機会がEKSを運用する中で何度もでてくると感じています。AWSの設計思想とKubernetesの設計思想の差異の中でどうそれぞれを管理運用していくか、面白い部分でもありつつ非常に難しい部分です。

Kubernetes External Secrets

非推奨に……なりましたね……。

github.com

以前ご紹介させていただいたものですが、移行先としては上記で案内されている「External Secrets Operator」が良いと思っています。導入後には改めてご紹介できればと思います。

tech.asoview.co.jp

github.com

最後に

振り返ってみるとここ数年でアソビュー!のインフラは大きく進化を遂げているなと改めて感じました。

目まぐるしくアップデートが重なる界隈ですが、自社のValueである「For You」を実現するためにこれからもよりよい技術を積極的に取り入れて改善を続けていき、サービスを成長させていきます!

アソビュー!では一緒にサービスを成長させていく仲間を積極的に募集しています!少しでもご興味の湧いた方は以下のリンクからぜひご応募ください。

www.asoview.com