/proc/PID/wchan: где именно завис процессЕсли процесс показывает высокий
D в ps, сам статус ещё не говорит, на чём именно он остановился.Для этого Linux предоставляет
/proc/<PID>/wchan.ps -eo pid,stat,wchan:30,cmd | grep '[D]'
Например:
2418 D io_schedule backup
Здесь
wchan показывает kernel wait channel - функцию ядра, в которой процесс ожидает продолжения.cat /proc/2418/wchan
Можно получить:
io_schedule
Или:
nfs_wait_bit_killable
Во втором случае уже появляется конкретная зацепка: процесс может ждать операции, связанной с NFS.
for p in /proc/[0-9]*; do
pid=${p##*/}
state=$(awk '{print $3}' "$p/stat" 2>/dev/null)
[ "$state" = D ] || continue
printf '%-8s %s\n' \
"$pid" \
"$(cat "$p/wchan" 2>/dev/null)"
done
Получится примерно:
2418 io_schedule
2421 io_schedule
2517 nfs_wait_bit_killable
Если десятки процессов одновременно находятся в одном
wchan, это уже может указывать на общий узкий участок.Для более глубокого разбора:
cat /proc/2418/stack
Можно увидеть цепочку вызовов ядра, в которой находится поток.
Например:
io_schedule
schedule
...
Доступ к
/proc/PID/stack зависит от прав и настроек безопасности.kill -9 иногда не помогаетПроцесс в состоянии
D находится в uninterruptible sleep. Если он ждёт завершения определённой операции ядра, SIGKILL не может мгновенно вывести его из этого состояния.Поэтому вместо бесконечного:
kill -9 2418
полезнее сначала посмотреть:
ps -o pid,stat,wchan,cmd -p 2418
cat /proc/2418/wchan
cat /proc/2418/stack
Так можно понять, почему процесс не реагирует.
ps показывает симптом - D. wchan помогает увидеть причину ожидания на уровне ядра.Для проблем с дисками, NFS, storage и зависшими I/O-операциями это один из самых быстрых способов перейти от «процесс завис» к пониманию, где именно он застрял.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
read -d: как читать Bash-поток не по строкамread обычно ждёт перевод строки:while IFS= read -r line; do
printf '%s\n' "$line"
done < file.txt
Но у
read можно изменить разделитель. Это особенно полезно для данных, где обычный \n не является надёжной границей записи.Например,
find умеет выдавать имена файлов через \0:find /tmp -type f -print0 |
while IFS= read -r -d '' file; do
printf '%s\n' "$file"
done
-d '' означает: читать до NUL-символа.Это позволяет корректно обработать имя вроде:
report
2026.txt
где внутри имени действительно может быть перевод строки.
read здесь опасенТакой вариант:
find /tmp -type f -print0 |
while read -r file; do
...
done
не соответствует формату данных:
find разделяет записи \0, а read по умолчанию ищет \n.Например, прочитать значения через
::
while IFS= read -r -d ':' value; do
printf '<%s>\n' "$value"
done <<< 'one:two:three:'
Получим:
<one>
<two>
<three>
read умеет ещё и ограничивать количество символовIFS= read -r -n 1 char
Теперь Bash прочитает только один символ.
А:
IFS= read -r -N 8 chunk
будет ждать ровно 8 символов, не считая разделитель строки.
read -d особенно хорошо сочетается с командами, поддерживающими NUL-разделители:find ... -print0
git ... -z
sort -z
xargs -0
Получается безопасная цепочка, в которой пробелы, кавычки и даже переводы строк внутри имён файлов не ломают обработку.
Разделитель данных должен совпадать с тем, как эти данные реально генерируются.
read - это не только «прочитать строку»: с -d, -n и -N он может работать с потоками гораздо точнее.BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
local с подстановкой команды: почему local result=$(cmd) скрывает ошибкуВ Bash есть тонкая ловушка внутри функций. На первый взгляд эти два варианта выглядят одинаково:
result=$(false)
echo "$?"
Здесь
$? будет 1.Но если написать:
local result=$(false)
echo "$?"
получим:
0
В конструкции:
local result=$(cmd)
выполняются сразу две вещи:
1. Bash выполняет
$(cmd) и получает результат.2.
local объявляет переменную.Проблема в том, что код возврата всей команды определяется
local, а не самой command substitution.local успешно создал переменную - поэтому функция видит 0.Например:
get_data() {
local result=$(curl -fsS https://example.com/api)
echo "status=$?"
}Если
curl завершится с ошибкой, проверка $? после local может показать успешное выполнение.Ошибка потеряна.
Разделите объявление и присваивание:
get_data() {
local result
result=$(curl -fsS https://example.com/api)
local status=$?
echo "status=$status"
}Теперь
$? относится непосредственно к curl.get_data() {
local result
if result=$(curl -fsS https://example.com/api); then
printf '%s\n' "$result"
else
echo "command failed"
return 1
fi
}Здесь условие
if проверяет именно exit code command substitution.localПохожая ситуация возникает с некоторыми встроенными командами, когда результат присваивания объединяется с другой операцией.
Поэтому правило простое:
local result
result=$(cmd)
а не:
local result=$(cmd)
В обычном скрипте такая мелочь может остаться незаметной. В функции, которая используется в цепочке:
deploy || exit 1
потерянный exit code уже превращается в реальную логическую ошибку: функция может сообщить об успехе, хотя команда внутри неё завершилась с ошибкой.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
RuntimeMaxSec: как systemd убивает сервис, который работает слишком долгоИногда сервис не падает и не зависает в классическом смысле - он просто продолжает работать часами, хотя по логике приложения должен завершиться.
В systemd для этого есть
RuntimeMaxSec=.В unit-файле:
[Service]
ExecStart=/usr/local/bin/job.sh
RuntimeMaxSec=30min
После 30 минут с момента запуска systemd остановит сервис.
Для более точного ограничения:
RuntimeMaxSec=1h 30min
Это не просто
kill -9.По умолчанию systemd инициирует обычную остановку сервиса. Если процесс не завершится в течение
TimeoutStopSec=, systemd применит настроенное принудительное завершение.Например:
[Service]
RuntimeMaxSec=30min
TimeoutStopSec=20s
Получается цепочка:
30 мин → остановка
↓
20 сек на graceful shutdown
↓
принудительное завершение
После запуска:
systemctl show myjob.service \
-p RuntimeMaxUSec \
-p TimeoutStopUSec
А текущий статус:
systemctl status myjob.service
RuntimeMaxSec не запрещает сервису запускаться снова.Если unit настроен так:
[Service]
Restart=on-failure
RuntimeMaxSec=30min
важен результат завершения и дополнительные параметры restart policy.
Сервис может быть запущен systemd снова после остановки.
Поэтому
RuntimeMaxSec стоит рассматривать вместе с Restart=, а не как самостоятельный механизм watchdog.Допустим, есть batch-задача:
[Service]
Type=oneshot
ExecStart=/opt/scripts/import.sh
RuntimeMaxSec=2h
Если импорт по какой-то причине зависнет на бесконечной операции, systemd не оставит процесс работать сутки.
RuntimeMaxSec - это лимит общего времени работы, а не проверка того, отвечает ли приложение.Если сервис должен завершаться за 5 минут, но иногда работает 10 минут по штатной причине, такой лимит будет ошибкой конфигурации.
Для контроля «жив ли сервис и отвечает ли он» нужны другие механизмы.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
tee + process substitution: один поток и несколько получателей
Иногда нужно одновременно: увидеть вывод команды в терминале, записать его в файл и передать на обработку другой программе Если делать это по очереди - потеряется поток данных. Здесь помогает связка tee + process substitution.
▪️ Что делает tee. tee дублирует поток:
command | tee file.log
вывод остается в терминале и одновременно пишется в
▪️ Несколько получателей. tee может писать сразу в несколько файлов:
command | tee out1.log out2.log
Но иногда нужно не просто файл, а другую команду.
▪️ Process substitution. Bash позволяет подставить вывод команды как файл:
>(command)
Это называется process substitution.
▪️ Комбинируем
command | tee >(grep ERROR > errors.log)
Одна копия идет в терминал, а другая в grep. Если найден ERROR, запись попадет в errors.log.
▪️ Более реальный пример. Допустим, идет сбор логов:
journalctl -f | tee >(grep ERROR >> errors.log)
Теперь: полный поток остается в терминале, ошибки автоматически сохраняются
▪️ Можно делать несколько обработчиков
journalctl -f | tee \
>(grep ERROR >> errors.log) \
>(grep WARN >> warn.log)
Один поток и сразу несколько фильтров.
BashTex📱 #bash #utils
Иногда нужно одновременно: увидеть вывод команды в терминале, записать его в файл и передать на обработку другой программе Если делать это по очереди - потеряется поток данных. Здесь помогает связка tee + process substitution.
command | tee file.log
вывод остается в терминале и одновременно пишется в
file.logcommand | tee out1.log out2.log
Но иногда нужно не просто файл, а другую команду.
>(command)
Это называется process substitution.
command | tee >(grep ERROR > errors.log)
command генерирует потокtee дублирует егоОдна копия идет в терминал, а другая в grep. Если найден ERROR, запись попадет в errors.log.
journalctl -f | tee >(grep ERROR >> errors.log)
Теперь: полный поток остается в терминале, ошибки автоматически сохраняются
journalctl -f | tee \
>(grep ERROR >> errors.log) \
>(grep WARN >> warn.log)
Один поток и сразу несколько фильтров.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6👍1
KillMode=: почему systemctl stop иногда оставляет процессыОстановить сервис через:
systemctl stop myapp.service
не всегда означает, что исчезнет только один процесс. У systemd есть отдельная настройка, определяющая, какие процессы будут остановлены вместе с unit.
Это
KillMode=.[Service]
ExecStart=/opt/myapp/start.sh
KillMode=control-group
По умолчанию systemd работает с control group сервиса. При остановке он может завершить все процессы внутри cgroup, а не только основной PID.
Проверить:
systemctl show myapp.service -p KillMode
process[Service]
KillMode=process
В этом режиме сигнал отправляется только основному процессу сервиса.
Если start.sh породил:
myapp
├─ worker1
└─ worker2
дочерние процессы могут продолжить работу после остановки unit.
control-group[Service]
KillMode=control-group
systemd отслеживает cgroup целиком:
myapp.service
├─ PID 1200
├─ PID 1201
└─ PID 1202
При остановке unit процессы внутри группы также попадут под завершение.
systemctl status myapp.service
или:
systemctl show myapp.service -p ControlGroup
Затем:
systemd-cgls
Можно увидеть дерево процессов по cgroup.
KillMode=mixed
При остановке
SIGTERM получает основной процесс, а при последующем принудительном завершении systemd применяет его ко всей control group.Это позволяет дать главному процессу шанс корректно завершиться, не оставляя дочерние процессы навсегда.
Особенно легко получить «призраков» при запуске приложения через shell-обёртку:
ExecStart=/bin/bash /opt/start.sh
Если скрипт запускает фоновые процессы, нужно понимать, в какой cgroup они окажутся и как
KillMode повлияет на их завершение.systemctl stop - это не просто отправка SIGTERM одному PID. В systemd процесс существует внутри cgroup, и политика KillMode определяет масштаб остановки.BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
failglob - почему Bash должен падать при нераскрывшемся glob
Обычно Bash спокойно оставляет glob как есть, если совпадений нет:
Если
Но в автоматизации это может быть проблемой: скрипт продолжит выполнение, хотя вы рассчитывали, что glob что-то нашёл.
▪️ failglob меняет поведение
Если совпадений нет, Bash сам выдаст ошибку:
То есть проблема обнаруживается на этапе раскрытия glob, ещё до запуска команды.
▪️ Особенно полезно в скриптах:
Без
С
▪️ Но есть нюанс
Тогда при отсутствии совпадений массив просто будет пустым.
Итого получается:
Для критичных автоматизаций
BashTex📱 #bash #linux
Обычно Bash спокойно оставляет glob как есть, если совпадений нет:
rm /var/log/app/*.old
Если
.old-файлов нет, команда фактически получит:rm: cannot remove '/var/log/app/*.old': No such file or directory
Но в автоматизации это может быть проблемой: скрипт продолжит выполнение, хотя вы рассчитывали, что glob что-то нашёл.
shopt -s failglob
files=(/var/log/app/*.old)
Если совпадений нет, Bash сам выдаст ошибку:
bash: no match: /var/log/app/*.old
То есть проблема обнаруживается на этапе раскрытия glob, ещё до запуска команды.
#!/usr/bin/env bash
set -e
shopt -s failglob
for file in /backup/*.tar.gz; do
process "$file"
done
Без
failglob цикл может получить буквально строку /backup/*.tar.gz.С
failglob скрипт сразу сигнализирует, что ожидаемых файлов нет.failglob не всегда нужен. Если отсутствие файлов - нормальная ситуация, лучше использовать nullglob:shopt -s nullglob
files=(/backup/*.tar.gz)
for file in "${files[@]}"; do
process "$file"
done
Тогда при отсутствии совпадений массив просто будет пустым.
Итого получается:
failglob → отсутствие совпадения считается ошибкой. nullglob → отсутствие совпадения превращается в пустой список.Для критичных автоматизаций
failglob помогает не пропустить логическую ошибку, замаскированную обычным поведением Bash.BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥1
PSI: /proc/pressure/* - как понять, чего реально не хватает системе
Для этого в Linux есть PSI - Pressure Stall Information:
Например:
▪️
▪️
Например:
Это уже гораздо интереснее, чем просто увидеть высокий RAM usage.
Если
Если начинает расти
То же самое для I/O:
Если I/O PSI высокий, а CPU почти не испытывает pressure, проблема может быть не в загрузке процессора, а в storage latency.
А CPU PSI позволяет увидеть ситуацию, когда CPU формально загружен не на 100%, но runnable-задачи всё равно регулярно ждут свою очередь.
Проверить всё сразу:
PSI полезен именно как метрика задержки из-за конкуренции за ресурсы, а не просто их потребления.
Поэтому вместо вопроса «CPU/RAM/Disk загружены?» иногда правильнее спросить:
«Какой ресурс заставляет процессы ждать?»
BashTex📱 #perfomance #linux
load average показывает, что в системе есть очередь на выполнение или I/O. Но он плохо отвечает на другой вопрос: насколько пользователи и процессы реально страдают от этого дефицита?Для этого в Linux есть PSI - Pressure Stall Information:
cat /proc/pressure/cpu
cat /proc/pressure/memory
cat /proc/pressure/io
Например:
full avg10=0.00 avg60=0.01 avg300=0.00 total=...
some - хотя бы часть задач была вынуждена ждать ресурс.full - все задачи соответствующей группы одновременно стояли из-за этого ресурса.avg10, avg60, avg300 - процент времени за последние 10, 60 и 300 секунд, когда происходило такое ожидание.Например:
some avg10=18.5
full avg10=7.2
Это уже гораздо интереснее, чем просто увидеть высокий RAM usage.
Если
memory some растёт - процессы регулярно упираются в нехватку памяти.Если начинает расти
memory full - система уже попадает в периоды, когда все учитываемые задачи одновременно не могут нормально продвигаться из-за memory pressure.То же самое для I/O:
cat /proc/pressure/io
Если I/O PSI высокий, а CPU почти не испытывает pressure, проблема может быть не в загрузке процессора, а в storage latency.
А CPU PSI позволяет увидеть ситуацию, когда CPU формально загружен не на 100%, но runnable-задачи всё равно регулярно ждут свою очередь.
Проверить всё сразу:
for f in /proc/pressure/*; do
echo "=== $f ==="
cat "$f"
done
PSI полезен именно как метрика задержки из-за конкуренции за ресурсы, а не просто их потребления.
Поэтому вместо вопроса «CPU/RAM/Disk загружены?» иногда правильнее спросить:
«Какой ресурс заставляет процессы ждать?»
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
systemd-delta: как найти, чем на самом деле переопределён unit
Иногда вы открываете unit-файл и видите одну конфигурацию, а systemd ведёт себя иначе.
Причина часто в drop-in override, созданном через
Например, основной unit:
а дополнительная настройка лежит здесь:
▪️ Посмотреть переопределения
Команда показывает отличия между vendor-конфигурацией и локальными изменениями.
Можно проверить конкретный unit:
Если сервис был переопределён, вы увидите соответствующий drop-in.
▪️ Почему это важно
Допустим, в основном unit указано:
Открыв только
systemd при запуске учитывает оба слоя.
▪️ Посмотреть итоговую конфигурацию
Для проверки того, что systemd реально использует:
Например:
А список связанных drop-in можно увидеть так:
Здесь systemd покажет основной unit и применённые drop-in-файлы.
▪️ Практический сценарий
Сервис неожиданно перезапускается:
В основном unit
Вместо того чтобы сразу менять конфигурацию, проверяем:
И обнаруживаем старый override:
Именно он мог изменить поведение сервиса.
▪️ Важный нюанс
Пакет может заменить vendor unit в
Поэтому при странном поведении unit полезно проверить не только сам файл, но и что поверх него было добавлено или изменено.
BashTex📱 #bash #systemd
Иногда вы открываете unit-файл и видите одну конфигурацию, а systemd ведёт себя иначе.
Причина часто в drop-in override, созданном через
systemctl edit.Например, основной unit:
/usr/lib/systemd/system/nginx.service
а дополнительная настройка лежит здесь:
/etc/systemd/system/nginx.service.d/override.conf
systemd-delta
Команда показывает отличия между vendor-конфигурацией и локальными изменениями.
Можно проверить конкретный unit:
systemd-delta nginx.service
Если сервис был переопределён, вы увидите соответствующий drop-in.
Допустим, в основном unit указано:
[Service]
Restart=no
А где-то в /etc/systemd/system/nginx.service.d/override.conf:
[Service]
Restart=always
Открыв только
/usr/lib/systemd/system/nginx.service, вы будете смотреть на неполную картину.systemd при запуске учитывает оба слоя.
Для проверки того, что systemd реально использует:
systemctl show nginx.service
Например:
systemctl show nginx.service -p Restart -p RestartUSec
А список связанных drop-in можно увидеть так:
systemctl cat nginx.service
Здесь systemd покажет основной unit и применённые drop-in-файлы.
Сервис неожиданно перезапускается:
systemctl status nginx
В основном unit
Restart= не настроен.Вместо того чтобы сразу менять конфигурацию, проверяем:
systemd-delta nginx.service
И обнаруживаем старый override:
/etc/systemd/system/nginx.service.d/override.conf
Именно он мог изменить поведение сервиса.
systemd-delta особенно полезен после обновлений дистрибутива.Пакет может заменить vendor unit в
/usr/lib/systemd/system/, но локальный override в /etc/systemd/system/ продолжит действовать.Поэтому при странном поведении unit полезно проверить не только сам файл, но и что поверх него было добавлено или изменено.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Please open Telegram to view this post
VIEW IN TELEGRAM
😁8🔥2🗿1
file vs расширение - как Linux определяет тип файла на самом деле
В Linux расширение файла само по себе ничего не определяет.
Для системы:
это просто имена файлов. Можно спокойно сделать:
и содержимое файла от этого никак не изменится.
▪️ Откуда тогда
Утилита
Например:
Даже если файл называется:
file всё равно может определить его как PNG.
▪️ Главный источник - magic bytes
А file сопоставляет такие сигнатуры с базой magic:
и использует правила из базы magic.
▪️ Можно обмануть расширение
Результат всё равно будет примерно таким:
Потому что имя изменилось, а байты остались прежними.
▪️ Но не всё определяется только первыми байтами
Для ELF:
может показать архитектуру, разрядность и тип исполняемого файла.
Для текстовых файлов утилита анализирует содержимое и может определить кодировку, тип текста и другие признаки.
▪️ Практический сценарий
Скачали файл с подозрительным расширением:
Не стоит сразу доверять имени.
Сначала:
А затем, если это бинарник:
или:
Так можно понять, что реально лежит внутри файла.
▪️ Важный нюанс
Если формат не имеет узнаваемой сигнатуры или несколько форматов используют похожую структуру, результат может быть приблизительным.
И ещё важнее: file определяет тип по содержимому, но не говорит, что файл безопасен.
Файл может называться
В Linux расширение - часть имени.
А реальный формат гораздо чаще определяется тем, какие байты находятся внутри файла.
BashTex📱 #bash #systemd
В Linux расширение файла само по себе ничего не определяет.
Для системы:
backup.tar.gz
backup.jpg
backup.txt
это просто имена файлов. Можно спокойно сделать:
mv image.jpg image.txtи содержимое файла от этого никак не изменится.
file знает типУтилита
file смотрит не на расширение, а на содержимое файла.file image.txt
file archive.bin
file unknown
Например:
unknown: PNG image data, 1920 x 1080, 8-bit/color RGB
Даже если файл называется:
photo.txt
file всё равно может определить его как PNG.
Многие форматы имеют характерную сигнатуру в начале файла.
Например, PNG начинается с:
89 50 4e 47 0d 0a 1a 0a
Проверить первые байты можно:
xxd -l 16 photo.png
А file сопоставляет такие сигнатуры с базой magic:
file --version
и использует правила из базы magic.
cp image.png document.txt
file document.txt
Результат всё равно будет примерно таким:
document.txt: PNG image data
Потому что имя изменилось, а байты остались прежними.
file может анализировать не только сигнатуру.Для ELF:
file /bin/ls
может показать архитектуру, разрядность и тип исполняемого файла.
Для текстовых файлов утилита анализирует содержимое и может определить кодировку, тип текста и другие признаки.
Скачали файл с подозрительным расширением:
download.exe
Не стоит сразу доверять имени.
Сначала:
file download.exe
А затем, если это бинарник:
readelf -h download.exe
или:
xxd -l 32 download.exe
Так можно понять, что реально лежит внутри файла.
file не является универсальным детектором содержимого.Если формат не имеет узнаваемой сигнатуры или несколько форматов используют похожую структуру, результат может быть приблизительным.
И ещё важнее: file определяет тип по содержимому, но не говорит, что файл безопасен.
Файл может называться
document.pdf, определяться как PDF и при этом содержать вредоносную нагрузку.В Linux расширение - часть имени.
А реальный формат гораздо чаще определяется тем, какие байты находятся внутри файла.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
du vs df: почему Linux показывает разный размер диска
Иногда ситуация выглядит странно:
показывает, что занято 80 GB.
А:
находит только 55 GB.
Кажется, что Linux «потерял» 25 GB. На самом деле
▪️ Что считает
Он показывает, сколько места файловая система считает занятым и свободным.
▪️ Что считает
▪️ Одна из главных причин расхождения
Процесс может держать открытым файл, который уже удалили.
Например:
Имя файла исчезло из каталога, но процесс всё ещё держит его открытым.
Для
А файловая система продолжает считать его блоки занятыми.
Проверить:
Можно увидеть что-то вроде:
Пока процесс не закроет этот дескриптор, место может не освободиться.
▪️ Есть и другие причины
Например,
Или часть пространства файловой системы зарезервирована и не доступна обычным пользователям.
Посмотреть файловую систему подробнее:
▪️ Практический сценарий
На сервере внезапно заканчивается место:
Показывает:
Но:
показывает только 70G.
Первое, что стоит проверить:
Если там огромный
▪️ Важный нюанс
«Сколько блоков файловой системы сейчас занято?»
А
«Сколько места занимают доступные мне файлы в этом дереве?»
Поэтому при расхождении
Сначала нужно понять, куда именно делось пространство.
BashTex📱 #bash #systemd
Иногда ситуация выглядит странно:
df -h /
показывает, что занято 80 GB.
А:
du -sh /
находит только 55 GB.
Кажется, что Linux «потерял» 25 GB. На самом деле
du и df измеряют разные вещи.dfdf смотрит на файловую систему и её блоки:df -h /
Он показывает, сколько места файловая система считает занятым и свободным.
dudu обходит дерево каталогов и суммирует пространство, связанное с найденными файлами:du -xhd1 / 2>/dev/null
-x здесь важен: он не позволяет уйти на другие файловые системы.Процесс может держать открытым файл, который уже удалили.
Например:
rm /var/log/app.log
Имя файла исчезло из каталога, но процесс всё ещё держит его открытым.
Для
du такого файла уже не существует.А файловая система продолжает считать его блоки занятыми.
Проверить:
lsof +L1
Можно увидеть что-то вроде:
app 1234 ... /var/log/app.log (deleted)
Пока процесс не закроет этот дескриптор, место может не освободиться.
Например,
du без -x может пересекать mount points и давать неожиданный результат.Или часть пространства файловой системы зарезервирована и не доступна обычным пользователям.
Посмотреть файловую систему подробнее:
findmnt /
df -h /
df -i /
На сервере внезапно заканчивается место:
df -h /
Показывает:
/dev/sda2 100G 95G 5G 95% /
Но:
du -xsh /
показывает только 70G.
Первое, что стоит проверить:
lsof +L1
Если там огромный
(deleted) лог, причина найдена.df отвечает примерно на вопрос:«Сколько блоков файловой системы сейчас занято?»
А
du:«Сколько места занимают доступные мне файлы в этом дереве?»
Поэтому при расхождении
df и du не стоит сразу запускать rm -rf по большим каталогам.Сначала нужно понять, куда именно делось пространство.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Sparse files: почему файл на 100 ГБ может занимать несколько мегабайт
Размер файла и реально занятое место на диске не всегда совпадают.
Можно создать файл, который логически имеет размер 100 ГБ, но физически занимает всего несколько мегабайт.
Для этого используются sparse files.
▪️ Создаём sparse-файл
Теперь:
покажет:
Но:
может показать всего несколько килобайт.
▪️ Что произошло
Файл содержит большой диапазон нулей, для которого файловая система не обязана физически выделять блоки.
Условно:
Второй вариант показывает размер так, будто sparse-областей нет.
Например:
▪️ Практический сценарий
Есть сервер виртуализации, и администратор видит:
Образы занимают:
Но:
показывает:
Это не обязательно ошибка подсчёта.
Часть образов может быть sparse.
▪️ Важный нюанс
Копирование sparse-файла не всегда сохраняет holes.
Например, некоторые способы копирования могут превратить разреженный файл в обычный и реально записать нулевые блоки.
Проверить результат можно снова через:
Поэтому при работе с большими VM-образами важно различать логический размер файла и реально выделенное дисковое пространство.
BashTex📱 #bash #systemd
Размер файла и реально занятое место на диске не всегда совпадают.
Можно создать файл, который логически имеет размер 100 ГБ, но физически занимает всего несколько мегабайт.
Для этого используются sparse files.
truncate -s 100G test.img
Теперь:
ls -lh test.img
покажет:
-rw-r--r-- 1 user user 100G test.img
Но:
du -h test.img
может показать всего несколько килобайт.
ls смотрит на логический размер, а du - на реально выделенные filesystem blocks.Файл содержит большой диапазон нулей, для которого файловая система не обязана физически выделять блоки.
Условно:
логический файл:
[данные][ огромная область нулей ][данные]
↓ ↓ ↓
блоки hole блоки
Эта незаполненная область называется hole.
▪️Можно посмотреть оба размера
stat test.img
Обратите внимание на:
Size:
Blocks:
Size — логический размер файла.
Blocks — сколько filesystem blocks реально выделено.
▪️Почему это важно
Sparse files часто используются для образов виртуальных машин и контейнеров:
VM disk image → 100 GB
actual allocated space → 12 GB
Пока виртуальная машина не записала данные в свободные области, физическое место не требуется.
Но если эти области постепенно заполняются, реальное потребление диска начинает расти.
▪️Как проверить sparse-файл
du -h test.img
du --apparent-size -h test.img
Второй вариант показывает размер так, будто sparse-областей нет.
Например:
8.0K test.img
100G test.img
Есть сервер виртуализации, и администратор видит:
ls -lh /var/lib/images/*
Образы занимают:
500GНо:
du -sh /var/lib/imagesпоказывает:
180GЭто не обязательно ошибка подсчёта.
Часть образов может быть sparse.
Копирование sparse-файла не всегда сохраняет holes.
Например, некоторые способы копирования могут превратить разреженный файл в обычный и реально записать нулевые блоки.
Проверить результат можно снова через:
du -h file
du --apparent-size -h file
Поэтому при работе с большими VM-образами важно различать логический размер файла и реально выделенное дисковое пространство.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
inotify vs polling: почему постоянный find может быть плохим мониторингом
Иногда нужно отследить появление нового файла в каталоге.
Самый простой вариант:
А с
Теперь процесс ждёт события вместо постоянного запуска
▪️ Можно сразу обрабатывать новые файлы
Когда файл появляется или перемещается в каталог, вызывается обработчик.
▪️ Почему
При polling:
мы постоянно проверяем состояние файловой системы, даже когда ничего не меняется.
При
процесс большую часть времени просто ожидает событие.
▪️ Но есть важный нюанс
У ядра есть ограничение на очередь событий:
Если приложение не успевает читать события, очередь может переполниться.
Тогда появляется:
И приложение уже не может считать, что знает обо всех произошедших изменениях.
Поэтому для критичных систем иногда нужен дополнительный механизм сверки состояния.
▪️ Практический сценарий
Есть каталог, куда другой сервис складывает файлы:
Вместо постоянного:
можно реагировать непосредственно на
Но если файл сначала создаётся и затем постепенно записывается, одного события создания недостаточно.
Нужно учитывать момент, когда файл полностью готов к обработке.
Именно поэтому в production важно следить не только за самим событием, но и за способом передачи файла.
BashTex📱 #bash #systemd
Иногда нужно отследить появление нового файла в каталоге.
Самый простой вариант:
while true; do
find /var/incoming -type f
sleep 1
done
Работает. Но каждые несколько секунд мы заново обходим каталог, даже если за это время ничего не произошло.
Для событийного мониторинга в Linux есть inotify.
▪️Следим за изменениями без постоянного обхода
Например, с inotifywait:
inotifywait -m /var/incoming
При создании файла можно получить:
/var/incoming/ CREATE report.csv
А с
-e можно ограничить интересующие события:inotifywait -m \
-e create -e moved_to \
/var/incoming
Теперь процесс ждёт события вместо постоянного запуска
find.inotifywait -m -e create -e moved_to --format '%w%f' \
/var/incoming |
while read -r file; do
process "$file"
done
Когда файл появляется или перемещается в каталог, вызывается обработчик.
polling хужеПри polling:
find → sleep → find → sleep → find
мы постоянно проверяем состояние файловой системы, даже когда ничего не меняется.
При
inotify:wait → event → обработка → waitпроцесс большую часть времени просто ожидает событие.
inotify не является очередью событий для бесконечного количества изменений.У ядра есть ограничение на очередь событий:
cat /proc/sys/fs/inotify/max_queued_events
Если приложение не успевает читать события, очередь может переполниться.
Тогда появляется:
IN_Q_OVERFLOW
И приложение уже не может считать, что знает обо всех произошедших изменениях.
Поэтому для критичных систем иногда нужен дополнительный механизм сверки состояния.
Есть каталог, куда другой сервис складывает файлы:
/var/incoming/
Вместо постоянного:
find /var/incoming ...
можно реагировать непосредственно на
CREATE или MOVED_TO.Но если файл сначала создаётся и затем постепенно записывается, одного события создания недостаточно.
Нужно учитывать момент, когда файл полностью готов к обработке.
Именно поэтому в production важно следить не только за самим событием, но и за способом передачи файла.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Minor и major page fault: почему page fault не всегда означает проблему с диском
Когда процесс обращается к виртуальной памяти, нужная страница не всегда уже находится в памяти именно в том состоянии, которое ожидает процесс.
Тогда возникает page fault - обращение к ядру для обработки этого случая.
Но сам факт page fault ещё не означает, что система полезла на диск.
▪️ Смотрим статистику процесса
Или:
Там можно увидеть количество minor и major faults.
▪️
При minor page fault данные уже находятся в RAM, но процессу нужно, например, создать или настроить соответствующее отображение страницы.
Дисковый I/O для загрузки этой страницы не требуется.
Поэтому большое количество minor faults само по себе не означает медленный диск.
▪️
При major fault для продолжения работы процесса требуется получить данные из более медленного источника, например с диска.
Условно:
Именно major faults интересны при поиске проблем с памятью и I/O.
▪️ Смотрим процесс в реальном времени
Можно увидеть примерно:
Здесь процесс создаёт много minor faults, но major faults нет.
Это совершенно другая ситуация, чем:
Во втором случае часть обращений действительно приводит к ожиданию загрузки данных.
▪️ Практический сценарий
Приложение внезапно начинает тормозить.
Но:
показывает заметный
Тогда стоит дополнительно посмотреть I/O:
Возможно, процесс регулярно ждёт чтения данных со storage.
▪️ Важный нюанс
Не стоит использовать количество page faults как самостоятельную метрику производительности.
Высокий
Гораздо интереснее смотреть на major faults вместе с latency и I/O activity.
То есть:
Сам по себе page fault - это не ошибка. Это механизм, который Linux постоянно использует при работе с виртуальной памятью.
BashTex📱 #bash #systemd
Когда процесс обращается к виртуальной памяти, нужная страница не всегда уже находится в памяти именно в том состоянии, которое ожидает процесс.
Тогда возникает page fault - обращение к ядру для обработки этого случая.
Но сам факт page fault ещё не означает, что система полезла на диск.
ps -o pid,comm,min_flt,maj_flt -p 1234
Или:
pidstat -r -p 1234 1
Там можно увидеть количество minor и major faults.
minor faultПри minor page fault данные уже находятся в RAM, но процессу нужно, например, создать или настроить соответствующее отображение страницы.
Дисковый I/O для загрузки этой страницы не требуется.
Поэтому большое количество minor faults само по себе не означает медленный диск.
major faultПри major fault для продолжения работы процесса требуется получить данные из более медленного источника, например с диска.
Условно:
minor
процесс → kernel → RAM → продолжение
major
процесс → kernel → storage → RAM → продолжение
Именно major faults интересны при поиске проблем с памятью и I/O.
pidstat -r -p 1234 1
Можно увидеть примерно:
PID minflt/s majflt/s
1234 15230.00 0.00
Здесь процесс создаёт много minor faults, но major faults нет.
Это совершенно другая ситуация, чем:
PID minflt/s majflt/s
1234 1200.00 85.00
Во втором случае часть обращений действительно приводит к ожиданию загрузки данных.
Приложение внезапно начинает тормозить.
top показывает:CPU: 20%
RAM: 60%
Но:
pidstat -r -p 1234 1
показывает заметный
majflt/s.Тогда стоит дополнительно посмотреть I/O:
iostat -xz 1
Возможно, процесс регулярно ждёт чтения данных со storage.
Не стоит использовать количество page faults как самостоятельную метрику производительности.
Высокий
minflt/s может быть совершенно нормальным для приложения с определённым характером работы с памятью.Гораздо интереснее смотреть на major faults вместе с latency и I/O activity.
То есть:
page fault
↓
minor или major?
↓
если major → есть ли реальный I/O?
↓
где процесс проводит время?
Сам по себе page fault - это не ошибка. Это механизм, который Linux постоянно использует при работе с виртуальной памятью.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
nohup vs session vs terminal - что реально происходит после закрытия SSH
Запустили процесс по SSH:
Закрыли SSH-сессию - а потом обнаружили, что процесс исчез.
Почему?
Проблема не в самом SSH. Нужно понимать связь между терминалом, session, shell и сигналами.
▪️ Что происходит при SSH-подключении
Условно:
Shell создаёт процессы в своей session и process group, а SSH предоставляет им псевдотерминал.
Когда соединение закрывается, терминал исчезает. Это может привести к отправке
Именно здесь обычный background-процесс может неожиданно завершиться.
▪️
Это не означает, что процесс переживёт закрытие SSH.
Проверить дерево:
Здесь особенно интересны:
Можно увидеть, к какой session и terminal относится процесс.
▪️ Что делает
Он в первую очередь меняет обработку
Проверить:
Процесс всё ещё может находиться в той же session.
Но получение
▪️ А что такое
Session - это уровень выше process group.
У неё есть лидер - обычно shell, запущенный после входа по SSH.
Посмотреть:
Например:
Оба процесса находятся в одной session, хотя имеют разные process groups.
▪️ Почему
Можно создать новую session:
Теперь процесс не находится в старой session SSH.
Это уже принципиально отличается от простого:
Но для полноценного фонового запуска всё равно нужно учитывать stdin/stdout/stderr и дальнейшее поведение приложения.
▪️ Практический вариант
Если нужен простой запуск долгой команды:
Если нужна полноценная интерактивная среда, которая переживает отключение SSH:
В
▪️ Важный нюанс
Поэтому вопрос «почему процесс умер после выхода из SSH?» лучше начинать не с
Сначала смотрим, к какой session, process group и terminal он вообще был привязан.
BashTex📱 #bash #systemd
Запустили процесс по SSH:
./backup.sh &
Закрыли SSH-сессию - а потом обнаружили, что процесс исчез.
Почему?
Проблема не в самом SSH. Нужно понимать связь между терминалом, session, shell и сигналами.
Условно:
sshd
↓
shell
↓
terminal / PTY
↓
команды
Shell создаёт процессы в своей session и process group, а SSH предоставляет им псевдотерминал.
Когда соединение закрывается, терминал исчезает. Это может привести к отправке
SIGHUP процессам, связанным с терминалом.Именно здесь обычный background-процесс может неожиданно завершиться.
& не делает процесс независимым./backup.sh &
& лишь запускает команду в фоне для текущего shell.Это не означает, что процесс переживёт закрытие SSH.
Проверить дерево:
ps -o pid,ppid,sid,pgid,tty,stat,cmd
Здесь особенно интересны:
PID PPID SID PGID TTY
Можно увидеть, к какой session и terminal относится процесс.
nohupnohup ./backup.sh >backup.log 2>&1 &
nohup не создаёт магическую «вечную» копию процесса.Он в первую очередь меняет обработку
SIGHUP и перенаправляет стандартные потоки, если они всё ещё связаны с терминалом.Проверить:
ps -o pid,ppid,sid,pgid,tty,cmd -p <PID>
Процесс всё ещё может находиться в той же session.
Но получение
SIGHUP уже не должно завершить его обычным способом.sessionSession - это уровень выше process group.
У неё есть лидер - обычно shell, запущенный после входа по SSH.
Посмотреть:
ps -o pid,ppid,sid,pgid,tty,cmd
Например:
PID PPID SID PGID TTY
1000 900 1000 1000 pts/0
1050 1000 1000 1050 pts/0
Оба процесса находятся в одной session, хотя имеют разные process groups.
setsid - это уже другоеМожно создать новую session:
setsid ./backup.sh
Теперь процесс не находится в старой session SSH.
Это уже принципиально отличается от простого:
./backup.sh &
Но для полноценного фонового запуска всё равно нужно учитывать stdin/stdout/stderr и дальнейшее поведение приложения.
Если нужен простой запуск долгой команды:
nohup ./backup.sh </dev/null >backup.log 2>&1 &
Если нужна полноценная интерактивная среда, которая переживает отключение SSH:
tmux
В
tmux процесс продолжает работать внутри отдельной terminal/session среды, а вы можете подключиться к ней позже.nohup, setsid и tmux решают разные задачи.& → background job
nohup → защита от SIGHUP + перенаправление потоков
setsid → новая session
tmux → отдельная управляемая terminal session
Поэтому вопрос «почему процесс умер после выхода из SSH?» лучше начинать не с
nohup, а с:ps -o pid,ppid,sid,pgid,tty,stat,cmd
Сначала смотрим, к какой session, process group и terminal он вообще был привязан.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
veth - как Linux соединяет два network namespace
Network namespace изолирует сетевой стек процесса. У каждого namespace могут быть свои интерфейсы, маршруты и таблица соседей.
Но как соединить два таких изолированных сетевых мира?
Для этого в Linux есть veth pair - виртуальный Ethernet-кабель с двумя концами.
▪️ Создаём два namespace
Теперь создаём пару:
Получили:
То, что отправлено в один конец, появляется на другом.
▪️ Разносим концы по namespace
Теперь:
В каждом namespace находится только свой конец виртуального кабеля.
▪️ Назначаем адреса
Теперь можно проверить связь:
Пакет проходит примерно так:
Никакого физического интерфейса между namespace здесь нет.
▪️ Почему это используется в контейнерах
Контейнер получает собственный network namespace.
Один конец veth помещается внутрь контейнера:
Второй конец остаётся на host и обычно подключается к Linux bridge.
Например:
Так несколько изолированных namespace получают доступ к одной L2-сети.
▪️ Проверяем, где находится интерфейс
На host:
В namespace:
Можно увидеть, что
▪️ Важный нюанс
Он просто соединяет два сетевых пространства на уровне Ethernet.
Если нужно выйти из namespace в интернет, поверх veth обычно добавляют bridge, routing, NAT или другой сетевой механизм.
То есть
А уже дальше Linux решает, куда отправлять пакеты.
BashTex📱 #bash #systemd
Network namespace изолирует сетевой стек процесса. У каждого namespace могут быть свои интерфейсы, маршруты и таблица соседей.
Но как соединить два таких изолированных сетевых мира?
Для этого в Linux есть veth pair - виртуальный Ethernet-кабель с двумя концами.
ip netns add ns1
ip netns add ns2
Теперь создаём пару:
ip link add veth1 type veth peer name veth2
Получили:
veth1 <──────────> veth2
То, что отправлено в один конец, появляется на другом.
ip link set veth1 netns ns1
ip link set veth2 netns ns2
Теперь:
namespace ns1 namespace ns2
veth1 <──────────> veth2
В каждом namespace находится только свой конец виртуального кабеля.
ip -n ns1 addr add 10.10.0.1/24 dev veth1
ip -n ns2 addr add 10.10.0.2/24 dev veth2
ip -n ns1 link set veth1 up
ip -n ns2 link set veth2 up
Теперь можно проверить связь:
ip netns exec ns1 ping 10.10.0.2
Пакет проходит примерно так:
process
↓
network stack ns1
↓
veth1
↓
veth2
↓
network stack ns2
↓
process
Никакого физического интерфейса между namespace здесь нет.
Контейнер получает собственный network namespace.
Один конец veth помещается внутрь контейнера:
container
eth0
│
│ veth
│
host
Второй конец остаётся на host и обычно подключается к Linux bridge.
Например:
container eth0
│
veth
│
bridge
├── veth
├── veth
└── host
Так несколько изолированных namespace получают доступ к одной L2-сети.
На host:
ip link
В namespace:
ip -n ns1 link
Можно увидеть, что
veth1 и veth2 физически не существуют как один интерфейс — это два связанных виртуальных endpoint’а.veth сам по себе не маршрутизирует трафик.Он просто соединяет два сетевых пространства на уровне Ethernet.
Если нужно выйти из namespace в интернет, поверх veth обычно добавляют bridge, routing, NAT или другой сетевой механизм.
То есть
veth - это буквально виртуальный кабель:namespace A
│
veth A
│
└──────── veth B
│
namespace B
А уже дальше Linux решает, куда отправлять пакеты.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥1
Почему Permission denied: как найти каталог, на котором ломаются права
Иногда файл имеет правильные права, пользователь состоит в нужной группе, но
Причина может быть выше по пути: чтобы открыть
Разберем, как быстро найти место, где ломается доступ.
1️⃣
В выводе будут отдельно показаны:
Сразу видно, на каком уровне пользователь может упереться в права.
2️⃣ Почему важен
Для каталога
Например:
Файлы внутри могут быть доступны для чтения, но войти в каталог без
Проверить можно:
3️⃣ Проверяем конкретного пользователя
Если доступ должен быть у
А группы:
Это помогает понять, действительно ли процесс запускается с теми группами, которые вы ожидаете.
4️⃣ ACL тоже могут менять картину
Обычных
Там могут быть дополнительные разрешения для конкретного пользователя или группы.
5️⃣ Почему
Команда:
показывает права самого файла.
Но если проблема находится в
Именно поэтому
BashTex📱 #bash #systemd
Иногда файл имеет правильные права, пользователь состоит в нужной группе, но
Permission denied всё равно появляется.Причина может быть выше по пути: чтобы открыть
/var/www/site/config.php, процесс должен иметь право x на каждый каталог в этом пути.Разберем, как быстро найти место, где ломается доступ.
namei показывает права на весь путьnamei -l /var/www/site/config.php
В выводе будут отдельно показаны:
drwxr-xr-x root root /
drwxr-xr-x root root var
drwxr-x--- root www-data www
drwx------ root root site
-rw-r----- root www-data config.php
Сразу видно, на каком уровне пользователь может упереться в права.
x у каталогаДля каталога
x означает не «запуск», а возможность проходить через него и обращаться к объектам внутри.Например:
chmod 644 /var/www/site
Файлы внутри могут быть доступны для чтения, но войти в каталог без
x уже нельзя.Проверить можно:
sudo -u www-data cat /var/www/site/config.php
Если доступ должен быть у
www-data:sudo -u www-data namei -l /var/www/site/config.php
А группы:
id www-data
Это помогает понять, действительно ли процесс запускается с теми группами, которые вы ожидаете.
Обычных
ls -l иногда недостаточно. Проверить ACL:getfacl /var/www/site/config.php
Там могут быть дополнительные разрешения для конкретного пользователя или группы.
ls -l файл не всегда помогаетКоманда:
ls -l /var/www/site/config.php
показывает права самого файла.
Но если проблема находится в
/var/www/site, информация о файле сама по себе этого не объяснит.Именно поэтому
namei -l удобен при странных Permission denied: он показывает всю цепочку каталогов, через которую процесс должен пройти до нужного файла.BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8