Lambda を使った画像のリサイズサーバー

Adventarを支える技術 Advent Calendar 2019 の14日目です。

今日は Lambda , API Gateway, CloudFront などを使って、画像のリサイズサーバーを作る話を書きます。

Adventar における画像のリサイズ

Adventar は記事を投稿した際に、記事の関連画像を取ってきて表示する機能があります。

f:id:hokaccha:20191214213045p:plain

これは OGP などのメタタグから画像の URL を取得していますが、取得した URL をそのまま使用して画像を取得すると、いくつか問題があります。

  • 毎回リクエストが飛ぶので行儀が悪い
  • 画像の URL がhttp(非 SSL) の場合に mixed contents になる
  • 画像サイズが大きいとパフォーマンスに影響する(OGP の画像は 1000px 超えで設定してるケースも多い)
    • Adventar でほしいのはせいぜい、100px〜200pxぐらい

これを解決するために、取得した画像をhttpsで配信し、適切なサイズにリサイズし、キャッシュするようなサーバーがあるとよさそうです。

画像のリサイズは FastlyImageFlux などのサービス、ngx_small_light などの OSS を使うなど、様々な方法がありますが、今回は自前で画像を持っているわけではなく、外部サイトの画像を取得して利用したいという少し違うユースケースというのもあり、Lambda と CloudFront で自作することにしました。

といっても全然難しい仕組みではなく、画像 URL を受け取ってリサイズして画像を返す Go のプログラムを Lambda で動かして、CloudFront でキャッシュしているだけです。Go のコードも100行ちょっとの簡単なものです。

https://github.com/adventar/adventar/blob/10b9b2386f76d1fd66c10c31d4dd0550f6d0527d/image-server/main.go

いくつか工夫していているとこがあるので説明します。

DB に依存させない

まず、一つ目が DB に依存せずに動作させるということです。画像の URL は、DB に保存されていますので、画像 URL を知るためには、カレンダーの投稿 ID を受け取って DB にアクセスし画像 URL を取ってくる、ということが必要になりますが、そのためだけに DB にアクセスするのはめんどくさいので、今回はリクエスト URL に直接画像の URL を埋め込んでいます。

https://img.adventar.org/?url=<画像URL>

のような感じです。Lambda でこの画像 URL を fetch して、適当なサイズにリサイズして返します。本当はサイズの指定もクエリでできるようにしたいのですが、とりあえず今は1サイズしかないので固定にしています。

https://github.com/adventar/adventar/blob/10b9b2386f76d1fd66c10c31d4dd0550f6d0527d/image-server/main.go#L84

ハードコードでだいぶひどい感じですが、かなりギリギリになって実装したので雑コードです(投稿が始まる12月までにあればいいので実装を一番最後に回した)。

ダイジェスト値による検証

ただ、これだと任意の URL のプロキシになってしまってよろしくないので、ダイジェスト値を設定して URL が改ざんされていないかを検証しています。OSS なのでコードを読めばわかりますが、単に画像 URL に SALT を混ぜて sha1 を取っているだけの簡単なものです。なので最終的な URL はこのような感じになっています。

https://img.adventar.org/img/<digest>?url=<画像URL>

実際の URL はこんな感じです。

https://img.adventar.org/img/c96546f133a9e2803dd7bb033681ffcf81146cb0?url=https%3A%2F%2Fcdn-ak.f.st-hatena.com%2Fimages%2Ffotolife%2Fh%2Fhokaccha%2F20191203%2F20191203213418.png

デプロイ

フロントエンドのデプロイバッチジョブのデプロイ にも利用した Serverless を利用しました。今回 Serverless 大活躍です。設定はこんな感じで何も難しくない。

https://github.com/adventar/adventar/blob/10b9b2386f76d1fd66c10c31d4dd0550f6d0527d/image-server/serverless.yml

デプロイスクリプト

$ serverless deploy

で終了。

https://github.com/adventar/adventar/blob/10b9b2386f76d1fd66c10c31d4dd0550f6d0527d/image-server/Makefile#L10

まとめ

Lambda を使った画像のリサイズサーバーについて書きました。明日は DB のスキーマ管理について書きます。