ru
Feedback
BashTex | Linux

BashTex | Linux

Открыть в Telegram

Авторский канал для тех, кто хочет глубже погрузиться в мир Linux. Подойдет для разработчиков, системных администраторов и DevOps Реклама: @dad_admin

Больше
2 515
Подписчики
+324 часа
+127 дней
-330 дней

Загрузка данных...

Похожие каналы
Нет данных
Возникли проблемы? Пожалуйста, обновите страницу или обратитесь к нашему support-менеджеру .
Входящие и исходящие упоминания
---
---
---
---
---
---
Привлечение подписчиков
окт. '26
октябрь '26
+18
в 0 каналах
сентябрь '26
+7
в 0 каналах
Get PRO
август '26
+54
в 11 каналах
Get PRO
июль '26
+16
в 1 каналах
Get PRO
июнь '26
+38
в 5 каналах
Get PRO
май '26
+8
в 0 каналах
Get PRO
апрель '26
+14
в 0 каналах
Get PRO
март '26
+22
в 0 каналах
Get PRO
февраль '26
+18
в 0 каналах
Get PRO
январь '26
+85
в 5 каналах
Get PRO
декабрь '25
+122
в 19 каналах
Get PRO
ноябрь '25
+93
в 2 каналах
Get PRO
октябрь '25
+107
в 7 каналах
Get PRO
сентябрь '25
+197
в 20 каналах
Get PRO
август '25
+16
в 1 каналах
Get PRO
июль '25
+130
в 13 каналах
Get PRO
июнь '25
+57
в 4 каналах
Get PRO
май '25
+70
в 1 каналах
Get PRO
апрель '25
+728
в 23 каналах
Get PRO
март '25
+129
в 6 каналах
Get PRO
февраль '25
+235
в 9 каналах
Get PRO
январь '25
+497
в 19 каналах
Get PRO
декабрь '24
+134
в 8 каналах
Get PRO
ноябрь '24
+819
в 17 каналах
Дата
Привлечение подписчиков
Упоминания
Каналы
07 октября0
06 октября+4
05 октября+14
04 октября0
03 октября0
02 октября0
01 октября0
Посты канала
Ephemeral ports - почему заканчиваются исходящие TCP-порты Когда приложение устанавливает исходящее TCP-соединение, порт выбирается автоматически. Это ephemeral port, временный исходный порт клиента. Например:
10.0.0.10:49152 → 142.250.74.14:443
Если приложение создаёт тысячи соединений, диапазон таких портов может стать узким местом. ▪️Посмотреть доступный диапазон В Linux его задаёт:
cat /proc/sys/net/ipv4/ip_local_port_range
Например:
32768 60999
Это около 28 тысяч портов для исходящих соединений с одного локального IP. ▪️Но количество соединений не всегда ограничено этим числом TCP-соединение определяется не одним портом, а набором:
source IP
source port
destination IP
destination port
protocol
То есть:
10.0.0.10:50000 → 1.1.1.1:443
10.0.0.10:50001 → 1.1.1.1:443
это разные соединения. Но тот же локальный порт теоретически может использоваться снова, если меняется destination tuple. Поэтому несколько destination IP/портов значительно расширяют пространство комбинаций. ▪️Как увидеть исходящие соединения
ss -tan state established
А чтобы посмотреть локальные порты:
ss -tan | awk 'NR>1 {print $4}' | sort | uniq -c | sort -nr | head
Если приложение создаёт огромное количество короткоживущих TCP-соединений, дополнительно стоит посмотреть TIME-WAIT:
ss -tan state time-wait | wc -l
▪️Почему TIME_WAIT особенно неприятен После закрытия TCP-соединения локальный порт не всегда сразу можно использовать для нового соединения. Это нужно для защиты TCP от старых сегментов предыдущего соединения. При большом количестве коротких запросов можно получить ситуацию:
requests
   ↓
тысячи TCP connections
   ↓
close()
   ↓
TIME_WAIT
   ↓
исчерпание доступных комбинаций
В итоге новое connect() начинает получать ошибки вроде:
Cannot assign requested address
▪️Практический сценарий Проблема часто появляется у прокси, API-клиентов, NAT-шлюзов и сервисов, которые вместо переиспользования соединений постоянно создают новые. Поэтому сначала стоит проверить:
ss -s
cat /proc/sys/net/ipv4/ip_local_port_range
ss -tan state time-wait | wc -l
А уже потом решать проблему: расширять диапазон, использовать connection pooling или уменьшать количество коротких TCP-соединений. Важно: увеличение ip_local_port_range не создаёт новые IP-адреса и не отменяет остальные ограничения TCP. BashTex 📱 #bash #utils

