Как работает Angie PRO#

Методы обработки соединений#

Angie PRO поддерживает различные методы обработки соединений. Наличие того или иного метода зависит от используемой платформы. Если на платформе доступно сразу несколько методов, Angie PRO обычно сам выбирает наиболее эффективный метод. Однако при необходимости можно явно выбрать метод обработки соединений с помощью директивы use.

Поддерживаются следующие методы обработки соединений:

select#

Стандартный метод. Модуль для поддержки этого метода собирается автоматически, если на платформе не обнаружено более эффективного метода. Можно принудительно разрешить или запретить сборку этого модуля с помощью параметров ‑‑with‑select_module и ‑‑without‑select_module.

poll#

Стандартный метод. Модуль для поддержки этого метода собирается автоматически, если на платформе не обнаружено более эффективного метода. Можно принудительно разрешить или запретить сборку этого модуля с помощью параметров ‑‑with‑poll_module и ‑‑without‑poll_module.

kqueue#

Эффективный метод, используемый во FreeBSD 4.1+, OpenBSD 2.9+, NetBSD 2.0 и macOS.

epoll#

Эффективный метод, используемый в Linux 2.6+.

/dev/poll#

Эффективный метод, используемый в Solaris 7 11/99+, HP/UX 11.22+ (eventport), IRIX 6.5.15+ и Tru64 UNIX 5.1A+.

eventport#

event ports, метод, используемый в Solaris 10+ (из-за имеющихся проблем вместо него рекомендуется использовать метод /dev/poll).

Обработка HTTP-сессий#

Обработка каждого HTTP-запроса происходит последовательными фазами. В каждой фазе запрос подвергается специфической обработке.

Post-read

Первая фаза. В этой фазе выполняется модуль http_realip.

Server-rewrite

Выполняются директивы модуля http_rewrite, определенные на уровне server(снаружи блоков location).

Find-config

Фаза выбора location в конфигурации на основании URI запроса.

Rewrite

Также, как в фазе Server-rewrite, выполняются директивы модуля http_rewrite, но теперь — определенные для location, выбранного в предыдущей фазе.

Post-rewrite

Специальная фаза, в которой запрос, как в фазе Find-config, перенаправляется в новый location, если его URI изменился при выполнении фазы Rewrite.

Preaccess

В этой фазе выполняются модули http_limit_conn и http_limit_req.

Access

Фаза выполнения модулей http_access и http_auth_basic.

Post-access

Специальная фаза обработки результатов предыдущей, выполняется директива satisfy any.

Precontent

Выполняются директивы try_files и модуля http_mirror.

Content

Фаза генерации HTTP-ответа на основе выполнения, например, директив модуля http_index или по результатам проксирования.

Log

Последняя фаза — логирование результатов обработки запроса. Выполняется модуль http_log.

Обработка TCP/UDP-сессий#

Обработка клиентской TCP/UDP-сессии происходит последовательными фазами:

Post-accept

Первая фаза после принятия клиентского соединения. В этой фазе выполняется модуль stream_realip.

Pre-access

Предварительная проверка доступа. В этой фазе выполняются модули stream_limit_conn и stream_set.

Access

Ограничение доступа для клиента перед обработкой данных. В этой фазе выполняется модуль stream_access.

SSL

Терминирование TLS/SSL. В этой фазе выполняется модуль stream_ssl.

Preread

Чтение первых байт данных в буфер предварительного чтения для анализа, например модулем stream_ssl_preread, перед их обработкой.

Content

Обязательная фаза, в которой происходит обработка данных; в большинстве случаев это проксирование на группу серверов или отправка клиенту заданного ответа.

Log

Заключительная фаза, в которой записывается результат обработки клиентской сессии. В этой фазе выполняется модуль stream_log.

Обработка запросов#

Выбор виртуального сервера#

Сначала соединение создается в контекcте сервера по умолчанию. Затем имя сервера может быть определено на следующих стадиях обработки запроса, каждая из которых участвует в выборе конфигурации:

  • предварительно во время операции SSL-рукопожатия согласно SNI

  • после обработки строки запроса

  • после обработки поля «Host» заголовка запроса

Если после обработки строки запроса или поля «Host» заголовка запроса имя сервера не было выбрано, то Angie PRO будет использовать пустое имя в качестве имени сервера.

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

  • в случае использования директивы ssl_protocols список протоколов задается библиотекой OpenSSL перед применением конфигурации сервера согласно имени, запрашиваемого через SNI. Таким образом, протоколы должны быть заданы только для сервера по умолчанию;

  • директивы client_header_buffer_size и merge_slashes задействуются перед чтением строки запроса, они используют конфигурацию сервера по умолчанию или конфигурацию сервера, выбранного через SNI;

  • в случае использования директив ignore_invalid_headers, large_client_header_buffers и underscores_in_headers, которые участвуют в обработке полей заголовка запроса, выбор сервера дополнительно зависит от того, была ли обновлена конфигурация сервера согласно строке запроса или полю заголовка «Host»;

  • ошибочный ответ будет обработан с помощью директивы error_page в том сервере, который в настоящий момент выполняет запрос.

Определение виртуального сервера по имени#

Angie PRO вначале решает, какой из серверов должен обработать запрос. Рассмотрим простую конфигурацию, где все три виртуальных сервера слушают на порту *:80:

server {
  listen      80;
  server_name example.org www.example.org;
# ...
}

server {
  listen      80;
  server_name example.net www.example.net;
#  ...
}

server {
  listen      80;
  server_name example.com www.example.com;
#  ...
}

В этой конфигурации, чтобы определить, какому серверу следует направить запрос, Angie PRO проверяет только поле «Host» заголовка запроса. Если его значение не соответствует ни одному из имен серверов или в заголовке запроса нет этого поля вовсе, Angie PRO направит запрос в сервер по умолчанию для этого порта. В вышеприведенной конфигурации сервером по умолчанию будет первый сервер, что соответствует стандартному поведению Angie PRO по умолчанию. Сервер по умолчанию можно задать явно с помощью параметра default_server в директиве listen:

server {
  listen      80 default_server;
  server_name example.net www.example.net;
#  ...
}

Примечание

Следует иметь в виду, что сервер по умолчанию является свойством слушающего сокета, а не имени сервера.

Интернационализованные имена#

Для указания интернационализированных доменных имен (IDNs) в директиве server_name следует указывать Punycode-представление имени:

server {
    listen       80;
    server_name  xn--e1afmkfd.xn--80akhbyknj4f;  # пример.испытание
#    ...
}

Как предотвратить обработку запросов без имени сервера#

Если запросы без поля «Host» в заголовке не должны обрабатываться, можно определить сервер, который будет их отклонять:

server {
  listen      80;
  server_name "";
  return      444;
}

Здесь в качестве имени сервера указана пустая строка, которая соответствует запросам без поля «Host» в заголовке, и возвращается специальный код 444, который закрывает соединение.

Определение виртуального сервера по имени и IP-адресу#

Рассмотрим более сложную конфигурацию, в которой некоторые виртуальные серверы слушают на разных адресах:

server {
  listen      192.168.1.1:80;
  server_name example.org www.example.org;
#  ...
}

server {
  listen      192.168.1.1:80;
  server_name example.net www.example.net;
#  ...
}

server {
  listen      192.168.1.2:80;
  server_name example.com www.example.com;
#  ...
}

В этой конфигурации Angie PRO вначале сопоставляет IP-адрес и порт запроса с директивами listen в блоках server. Затем он сопоставляет значение поля «Host» заголовка запроса с директивами server_name в блоках server, которые соответствуют IP-адресу и порту. Если имя сервера не найдено, запрос будет обработан в сервере по умолчанию. Например, запрос www.example.com, пришедший на порт 192.168.1.1:80, будет обработан сервером по умолчанию для порта 192.168.1.1:80, т.е. первым сервером, т.к. для этого порта www.example.com не указан в списке имен серверов.

Сервер по умолчанию является свойством слушающего сокетa, поэтому у разных сокетов могут быть определены свои серверы по умолчанию:

server {
  listen      192.168.1.1:80;
  server_name example.org www.example.org;
#  ...
}

server {
  listen      192.168.1.1:80 default_server;
  server_name example.net www.example.net;
#  ...
}

server {
  listen      192.168.1.2:80 default_server;
  server_name example.com www.example.com;
#  ...
}

Определение location#

Hа примере простого PHP-сайта:

server {
  listen      80;
  server_name example.org www.example.org;
  root        /data/www;

  location / {
     index   index.html index.php;
  }

  location ~* \.(gif|jpg|png)$ {
     expires 30d;
  }

  location ~ \.php$ {
     fastcgi_pass  localhost:9000;
     fastcgi_param SCRIPT_FILENAME
                   $document_root$fastcgi_script_name;
     include       fastcgi_params;
  }
}

Angie PRO вначале ищет среди всех префиксных location, заданных строками, максимально совпадающий. В вышеприведенной конфигурации указан только один префиксный location /, и, поскольку он подходит под любой запрос, он и будет использован, если других совпадений не будет найдено. Затем Angie PRO проверяет location, заданные регулярными выражениями, в порядке их следования в конфигурационном файле. При первом же совпадении поиск прекращается и Angie PRO использует совпавший location. Если запросу не соответствует ни одно из регулярных выражений, Angie PRO использует максимально совпавший префиксный location, найденный ранее.

Примечание

Следует иметь в виду, что location всех типов сопоставляются только с URI-частью строки запроса без аргументов. Так делается потому, что аргументы в строке запроса могут быть заданы различными способами, например:

/index.php?user=john&page=1
/index.php?page=1&user=john

Кроме того, в строке запроса можно запросить что угодно:

/index.php?page=1&something+else&user=john

Теперь посмотрим, как бы обрабатывались запросы в вышеприведенной конфигурации:

Запросу /logo.gif во-первых соответствует префиксный location /, а во-вторых — регулярное выражение .(gif|jpg|png)$, поэтому он обрабатывается location’ом регулярного выражения. Согласно директиве root /data/www запрос отображается в файл /data/www/logo.gif, который и посылается клиенту.

Запросу /index.php также во-первых соответствует префиксный location /, а во-вторых — регулярное выражение .(php)$. Следовательно, он обрабатывается location’ом регулярного выражения и запрос передается FastCGI-серверу, слушающему на localhost:9000. Директива fastcgi_param устанавливает FastCGI-параметр SCRIPT_FILENAME в /data/www/index.php, и сервер FastCGI выполняет указанный файл. Переменная $document_root равна значению директивы root, а переменная $fastcgi_script_name равна URI запроса, т.е. /index.php.

Запросу /about.html соответствует только префиксный location /, поэтому запрос обрабатывается в нем. Согласно директиве root /data/www запрос отображается в файл /data/www/about.html, который и посылается клиенту.

Обработка запроса / более сложная. Ему соответствует только префиксный location /, поэтому запрос обрабатывается в нем. Затем директива index проверяет существование индексных файлов согласно своих параметров и директиве root /data/www. Если файл /data/www/index.html не существует, а файл /data/www/index.php существует, то директива делает внутреннее перенаправление на /index.php и Angie PRO снова сопоставляет его с location’ами, как если бы такой запрос был послан клиентом. Как мы видели ранее, перенаправленный запрос будет в конечном итоге обработан сервером FastCGI.

Проксирование и балансировка нагрузки#

Одним из частых применений Angie PRO является использование его в качестве прокси-сервера, то есть сервера, который принимает запросы, перенаправляет их на проксируемые сервера, получает ответы от них и отправляет их клиенту.

Простейшая конфигурация прокси-сервера:

server {
    location / {
        proxy_pass http://backend:8080;
    }

Директива proxy_pass инструктирует Angie PRO передавать клентские запросы на сервер бэкенда backend:8080 (проксируемый сервер). Проксирование посредством Angie PRO гибко конфигурируется множеством других директив.

Проксирование FastCGI#

Angie PRO можно использовать для перенаправления запросов на FastCGI-серверы. На них могут исполняться приложения, созданные с использованием разнообразных фреймворков и языков программирования, например, PHP.

Базовая конфигурация Angie PRO для работы с проксируемым FastCGI-сервером включает в себя использование директивы fastcgi_pass вместо директивы proxy_pass, и директив fastcgi_param для настройки параметров, передаваемых FastCGI-серверу. Представьте, что FastCGI-сервер доступен по адресу localhost:9000. В PHP параметр SCRIPT_FILENAME используется для определения имени скрипта, а в параметре QUERY_STRING передаются параметры запроса. Получится следующая конфигурация:

server {
    location / {
        fastcgi_pass  localhost:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param QUERY_STRING    $query_string;
    }

    location ~ \.(gif|jpg|png)$ {
        root /data/images;
    }
}

Таким образом будет настроен сервер, который будет перенаправлять все запросы, кроме запросов статических изображений, на проксируемый сервер, работающий по адресу localhost:9000, по протоколу FastCGI.

Проксирование WebSocket#

Для превращения соединения между клиентом и сервером из HTTP/1.1 в WebSocket используется доступный в HTTP/1.1 механизм смены протокола.

Но есть сложность: поскольку «Upgrade» является hop-by-hop заголовком, то он не передается от клиента к проксируемому серверу. При прямом проксировании клиенты могут использовать метод CONNECT, чтобы обойти эту проблему. Однако при обратном проксировании такой подход не работает, так как клиент ничего о проксирующем сервере не знает, и требуется специальная обработка на проксирующем сервере.

В Angie PRO предусмотрен особый режим работы, который позволяет установить туннель между клиентом и проксируемым сервером, если проксируемый сервер вернул ответ с кодом 101 (Switching Protocols), и клиент попросил сменить протокол с помощью заголовка «Upgrade» в запросе.

Как уже отмечалось выше, hop-by-hop заголовки, включая «Upgrade» и «Connection», не передаются от клиента к проксируемому серверу, поэтому, для того чтобы проксируемый сервер узнал о намерении клиента сменить протокол на WebSocket, эти заголовки следует передать явно:

location /chat/ {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

Более сложный пример, в котором значение поля «Connection» в заголовке запроса к проксируемому серверу зависит от наличия поля «Upgrade» в заголовке запроса клиента:

http {
    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }

    server {
        ...

        location /chat/ {
            proxy_pass http://backend;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
        }
    }

По умолчанию соединение будет закрыто, если с проксируемого сервера данные не передавались в течение 60 секунд. Этот таймаут можно увеличить при помощи директивы proxy_read_timeout. Кроме того, на проксируемом сервере можно настроить периодическую отправку WebSocket ping-фреймов для сброса таймаута и проверки работоспособности соединения.

Балансировка нагрузки#

Для оптимизации использования ресурсов, повышения устойчивости и скорости обработки запросов широко используется распределение нагрузки клиентского трафика между несколькими серверами приложений. Angie PRO эффективно решает эту задачу.

Простейшая конфигурация для распределения клиентского трафика по трем сеоверам выглядит так:

http {
    upstream myapp1 {
        server srv1.example.com;
        server srv2.example.com;
        server srv3.example.com;
    }

    server {
        listen 80;

        location / {
            proxy_pass http://myapp1;
        }
    }
}

В примере три сервера srv1-srv3 одного приложения объединены в группу upstream и при проксировании Angie PRO распределит между ними клиентские запросы. По умолчанию используется алгоритм распределения round-robin. Возможно использование других алгоритмов: weight, least_conn, ip_hash. Для группы серверов upstream по умолчанию работает механизм проверки работоспособности каждого сервера, настраиваемый в контексте upstream параметрами max_fails и fail_timeout директивы server.

Логирование#

Запись в syslog#

Директивы error_log и access_log поддерживают запись в syslog. Запись в syslog настраивается при помощи следующих параметров:

server=адрес

Задает адрес сервера syslog. Адрес может быть указан в виде доменного имени или IP-адреса, и необязательного порта, или в виде пути UNIX-сокета, который указывается после префикса «unix:». Если порт не указан, используется UDP-порт 514. Если доменному имени соответствует несколько IP-адресов, используется только первый адрес.

facility=строка

Задает категорию сообщений syslog в соответствии с RFC 3164. В качестве категории может быть указано одно из следующих значений: «kern», «user», «mail», «daemon», «auth», «intern», «lpr», «news», «uucp», «clock», «authpriv», «ftp», «ntp», «audit», «alert», «cron», «local0»..»local7». По умолчанию используется «local7».

severity=строка

Задает важность сообщений syslog для access_log в соответствии с RFC 3164. Возможны те же самые значения, что и у второго параметра (уровень) директивы error_log. По умолчанию используется «info». Важность сообщений об ошибках определяется самим Angie PRO, поэтому в директиве error_log параметр игнорируется.

tag=строка

Задает метку сообщений syslog. По умолчанию используется «angie».

nohostname

Запрещает добавление поля «hostname» в заголовок сообщения syslog

Пример конфигурации syslog:

error_log syslog:server=192.168.1.1 debug;

access_log syslog:server=unix:/var/log/angie.sock,nohostname;
access_log syslog:server=[2001:db8::1]:12345,facility=local7,tag=angie,severity=info combined;

Отладочный лог#

Для работы отладочного лога Angie PRO должен быть сконфигурирован с поддержкой отладки на этапе сборки:

./configure –with-debug …

Чтобы убедиться, что поддержка отладки сконфигурирована, необходимо выполнить команду angie -V:

configure arguments: –with-debug …

Готовые пакеты для Linux по умолчанию предоставляют поддержку отладочного лога при помощи бинарного файла angie-debug. Из пакета бинарные файлы Angie PRO располагаются:

$ ls -l /usr/sbin/ | grep angie
   lrwxrwxrwx 1 root root      13 Sep 21 18:58 angie -> angie-nodebug
   -rwxr-xr-x 1 root root 1561224 Sep 21 18:58 angie-debug
   -rwxr-xr-x 1 root root 1426056 Sep 21 18:58 angie-nodebug

systemd service Angie PRO сконфигурирован:

DAEMON=${DAEMON:-/usr/sbin/angie}

Поэтому для того, чтобы включить отладочный лог, нужно

задать уровень debug с помощью директивы error_log:

error_log /path/to/log debug;

и запустить бинарный файл Angie PRO с поддержкой отладочного лога:

  1. переназначить symlink:

$ sudo ln -fs angie-debug /usr/sbin/angie
  1. выполнить команду

$ sudo angie -t && sudo service angie upgrade

которая запустит процедуру обновления исполняемого файла на лету.

Для того, чтобы отключить отладочный лог, нужно:

  1. переназначить symlink:

$ sudo ln -fs angie-nodebug /usr/sbin/angie
  1. выполнить команду

$ sudo angie -t && sudo service angie upgrade

Если при этом оставить уровень debug в директиве error_log, Angie PRO будет записывать лог на уровне info.

Обратите внимание, что переопределение лога без одновременного указания уровня debug отключит отладочный лог. В примере ниже, переопределение лога на уровне server отключает отладочный лог для этого сервера:

error_log /path/to/log debug;

http {
   server {
     error_log /path/to/log;
    # ...

Чтобы избежать этого, следует либо закомментировать строку, переопределяющую лог, либо добавить определение уровня debug:

error_log /path/to/log debug;

http {
   server {
     error_log /path/to/log debug;
   #  ...

Отладочный лог для определенных клиентов#

Можно включить отладочный лог только для определенных клиентских адресов:

error_log /path/to/log;

events {
  debug_connection 192.168.1.1;
  debug_connection 192.168.10.0/24;
}

Запись в кольцевой буфер в памяти#

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

error_log memory:32m debug;

Запись в буфер в памяти на уровне debug не оказывает существенного влияния на производительность даже при высоких нагрузках. В этом случае лог может быть извлечен при помощи gdb-скрипта, подобного следующему:

set $log = ngx_cycle->log

while $log->writer != ngx_log_memory_writer
  set $log = $log->next
end

set $buf = (ngx_log_memory_buf_t *) $log->wdata
dump binary memory debug_log.txt $buf->start $buf->end