company_banner

Cборка динамических модулей для Nginx

    image


    Недавно мы собирали динамический модуль для Nginx, а когда всё было уже готово, то выяснилось, что наш модуль оказался не совместимым с Nginx, который уже был установлен на сервере. Готового решения возникшей проблемы нам найти не удалось и мы начали бороться с ней самостоятельно. Мы потратили много времени, но получили новый опыт и, самое главное, работающее решение. Которым и хотелось бы поделиться.


    Начнём с описания процесса сборки динамического модуля на примере https://github.com/vozlt/nginx-module-vts. А затем покажем какая возникает ошибка и что с ней делать.


    Вводные данные:
    ОС Debian 9
    Nginx v1.10.3 из стандартного репозитория Debian. Вывод nginx -V:


    nginx version: nginx/1.10.3
    built with OpenSSL 1.1.0k  28 May 2019 (running with OpenSSL 
    1.1.1c  28 May 2019)
    TLS SNI support enabled
    configure arguments: --with-cc-opt='-g -O2 
    -fdebug-prefix-map=/build/nginx-DhOtPd/nginx-1.10.3=. 
    -fstack-protector-strong -Wformat -Werror=format-security 
    -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-z,relro 
    -Wl,-z,now' --prefix=/usr/share/nginx --conf-path=/etc/
    nginx/nginx.conf --http-log-path=/var/log/nginx/access.log 
    --error-log-path=/var/log/nginx/error.log 
    --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid 
    --modules-path=/usr/lib/nginx/modules 
    --http-client-body-temp-path=/var/lib/nginx/body 
    --http-fastcgi-temp-path
    =/var/lib/nginx/fastcgi 
    --http-proxy-temp-path=/var/lib/nginx/proxy 
    --http-scgi-temp-path=/var/lib/nginx/scgi 
    --http-uwsgi-temp-path=/var/lib/nginx/uwsgi 
    --with-debug --with-pcre-jit --with-ipv6 --with-http_ssl_module -
    -with-http_stub_status_module --with-http_realip_mod
    ule --with-http_auth_request_module --with-http_v2_module
    --with-http_dav_module --with-http_slice_module --with-threads 
    --with-http_addition_module --with-http_geoip_module=dynamic 
    --with-http_gunzip_module --with-http_gzip_static_module 
    --with-http_image_filter_module=
    dynamic --with-http_sub_module --with-http_xslt_module=dynamic 
    --with-stream=dynamic --with-stream_ssl_module --with-mail=dynamic 
    --with-mail_ssl_module 
    --add-dynamic-module=/build/nginx-DhOtPd/nginx-1.10.3/debian/modules/nginx-auth-pam 
    --add-dynamic-module=/build/nginx-DhOtPd/nginx-1.10.3/debian/modules/nginx-dav-ext-module 
    --add-dynamic-module=/build/nginx-DhOtPd/nginx-1.10.3/debian/modules/nginx-echo 
    --add-dynamic-module=/build/nginx-DhOtPd/nginx-1.10.3/debian/modules/nginx-upstream-fair 
    --add-dynamic-module=/build/nginx-DhOtPd/nginx
    -1.10.3/debian/modules/ngx_http_substitutions_filter_module

    В выводе этой команды есть информация, которая необходима для сборки динамических модулей, а именно, все что написано после configure arguments: и до первого --add-dynamic-module, то есть:


    --with-cc-opt='-g -O2 
    -fdebug-prefix-map=/build/nginx-DhOtPd/nginx-1.10.3=. 
    -fstack-protector-strong -Wformat -Werror=format-security 
    -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-z,relro 
    -Wl,-z,now' --prefix=/usr/share/nginx --conf-path=/etc/
    nginx/nginx.conf --http-log-path=/var/log/nginx/access.log 
    --error-log-path=/var/log/nginx/error.log 
    --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid 
    --modules-path=/usr/lib/nginx/modules 
    --http-client-body-temp-path=/var/lib/nginx/body 
    --http-fastcgi-temp-path
    =/var/lib/nginx/fastcgi 
    --http-proxy-temp-path=/var/lib/nginx/proxy 
    --http-scgi-temp-path=/var/lib/nginx/scgi 
    --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug 
    --with-pcre-jit --with-ipv6 --with-http_ssl_module 
    --with-http_stub_status_module --with-http_realip_mod
    ule --with-http_auth_request_module --with-http_v2_module 
    --with-http_dav_module --with-http_slice_module --with-threads 
    --with-http_addition_module --with-http_geoip_module=dynamic 
    --with-http_gunzip_module --with-http_gzip_static_module 
    --with-http_image_filter_module=
    dynamic --with-http_sub_module --with-http_xslt_module=dynamic 
    --with-stream=dynamic --with-stream_ssl_module 
    --with-mail=dynamic --with-mail_ssl_module

    Итак, приступаем к сборке модуля:


    • Для сборки удобно использовать docker контейнер, чтобы не захламлять основную систему, за основу возьмем образ debian:9, для удобства копирования готового модуля из контейнера можно указать volume. Команда для запуска контейнера в таком случае будет выглядеть так:

    docker run --rm -it -v /tmp/nginx_module:/nginx_module debian:9 bash

    • После запуска контейнера устанавливаем необходимые пакеты. Прошу заметить, что пакеты подобраны по выводу команды nginx -V, поэтому, возможно, часть библиотек Вам не понадобится. В любом случае, configure скрипт предупредит Вас об отсутствии зависимостей:

    apt update
    apt install git make gcc autoconf wget libpcre3-dev libpcre++-dev zlib1g-dev libxml2-dev libxslt-dev libgd-dev libgeoip-dev

    В данном списке отсутствует пакет libssl-dev, по той причине, что nginx установленный на сервере собран с версией OpenSSL 1.1.0k, а на момент написания статьи при установке пакета libssl-dev из репозитория, версия OpenSSL в нем была уже 1.1.0l. Поэтому, исходники OpenSSL нужной версии необходимо скачать отдельно.


    • Скачиваем с сайта http://nginx.org/download nginx нужной версии, в нашем случае это 1.10.3. Исходники OpenSSL скачиваем с официального сайта https://www.openssl.org/source/old и, собственно, исходный код самого модуля nginx-module-vts загружаем с github.

    cd /usr/local/src/
    wget http://nginx.org/download/nginx-1.10.3.tar.gz
    wget https://www.openssl.org/source/old/1.1.0/openssl-1.1.0k.tar.gz
    git clone git://github.com/vozlt/nginx-module-vts.git
    tar xvfz nginx-1.10.3.tar.gz
    tar xzvf openssl-1.1.0k.tar.gz
    cd nginx-1.10.3

    • Теперь все готово к запуску скрипта configure. В качестве параметров скрипта указываем все параметры из вывода команды nginx -V, о которых я писал в начале статьи. Также, добавляем параметр --add-dynamic-module для сборки модуля nginx-module-vts и указываем путь к директории с исходными файлами OpenSSL через параметр --with-openssl. Итоговая команда будет выглядеть так:

    ./configure --with-cc-opt='-g -O2 
    -fdebug-prefix-map=/build/nginx-DhOtPd/nginx-1.10.3=. 
    -fstack-protector-strong -Wformat -Werror=format-security 
    -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-z,relro 
    -Wl,-z,now' --prefix=/usr/share/nginx 
    --conf-path=/etc/nginx/nginx.conf 
    --http-log-path=/var/log/nginx/access.log 
    --error-log-path=/var/log/nginx/error.log 
    --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid 
    --modules-path=/usr/lib/nginx/modules 
    --http-client-body-temp-path=/var/lib/nginx/body 
    --http-fastcgi-temp-path=/var/lib/nginx/fastcgi 
    --http-proxy-temp-path=/var/lib/nginx/proxy 
    --http-scgi-temp-path=/var/lib/nginx/scgi 
    --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug 
    --with-pcre-jit --with-ipv6 --with-http_ssl_module 
    --with-http_stub_status_module --with-http_realip_module 
    --with-http_auth_request_module --with-http_v2_module 
    --with-http_dav_module --with-http_slice_module 
    --with-threads --with-http_addition_module 
    --with-http_geoip_module=dynamic --with-http_gunzip_module 
    --with-http_gzip_static_module 
    --with-http_image_filter_module=dynamic 
    --with-http_sub_module --with-http_xslt_module=dynamic 
    --with-stream=dynamic --with-stream_ssl_module 
    --with-mail=dynamic --with-mail_ssl_module 
    --with-openssl=../openssl-1.1.0k/ 
    --add-dynamic-module=../nginx-module-vts/ 

    • После завершения работы скрипта configure, запускаем сборку модулей, весь nginx нам собирать незачем:

    make modules

    Заметьте, данная команда доступна только с nginx версии 1.9.13.


    • После завершения процесса сборки, копируем полученный so файл в volume, который был смонтирован при старте контейнера :

    cp objs/ngx_http_vhost_traffic_status_module.so /nginx_module/

    • Далее размещаем файл на целевом сервере, в данном случае, каталог с модулями находится по пути /usr/share/nginx/modules. Для того, чтобы включить модуль в nginx создаем файл /etc/nginx/modules-enabled/mod-http-vhost-traffic-status.conf со следующим содержимым:

    load_module modules/ngx_http_vhost_traffic_status_module.so;

    И создаем символьную ссылку на этот файл в каталог /etc/nginx/modules-enabled.


    После проделанных работ, вызываем команду nginx -t и… получаем ошибку:


    nginx: [emerg] module "/usr/share/nginx/modules/ngx_http_vhost_traffic_status_module.so" is not binary compatible in /etc/nginx/modules-enabled/50-mod-http-vhost-traffic-status.conf:1

    И вот здесь обычно наступает некоторое недоумение, ведь версия исходников и версия nginx установленного на сервере одинаковые, версии OpenSSL тоже, параметры для скрипта configure так же скопированы целиком. Так в чем же дело?


    Чтобы разобраться в этой проблеме пришлось понять, как nginx проверяет подходит ли ему динамический модуль. За эту проверку отвечает код, который расположен в исходниках nginx в файле src/core/ngx_module.h (https://github.com/nginx/nginx/blob/master/src/core/ngx_module.h). В этом файле имеется некоторое количество проверок, в данном случае 34, при прохождении которых nginx задает для переменных вида NGX_MODULE_SIGNATURE_0 (1,2,3 и т.д.)
    значения 1 или 0. Далее следует переменная NGX_MODULE_SIGNATURE
    в которой собираются результаты всех проверок в одну строку. Соответственно, проблема заключается в том, что строка сигнатур нового модуля не соответветствует строке сигнатур имеющегося nginx. Чтобы проверить значения строк сигнатур можно воспользоваться следующими командами :


    • Строка сигнатур модуля, который был собран :

    strings /usr/share/nginx/modules/ngx_http_vhost_traffic_status_module.so| fgrep '8,4,8'
    8,4,8,000011111101011111111111110110111

    • Строка сигнатур бинарного файла nginx установленного на сервере:

    strings /usr/sbin/nginx| fgrep '8,4,8'
    8,4,8,000011111101011111111111110111111

    При сравнении этих строк невооруженным взглядом видно, что в строке сигнатур модуля, четвертая с конца переменная получилась 0, при том что у nginx она 1. Для понимания того, чего именно не хватает в модуле, необходимо обратиться к файлу src/core/ngx_module.h и найти четвертую переменную с конца, выглядит это, примерно, так :


    #if (NGX_HTTP_REALIP)
    #define NGX_MODULE_SIGNATURE_29  "1"
    #else
    #define NGX_MODULE_SIGNATURE_29  "0"
    #endif
    
    #if (NGX_HTTP_HEADERS)
    #define NGX_MODULE_SIGNATURE_30  "1"
    #else
    #define NGX_MODULE_SIGNATURE_30  "0"
    #endif
    
    #if (NGX_HTTP_DAV)
    #define NGX_MODULE_SIGNATURE_31  "1"
    #else
    #define NGX_MODULE_SIGNATURE_31  "0"
    #endif
    
    #if (NGX_HTTP_CACHE)
    #define NGX_MODULE_SIGNATURE_32  "1"
    #else
    #define NGX_MODULE_SIGNATURE_32  "0"
    #endif
    
    #if (NGX_HTTP_UPSTREAM_ZONE)
    #define NGX_MODULE_SIGNATURE_33  "1"
    #else
    #define NGX_MODULE_SIGNATURE_33  "0"
    #endif
    
    #define NGX_MODULE_SIGNATURE                                                  

    Нас интересует переменная NGX_HTTP_HEADERS, при сборке nginx она была 1, а при сборке модуля 0. Для того, чтобы модуль собирался с NGX_HTTP_HEADERS необходимо скорректировать файл config в директории с исходных кодом модуля, добавив строку have=NGX_HTTP_HEADERS . auto/have в начало файла config. Ниже представлено начало файла config после внесения изменений:


    ngx_addon_name=ngx_http_vhost_traffic_status_module
    have=NGX_STAT_STUB . auto/have
    have=NGX_HTTP_HEADERS . auto/have
    ...

    Далее делаем make clean, а затем заново запускаем configure и make modules. После сборки модуля проверяем его строку сигнатур и видим, что теперь она соответствует строке nginx:


    $ strings /usr/sbin/nginx| fgrep '8,4,8'
    8,4,8,000011111101011111111111110111111
    $ strings /usr/share/nginx/modules/ngx_http_vhost_traffic_status_module.so | fgrep '8,4,8'
    8,4,8,000011111101011111111111110111111

    После этого, модуль подключается без каких-либо проблем и можно собирать расширенную статистику с nginx.


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


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


    Также читайте другие статьи в нашем блоге:


    Nixys
    Эксперты в DevOps и Kubernetes

    Комментарии 5

      0
      Вот здесь сборка nginx с модулями автоматизирована habr.com/ru/company/tinkoff/blog/452336
        +3
        В указанной Вами статье nginx собирается вместе с модулями, иногда это удобно, но иногда уже имеется рабочий nginx на боевом сервере, к которому нужно добавить тот или иной модуль, моя статья именно про это.
        0
        А почему бы всю эту портянку сборки не обернуть в Dockerfile и в конце статьи показать готовый результат? Наверняка это не единичный случай, да и было бы нагляднее.
          +1
          В данном случае хотелось именно показать последовательность действий, да и команды всегда можно подставить в Dockerfile.
            0

            Более того, его можно и из чужого докер-образа вытащить в момент сборки своего, вполне обычная практика. Можно сравнить с использованием примесей.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое