СофтХабрахабр

[Перевод] Отлаживаем развертывание ПО со strace

Моя основная работа — это, по большей части, развертывание систем ПО, то есть уйму времени я трачу, пытаясь ответить на такие вот вопросы:

  • У разработчика это ПО работает, а у меня нет. Почему?
  • Вчера это ПО у меня работало, а сегодня нет. Почему?

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

И мой любимый инструмент для борьбы с проблемой типа "Почему это ПО у меня не пашет?" называется strace. Поэтому вместо обычных инструментов для отладки вроде gdb у меня есть другой набор инструментов для отладки развертывания.

Что же такое strace?

Изначально создавался под Linux, но те же фишки по отладке можно проворачивать и с инструментами для других систем (DTrace или ktrace). strace — это инструмент для "трассировки системного вызова".

Надо лишь запустить strace c любой командой и он отправит в дамп все системные вызовы (правда, сперва, наверное, придется установить сам strace): Основное применение очень просто.

$ strace echo Hello
...Snip lots of stuff...
write(1, "Hello\n", 6) = 6
close(1) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++

Это нечто вроде API для ядра операционной системы. Что это за системные вызовы? Если, например, нужно было отобразить что-нибудь на экране, оно играло с портами и\или отображаемыми в память регистрами для видео-устройств. Давным-давно у ПО был прямой доступ к "железу", на котором оно работало. Ошибки в одном приложении могли обрушить работу прочих, если не всю систему целиком. Когда стали популярны многозадачные компьютерные системы, воцарился хаос, потому что различные приложения дрались за "железо". Самым привилегированным становилось ядро: оно получало полный доступ к "железу", плодя менее привилегированные приложения, которым уже приходилось запрашивать доступ у ядра для взаимодействия с "железом" — через системные вызовы. Тогда в ЦПУ появились режимы привилегий (или "кольцевая защита").

Т.е. На бинарном уровне системный вызов немного отличается от простого вызова функции, однако большинство программ используют обертку в стандартной библиотеке. стандартная библиотека POSIX C содержит вызов функции write(), которая содержит весь зависящий от архитектуры код для системного вызова write.

Поэтому когда ПО на одно машине работает, а на другой нет, хорошо бы заглянуть в результаты трассировки системных вызовов. Если вкратце, то любое взаимодействие приложения со своей средой (компьютерными системами) осуществляется через системные вызовы. Если же конкретнее, то вот вам список типичных моментов, которые можно проанализировать при помощи трассировки системного вызова:

  • Консольный ввод-вывод
  • Сетевой ввод-вывод
  • Доступ к файловой системе и файловый ввод-вывод
  • Управление сроком жизни процесса\потока
  • Низкоуровневое управление памятью
  • Доступ к драйверам особых устройств

Когда использовать strace?

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

Обычно тут требуется комбинация strace, иногда ltrace и инструментов более высокго уровня (вроде инструмента командной строки openssl для отладки сертификата). Во всем блеске strace проявляет себя во время отладки ПО, которое хорошо работает на одной машине, а на другой вдруг перестает работать, выдавая невнятные сообщения о файлах, разрешениях или неудачных попытках выполнить какие-то команды или еще что… Жаль, но не так хорошо он сочетается с высокоуровневыми проблемами типа ошибки проверки сертификата.

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

Пример простой отладки

Скажем, хотите вы запустить потрясающее серверное приложение foo, а получается вот что:

$ foo
Error opening configuration file: No such file or directory

Так происходит, потому что иногда, когда менеджеры пакетов, компилируя приложение, переопределяют ожидаемое расположение файлов. Очевидно, у него не получилось найти написанный вами конфигурационный файл. Решить проблему можно было бы за пару секунд, если бы сообщение об ошибке говорило, где следует искать конфигурационный файл, но ведь оно же не говорит. И если следовать руководству по установке для одного дистрибутива, в другом находишь файлы совершенно не там, где ожидал. Так где искать?

