Table of Contents
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;
}
}
Этот конфиг предполагает три важные вещи:
- Адресом репозитория будет /srv/git, поэтому выставляем соответствующие права доступа:
$ sudo chown -R www-data:www-data /srv/git
- Сам репозиторий должен быть открыт на чтение анонимусами и позволять аплоад по HTTP:
$ cd /srv/git $ git config core.sharedrepository true $ git config http.receivepack true
- Аутентификация осуществляется с помощью файла htpasswd, нужно его создать и добавить в него пользователей:
$ sudo apt-get install apache2-utils $ htpasswd -c /etc/nginx/htpasswd user1 $ htpasswd /etc/nginx/htpasswd user2 ...
На этом все, перезагружаем nginx:
$ sudo service nginx restart
Далее можно подключиться к репозиторию с помощью клиента 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 — это только часть таких задач.