NGINX 및 NGINX Plus로 번거로움 없이 반응형 이미지 제공

NGINX | 2016년 9월 29일

사진: Phil Roeder

Image‑Filter 모듈과 srcset 태그를 사용하여 이미지를 즉석에서 크기 조정하기

반응형 웹 디자인은 최신 웹사이트와 웹 애플리케이션의 표준으로 자리 잡았으며, 다양한 기기에서 일관된 경험을 제공하는 동시에 각 기기에 맞게 화면 표시를 최적화합니다. 하지만 최신 기기는 화면 크기뿐만 아니라 픽셀 밀도도 서로 다릅니다. HTML5 img 태그는 서버에서 여러 변형 이미지를 제공할 경우 브라우저가 가장 적합한 리소스를 선택할 수 있도록 다양한 기능을 제공합니다. 웹사이트에서 동일한 이미지를 여러 크기로 제공하면 웹 브라우저가 현재 환경에 가장 적합한 크기를 선택할 수 있습니다.

따라서 반응형 이미지를 사용하면 웹 브라우저가 디자이너의 의도에 더욱 부합하는 화면을 렌더링할 수 있습니다. 이는 사용자 경험을 개선하지만, 기본 이미지뿐만 아니라 다양한 이미지 애셋 변형을 생성하고 배포해야 하는 개발 및 운영 팀에는 추가적인 부담이 됩니다.

이 블로그 게시물에서는 Image‑Filter 모듈을 NGINX 및 NGINX Plus와 함께 사용하여 수많은 이미지 애셋 변형을 만들고 관리하는 번거로움 없이 반응형 이미지를 제공하는 방법을 설명합니다. 각 이미지의 단일 ‘원본’ 버전만 배포하면 NGINX 또는 NGINX Plus가 이를 즉석에서 크기 조정할 수 있습니다. 이 게시물의 내용은 Image‑Filter 모듈 설치에 관한 별도 지침을 제외하고 NGINX Open Source와 NGINX Plus 모두에 적용됩니다. 간결한 설명을 위해 이 글에서는 모두 NGINX Plus로 지칭합니다.

The srcset 속성

반응형 이미지를 제공하는 주요 도구는 HTML5 srcset 속성이 있는 img 태그입니다. 이 속성을 사용하면 다양한 픽셀 밀도와 뷰포트 크기에 맞는 여러 이미지 애셋 변형을 지정할 수 있습니다. 뷰포트는 데스크톱의 창이든 모바일 기기의 전체 화면 앱이든 웹 브라우저에서 사용할 수 있는 표시 공간을 통칭하는 용어입니다.

In the following example, the src attribute defines a default image for browsers that do not support the srcset attribute and the srcset attribute names two variants – one for displays with standard pixel density (1x), and a second for displays with double pixel density, such as Apple Retina™ displays and some 4K monitors (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 속성과 기타 기법에 대한 자세한 내용은 이 훌륭한 블로그 게시물을 참조하세요.

이미지 필터 모듈 설치

해당 이미지 필터 모듈을 설치하는 절차는 NGINX Plus와 NGINX에서 서로 다릅니다.

NGINX Plus용 Image‑Filter 모듈 설치

Image‑Filter 모듈은 NGINX Plus 구독자에게 무료 동적 모듈로 제공됩니다.

  1. NGINX Plus 리포지토리에서 모듈을 설치하여 모듈 자체를 확보합니다. Ubuntu 및 Debian 시스템의 경우:
$ sudo apt-get install nginx-plus-module-image-filter
  1. RedHat, CentOS 및 Oracle Linux 시스템의 경우:
$ sudo yum install nginx-plus-module-image-filter
  1. 모듈을 활성화하려면 load_module 지시어를 nginx.conf 구성 파일의 최상위(“main”) 컨텍스트에 포함합니다(즉, http 또는 stream 컨텍스트가 아닌 위치).
load_module modules/ngx_http_image_filter_module.so;
  1. 실행 중인 인스턴스에 Image‑Filter 모듈을 로드하려면 NGINX Plus를 다시 로드합니다.
$ sudo nginx -s reload>

NGINX Open Source용 이미지 필터 모듈 설치

Image‑Filter 모듈을 설치하는 가장 쉬운 방법은 공식 NGINX 저장소에서 가져오는 것입니다. 이 지침에 따라 시스템에서 공식 NGINX 저장소에 액세스할 수 있도록 구성한 다음, 운영 체제의 패키지 관리자를 사용하여 설치합니다.

Ubuntu 및 Debian 시스템의 경우:

$ sudo apt-get install nginx-module-image-filter

Red Hat, CentOS 및 Oracle Linux 시스템의 경우:

$ sudo yum install nginx-module-image-filter

설치가 완료되면 NGINX Plus용 이미지 필터 모듈 설치의 2단계와 3단계에 따라 NGINX를 구성하고 다시 로드합니다.

Image‑Filter 모듈을 소스에서 컴파일하여 정적으로 컴파일된 모듈 또는 동적 모듈로 로드할 수도 있습니다. 자세한 내용은 NGINX Plus 관리자 가이드를 참조하세요.

픽셀 밀도에 맞게 이미지 크기 조정

Image‑Filter 모듈을 사용하면 각 이미지의 단일 ‘원본’ 버전만 생성하고 배포한 뒤, NGINX Plus가 브라우저에서 요청한 크기에 맞춰 실시간으로 이미지 크기를 조정해 다양한 크기의 이미지를 제공하도록 할 수 있습니다. 이미지를 수동으로 크기 조정하여 웹 서버에 배포하지 않고도 HTML 소스 내에서 반응형 웹 페이지와 이미지를 세밀하게 조정할 수 있습니다.

이 샘플 HTML 파일에서는 픽셀 밀도가 서로 다른 기기를 위해 네 가지 이미지 변형을 정의합니다.

<!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 지시문은 NGINX Plus가 HTTP 트래픽의 기본 포트인 80번 포트에서 수신 대기하도록 지정합니다. root 지시문은 이 웹사이트가 디스크에서 위치하는 경로를 지정합니다. 이 간단한 예에서는 NGINX Plus에서 호스팅하는 정적 웹사이트를 사용하지만, NGINX Plus를 동적 콘텐츠의 리버스 프록시나 FastCGI와 같은 애플리케이션 커넥터로 사용하는 경우도 많습니다. 이러한 모든 사용 사례에서는 원본 이미지를 NGINX Plus 서버에 배포하여 여기에서 설명한 대로 Image‑Filter 모듈을 활용할 수 있습니다.

The location 블록은 정규식을 사용하여 /img로 시작하고 그 뒤에 하나 이상의 숫자가 오는 디렉터리에 저장된 자산에 대한 요청을 일치시킵니다. 숫자는 변수 $1에 캡처되고, 그 뒤에 오는 파일 이름은 변수 $2에 캡처됩니다. 그런 다음 alias 지시문을 사용하여 원본 이미지가 들어 있는 디스크상의 디렉터리에서 이 요청을 처리합니다. 이 디렉터리는 root 경로 아래에 있지 않으므로 클라이언트가 원본 이미지를 직접 요청할 수 없습니다.

원본 이미지는 너비가 수천 픽셀에 이를 정도로 매우 클 수 있으므로, Image‑Filter 모듈이 이미지를 불러오고 크기를 조정하는 데 충분한 메모리를 할당하도록 해야 합니다. 이 예에서는 image_filter_buffer 지시어를 사용하여 최대 10 MB 크기의 이미지 파일을 지원합니다.

마지막으로 image_filter 지시문은 Image‑Filter 모듈에 원본 이미지의 크기를 /img 디렉터리 이름의 접미사에서 캡처한 너비에 맞게 조정하도록 지시합니다. 대시(‑)는 NGINX Plus가 원본 이미지의 가로세로 비율을 유지하도록 합니다.

프로덕션 환경에서 사용할 때의 고려 사항

The configuration described in 이미지 크기를 픽셀 밀도에 맞추기에서 설명한 구성은 이미지를 요청할 때 사용한 디렉터리 이름에 따라 다양한 크기의 이미지를 제공할 수 있습니다. 하지만 프로덕션 환경에서는 요청이 들어올 때마다 웹 서버가 이미지 크기를 조정할 때까지 기다리는 것은 바람직하지 않습니다. 이는 전반적인 지연 시간을 늘릴 뿐만 아니라 CPU 오버헤드도 크게 증가시킬 수 있습니다.

가장 효과적인 해결 방법은 크기가 조정된 이미지 변형을 캐시하여, 이후 각 변형에 대한 요청이 Image‑Filter 모듈을 거치지 않고 캐시에서 처리되도록 하는 것입니다. NGINX Plus 구성에서 이미지 크기 조정을 수행하는 별도의 가상 서버를 정의하고, 요청된 크기의 이미지가 캐시에 아직 없는 경우에만 해당 서버로 요청을 프록시하면 이를 구현할 수 있습니다. 이를 반응형 이미지 서버라고 합니다.

그림 1. 프런트엔드 웹 서버에서 캐싱을 활성화한 반응형 이미지 서버의 프로덕션 구성

임의의 요청이 이미지 크기 조정 작업을 수행하도록 허용할 때 발생할 수 있는 보안 문제도 고려해야 합니다. 공격자가 다음과 같이 고유한 이미지 애셋 변형을 빠르게 요청한다면 캐싱을 구성해도 도움이 되지 않습니다. /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가 새 캐시 항목을 위한 공간을 확보하기 위해 캐시에서 가장 오랫동안 요청되지 않은 이미지를 제거하기 시작하는 기준을 정의합니다.

프런트엔드 웹 서버(포트 80에서 수신 대기하는 서버)의 location 지시문은 proxy_pass 지시문을 사용하여 /img 접두사가 붙은 요청을 내부에서 호스팅되는 반응형 이미지 서버(127.0.0.1:9001)로 전송합니다. proxy_cache 지시문은 반응형 이미지 서버의 응답을 저장하는 데 사용할 캐시의 이름(resized)을 지정하여 이 위치에 대한 캐싱을 활성화합니다. proxy_cache_valid 지시문은 크기가 조정된 이미지가 캐시에 최소 180분 동안 유지되도록 하고(캐시가 max_size를 초과했으며 해당 이미지가 가장 오래 요청되지 않은 항목에 속하는 경우는 제외), 반응형 이미지 서버의 오류 응답은 캐시되지 않도록 합니다.

캐싱에 대한 자세한 설명은 NGINX 및 NGINX Plus를 활용한 캐싱 가이드를 참조하세요.

반응형 이미지 서버 자체를 정의하기 전에 limit_req_zone 지시어를 사용하여 요청 속도 제한을 지정합니다. 이 지시어 자체가 요청 속도 제한을 적용하는 것은 아닙니다. 초당 요청 2건으로 제한을 정의한 다음, 반응형 이미지 서버의 limit_req 블록에 server 지시어를 포함하여 이 제한을 적용합니다(다음 단락 참조). 일반적으로 요청 속도 제한은 요청의 속성을 키로 사용하지만, 여기서는 모든 요청자에게 제한이 적용되도록 정적 키 값 "1"을 지정합니다. 키의 카디널리티가 1로 고정되어 있으므로 공유 메모리 영역의 크기를 가능한 최솟값인 3KB로 설정합니다.

반응형 이미지 서버의 server 블록은 9001번 포트에서 수신 대기합니다. allow 및 deny 지시어를 포함하여 localhost(프런트엔드 웹 서버)만 반응형 이미지 서버에 연결할 수 있도록 지정합니다. 그런 다음 limit_req 지시어를 포함하여 앞서 정의한 요청 속도 제한을 적용합니다. burst 매개변수는 요청 속도 제한을 적용하기 전에 최대 10개의 동시 요청을 허용합니다. 요청 속도 제한이 적용되면 초과 요청은 제한 범위 내에서 처리할 수 있을 때까지 지연됩니다.

The location 블록은 픽셀 밀도에 맞게 이미지 크기 조정의 블록과 동일하지만, 이제 요청된 이미지가 캐시에 없고 요청 속도 제한을 초과하지 않은 경우에만 실행됩니다.

이 기본 구성에서는 단일 NGINX Plus 인스턴스가 프런트엔드 웹 서버와 반응형 이미지 서버 역할을 모두 수행합니다. 이미지 처리는 매우 많은 연산 리소스를 사용할 수 있으므로 워크로드가 극도로 높아지고 NGINX Plus가 DoS 공격에 노출될 가능성이 있습니다. 모든 워커 프로세스가 이미지 크기 조정 요청을 처리하느라 사용 중이어서 프런트엔드 웹 서버가 새 요청을 즉시 수락하지 못하는 상황을 방지하려면 이미지 처리 전용 NGINX Plus 인스턴스를 별도로 실행하는 것이 좋습니다. 이렇게 하면 프런트엔드 웹 서버의 워커 프로세스를 이미지 처리 워커 프로세스와 분리할 수 있습니다. 동일한 호스트에서 별도의 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 브라우저에서 이 웹 페이지의 콘텐츠를 보여 주며, 검사 도구의 네트워크 탭이 열려 있습니다. 이름 열에는 서버에 요청된 각 이미지 변형의 경로가 표시되므로 웹 서버의 로그를 확인하지 않고도 선택된 크기를 알 수 있습니다.

그림 2와 같이 뷰포트가 좁을 때 브라우저는 너비가 220~310픽셀인 이미지를 선택했습니다(/img 디렉터리 이름의 숫자 접미사는 Name 열에서 이 범위의 값으로 표시됩니다).

그림 2. 뷰포트가 좁을 때의 반응형 이미지 갤러리

그림 3에서 브라우저 창을 넓히면 브라우저는 너비가 440~540픽셀인 이미지(목록의 마지막 네 개 이미지)를 선택합니다. 이러한 이미지의 Initiator 열 값은 Other입니다.

그림 3. 뷰포트가 더 넓을 때의 반응형 이미지 갤러리

결론

NGINX Plus와 Image‑Filter 모듈을 사용하면 현재 브라우저 환경에 가장 적합한 크기의 이미지를 제공할 수 있습니다. 또한 사전 제작 단계에서 이미지 크기를 조정하거나 일괄 처리할 필요가 없으며, 디스크에 수백 가지 이미지 애셋 변형을 저장하고 관리할 필요도 없습니다. 이는 NGINX Plus가 완벽한 애플리케이션 제공을 지원하는 또 하나의 방법입니다.

NGINX Plus로 반응형 이미지를 직접 사용해 보세요. 지금 30일 무료 체험을 시작하거나 사용 사례에 대해 논의하려면 문의해 주세요.


공유
태그: F5 NGINX, Tech

작성자 소개

관련 블로그 게시물

시청하기: KubeCon North America 2023에서의 NGINX Gateway Fabric
NGINX | 2023. 11. 30.

시청하기: KubeCon North America 2023에서의 NGINX Gateway Fabric

이 영상에서는 KubeCon의 NGINX 부스를 찾아가, 저희가 공개한 Kubernetes Gateway API 구현을 살펴봅니다.

F5 NGINX로 대규모 WAF 플릿을 확인하고 관리하는 2가지 방법
NGINX | 2023. 03. 23.

F5 NGINX로 대규모 WAF 플릿을 확인하고 관리하는 2가지 방법

앱 보안을 강화하는 두 가지 방법을 소개합니다. NGINX Management Suite를 통해 WAF 운영에 대한 가시성을 높이고, NGINX App Protect WAF를 활용해 코드형 보안을 구현하는 것입니다.

Ingress 컨트롤러 선택 가이드, 4부: NGINX Ingress Controller 옵션
NGINX | 2021. 09. 27.

Ingress 컨트롤러 선택 가이드, 4부: NGINX Ingress Controller 옵션

개발 주체, 개발 철학, 프로덕션 환경 준비도, 보안 및 지원을 기준으로 어떤 NGINX Ingress 컨트롤러가 가장 적합한지 알아보세요.

진화하는 공격 환경에 NGINX App Protect Denial of Service가 적응하는 방법
NGINX | 2021. 07. 06.

진화하는 공격 환경에 NGINX App Protect Denial of Service가 적응하는 방법

NGINX App Protect Denial of Service(DoS)가 계층 7 DoS 공격을 방어하기 위해 완화 방법을 실시간으로 조정하는 방식을 설명합니다. 정상적인 사용자 행동과 서버 상태에 대한 모델을 생성하여 공격을 나타내는 이상 징후를 감지하고 맞춤형 완화 시그니처를 적용할 수 있습니다.

NGINX Instance Manager를 활용한 대규모 구성 업데이트 및 인증서 관리
NGINX | 2021. 06. 30.

NGINX Instance Manager를 활용한 대규모 구성 업데이트 및 인증서 관리

NGINX Instance Manager 버전 1.0에는 두 가지 새로운 기능이 도입되었습니다. NGINX 인스턴스에 태그와 사용자 역할을 지정하면 전체 그룹에 구성을 한 번에 적용할 수 있습니다. 인증서 검사는 만료 예정인 인증서를 감지하여 서비스 중단을 방지할 수 있도록 인증서를 교체할 수 있게 해줍니다.

Netflix가 자사 CDN의 핵심으로 NGINX를 선택한 이유
NGINX | 2015. 03. 25.

Netflix가 자사 CDN의 핵심으로 NGINX를 선택한 이유

Netflix가 글로벌 콘텐츠 전송 네트워크인 Open Connect의 스트리밍 미디어 기술로 NGINX를 선택한 이유를 NGINX 개발자 Gleb Smirnoff에게서 알아보세요.