2
name_to_handle_at() - как получить файловый объект без обычного пути В Linux обычно обращаются к файлу через путь: /etc/hosts Но ядро умеет работать с файловым объектом иначе. Системный вызов name_to_handle_at() позволяет получить специальный file handle, связанный с объектом filesystem. Это используется низкоуровневыми инструментами, которым нужно сохранить ссылку на объект, а не его текстовый путь. ▪️Получаем handle Сам системный вызов выглядит примерно так: int name_to_handle_at( int dirfd, const char *path, struct file_handle *handle, int *mount_id, int flags ); Например, программа может передать: /etc/hosts и получить структуру с handle и идентификатором mount. Важно: это не файловый дескриптор. Handle не позволяет просто сделать read(). ▪️Зачем тогда он нужен Основная идея - получить устойчивое представление объекта внутри filesystem, которое затем можно использовать для повторного обращения к нему. Для этого существует парный системный вызов: open_by_handle_at() Схема получается такой: path ↓ name_to_handle_at() ↓ file handle + mount ID ↓ open_by_handle_at() ↓ file descriptor То есть сначала filesystem выдаёт идентификатор объекта, а позже по нему можно снова получить FD. ▪️Почему это отличается от обычного пути Представим: /var/data/report Путь зависит от directory entries. Файл могут переименовать: mv /var/data/report /var/archive/report Сам inode при этом остаётся тем же объектом. File handle предназначен именно для работы с объектом filesystem, а не с конкретным именем в каталоге. ▪️Но есть важное ограничение Handle не является универсальным идентификатором любого файла на всех filesystem. Его формат зависит от filesystem, а поддержка механизма должна быть реализована самой filesystem. Кроме того, open_by_handle_at() требует соответствующих привилегий. Обычное приложение не получает возможность произвольно открывать объекты по filesystem handles. ▪️Где это реально встречается Механизм особенно интересен для низкоуровневых инструментов резервного копирования, мониторинга и работы с filesystem, где обычный путь может быть неудобен или недостаточно надёжен. И главное: name_to_handle_at() не «обходит filesystem по inode». Он просит конкретную filesystem предоставить handle для объекта, а затем этот handle может быть использован через соответствующий kernel API. BashTex 📱 #bash #utils
227
3
madvise() - как процесс подсказывает ядру, как он собирается использовать память Когда процесс работает с большой областью памяти, ядро не всегда знает, как именно приложение будет обращаться к этим страницам. Linux предоставляет madvise() - системный вызов, через который процесс может сообщить ядру предполагаемый характер использования памяти. Это особенно интересно для mmap() и больших memory-mapped файлов. ▪️Последовательный доступ Если программа собирается читать память последовательно: madvise(addr, length, MADV_SEQUENTIAL); ядро получает подсказку, что страницы будут использоваться примерно по порядку. Это позволяет иначе управлять page cache и упреждающим чтением. Например, обработчик большого файла может пройти по нему от начала до конца вместо случайных обращений. ▪️Случайный доступ Для случайного доступа есть: madvise(addr, length, MADV_RANDOM); Если приложение читает страницы в произвольном порядке, агрессивное read-ahead может оказаться бесполезным. Подсказка позволяет ядру учитывать такой сценарий. ▪️Можно сообщить, что страницы больше не нужны Особенно интересен: madvise(addr, length, MADV_DONTNEED); Процесс говорит ядру, что содержимое этой области ему сейчас не требуется. Для анонимной памяти это может позволить освободить физические страницы. Для файлового отображения поведение связано с отображёнными страницами и page cache. Важно: это не то же самое, что free(). Виртуальный адрес всё ещё может оставаться отображённым, но физические страницы могут быть освобождены и восстановлены при следующем обращении. ▪️Есть и подсказка WILLNEED madvise(addr, length, MADV_WILLNEED); Она сообщает ядру, что страницы, вероятно, скоро понадобятся. Это может помочь подготовить данные заранее, например перед обработкой большого memory-mapped файла. Но madvise() именно подсказывает, а не заставляет ядро выполнить конкретную стратегию. ▪️Практический сценарий Представим программу, которая через mmap() обрабатывает несколько гигабайт файла строго последовательно. Вместо случайного поведения с page cache она может сделать: void *p = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); madvise(p, size, MADV_SEQUENTIAL); Теперь приложение явно сообщает ядру характер будущего доступа. А когда большой участок больше не нужен: madvise(p, size, MADV_DONTNEED); Это может уменьшить давление на память, не требуя немедленно уничтожать само отображение. ▪️Важный нюанс madvise() не является универсальной кнопкой «ускорить память». Эффект зависит от конкретного флага, типа mapping, версии ядра и сценария доступа. И особенно важно не путать MADV_DONTNEED с гарантированным физическим освобождением каждой страницы: это рекомендация ядру о том, что содержимое региона можно считать ненужным. То есть madvise() - это интерфейс, через который userspace сообщает kernel memory manager: «я примерно знаю, что собираюсь делать с этой памятью». BashTex 📱 #bash #utils
331
4
flock vs fcntl - почему две блокировки одного файла могут вести себя совершенно по-разному В Linux есть несколько механизмов блокировки файлов. Наиболее часто встречаются flock() и fcntl(). На первый взгляд они делают одно и то же: не дают нескольким процессам одновременно работать с одним файлом. Но внутри ядра это разные механизмы, поэтому блокировки могут вообще не замечать друг друга. ▪️flock блокирует сам файл Простейший вариант: flock /tmp/app.lock -c 'echo "working"; sleep 10' Пока первый процесс держит блокировку, второй: flock -n /tmp/app.lock -c 'echo "working"' получит ошибку и сразу завершится из-за -n. Часто этот механизм используют для защиты cron-задач: flock -n /run/myjob.lock /usr/local/bin/myjob ▪️fcntl работает через диапазоны файла Здесь можно блокировать не только весь файл, но и конкретный диапазон байтов. На уровне C: struct flock lock = { .l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 100 }; fcntl(fd, F_SETLK, &lock); Можно заблокировать первые 100 байт, а другой процесс при этом сможет работать с другим диапазоном. ▪️Главная ловушка Процесс A: flock /tmp/test.lock Процесс B использует fcntl() для того же файла. Они не обязаны конфликтовать. Причина в том, что flock и традиционные POSIX advisory locks через fcntl исторически используют разные пространства блокировок. То есть наличие: flock → locked не означает автоматически: fcntl → blocked И наоборот. ▪️Обе блокировки advisory Ни flock, ни fcntl сами по себе не запрещают процессу открыть и изменить файл. Если приложение вообще не проверяет блокировку, оно может спокойно сделать: echo "data" >> /tmp/file Поэтому механизм работает только тогда, когда все участники системы договариваются его соблюдать. ▪️Есть ещё одна важная разница Для flock блокировка связана с открытым file description. При fork() дочерний процесс наследует соответствующую блокировку. У fcntl семантика другая: POSIX locks связаны с процессом и имеют собственные правила поведения при close(). Из-за этого без понимания модели владения FD легко получить ситуацию, когда один close() неожиданно освобождает блокировку. ▪️Практический вывод Если нужно просто гарантировать, что cron или несколько экземпляров shell-скрипта не выполняются одновременно: flock -n /run/myjob.lock /usr/local/bin/myjob обычно достаточно. Если приложение должно блокировать отдельные диапазоны файла или ему нужна POSIX-модель блокировок, используют fcntl(). И главное: нельзя считать flock и fcntl взаимозаменяемыми только потому, что оба называются file locking. BashTex 📱 #bash #utils
325
5
DEBUG trap - как Bash выполняет код перед каждой командой В Bash есть специальный DEBUG trap, который позволяет выполнить собственный код непосредственно перед выполнением команды. Это удобно не только для отладки. Через него можно посмотреть, какие команды реально выполняет скрипт, с какими аргументами и в каком контексте. ▪️Самый простой пример trap 'echo "CMD: $BASH_COMMAND"' DEBUG echo "hello" mkdir /tmp/test rm -rf /tmp/test Перед каждой командой Bash вызовет trap: CMD: echo "hello" CMD: mkdir /tmp/test CMD: rm -rf /tmp/test $BASH_COMMAND содержит команду, которую Bash собирается выполнить. ▪️Можно добавить PID и функцию trap 'printf "[%s] %s: %s\n" "$$" "${FUNCNAME[1]:-main}" "$BASH_COMMAND"' DEBUG Теперь при выполнении функций можно получить что-то вроде: [4217] main: prepare [4217] deploy: mkdir -p /srv/app [4217] deploy: cp app.conf /srv/app/ Это уже превращается в простой трассировщик Bash-скрипта. ▪️Но DEBUG не означает буквально «перед каждой строкой» Trap срабатывает перед выполнением простых команд, for, case, некоторых условных конструкций и других элементов shell execution. Поведение также зависит от того, включено ли наследование trap внутри функций и subshell. Например: trap 'echo "DEBUG: $BASH_COMMAND"' DEBUG foo() { echo "inside" } foo Чтобы DEBUG распространялся внутрь функций, часто используют: set -T или: shopt -s functrace ▪️У trap есть интересный побочный эффект Сам код внутри DEBUG тоже выполняется Bash, поэтому слишком сложный обработчик может сам стать источником неожиданных эффектов. Для диагностики лучше держать его простым: trap 'printf "%s\n" "$BASH_COMMAND" >> /tmp/bash-debug.log' DEBUG А после диагностики обязательно убрать: trap - DEBUG ▪️Практический сценарий Есть большой deployment-скрипт, который запускает десятки функций и команд. Лог показывает только: deployment failed Вместо того чтобы расставлять echo по всему скрипту, временно включаем: trap 'printf "[%s] %s\n" "$SECONDS" "$BASH_COMMAND"' DEBUG и получаем последовательность реально выполнявшихся команд. Важно: DEBUG предназначен прежде всего для трассировки. Для полноценного production-аудита он неудобен: trap может влиять на поведение скрипта и генерировать очень много вывода. BashTex 📱 #bash #utils
278
6
tee + process substitution: один поток и несколько получателей Иногда нужно одновременно: увидеть вывод команды в терминале, записать его в файл и передать на обработку другой программе Если делать это по очереди - потеряется поток данных. Здесь помогает связка tee + process substitution. ▪️ Что делает tee. tee дублирует поток: command | tee file.log вывод остается в терминале и одновременно пишется в file.log ▪️ Несколько получателей. tee может писать сразу в несколько файлов: command | tee out1.log out2.log Но иногда нужно не просто файл, а другую команду. ▪️ Process substitution. Bash позволяет подставить вывод команды как файл: >(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 📱 #bash #utils
339
7
Архитектура большого bash-скрипта Пока скрипт на 30 строк - все терпимо. На 300 строк начинается хаос. На 1000 - уже никто не понимает, где init, где логика, где cleanup. Чтобы bash не превратился в лапшу, ему нужна архитектура. 📂 Нормальная структура проекта project/ ├── main.sh ├── lib/ │ ├── log.sh │ ├── config.sh │ ├── checks.sh │ └── deploy.sh ├── conf/ │ └── app.conf └── tmp/ Где: main.sh - точка входа lib/ - функции по темам conf/ - конфиги tmp/ - временные файлы ▪️ Деление на модули Не надо держать все в одном файле. Лучше так: source "$(dirname "$0")/lib/log.sh" source "$(dirname "$0")/lib/checks.sh" Примеры модулей: log.sh - логгер config.sh - загрузка переменных checks.sh - проверки окружения actions.sh - основная логика ▪️ Naming: единый стиль Худший вариант: doStuff() x() RunAll() Лучше: log_info() check_dependencies() deploy_app() cleanup_tmp() Для приватных функций можно префикс: _internal_parse_config() ▪️ Поток исполнения. В начале файла: main() { load_config check_dependencies run_tasks } main "$@" Это делает скрипт читаемым как программу, а не как свалку команд. BashTex 📱 #bash #scripts
353
8
BashTex 📱 #мем
BashTex 📱 #мем
520
9
Почему Permission denied: как найти каталог, на котором ломаются права Иногда файл имеет правильные права, пользователь состоит в нужной группе, но Permission denied всё равно появляется. Причина может быть выше по пути: чтобы открыть /var/www/site/config.php, процесс должен иметь право x на каждый каталог в этом пути. Разберем, как быстро найти место, где ломается доступ. 1️⃣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 Сразу видно, на каком уровне пользователь может упереться в права. 2️⃣Почему важен x у каталога Для каталога x означает не «запуск», а возможность проходить через него и обращаться к объектам внутри. Например: chmod 644 /var/www/site Файлы внутри могут быть доступны для чтения, но войти в каталог без x уже нельзя. Проверить можно: sudo -u www-data cat /var/www/site/config.php 3️⃣Проверяем конкретного пользователя Если доступ должен быть у www-data: sudo -u www-data namei -l /var/www/site/config.php А группы: id www-data Это помогает понять, действительно ли процесс запускается с теми группами, которые вы ожидаете. 4️⃣ACL тоже могут менять картину Обычных ls -l иногда недостаточно. Проверить ACL: getfacl /var/www/site/config.php Там могут быть дополнительные разрешения для конкретного пользователя или группы. 5️⃣Почему ls -l файл не всегда помогает Команда: ls -l /var/www/site/config.php показывает права самого файла. Но если проблема находится в /var/www/site, информация о файле сама по себе этого не объяснит. Именно поэтому namei -l удобен при странных Permission denied: он показывает всю цепочку каталогов, через которую процесс должен пройти до нужного файла. BashTex 📱 #bash #systemd
525
10
veth - как Linux соединяет два network namespace Network namespace изолирует сетевой стек процесса. У каждого namespace могут быть свои интерфейсы, маршруты и таблица соседей. Но как соединить два таких изолированных сетевых мира? Для этого в Linux есть veth pair - виртуальный Ethernet-кабель с двумя концами. ▪️Создаём два namespace ip netns add ns1 ip netns add ns2 Теперь создаём пару: ip link add veth1 type veth peer name veth2 Получили: veth1 <──────────> veth2 То, что отправлено в один конец, появляется на другом. ▪️Разносим концы по namespace 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 📱 #bash #systemd
433
11
nohup vs session vs terminal - что реально происходит после закрытия SSH Запустили процесс по SSH: ./backup.sh & Закрыли SSH-сессию - а потом обнаружили, что процесс исчез. Почему? Проблема не в самом SSH. Нужно понимать связь между терминалом, session, shell и сигналами. ▪️Что происходит при SSH-подключении Условно: 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 относится процесс. ▪️Что делает nohup nohup ./backup.sh >backup.log 2>&1 & nohup не создаёт магическую «вечную» копию процесса. Он в первую очередь меняет обработку SIGHUP и перенаправляет стандартные потоки, если они всё ещё связаны с терминалом. Проверить: ps -o pid,ppid,sid,pgid,tty,cmd -p <PID> Процесс всё ещё может находиться в той же session. Но получение SIGHUP уже не должно завершить его обычным способом. ▪️А что такое session Session - это уровень выше 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 📱 #bash #systemd
360
12
Minor и major page fault: почему page fault не всегда означает проблему с диском Когда процесс обращается к виртуальной памяти, нужная страница не всегда уже находится в памяти именно в том состоянии, которое ожидает процесс. Тогда возникает 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 📱 #bash #systemd
371
13
BashTex 📱 #мем
BashTex 📱 #мем
654
14
inotify vs polling: почему постоянный find может быть плохим мониторингом Иногда нужно отследить появление нового файла в каталоге. Самый простой вариант: 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 📱 #bash #systemd
562
15
Sparse files: почему файл на 100 ГБ может занимать несколько мегабайт Размер файла и реально занятое место на диске не всегда совпадают. Можно создать файл, который логически имеет размер 100 ГБ, но физически занимает всего несколько мегабайт. Для этого используются sparse files. ▪️Создаём sparse-файл 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 📱 #bash #systemd
551
16
du vs df: почему Linux показывает разный размер диска Иногда ситуация выглядит странно: df -h / показывает, что занято 80 GB. А: du -sh / находит только 55 GB. Кажется, что Linux «потерял» 25 GB. На самом деле du и df измеряют разные вещи. ▪️Что считает df df смотрит на файловую систему и её блоки: df -h / Он показывает, сколько места файловая система считает занятым и свободным. ▪️Что считает du du обходит дерево каталогов и суммирует пространство, связанное с найденными файлами: 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 📱 #bash #systemd
477
17
file vs расширение - как Linux определяет тип файла на самом деле В 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. ▪️Главный источник - magic bytes Многие форматы имеют характерную сигнатуру в начале файла. Например, 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 📱 #bash #systemd
456
18
BashTex 📱 #мем
BashTex 📱 #мем
700
19
systemd-delta: как найти, чем на самом деле переопределён unit Иногда вы открываете 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 📱 #bash #systemd
587
20
PSI: /proc/pressure/* - как понять, чего реально не хватает системе 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 📱 #perfomance #linux
597