LinuxSkill - Сводки с прода и Шпаргалки
10.7K subscribers
67 photos
102 videos
12 files
530 links
Следим за новостями Linux, DevOps и ИБ, чтобы быть готовым к любым факапам.
Бонусом — плотные шпаргалки и чеклисты для ежедневной работы в терминале.

📩 По всем вопросам: @chorapov

Зеркало в MAX: https://max.ru/LinuxSkill

РКН https://vk.cc/cMUwm4
Download Telegram
Собрать архив с исключениями и не обнаружить потом, что исключения не сработали.

Исключить каталог из архива


tar --exclude='data/logs' -czf arch.tgz data


Нюанс: --exclude обязан стоять до источника. Поставил после — tar честно предупредил --exclude 'data/logs' has no effect и всё равно упаковал логи. Второе: шаблон сравнивается с тем, как путь записан внутри архива. Упаковываете data, значит исключать надо data/logs, а не /tmp/ta/data/logs — с абсолютным путём совпадения не будет.

Достать один файл из архива


tar -tf arch.tar | grep app.conf
tar -xf arch.tar tmp/ta/data/conf/app.conf


Нюанс: при упаковке по абсолютному пути tar срезает ведущий слеш и пишет Removing leading '/' from member names. Поэтому распаковать по тому же абсолютному пути не выйдет — получите Not found in archive. Сначала посмотрите список через -t, потом копируйте путь оттуда.

Дописать или удалить файл в архиве


tar -rf arch.tar newfile.txt
tar --delete -f arch.tar oldfile.txt


Нюанс: только для несжатых архивов. На .tgz обе команды падают с Cannot update compressed archives и Error is not recoverable: exiting now. Чтобы поправить сжатый архив, его придётся распаковать целиком.

Сжать быстрее и плотнее gzip


tar --zstd -cf arch.tar.zst data


На моих тестовых данных zstd дал 199 байт против 225 у gzip. Нюанс: если пакета zstd в системе нет, tar создаст файл нулевого размера и завершится с кодом 2. Файл при этом лежит на месте и выглядит как архив — в скрипте бэкапа такое заметят не сразу. Проверяйте код возврата, а не наличие файла.

Не перезаписать свежие файлы при распаковке


tar -xf arch.tar --keep-newer-files


Работает: файл, который новее архивного, остаётся нетронутым, в выводе Current 'data/logs/a.log' is newer or same age. Нюанс: код возврата при этом 2, как при ошибке. Под set -e скрипт упадёт, хотя всё прошло по плану.

Про сравнение архива с диском: tar -df arch.tar показывает Mod time differs и Size differs по каждому расхождению. Удобно проверить бэкап, не распаковывая.

А вы чем сжимаете бэкапы — gzip по привычке или уже переехали на zstd?

#Linux #tar #Backup #Bash #DevOps
👍7
Добавить пользователя в группу и не выкинуть его при этом из sudo

Добавить во вторичную группу


usermod -aG developers lisa


Нюанс, ради которого стоит перечитать свои скрипты: без -a флаг -G не добавляет, а заменяет весь список. Было sudo,developers,ops — после usermod -G developers lisa осталось только developers. Ни ошибки, ни предупреждения, код возврата 0. Человек узнает об этом, когда в следующий раз наберёт sudo.

Удалить из группы


gpasswd -d bob developers


Печатает Removing user bob from group developers. В отличие от usermod -G, трогает ровно одну группу.

Сменить GID существующей группы


groupmod -g 2000 developers
find / -xdev -nogroup 2>/dev/null
chgrp -R developers /data


Нюанс: groupmod меняет запись в /etc/group, но файлы на диске остаются со старым числовым GID. У меня после смены ls -ld /data показал вместо имени голое 1004 — каталог стал ничьим. Ищите такие через find -nogroup и чините chgrp -R, иначе доступ к данным потеряется молча.

Создать группу в скрипте


groupadd -f developers
groupadd -r pgsql


