From: Андрей Синицын <http://boombick.org>
Date: Mon, 12 Dec 2007 14:31:37 +0000 (UTC)
Subject: Локализация внутренних проблем Apache
Оригинал: http://boombick.org/blog/posts/24
Веб-сервер Apache установлен на большинстве серверов в мире. Его
выбирают из-за отличной настраиваемости, расширяемости и превосходной
функциональности. Однако большое количество модулей и, к сожалению, не
всегда качественно написаные приложения порождают проблемы, которые
бывает непросто решить. Поэтому очень важно иметь под рукой
инструментарий для быстрой локализации и устранения неполадки. В этой
статье я постараюсь помочь вам.
Запустите сервер в единственном экземпляре
Другими словами, запретите ему форкаться :)
При локализации причины сбоя это самый первый и необходимый шаг.
Обычно apache порождает несколько своих копий при запуске, которые
ожидают запросов от пользователей. Запуск сервера в единственном
экземпляре облегчит отладку. Есть два способа запустить "одиночный"
сервер: первый заключается в ручном изменении количества форков и
нитей в конфиг-файле. Второй проще, можно запустить сервер с опцией
"-X"
# httpd -X
В таком режиме работы сервер не порождает новых форков и не теряет
связи с родительским терминалом. Благодаря этому все коммуникации идут
через один процесс, к которому легко можно подключить какой-нибудь
отладчик (например, gdb)
Проверка конфигурационного файла
Для некоторых веб-проектов конфигурационные файлы Apache разрастаются
в геометрической прогрессии и локализовать ошибки в них становится все
труднее. Сам сервер предлагает два способа решения этой проблемы.
Первый - это запуск сервера с ключом -t, который активирует проверку
конфигурационного файла:
$ httpd -t -c httpd.conf
Syntax error on line 236 of /etc/httpd/httpd.conf: LogLevel
requires level keyword: one of
emerg/alert/crit/error/warn/notice/info/debug
Если ошибка найдена, то apache предложит возможные способы ее
устранения. В приведенном примере неверно задано значение для
директивы LogLevel.
Если проверка говорит, что с конфигами все в порядке, но сервер, тем
не менее, не стартует, то можно включить вывод более подробной
информации в error_log: для этого установите директиву LogLevel в
значение Debug в конфигурационном файле.
Отладка cgi-сценариев
Apache позволяет выполнять сценарии, написанные практически на любом
языке, при помощи mod_cgi. Эти сценарии могут формировать динамический
контент по запросу клиента. Но вот локализация ошибок в подобных
программах может стать не очень тривиальной задачей.
Один из способов отладки таких скриптов - это директива ScriptLog
модуля mod_cgi. Когда директива активирована, то сервер записывает в
лог-файл вывод любого скрипта, работа которого завершилась неудачно.
Вывод включает в себя код ответа сервера, запрос от клиента и ответ
клиенту (если он имел место быть). Это великолепный отладочный
инструмент для сценариев, которые не пишут ошибки в "традиционный"
error_log или для быстрого выяснения проблемы, когда "что-то не
работает"
Для активирования логирования CGI-скриптов необходимо добавить
директиву ScriptLog и указать расположение лог-файла в
конфигурационном файле Apache. Когда логирование включено, mod_cgi
пишет примерно вот такие логи:
%% [Tue Dec 26 14:47:21 2006] GET /cgi-bin/print HTTP/1.1
%% 500 /var/tmp/apache/cgi-bin/print
%request
Accept: */*
Accept-Language: en
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en)
AppleWebKit/418.9.1 (KHTML, like Gecko) Safari/419.3
Connection: keep-alive
Host: 192.168.1.3:8080
%response
Script /cgi-bin/print
%stdout
Script Location /var/tmp/apache/cgi-bin/print
Для нагруженных веб-сайтов или нехватке дискового пространства размер
лога можно ограничивать с помощью директивы ScriptLogLength
Отладка запросов и ответов
При поддержке веб-приложений необходимо четко локализовать место
ошибки: это может сервер приложений, прокси-сервер или же сам
веб-сервер. Для облегчения этого процесса хорошо помогает полный дамп
диалога сервера с клиентом (то есть все запросы клиента и ответы
сервера). Модуль mod_dumpio предназначен как раз для записи заголовков
запроса и ответа в error_log. Настройка mod_dumpio выполняется очень
быстро и просто: для логирования запросов директива DumpIOInput должна
быть установлена в значение On; для логирования ответов установите в
On директиву DumpIOOutput. Когда логирование активно, то в error_log
появятся примерно вот такие строки:
Sat Oct 28 10:52:31 2006] [debug] mod_dumpio.c(103): mod_dumpio:
dumpio_in [getline-blocking] 0 readbytes
[Sat Oct 28 10:52:31 2006] [debug] mod_dumpio.c(51): mod_dumpio:
dumpio_in (data-HEAP): 16 bytes
[Sat Oct 28 10:52:31 2006] [debug] mod_dumpio.c(67): mod_dumpio:
dumpio_in (data-HEAP): GET / HTTP/1.1rn
< ..... >
Модуль mod_dumpio создает небольшую дополнительную нагрузку при
обработке запросов и включение/выключение директив потребует его
перезапуска. Вы можете мягкую перезагрузку (graceful restart) чтобы не
потерять активных соединений.
Локализация причин "зависания" сервера
Apache прекрасно оттестирован, но, тем не менее, ошибки все равно
есть. Они могут выражаться в "подвисаниях" сервера, внезапных
остановках или в неверном контенте, переданном пользователю. Когда
случаются подобные ситуации, главное - это быстро найти модуль,
который вызвал ошибку.
Один из способов локализации ошибок такого рода - это просмотр стека
вызовов подвисшего процесса. Стек вызовов содержит список функций,
вызванных до той, которая породила ошибка. После зависания вам,
вероятно, захочется узнать имя некорректно работающей функции, хотя бы
для того, чтобы использовать его в качестве аргумента при поиске
ошибки в багтрекерах и в качестве отправной точки при анализе кода
сбойного модуля.
Одна из утилит, позволяющих получить такой стек, называется pstack.
Она возвращает стек вызовов для процесса, номер которого передается ей
в качестве аргумента.
На платформах, где нет pstack можно воспользоваться утилитами gdb и
gcore для получения стека вызовов. Для подключения к процессу напрямую
и получению стека вызовов gdb должен быть запущен с ключом -p,
идентификатором процесса и вызовом backtrace уже внутри оболочки
отладчика
#0 0 *0046e7a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
#1 0 *0063b681 in accept () from /lib/tls/libpthread.so.0
#2 0 *00b14814 in apr_socket_accept (new=0xbff85740, sock=0 *9671538,
connection_context=0 *97115d8) at network_io/unix/sockets.c:187
#3 0 *080819ce in unixd_accept (accepted=0xbff85774, lr=0 *9671518,
ptrans=0 *97115d8) at unixd.c:466
#4 0 *0807fd2e in child_main (child_num_arg=Variable
"child_num_arg" is not available.) at prefork.c:621
#5 0 *0807ffc2 in make_child (s=Variable "s" is not available.) at prefork.c:736
#6 0 *08080050 in startup_children (number_to_start=5) at prefork.c:754
#7 0 *0808089b in ap_mpm_run (_pconf=0 *96730a8, plog=0 *96a1160,
s=0 *9674f48) at prefork.c:975
#8 0 *08061b08 in main (argc=3, argv=0xbff85a84) at main.c:717
Каждый дамп стека вызовов содержит несколько фрагментов отладочной
информации, однако для полноценного устранения проблемы необходимо
бОльшее количество информации и выборочный дамп структур данных
сервера. Это может оказаться неприемлемым для проектов, не позволяющих
даже кратковременных остановок. В этом слкчае на помощь приходит
утилита gcore. Она может сгенерировать core-файл для зависшего
процесса, который может быть использован для анализа ситуации
системными администраторами и производителями операционных систем. В
примере показано использование gcore для получения core-файла и
использование gdb для анализа полученной информации:
#0 0 *0046e7a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
#1 0 *0063b681 in accept () from /lib/tls/libpthread.so.0
#2 0 *00b14814 in apr_socket_accept (new=0xbff85740, sock=0 *9671538,
connection_context=0 *97115d8) at network_io/unix/sockets.c:187
#3 0 *080819ce in unixd_accept (accepted=0xbff85774, lr=0 *9671518,
ptrans=0 *97115d8) at unixd.c:466
#4 0 *0807fd2e in child_main (child_num_arg=Variable
"child_num_arg" is not available.) at prefork.c:621
#5 0 *0807ffc2 in make_child (s=Variable "s" is not available.) at prefork.c:736
#6 0 *08080050 in startup_children (number_to_start=5) at prefork.c:754
#7 0 *0808089b in ap_mpm_run (_pconf=0 *96730a8, plog=0 *96a1160,
s=0 *9674f48) at prefork.c:975
#8 0 *08061b08 in main (argc=3, argv=0xbff85a84) at main.c:717
В приведенных выше примерах можно увидеть, что выполнялась функция
accept(), когда был получен системный сигнал SIGSEGV, которая, в свою
очередь, была вызвана из метода apr_socket_accept(). Если эта проблема
известна и описана, то выможете использовать эту информацию для поиска
по базам данных ошибок. Для более подробной информации обратитесь к
официальной документации.
Локализация причин аварийных остановок сервера
Если процесс Apache аварийно завершился, то pstack и gcore будут
бесполезны, так как процесс уже завершен. Теоретически, core-файл
должен записываться в директорию, определенную в ServerRoot, но у
пользователя, под которым запущен сервер, должны быть права на запись
в эту директорию, должно быть достаточное количество дискового
пространства. Если политики безопасности запрещают запись в
ServerRoot, то можно определить местоположение core-файла при помощи
директивы CoreDumpDirectory
На некоторых платформах нет возможности автоматически генерировать
core-файл. Если вы используете именно такую платформу, то вам поможет
модуль mod_backtrace. Этот модуль срабатывает при получении любого
сигнала, останавливающего работу приложения (например SIGSEGV, SIGBUS
итд.) При получении подобного сигнала mod_backtrace вызывает
платформо-зависимую функцию для генерации стека вызовов и пишет его в
error_log.
Этот модуль распространяется в исходных кодах и может быть загружен с
сайта Apache
Для установки и подключения модуля утилита Apache apxs c ключом -c для
сборки модуля и с ключом -i для его установки.
$ apxs -ci mod_backtrace.c
Для использования mod_backtrace apache должен быть собран с поддержкой
ExceptionHook. Для этого можно указать "-enable-exception-hook" при
сборке сервера. Когда модуль установлен, загружен и exception hook
активен, то активировать модуль можно при помощи директивы LoadModule
и установки директивы EnableExceptionHook в значение On:
LoadModule backtrace_module modules/mod_backtrace.so
EnableExceptionHook On
После активации модуля при получении сервером критического сигнала в
error_log окажутся похожие строки:
$ kill -SIGSEGV 18745
[Wed Dec 20 16:48:32 2006] pid 18745 mod_backtrace backtrace for
sig 11 (thread "pid" 18745)
[Wed Dec 20 16:48:32 2006] pid 18745 mod_backtrace main() is at 26bc8
/var/tmp/apache/modules/mod-backtrace.so:bt_exception_hook+0 *108
/var/tmp/apache/bin/httpd:ap_run_fatal_exception+0 *34
/var/tmp/apache/bin/httpd:0 *32788
/lib/libc.so.1:0xc01dc
/lib/libc.so.1:0xb52d4
/lib/libc.so.1:_so_accept+0 *8 [ Signal 11 (SEGV)]
/var/tmp/apache/bin/httpd:unixd_accept+0 *10
/var/tmp/apache/bin/httpd:0 *3a3c0
/var/tmp/apache/bin/httpd:0 *3a6c8
/var/tmp/apache/bin/httpd:0 *3a798
/var/tmp/apache/bin/httpd:ap_mpm_run+0 *9d4
/var/tmp/apache/bin/httpd:main+0 *710
/var/tmp/apache/bin/httpd:_start+0 *5c
[Wed Dec 20 16:48:32 2006] pid 18745 mod_backtrace end of backtrace
mod_backtrace использует backtrace() на GNU системах и printstack() на
платформах Solaris для получения корректного стека вызовов. В
некоторых случаях дамп может содержать некорректные символы. В этом
случае вам помогут утилиты nm, readelf и elfdump. Для использования
mod_backtrace на хостах с Solaris на него необходимо наложить патч
(ссылка на патч приведена в конце статьи)
Отладка в "режиме разработчика" и макросы gdb для apache
Отладка на основе core-файлов - это искусство, требующее недюжинных
знаний отлаживаемого кода и технологий его написания и сопровождения.
Разработчики Apache это понимают и поэтому они реализовали несколько
отличных инструментов для отладки сервера и включили два ключевых
приема отладки в сервер. Первый - это так называемый "режим
разработчика" (maintainer mode). Для его активации необходимо собрать
сервер с ключом -enable-maintainer-mode. Этот режим включает
отладочную информацию для всех функций сервера, в нем отсутствует
оптимизация и исходный код на С может быть показан при помощи
дебаггера.
Второй ключевой момент - это gdb-макросы. С их помощью можно увидеть
содержмое сложных структур данных apache (например цепочек фильтров),
эти сведения очень полезны при анализе core-файлов, полученных после
сбоя сервера. Эти макросы находятся в файле .gdbinit, который
расположен в корневой директории с исходными кодами сервера. Если вы
хотите больше узнать о этих макросах, то в конце статьи приведены
ссылки.
Использование исходного кода Apache для устранения проблем
Когда анализ лог-файлов, core-файлов и использование системных утилит
не приносит результатов, можно обратиться напрямую к исходным кодам
сервера. Исходный код помещен в четкую иерархическую структуру и
каждый каталог содержит код конкретного объекта. В релизе 2.2.3 в
дереве содержатся следующие каталоги:
* $SRCROOT/server - ядро сервера
* $SRCROOT/include - заголовочные файлы
* $SRCROOT/srclib/apr - окружение среды выполнения
* $SRCROOT/srclib/apr-util - утилиты среды выполнения
* $SRCROOT/support - утилиты
* $SRCROOT/modules - модули
Если стек вызовов или сессия отладки показывают на проблему со
специфичной функцией, то можно найти ее описание в дереве исходных
кодов при помощи утилиты find
В приведенном пример прототип функции находится в файле apr_tables.h,
а описание в apr_tables.c. Но помните, что в зависимости от того, что
вы ищете (функцию или макрос) результаты поиска могут потребовать
дополнительной обработки.
Полезные ссылки
[[http://httpd.apache.org/ Официальный сайт Apache
[[http://prefetch.net/presentations/Apache_Presentation.pdf Внутреннее устройство и отладка Apache
[[http://www.cs.virginia.edu/~jcw5q/talks/apache/apache2moddebugging.ppt Устройство модулей в Apache2
[[http://people.apache.org/~trawick/exception_hook.html Модуль mod_backtrace
[[http://prefetch.net/code/mod-backtrace.diff Патч для модуля mod_backtrace для использования на платформе Solaris