Dockerを使用してimgproxyで次世代イメージを提供する方法
著者は、 Diversity in Tech Fund を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
Web開発者が使用する最も一般的な画像形式は、JPEGとPNGです。 .jpg
ファイルは写真に最適ですが、.png
ファイルは、ロゴや図など、同じような色の大きなブロックを持つ画像に最適です。 ただし、これらの形式は、それぞれ1992年と1996年に最初に開発されました。 近年、ビデオ圧縮の進歩により、Web用の2つの新しい主要な画像形式webpとavifが開発されました。 「次世代」(または「次世代」)画像形式として知られるこれらの形式は、自由に使用でき、画像が大幅に小さくなり(20%から50%削減)、Webブラウザで広くサポートされるようになります。 webp
とavif
は画像が小さくなるため、これらの新しい形式で画像を提供する方が効率的で高速です。
ただし、「次世代」フォーマットの使用にはいくつかの課題があります。 まず、古いブラウザのユーザーに最大限の互換性を持たせたい場合は、必要に応じてpng
とjpg
を使用することをお勧めします。 第二に、アップロードされた画像の大規模なライブラリがある場合、それらの変換には時間がかかります(そして費用がかかります)。
これは、イメージプロキシが役立つ場合があります。 画像プロキシは、Webリクエストに基づいてその場でカスタム画像を生成または取得するアプリケーションです。 これらは、画像のトリミングやサイズ変更、透かしや画像効果の追加、フォーマットの変更に使用できます。 イメージプロキシを使用すると、エンドユーザーには同じように見えるが、はるかに効率的なイメージを配信できます。
使用可能なプロキシの1つは、imgproxy
と呼ばれます。 Evil Martians に支えられており、必要に応じて、商用の「Pro」拡張機能を備えたセルフホストの無料バージョンとして利用できます。 imgproxy
はGoで記述され、libvipsを使用して画像変換を強化します。 libvips
は、利用可能な最も高速で効率的な画像処理ライブラリの1つであり、ユーザーの要求に応じて画像を生成するのに最適です。
このチュートリアルでは、imgproxy
を設定し、画像を変更するためのプロキシURLを生成します。 また、imgproxy
を構成して、署名付きURLを使用して画像を安全に提供し、サポートされている場合は自動的に「次世代」画像形式に切り替えます。
前提条件
このチュートリアルに従うには、次のものが必要です。
- Dockerがインストールされたサーバーまたは開発マシン。 Ubuntu 20.04の場合は、ガイド Ubuntu 20.04 にDockerをインストールして使用する方法(または別のバージョンまたはディストリビューションを選択する方法)に従うことができます。 macOSまたはWindowsの場合は、 DockerDesktopの製品ドキュメントを参照してください。
- サンプルスクリプトを実行するためのスクリプト言語。 このチュートリアルでは、Rubyを使用します。これは、ガイド Ubuntu20.04でrbenvを使用してRubyonRailsをインストールする方法に従ってUbuntuにインストールできます。 macOSでは、組み込みバージョンを使用できます。
- ブラウザ開発者ツールにある程度精通していると便利です。 Chromeについては、公式ドキュメントまたはチュートリアルChromeツールを使用してパフォーマンスのボトルネックを見つける方法を確認してください。
ステップ1—Dockerを使用してimgproxy
をインストールする
この手順では、imgproxy
をインストールして、正しく実行されていることを確認します。 imgproxy
をインストールする方法はいくつかありますが、このチュートリアルでは、 Docker を使用します。これは、最も移植性の高い方法であり、本番環境に移行する際の変更が最小限であるためです。 。
Dockerがインストールされているサーバーまたは開発マシンで、次のコマンドを実行して、Dockerが実行されていることを確認します。
- docker info
出力の開始は次のようになります(コンテナまたはイメージの数がわずかに異なっていても心配しないでください)。
OutputClient:
Context: default
Debug Mode: false
Plugins:
buildx: Docker Buildx (Docker Inc., v0.7.1)
compose: Docker Compose (Docker Inc., v2.2.1)
scan: Docker Scan (Docker Inc., v0.14.0)
Server:
Containers: 15
Running: 0
Paused: 0
Stopped: 15
Images: 47
Server Version: 20.10.11
...
Dockerが実行されていることを確認したら、pull
サブコマンドを使用して、Dockerhubからimgproxy
イメージの最新バージョンをダウンロードできます。
- docker pull darthsim/imgproxy:latest
これにより画像がダウンロードされている間、一連のプログレスバーが表示されます。
次に、run
サブコマンドを使用してコンテナのインスタンスを起動します。
- docker run -p 8080:8080 -it darthsim/imgproxy
-p
オプションは、コンテナーのポート8080
をマシンのポート8080
にマップするようにDockerに指示します。 --it
オプションは、インタラクティブな端末が必要であることをDockerに通知します(この場合、imgproxy
によって出力されたログを表示する必要があります)。 最後に、darthsim/imgproxy
は、以前にダウンロードして実行したいイメージの名前です。
出力は次のようになります。
WARNING [2021-12-24T03:21:18Z] No keys defined, so signature checking is disabled
WARNING [2021-12-24T03:21:18Z] No salts defined, so signature checking is disabled
INFO [2021-12-24T03:21:18Z] Starting server at :8080
警告について心配する必要はありません。 これらは後の手順で修正します。
この手順では、impgproxy
をインストールし、実行されていることを確認しました。 次のステップでは、imgproxy
URLを使用して変更された画像をリクエストします。
ステップ2—画像を変更するためのimgproxy
URLの作成
このステップでは、変更された画像をリクエストするために使用できるimgproxy
URLを作成します。
imgproxy
を使用する場合、Webサイトのエンドユーザーはimgproxy
にHTTPリクエストを送信します。 次に、imgproxy
はソースイメージの取得を要求します。 ソースイメージは、プロキシにアクセスできるインターネット上のどこにあってもかまいません。 (これは後の手順で保護します。)ソースイメージが取得されると、imgproxy
は要件に応じてイメージを変更し、そのイメージをエンドユーザーに送信します。
URLパラメータで指定することにより、imgproxy
に必要な変更の種類を指定します。 imgproxy
URLは次の形式を使用します。
http://your-imgproxy-host/%signature/%processing_options/%encoded_source_url.%extension
このURLの%signature
はオプションであり、後の手順で構成します。 %processing_options
は、imgproxy
に画像の切り抜きや透かしなどを行うように指示する場所です。または、画像を変更したくない場合は、画像を完全に省略できます。 %encoded_source_url
は、ソースイメージを指します。これは、imgproxy
に変更を要求しているイメージです。 別のURLを埋め込むときに有効なままになるように、エンコードする必要があります。 最後に、オプションの%extension
パラメーターを使用すると、imgproxy
の画像ファイルタイプを指定して、.jpg
などの一般的なファイル拡張子を使用して変換できます。
プロキシされたURLを生成したら、img
タグのsrc
属性を新しいプロキシされたURLに置き換えることができます。 たとえば、一般的な画像のURLは次のとおりです。
<img src="https://yourserver.com/assets/image.png" />
imgproxy
を使用すると、そのURLを次のようなものに置き換えることができます。
<img src="https://imgproxy.yourserver.com/your imgproxy url here />
これは、Webサイトを変更するときにうまく機能します。 ただし、imgproxy
の使用方法を学ぶ場合、結果を表示する最も簡単な方法は、お気に入りのWebブラウザーのアドレスバーにURLを入力することです。これにより、結果がすぐに表示されます。 次に、imgproxy
URLのさまざまな部分を実行し、ブラウザでアクセスします。
Dockerでimgproxy
を起動したため、サーバーはlocalhost
で実行され、ポート8080
でリッスンしています。 これにより、imgproxy
ホストが作成されます。
http://localhost:8080
注:このチュートリアルを実行するためにローカルマシンの代わりにリモートサーバーを使用している場合は、次の例の「localhost」をサーバーのIPアドレスに置き換えてください。 正しいURLをお持ちの場合は、http://your-server-ip:8080
にアクセスすると、 Hey、I’mimgproxyメッセージが表示されます。
このチュートリアルでは、次のリンクにある子犬のこの写真を使用できます:https://i.imgur.com/KSLD4VV.jpeg
。 (この画像は元々 Unsplash からのものです。)または、インターネットでアクセス可能な任意の画像を使用できます。 このチュートリアルでは、イラストやロゴではなく、必ず写真(jpg
画像)を使用してください。
プロキシURLを作成するための最初のステップは、ソースURLをエンコードすることです。 これは、URLで意味のあるすべての特殊文字(たとえば、://
、/
、.
)を意味のない安全な文字に置き換えることを意味します。 このように、ソースイメージURLをプロキシURLに埋め込んだ場合、それは有効なままであり、クライアントはそれをプロキシURLから分離できます。
imgproxy
はURLセーフなBase64エンコーディングを使用します。 これに使用するお気に入りのプログラミング言語がある場合は、オンラインの Base64エンコーディングツールでうまくいきます([URLセーフエンコーディングを実行する(Base64URL形式を使用する)]チェックボックスをオンにしてください)。
注: /plain
オプションを使用すると、Base64エンコードの代わりにプレーンURLを使用できますが、これは、クエリパラメーターをエンコードする必要があることを意味します。 一般に、Base64エンコーディングはエラーが発生しにくいです。
URLセーフBase64を使用して、サンプルの子犬の画像はaHR0cHM6Ly9pLmltZ3VyLmNvbS9LU0xENFZWLmpwZWc
にエンコードされます。 これを使用して、imgproxy
URLを作成し、次のようにWebブラウザーに入力できます。
http://localhost:8080/sig/aHR0cHM6Ly9pLmltZ3VyLmNvbS9LU0xENFZWLmpwZWc
注:別の画像でテストする場合は、代わりにこのURLの最後の部分をBase64でエンコードされたURLに置き換えてください。
これをロードすると、子犬の変更されていない画像が表示されます。
注:画像が表示されない場合は、代わりにエラーメッセージが表示されるか、手順1のdocker run
コマンドの出力を確認できます。 エラーが発生したリクエストを含むimgproxy
ログが表示されます。
次に、URLの.extension
の部分を使用して画像形式を変更します。 ブラウザで以下を開きます。
http://localhost:8080/sig/aHR0cHM6Ly9pLmltZ3VyLmNvbS9LU0xENFZWLmpwZWc.png
これは読み込みに時間がかかります。ブラウザの開発者ツールでネットワークタブを見ると、この画像が元の画像よりもはるかに大きいことがわかります。サンプルの子犬の画像の場合、 png
のバージョンは15.84MBですが、元のjpg
のバージョンはわずか1.3MBです。 クライアントが画像をリクエストすると、imgproxy
が自動的にjpg
からpng
に変更しました。
次のように、これらの形式の1つを指定子として追加することにより、この画像をより効率的な「次世代」形式webp
またはavif
に変換することもできます。
http://localhost:8080/sig/aHR0cHM6Ly9pLmltZ3VyLmNvbS9LU0xENFZWLmpwZWc.webp
テストイメージのwebp
バージョンは約600KBです。これは、元の.jpg
と比較して約53% dの増加です。 ご覧のとおり、この画像形式を使用すると、画像のリクエストを高速化できます。
画像サイズを変更したり、さまざまな効果を追加したりすることもできます。 これを行うには、%processing_options
パラメーターとしてさまざまな変更を指定します。 この場合、resizing_type
のfill
を使用して、imgproxy
に画像のサイズをsize
で幅200、高さ500ピクセルに変更するように依頼します。 (画像アスペクト比を維持し、その外側の画像の一部をトリミングします)、次にマスクサイズ5を使用してガウスぼかしを追加します。
ブラウザで次のURLにアクセスして、これを試してください。
http://localhost:8080/sig/size:200:500/resizing_type:fill/blur:5/aHR0cHM6Ly9pLmltZ3VyLmNvbS9LU0xENFZWLmpwZWc
そのURLをブラウザにロードすると、テスト画像のぼやけたサイズ変更されたバージョンが表示されます。
サイズ変更とぼかしに加えて、使用できるさまざまな変換があります。 その他のオプションについては、imgproxy
ドキュメントのURLの生成セクションを確認してください。
このステップでは、ソースイメージを提供および変更するためのimgproxy
URLを生成しました。 次に、imgproxy
を保護して、%signature
URLパラメーターを使用して特に許可したURLからのソース画像のみを提供するようにします。
ステップ3—署名でimgproxy
を保護する
これまで、imgproxy
URLの「署名」部分でプレースホルダーを使用してきました。 ただし、使用しているURLに署名することが重要です。 つまり、imgproxy
が処理するURLは、秘密鍵と塩のペアを使用して作成したURLのみです。 これにより、悪意のあるユーザーがサーバーリソースを使い果たすのを防ぎます。 このステップでは、署名を要求するようにimgproxy
を構成し、URLで使用する有効な署名を作成します。
imgproxy
は12ファクターアプリです。つまり、ファイルを変更せずに環境変数を使用して構成できます。 Dockerは環境変数を設定するための--env
フラグを提供するため、これはDockerで実行する場合にうまく機能します。
署名を作成するには、最初にキーと塩のペアが必要です。これは、任意のランダムな16進文字列にすることができます。 コンピューター上のランダムデータを使用するか、オンラインランダムジェネレーターツールを使用してそれらを生成できます。 必ず64桁を指定し、少なくとも2つの文字列を生成します。1つはキー用、もう1つはソルト用です。
以下に示すように、64桁の2つの16進文字列を生成します。
Example Random Hex49d5e2cd30d80fccc2e30877e4e58b2f0854a8dca6fb2e980b129171910080ed7ffa5dfbfde006e0c1a8ff52e7b5c614f0d3e9ec6e6ed754399fb0e2eb473c59
(文字列は上記とは異なることに注意してください。)次のコマンドで使用するために、生成された文字列を記録します。
次に、手順1で使用したのと同じコマンドを使用してDockerを再起動しますが、今回は2つの--env
オプションを追加して、IMGPROXY_KEY
およびIMGPROXY_SALT
環境変数をランダムに生成された値に設定します。実行中のコンテナー。 強調表示された部分を、以前に生成した文字列に置き換えます。
- docker run --env IMGPROXY_KEY=first-hex-result --env IMGPROXY_SALT=second-hex-result -p 8080:8080 -it darthsim/imgproxy
これで、Webブラウザでhttp://localhost:8080/sig/aHR0cHM6Ly9pLmltZ3VyLmNvbS9LU0xENFZWLmpwZWc
を開くと、「403Forbidden」という応答が返されます。 Dockerログに次のようなエントリも表示されます。
Example Docker LogWARNING [2021-12-28T03:12:34Z] Completed in 134.6µs /sig/aHR0cHM6Ly9pLmltZ3VyLmNvbS9LU0xENFZWLmpwZWc request_id=WQGTfRgeBXPvaQYHkNjab method=GET status=403 client_ip=172.17.0.1 error="Invalid signature"
そのログ行の最後にあるように、imgproxy
は、imgproxy
が期待するものと一致する署名を指定していないため、403エラーを返しました。 これは、署名を要求するようにimgproxy
を正しく構成したことを意味します。 この署名照合は、共有秘密認証の例です。 imgproxy
は、要求されたURLの SHA256 ハッシュに加えて、キーとソルトを使用して、内部で署名を計算しています。 同じ署名を生成するには、同じ秘密鍵とソルトを提供する必要があります。 つまり、IMGPROXY_KEY
とIMGPROXY_SALT
の値を持つユーザーまたはプログラムのみが、imgproxy
インスタンスが応答するURLを生成できます。 これらの値がないユーザーは、リソースを使用できません。
注:キーまたはソルトの値をURLで直接指定しないでください。これは、Webページのソースで、または他のブラウザーやプロキシによってキャッシュされるときに公開されるためです。 そのため、SHA256のような「一方向」ハッシュを使用することが重要です。誰かがあなたの署名を取得してそれを使用して秘密を再作成する方法はありません。
最後に、有効なリクエストを行うには、imgproxy
と同じ方法で有効な署名を生成する必要があります。 imgproxy
Githubリポジトリで利用可能な署名アルゴリズムの実装例は多数ありますが、このチュートリアルでは、Rubyスクリプトを使用します。
nano
またはお気に入りのテキストエディタを使用して、signature.rb
というファイルを作成します。
- nano signature.rb
以下の内容をファイルにコピーします。 キーと塩のペアを、以前に生成したIMGPROXY_KEY
およびIMGPROXY_SALT
の値に置き換えます。 ローカルコンピューターではなくリモートサーバーを使用している場合は、localhost
の出力を最後の行のサーバーIPに置き換える必要があります。
# adapted from https://github.com/imgproxy/imgproxy/blob/master/examples/signature.rb
require "openssl"
require "base64"
key = ["your IMGPROXY_KEY value"].pack("H*")
salt = ["your IMGPROXY_SALT value"].pack("H*")
url = "https://i.imgur.com/KSLD4VV.jpeg"
encoded_url = Base64.urlsafe_encode64(url).tr("=", "").scan(/.{1,16}/).join("/")
path = "/#{encoded_url}"
digest = OpenSSL::Digest.new("sha256")
hmac = Base64.urlsafe_encode64(OpenSSL::HMAC.digest(digest, key, "#{salt}#{path}")).tr("=", "")
signed_path = "/#{hmac}#{path}"
puts "Open http://localhost:8080#{signed_path} in a web browser"
まず、このコードブロックには、署名を生成するために必要なライブラリが必要です。 次に、.pack("H*")
を使用してキーとソルト文字列を16進バイトに変換します。これは、OpenSSLライブラリが期待するものだからです(ここでパックとアンパックの詳細を読むことができます)。
次に、スクリプトは URL-safe Base64 が、手順1で行ったのと同様に、ソースURLをエンコードします。 ただし、この場合、Base64でパディングとして使用されている=
文字もすべて削除され、/
で16文字ごとに分割されます。 これらの変更により、見栄えの良いURLが生成されます。
スクリプトにエンコードされたソースURLが含まれると、SHA256アルゴリズム、秘密鍵、およびソルトを使用して署名が生成されます。 署名自体はBase64でエンコードされるため、結果のURLに安全に含めることができます。
最後に、スクリプトは、ブラウザで開くことができる署名付きURLを印刷します。
このスクリプトを実行して、署名されたURLを取得します。 ターミナルで別のタブを開くか、Ctrl+C
を押してimgproxy
を停止し、スクリプトを実行してから、docker
コマンドを使用して再度開始します。
- ruby signature.rb
出力は次のようになります。
OutputOpen http://localhost:8080/-CAkkjs5IioquivOi5LYyDnVxEULmPK-xIwIwXTleUA/aHR0cHM6Ly9pLmlt/Z3VyLmNvbS9LU0xE/NFZWLmpwZWc in a web browser
強調表示されている部分は、URLによって異なります。 これは署名されたURLであり、キーと塩のペアを知っているのは自分だけなので、自分だけが生成できます。 署名されたURLをブラウザで開くと、403ではなく期待される画像が表示されます。 後の手順で使用できるように、署名されたURLを必ず保存してください。
これで、署名付きURLを要求するようにimgproxy
を構成し、有効な署名付きURLを生成しました。 これで、キーとソルトのペアにアクセスできる人またはプログラムのみがimgproxy
と通信できます。
次に、imgproxy
を構成して、可能な限り最新の画像形式を自動的に提供します。
ステップ4—「次世代」の画像を自動的に提供するようにimgproxy
を構成する
これまで、画像を返すときに使用する形式をimgproxy
に明示的に指示してきました。 もう1つのオプションは、imgproxy
に可能な限り最も効率的な画像形式を使用するように指示することです。 これは、ユーザーのブラウザがサポートしている場合にのみ、「次世代」フォーマット(webp
またはavif
)を提供できることを意味します。 それ以外の場合、imgproxy
は自動的にpng
またはjpg
にフォールバックします。
imgproxy
は、Webブラウザがサポートされている画像形式の確認を要求したときに自動的に送信されるAcceptヘッダーを確認することでこれを行います。 MDN上の画像のデフォルトのAcceptヘッダーのリストを確認できます。 たとえば、Chromeは次を送信します。
image/avif,image/webp,image/apng,image/*,*/*;q=0.8
image/avif
とimage/webp
(それぞれの画像形式の MIMEタイプ)の両方が存在するため、Chromeは両方の画像形式をサポートしていることがわかります。
AVIF / WebP Support Detection を使用して、サポートされている最新の画像形式を使用するようにimgproxy
に指示できます。 これを有効にするには、IMGPROXY_ENABLE_WEBP_DETECTION
環境変数をtrue
に設定します。これは、docker run
を呼び出すときに指定することで実行できます。
まず、Ctrl+C
を使用して、実行中のDockerイメージを停止します。 次に、次のコマンドを使用して再起動し、強調表示された部分をキーとソルトの値に置き換えてください。
- docker run --env IMGPROXY_ENABLE_WEBP_DETECTION=true --env IMGPROXY_KEY=your-key-from-Step-3 --env IMGPROXY_SALT=your-Salt-from-Step 3 -p 8080:8080 -it darthsim/imgproxy
これは、手順3で使用したのと同じdocker
コマンドですが、webp
検出を有効にするための--env
フラグが追加されています。
設定を確認するには、ブラウザで画像を開きます。 手順3で生成した署名付きURLにアクセスします。これは次のようになります。
Example Image URLhttp://localhost:8080/-CAkkjs5IioquivOi5LYyDnVxEULmPK-xIwIwXTleUA/aHR0cHM6Ly9pLmlt/Z3VyLmNvbS9LU0xE/NFZWLmpwZWc
webp
の画像が提供されます。 これを確認するには、リクエストのサイズを確認してブラウザ開発ツールに入力するか、curl
を使用します。 curl
を使用して確認するには、ターミナルで新しいタブを開いてimgproxy
が実行されたままになるようにし、次のコマンドを使用して、強調表示されたURLを独自のものに置き換えます。
- curl -s -v -H "Accept: image/avif,image/webp,image/apng,image/*,*/*;q=0.8" http://localhost:8080/M-RTYvp5xktEK9gG93hPwAB6on9aX7H5XciGsI3XSac/aHR0cHM6Ly9pLmlt/Z3VyLmNvbS9LU0xE/NFZWLmpwZWc
-s
フラグは、プレーンテキストで出力する場合、画像があまり意味をなさないため、出力を無音にすることを意味します。 -v
は冗長を意味するため、リクエストヘッダーとレスポンスヘッダーを確認できます。 最後に、-H
オプションは、Accept
ヘッダーをChromeのヘッダーと同じになるように設定します。
出力は次のようになります。
Output* Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> GET /M-RTYvp5xktEK9gG93hPwAB6on9aX7H5XciGsI3XSac/aHR0cHM6Ly9pLmlt/Z3VyLmNvbS9LU0xE/NFZWLmpwZWc HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.77.0
> Accept: image/avif,image/webp,image/apng,image/*,*/*;q=0.8
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Cache-Control: max-age=3600, public
< Content-Disposition: inline; filename="KSLD4VV.webp"
< Content-Length: 601246
< Content-Type: image/webp
< Expires: Tue, 28 Dec 2021 09:08:32 GMT
< Server: imgproxy
< Vary: Accept
< X-Request-Id: I3sp-dXATs2AClfpHcSg-
< Date: Tue, 28 Dec 2021 08:08:32 GMT
<
* Failure writing output to destination
* Closing connection 0
Content-Type
ヘッダーで、imgproxy
がwebp画像を返したことがわかります。
必要に応じて、IMGPROXY_ENABLE_AVIF_DETECTION
環境変数についても同じことができます。 これも同様に機能しますが、webp
ではなくavif
形式の場合です。
この手順では、ユーザーのブラウザでサポートされている場合にのみ、次世代の画像形式を自動的に使用するようにimgproxy
を構成しました。
結論
このチュートリアルでは、新しいイメージ形式と、Dockerを介してimgproxy
を実行、構成、および保護する方法について学習しました。 また、imgproxy
を構成して、webp
画像を処理できるブラウザーを使用して任意のWebサイト訪問者に自動的に提供します。 これで、Dockerコンテナを自動的に実行して画像をオンザフライで変換し、 WebVitalsスコアを向上させる場所にimgproxy
をデプロイする準備が整いました。
次のステップとして、imgproxy
を使用して画像を処理するための追加オプションを調べることができます。 処理オプションの完全なリストは、製品ドキュメントで確認できます。 追加の構成オプションを調べることもできます。 最後に、imgproxyコンテナをDigitalOcean AppPlatformにデプロイできます。