トラッカーズオークションにおけるドメインリファクタリング

株式会社Azoopで「トラッカーズ」の開発している員(Yun)です。

最近、ドメインにおける大規模なリファクタリングを実施したので、その経緯と内容を共有したいと思います。

背景

「トラッカーズ」は、中古車のオークションサービスとしてスタートし、多くの試行錯誤を経てきました。私が入社する前から、さまざまなビジネスモデルが試されていたようです。主要なサービス「トラッカーズオークション」は、フリマやヤフオクのような形式から独自の形に進化してきました。しかしその過程で、サービスの変更が頻繁に行われ、コード上のドメインモデルが現実のビジネスモデルに追いついていない状態が発生しました。

リファクタリング前の状態

以下は、リファクタリングを始める前のドメインモデルの状態です。
主に二つバージョンのオークションが存在していました。

旧オークションモデル


 

現行オークションモデル

 
 そして、新しい概念として「入札会(Sale)」を導入し、以下のように車両をグルーピングしました。

全体像はこの感じです。
 

これらの複雑なモデルを扱うため、生のSQLを駆使していましたが、その結果、メンテナンスが困難なSQLが生成されることがありました。
(例えば、ログイン中に会社が自身の特定の入札会においての入札を全て抽出する)

新しいオークションモデル

現実のビジネスをべースに、議論を通して、重複の概念を除去した以下のような新しいドメインモデルを策定しました。
CurrentAuction/OldAuctionの責務をSaleとTruckSaleに移り、BidをTruckerSaleに紐づくように変更しました。

リファクタリングのステップ

新しいモデルへの移行をスムーズに行うため、以下の要件を満たす必要がありました。

  • サービスを停止せずに移行する
  • 業務に影響を与えない
  • 問題が発生した場合、すぐ前の状態に戻せる必要がある

これらの要件を基に、以下のステップでリファクタリングを行いました。

  1. 新しい関連を持つテーブルのマイグレーション
  2. 新旧のデータ構造を同時に生成
  3. 新しいデータ構造へのアクセスを優先し、新しいデータがない場合は古いデータにフォールバック
  4. 既存のデータを新しい構造に移行
  5. 古いデータを扱うコードの削除
  6. 不要な外部キーとテーブルの削除

結果として、オークションに関するドメインモデルが現実のビジネスモデルにより近づいたと感じています。

今後の展望

開発の効率は大幅に向上しましたが、まだ「トラッカーズマーケット」というサービスとの結合度が高い部分が残っています。次のステップとして、ドメインリファクタリングを続ける予定です。この挑戦に参加したい方は、是非、弊社のエンジニアチームにご応募ください

AWS WAF完全に理解した

いつもお世話になっております。Azoopの武田です。
ここ3ヶ月エオルゼアで毎週1回某お母様と戦い続けて欲しかった白魔道士の杖が取れました。

さて弊社のプロダクトである運送業務支援サービス『トラッカーズマネージャー』(以下、トラマネ)ですが、お客様である運送会社の車両やドライバーの情報といった大切なデータを預かり、また輸送案件の管理や請求書発行など基幹業務を担わせていただいています。
ブラウザ経由で使うという性質上インターネットという広大で物騒な世界に露出しており、ログ上でも既知の脆弱性を元にした攻撃や攻撃の足掛かりを探す不審なアクセスを観測しています。
弊社のプロダクトに攻撃を仕掛けてくる悪い人たちをレイドの時間切れを迎えた光の戦士たちと同じように

『渦なす生命の色、七つの扉開き、力の塔の天に至らん!』

と魔法一撃でワイプできたらいいのですが、この世界にはそんな都合のいいものはないのでトラマネではウェブアプリケーションファイアウォール(WAF)を導入して防御を固めています。

WAF

