articles

Обработка изображений на лету с помощью Nginx]

назад к оглавлению

Данное решение опробовано на Linux Debian 12/11/10 У вас должен быть root доступ к серверу

По истечению времени многие сталкиваются с задачей, что необходимо на сайте обрабатывать изображения:

Привычным решением для многих становится использовать скрипты на PHP/JS/Python/etc. Но у такого способа есть недостатки: необходимо заботиться о кэше, времени жизни. А решение на PHP для каждой задачи вообще создает отдельный процесс.

В данной статье я опишу процесс: как это можно автоматизировать с помощью Nginx и возложить на него все задачи.

1. Подготовка

За обработку изображений в Nginx отвечает модуль ngx_http_image_filter_module. В последних версиях он включен по умолчанию. Если у Вас уже установлен Nginx, вы можете проверить его наличие с помощью команды:

nginx -V 2>&1 | xargs -n1 | grep 'with-http_image_filter_module' | wc -l

Если установлен, то в ответе Вы увидите 1. Если Вам не нужно поддержки “водяного знака”, можете пропускать следующий шаг

2. Сборка Nginx из исходников

Я обычно устанавливаю Nginx из исходников, так как данный вариант дает полный контроль над процессом.

2.1 Скачиваем Nginx

Скачать исходники для nginx можно на официальном сайте. Предлагаю сделать это на стороне нашего сервера.

Подключаемся по SSH к серверу и переходим в домашнюю папку

cd ~

В данной папке я создам папку sources, в ней будем хранить исходники программ

mkdir sources; cd sources

Скачиваем исходники (в примере используется стабильная версия 1.24.0)

wget https://nginx.org/download/nginx-1.24.0.tar.gz

Если у вас не установлена утилита wget, ее можно установить из репозитория

apt install wget -y

Распаковываем скачанных архив

tar -zxvf nginx-1.24.0.tar.gz

После мы должны увидеть директорию с исходниками:

$ ls -al
drwxr-xr-x 3 user user    4096 Sep 22 08:40 .
drwx------ 3 user user    4096 Sep 22 08:39 ..
drwxr-xr-x 8 user user    4096 Apr 10 21:45 nginx-1.24.0
-rw-r--r-- 1 user user 1112471 Apr 11 12:04 nginx-1.24.0.tar.gz

Переходим в папку nginx-1.24.0

cd nginx-1.24.0

2.2 Добавление модуля наложения водяного знака

Если Вас не нужно добавление водяного знака на изображение, пропустите этот шаг. Дело в том, что стандартный модуль Nginx не умеет накладывать водяной знак на изображение. Благо существует пропатченая версия этого модуля, которая добавляет такую возможность. Нам необходимо скачать из репозитория пропатченную версию файла ngx_http_image_filter_module.c и заменить им стандартный модуль в исходниках в папке src/http/modules/

2.3 Сборка

Теперь нам необходимо собрать nginx из исходников

./configure --with-http_image_filter_module=dynamic --with-http_ssl_module --with-http_v2_module --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --with-pcre --pid-path=/var/run/nginx.pid --with-http_ssl_module

Немного поясню:

Во время сборки мы можем получить ошибки:

./configure: error: C compiler cc is not found

Устанавливаем компилятор C

apt install gcc -y
./configure: error: the HTTP rewrite module requires the PCRE library.

Отсутствует библиотека для регулярных выражений (Perl Compatible Regular Expressions). Устанавливаем

apt install libpcre3 libpcre3-dev -y
./configure: error: SSL modules require the OpenSSL library.

Отсутствует библиотека ssl. Устанавливаем

apt install libssl-dev -y
./configure: error: the HTTP gzip module requires the zlib library.

Отсутствует библиотека для компрессии. Устанавливаем

apt install zlib1g zlib1g-dev -y
./configure: error: the HTTP image filter module requires the GD library.

Отсутствует библиотека обработки изображений. Устанавливаем

apt install libgd-dev -y

Если конфигурация прошла успешно, мы должны получить в консоли текст:

...
Configuration summary
  + using system PCRE library
  + using system OpenSSL library
  + using system zlib library
...

Компилируем

make

Если вы получили ошибку

make: command not found

то установите утилиту

apt install make -y

После компиляции посмотрите в логе, нет ли ошибок. Устанавливаем

make install

Поздравляю! Вы установили Nginx из исходников. Проверим версию Nginx

