[Настройка Linux, Системное администрирование, Nginx, *nix, C] Nginx. О чем не пишут в книгах

Автор Сообщение
news_bot ®

Стаж: 6 лет 2 месяца
Сообщений: 27286

Создавать темы news_bot ® написал(а)
24-Июн-2021 19:33

Эта статья родилась случайно. Слоняясь по книжному фестивалю и наблюдая, как дочка пытает консультантов, заставляя их искать Иэна Стюарта, мой глаз зацепился за знакомые буквы на обложке: "Nginx".Надо же, на полках нашлось целых три книги - не полистать их было бы преступлением. Первая, вторая, третья... Ощущение, будто что-то не так. Ну вроде страниц много, текст связный, но каково содержание? Установка nginx, список переменных и модулей, а дальше docker, ansible. Открываем вторую: wget, лимиты запросов и памяти, балансировка, kubernetes, AWS. Третья: GeoIP, авторизация, потоковое вещание, puppet, Azure. Ребята, а где про то, как вообще работает nginx? На кого рассчитаны ваши книги? На состоявшегося админа, который и так знает архитектуру этого веб-сервера? Да он вроде с базовыми настройками и сам справится. На новичка, который не знает как пользоваться wget? Вы уверены, что ему знание о существовании ngx_http_degradation_module и тем паче "облака" важнее порядка прохождения запроса? Итак. О чем не пишут в книгах.
(здесь и дальше мы говорим только о NGX_HTTP_) Фазы обработки запросаNginx разделяет обработку запросов на одиннадцать этапов (фаз). Модуль, обрабатывая запрос, может реализовывать свои функции на одном или нескольких этапах. Эти этапы определены следующим образом:/src/http/ngx_http_core_module.h
typedef enum {
    NGX_HTTP_POST_READ_PHASE = 0,
    NGX_HTTP_SERVER_REWRITE_PHASE,
    NGX_HTTP_FIND_CONFIG_PHASE,
    NGX_HTTP_REWRITE_PHASE,
    NGX_HTTP_POST_REWRITE_PHASE,
    NGX_HTTP_PREACCESS_PHASE,
    NGX_HTTP_ACCESS_PHASE,
    NGX_HTTP_POST_ACCESS_PHASE,
    NGX_HTTP_PRECONTENT_PHASE,
    NGX_HTTP_CONTENT_PHASE,
    NGX_HTTP_LOG_PHASE
} ngx_http_phases;
В данном перечислении фазы расположены именно в том порядке, в котором они исполняются. Каждый запрос, проходя цепочку модулей, может быть обработан на определенном этапе, но именно в такой очередности. Строго говоря, в данной очередности может появиться петля, в случае изменения URI, но обо всём по порядку.NGX_HTTP_POST_READ_PHASE - начальная фаза обработки запроса. На ней обрабатывается хэндлер только одного встроенного модуля ngx_http_realip_module, который позволяет менять адрес (и порт клиента) на переданные в поле заголовка определенном директивой real_ip_header.Вторая и четвертая фазы (NGX_HTTP_SERVER_REWRITE_PHASE, NGX_HTTP_REWRITE_PHASE) являются этапами перенаправлений, на которых обрабатываются директивы модуля ngx_http_rewrite_module (пример того, как хэндлеры модуля могут быть реализованы на разных фазах). Как и следует из названия, NGX_HTTP_SERVER_REWRITE_PHASE обрабатывает директивы в контексте server, в то время как NGX_HTTP_REWRITE_PHASE обрабатывает директивы определенные в местоположении заданном на этапе NGX_HTTP_FIND_CONFIG_PHASE. Этот этап (третий из нашего списка) не позволяет навешивать собственные обработчики. На нем nginx определяет новое (отличного от дефолтного) местоположение обработки запроса.Продемонстрируем вышесказанное на практическом примере. Для этого пересоберем nginx c поддержкой отладки и рассмотрим следующий конфиг:
server {
  listen       *:80;
  server_name  .example.com;
  set $test 101;
  location / {
    set $test 201;
    set $test 202;
    return 200 "$test\n";
    set $test 203;
  }
  set $test 102;
}
Делаем запрос
$ GET -S example.com
GET http://example.com
200 OK
202
и видим в логе:
$ grep -E 'http script (set|val)' /var/log/nginx/error.log | cut -d " " -f 6-
http script value: "101"
http script set $test
http script value: "102"
http script set $test
http script value: "201"
http script set $test
http script value: "202"
http script set $test
Директивы модуля ngx_http_rewrite_module (break, if, return, rewrite и set) обрабатываются последовательно, в порядке указанном в конфигурационном файле, но в очередности определенной фазами nginx, поэтому сначала в фазе NGX_HTTP_SERVER_REWRITE_PHASE переменной $test присвоилось значение 101, затем 102 (не смотря на его расположение ниже блока location), затем в фазе NGX_HTTP_REWRITE_PHASE она стала равна 201 и 202, после чего исполнилась директива return, на которой обработка запроса в данной фазе прервалась, и до "set $test 203;" ход уже не дошел.Следующая (пятая) фаза NGX_HTTP_POST_REWRITE_PHASE также является служебной и не позволяет регистрировать собственные обработчики. По сути, на ней происходит повторный переход к фазе NGX_HTTP_FIND_CONFIG_PHASE в случае, если URI запроса изменился на предыдущем этапе. Именно об этом (возможном) цикле и говорилось ранее.Важным моментом здесь является именно, то, что фактический rewrite происходит именно в фазе NGX_HTTP_POST_REWRITE_PHASE, а не в NGX_HTTP_REWRITE_PHASE, как может ожидаться исходя из того, что директива rewrite модуля ngx_http_rewrite_module обрабатывается именно в ней.
server {
  listen       *:80;
  server_name  .example.com;
  location / {
    rewrite ^ /one;
    rewrite ^ /two;
    rewrite ^ /three;
  }
  location /one {
    return 200 "one\n";
  }
  location /two {
    return 200 "two\n";
  }
  location /three {
    return 200 "three\n";
  }
}
$ GET -S example.com
GET http://example.com
200 OK
three
$ grep -E '(rewritten|finalize)' /var/log/nginx/error.log | cut -d " " -f 6-
rewritten data: "/one", args: "", client: 12.34.56.78, server: example.com, request: "GET / HTTP/1.1", host: "example.com"
rewritten data: "/two", args: "", client: 12.34.56.78, server: example.com, request: "GET / HTTP/1.1", host: "example.com"
rewritten data: "/three", args: "", client: 12.34.56.78, server: example.com, request: "GET / HTTP/1.1", host: "example.com"
http finalize request: 0, "/three?" a:1, c:1
Здесь ошибочно могло ожидаться перенаправление в location "/one" на этапе "rewrite ^ /one;". На самом же деле, сначала последовательно зафиксируются все три rewrite, и только после этого, в фазе NGX_HTTP_POST_REWRITE_PHASE (один раз) действительно выполнится перенаправление.Ещё немного модифицируем вышеприведенный конфиг:
server {
  listen       *:80;
  server_name  .example.com;
  location / {
    set $test 101;
    rewrite ^ /one;
    set $test 102;
    rewrite ^ /two;
    set $test 103;
    rewrite ^ /three;
  }
  location /one {
    return 200 "$test\n";
  }
  location /two {
    return 200 "$test\n";
  }
  location /three {
    return 200 "$test\n";
  }
}
$ GET -S example.com
GET http://example.com
200 OK
103
$ grep -E '(set|val|rewritten|finalize)' /var/log/nginx/error.log | cut -d " " -f 6-
kevent set event: 3: ft:-1 fl:0025
http script value: "101"
http script set $test
rewritten data: "/one", args: "", client: 12.34.56.78, server: example.com, request: "GET / HTTP/1.1", host: "example.com"
http script value: "102"
http script set $test
rewritten data: "/two", args: "", client: 12.34.56.78, server: example.com, request: "GET / HTTP/1.1", host: "example.com"
http script value: "103"
http script set $test
rewritten data: "/three", args: "", client: 12.34.56.78, server: example.com, request: "GET / HTTP/1.1", host: "example.com"
http set discard body
http finalize request: 0, "/three?" a:1, c:1
Здесь интересны два момента:1) Уже вышесказанное - директивы модуля ngx_http_rewrite_module (break, if, return, rewrite и set) обрабатываются в порядке следования в конфигурационном файле. При этом обработка может быть прервана только исполнением директивы return или в случае, если строка замены директивы rewrite начинается с "http://", "https://" или "$scheme".Теперь становится понятно, почему в такой конфигурации
server {
  listen       *:80;
  server_name  .example.com;
  location / {
    rewrite ^ /one;
    return 200 "zero\n";
  }
  location /one {
    return 200 "one\n";
  }
}
не происходит перенаправления в location one.
$ GET -S example.com
GET http://example.com
200 OK
zero
2) Время (а точнее место) жизни переменной не ограничивается одним location - после выполнения трех директив set переменная $test была установлена в значение "103" и не утратила этого значения при перемещении в location "/three". Это касается не только обычных перенаправлений, но и подзапросов.Для наглядности добавим в сборку модуль echo-nginx-module, реализующий директиву echo, которая позволяет передать клиенту контент, в том числе, содержащий переменные. В целом я противник OpenResty, хотя и не такой категоричный как Максим Дунин:
Я бы не рекомендовал ничего искать в исходниках "echo", там у автора подход "если оно работает, то и хорошо". Имеет смысл для начала смотреть в исходники самого nginx'а и стандартных модулей.
Но для наглядности он бывает полезен, да и сама эта статья во многом перекликается с http://openresty.org/download/agentzh-nginx-tutorials-en.htmlВопрос (в общем-то, простой, но, на мой взгляд, весьма неплохой для того же собеседования): "Какой ответ получит клиент, при выполнении запроса к корню сайта со следующим конфигом?" Отмечу, что echo-nginx-module реализован на этапе NGX_HTTP_CONTENT_PHASE.
server {
  listen       *:80;
  server_name  .example.com;
  location / {
    set $test one;
    echo "root: $test";
    set $test "$test two";
    auth_request /auth;
    set $test "$test three";
  }
  location /auth {
    set $test "$test auth_pre";
    return 200 "auth: $test\n";
    set $test "$test auth_post";
  }
}
Ответ
$ GET -S example.com
GET http://example.com
200 OK
root: one two three auth_pre
  • Сначала исполняются все директивы set в локейшине "/";
  • Затем выполняется подзапрос auth_request /auth;
  • В локейшине "/auth" выполняется ещё одна директива set, при этом предыдущее значение переменной $test не теряется;
  • После выполнения подзапроса исполнение возвращается в локейшин "/" и происходит отдача контента клиенту директивой echo.Отдельным интересным моментом является, то, что return 200 "auth: $test\n"; не возвращает клиенту никакого контента, так как тело ответа от подзапроса "отбрасывается" модулем ngx_http_auth_request_module.
NGX_HTTP_PREACCESS_PHASE - здесь работают, как редкий, уже упомянутый выше, известный только фряшникам, ngx_http_degradation_module, так и весьма популярные ngx_http_limit_req_module и ngx_http_limit_conn_module. То есть это этап, на котором ещё не требуется выяснять права доступа, но уже можно произвести контроль количества соединений, запросов и потребляемой памяти.Седьмая фаза (NGX_HTTP_ACCESS_PHASE) - место где работают модули ngx_http_access_module, ngx_http_auth_basic_module и ngx_http_auth_request_module. Клиент должен пройти проверку авторизации всех (поведение можно изменить установкой директивы satisfy в any) хэндлеров, данных модулей, чтобы перейти к следующей фазе NGX_HTTP_POST_ACCESS_PHASE. В этой фазе и происходит обработка директивы satisfy и также нельзя навесить собственные обработчики.Почти добрались до контента, но на этапе NGX_HTTP_PRECONTENT_PHASE ещё есть возможность проверить существование файла(ngx_http_try_files_module), которому передать запрос или отзеркалировать (ngx_http_mirror_module) его.NGX_HTTP_CONTENT_PHASE - этап генерации ответа, самая "популярная" фаза. Хэндлеры исполняются последовательно, пока один из них не сформирует и вернет ответ.Ну и заключительный этап NGX_HTTP_LOG_PHASE - фаза логирования. На ней работает только один стандартный модуль ngx_http_log_module. Этой фазой завершается обработка запроса.Следует отметить, что модуль не обязательно должен исполняться на определенном этапе - он может быть "фазонезависим". Классическим примером является ngx_http_map_module. Да, вычисление директивы map происходит именно в момент использования переменной, но определение "формулы" по которой происходит вычисление переменной - это просто декларация не зависящая от фазы обработки запроса.На этом можно было бы закончить с фазами, так как сказанное ниже больше относится к модулям (а это тема отдельного цикла статей), но всё же пару строк о хэндлерах и встраивании модуля в nginx на определенной фазе.Ожидается, что хэндлеры могут возвращать следующие коды:
  • NGX_OK - Обработка завершена успешно, переходим к следующей фазе.
  • NGX_DECLINED - Запрос не предназначен данному хэндлеру, необходимо перейти к следующему хэндлеру текущей фазы. Если же текущий хэндлер является последним в текущей фазе, то переходим к следующей фазе.
  • NGX_AGAIN (для фаз NGX_HTTP_SERVER_REWRITE_PHASE, NGX_HTTP_REWRITE_PHASE , NGX_HTTP_PREACCESS_PHASE, NGX_HTTP_ACCESS_PHASE) или NGX_DONE (для фазы NGX_HTTP_CONTENT_PHASE) - выполнение хэндлера завершено успешно, необходимо подождать появления некоторого события (например, асинхронной операцией ввода-вывода) и повторить вызов хэндлера;
  • NGX_ERROR или NGX_HTTP_* — при выполнении хэндлера произошла ошибка. В случае NGX_ERROR соединение будет прервано, иначе будет возвращен HTTP код ошибки.
Зарегистрировать хэндлеры можно одним из двух способов:1) Обратиться к основной конфигурации модуля ngx_http_core_module и добавить хэндлер к одному из элементов вектора phases. Пример регистрации хэндлера в фазе NGX_HTTP_CONTENT_PHASE:
static ngx_int_t
ngx_http_sample_init(ngx_conf_t *cf)
{
  ngx_http_handler_pt        *h;
  ngx_http_core_main_conf_t  *cmcf;
  cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
  h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
  if (h == NULL) {
    return NGX_ERROR;
  }
  *h = ngx_http_sample_handler;
  return NGX_OK;
}
2) Получить конфигурацию location и указать функцию, которая будет обрабатывать запросы для этого location (данный метод применим только для фазы NGX_HTTP_CONTENT_PHASE):
static char *
ngx_http_sample(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
  ngx_http_core_loc_conf_t  *clcf;
  clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
  clcf->handler = ngx_http_sample_handler;
  return NGX_CONF_OK;
}
Теперь, зная способы встраивания "фазовых" модулей, мы можем "расшифровать" заглавную картинку и посмотреть какой из стандартных модулей на каком этапе реализует свои функции:
$ grep -E '[ ]+NGX_HTTP_.+_PHASE' ../ngx_http_core_module.h |\
> cut -d ',' -f 1 |\
> awk '{print $1}' |\
> xargs -tI %% grep -rF %% . |\
> cut -d ':' -f 1 \
>
grep -rF NGX_HTTP_POST_READ_PHASE .
./ngx_http_realip_module.c
grep -rF NGX_HTTP_SERVER_REWRITE_PHASE .
./ngx_http_rewrite_module.c
grep -rF NGX_HTTP_FIND_CONFIG_PHASE .
grep -rF NGX_HTTP_REWRITE_PHASE .
./ngx_http_rewrite_module.c
grep -rF NGX_HTTP_POST_REWRITE_PHASE .
grep -rF NGX_HTTP_PREACCESS_PHASE .
./ngx_http_limit_req_module.c
./ngx_http_limit_conn_module.c
./ngx_http_realip_module.c
./ngx_http_degradation_module.c
grep -rF NGX_HTTP_ACCESS_PHASE .
./ngx_http_auth_basic_module.c
./ngx_http_access_module.c
./ngx_http_auth_request_module.c
grep -rF NGX_HTTP_POST_ACCESS_PHASE .
grep -rF NGX_HTTP_PRECONTENT_PHASE .
./ngx_http_mirror_module.c
./ngx_http_try_files_module.c
grep -rF NGX_HTTP_CONTENT_PHASE .
./ngx_http_random_index_module.c
./ngx_http_static_module.c
./ngx_http_dav_module.c
./ngx_http_gzip_static_module.c
./ngx_http_index_module.c
./ngx_http_autoindex_module.c
grep -rF NGX_HTTP_LOG_PHASE .
./ngx_http_log_module.c
$ grep -rF 'clcf->handler = ' . |\
> cut -d ':' -f 1 \
>
./ngx_http_uwsgi_module.c
./ngx_http_uwsgi_module.c
./ngx_http_memcached_module.c
./ngx_http_grpc_module.c
./ngx_http_grpc_module.c
./ngx_http_mp4_module.c
./ngx_http_fastcgi_module.c
./ngx_http_fastcgi_module.c
./ngx_http_flv_module.c
./ngx_http_scgi_module.c
./ngx_http_scgi_module.c
./ngx_http_stub_status_module.c
./ngx_http_empty_gif_module.c
./ngx_http_proxy_module.c
./ngx_http_proxy_module.c
./perl/ngx_http_perl_module.c
На этом всё. Точнее ещё самое главное.
Спасибо!Игорю Сысоеву
Максиму Дунину
Валентину Бартеневу
и всей команде Nginx за изумительный продуктРоману Арутюняну
Валерию Холодкову (книга не попадалась :) )
за замечательные модули и статьиИчунь Чжану за спорные, но интересные решенияЭвану Миллеру за "сушеный укроп"
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_nastrojka_linux (Настройка Linux), #_sistemnoe_administrirovanie (Системное администрирование), #_nginx, #_*nix, #_c, #_nginx, #_nastrojka_linux (
Настройка Linux
)
, #_sistemnoe_administrirovanie (
Системное администрирование
)
, #_nginx, #_*nix, #_c
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 27-Апр 19:06
Часовой пояс: UTC + 5