Lambda を使った画像のリサイズサーバー
Adventarを支える技術 Advent Calendar 2019 の14日目です。
今日は Lambda , API Gateway, CloudFront などを使って、画像のリサイズサーバーを作る話を書きます。
Adventar における画像のリサイズ
Adventar は記事を投稿した際に、記事の関連画像を取ってきて表示する機能があります。
これは OGP などのメタタグから画像の URL を取得していますが、取得した URL をそのまま使用して画像を取得すると、いくつか問題があります。
- 毎回リクエストが飛ぶので行儀が悪い
- 画像の URL が
http
(非 SSL) の場合に mixed contents になる - 画像サイズが大きいとパフォーマンスに影響する(OGP の画像は 1000px 超えで設定してるケースも多い)
- Adventar でほしいのはせいぜい、100px〜200pxぐらい
これを解決するために、取得した画像をhttps
で配信し、適切なサイズにリサイズし、キャッシュするようなサーバーがあるとよさそうです。
画像のリサイズは Fastly や ImageFlux などのサービス、ngx_small_light などの OSS を使うなど、様々な方法がありますが、今回は自前で画像を持っているわけではなく、外部サイトの画像を取得して利用したいという少し違うユースケースというのもあり、Lambda と CloudFront で自作することにしました。
といっても全然難しい仕組みではなく、画像 URL を受け取ってリサイズして画像を返す Go のプログラムを Lambda で動かして、CloudFront でキャッシュしているだけです。Go のコードも100行ちょっとの簡単なものです。
いくつか工夫していているとこがあるので説明します。
DB に依存させない
まず、一つ目が DB に依存せずに動作させるということです。画像の URL は、DB に保存されていますので、画像 URL を知るためには、カレンダーの投稿 ID を受け取って DB にアクセスし画像 URL を取ってくる、ということが必要になりますが、そのためだけに DB にアクセスするのはめんどくさいので、今回はリクエスト URL に直接画像の URL を埋め込んでいます。
https://img.adventar.org/?url=<画像URL>
のような感じです。Lambda でこの画像 URL を fetch して、適当なサイズにリサイズして返します。本当はサイズの指定もクエリでできるようにしたいのですが、とりあえず今は1サイズしかないので固定にしています。
ハードコードでだいぶひどい感じですが、かなりギリギリになって実装したので雑コードです(投稿が始まる12月までにあればいいので実装を一番最後に回した)。
ダイジェスト値による検証
ただ、これだと任意の URL のプロキシになってしまってよろしくないので、ダイジェスト値を設定して URL が改ざんされていないかを検証しています。OSS なのでコードを読めばわかりますが、単に画像 URL に SALT を混ぜて sha1 を取っているだけの簡単なものです。なので最終的な URL はこのような感じになっています。
https://img.adventar.org/img/<digest>?url=<画像URL>
実際の URL はこんな感じです。
デプロイ
フロントエンドのデプロイ や バッチジョブのデプロイ にも利用した Serverless を利用しました。今回 Serverless 大活躍です。設定はこんな感じで何も難しくない。
デプロイスクリプトも
$ serverless deploy
で終了。
まとめ
Lambda を使った画像のリサイズサーバーについて書きました。明日は DB のスキーマ管理について書きます。