WAF(Web Application Firewall)とはHTTPリクエストのヘッダやボディの内容を監視し脆弱性を突いた攻撃を防ぐセキュリティ対策です。例えばウェブアプリケーションセキュリティの教科書の最初ほうに載っているような「フォームにスクリプトタグを仕込む」というような単純なXSSから世界中を震え上がらせた(?)Log4j脆弱性のような既知の脆弱性を使った攻撃を防いでくれます。
弊社ではプロダクトのインフラとしてAWSを使っており簡単に導入できることからAWS WAFを使用しています。

AWS WAF

WAF自体はF5やImpervaなどのセキュリティベンダーからEC2で動かすためのAMIがAWS Marketplaceを通してリリースされており、慣れていたりより詳細に設定したいといったニーズには対応できますが専門的な知識がないと構築も難しく私を含めWAFについてあまり詳しくないメンバーで運用するにはハードルが天に届く勢いで高いです。
一方、AWS WAFを使うとALB, CloudFront, API GatewayとAppSyncのいずれかを使ってさえいればマネージメントコンソールから操作するだけで導入できるためそれだけでなんとか地上から見えるぐらいまでハードルを下げることができます。さらに後述するスコープダウンステートメントを設定するときのクセの強さはあるものの簡単な操作でルールやログの保管などの設定を行うことができます。また導入するルール自体もAWSが無料のマネージドルールセットとしてメジャーなものを用意しているのでそれをひとまず入れるだけでも対策の導入としては十分でしょう。
またAWS以外のもセキュリティベンダー各社がAWS WAF向けにAWS Marketplaceでマネージドルールセットを時間+リクエスト数による課金で販売しているので比較的安価に専門ベンダーの知見を取り入れることが可能です。

構成と運用

ではここでトラマネにおける構成と運用についてみていきます。

AzoopのWAF構成

トラマネはALBでリクエストを受けて後ろにFargateで動作しているRails製のアプリケーションサーバがいるというシンプルな構成になっています。(データベースその他のコンポーネントは省略)
Web ACLはステージング(QA用)環境と本番環境で共有しており挙動を揃えることでステージング環境でWAFの挙動起因による不具合に早いタイミングで気付くことができるようにしています1

ルールセット

Web ACLに設定しているルールはAWSマネージドルールといくつかの独自ルールを組み合わせてセットしています。

一部を抜粋すると以下のとおりです。

AWSマネージドルールセットのうち最初の5つは様々なブログやサイトで「基本セット」として紹介されていることがあるものです。基本セットと紹介されるだけあってメジャーな攻撃は一通り網羅されています。これをIP評価ルールと組み合わせることで体感で半分以上の攻撃は防げています。
さらにトラマネは日本国内で利用されているお客様がほとんどのため一部の国を除き海外からのアクセスを遮断することで8割近い攻撃を防げるようになりました。ここで一部の国としたのは例えばアメリカを遮断するとGoogleのクローラーまで遮断してしまう & クローラーのみ通すとクローキングにあたり禁止行為に抵触する可能性があるためです。

クセが強いスコープダウンステートメント

AWS WAFは検査対象をスコープダウンステートメントで設定できます。
スコープダウンステートメントは「検査対象を決める条件」なので設定時はあるパスを除外したいときは「このパスは検査しない」ではなく「このパス以外は検査する」ように書く必要があります。1つだけならNOTの設定ができるので簡単にできますが複数のパスを除外したいときは少し面倒です。NOTは複数設定できないのでスコープダウンステートメントを設定するときにANDまたはORを追加しそれぞれのスロットの「Negate statement results」にチェックを入れることになります。

つまり、複数のNOTを書くにはド・モルガンの法則による変換が必要です……私は頭の中で組み立てるのが苦手なのでメモ用紙に書きながらやりました……!将来のアップデートでこのあたりをいい感じにできるようになることを期待したいと思います。

マネージドルールセットから一部を除外する

AWSが提供しているマネージドルールセットから一部のルールを除外したい場合もあるでしょう。ルールのアクションはその種類に関わらずラベルを付与するのでマネージドルールセットのアクションはCOUNTにして後続に付与されたラベルに対するアクションを決めるルールを追加します。独自ルールは数による課金もあるので細かくやりすぎると結構な出費になることがあるので必要最小限に抑えるようすると運用にもお財布にも優しいです。

WAFのログ

弊社ではログは基本的にDatadog Logsに集約しており、AWS WAFのログもS3を経由してDatadogに流すことで検索できるようにしてあります。Datadog LogsはデフォルトでAWS WAFが出力するログをパースできるようになっているのでとりあえず流せば簡単に検索できるようになります。
AWS WAFは何も設定しないとすべてのアクションのログが送信されてくるので膨大な量になり検索性やお財布を大変痛めつけることになります。そのためCOUNTとBLOCK, EXCLUDED_AS_COUNTのログのみ出力されるように設定しています。

お客様のアクセスに対して意図しないブロックが発生したときはDatadogを使ってログを検索しスコープダウンステートメントを追加するといった対策を実施できるようにしています。

まとめ

トラマネではAWS WAFやセキュリティ診断を活用してお客様の情報を守っています。誤検知が起きて意図せずお客様をブロックしてしまってもDatadog Logsを活用して素早く問題を発見し必要なアクションを取れるようにしています。

そしてタイトルで完全に理解したと言っていますがわからないことだらけなのでこれからもよりよい付き合い方を探し求めて行こうと思います。


  1. Web ACL単位でも課金が発生するためコスト削減の意味合いもあります
  2. これがないとセキュリティ診断からの通信はXSSSQLインジェクションなどの攻撃を含むのでWAFで遮断されRailsアプリケーションまで到達せず診断できないのです…

ChatGPTのAPIをサービスに組み込んでみました

こんにちは。

株式会社Azoopで開発をしている真面目で誠実なエンジニアのokamuraです。
ChatGPTを使って何かできないかな?と思っているところに、ちょうど活用できそうな場面に遭遇したので、サービスに組み込んでみました。

どんなサービスに組み込んだ?

現在Azoopで提供しているトラッカーズマネージャーという運送会社向けのサービスに組み込みました。

具体的な機能の内容

トラックの修理整備記録データをシステムに入力する際に、システム側に用意されている修理整備箇所カテゴリのどれに該当するかを推測する、という機能です。

図のように、修理整備記録の請求書に記載されている内容を見て該当するカテゴリを選択するのですが、専門知識を持ったユーザでないと選択が難しい場合があります。

そのような場合に、質問するとどのカテゴリに入るか推測して教えてくれる補助機能があると便利そう、という要望がカスタマーサクセスの方から出ていました。

ChatGPT使えそう!と思ったので、質問するとどのカテゴリに入るか推測して教えてくれる補助機能を、ChatGPTのAPIを組み込んで作ってみました。

機能としてはシンプルで、単語を受け取って、ChatGPTのAPIを使ってカテゴリを推測し、推測した結果を返す、というものです。

実装の内容

ChatGPTのAPIでは、system、assistant、userという3種類のロールでメッセージを渡すことができます。

system
  • ChatGPTに役割や指示を与える
assistant
  • ChatGPTとの今までのやりとりを与える(今までのやりとりを踏まえた上で答えてくれる)
  • APIではインタラクティブなやりとりはできず、過去のやりとりからの文脈を判断して答えることができないので、過去のやりとりが必要な場合に必要となる
user
  • ChatGPTにユーザとしてのメッセージを与える

 

今回は、assistantは不要だったので、systemとuserのロールでメッセージを渡しています。

systemロールでは、下記のような指示をいくつか与えています。

  • あなたはトラックの修理整備について詳しいです
  • 与えられる単語に対して、どの選択肢が近いかを「AAA、BBB、CCC」の選択肢の中から答えてください。

userロールでは、ユーザから入力された単語を与えています。

まとめ

以上が、ChatGPTのAPIをサービスに組み込んでみた例でした。