/usr/sbin/nginx -V

nginx version: nginx/1.24.0
built by gcc 12.2.0 (Debian 12.2.0-14)
built with OpenSSL 3.0.9 30 May 2023
TLS SNI support enabled
configure arguments: --with-http_image_filter_module=dynamic --with-http_ssl_module --with-http_v2_module --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --with-pcre --pid-path=/var/run/nginx.pid --with-http_ssl_module

Запускаем Nginx

/usr/sbin/nginx 

Проверяем, запущен ли процесс:

root       14413  0.0  0.0  43348  3404 ?        Ss   09:00   0:00 nginx: master process /usr/sbin/nginx
nobody     14414  0.0  0.1  44284  7232 ?        S    09:00   0:00 nginx: worker process
user        14471  0.0  0.0   6060  1856 pts/0    S+   09:00   0:00 grep nginx

Проверим его работу

curl http://localhost

Если Вы получили ошибку

bash: curl: command not found

то установите утилиту cURL:

apt install curl -y

и повторите проверку.

В результате мы получим:

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Теперь нам надо “убить” процессы Nginx

kill -9 $(sudo lsof -t -i:80)

2.4 Создаем системный сервис

Сервис позволит не только запускать и останавливать nginx, но так же стартовать nginx при загрузке/перезагрузке системы.

Создаем/редактируем файл в любом тексотовом редакторе на сервере (я использую nano)

nano /lib/systemd/system/nginx.service

И пишем конфигурацию сервиса

[Unit]
Description=The NGINX HTTP and reverse proxy server
After=syslog.target network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/var/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/usr/sbin/nginx -s reload
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Запускаем nginx:

systemctl start nginx

И проверяем есть ли процесс:

ps aux | grep nginx

root       15393  0.0  0.0  43348  3396 ?        Ss   09:07   0:00 nginx: master process /usr/sbin/nginx
nobody     15394  0.0  0.1  44284  7240 ?        S    09:07   0:00 nginx: worker process
user        15450  0.0  0.0   6060  1844 pts/0    S+   09:08   0:00 grep nginx

Судя по ответу всё в порядке. Поскольку теперь у нас есть сервис, мы можем посмотреть статус nginx используя команду:

systemctl status nginx

● nginx.service - The NGINX HTTP and reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; disabled; preset: enabled)
     Active: active (running) since Fri 2023-09-22 09:07:39 EDT; 53s ago
    Process: 15391 ExecStartPre=/usr/sbin/nginx -t (code=exited, status=0/SUCCESS)
    Process: 15392 ExecStart=/usr/sbin/nginx (code=exited, status=0/SUCCESS)
   Main PID: 15393 (nginx)
      Tasks: 2 (limit: 4583)
     Memory: 3.0M
        CPU: 37ms
     CGroup: /system.slice/nginx.service
             ├─15393 "nginx: master process /usr/sbin/nginx"
             └─15394 "nginx: worker process"

На данный момент, после перезагрузки машины, Nginx не будет запущен, что, очевидно, плохо для веб-сервера. Поэтому сделаем так, чтобы Nginx “заводился” после ребута.

Остановим сервис:

systemctl stop nginx

И выполним:

systemctl enable nginx

и получим сообщение:

Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service → /lib/systemd/system/nginx.service.

2.5 Базовая настройка Nginx

Открываем файл /etc/nginx/nginx.conf У Вас там может быть портянка из настроек. Я приведу пример своей конфигураци

user  www-data;
worker_processes  4;
load_module modules/ngx_http_image_filter_module.so;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
    use epoll;
    multi_accept on;
}