Нюанс: обычный groupadd на существующей группе возвращает код 9 и роняет скрипт под set -e. Флаг -f делает операцию идемпотентной — то, что нужно для повторных прогонов Ansible или cloud-init. А -r создаёт системную группу: у меня она получила GID 996, обычная — 2002.

Удалить группу


groupdel testers


Нюанс: если группа у кого-то основная, команда откажет — cannot remove the primary group of user 'bob', код 8. Вторичную удалит без вопросов, и у пользователей она просто исчезнет из списка.

Про проверку: id -nG lisa показывает актуальный список из базы, а groups в чужой открытой сессии — то, что было на момент входа. Пользователь не увидит новых групп, пока не перелогинится или не выполнит newgrp.

А вы группы раскатываете Ansible-модулями или скриптом с groupadd -f?

#Linux #Security #Ansible #DevOps #Bash

---

Что нашлось при проверке

Главную ловушку воспроизвёл. Завёл lisa в sudo, developers, ops. После usermod -G developers lisa осталась одна developers, код возврата 0, ни слова в выводе. Исходник называет это в «честном минусе», но в самом пункте 7 стоит только скобка «обязательно -a» — я вынес разбор в тело, потому что это главное содержание всей темы.

Находка сверх исходника: `groupmod -g` оставляет файлы без группы. Сменил GID у developers с 1004 на 2000 — каталог /data стал показывать 1004 вместо имени. Формально данные не потеряны, но группа к ним больше не имеет отношения. Исходник даёт команду без единой оговорки. Добавил find -nogroup и chgrp -R как лечение.

`groupadd -f` для идемпотентности. Обычный вызов на существующей группе — код 9. В скриптах инициализации, о которых говорит сам исходник, это ровно та причина, по которой люди начинают городить проверки через grep /etc/group. Флага -f в исходнике нет.

`groupdel` на основной группе. cannot remove the primary group of user 'bob', код 8. Проверил обе ветки — вторичная удаляется молча.

`groupadd -r` даёт GID из системного диапазона. У меня 996 против 2002 у обычной. Исходник пишет «GID до 1000» — подтвердилось.

Из пятнадцати пунктов оставил шесть. Убрал groups, id, chgrp, find -group, newgrp, groupmod -n, gpasswd -A как отдельные блоки — по ним нечего добавить сверх справки. Мысль про groups против id вплёл в текст: она объясняет, почему пользователь «не видит» новую группу.

Метрики: объём 2027, подводка 67 символов, форма — инфинитив цели. Строк кода длиннее 60 нет, восклицательных нет, хештегов 5.
👍3
Закрыть периметр правилами iptables и не остаться при этом за дверью вместе с атакующим.

Посмотреть, что слушает наружу


ss -tulpn


Даёт порт, состояние и процесс: 0.0.0.0:8099 users:(("python3",pid=3545,fd=3)). Первое, с чего начинается любой аудит — половина находок обычно в том, что сервис слушает 0.0.0.0 вместо 127.0.0.1.

Найти, кто держит больше всего соединений


ss -Hntu | awk '{print $5}' |
sed 's/:[^:]*$//' | sort | uniq -c | sort -rn