今回は部分的に小さい規模でAPIを組み込んだのみでしたが、ChatGPTの一歩目としてはやれて良かったと思います。

今後もChatGPTの使いどころ探していきたいです。まだ公式のAPIは出ていないようですが、Code Interpreterもいろいろ使いどころありそうで気になります。

QuickSight余話

どうも、Azoopでエンジニアとして末席に名を連ねている杉本です。

最近社内の一部でグルメ部なるものを立ち上げる機運が少しずつ高まっていて、お財布の中が心配になってます。

今日はAWSで利用できるBIサービスの1つ、Amazon QuickSightのお話をしたいと思います。 弊社では、お客様の車両に関連する情報から車両の維持や運用にかかるコストを可視化し、お客様に現状をより理解しやすい形でご提供するためにQuickSightを利用しています。

今回QuickSightのお話をするにおいて、一度全体の機能をさらっとご紹介しようかと考えていたのですが、どうしてもボリュームが多くなってしまうので実際にQuickSightを利用した機能をリリースするまでの間で困ったり詰まったりしたポイントに絞って、困り具合とともにご紹介していこうと思います。

開発開始からリリースまでで困ったこと・詰まったこと

データソース大量登録問題(困り具合: ★☆☆)

データセットを作成する際、必ずデータソースを求められます。 最初の頃、いくつかのデータセットを作成しようとデータセットの作成画面にアクセスしていました。そして、都度RDSへの接続情報を入力していました。 時間が経ち、ふとあるとき新たしいデータセットの画面で下へスクロールすると、データセットと対になったデータソースが大量に発生していることに気づき…。

すでに作ったデータセットと同じデータソースを使う際は、データソースの一覧の下の方にある 既存データソースから から選ぶと、データソースが増殖しなくていいかもしれません。

いつの間にかデータソースが大変なことに

ユーザー権限数上限問題(困り具合: ★★★)

お客様のアカウントを作成する際、契約によってはQuickSightのダッシュボードを閲覧できるようにするためにQuickSightのユーザーも同時に作成し、ダッシュボードに対してユーザーへの閲覧権限を付与していました。 あるとき、エラーが出てアカウントが作成できない!と問い合わせをいただき、調査したところ権限数の上限に達して付与できず、アカウント作成の途中でロールバックしていました。 実は分析・ダッシュボード・テンプレート・テーマなどは、最大で100個のプリンシパルと共有できるとあり、裏を返せば 上限を超えると権限付与ができなくなる という落とし穴があります。

これらはグループ機能またはフォルダ機能などを使って解決可能です。弊社ではグループ機能を利用しています。

参照: https://docs.aws.amazon.com/quicksight/latest/APIReference/qs-api-permissions.html#qs-api-permissions-best-practices

ステージングと本番の同期問題(困り具合: ★★☆)

開発またはステージング環境で分析・作成のトライアンドエラー・もし問題ないと分かれば本番環境にも… 分析を環境ごとに分けたいという要望は普通にあると思います。別環境で作った分析を本番環境に移行するには、グラフ1つ1つの設定をコピーし、テーマを設定して…というのは、漏れなく移行するのは難しい話です。

これもいくつかやり方があるのですが、弊社ではAPIからテンプレート機能を利用してコピーする方法を取っています。

ざっくりとしたイメージ図です

画面とQuickSight上の数値ずれ問題(困り具合: ★★★)

すでにサービス上に集計された画面があり、その集計された数値をダッシュボードで表示したいとなったとき、データの構造が違うために取得する条件が変わり同じ数値にならないときがあります。そういう場合調査が大変なこともあり、アプリケーションやフレームワークなど見えないところでよしなにしてくれているものがあればなおさら原因が見えづらいことも。

こればかりは最初からデータの取得ロジックを揃えておいたり、データソースをファイルベースにしてしまい、ロジックをアプリケーション側で使い回すなどといった工夫が必要かもしれません。

テーマ間違っていじっちゃった問題(困り具合: ★☆☆)