Хороший запасной план, но не самое быстрое решение. Если есть доступ к исходному коду, можно прочесть его и все выяснить. Можно прибегнуть к пошаговому отладчику вроде gdb и посмотреть, что делает программа, но куда эффективнее использовать инструмент, который специально спроектирован показывать взаимодействие со средой: strace.

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

$ strace -o /tmp/trace foo
Error opening configuration file: No such file or directory
$ cat /tmp/trace
execve("foo", ["foo"], 0x7ffce98dc010 /* 16 vars */) = 0
brk(NULL) = 0x56363b3fb000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, ) = 0
mmap(NULL, 25186, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f2f12cf1000
close(3) = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260A\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1824496, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2f12cef000
mmap(NULL, 1837056, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f2f12b2e000
mprotect(0x7f2f12b50000, 1658880, PROT_NONE) = 0
mmap(0x7f2f12b50000, 1343488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x22000) = 0x7f2f12b50000
mmap(0x7f2f12c98000, 311296, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16a000) = 0x7f2f12c98000
mmap(0x7f2f12ce5000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b6000) = 0x7f2f12ce5000
mmap(0x7f2f12ceb000, 14336, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f2f12ceb000
close(3) = 0
arch_prctl(ARCH_SET_FS, 0x7f2f12cf0500) = 0
mprotect(0x7f2f12ce5000, 16384, PROT_READ) = 0
mprotect(0x56363b08b000, 4096, PROT_READ) = 0
mprotect(0x7f2f12d1f000, 4096, PROT_READ) = 0
munmap(0x7f2f12cf1000, 25186) = 0
openat(AT_FDCWD, "/etc/foo/config.json", O_RDONLY) = -1 ENOENT (No such file or directory)
dup(2) = 3
fcntl(3, F_GETFL) = 0x2 (flags O_RDWR)
brk(NULL) = 0x56363b3fb000
brk(0x56363b41c000) = 0x56363b41c000
fstat(3, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x8), ...}) = 0
write(3, "Error opening configuration file"..., 60) = 60
close(3) = 0
exit_group(1) = ?
+++ exited with 1 +++

(Много вызовов mmap, mprotect, brk для вещей типа обнаружения низкоуровневой памяти и отображение динамических библиотек.) Вообще-то, во время отладки выводы strace лучше читать с самого конца. Примерно вся первая страница вывода strace — это обычно низкоуровневая подготовка к запуску. Смотрим выше, и видим первый ошибочный системный вызов — вызов openat, выдающий ошибку ENOENT ("файл или каталог не найдены"), пытавшийся открыть /etc/foo/config.json. Внизу будет вызов write, выводящий сообщение об ошибке. Вот, здесь и должен лежать конфигурационный файл.

Ниже — полное пошаговое руководство по отладке: Это был просто пример, но я бы сказал, что 90% времени, что я пользуюсь strace, ничего сильно сложнее этого выполнять и не приходится.

  • Расстроиться из-за невнятного сообщения о system-y ошибке от программы
  • Заново запустить программу со strace
  • Найти в результатах трассировки сообщение об ошибке
  • Идти выше, пока не уткнетесь в первый неудачный системный вызов

Весьма вероятно, что системный вызов в 4-м шаге покажет, что пошло не так.

Подсказки

Прежде чем показать пример более сложной отладки, открою вам несколько приемов для эффективного использования strace:

man — ваш друг

Вы увидите вещи вроде brk(2), а значит, больше информации можно получить, запустив man 2 brk. На многих *nix системах полный список системных вызовов к ядру можно получить, запустив man syscalls.

Семантика вызова fork остается той же, если написать программу, использующую fork(), и запустить трассировку — я не найду вызовов fork, вместо них будут clone(). Небольшие грабли: man 2 fork показывает мне страницу для оболочки fork() в GNU libc, которая, оказывается, которая реализована с помощью вызова clone(). Такие вот грабли только путают, если начать сравнивать исходник с выводом strace.

Используйте -о, чтобы сохранить вывод в файл

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

Используйте -s, чтобы просмотреть больше данных аргумента

Это потому, что strace по умолчанию показывает только первые 32 байта аргумента строки. Вы наверняка заметили, что вторая половина сообщения об ошибке не показана в приведенном выше примере трассировки. Если хотите видеть больше, добавьте что-нибудь вроде -s 128 к вызову strace.

-у облегчает отслеживание файлов\сокетов\и проч.

Это удобно для программирования, но мешает следить за тем, что происходит на самом деле, когда видите общие read и write в результатах трассировки системного вызова. "Все — файл" означает, что *nix системы выполняют все вводы-выводы, используя файловые дескрипторы, применимо ли именно к файлу или сети, или межпроцессным каналам.

Добавив оператор , вы заставите strace аннотировать каждый файловый дескриптор в выводе с примечанием, на что он указывает.

Прикрепите к уже запущенному процессу с -p**

Если известно, что она запущена как процесс 1337 (скажем, из выводов ps), то можно осуществить трассировку ее вот так: Как будет видно из приведенного ниже примера, иногда нужно производить трассировку программы, которая уже запущена.

$ strace -p 1337
...system call trace output...

Возможно, вам нужны root-права.

Используйте -f, чтобы следить за дочерними процессами

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

Минус этого в том, что вывод еще больше собьет вас с толку. Если считаете, что ошибка — в дочернем процессе, используйте оператор -f, это включит его трассировку. Когда он трассирует сразу несколько процессов, то, вы, возможно, увидите начало вызова, прерванного сообщением <unfinished ...>, затем — кучку вызовов для других веток исполнения, и только потом — окончание первого с <… foocall resumed>. Когда strace трассирует один процесс или одну ветку, он показывает единый поток событий вызовов. Или разделите все результаты трассировки по разным файлам, используя также оператор -ff (детали — в руководстве по strace).

Фильтруйте трассировку при помощи -e

Флагом -e можно отфильтровать трассировку (см. Как видите, результат трассировки — реальная куча всех возможных системных вызовов. Главное преимущество в том, что запускать трассировку с фильтрацией быстрее, чем делать полную трассировку, а потом grep`ать. руководство по strace). Если честно, то мне почти всегда пофиг.

Не все ошибки плохи

Простой и распространенный пример — программа, ищущая файл сразу в нескольких местах, вроде оболочки, ищущей, в которой корзине\каталоге содержится исполняемый файл:

$ strace sh -c uname
...
stat("/home/user/bin/uname", 0x7ffceb817820) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/uname", 0x7ffceb817820) = -1 ENOENT (No such file or directory)
stat("/usr/bin/uname", {st_mode=S_IFREG|0755, st_size=39584, ...}) = 0
...

Как бы там ни было, логично начинать с самого конца. Эвристика типа "последний неудачный запрос перед сообщением об ошибке" хороша в поиске релевантных ошибок.

Понять системные вызовы хорошо помогают руководства по программированию на языке С

Так что, если вы хоть немного понимаете, как и что делать в С, вам будет легче разобраться в результатах трассировки системного вызова. Стандартные вызовы к библиотекам С — не системные вызовы, но лишь тонкий поверхностный слой. Например, у вас беда с отладкой вызовов к сетевым системам, просмотрите то же классическое "Руководство по сетевому программированию" Биджа.

Пример отладки посложнее

Однако порой требуется настоящее расследование, так что вот вам реальный пример отладки посложнее. Я уже говорил, что пример простой отладки — это пример того, с чем мне, по большей части, приходится иметь дело в работе со strace.

Он установлен на сервере, но когда кто-то пытается редактировать расписание, происходит вот что: bcron — планировщик обработки задач, еще одна реализация демона *nix cron.

# crontab -e -u logs
bcrontab: Fatal: Could not create temporary file

Расчехляем strace: Ладненько, значит, bcron попытался написать некий файл, но у него не вышло, и он не признается почему.

# strace -o /tmp/trace crontab -e -u logs
bcrontab: Fatal: Could not create temporary file
# cat /tmp/trace
...
openat(AT_FDCWD, "bcrontab.14779.1573691864.847933", O_RDONLY) = 3
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f82049b4000
read(3, "#Ansible: logsagg\n20 14 * * * lo"..., 8192) = 150
read(3, "", 8192) = 0
munmap(0x7f82049b4000, 8192) = 0
close(3) = 0
socket(AF_UNIX, SOCK_STREAM, 0) = 3
connect(3, {sa_family=AF_UNIX, sun_path="/var/run/bcron-spool"}, 110) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f82049b4000
write(3, "156:Slogs\0#Ansible: logsagg\n20 1"..., 161) = 161
read(3, "32:ZCould not create temporary f"..., 8192) = 36
munmap(0x7f82049b4000, 8192) = 0
close(3) = 0
write(2, "bcrontab: Fatal: Could not creat"..., 49) = 49
unlink("bcrontab.14779.1573691864.847933") = 0
exit_group(111) = ?
+++ exited with 111 +++

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

Как узнать, что представляет файловый дескриптор 3? Если посмотреть на man 2 read, то можно увидеть, что первый аргумент (3) — это файловый дескриптор, который *nix и использует для всех обработок ввода-вывода. выше), и он автоматически расскажет, однако, чтобы вычислять подобные штуки, полезно знать, как читать и анализировать результаты трассировки. В данном конкретном случае можно запустить strace с оператором (см.

ищем "= 3" в результатах трассировки). Источником файлового дескриптора может быть один из многих системных вызовов (все зависит от того, для чего дескриптор — для консоли, сетевого сокета, собственно файла или чего-то иного), но как бы там ни было, вызовы мы ищем, возвращая 3 (т.е. openat открывает файл, но close(3) после этого покажет, что он закрывается снова. В этом результате их 2: openat в самом верху и socket в середине. Вызов socket() подходит, поскольку он последний перед read(), и получается, что bcrontab работает с чем-то через сокет. (Грабли: файловые дескрипторы могут использоваться заново, когда их открывают и закрывают). Следующая строка показывает, что файловый дескриптор связан с unix domain socket по пути /var/run/bcron-spool.

Для этой цели есть парочка изящных трюков, и оба пригодятся для отладки серверных развертываний. Итак, надо найти процесс, привязанный к unix socket с другой стороны. Обе команды показывают активные сетевые соединения системы и берут оператор -l для описания слушающих сокетов, а также оператор -p для отображения программ, подключенных к сокету в качестве клиента. Первый — использовать netstat или более новый ss (socket status). (Полезных опций намного больше, но для этой задачи достаточно и этих двух.)

# ss -pl | grep /var/run/bcron-spool
u_str LISTEN 0 128 /var/run/bcron-spool 1466637 * 0 users:(("unixserver",pid=20629,fd=3))

(И, по совпадению, она использует файловый дескриптор 3 в качестве сокета.) Это говорит о том, что слушающий — это команда inixserver, работающая с ID процесса 20629.

Он перечисляет все октрытые файлы (или файловые дескрипторы) в системе. Второй реально полезный инструмент для нахождения той же информации называется lsof. Или же можно получить информацию об одном конкретном файле:

# lsof /var/run/bcron-spool
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
unixserve 20629 cron 3u unix 0x000000005ac4bd83 0t0 1466637 /var/run/bcron-spool type=STREAM

Если редактировать задание cron в другом терминале — получим вывод результатов трассировки с возникающей ошибкой. Процесс 20629 — это долгоживущий сервер, так что можно прикрепить к нему strace при помощи чего-то вроде strace -o /tmp/trace -p 20629. А вот и результат:

accept(3, NULL, NULL) = 4
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21181
close(4) = 0
accept(3, NULL, NULL) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=21181, si_uid=998, si_status=0, si_utime=0, si_stime=0} ---
wait4(0, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG|WSTOPPED, NULL) = 21181
wait4(0, 0x7ffe6bc36764, WNOHANG|WSTOPPED, NULL) = -1 ECHILD (No child processes)
rt_sigaction(SIGCHLD, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, 8) = 0
rt_sigreturn({mask=[]}) = 43
accept(3, NULL, NULL) = 4
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21200
close(4) = 0
accept(3, NULL, NULL) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=21200, si_uid=998, si_status=111, si_utime=0, si_stime=0} ---
wait4(0, [{WIFEXITED(s) && WEXITSTATUS(s) == 111}], WNOHANG|WSTOPPED, NULL) = 21200
wait4(0, 0x7ffe6bc36764, WNOHANG|WSTOPPED, NULL) = -1 ECHILD (No child processes)
rt_sigaction(SIGCHLD, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, 8) = 0
rt_sigreturn({mask=[]}) = 43
accept(3, NULL, NULL

Мы не видим ни одного сообщения, которые бы bcrontag посылал сокету или принимал от него. (Последний accept() не будет завершен при трассировке.) И снова, как ни жаль, но данный результат не содержит ошибки, которую мы ищем. И если надо поймать ее след, добавьте к вызову strace -f. Вместо них сплошное управление процессом (clone, wait4, SIGCHLD и проч.) Этот процесс порождает дочерний процесс, который, как можно догадаться, и выполняет настоящую работу. Вот что мы найдем, поискав сообщение об ошибке в новом результате со strace -f -o /tmp/trace -p 20629:

21470 openat(AT_FDCWD, "tmp/spool.21470.1573692319.854640", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EACCES (Permission denied) 21470 write(1, "32:ZCould not create temporary f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logs:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (No such file or directory)
21470 exit_group(111) = ?
21470 +++ exited with 111 +++

Процесс 21470 получает ошибку "отказано в доступе" при попытке создать файл по пути tmp/spool. Вот, это уже кое-что. 1573692319. 21470. Если бы мы просто знали текущий рабочий каталог, мы бы знали и полный путь и смогли выяснили, почему процесс не может создать в нем свой временный файл. 854640 (относящийся к текущему рабочему каталогу). (Если таких нет, PID 21470, должно быть, унаследовали их от родителя, и это уже через lsof -p не выяснить.) Этот системный вызов — chdir (что несложно выяснить при помощи современных сетевых поисковых движков). К несчастью, процесс уже вышел, поэтому не получится просто использовать lsof -p 21470 для того, чтобы найти текущий каталог, но можно поработать в обратную сторону — поискать системные вызовы PID 21470, меняющие каталог. А вот и результат обратных поисков по результатам трассировки, до самого сервера PID 20629:

20629 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21470
...
21470 execve("/usr/sbin/bcron-spool", ["bcron-spool"], 0x55d2460807e0 /* 27 vars */) = 0
...
21470 chdir("/var/spool/cron") = 0
...
21470 openat(AT_FDCWD, "tmp/spool.21470.1573692319.854640", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EACCES (Permission denied) 21470 write(1, "32:ZCould not create temporary f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logs:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (No such file or directory)
21470 exit_group(111) = ?
21470 +++ exited with 111 +++

21470. (Если теряетесь, то вам, возможно, стоит прочесть мой предыдущий пост об управлении процессом *nix и оболочках.) Итак, сервер PID 20629 не получил разрешения создать файл по пути /var/spool/cron/tmp/spool. 854640. 1573692319. Проверим: Вероятнее всего, причиной тому — классические настройки разрешений файловой системы.

# ls -ld /var/spool/cron/tmp/
drwxr-xr-x 2 root root 4096 Nov 6 05:33 /var/spool/cron/tmp/
# ps u -p 20629
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
cron 20629 0.0 0.0 2276 752 ? Ss Nov14 0:00 unixserver -U /var/run/bcron-spool -- bcron-spool

Сервер работает как пользовательский cron, но только у root есть разрешение записывать в каталог /var/spool/cron/tmp/. Вот где собака зарыта! (Если проблема заключалась не в этом, то следующий наиболее вероятный подозреваемый — модуль безопасности ядра типа SELinux или AppArmor, так что я бы проверил журнал сообщений ядра с помощью dmesg.) Простая команда chown cron /var/spool/cron/tmp/ заставит bcron работать правильно.

Итого

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

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть