アクトインディ開発者ブログ

子供とお出かけ情報「いこーよ」を運営する、アクトインディ株式会社の開発者ブログです

Lambda@Edge を利用したリアルタイム画像変換で学ぶ DevOps

概要

Lambda@Edge でリクエストに応じてリアルタイムに画像のリサイズ、WebP 変換するプログラムを作成したら、インフラ構築、アプリケーション設計、実装、テスト、CI によるビルド & デプロイ の一連の流れを広く身につける事ができるチュートリアルに最適な案件でした。自分と同じ、インフラやサーバーサイドの経験が浅い人向けの記事で、実装に関するコードは後日紹介したいと思います。作成したものは以下で、黒が既存。赤が今回新たに作成したものになります。

f:id:t-namikata:20200317093416p:plain

Lambda@Edge の良かった点と課題

良かった点
  • 画像変換を行う Lambda はサーバレスなので、管理コストがかからない。
  • AWS の S3 を利用しているサービスは導入が容易。
課題
  • CloudFront にキャッシュがない時は、 Lambda やっぱり遅い(Lambda の起動と画像処理で 2 秒ぐらいかかる)。
  • 頑張って作る必要ある? imgix 等の外部サービスの実際の使用感と金額知りたい。

はじめに

ユーザーが投稿してくれた画像を綺麗に見せてあげたい

iOS アプリの開発を担当している namikata です。今回は、アプリ用に WebP 変換した画像を提供する仕組みを AWS の Lambda@Edge を利用して作成した話をしたいと思います。実際にイチユーザーとして、いこーよアプリを利用しているのですが、前々から、アプリ内で表示している画質が荒いなぁと感じていました。それもそのはずで、いこーよアプリでは、ユーザーが投稿してくれた画像は 450x450px のサイズで画像を表示していた為、横幅いっぱいまで広げると 2x, 3x だと、ぼやけて表示されてしまいます。口コミ投稿する度に、なんだか画像がぼやけてしまって、楽しかった思い出も、なにやらぼやけてしまう感覚に陥ったり、陥らなかったり、やっぱり陥ったり。

左: アップロード後、右: オリジナル画像 f:id:t-namikata:20200317083928p:plain

子育てで忙しい中、せっかくユーザーの皆さんが、他の人の参考になればと投稿してくれたものなので、出来るだけ綺麗に表示してあげたい。ただ、日々使ってもらうアプリとして、通信量はとても大切なポイントとなるので、ユーザー体験を損なわないように出来る限り通信量は抑えたい。という、通信量は抑えつつ、綺麗な画像を配信する仕組みが必要になりました。

現状のいこーよの画像配信の仕組み

いこーよでは、コンテンツデリバリーの方法として CloudFront + S3 という一般的な組み合わせで画像を配信していて、画像 URL には、画像から計算されたフィンガープリントを付与することで、画像に変更があれば、画像の URL が変更になり、変更が即座に反映される仕組みで構築されています。画像をアップロードすると、アプリケーション内で定義したサイズにリサイズして S3 に保存され、 View で利用したい画像サイズを指定します。

  • source.jpeg( 2000x2000px 元の画像)
  • normal.jpeg( 450x450px)
  • thumbnail.jpeg ( 180x180px )

※ 既にいこーよでは nginx を利用して任意の画像サイズへリサイズする仕組みが導入されてますが、WebP 変換にはまだ未対応で、説明を簡単にする為にその紹介はスキップします。気になる方は是非こちらのブログを参照ください。

tech.actindi.net

綺麗な画像を見せたいだけであれば source.jpeg を参照すれば良いだけですが、前述した通り、できる限り通信量を抑えたいので、容量の大きい source.jpeg は参照したくありません。その為、一番直感的な方法としては、normal より大きい large.jpeg ( 1080x1080px ) を作成してあげる事です。ただそれだけだと、やっぱり通信量は増えてしまうので、アプリでの利用であれば WebP 配信してあげるのがいいでしょう。

