Image-Filter 모듈로 NGINX 반응형 이미지 제공

NGINX Plus의 Image-Filter 모듈과 srcset 태그를 사용하여 실시간으로 이미지 크기를 조정하세요.

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

따라서 반응형 이미지를 사용하면 웹 브라우저가 디자이너의 의도와 거의 일치하는 렌더링을 생성할 수 있습니다. 이는 사용자 경험을 향상시키지만 개발 및 운영팀에게는 기본 이미지뿐만 아니라 수많은 이미지 리소스를 생성하고 배포해야 하는 추가적인 부담을 안겨줍니다.

이 포스트에서는 수많은 이미지 자료의 변형을 생성하고 관리해야 하는 번거로움 없이 NGINX 및 NGINX Plus용 Image-Filter 모듈을 사용하여 반응형 이미지를 제공하는 방법을 보여드리며, 대신 각 이미지의 단일 “Source” 버전을 배포하여 NGINX 또는 NGINX Plus가 실시간으로 크기를 조정할 수 있도록 합니다.

이 포스트의 정보는 Image-Filter 모듈 설치에 대한 별도의 지침을 제외하고는 NGINX Open Source와 NGINX Plus 모두에 적용되며, 간결성을 위해 전체적으로 NGINX Plus를 기준으로 합니다.

목차

1. srcset 속성
2. Image-Filter 모듈 설치
2-1. NGINX Plus용 Image-Filter 모듈 설치

2-2. NGINX Open Source용 Image-Filter 모듈 설치
3. 이미지 크기와 픽셀 밀도 일치시키기
4. Production 사용 시 고려 사항
5. 실제 반응형 이미지
6. 결론

1. srcset 속성

반응형 이미지를 전달하기 위한 주요 도구는 HTML5 이미지 태그의 srcset 속성입니다. 이를 사용하여 다양한 픽셀 밀도와 Viewport 크기에 맞는 여러 이미지 리소스를 지정할 수 있습니다. Viewport는 데스크톱의 창이든 모바일 디바이스의 전체 화면 애플리케이션이든 웹 브라우저에서 사용할 수 있는 디스플레이 공간을 통칭하는 용어입니다.

다음 예제에서 src 속성은 srcset 속성을 지원하지 않는 브라우저의 기본 이미지를 정의하고, srcset 속성은 표준 픽셀 밀도(1x)의 디스플레이용 이미지와 Apple Retina™ 디스플레이 및 일부 4K 모니터(2x)와 같은 이중 픽셀 밀도 디스플레이용 이미지의 두 가지 변형을 지정합니다.

<img src="/images/mylogo-default.png"
  srcset="/images/mylogo-density1.png 1x, /images/mylogo-density2.png 2x">

다음은 Viewport 너비에 따라 표시할 여러 이미지 리소스 변형을 정의하는 보다 정교한 예제입니다. 크기 속성은 Viewport가 10m보다 넓은 경우 브라우저가 Viewport의 절반에 이미지를 렌더링하고 그렇지 않은 경우 전체 Viewport를 사용하도록 지정합니다. 브라우저는 이미지에 사용할 수 있는 공간을 결정하고 사용 가능한 공간에 가장 적합한 이미지 리소스 유형을 선택하며, 일반적으로 다음 너비(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 속성 및 기타 기법에 대한 자세한 내용은 이 블로그 포스트를 참조하세요.

2. Image-Filter 모듈 설치

해당 Image-Filter 모듈을 얻는 절차는 NGINX Plus와 NGINX에 따라 다릅니다.

2-1. NGINX Plus용 Image-Filter 모듈 설치

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

1. 모듈 자체는 NGINX Plus Repository에서 설치하여 얻습니다.

Ubuntu 및 Debian 시스템의 경우:

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

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

$ sudo yum install nginx-plus-module-image-filter

2. nginx.conf 구성 파일의 최상위(“main”) 컨텍스트(즉, http 또는 stream 컨텍스트가 아닌)에 모듈에 대한 load_module 지시문을 포함하여 모듈을 사용하도록 설정합니다.

load_module modules/ngx_http_image_filter_module.so;

3. 실행 중인 인스턴스에 Image-Filter 모듈을 로드하려면 NGINX Plus를 Reload합니다.

$ sudo nginx -s reload>

2-2. NGINX Open Source용 Image-Filter 모듈 설치

Image‑Filter 모듈을 설치하는 가장 쉬운 방법은 공식 NGINX Repository에서 다운로드하는 것입니다.

Ubuntu 및 Debian 시스템의 경우:

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

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

$ sudo yum install nginx-module-image-filter

설치가 완료되면 NGINX Plus용 Image-Filter 모듈 설치의 2단계와 3단계에 따라 NGINX를 구성하고 Reload합니다.

Source에서 Image-Filter 모듈을 컴파일하여 정적으로 컴파일된 모듈 또는 동적 모듈로 로드할 수도 있습니다.

3. 이미지 크기와 픽셀 밀도 일치시키기

Image-Filter 모듈을 사용하면 각 이미지의 단일 “Source” 버전을 생성 및 배포할 수 있으며, 브라우저에서 요청하는 모든 크기 변형을 제공하기 위해 NGINX Plus가 실시간으로 크기를 조정하도록 할 수 있습니다. 이미지를 수동으로 크기를 조정하고 웹 서버에 배포할 필요 없이 HTML Source 내에서 반응형 웹 페이지와 이미지를 세밀하게 조정할 수 있습니다.

다음 샘플 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 -;
    }
}

