Недавно мы собирали динамический модуль для 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
вместе с нужным модулем.
Также читайте другие статьи в нашем блоге:
- Введение в Hashicorp Consul’s Kubernetes Авторизацию
- К чему привела миграция с ClickHouse без авторизации на ClickHouse с авторизацией
- Blue-Green Deployment приложений на Spring c веб-сервером Nginx
- Разбираемся с пакетом Context в Golang
- Три простых приема для уменьшения Docker-образов
- Бэкапы Stateful в Kubernetes