こんにちは。請求管理ロボシステムチームの @trunkatree です。
今回は "署名付きURLを使ってS3に置いてあるファイルを取得するようにしました" というお話をしたいと思います。 ユーザーから見た挙動は特に変わらない地味な対応ですが、こういう裏方仕事、私はけっこう好きです笑
前置き
請求管理ロボシステム基盤の概要 にて紹介のとおり、請求管理ロボはレガシーな背景のあるアプリをコンテナ化してECSで動かしています。基盤刷新するにあたってもろもろの改修をしたわけですが、当時スケジュールなどの制約もあって全箇所を対応することはできませんでした。
対応しきれなかった箇所の1つとして、ユーザーがアップロードした画像ファイルの配信方法があります。アンチパターンの代表のような話ですが、もともとはアップロードした画像ファイルはアプリケーションサーバのファイルシステム上に置かれていました。基盤刷新の際にアプリケーションコンテナはステートレスになるよう改修を進めていましたが、先述のとおりスケジュール等の都合でどうしても間に合わず、配信方法まで完遂することはできませんでした。結果として、ファイルの実体はS3に置くものの、暫定的に goofys というS3バケットを擬似的にマウントするツールを導入し場をしのぐことになりました。
しばらくこの構成で運用してきたのですが、これはS3本来の使い方ではないことや、まれにgoofysプロセスが落ちて接続障害が発生する等いろいろと問題がありました。そのため、この度あるべき姿を整理し署名付きURLを用いた配信方法に改修する運びとなりました。
署名付きURLを使ってS3のファイルを取得する
署名付きURLとは
S3に置いてあるファイルを取得するにはそれ相応のアクセス権限が必要です。権限を持たない人に対してファイルを共有したい場合に、あらかじめアクセス権限を期限付きで付与したURLを発行することができます。これが 署名付きURL(Pre-Signed URL) です。認証付きURL、期限付きURLなどと呼ばれることもあるようです。期限内であればアクセス権限を持たない不特定多数のユーザーがこのURLを使ってファイルにアクセスできるようになります。
実装
懸念点の確認
署名付きURLは指定した期間内であれば誰でもアクセスできるものなので、機密情報を扱う際には注意が必要です。機密レベルを考慮し、適切な共有方法と有効期限を検討します。今回は特に問題ないと判断しました。
コード実装
S3と密結合にならないようにストレージレイヤを抽象化する実装を心がけます。 環境変数で、ストレージにS3を使うかどうか、バケット名、署名付きURLの有効期限など環境固有の情報を扱うようにし、環境ごとに切り替えられるようにおきます。
署名付きURLの発行は、請求管理ロボはPHPで書かれているので AWS SDK for PHP を用います。使い方は AWS SDK for PHP のドキュメント に詳しく書いてあります。
* サンプルコード
public function getOnetimeUrl($s3_Key) { $cmd = $this->s3->getCommand('GetObject', [ 'Bucket' => $this->bucket, 'Key' => $s3_Key ]); try { $url_object = $this->s3->createPresignedRequest($cmd, $this->s3_url_expiration); return (string)$url_object->getUri(); } catch (Exception $e) { UtilityApplicationLog::logError('failed createPresignedRequest() in getOnetimeUrl(): ' . $e->getMessage()); return false; } }
大変だったこと
もともと影響範囲の洗い出しがボリューム的にネックになっていたので、対応漏れのないように注意して進めました。結果的に対応する必要があったのは全体に対して一部のファイルだけだったのですが、よくわからない状態からスタートするのは怖いものですね。
気をつけたこと
当初、署名付きURLを発行してからファイル取得されるまで大した時間はかからないだろうと想定し、有効期限はわりと短め(でも十分と思える)な期間に設定していました。 しかし、多量の件数をシーケンシャルに処理するようなものがあり、処理のはじめのほうで発行した署名付きURLが実際にファイル取得される頃には期限が切れているということありました。
期限切れ発生イメージ図 ↓
・・・ for { ・・・ // 署名付きURLを発行 ・・・ } ・・・ // 署名付きURLでファイル取得 // → forループの初回のほうで発行された署名付きURLは有効期限切れで失敗 ・・・
この事象は有効期限をさらに十分に伸ばすことにより解決しました。期限設定を検討するにあたっては こちらのドキュメント も参考にしました。発行した認証付きURLがどういったタイミングで使われることになるのか、しっかり把握していないと思わぬ挙動になってしまうこともあるので気をつけたいですね。
実施して良くなったこと
署名付きURLを使って直接的にファイル取得するようにしたことで、S3の可用性をそのまま享受できるようになったというメリットがあると思います(S3の可用性については こちら )。
また、これまでは goofys とその監視のための mackerel-agent をECSインスタンスに入れていたのですが、その必要がなくなったので launch configuration を自作して管理する必要がなくなりました。これによって請求管理ロボはインスタンスに依存しない、よりコンテナベースなプロダクトに近づきました。今後 Fargate を利用するなどの選択も取れるようになり、よりシンプルでポータブルなシステムになりました。
まとめ
今回の取り組みで、基盤刷新の際に対応しきれなくて心残りだった部分を解消することができました。ECSインスタンスに手を加える必要がなくなったのはけっこう大きなポイントだと思っています。即座にユーザーへの変化はなくても、今後の請求管理ロボの発展における礎となったことでしょう。
クラウドベンダーが用意した仕組みをうまく利用してよりよいシステムをつくっていきたいですね。 今回は以上となります。最後までお付き合いいただきありがとうございました。