写真:Phil Roeder
Image‑Filterモジュールとsrcsetタグを使用して画像を動的にリサイズ
レスポンシブWebデザインは、現代のWebサイトやWebアプリケーションの標準となっており、さまざまなデバイスで一貫したエクスペリエンスを提供しながら、各デバイスに合わせて表示を最適化します。ただし、現代のデバイスは画面サイズだけでなく、ピクセル密度も異なります。HTML5のimgタグには、サーバーが複数のバリエーションを提供している場合に、ブラウザーが最適なアセットを選択できるようにする機能が複数用意されています。Webサイトで同じ画像を複数の異なるサイズで配信すると、Webブラウザーは現在の環境に最適なサイズを選択できます。
そのため、レスポンシブ画像を使用すると、Webブラウザーはデザイナーの意図により近い表示を実現できます。これによりユーザーエクスペリエンスが向上する一方で、開発チームと運用チームには、デフォルト画像に加えて多数の画像アセットのバリエーションを作成し、デプロイするという負担が増えます。
このブログ記事では、NGINXおよびNGINX Plus向けのImage‑Filterモジュールを使用して、多数の画像アセットのバリエーションを作成・管理する手間をかけずにレスポンシブ画像を配信する方法を紹介します。画像ごとに1つの「ソース」バージョンをデプロイし、NGINXまたはNGINX Plusで動的にサイズ変更できます。この記事の内容は、Image‑Filterモジュールのインストールに記載された個別の手順を除き、NGINX Open SourceとNGINX Plusの両方に適用されます。簡潔にするため、以降は一貫してNGINX Plusと表記します。
The srcset属性
レスポンシブ画像を配信するための主要な手段は、HTML5のsrcset属性です。この属性はimgタグで使用し、ピクセル密度やビューポートのサイズに応じて複数の画像アセットのバリエーションを指定できます。ビューポートとは、デスクトップ上のウィンドウ内か、モバイルデバイス上のフルスクリーンアプリ内かを問わず、Webブラウザが利用できる表示領域の総称です。
次の例では、src属性でデフォルト画像を指定します。これは、「srcset」属性に対応していないブラウザー向けです。また、srcset属性では2つのバリエーションを指定します。1つは標準ピクセル密度のディスプレイ向け(1x)、もう1つはApple Retina™ディスプレイや一部の4Kモニターなど、2倍のピクセル密度のディスプレイ向け(2x)です。
<img src="/images/mylogo-default.png" srcset="/images/mylogo-density1.png 1x, /images/mylogo-density2.png 2x">次のより高度な例では、ビューポートの幅に応じて表示する複数の画像アセットのバリエーションを定義します。sizes属性では、ビューポートの幅が10emを超える場合はビューポートの半分の幅で画像をレンダリングし、それ以外の場合はビューポート全体を使用するようブラウザに指定します。ブラウザは画像に使用できるスペースを判断し、そのスペースに最適な画像アセットのバリエーションを選択します。通常は次の幅(wサフィックス)に「切り上げ」、画像がスペースを正確に埋めるよう内部でサイズを変更します。
<img src="/images/racecar-default.jpg" sizes="(min-width: 10em) 50vw, 100vw"
srcset="/images/racecar-100px.jpg 100w, /images/racecar-225px.jpg 225w, /images/racecar-450px.jpg 450w, /images/racecar-675px.jpg 675w">さまざまな画像アセットのバリエーションを指定してレスポンシブ画像を配信するこの方法は、実装が容易で非常に効果的です。ただし、画像バリエーション自体の作成と管理には課題があります。事前に大量の画像をリサイズし、より多くのファイルをサーバーにデプロイする必要があります。また、各バリエーションの最適な数とサイズを調整するには時間がかかる場合があり、その結果、各画像アセットのバリエーションにアクセスできるかどうかのテストも難しくなります。
レスポンシブ画像のsrcset属性やその他の手法について詳しくは、こちらの優れたブログ記事をご覧ください。
Image‑Filterモジュールのインストール
Image‑Filterモジュールの入手手順は、NGINX PlusとNGINXで異なります。
NGINX Plus向けImage‑Filterモジュールのインストール
Image‑Filterモジュールは、NGINX Plusサブスクライバー向けに無料の動的モジュールとして提供されています。
- NGINX Plusリポジトリからインストールして、モジュール本体を入手します。UbuntuおよびDebianシステムの場合:
$ sudo apt-get install nginx-plus-module-image-filter- RedHat、CentOS、Oracle Linuxシステムの場合:
$ sudo yum install nginx-plus-module-image-filter- モジュールを有効にするには、
load_moduleディレクティブをnginx.conf構成ファイルの最上位(「main」)コンテキストに追加します(つまり、httpまたはstreamコンテキストには追加しません)。
load_module modules/ngx_http_image_filter_module.so;- NGINX Plusをリロードし、実行中のインスタンスにImage‑Filterモジュールを読み込みます。
$ sudo nginx -s reload>NGINX Open Source向けImage‑Filterモジュールのインストール
Image‑Filterモジュールをインストールする最も簡単な方法は、NGINX公式リポジトリから入手することです。こちらの手順に従って、NGINX公式リポジトリにアクセスできるようにシステムを構成し、オペレーティングシステムのパッケージマネージャーを使用してインストールしてください。
UbuntuおよびDebianシステムの場合:
$ sudo apt-get install nginx-module-image-filterRed Hat、CentOS、Oracle Linuxシステムの場合:
$ sudo yum install nginx-module-image-filterインストールが完了したら、「NGINX Plus用Image-Filterモジュールのインストール」の手順2と3に従ってNGINXを設定し、再読み込みします。
Image‑Filterモジュールをソースからコンパイルし、静的コンパイル済みモジュールまたは動的モジュールとして読み込むこともできます。詳細については、「NGINX Plus Admin Guide」を参照してください。
画像サイズをピクセル密度に合わせる
Image‑Filterモジュールを使用すると、各画像について単一の「ソース」バージョンを作成してデプロイし、ブラウザーから要求された任意のサイズにNGINX Plusで動的にリサイズして配信できます。画像を手動でリサイズしてWebサーバーにデプロイすることなく、HTMLソース内だけでレスポンシブWebページと画像を細かく調整できます。
このサンプルHTMLファイルでは、ピクセル密度が異なるデバイス向けに4種類の画像バリエーションを定義します。
<!DOCTYPE html><html>
<head>
<title>Responsive Logo</title>
</head>
<body>
<h2>Logo selection based on pixel density</h2>
<img src="/img400/mylogo.png"
srcset="/img400/mylogo.png 1x, /img800/mylogo.png 2x, /img1200/mylogo.png 3x, /img1600/mylogo.png 4x">
</body>
</html>実際には、/img400、/img800、/img1200、/img1600というディレクトリは存在しません。代わりに、以下のNGINX Plus設定では、先頭に/imgが付いたアセットへのリクエストを照合し、元のファイル名の画像(たとえば、前述のHTML内のmylogo.png)をリサイズするリクエストに変換します。
server { listen 80;
root /var/www/public_html;
location ~ ^/img([0-9]+)(?:/(.*))?$ {
alias /var/www/source_images/$2;
image_filter_buffer 10M;
image_filter resize $1 -;
}
}The serverブロックでは、NGINX Plusが受信HTTPリクエストを処理する方法を定義します。listenディレクティブは、HTTPトラフィックのデフォルトであるポート80でリッスンするようNGINX Plusに指示します。rootディレクティブは、このWebサイトのディスク上の場所を指定します。この簡単な例では、NGINX Plusでホストされる静的Webサイトを使用していますが、NGINX Plusを動的コンテンツのリバースプロキシやFastCGIなどのアプリケーションコネクターとして使用することも一般的です。いずれのユースケースでも、ソース画像をNGINX Plusサーバーに配置することで、ここで説明するImage‑Filterモジュールを活用できます。
The locationブロックでは、正規表現を使用して、/imgで始まり、その後に1桁以上の数字が続く任意のディレクトリに保存されたアセットへのリクエストを照合します。数字は変数$1として、その後に続くファイル名は変数$2としてキャプチャされます。次に、aliasディレクティブを使用して、ソース画像が格納されているディスク上のディレクトリからこのリクエストに応答します。このディレクトリはrootパス配下にないため、クライアントはソース画像を直接リクエストできません。
ソース画像は幅が数千ピクセルに及ぶなど、非常に大きい可能性があるため、Image‑Filterモジュールが画像の読み込みとサイズ変更に十分なメモリを割り当てるようにする必要があります。この例では、image_filter_bufferディレクティブを使用して、最大10 MBの画像ファイルを処理できるようにします。
最後に、image_filterディレクティブは、Image‑Filterモジュールに対し、ソース画像のサイズを/imgディレクトリ名のサフィックスから取得した幅に変更するよう指示します。ダッシュ(‑)は、ソース画像のアスペクト比を維持するようNGINX Plusに指示します。
本番環境での使用に関する考慮事項
「画像サイズとピクセル密度の対応」で説明した構成では、画像のリクエストに使用するディレクトリ名に応じて、任意のサイズの画像を配信できます。ただし、本番環境では、リクエストのたびにWebサーバーが画像のサイズを変更するのを待つことは避ける必要があります。全体的なレイテンシーに悪影響を及ぼすだけでなく、CPUのオーバーヘッドも大幅に増加する可能性があるためです。
最も効果的な解決策は、サイズ変更した画像の各バリエーションをキャッシュし、以降のリクエストにはImage‑Filterモジュールを経由せず、キャッシュから配信することです。これは、画像のサイズ変更を行う別の仮想サーバーをNGINX Plusの設定で定義し、リクエストされたサイズの画像がキャッシュにない場合にのみ、そのサーバーへリクエストをプロキシすることで実現できます。このサーバーを「レスポンシブ画像サーバー」と呼びます。
図1. フロントエンドWebサーバーでキャッシュを有効にしたレスポンシブ画像サーバーの本番環境構成
任意のリクエストによる画像のサイズ変更処理を許可する場合は、セキュリティへの影響も考慮する必要があります。攻撃者が、次のような一意の画像アセットのバリエーションに対して短時間に連続してリクエストを送信すると、キャッシュを設定していても効果はありません。/img1001/mylogo.png、/img1002/mylogo.png、/img1003/mylogo.pngなどです。リクエスト数が比較的少なくても、CPU使用率が過度に上昇し、サービス拒否(DoS)を引き起こす可能性があります。この問題に対処するため、キャッシュされたバリエーションを保持するフロントエンドサーバーには適用せず、レスポンシブ画像サーバーにレート制限を適用します。次の設定では、「ピクセル密度に合わせた画像サイズの調整」の設定を拡張し、Image‑Filterモジュールにキャッシュとレート制限を適用します。
proxy_cache_path /var/www/imgcache levels=1 keys_zone=resized:1m max_size=256m;
server {
listen 80;
root /var/www/public_html;
location ~ ^/img([0-9]+)(?:/(.*))?$ {
proxy_pass http://127.0.0.1:9001;
proxy_cache resized;
proxy_cache_valid 180m;
}
}
limit_req_zone "1" zone=2persec:32k rate=2r/s;
server {
listen 9001;
allow 127.0.0.1;
deny all;
limit_req zone=2persec burst=10;
location ~ ^/img([0-9]+)(?:/(.*))?$ {
alias /var/www/source_images/$2;
image_filter_buffer 10M;
image_filter resize $1 -;
}
}まず、キャッシュされた画像の保存場所をproxy_cache_pathディレクティブで定義します。keys_zoneパラメーターは、キャッシュインデックス用の共有メモリゾーン(名前はresized)を定義し、1 MBを割り当てます。これは、サイズ変更された画像約8,000点分に相当します。max_sizeパラメーターは、新しい項目をキャッシュする領域を確保するために、NGINX Plusがリクエスト日時の最も古い画像をキャッシュから削除し始めるしきい値を定義します。
フロントエンドWebサーバー(ポート80でリッスンするサーバー)のlocationディレクティブは、proxy_passディレクティブを使用して、先頭に/imgが付くリクエストを内部でホストされているレスポンシブ画像サーバー(127.0.0.1:9001)に送信します。proxy_cacheディレクティブは、レスポンシブ画像サーバーからのレスポンスの保存に使用するキャッシュ名(resized)を指定することで、このlocationのキャッシュを有効にします。proxy_cache_validディレクティブにより、サイズ変更された画像は少なくとも180分間キャッシュに保持され(キャッシュがmax_sizeを超え、かつその画像が最近リクエストされていないものの1つである場合を除く)、レスポンシブ画像サーバーからのエラーレスポンスはキャッシュされません。
キャッシュの詳細については、「A Guide to Caching with NGINX and NGINX Plus」を参照してください。
レスポンシブ画像サーバー自体を定義する前に、limit_req_zoneディレクティブでレート制限を指定します。このディレクティブ自体はレート制限を適用するものではありません。1秒あたり2リクエストというレート制限を定義し、レスポンシブ画像サーバーのlimit_reqディレクティブをserverブロックに含めることで適用します(次の段落を参照)。通常、レート制限のキーにはリクエストの属性を使用しますが、ここではすべてのリクエスト元に制限が適用されるよう、静的なキー値"1"を指定します。このキーのカーディナリティは常に1であるため、共有メモリゾーンのサイズは設定可能な最小値である3 KBに設定します。
レスポンシブ画像サーバーのserverブロックはポート9001で待ち受けます。allowおよびdenyディレクティブを指定し、localhost(フロントエンドWebサーバー)のみがレスポンシブ画像サーバーに接続できるようにします。次に、limit_reqディレクティブを指定して、先ほど定義したレート制限を適用します。burstパラメーターにより、レート制限が適用されるまで10件の同時リクエストが許可されます。レート制限が有効になると、上限を超えたリクエストは、制限内で処理できるようになるまで遅延します。
The locationブロックは「画像サイズとピクセル密度の一致」のブロックと同じですが、要求された画像がキャッシュに存在せず、かつレート制限を超えていない場合にのみ処理されるようになりました。
この基本構成では、1つのNGINX PlusインスタンスがフロントエンドWebサーバーとレスポンシブ画像サーバーの両方として機能します。画像処理は計算負荷が非常に高くなる場合があり、ワークロードが極端に増大して、NGINX PlusがDoS攻撃の影響を受ける可能性があります。すべてのワーカープロセスが画像のサイズ変更リクエストの処理でビジー状態になり、フロントエンドWebサーバーが新しいリクエストをすぐに受け付けられなくなる事態を避けるため、画像処理専用のNGINX Plusインスタンスを別途実行することを推奨します。これにより、フロントエンドWebサーバーのワーカープロセスを、画像処理を行うワーカープロセスから分離できます。同じホスト上で別のNGINX Plusインスタンスを実行するには、コマンドラインで別の設定ファイルを指定します。
$ sudo nginx -c /etc/nginx/resize-server.confレスポンシブ画像の実践
レスポンシブ画像の動作を確認する最も効果的な方法は、ビューポートのサイズが変化する際に、ブラウザーがどのsrcset画像バリアントを使用するか判断する様子を観察することです。以下は、シンプルな画像ギャラリーのHTMLソースです。デモ用に、サイズのバリアントは画像ごとにわずかに異なっており、ブラウザーが別のバリアントを選択できる「ブレークポイント」が多数設定されています。
<!DOCTYPE html>
<html>
<head>
<title>Responsive Image Gallery</title>
</head>
<body>
<h2>Responsive Image Gallery</h2>
<img src="/img100/1-dominos.jpg" sizes="(min-width: 20em) 40vw, 100vw"
srcset="/img110/1-dominos.jpg 110w, /img210/1-dominos.jpg 210w, /img310/1-dominos.jpg 310w, /img410/1-dominos.jpg 410w, /img510/1-dominos.jpg 510w, /img610/1-dominos.jpg 610w">
<img src="/img100/2-sign.jpg" sizes="(min-width: 20em) 40vw, 100vw"
srcset="/img120/2-sign.jpg 120w, /img220/2-sign.jpg 220w, /img330/2-sign.jpg 330w, /img420/2-sign.jpg 420w, /img520/2-sign.jpg 520w, /img620/2-sign.jpg 620w">
<img src="/img100/3-thruppence.jpg" sizes="(min-width: 20em) 40vw, 100vw"
srcset="/img130/3-thruppence.jpg 130w, /img230/3-thruppence.jpg 230w, /img330/3-thruppence.jpg 330w, /img440/3-thruppence.jpg 440w, /img550/3-thruppence.jpg 550w, /img660/3-thruppence.jpg 660w">
<img src="/img100/4-aces.jpg" sizes="(min-width: 20em) 40vw, 100vw"
srcset="/img140/4-aces.jpg 140w, /img240/4-aces.jpg 240w, /img340/4-aces.jpg 340w, /img440/4-aces.jpg 440w, /img540/4-aces.jpg 540w, /img640/4-aces.jpg 640w">
</body>
</html>以下のスクリーンショットは、ChromeブラウザでこのWebページを表示し、インスペクターのネットワークタブを開いた状態を示しています。名前列には、サーバーにリクエストされた各画像バリエーションのパスが表示されるため、Webサーバーのログを確認しなくても、選択されたサイズを把握できます。
図2のようにビューポートの幅が狭い場合、ブラウザーは幅220~310ピクセルの画像を選択しています([/img]ディレクトリ名の数値サフィックスは、[Name]列でこの範囲の値になっています)。
図2. ビューポートが狭い場合のレスポンシブ画像ギャラリー
図3のようにブラウザーウィンドウの幅を広げると、ブラウザーは幅440~540ピクセルの画像(リストの最後の4つ)を選択します。これらの画像のInitiator列の値はOtherです。
図3. ビューポートの幅を広げた場合のレスポンシブ画像ギャラリー
まとめ
NGINX PlusとImage‑Filterモジュールを使用すれば、現在のブラウザー環境に最適なサイズの画像を配信できます。しかも、制作前に画像のサイズを変更したり、バッチ処理を行ったり、ディスク上で数百種類もの画像アセットを管理したりする必要はありません。これも、NGINX Plusがシームレスなアプリケーションデリバリーの実現を支援する方法の1つです。
NGINX Plusでレスポンシブ画像をぜひお試しください。今すぐ30日間の無料トライアルを開始するか、ユースケースについてご相談いただくためにお問い合わせください。
著者について