Нюанс: в шпаргалках этот однострочник идёт без -H и с cut -d: -f1. Оба места ломаются. Без -H в подсчёт попадает строка заголовка, и в топе появляется несуществующий адрес Local. А cut по первому двоеточию режет IPv6: адрес [2001:db8::2]:54321 превращается в [2001. Отсечение через sed берёт только порт с конца.

Закрыть вход по умолчанию


iptables -A INPUT -m conntrack \
--ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -P INPUT DROP


Нюанс: в подборках порядок обратный — сначала -F, потом -P INPUT DROP, а разрешающие правила ниже. Между этими двумя строками сессия уже мертва, и до правил вы не дойдёте. Политика ставится последней, всегда.

Заодно -m state там устаревший, актуален -m conntrack --ctstate.

Ограничить SSH подсетью и числом сессий


iptables -A INPUT -p tcp --dport 22 \
! -s 10.0.0.0/24 -j DROP
iptables -A INPUT -p tcp --dport 22 \
-m connlimit --connlimit-above 3 -j REJECT


Оба правила встали. Полезная деталь: iptables -S показывает, что модуль сам дописал --connlimit-mask 32 --connlimit-saddr — лимит считается по одному адресу, а не по подсети. Если нужен другой охват, маску задавайте явно.

Отправить подсеть в никуда без файрвола


ip route add blackhole 192.168.10.0/24


Нюанс: это не тихий дроп. Локальные попытки получают отлуп сразу — ping вернул connect: Invalid argument, curl завершился с кодом 7. Для исходящего трафика это плюс, ошибка приходит мгновенно вместо таймаута, но маскировки не ждите.

И общее: всё это живёт до перезагрузки. netfilter-persistent для правил, ip route — в конфигурацию сети, иначе следующий ребут откроет всё обратно.

А вы вредоносные IP баните iptables напрямую или отдаёте это CrowdSec и fail2ban?

#Linux #Security #iptables #Networking #DevOps
👍4
Разобрать строку штатным <regex.h> и не получить молчаливое «нет совпадения».

Проверял на glibc, gcc 13.3, Ubuntu 24.04. Все фрагменты ниже собраны в одну программу и скомпилированы с -Wall -Wextra без единого предупреждения.


Скомпилировать шаблон

regex_t re;
int rc = regcomp(&re, "^([a-z]+)=([0-9]+)$",
REG_EXTENDED);


REG_EXTENDED включает ERE: скобки и + работают без обратных слешей. Без флага получите BRE, где группа пишется как \(...\).


Понять, почему не скомпилировалось

char err[256];
if (rc != 0) {
regerror(rc, &re, err, sizeof err);
fprintf(stderr, "regcomp: %s\n", err);
}


Буфер обязан быть массивом. На незакрытой скобке [a-z получите Unmatched [, [^, [:, [., or [=. Нюанс: regfree вызывайте только после успешного regcomp, для неудачной компиляции освобождать нечего.


Сопоставить и достать группы

regmatch_t m[3];
const char *s = "app=100";
if (regexec(&re, s, 3, m, 0) == 0)
printf("%.*s\n", (int)(m[1].rm_eo - m[1].rm_so),
s + m[1].rm_so);


m[0] — всё совпадение, m[1] и дальше — группы по порядку. На app=100 вышло app в первой группе и 100 во второй. Нюанс: смещения в байтах, не в символах. В строке ключ=42 вторая группа начинается с байта 9, хотя символов перед ней пять. И если компилировали с REG_NOSUB, массив m не заполняется вовсе.


Не забыть освободить

regfree(&re);


Замерил: 100 тысяч компиляций в цикле без regfree раздули процесс до 1.4 ГБ, с ним — 1.8 МБ. В демоне, который компилирует шаблон на каждый запрос, это вопрос часов.


Где POSIX молчит вместо ошибки

Самое неприятное не в том, чего API не умеет, а в том, как он об этом сообщает:

\d+     на "abc 123" -> No match
<.*?> на "<a><b>" -> <a><b>
(?<=x)y -> Invalid preceding regular expression


Просмотры и именованные группы падают на компиляции — это честно. А вот \d компилируется и тихо ничего не находит, вместо него нужен [0-9]. И .*? не ошибка, а просто жадный .*: вернул всю строку вместо <a>. Привычки из PCRE здесь ломают логику молча.

А вы в C-утилитах держитесь <regex.h> или сразу тянете PCRE2?

#Linux #C #POSIX #regex #DevOps
🔥 Знаешь только ip a? 10 неочевидных команд ip для Linux

Большинство админов знают про ip ровно две команды — ip a и ip r, а дальше ifconfig по старой памяти. Держи 10 команд, которые покажут то, чего не видно в ip a: скрытые маршруты VPN, дропы на интерфейсе и мёртвый шлюз. Проверил на iproute2 6.1.

1️⃣ Вся сеть одним экраном, в цвете:

ip -br -c a

Интерфейс, состояние и адреса — по строке на каждый. -br работает и с link.

2️⃣ Куда реально уйдёт пакет:

ip route get 8.8.8.8

# какая запись в таблице сработала
ip route get fibmatch 8.8.8.8

Покажет интерфейс, шлюз и source-адрес. Незаменимо, когда маршрутов много и непонятно, какой победил.

3️⃣ Маршруты, которых нет в ip route:

ip rule
ip route show table all

ВАЖНО: ip route показывает только таблицу main. wg-quick с AllowedIPs = 0.0.0.0/0 уводит дефолт в таблицу 51820, а ip route делает вид, что всё как раньше.

4️⃣ Сменить шлюз без риска потерять сервер:

sudo ip route replace default via <gw>

Атомарно: нет окна без маршрута, как между del и add. Упадёт с ошибкой — старый маршрут останется на месте.

5️⃣ Дропы и ошибки на интерфейсе:

ip -s link show <iface>

# двойной -s — ошибки с разбивкой: crc, frame, fifo
ip -s -s link show <iface>

Растут errors или dropped — копай в кабель, драйвер или буферы, а не в приложение.

6️⃣ Жив ли шлюз на L2:

ip neigh show dev <iface>

REACHABLE и STALE — норма. Шлюз висит в FAILED или INCOMPLETE — он не отвечает на ARP, и пинговать дальше бесполезно.

7️⃣ Сеть в прямом эфире:

ip -ts monitor link route

Каждое изменение с меткой времени: упал линк, DHCP или VPN переписали маршрут. Оставь в tmux и лови, кто ломает сеть по ночам.

8️⃣ Отрезать IP без фаервола:

sudo ip route add blackhole 203.0.113.5/32

# проверить и убрать
ip route show type blackhole
sudo ip route del blackhole 203.0.113.5/32

Ответы на этот адрес уходят в никуда, TCP-сессия с ним не установится. ip route get по нему вернёт Invalid argument — так ядро отвечает на blackhole, это не ошибка.

9️⃣ IP интерфейса в скрипте без awk-акробатики:

ip -j -4 addr show dev <iface> \
| jq -r '.[0].addr_info[0].local'

-j отдаёт JSON: парсинг не сломается, когда поменяется формат вывода. Нужен пакет jq.

🔟 Процесс без сети за две команды:

sudo ip netns add lab
sudo ip netns exec lab <command>

# убрать за собой
sudo ip netns del lab

В новом namespace только выключенный lo. Удобно проверить, как приложение переживёт падение сети, не трогая хост.

Всё, что меняешь через ip, живёт до перезагрузки. Постоянные настройки — в netplan, NetworkManager или systemd-networkd.

А какую из десяти ты уже держишь в алиасах?

❗️ Нравится формат? Ставь 👍

👉 Рубрика: #шпаргалка@LinuxSkill

#Linux #Networking #iproute2 #CheatSheet #SysAdmin #DevOps
👍18🔥2
💀 CPU под 100%, а в top пусто? Ловим невидимые процессы через eBPF

Сервер греется, в шапке top us под 90%, а в списке процессов — никого. Классика: кто-то плодит процессы, которые живут 10–50 мс, а top опрашивает /proc раз в 3 секунды и их просто не застаёт. Показываю, как поймать виновника, не роняя прод.

Ловим каждый запуск

# 1. В Ubuntu утилита идёт в пакете bpftrace
sudo apt install bpftrace

# Каждый execve() в реальном времени:
# время, PID и PPID родителя.
sudo execsnoop.bt

TIME            PID   PPID  ARGS
16:55:42.684175 1112 1097 /bin/true
16:55:42.685129 1113 1097 curl --version
16:55:42.691294 1114 1097 sleep 0.05


Один и тот же PPID и новые процессы каждые 50 мс — вот он, виновник.


Кто плодит больше всех

# 2. Считаем запуски по родителю за 10 секунд.
# Подсчёт идёт ПРЯМО в ядре, без потока событий.
sudo bpftrace -e '
tracepoint:syscalls:sys_enter_execve
{ @[comm, curtask->real_parent->tgid] = count(); }
interval:s:10 { exit(); }'

# Вывод: @[bash, 1097]: 525
# bash с PID 1097 за 10 секунд породил 525 процессов.

# 3. Смотрим, кто это и откуда он взялся
pstree -sp \<PPID>
ps -o pid,etime,cmd -p \<PPID>



Почему не strace -f
Проверил: dd с bs=1 под strace работает в ~47 раз медленнее. И в ~37 раз — даже если трассировать только accept(), которого dd вообще не вызывает: ptrace останавливает процесс на КАЖДОМ системном вызове. eBPF процесс не останавливает и считает события в ядре.


ВАЖНО: bcc-версия execsnoop-bpfcc компилирует программу на лету и без linux-headers-$(uname -r) падает с «Unable to find kernel headers». execsnoop.bt работает через BTF, заголовки ему не нужны. Есть ли BTF в ядре: ls /sys/kernel/btf/vmlinux.


Вместо «CPU горит, а кто — непонятно» получаешь имя процесса, аргументы и PID родителя. Дальше дело техники: поправить крон или скрипт.


❗️ Нравится формат? Ставь 👍
👉 Рубрика: #шпаргалка@LinuxSkill
#Linux #eBPF #bpftrace #Troubleshooting #SysAdmin #DevOps
👍5🔥5
📊 Диск тормозит, а iostat показывает норму? Ищем хвост задержек

Приложение подвисает на записи, в логах таймауты к БД, а iostat рисует w_await в пару миллисекунд. Подвох в том, что iostat показывает среднее: 990 запросов по 0.1 мс и 10 по 200 мс дают в среднем 2 мс. Показываю, как достать хвост, который тонет в среднем.

Что на самом деле показывает iostat

# 1. -x — расширенные колонки, раз в секунду
iostat -x 1
# r_await и w_await — СРЕДНЕЕ за интервал:
# редкие зависшие запросы в нём растворяются


ВАЖНО: %util под 100% на SSD, NVMe и RAID не значит, что диск упёрся: они обслуживают запросы параллельно. Это прямо написано в man iostat.

Гистограмма вместо среднего

# 2. В Ubuntu утилита идёт в пакете bpftrace.
# Ctrl+C — и получаешь распределение задержек.
sudo biolatency.bt

@usecs:
[16, 32) 426043
[32, 64) 15436
[64, 128) 527
...
[1K, 2K) 24
[2K, 4K) 17
[4K, 8K) 13


Проверил на тестовой нагрузке (столбики убрал, чтобы влезло в экран): iostat в тот же момент показывал w_await 0.02 мс, а в хвосте висели запросы по 4–8 мс — в сотни раз дольше среднего.

Чей I/O висит

# 3. Каждый запрос дольше 10 мс — с процессом.
# Порог подстрой: для HDD 10 мс — норма.
# На tracepoint'ах: не сломается с новым ядром.
sudo bpftrace -e '
t:block:block_bio_queue {
@ts[args->sector] = nsecs;
@who[args->sector] = comm;
}
t:block:block_rq_complete /@ts[args->sector]/ {
$ms = (nsecs - @ts[args->sector]) / 1000000;
if ($ms >= 10) {
printf("%s %d ms\n", @who[args->sector], $ms);
}
delete(@ts[args->sector]);
delete(@who[args->sector]);
}
END { clear(@ts); clear(@who); }'


Видишь kworker или jbd2 вместо своего процесса — это отложенная запись из page cache. Настоящего писателя покажет pidstat -d 1.

Среднее говорит «всё хорошо», гистограмма показывает хвост, а третья команда — чей он. Дальше — по месту: вынести бэкап, развести нагрузку или идти к провайдеру с цифрами.

❗️ Нравится формат? Ставь 👍
👉 Рубрика: #шпаргалка@LinuxSkill
#Linux #eBPF #Storage #Troubleshooting #SysAdmin #DevOps
👍8
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
GitOps-практики: развёртываем сервис через ArgoCD

Классический CI/CD запускает деплой. Но как сделать так, чтобы состояние Kubernetes после него соответствовало конфигурации в Git? На вебинаре разберём принципы GitOps и посмотрим, как ArgoCD помогает управлять изменениями в кластере.

7 октября в 20:00 МСК на открытом вебинаре OTUS развернём сервис в Kubernetes с помощью ArgoCD и покажет работу подхода на практике.

На практике рассмотрим:
— чем GitOps отличается от классического CI/CD и когда его применять;
— как устроен ArgoCD и какие есть варианты реализации GitOps;
— как развернуть сервис в Kubernetes через ArgoCD;
— что произойдёт при изменении конфигурации напрямую в кластере;
— какие подходы к работе с ArgoCD полезны в проектах.

После вебинара вы поймёте принципы GitOps, познакомитесь с возможностями ArgoCD и увидите полный процесс развёртывания сервиса. Полученные подходы сможете применить при работе со своими Kubernetes-кластерами.

Урок проходит в преддверии старта курса «DevOps. Экспертный уровень».

👉 Регистрируйтесь: https://otus.pw/wyNA/

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
👎1
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
💀 df показывает 100%, а du молчит? 5 шагов спасения прода

Сервисы падают с «No space left on device», df -h рисует 100%, а du -sh /* насчитывает куда меньше. Классика: кто-то удалил гигантский лог, а процесс держит его открытым — файла уже нет, а блоки заняты. Показываю, как вернуть место без рестарта сервиса.

Шаг 1. Что именно кончилось
df -h /var
# Место есть, а пишет «No space left»? Кончились inode:
df -i /var

При 100% в df сервисы от root ещё пишут в резерв ext4, а остальные уже падают.

Шаг 2. Ищем удалённые, но открытые файлы
# +L1 — файлы без единой ссылки в ФС.
# Точка монтирования в конце = только эта ФС.
sudo lsof -nP +L1 \<mountpoint>

COMMAND PID  FD  SIZE/OFF  NAME
python3 690 3w 73400887 /mnt/t/app.log (deleted)


Шаг 3. PID и номер дескриптора
3w в колонке FD — дескриптор 3, открыт на запись. Проверка:
sudo ls -l /proc/\<PID>/fd/\<FD>
# ... -> /mnt/t/app.log (deleted)

Шаг 4. Обнуляем файл без рестарта
# ВАЖНО: sudo : > файл НЕ сработает —
# перенаправление делает твой шелл, а не sudo.
sudo truncate -s 0 /proc/\<PID>/fd/\<FD>

Проверил на тестовой ФС: 141 МБ вернулись мгновенно, процесс продолжил писать. Только для логов и мусора — усечёшь файл данных БД, потеряешь данные.

Подвох: если сервис пишет без O_APPEND (например, запущен как app > file.log), он продолжит с прежнего смещения. Размер снова покажет 70 МБ, хотя на диске файл занимает 4 КБ: он станет разреженным, и мониторинг по размеру будет врать.

Шаг 5. Аварийный запас на ext4
# Резерв под root, по умолчанию 5%
sudo tune2fs -l /dev/\<disk> | grep -i "reserved block"
# Временно отдать 4% — работает на лету
sudo tune2fs -m 1 /dev/\<disk>

После уборки верни -m 5: резерв нужен, чтобы root мог чинить систему на полном диске. Только ext2/3/4, на XFS не сработает.

lsof +L1 пуст, а расхождение осталось? Файлы могут прятаться под точкой монтирования: смонтируй корень через mount --bind в пустую папку и запусти du там.

Чтобы не повторилось — logrotate с postrotate, который просит сервис переоткрыть лог, вместо ручного rm.

❗️ Нравится формат? Ставь 👍
👉 Рубрика: #шпаргалка@LinuxSkill
#Linux #Troubleshooting #Storage #SysAdmin #DevOps
👍5