Используем nginx для выполнения интересных и нестандартных задач

Nginx стремительными темпами набирает популярность, превращаясь из просто ускорителя отдачи статики для Apache в полнофункциональный и развитый веб-сервер, который все чаще применяется обособленно. В этой статье мы поговорим об интересных и нестандартных сценариях использования nginx, которые позволят выжать из веб-сервера максимум.

Почтовый прокси

Начнем с самого очевидного — со способности nginx выступать в роли почтового прокси. Эта функция есть в nginx изначально, а вот используется в продакшн она почему-то крайне редко, некоторые так и вообще не догадываются о ее существовании. Как бы там ни было, nginx поддерживает проксирование протоколов POP3, IMAP и SMTP с разными методами аутентификации, включая SSL и StartTLS, причем делает это очень быстро.

Зачем это нужно? Есть как минимум два применения данной функциональности. Первая: использовать nginx в качестве щита от назойливых спамеров, пытающихся отправить мусорные письма через наш SMTP-сервер. Обычно спамеры не создают много проблем, так как быстро отшибаются на этапе аутентификации, однако, когда их становится действительно много, nginx поможет сэкономить процессорные ресурсы. Вторая: использовать nginx для перенаправления пользователей на несколько почтовых POP3/IMAP-серверов. С этим, конечно, мог бы справиться и другой почтовый прокси, но зачем городить огород серверов, если на фронтенде уже установлен nginx для отдачи статики по HTTP, например?

Почтовый прокси-сервер в nginx сделан не совсем стандартно. Он использует дополнительный слой аутентификации, реализованный средствами HTTP, и, только если пользователь проходит этот барьер, он пропускается дальше. Обеспечивается такая функциональность путем создания страницы/скрипта, которой nginx отдает данные пользователя, а она/он возвращает ответ в виде стандартных OK или причины отказа (типа «Invalid login or password»). Скрипт запускается со следующими заголовками:

Входные данные скрипта аутентификации HTTP_AUTH_USER: юзер HTTP_AUTH_PASS: пароль HTTP_AUTH_PROTOCOL: почтовый протокол (IMAP, POP3 или SMTP)

А возвращает такие:

Выходные данные скрипта аутентификации HTTP_AUTH_STATUS: OK или причина отказа HTTP_AUTH_SERVER: реальный почтовый сервер для перенаправления HTTP_AUTH_PORT: порт сервера

Замечательная особенность такого подхода в том, что его можно использовать вовсе не для самой аутентификации, а чтобы раскидать пользователей по разным внутренним серверам, в зависимости от имени юзера, данных о текущих нагрузках на почтовые серверы либо вообще организовав простейшую балансировку нагрузки с помощью round-robin. Впрочем, если требуется всего лишь перекинуть пользователей на внутренний почтовый сервер, можно использовать вместо реального скрипта заглушку, реализованную самим nginx. Например, простейший SMTP- и IMAP-прокси в конфиге nginx будет выглядеть следующим образом:

# vi /etc/nginx/nginx.conf  mail { # Адрес скрипта аутентификации auth_http localhost:8080/auth; # Отключаем команду XCLIENT, некоторые почтовые серверы ее не понимают xclient off; # IMAP-сервер server { listen 143; protocol imap; proxy on; } # SMTP-сервер server { listen 25; protocol smtp; proxy on; } }

Далее в секцию http конфига добавляем следующее:

# vi /etc/nginx/nginx.conf http { # Маппинг на нужный порт почтового сервера в зависимости от порта, отправленного в заголовке HTTP_AUTH_PROTOCOL map $http_auth_protocol $mailport { default 25; smtp 25; imap 143; } # Реализация «скрипта» аутентификации — всегда возвращает OK и перекидывает пользователя на внутренний почтовый сервер, выставляя нужный порт с помощью приведенного выше маппинга server { listen 8080; location /auth { add_header "Auth-Status" "OK"; add_header "Auth-Server" "192.168.0.1"; add_header "Auth-Port" $mailport; return 200; } } }

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

Потоковое вещание видео

Поднять обычный видеохостинг на базе nginx легко. Достаточно только выложить перекодированное видео в доступный серверу каталог, прописать его в конфиг и настроить флеш- или HTML5-проигрыватель так, чтобы он брал видео из этого каталога. Однако, если требуется настроить непрерывное вещание видео из какого-то внешнего источника или веб-камеры, такая схема не сработает, и придется смотреть в сторону специальных потоковых протоколов.

Есть несколько протоколов, решающих эту задачу, наиболее эффективный и поддерживаемый из них RTMP. Беда только в том, что почти все реализации RTMP-сервера страдают от проблем. Официальный Adobe Flash Media Server платный. Red5 и Wowza написаны на Java, а потому не дают нужной производительности, еще одна реализация, Erlyvideo, написана на Erlang, что хорошо в случае настройки кластера, но не так эффективно для одиночного сервера.