関連するブログ記事
チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する
OpenTelemetryを使用してNGINXサーバーの状態と健全性をより明確に把握することで、問題のトラブルシューティングを行い、Webアプリケーションのパフォーマンスを向上できます。
AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正
Akita SoftwareのAPIオブザーバビリティ向け新しいNGINX Plus Certified Moduleを活用して、APIエンドポイントの問題をより迅速に特定し、修正できます。
JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法
Red HatのシニアプリンシパルデベロッパーアドボケイトであるDaniel Oh氏が、Javaプロジェクトをゼロから構築するためのひな形を作成し、Kubernetesネイティブアプリケーションとしてビルドする方法を解説します。
F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング
NGINXaaS for Azureは、NGINX PlusをAzureネイティブのSaaSとして利用できるサービスです。NGINXの設定を容易にリフト&シフトできるため、クラウド導入とモダンアプリの展開を加速できます。

SELinuxでNGINXおよびNGINX Plusを使用する
Red Hat Enterprise Linux(RHEL)および関連ディストリビューションでSecurity-Enhanced Linux(SELinux)を有効にすると、デフォルト設定によりNGINXとNGINX Plusの一部の操作が制限されます。この記事では、すべての機能を利用できるようにSELinuxの設定を変更する方法について説明します。
NGINX PlusによるAWS Auto Scalingグループの負荷分散
新しいnginx-asg-sync統合ソフトウェアにより、NGINX PlusはAWS Auto Scalingグループの負荷を直接分散できるため、クラウドでの負荷分散がさらに柔軟になります