分析を複数人で編集しているときなどで起こりうる話なのですが、共有されていないテーマを別の人が設定していたときに、自分が間違えて自分のマイテーマをクリックした瞬間テーマが切り替わり、さらに分析の自動保存が実行されて戻せなくなったりすることがあります(実際ありました)。

テーマを共有してもらえばなんとかなるかもしれませんが、そうではない場合はコンソールやAPIからテーマを取得して自分に共有したりなど少し面倒な対応が必要になるかもしれません。

QuickSightを運用する際、見えない仕様で困ったりコピーする機能が確立されてなかったりで色々と考えなければならない問題がいくつもあります。慣れが必要なのは言うまでもないですが、そういう状況になったときにこの記事が一助になれば幸いです。

このuseEffectは不要だとおれのサイドエフェクトが言ってます

すいません、そんなサイドエフェクトは持っていません・・・

ワールドトリガー面白いですよね。

こんにちは、株式会社Azoopで主にトラッカーズマネージャーの開発をしている中山です。

TL;DR

Reactの公式ページリニューアルされてとても良きです・・・

react.dev

駄文

少し前にReactの公式ページがリニューアルされて、面白そうな学習ページが増えたり、練習問題がたくさん用意されたりして、React学習の敷居が少し下がったなと感じました。

非常にいいページなので、紹介したくブログを書きました。

ぜひ見てください!!!(完)

react.dev

とするのは乱暴なので、自分が見て気に入ったページの

You Might Not Need an Effect – Reactの内容を一部、感想を添えて紹介したいと思います。

PropsやStateに基づく、Stateの更新

Updating state based on props or state

下にこのケースに該当するコードの例を置いています。

あるStateが更新された際に、別のStateを更新したい例です。

このケースは例くらい分かりやすい状態だとあまり発生しないかなと思います。

Stateの数が増えてきて、ソースコードが複雑になってきた時に発生しうるかなと思います。

  • useEffectの中でsetStateを行っている
  • useEffectの第2引数に変数が多く並んでいる

上記の場合は、このケースに該当するかもしれない・・・と疑ってソースコードを見ています。

例) useEffect削除前

例) useEffect削除後

Propsが変化したときに、Stateの値をリセットする

Resseging all state when a prop changes

下にこのケースに該当するコードの例を置いています。

あるPropsやStateの変化に応じて、フォームの値をクリアしたい例です。

これは過去、自分もuseEffectを使って書いてしまったなと反省しています😇

コンポーネントの状態をkeyを使ってリセットできるということを知らなかったが故の苦肉の策でした・・・

  • useEffectの中でsetState(""), setState(null)などを行っている

上記の場合は、このケースに該当するかもしれない・・・と疑ってソースコードを見ています。

例) useEffect削除前

例) useEffect削除後

 

送りコード2023春が開催されました

最近寒暖差が激しくて少しダレています。どうも、Azoop杦本です。

先月、数ヶ月に1度開催されている送りコードというものが開催されました。今回が第3回となります。 送りコードというのは以前の記事にも記載されている通り、エンジニアメンバー全員が普段の業務から離れて技術的負債の解消に集中する 日となっております。詳しくは下の記事をご覧ください。

tech.azoop.co.jp

重厚なメソッドの分解、フレームワークの更新、不要なファイルの削除、今まで見て見ぬ振りをしてきたものの対応、自動テストのチューニング、見えづらい問題の可視化など、今後もシステムを運用する上で必要な作業を2日間行い、最後にエンジニア全員で集まって作業内容の発表と質疑応答を行いました。

カジュアルに昼食を食べながら始まった送りコードですが、今回は私の希望で寿司となりました。いいですよね、寿司。

発表の前に乾杯!ただしノンアルコールです。

弊社エンジニアグループでは、不定期で読書会なるものを朝会の後に開催しているのですが、そこでリファクタリングデザインパターンの知識を改めて学び、ディスカッションしています。そうやって得た知見を送りコードの場でリファクタリングをする際に実践するなど、うまくこの機会を利用している人もいました。