server 블록은 NGINX Plus가 들어오는 HTTP 요청을 처리하는 방법을 정의합니다. listen 지시문은 HTTP 트래픽의 기본값인 포트 80에서 수신 대기하도록 NGINX Plus에 지시합니다. root 지시문은 이 웹사이트의 디스크 위치를 지정합니다. 이 간단한 예제에서는 NGINX Plus에서 호스팅하는 정적 웹사이트를 사용하고 있지만, NGINX Plus가 동적 콘텐츠 또는 FastCGI와 같은 애플리케이션 커넥터에 대한 Reverse Proxy로 작동하는 것도 일반적입니다. 이러한 모든 사용 사례는 여기에 설명된 대로 Source 이미지를 NGINX Plus 서버에 배포하여 Image-Filter 모듈을 활용할 수 있습니다.

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

Source 이미지의 너비가 수천 픽셀로 매우 클 수 있으므로 Image-Filter 모듈이 이미지를 로드하고 크기를 조정할 수 있는 충분한 메모리를 할당하는지 확인해야 합니다. 이 예제에서는 image-filter-buffer 지시문을 사용하여 최대 10MB 크기의 이미지 파일을 지원합니다.

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

4. Production 사용 시 고려 사항

이미지 크기와 픽셀 밀도 일치에 설명된 구성은 이미지 요청에 사용된 디렉터리 이름에 따라 이미지의 모든 크기 변형을 제공할 수 있습니다. 그러나 Production 환경에서는 웹 서버가 각 요청에 대해 이미지 크기를 조정할 때까지 기다리는 것을 원하지 않습니다. 이는 전반적인 지연 시간에 좋지 않으며 CPU 오버헤드도 크게 증가시킬 수 있습니다.

가장 효과적인 해결책은 크기가 조정된 이미지 변형을 캐시에 저장하여 각 변형에 대한 후속 요청이 Image-Filter 모듈을 거치지 않고 캐시에서 제공되도록 하는 것입니다. 이미지 크기 조정을 수행하는 별도의 가상 서버를 정의하고 요청된 이미지 크기가 아직 캐시에 없는 경우에만 해당 서버로 요청을 Proxy하는 NGINX Plus 구성을 통해 이를 달성할 수 있습니다. 이를 반응형 이미지 서버라고 부릅니다.

A production configuration of NGINX Plus with 'location' blocks for a frontend web server and a responsive image server. For a more responsive web design, the web server caches size variants created by the image server using the Image-Filter module.

그림 1. Frontend 웹 서버에서 캐싱이 활성화된 반응형 이미지 서버의 Production 구성

또한 이미지 크기 조정 작업을 수행하기 위해 임의의 요청을 허용하는 것이 보안에 미치는 영향도 고려해야 합니다. 캐싱을 설정해도 공격자가 /img1001/mylogo.png, /img1002/mylogo.png, /img1003/mylogo.png 등과 같은 고유한 이미지 리소스 변형을 빠르게 요청할 경우 도움이 되지 않습니다. 이러한 공격은 요청량이 상대적으로 적더라도 과도한 CPU 사용률로 인해 서비스 거부(DoS)를 유발할 수 있습니다. 이 문제를 해결하기 위해 반응형 이미지 서버에는 속도 제한을 적용하지만 캐시된 변형을 포함하는 Frontend 서버에는 적용하지 않습니다. 다음 구성은 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 매개변수는 캐시 인덱스의 공유 메모리 Zone을 정의하고 크기가 조정된 이미지 약 8,000개에 충분한 1MB를 할당합니다. max_size 매개변수는 새로운 캐시 항목을 위한 공간을 확보하기 위해 NGINX Plus가 캐시에서 가장 최근에 요청된 이미지를 제거하기 시작하는 지점을 정의합니다.

Frontend 웹 서버(포트 80에서 수신 대기 중인 서버)의 location 지시문은 proxy_pass 지시문을 사용하여 내부적으로 호스팅되는 반응형 이미지 서버(127.0.0.1:9001)로 /img가 접두사로 붙은 요청을 보냅니다. proxy_cache 지시문은 반응형 이미지 서버의 응답을 저장하는 데 사용할 캐시의 이름을 지정하여 이 위치에 대한 캐싱을 활성화합니다. proxy_cache_valid 지시문은 크기가 조정된 이미지가 캐시에 최소 180분 동안 유지되도록 하며(캐시가 max_size를 초과하지 않고 가장 최근에 요청된 이미지가 아닌 경우), 응답 이미지 서버의 잘못된 응답이 캐시되지 않도록 합니다.