それぞれ画像アップロードの際に生成するようにすると、結構な枚数になりました。

  • source.jpeg( 2000x2000px 元の画像)
  • large.jpeg( 1080x1080px )
  • large.webp( 1080x1080px )
  • normal.jpeg( 450x450px)
  • normal.webp( 450x450px)
  • thumbnail.jpeg ( 180x180px )
  • thumbnail.webp ( 180x180px )

出来ない事はないですが、今後サイズをまた変更したいなと思った時には、どこまで笑顔で対応できるか自信がありません。また ImageMagick を利用して画像加工をしている場合は WebP に対応したバージョンを利用する必要があります。今後の運用を考えると、理想の形として見えてきたのは

  1. S3 では元画像の source.jpeg のみ保存する
  2. リクエストに合わせて source.jpeg から適切に画像をリサイズ、圧縮(WebP)し CloudFront に返却する
  3. CloudFront は画像をキャッシュし、配信する

といった形が、楽しく仕事をしていく為の条件になっていきそうです。

サービスを選定する

早く、簡単に実現するなら、外部サービスを利用するのが最適解です。ImageFlux、Akamai、Fastly、imgix 等が上げられ、全て調べた訳ではないですが、基本的にはリクエスト数に応じての重量課金で提供されていると思います。 https://example.com?w=1080&webp=t などのようにリクエストすると、画像を保管しているサーバーから画像を取って来て、クエリに応じた形式に変換してくれるサービスを提供しています。また、顔認識を行い、人物にフォーカスが当たる形でトリミングを行ってくれるサービスもあったりします。

最初は外部サービスを利用する前提で調査を進めていたのですが Lambda@Edge を利用すれば、同様の事が出来るという事がクックパッドさんの記事で分かったので、実際に利用できそうか検証してみる事にしました。いこーよアプリは画像ギャラリーなどがあるサービスなので、画像へのリクエストも多めで、極力コストを抑えたいと思っていましたし、何より、実際に変換する処理を自分で書く事で、画像変換のノウハウを学習するチャンスです。

大変参考にさせていただきました。ありがとうございます。

techlife.cookpad.com

また、公式サイトでも Lambda@Edge を利用した画像変換プログラムについての紹介記事があります。

aws.amazon.com

初学者でも、インフラ構築、アプリケーション設計、実装、テスト、CI によるビルド & デプロイ の一連の流れを広く身につける事ができるチュートリアルに最適な案件だった。

最近 Firebase の CloudFunction とか使う機会が増えてきて、しっかり javascript を勉強したいと思い、勉強できる機会をさぐっていたのですが、正に適任といえる案件でした。Lambda@Edge は Node.js v12 をサポートしているので node + typescript で実装する事に決めました。また、インフラ周りは AWS CDK を利用することで typescript によるコード管理が可能です。

具体的な実装については次回の記事でまとめたいと思いますが、 Lambda@Edge を利用した画像最適化の実装には、インフラを含めて必要な言語の知識は typescript のみで済むので、とてもとっつきやすいと思います(AWS の各種サービスの知識はモリモリ必要です。あと、AWS のドキュメントって何であんなに日本語難しいんだろう)。実際に、自分も typescript を書いた事は、総人生の中で 10 時間ぐらいで、全くの初学者というレベルでしたが、処理の 99% は sharp というライブラリの活用で、実装がとても簡単だった為、すんなりと書き進める事ができました。

インフラ構築

作成するプログラムは AWS CDK を利用して、コードで管理することができます。コードで管理できる事がすごく重要で、AWS の管理コンソールを触らなくても、 AWS CDK のドキュメントを参照しながら、 Lambda や CloudFront を作成するコードを書いていく事で、自然と Lambda の実行に必要な権限の付与(アクセスコントールの仕組み)、 CloudFront と Lambda@Edge の紐付け(複数サービスの連携)などを学んで行く事ができます。また、開発基盤として各種用途に合わせたステージ(開発検証用の development, 本番リリース前に検証する staging, 本番の production 等)を用意することで、なるべく本番と差異なく、でも、必要な人だけアクセスできるように管理をしないといけない staging 環境を作るのが一番しんどいんだということを身を持って学ぶ事ができます。

また、今回作成するアプリケーションは、

  • CloudFront: リクエスト数に応じて課金
  • Lambda: リクエスト処理にかかる稼働時間に応じて課金
  • CodeBuild: デプロイ処理にかかる稼働時間に応じて課金

と全てが稼働時間やリクエストに応じての重量課金になっているので、 staging や development 環境など、リクエストが少ない環境ではほとんど金額が発生しないメリットがあります。

アプリケーション設計、実装、テスト

Lambda@Edge とかあまり聞いた事ないサービスを利用するので、少し敷居が上がるイメージがありますが、画像変換の処理自体はとてもシンプルで簡単なものです。簡単なアプリケーションですが、 Lambda で実行するプログラムを書く事になるので、外部依存する処理(CloudFront や S3 にアクセスする処理)と、本質的な処理である画像加工(Lambda 環境でなくても使い回せるアプリケーションが叶えたい目的)に分けられ、どのように実装していくと、テストが書きやすく、コードの品質が担保しやすいかを学ぶいい機会になります。こちらの和田さんの記事がとても参考になりました。

speakerdeck.com

テストを書きながら、実装とリファクタリングを通して、設計のブラッシュアップが出来る良い教材です。

CI によるビルド & デプロイ

インフラとアプリケーションのコードができたら Github と連携して各種ステージに自動的にビルドできる環境を作成します。AWS には CodeBuild というデプロイツールがあるので、それを利用する事にしました。特定のブランチへの push に応じて、development, staging, production へ自動でデプロイする方法を学びます。

また push をトリガーに インフラ用のスナップショットテストや、アプリケーション用の単体テストの実行、lint チェックを Github Actions で行い、テストがパスする事でサービスの品質が担保されている事の確認、コード規約が守られている事をチェックすることで、コードの品質が担保されている事の確認を行い、継続的な運用に必要な処理を行います。

Lambda@Edge を利用したリアルタイム画像変換の良かった点と課題

最後に Lambda@Edge を利用したリアルタイム画像変換を実装してみてと良かった点と課題をまとめたいと思います。

良かった点

画像変換を行う Lmabda はサーバレスなので、管理コストがかからない点は、外部サービスを利用する際と同様で、一度構築してしまえば、運用の手間がなくとても楽です。Lambda の同時実行数に対して上限が設けられているので、サービスの規模に合わせて、緩和申請が必要になる点には注意が必要です。

また、AWS の S3 を画像の保管場所として利用している場合は、既存の仕組みに手を加える事なく、画像変換処理を追加する事ができるので、新規実装時に本番に与える影響がなく、変更後の切り戻しも容易な点はメリットかなと思いました。

課題

CloudFront にキャッシュがない時は、画像変換用に Lambda が起動するのでやっぱり遅いです(Lambda の起動と変換で 2 秒ぐらいかかる)。ただ、画像はあまり変更頻度が高くないので、キャッシュの生存期間を 1 年など長めに設定し、変更があった際にはキャッシュの削除を行う設計にすれば、CloudFront にキャッシュがある場合は Lambda が起動せず、高速に動作するので、そこまでクリティカルな問題にはなりませんでした。ただ、ユーザー投稿により画像は常に増えるので、キャッシュが存在しない新規に増えた画像用に、アプリ側でいい感じに Prefetch して画像を先読みしたり、キャッシュヒット率を上げる為に、アプリ内で利用する画像サイズを統一したりとユーザー体験を上げる施策の実施は効果的です。

また、学習目的以外には、外部サービスを利用しない明確なメリットが感じられなかったので(当然、外部サービスに依存しないというメリットがありますが、餅は餅屋に的な話もあり。もしかしたら料金面で大きなメリットがあるかも)、どこかで外部サービスとの比較を行えると、Lambda@Edge の適正な評価になるのかなと思いました。

今、作成した Lambda@Edge を本番リリースに向けて準備中なので、具体的な実装の紹介は、近日中にまとめたいと思います。