Я же предлагаю другой подход — воспользоваться модулем RTMP для nginx. Он обладает превосходной производительностью и к тому же позволит использовать один сервер для отдачи как веб-интерфейса сайта, так и видеопотока. Проблема только в том, что модуль этот неофициальный, поэтому nginx с его поддержкой придется собрать самостоятельно. Благо сборка осуществляется стандартным способом:

$ sudo apt-get remove nginx $ cd /tmp $ wget http://bit.ly/VyK0lU -O nginx-rtmp.zip $ unzip nginx-rtmp.zip $ wget http://nginx.org/download/nginx-1.2.6.tar.gz $ tar -xzf nginx-1.2.6.tar.gz $ cd nginx-1.2.6 $ ./configure --add-module=/tmp/nginx-rtmp-module-master $ make $ sudo make install

Теперь модуль нужно настроить. Делается это, как обычно, через конфиг nginx:

rtmp { # Активируем сервер вещания на порту 1935 по адресу сайт/rtmp server { listen 1935; application rtmp { live on; } } }

Модуль RTMP не умеет работать в многопоточной конфигурации, поэтому количество рабочих процессов nginx придется сократить до одного (позже я расскажу, как обойти эту проблему):

worker_processes 1;

Теперь можно сохранить файл и заставить nginx перечитать конфигурацию. Настройка nginx завершена, но самого видеопотока у нас еще нет, поэтому его нужно где-то взять. Для примера пусть это будет файл video.avi из текущего каталога. Чтобы превратить его в поток и завернуть в наш RTMP-вещатель, воспользуемся старым добрым FFmpeg:

# ffmpeg -re -i ~/video.avi -c copy -f flv rtmp://localhost/rtmp/stream

В том случае, если видеофайл представлен не в формате H264, его следует перекодировать. Это можно сделать на лету с помощью все того же FFmpeg:

# ffmpeg -re -i ~/video.avi -c:v libx264 -c:a libfaac -ar 44100 -ac 2 -f flv rtmp://localhost/rtmp/stream

Поток также можно захватить прямо с веб-камеры:

# ffmpeg -f video4linux2 -i /dev/video0 -c:v libx264 -an -f flv rtmp://localhost/rtmp/stream

Чтобы просмотреть поток на клиентской стороне, можно воспользоваться любым проигрывателем с поддержкой RTMP, например mplayer:

$ mplayer rmtp://example.com/rtmp/stream

Или встроить проигрыватель прямо в веб-страницу, которая отдается тем же nginx (пример из официальной документации):

Простейший веб-проигрыватель RTMP

<script type="text/javascript" src="/jwplayer/jwplayer.js"></script> <script type="text/javascript"> jwplayer("container").setup({ modes: [{ type: "flash", src: "/jwplayer/player.swf", config: { bufferlength: 1, file: "stream", streamer: "rtmp://localhost/rtmp", provider: "rtmp", } }] }); </script>

Важных строки тут всего две: «file: “stream”», указывающая на RTMP-поток, и «streamer: “rtmp://localhost/rtmp”», в которой указан адрес RTMP-стримера. Для большинства задач таких настроек будет вполне достаточно. По одному адресу можно пустить несколько разных потоков, а nginx будет эффективно их мультиплексировать между клиентами. Но это далеко не все, на что способен RTMP-модуль. С его помощью, например, можно организовать ретрансляцию видеопотока с другого сервера. Сервер FFmpeg для этого вообще не нужен, достаточно добавить следующие строки в конфиг:

# vi /etc/nginx/nginx.conf  application rtmp { live on; pull rtmp://rtmp.example.com; }

Если требуется создать несколько потоков в разном качестве, можно вызвать перекодировщик FFmpeg прямо из nginx:

# vi /etc/nginx/nginx.conf  application rtmp { live on; exec ffmpeg -i rtmp://localhost/rtmp/$name -c:v flv -c:a -s 320x240 -f flv rtmp://localhost/rtmp-320x240/$name; }  application rtmp-320x240 { live on; }

С помощью такой конфигурации мы получим сразу два вещателя, один из которых будет доступен по адресу rtmp://сайт/rtmp, а второй, вещающий в качестве 320 x 240, — по адресу rtmp://сайт/rtmp–320×240. Далее на сайт можно повесить флеш-плеер и кнопки выбора качества, которые будут подсовывать плееру тот или иной адрес вещателя.

Ну и напоследок пример вещания музыки в сеть:

while true; do ffmpeg -re -i "`find /var/music -type f -name '*.mp3'|sort -R|head -n 1`" -vn -c:a libfaac -ar 44100 -ac 2 -f flv rtmp://localhost/rtmp/stream; done

Git-прокси

Система контроля версий Git способна обеспечивать доступ к репозиториям не только по протоколам Git и SSH, но и по HTTP. Когда-то реализация доступа по HTTP была примитивной и неспособной обеспечить полноценную работу с репозиторием. С версии 1.6.6 ситуация изменилась, и сегодня этот протокол можно использовать, чтобы, например, обойти ограничения брандмауэров как с той, так и с другой стороны соединения либо для создания собственного Git-хостинга с веб-интерфейсом.

К сожалению, официальная документация рассказывает только об организации доступа к Git средствами веб-сервера Apache, но, так как сама реализация представляет собой внешнее приложение со стандартным CGI-интерфейсом, ее можно прикрутить практически к любому другому серверу, включая lighttpd и, конечно же, nginx. Для этого не потребуется ничего, кроме самого сервера, установленного Git и небольшого FastCGI-сервера fcgiwrap, который нужен, потому что nginx не умеет работать с CGI напрямую, но умеет вызывать скрипты с помощью протокола FastCGI.

Вся схема работы будет выглядеть следующим образом. Сервер fcgiwrap будет висеть в фоне и ждать запроса на исполнение CGI-приложения. Nginx, в свою очередь, будет сконфигурирован на запрос исполнения CGI-бинарника git-http-backend через FastCGI-интерфейс каждый раз при обращении к указанному нами адресу. Получив запрос, fcgiwrap исполняет git-http-backend с указанными CGI-аргументами, переданными GIT-клиентом, и возвращает результат.

Чтобы реализовать такую схему, сначала установим fcgiwrap:

$ sudo apt-get install fcgiwrap

Настраивать его не нужно, все параметры передаются по протоколу FastCGI. Запущен он будет тоже автоматически. Поэтому остается только настроить nginx. Для этого создаем файл /etc/nginx/sites-enabled/git (если такого каталога нет, можно писать в основной конфиг) и пишем в него следующее:

# vi /etc/nginx/sites-enabled/git  server { # Висим на порту 8080 listen 8080; # Адрес нашего сервера (не забудь добавить запись в DNS) server_name git.example.ru;  # Логи access_log /var/log/nginx/git-http-backend.access.log; error_log /var/log/nginx/git-http-backend.error.log;  # Основной адрес для анонимного доступа location / { # При попытке загрузки отправляем юзера на приватный адрес if ($arg_service ~* "git-receive-pack") { rewrite ^ /private$uri last; }  include /etc/nginx/fastcgi_params;  # Адрес нашего git-http-backend fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend; # Адрес Git-репозитория fastcgi_param GIT_PROJECT_ROOT /srv/git; # Адрес файла fastcgi_param PATH_INFO $uri; # Адрес сервера fcgiwrap fastcgi_pass 127.0.0.1:9001; }  # Адрес для доступа на запись location ~/private(/.*)$ { # Полномочия юзера auth_basic "git anonymous read-only, authenticated write"; # HTTP-аутентификация на основе htpasswd auth_basic_user_file /etc/nginx/htpasswd;  # Настройки FastCGI include /etc/nginx/fastcgi_params; fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend; fastcgi_param GIT_PROJECT_ROOT /srv/git; fastcgi_param PATH_INFO $1; fastcgi_pass 127.0.0.1:9001; } }

Этот конфиг предполагает три важные вещи:

  1. Адресом репозитория будет /srv/git, поэтому выставляем соответствующие права доступа:
    $ sudo chown -R www-data:www-data /srv/git
  2. Сам репозиторий должен быть открыт на чтение анонимусами и позволять аплоад по HTTP:
    $ cd /srv/git $ git config core.sharedrepository true $ git config http.receivepack true
  3. Аутентификация осуществляется с помощью файла htpasswd, нужно его создать и добавить в него пользователей:
    $ sudo apt-get install apache2-utils $ htpasswd -c /etc/nginx/htpasswd user1 $ htpasswd /etc/nginx/htpasswd user2 ...

На этом все, перезагружаем nginx:

$ sudo service nginx restart

Далее можно подключиться к репозиторию с помощью клиента Git.

Настраиваем Git-прокси
Настраиваем Git-прокси

Микрокеширование

Представим себе ситуацию с динамичным, часто обновляемым сайтом, который вдруг начинает получать очень большие нагрузки (ну попал он на страницу одного из крупнейших новостных сайтов) и перестает справляться с отдачей контента. Грамотная оптимизация и реализация правильной схемы кеширования займет долгое время, а проблемы нужно решать уже сейчас. Что мы можем сделать?

Есть несколько способов выйти из этой ситуации с наименьшими потерями, однако наиболее интересную идею предложил Фенн Бэйли (Fenn Bailey, fennb.com). Идея в том, чтобы просто поставить перед сервером nginx и заставить его кешировать весь передаваемый контент, но не просто кешировать, а всего на одну секунду. Изюминка здесь в том, что сотни и тысячи посетителей сайта в секунду, по сути, будут генерировать всего одно обращение к бэкенду, получая в большинстве своем кешированную страницу. При этом разницу вряд ли кто-то заметит, потому что даже на динамичном сайте одна секунда обычно ничего не значит.

Конфиг с реализацией этой идеи будет выглядеть не так уж и сложно:

# vi /etc/nginx/sites-enabled/cache-proxy  # Настройка кеша proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=microcache:5m max_size=1000m;  server { listen 80; server_name example.com;  # Кешируемый адрес location / { # Кеш включен по умолчанию set $no_cache "";  # Отключаем кеш для всех методов, кроме GET и HEAD if ($request_method !~ ^(GET|HEAD)$) { set $no_cache "1"; }  # В случае если клиент загружает контент на сайт (no_cache = 1), делаем так, чтобы отдаваемые ему данные не кешировались в течение двух секунд и он смог увидеть результат загрузки if ($no_cache = "1") { add_header Set-Cookie "_mcnc=1; Max-Age=2; Path=/"; add_header X-Microcachable "0"; } if ($http_cookie ~* "_mcnc") { set $no_cache "1"; }  # Включаем/отключаем кеш в зависимости от состояния переменной no_cache proxy_no_cache $no_cache; proxy_cache_bypass $no_cache;  # Проксируем запросы на реальный сервер proxy_pass http://appserver.example.ru; proxy_cache microcache; proxy_cache_key $scheme$host$request_method$request_uri; proxy_cache_valid 200 1s;  # Защита от проблемы Thundering herd proxy_cache_use_stale updating;  # Добавляем стандартные хидеры proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  # Не кешируем файлы размером больше 1 Мб proxy_max_temp_file_size 1M; } }

Особое место в этом конфиге занимает строка «proxy_cache_use_stale updating;», без которой мы бы получили периодические всплески нагрузки на бэкенд-сервер из-за запросов, пришедших во время обновления кеша. В остальном все стандартно и должно быть понятно без лишних объяснений.

Тестирование производительности с выключенным/включенным микрокешированием
Тестирование производительности с выключенным/включенным микрокешированием
Тестирование производительности с выключенным/включенным микрокешированием
Тестирование производительности с выключенным/включенным микрокешированием

Приближение прокси к ЦА

Несмотря на повсеместное глобальное увеличение скоростей интернета, физическая удаленность сервера от целевой аудитории все равно продолжает играть свою роль. Это значит, что, если русский сайт крутится на сервере, расположенном где-нибудь в Америке, скорость доступа к нему будет априори медленнее, чем с российского сервера с такой же шириной канала (естественно, если закрыть глаза на все остальные факторы). Другое дело, что размещать серверы за рубежом зачастую выгоднее, в том числе и в плане обслуживания. Поэтому для получения профита, в виде более высоких скоростей отдачи, придется идти на хитрость.

Один из возможных вариантов: разместить основной производительный сервер на Западе, а не слишком требовательный к ресурсам фронтенд, отдающий статику, развернуть на территории России. Это позволит без серьезных затрат выиграть в скорости. Конфиг nginx для фронтенда в этом случае будет простой и всем нам знакомой реализацией прокси:

# vi /etc/nginx/sites-enabled/proxy  # Храним кеш 30 дней в 100 Гб хранилище proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=static:32m inactive=30d max_size=100g;  server { listen 80; server_name example.com;  # Собственно, наш прокси location ~* .(jpg|jpeg|gif|png|ico|css|midi|wav|bmp|js|swf|flv|avi|djvu|mp3)$ { # Адрес бэкенда proxy_pass back.example.com:80; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_buffer_size 16k; proxy_buffers 32 16k; proxy_cache static; proxy_cache_valid 30d; proxy_ignore_headers "Cache-Control" "Expires"; proxy_cache_key "$uri$is_args$args"; proxy_cache_lock on; } }

Выводы

Сегодня с помощью nginx можно решить множество самых разных задач, многие из которых вообще не связаны с веб-сервером и протоколом HTTP. Почтовый прокси, сервер потокового вещания и интерфейс Git — это только часть таких задач.

Nginx включен в состав базовой системы OpenBSD 5.2
Nginx включен в состав базовой системы OpenBSD 5.2
Список вкомпилированных модулей nginx
Список вкомпилированных модулей nginx

Оставить мнение

Check Also

Google как средство взлома. Разбираем актуальные рецепты Google Dork Queries

Тесты на проникновение обычно требуют набора специальных утилит, но одна из них доступна к…