運用していくとどうしても負債がたまっていくアプリケーションの整理、皆様はどのようにされてますか?

中身が謎な闇と化したEC2に光を当ててECSに移した話

エオルゼアの情勢が落ち着いたので次は王になろうと狭間の地へ向かい、大ルーンを集めていたら流れでラニ様にお仕えすることになり星の世紀を開いたAzoopのソフトウェアエンジニアの武田です。ELDEN RINGはトロコンしました。DLCが楽しみです。

 

以前、会社のnoteに書いたトラッカーズマネージャーのECS on Fargateへの移行に続く第2弾として中古トラックのオンライン売買プラットフォームのトラッカーズもほぼ同じ構成で移行したのでこれまた同じように語りたいと思います。

サービスの命に関わる「闇」

下の図は移行前のトラッカーズの構成です。

(移行後の構成については先述のnoteに書いたトラッカーズマネージャーとほぼ同じ構成になっているので省略します。)

トラッカーズのECS移行前

移行前

冗長化のため複数台で構成されたアプリサーバとデプロイに用いるJenkinsサーバがありそれぞれEC2に構築されています。

デプロイはJenkinsによって行われており、デプロイしたい人がデプロイ用ブランチに差分をマージしPipelineの実行ボタンを押すことで本番に反映されます。ここまではどこかしらで似たような構成を見たり聞いたことがあるような気がする構成です。

そしてこの構成に多くの人が開発と運用に関わり、時には履歴が追跡できない方法で変更されるなど歴史が積み重なった結果巨大な「闇」と化しました。

EC2ホストのディレクトリをマウントしたDockerコンテナ

アプリサーバが起動しているEC2にはDockerがインストールされており、Rubyやアプリケーションが利用するパッケージ類はコンテナ化されていました。ところがアプリケーション本体はイメージに含まれておらずホストのディレクトリがマウントされていました。設定はリポジトリ内のdocker-compose.ymlの環境変数、クレデンシャルはデプロイ時にJenkinsから配布されるenvファイル内に書かれていましたが長年の運用によりホストに直接設置されたファイルがいくつか存在している無法地帯と化していました。

また、デプロイはJenkinsから各サーバへSSHしコマンドを発行しておりソースコードGithubからクローンしコンテナイメージのビルド、アセットパイプラインの実行まですべて本番環境上で実行されていました。このため時々リソース特にメモリ不足によりデプロイに失敗することがありました。そのためアプリケーションの動作に必要なリソース量の1.5倍~2倍のスペックを用いておりコストが増える要因になっていました。

アップデートも復元もできないJenkins

デプロイに使っているJenkinsは自分が入社した頃から使われておりおそらくサービスの開発を始めた頃からあるものです。社内のどこにもこのJenkinsを構築した資料やツールが見当たらないことから手動で構築されたものと思われます。Jenkins Pipelineをはじめとする設定類はバージョン管理されておらずバックアップはEC2のスナップショットのみ、さらに最後のバックアップは1年以上前と障害等で再構築が必要になっても救出できないかもしれない状態でした。*1

また構築されてから移行までアップデートを実施しておらず最新版とはかなり乖離があったため素直にアップデートができなくなっていました。

闇に光を当てる

トラッカーズマネージャーの移行作業で得たノウハウをもとにトラッカーズの移行を始めることが決まりました。(調査に結構な時間がかかることからまずはトラッカーズマネージャーの移行をしてノウハウを得て実施する、と判断していました)

いよいよ闇と化したEC2に光を当てるときが来たのです。

本番環境にしかない必要なファイルを救出し「正しいフロー」に乗せる

まずは「プロダクトのリポジトリには存在しないがEC2上にはあるファイルやディレクトリ」の調査から取り掛かりました。これはEC2上にgit cloneされ、デプロイ時にgit pullしていたためgit statusを実行しバージョン管理外になっているファイルを1つずつ確認していく作業を繰り返します。
*2