반응형 이미지 서버 자체를 정의하기 전에 limit_req_zone 지시문을 사용하여 속도 제한(Rate Limit)을 지정합니다. 이 지시문은 그 자체로 속도 제한(Rate Limit)을 적용하는 것이 아니라 초당 두 개의 요청으로 속도 제한(Rate Limit)을 정의한 다음 server 블록에 limit_req 지시문을 포함시켜 반응형 이미지 서버에 적용합니다(다음 단락 참조). 일반적으로 속도 제한(Rate Limit)은 요청의 속성에 대해 Key가 지정되지만, 이 경우에는 모든 요청자에게 제한이 적용되도록 정적 Key 값 “1”을 지정합니다. Key의 고정 Cardinality가 1이기 때문에 공유 메모리 Zone의 크기를 가능한 가장 작은 값인 3KB로 설정했습니다.

반응형 이미지 서버용 server 블록은 포트 9001에서 수신 대기합니다. allow 및 deny 지시문을 포함시켜 localhost(Frontend 웹 서버)만 반응형 이미지 서버에 연결할 수 있도록 지정합니다. 그런 다음 limit_req 지시문을 포함하여 이전에 정의한 전송률 제한을 적용하고, burst 매개변수는 전송률 제한을 적용하기 전에 10개의 동시 요청을 허용합니다. 전송률 제한이 적용되면 초과 요청은 제한 내에서 처리될 때까지 지연됩니다.

location 블록은 이미지 크기와 픽셀 밀도 일치의 블록과 동일하지만 이제 요청된 이미지가 캐시에 없고 전송률 제한을 초과하지 않은 경우에만 실행됩니다.

이 기본 구성에서는 단일 NGINX Plus 인스턴스가 Frontend 웹 서버와 반응형 이미지 서버의 역할을 모두 수행합니다. 이미지 처리는 매우 계산 집약적일 수 있으므로 잠재적으로 Workload가 매우 높아질 수 있으며 NGINX Plus가 DoS 공격의 대상이 될 수 있습니다. 모든 Worker Process가 이미지 크기 조정 요청으로 바빠서 Frontend 웹 서버가 새 요청을 즉시 수락할 수 없는 상황을 방지하려면 이미지 처리 전용의 별도의 NGINX Plus 인스턴스를 실행하는 것이 좋습니다. 이렇게 하면 Frontend 웹 서버의 Worker Process와 이미지 처리를 수행하는 프로세스가 분리됩니다. 동일한 호스트에서 별도의 NGINX Plus 인스턴스를 실행하려면 command line에서 다른 구성 파일을 지정하세요.

$ sudo nginx -c /etc/nginx/resize-server.conf

5. 실제 반응형 이미지

반응형 이미지가 실제로 작동하는 것을 확인하는 가장 효과적인 방법은 브라우저가 Viewport의 크기가 변경될 때 어떤 srcset 이미지 변형을 사용할지 결정하는 것을 관찰하는 것입니다. 다음은 간단한 이미지 갤러리의 HTML Source입니다. 데모 목적으로 각 이미지마다 크기가 조금씩 다르기 때문에 브라우저가 다른 변형을 선택할 수 있는 ‘Breakpoint’이 많이 생깁니다.

<!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>

아래 스크린샷은 InspectorNetwork 탭으로 열어둔 상태에서 Chrome 브라우저에서 이 웹 페이지의 내용을 보여줍니다. Name 열에는 서버에서 요청된 각 이미지 변형의 경로가 표시되므로 웹 서버의 로그를 확인하지 않고도 선택한 크기를 확인할 수 있습니다.

그림 2의 Viewport에서 브라우저는 220~310픽셀 너비의 이미지를 선택했습니다(이름 열의 /img 디렉터리 Name에 있는 숫자 접미사는 이 값 사이의 범위입니다).

Image illustrating responsive web design: when the viewport is narrow, a web browser chooses smaller size variants of each image, as generated by a responsive image server with the NGINX Plus Image-Filter module installed

그림 2. Viewport가 좁은 경우의 반응형 이미지 갤러리

그림 3에서 브라우저 창을 넓히면 브라우저에서 440~540픽셀 너비의 이미지가 선택됩니다. Initiator 열에서 이러한 이미지의 값은 기타입니다.

Image illustrating responsive web design: when the viewport is wider, a web browser chooses larger size variants of each image, as generated by a responsive image server with the NGINX Plus Image-Filter module installed

그림 3. Viewport가 넓은 경우의 반응형 이미지 갤러리

6. NGINX Image-Filter 모듈 결론

NGINX Plus와 Image-Filter 모듈을 사용하면 현재 브라우저 조건에 맞는 최적의 이미지 크기를 제공할 수 있습니다. 또한 사전 Production 이미지 크기 조정, 일괄 처리 또는 디스크에서 수백 개의 이미지 리소스 변형을 관리할 필요 없이 이 작업을 수행할 수 있습니다. NGINX Plus가 완벽한 애플리케이션 전송을 지원하는 또 다른 방법입니다.

NGINX Plus로 반응형 이미지를 사용하거나 테스트해 보려면 지금 30일 무료 평가판을 신청하거나 사용 사례에 대해 최신 소식을 빠르게 전달받고 싶으시면 아래 뉴스레터를 구독하세요.