http {
    include       mime.types;
    default_type  application/octet-stream;
    include /home/user/configurations/nginx/*.conf;
}

Поясним:

Сохраняемся и проверяем конфигурацию

nginx -t

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

3. Настройка хоста для обработки изображения

Обычно я храню настройки для хостов отдельно от папки Nginx. Создадим в домашней директории папку для хранения конфигураций

cd ~; mkdir -p configurations/nginx; cd configurations/nginx

Создаем файл с расширением .conf. Для примера я буду использовать домен img.site.local Создаем файл:

nano img.site.local.conf

И прописываем конфигурацию

proxy_cache_path /web/cache/images levels=1:2 keys_zone=thumbs:10m inactive=24h max_size=5G;

server {
    listen 80;
    listen [::]:80;
    server_name img.site.local;
    server_tokens off;
    location / {
        proxy_pass http://localhost:8081;
        proxy_cache thumbs;
        proxy_cache_valid  200      24h;
        proxy_cache_valid  404 415  1m;
    }
}

server {
    listen 8081;

    root /web/mediacontent;

    set $sharpen 0;
    set $quality 100;

    image_filter_buffer 20M;

    if ($uri ~ ^/(crop|resize)/(\d+|-)x(\d+|-)/){
         set $w $2;
         set $h $3;
    }

    error_page 415 = /empty;

    location ~ ^/crop/(?:\d+|-)x(?:\d+|-)/.*\.(?:jpg|gif|png)$ {
        rewrite ^/crop/[\w\d-]+/(.*)$ /$1;
        if (!-f $request_filename) {
            rewrite ^.*$ /notfound last;
        }
        include /home/<user>/configurations/nginx/inc/watermark.conf;
        image_filter crop $w $h;
        break;
    }
    location ~ ^/resize/(?:\d+|-)x(?:\d+|-)/.*\.(?:jpg|gif|png)$ {
        rewrite ^/resize/[\w\d-]+/(.*)$ /$1;
        if (!-f $request_filename) {
            rewrite ^.*$ /notfound last;
        }
        include /home/<user>/configurations/nginx/inc/watermark.conf;
        image_filter resize $w $h;
        break;
    }
    location ~ ^/original/.*\.(?:jpg|png)$ {
        rewrite ^/original/(.*)$ /$1;
        if (!-f $request_filename) {
            rewrite ^.*$ /notfound last;
        }
        break;
    }
    location ~ ^.*\.(?:jpg|png)$ {
        image_filter_sharpen $sharpen;
        image_filter_jpeg_quality $quality;
        image_filter_webp_quality $quality;
        include /home/<user>/configurations/nginx/inc/watermark.conf;
    }

    location = /notfound {
        return 404;
    }

    location = /empty {
        empty_gif;
    }
}

Поясним:

Проверим конфигурацию Nginx:

nginx -t

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Если все хорошо

service nginx start # Если Nginx не запущен
service nginx reload # Если он был запущен ранее

4. Проверка результата

Оригинальный размер с водным знаком

URL: http://img.site.local/picture.jpg

Оригинальный размер с водяным знаком

Оригинальная картинка

URL: http://img.site.local/original/picture.jpg

Оригинальная картинка

Изменение размера 300x300

URL: http://img.site.local/resize/300x300/picture.jpg

Изменение размера 300x300

Обрезка 300x300

URL: http://img.site.local/crop/300x300/picture.jpg

Обрезка 300x300*

Обрезка 250x140 (без водяного знака)

URL: http://img.site.local/crop/250x140/picture.jpg

Обрезка 250x140 без водного знака

Дополнительно

Если Вы используете водяной знак, но хотите добавить возможность убирать знак, можно добавить такую возможность. Для этого достаточно (к примеру), добавить еще несколько правил:

# после проверки url
# ...
if ($uri ~ ^/nowater/(crop|resize)/(\d+|-)x(\d+|-)/){
        set $w $2;
        set $h $3;
}
# ...
location ~ ^/nowater/crop/(?:\d+|-)x(?:\d+|-)/.*\.(?:jpg|gif|png)$ {
    rewrite ^/nowater/crop/[\w\d-]+/(.*)$ /$1;
    if (!-f $request_filename) {
        rewrite ^.*$ /notfound last;
    }
    image_filter crop $w $h;
    break;
}
location ~ ^/nowater/resize/(?:\d+|-)x(?:\d+|-)/.*\.(?:jpg|gif|png)$ {
    rewrite ^/nowater/resize/[\w\d-]+/(.*)$ /$1;
    if (!-f $request_filename) {
        rewrite ^.*$ /notfound last;
    }
    image_filter resize $w $h;
    break;
}
location ~ ^/nowater/(?!crop/|resize/).*\.(?:jpg|png)$ {
    rewrite ^/nowater/(?!crop|resize)/(.*)$ /original/$2;
    break;
}

Обрезка 400x400 без водяного знака

URL: http://img.site.local/nowater/crop/400x400/picture.jpg

Обрезка 400x400 без водяного знака

Изменение размера 400x400 без водяного знака

URL: http://img.site.local/nowater/resize/400x400/picture.jpg

Изменение размера 400x400 без водяного знака