Диагностика в Linux: ss, strace, lsof
Этот блок ближе всего к живой работе: вам описывают симптом и ждут порядок действий с конкретными командами. Ответ «посмотрю логи» не проходит.
Стержень: инструмент выбирается по симптому - занят порт смотрят по сокетам, зависший без нагрузки процесс по системным вызовам, исчезнувшее место по дескрипторам.
// Формулировки: «кто занял порт?», «процесс не отвечает, процессор ноль», «после ротации логи пишутся в никуда»
Кто занял порт и кто держит файл
Вопрос «кто слушает порт» решается одной командой, показывающей слушающие сокеты вместе с процессом-владельцем. Сокет - это конец сетевого соединения со стороны программы; слушающий сокет и есть открытый порт, на котором сервис ждёт входящих. Вот кусок настоящего вывода с моего сервера: строки с адресом 0.0.0.0:443 и 0.0.0.0:22 - это службы, доступные снаружи, а строка 127.0.0.53%lo:53 - служба, слушающая только на самой машине.
И вот тут прячется половина случаев «снаружи не подключается». Адрес 0.0.0.0 означает «на всех сетевых интерфейсах», а 127.0.0.1 - «только внутри этой машины». Если сервис слушает второй адрес, снаружи к нему не подключиться никогда, и правка правил межсетевого экрана не поможет: чинить надо настройку самого сервиса.
// Похожая команда отвечает на вопрос «кто держит файл»: она же находит удалённые файлы, из-за которых не освобождается место, - у них в списке открытых файлов процесса стоит пометка об удалении. Если владельца порта найти не удалось вовсе, порт может держать не процесс, а остаток закрытого соединения, который система придерживает пару минут.
- адрес прослушивания
- 0.0.0.0 - отовсюду, 127.0.0.1 - только с этой машины
- владелец сокета
- процесс, который держит порт; ищется одной командой, а не перебором
Процесс висит, а процессор ноль
Нулевая нагрузка на процессор говорит важную вещь: процесс не крутится, а ждёт. Значит, вопрос не «что он считает», а «чего он ждёт», и отвечает на него последний системный вызов - обращение процесса к ядру.
Инструмент для этого - трассировка системных вызовов живого процесса: она покажет, стоит ли он на чтении из сокета, на блокировке или на записи. Второй источник - файл в системной папке процесса, где ядро пишет имя функции, в которой заснул поток.
// А профилировщик тут не поможет: он ищет горячий код, а горячего кода нет вовсе, процесс спит. Это частая ошибка - взять инструмент под другой симптом. Дальше действия ветвятся: если ждёт сокет, проверяю соединение и таймауты второй стороны; если ждёт блокировку, ищу, кто её держит, и нет ли взаимной блокировки; если диск, смотрю статистику устройств и системные сообщения.
- трассировка системных вызовов
- показывает, на каком обращении к ядру стоит живой процесс
- профилировщик не для ожидания
- он ищет горячий код; у спящего процесса его нет
Ротация логов: куда уходят строки
Замер, объясняющий формулировку «после ротации логи пишутся в никуда». Открыл файл на запись, записал строку - номер файла 4484922. Дальше сделал то, что по умолчанию делает штатная программа ротации: переименовал файл и создал на его месте новый пустой (номер 4484923). Записал вторую строку и посмотрел, где она оказалась.
В новом файле - пусто. Обе строки оказались в переименованном. Причина в том, что открытый дескриптор привязан к самому файлу, а не к имени: переименование для процесса вообще ничего не значит, он продолжает писать туда же, куда писал.
// Отсюда два рабочих варианта в настройке ротации. Первый - копировать содержимое и обрезать файл на месте, не создавая новый: дескриптор остаётся валидным. Минус - между копированием и обрезкой можно потерять строки. Второй - после ротации послать сервису сигнал переоткрыть лог: так делает, например, nginx. И проверять надо не настройку, а факт: после ротации место должно освободиться, а новый файл - расти.
- переименование не меняет дескриптор
- процесс пишет в тот же файл, как бы тот ни назывался
- два способа ротации
- копировать и обрезать на месте либо сигналом заставить переоткрыть лог
Как отвечать: «Сервис не отвечает, процессор ноль. С чего начнёшь?»
Раз процессор ноль, процесс не работает, а ждёт - значит, интересен последний системный вызов, то есть последнее обращение к ядру. Ставлю трассировку системных вызовов на этот процесс и смотрю файл, где ядро пишет функцию, в которой заснул поток: обычно сразу видно, стоит ли он на чтении из сокета, на блокировке или на вводе-выводе. Дальше ветвлюсь по картине. Если сеть - проверяю соединение, отвечает ли вторая сторона, какие у клиента таймауты. Если блокировка - ищу, кто её держит, и нет ли взаимной блокировки двух потоков. Если диск - смотрю статистику по устройствам и системные сообщения ядра, там обычно видно проблемное устройство. Профилировщик в такой ситуации бесполезен: он ищет горячий код, а его нет, процесс спит. И параллельно проверяю банальное - на каком адресе сервис слушает: если он поднялся только на локальном адресе, снаружи к нему не подключится никто, сколько ни правь межсетевой экран.
Ответ показывает выбор инструмента под симптом и ветвление по результату, а не заученный список утилит.
На чём валятся
- −Ищут владельца порта перебором процессов вместо команды по сокетам.
- −Ставят профилировщик на процесс, который простаивает в ожидании: горячего кода там нет.
- −Настраивают ротацию и не проверяют факт - обе строки ушли в переименованный файл, новый остался пустым.
- −Забывают посмотреть адрес прослушивания и чинят межсетевой экран вместо настройки сервиса.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 6, остальные разбираются в тренажёре.
- Процесс завис: CPU 0%, ответов нет, логи молчат. Чем понять, где именно он стоит?A)perf top по этому PIDB)top в режиме потоков (клавиша H)C)strace -p PID и /proc/PID/wchanD)kill -QUIT и разбор дампа потоков
показать ответ и разбор
+C)strace -p PID и /proc/PID/wchan// разбор: Нулевой CPU означает, что процесс не крутится, а ждёт, — значит, интересен последний системный вызов. strace -p покажет, на чём он стоит (read из сокета, futex, flock), /proc/PID/wchan — функцию ядра, в которой заснул поток. Профилировщики ищут горячий код и при простое показывают пустоту.
- После ротации логов сервис пишет в никуда, и место на диске не освободилось. Почему?A)logrotate переименовал файл, а процесс держит дескриптор на старый inodeB)logrotate сжал файл, и процесс дописывает в архивC)На новый файл не хватило прав, запись отвергаетсяD)Файл открыт в режиме дописывания, а после ротации нужен режим перезаписи
показать ответ и разбор
+A)logrotate переименовал файл, а процесс держит дескриптор на старый inode// разбор: Приложение держит открытым дескриптор на inode, а не на имя. logrotate по умолчанию переименовывает файл и создаёт новый — процесс продолжает писать в старый inode: в архив либо в удалённый файл, чьи блоки не освободятся, пока открыт fd. Поэтому в конфиге ставят copytruncate (копия и обрезка на месте) или postrotate с сигналом приложению переоткрыть лог, как USR1 у nginx.
- Сервис под нагрузкой начал сыпать «Too many open files». Куда смотреть первым?A)На разделе кончились inode, из-за этого и не открываются файлыB)Это утечка памяти под нагрузкой, надо смотреть рост RSSC)В лимит дескрипторов: ulimit -n, /proc/PID/limits, LimitNOFILE юнитаD)Кончились сетевые порты, надо расширить ephemeral-диапазон
показать ответ и разбор
+C)В лимит дескрипторов: ulimit -n, /proc/PID/limits, LimitNOFILE юнита// разбор: «Too many open files» — процесс упёрся в лимит открытых дескрипторов (RLIMIT_NOFILE), а это и файлы, и сокеты, и pipes. Проверяют текущий лимит (ulimit -n или /proc/PID/limits), сколько открыто (ls /proc/PID/fd | wc -l, lsof) и нет ли утечки (fd растёт и не закрывается). Поднимают лимит: для сервиса systemd — LimitNOFILE= в юните, а не ulimit из интерактивного шелла (демона он не касается).
- Процесс периодически кто-то убивает: он просто исчезает без паники в своих логах. Как узнать, кто и почему?A)В dmesg / journalctl -k — там ядро пишет про OOM и сигналыB)Повесить strace на этот процесс уже после того, как он умерC)Смотреть только в лог самого приложения, там будет причинаD)Если приложение ничего не записало, копать причину уже негде
показать ответ и разбор
+A)В dmesg / journalctl -k — там ядро пишет про OOM и сигналы// разбор: Когда процесс убивают SIGKILL (в т.ч. OOM-killer), сам он ничего не логирует — сигнал не перехватывается. Но ядро оставляет запись: dmesg -T или journalctl -k покажут «Out of memory: Killed process PID (name)» с деталями (сколько памяти, из какой cgroup). Если это не OOM, а внешний kill — помогает auditd (правило на kill). Начинают всегда с dmesg.
- Приложение под нагрузкой упирается в потолок; профилировать в отладчике на проде нельзя. Чем найти горячий код с минимальным оверхедом?A)Повесить strace на весь процесс и крутить его в цикле под нагрузкойB)Обвесить подозрительные места print-ами и разбирать по логамC)Подключить gdb и пройтись степпингом по инструкциям на продеD)perf (perf top / record + flame graph) — семплирует по счётчикам CPU
показать ответ и разбор
+D)perf (perf top / record + flame graph) — семплирует по счётчикам CPU// разбор: На проде нужен низкооверхедный семплирующий профайлер. perf собирает стеки по таймеру/PMU-счётчикам (perf record -g, perf top), из чего строят flame graph — сразу видно, где процессор проводит время. strace, наоборот, перехватывает каждый системный вызов и замедляет цель в разы — он про «что вызывает», а не «где горит CPU». gdb со степпингом на проде недопустим. Для конкретных языков есть свои семплеры (py-spy, async-profiler), но perf — универсальный системный.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.