Глеб Гончаров

Сисадмин и девопс из Ульяновска

X-Accel-Redirect для POST-запросов в NGINX

Ещё об одном нестандартном использовании NGINX. Рассмотрим гипотетический веб-проект для меломанов. Сайт доступен только для авторизованных пользователей: те слушают треки и оставляют комментарии под ними. Сам сервис не хранит ни музыку, ни сообщения — этим заведует контент-провайдер, трафик к которому проксируется по HTTP.

Чтобы запретить гостям слушать музыку без регистрации, запросы к трекам обрабатывает приложение, присылая в ответ X-Accel-Redirect на internal локейшен до контент-провайдера. Веб-сервер получит заголовок, найдёт локейшен, выполнит HTTP-запрос и вернёт результат клиенту.

Особенность NGINX такова, что тот при работе с X-Accel-Redirect выполнит GET-запрос вне зависимости от метода начального запроса. Скажем, если отправить POST-запрос с новым комментарием, то сообщение будет отброшено, а до апстрима дойдут только заголовки.

Решить эту проблему можно несколькими способами. Например, передавать POST как GET, помещая тело запроса в кастомный заголовок X-Accel-Post-Body. Вот так:

server {
  listen 80;

  location / {
    proxy_pass http://app.example.tld;
  }

  location /content {
    rewrite_by_lua_block {
        if ngx.header["X-Accel-Post-Body"] ~= nil then
          ngx.req.set_method(ngx.HTTP_POST)
          ngx.req.set_body_data(ngx.header["X-Accel-Post-Body"])
        end
    }

    internal;
    proxy_pass http://provider.example.tld/content/;
  }
}

Или форсировать метод POST с помощью директивы proxy_method. В этом случае придётся описать два локейшена, чтобы не передавать через POST запросы для GET.

server {
  listen 80;

  location / {
    proxy_pass http://app.example.tld;
  }

  location /content_get/ {
    internal;
    proxy_pass http://provider.example.tld/content/;    
  }

  location /content_post/ {
    internal;
    proxy_method POST;
    proxy_pass http://provider.example.tld/content/;
  }
}

На практике удобнее proxy_method. В первом случае, чтобы передать большое тело запроса в заголовке, потребуется увеличить large_client_header_buffers и изменить приложение, научив работать с кастомным заголовком. Также NGINX должен поддерживать Lua или Perl — стандартными средствами описать такое поведение не получится.

О работе gzip_no_buffer в NGINX

В сентябре вышел пост SRE Dropbox об оптимизации веб-серверов в их геораспределённой сети доставки контента. Я заинтересовался опцией gzip_no_buffer в NGINX для сокращения времени получения первого байта TCP-пакета с сервера (TTFB).

Этой опции нет в официальной документации, а единственное упоминание в книге NGINX HTTP Server Кле́мана Неде́льку вторит ответу Игоря Сысоева в почтовой рассылке 2006-го года:

By default Nginx waits until at least one buffer (defined by gzip_buffers) is filled with data before sending the response to the client. Enabling this directive disables buffering.

Из описания кажется, что опция отключает буферизацию, если ответ укладывается в один буфер, а если контент больше, то просто буферизирует вывод. Спешу огорчить: это не так.

if (conf->no_buffer && ctx->in == NULL) {

    cl = ngx_alloc_chain_link(r->pool);
    if (cl == NULL) {
        return NGX_ERROR;
    }

    cl->buf = ctx->out_buf;
    cl->next = NULL;
    *ctx->last_out = cl;
    ctx->last_out = &cl->next;

    return NGX_OK;
}

Если включен gzip_no_buffer, то заполняется один буфер, а затем возвращается клиенту без обработки следующей порции. В случае же превышения размера буфера, клиент получит ответ нулевой длины.

Иными словами, если размер буфера окажется недостаточным, веб-сервер не будет отдавать контент вовсе. Учитывайте эту особенность при конфигурировании NGINX. Выберите подходящее значение gzip_buffers для вашего сервиса, если планируете использовать опцию в будущем.

Я рекомендую не отключать буферизацию. Возможно, эта оптимизация и играет роль на больших объёмах трафика, однако в небольших проектах влияние на производительность будет малозаметным.

Версионируйте релизы правильно

Среди разработчиков свободного ПО есть мнение, что хеш коммита — хороший выбор для версии. Это не так:

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

Вот плохой пример: разработчики библиотеки x264 используют составной идентификатор из даты и номера снапшота (который почему-то не меняется с августа 2005-го года). Выходит, о потере совместимости нельзя будет узнать из номера версии.

Потому к версии этого пакета дописывают префикс — версию API из константы X264_BUILD из заголовочного файла x264.h. Это, на минуточку, популярный кодек H.264/MPEG-4 AVC в видеоплеере VLC и наборе библиотек FFmpeg.

Есть исключение: нумерация версий в проектах TeX и METAFONT. Каждое обновление добавляет дополнительную десятичную цифру в конце номера, ассимптотически приближая значение к числу Пи и Эйлера. Так Дональд Кнут хотел подчеркнуть, что добавление новой функциональности не планируется, а последующие изменения являются исправлениями ошибок.

Помните, что нумерация версий нужна не только машинам, но и людям. Выбирайте с умом.

Приватный репозиторий образов Vagrant

Для запуска виртуальной машины в Vagrant используют публичные образы или собирают собственный с помощью Packer. Компания HashiCorp предлагает использовать сервис Vagrant Cloud для хранения и версионирования этих образов, чтобы распространять обновления в команде разработчиков. Сервис бесплатный, но артефакты публичные и доступны каждому без авторизации.

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

Для решения этой проблемы написал vgrepo — простую программу командной строки для управления образами Vagrant. В паре с HTTP-сервером утилита упрощает доставку и обновление образов пользователям. Посмотрите скринкаст с демонстрацией работы:

asciicast

При запуске команд up, reload и resume Vagrant ищет метаданные образа в формате JSON. По ним Vagrant проверяет локальную версию и доступную в каталоге на сервере. Если у пользователя версия старше, чем в каталоге, то будет предложено обновить образ.

vgrepo призван помочь с управлением образов, сняв головную боль по поддержке метаданных. Данные об образах автоматически изменяются при добавлении и удалении их из каталога.

Пользователю достаточно указать ссылку на JSON в переменной config.vm.box_url. Обратите внимание, имя образа в config.vm.box должно совпадать с именем в метаданных:

Vagrant.configure("2") do |config|
  config.vm.box = "centos7-x64"
  config.vm.box_url = "https://vagrant.gongled.me/r/centos7-x64/"
end

На разработку проекта меня вдохновил туториал по развёртке приватного Vagrant Cloud, где подробно описан весь процесс. Если вам нравится проект и вы хотите помочь — присылайте ишьюсы и пул-реквесты на Гитхабе. С удовольствием читаю всё, что мне присылают.

Настройка RAID-контроллера SmartArray B120i в CentOS 7

Небольшая инструкция об установке CentOS 7 на HPE ProLiant Microserver Gen8 с аппаратным RAID-контроллером HP Dynamic Smart Array B120i:

Загрузите образ дискеты для RAID-контроллера с официального сайта Hewlett Packard. Запишите образ на USB-диск с помощью утилиты dd. Не забудьте заменить путь до блочного устройства /dev/sdd и отмонтировать устройство перед запуском:

gunzip < hpvsa-1.2.10-120.rhel7u0.x86_64.dd.gz | pv | sudo dd of=/dev/sdd

Запустите установку CentOS 7, на экране приглашения нажмите TAB, укажите опцию dd и отключите поддержку AHCI для использования драйвера от HP:

linux dd blacklist=ahci

Готово. Во время загрузки ядра будет предложено выбрать источник для драйвера. Выберите USB-диск и продолжите установку на логический диск.

Страницы: 1 2 3