一通り洗い出しが終わったらオープンにして大丈夫なファイルはプロダクトのリポジトリへ、クレデンシャルはJenkinsから配布するように設定します。

他にもデプロイでしか使わないスクリプトが見つかったためひとまずGithubに専用のリポジトリを作って避難させました。*3

Dockerイメージを作る

これまでホストのディレクトリをマウントすることを前提にしていたDockerイメージをアプリケーションに必要なコンポーネントをまとめたものに作り直します。
トラッカーズ本体はごくごく普通のRailsアプリなので難しいことはなくマルチステージビルドを使って最終的に出来上がるイメージには動作に必要なもののみ入ってる状態にしています。

また、移行完了まで並行運用期間が存在するため既存のDockerfileとは独立して作成しています。

脱・Jenkins

デプロイに使っていたJenkinsは前述の通りとても古いためこのまま使い続けるわけにはいかず、またJenkinsをアップデートしようにも再構築に近い作業になりそうでした。また既存のインフラとの並行運用期間が存在する以上そちらに支障をきたすような事象は避けなければなりません。このため新たにCodePipelineでデプロイフローを構築しました。

本番環境の切り替え

ここまでで一通り準備ができたのでいよいよ本番を切り替えます。(Cronはトラマネと同様ECS Scheduled task + ecscheduleで管理するようにしました)

アプリサーバの切り替え

トラッカーズはお客様はもちろん社内でも出品の代行など業務での使用者が多いプロダクトです。そのためテストに協力をしてもらいやすかったためHTTPヘッダーに特定のキーと値をセットした状態でアクセスするとECS側に接続されるようにALBのリスナールールを設定しました。この状態で協力してもらったスタッフに1週間5営業日程度使用してもらいログやコンテナの監視を入れつつ最後の調整を行いました。

完全切り替え時はALBのリスナールールを修正するだけなのでスムーズに移行することができました。

サイトマップの配信

トラッカーズではSEO対策としてサイトマップを配信しています。EC2で動作していたときにはcronスクリプトでXMLをDockerコンテナにマウントされたホストのディレクトリに生成していました。

ところがFargate on ECSを使うとデプロイ時にコンテナが破棄されるため別途永続ストレージを用意しなければ生成したサイトマップが消失してしまいます。

EFSをコンテナにマウントする方法もありますが、今回はシンプルにS3にサイトマップを生成しそこから配信することにしました。
ただし配信するドメインには注意が必要で、基本的には同一ドメインから配信する必要があるようです。

CloudFrontから配信することも考えましたがアプリと同じドメインから配信する必要がある制約から「できないことはないがちょっとしんどい」*4ということがわかりs3からサイトマップを取得するLambdaを作成しそれをALBにぶら下げて配信に使用することにしました。このLambda関数の中身はaws-sdkを使って書いた単純なnode.jsのハンドラになっています。これをGithub Actionsを使ってDockerイメージにビルドしLambdaにデプロイしています。

まとめ

多くの人が開発と運用に関わり、長い時が経過した結果構成の意図が行方不明になってしまい「闇」となったインフラに光を当てた話でした。

当時はこれが最速で立ち上げることができる手段として選択されなんやかんやで表に出てしまうような問題がなく運用できていたのは先人たちのおかげなので感謝を捧げつつ必要な改善が必要なタイミングで実行できる環境に作り変えました。

そして「できたから安心」としていると何ヶ月何年経過したときに将来のメンバーにまた同じような負債を押し付けてしまうことになるのでこれからも引き続き改善に取り組んでいく所存であります。

*1:救出できたところで使っていたものとも乖離しているため使い物にならないでしょう

*2:一時ファイルやcrontabに設定されているスクリプトによって生成されるファイルがそこそこの数あったため骨が折れる作業でした……

*3:最終的に不要になったので削除しました

*4:同一ドメインで配信するためにCFの前にnginxを建てるなど