Bash Days | Linux | DevOps
23.3K subscribers
152 photos
25 videos
665 links
Авторский канал от действующего девопса

Самобытно про разработку, devops, linux, скрипты, сисадминство, техдирство и за айтишную жизу.

Автор: Роман Шубин
Реклама: @maxgrue

MAX: https://max.ru/bashdays

Курс: @tormozilla_bot
Блог: https://bashdays.ru
Download Telegram
Интересный вопрос сегодня залетел:

Если функции, вынесены в файл, подключенный через source, bash каждый раз его будет открывать/перечитывать при обращении к функции? Или как-то считает в память и все?


Давай посмотрим что происходит в кишках в это время. Давненько мы с тобой в strace не смотрели.

Предположим есть bash скрипт с функциями:

functions.sh

#!/bin/bash

bashdays_function() {
echo "Hello from BashDays"
}


И основной скрипт, который сорсит файл с функциями:

test_script.sh

#!/bin/bash

source ./functions.sh

bashdays_function
bashdays_function


Не забываем сделать: chmod +x test_script.sh

Запускаем test_script.sh под контролем strace:

strace -e trace=openat ./test_script.sh


И видим:


openat(AT_FDCWD, "./test_script.sh", O_RDONLY) = 3
openat(AT_FDCWD, "./functions.sh", O_RDONLY) = 3

Hello from BashDays
Hello from BashDays


То есть в контексте скрипта test_script.sh, файл с функциями был прочитан один раз.

При втором вызове функции всё считалось из памяти.

Если бы файл functions.sh читался каждый раз, то мы увидели бы несколько строчек openat(AT_FDCWD).

Грубо говоря при каждом запуске test_script.sh, подключенный файл через source будет прочитан один раз.

Вроде очевидно, но порой заставляет задуматься.

Изучай!

tags: #bash #debug #linux

🔔 @bashdays➡️ @gitgate
Please open Telegram to view this post
VIEW IN TELEGRAM
166625
Здрасти. Как-то я писал про strace и как применять инъекции. Если пропустил, то читай тут и тут.

Ниже скрипт который автоматически пронумерует системные вызовы для последующих инъекций.

#!/usr/bin/perl

use strict;
use warnings;

my %numbs;
select STDERR;
while(<STDIN>) {
if( /^[0-9]++\s++([a-z0-9_]++(?=\())/ ) {
my $t = ++$numbs{$1};
s/\s+/ \e[31m$t\e[m /;
die $! if( keys %numbs == 1000 );
}
print;
}
exit(0);


Сохраняем это безобразие в файл num_syscalls и делаем chmod +x, ну а дальше запускаем в связке с strace:

strace -o'|./num_syscalls' -yf sh -c 'ls|cat'


Теперь получаем такой выхлоп:

456107 48 close(3</usr/) = 0
456107 52 rt_sigreturn({mask=[]})
456107 63 openat(AT_FDCWD</usr/local/sbin>)
456107 53 newfstatat(3)
456107 64 openat(AT_FDCWD</usr/local/sbin>)


Смотрим второй столбик, включаем логику и видим, что системные вызовы нумеруются.

Например, возьмем системный вызов openat, видим 63, 64. Это значит что openat был вызван 64 раза. А newfstatat 53.

Теперь берем нужный номер системного вызова и применяем инъекцию. Как это сделать и для чего, опять же показывал на примерах (ссылки в начале этого поста).

Тема крутая, не нужно ебаться и считать руками.

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

А чтобы получить только трассировку, можно сделать так:

strace -o'|./num_syscalls' -yf ls > /dev/null


Если бесит подсветка, выпили из перловского скрипта управляющий символ «\e[31m\[em».

Такие дела, изучай!

tags: #linux #debug

🔔 @bashdays➡️ @gitgate
Please open Telegram to view this post
VIEW IN TELEGRAM
113415
Service или Systemctl?

Каждый стартует или стопает процессы как привык. Во времена динозавров когда еще не существовало systemd, всё сводилось к командам:

service apache restart


Сейчас мир изменился, появился systemd, но привычки остались прежними.

Теперь сервисами рекомендуется управлять через systemctl.

Но какая в хуй разница как управлять сервисам?

И service и systemctl выполняют по сути одно и тоже и ты по итогу получаешь ожидаемый результат.

На самом деле сейчас команда service это обёртка вокруг systemctl.

Давай убедимся:

strace -f service nginx restart 2>&1 | grep execve


По итогу ты получишь:

execve("/usr/bin/systemctl", ["systemctl", "restart", "nginx.service"])


То есть service обратился к systemctl и произвел перезапуск nginx.

А если глянуть исходник service:

cat /usr/sbin/service


То ты увидишь обычный bash скрипт в котором и происходит вся эта магия.

Если система использует SysVinit или Upstart (например, старые версии Debian, CentOS 6, Ubuntu 14.04), то service будет работать напрямую со скриптами /etc/init.d/.

По бест-практикам старайся использовать systemctl. Но опять же service никто не запретит тебе использовать.

Хотя когда молодые ребята видят как ты вводишь service, у них разрывает жопу и ты автоматически присваиваешь себя к престарелым скуфам.

Как это делаю я? И так и сяк, мне вообще похуй, главное достигнут ожидаемый результат.

Кроме systemctl и service есть такие штуки для управления процессами:

/etc/init.d/nginx restart
rc-service nginx restart
sv restart nginx
s6-rc -d change nginx
supervisorctl restart nginx


А чтобы проверить какая система инициализации у тебя, вот тебе пиздатая команда:

ps -p 1 -o comm=


Вот и вся наука.

А как ты рулишь сервисами? Как скуф или как современный молодой человек?

tags: #linux #debug #strace

🔔 @bashdays➡️ @gitgate
Please open Telegram to view this post
VIEW IN TELEGRAM
88
Если тебе необходимо изменить файл сервиса в Linux, не нужно пиздовать ручками в папку /etc/systemd искать и править его.

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


А бест-практика уже заложена в systemctl!

Достаточно выполнить команду:

sudo systemctl edit nginx


Откроется редактор с нужным сервисом. НО, редактировать ты будешь не корневой юнит, а override. То есть прокладку, которая переопределит параметры основного юнита.

В моём случае с nginx будет открыт файл:

/etc/systemd/system/nginx.service.d/override.conf

В нем я переопределяю нужные параметры для сервиса и НЕ трогаю основной файл юнита.

А если я хочу править корневой?

Да похуй, вот тебе команда:

sudo systemctl edit --full nginx


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

1. Копирует оригинальный юнит-файл из /lib/systemd/system/nginx.service в /etc/systemd/system/nginx.service

2. Открывает его в редакторе.

3. После сохранения — systemd использует именно эту копию в /etc/.

Это безопасный способ редактировать полные юниты, без риска перезаписи при обновлении пакетов.

Есть еще ключ --force, но про него погугли сам.


Как проверить валидность файла юнита?

systemd-analyze verify /etc/systemd/system/nginx.service


В ответ получишь:

/etc/systemd/system/nginx.service:31: Missing '=', ignoring line.


Ага, ошибочка, правим и только после этого можно делать:

sudo systemctl daemon-reload
sudo systemctl restart nginx


Короче учись работать правильно и всё у тебя будет хорошо!

С пятницей! Хороших тебе предстоящих выходных и береги себя!

🛠 #linux #tricks #debug #systemd

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
8211
Сегодня будем профилировать Linux сервисы.

В прошлый раз мы с тобой посмотрели, как без хуйни обращаться с юнитами.

Сегодня же посмотрим, как определить какие юниты дольше всего грузятся при старте системы.

Это достаточно пиздатый хак, особенно при отладке медленного запуска системы. Ну и изобретать нам ничего не придется, всё уже придумали за нас.


Запускаем и смотрим:

systemd-analyze blame


По итогу получаем список юнитов и их время запуска.

17.084s docker.service
10.961s systemd-journal-flush.service
7.595s containerd.service
7.496s cloud-final.service
7.189s cloud-init-local.service
3.260s apt-daily-upgrade.service
2.522s cloud-init.service
2.095s dpkg-db-backup.service
1.991s networkd-dispatcher.service
1.963s chrony.service


В моём примере дольше всего грузится служба с докером.

Отрубаем службу и радуемся приросту в 17 секунд. Но НЕТ! На самом деле тут всё немного сложнее.

Иногда сам юнит стартует достаточно быстро, но сука ждет другой юнит, который «Блиц — скорость без границ».

Смотрим цепочку зависимостей:

systemd-analyze critical-chain


└─docker.socket @13.269s +8ms
└─sysinit.target @13.261s
└─cloud-init.service @10.735s +2.522s
└─systemd-networkd-online.service @12.806s +31ms
└─systemd-networkd.service @12.741s +59ms


Ага, то есть дело тут не только в юните докера, юнит ждет другой юнит, в нашем случае докер ждет пока на сервере поднимется сетка.

То есть docker.service зависит от systemd-networkd-wait-online.service, ну и дальше пошли по цепочке.

Почему так?

Docker по умолчанию может иметь After=network-online.target, а при использовании systemd-networkd это приводит к ожиданию systemd-networkd-wait-online.service.

А чё делать?

Выйти из айти, купить яхту и поехать ловить крабов.

Если тебе нужно, чтобы Docker НЕ ждал сеть, поменяй юнит:

sudo systemctl edit docker.service


[Unit]
After=network.target
Wants=network.target


Ну или вообще убрать After=network-online.target, если нет зависимости от сети на старте.

После этого снова смотрим выхлоп через blame:

4.739s cloud-init-local.service
4.041s containerd.service
2.329s dev-sda1.device


Всё блядь! Теперь докер не тормозит загрузку, но после загрузки системы докер исправно работает.

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

Забирай в копилку, для дебага маст-хев!

🛠 #linux #tricks #debug #systemd

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
3128
Как безопасно тестировать юниты

Так, мы уже знаем как безопасно переопределять юниты, как их дебажить и ну и так по мелочи.

Но не знаем как всё это дело тестировать. А для тестирования тебе пригодится ключик --runtime.

Ключ --runtime — создаёт временное переопределение, которое исчезнет после перезагрузки.


Идеально подходит, если ты тестишь, не уверен или не хочешь ничего портить.

А вот и сама команда:

SYSTEMD_EDITOR=vim systemctl edit --runtime nginx


Как ты мог заметить, зачастую по умолчанию открывается nano редактор, но у многих с ним проблемы. Поэтому в команде сразу втыкаем нужный редактор, например mcedit или vim.


Всё! Теперь правим юнит, тестируем. Временный файл с override будет создан тут /run/systemd/.

И самое главное — все изменения сохранятся только до следующей перезагрузки системы.

А какая в этом польза?

1. Для временного изменения конфигурации systemd без затрагивания оригинального файла.

2. Для тестов или отладки (например, сменить ExecStart для nginx только на время текущей сессии).

3. Чтобы не создавать перманентные изменения, которые могут повлиять на стабильность после перезагрузки.

Пример с nginx

Хочу временно запустить nginx с другим конфигом! Создаю рантайм.

[Service]
ExecStart=
ExecStart=/usr/sbin/nginx -c /home/user/nginx-bashdays.conf


Перезапускаю nginx и радуюсь результату, теперь nginx запущен с другим конфигом.

А почему ExecStart идет дважды?

Хе брат, это очередная приколюха systemd. Оно очищает предыдущее значение ExecStart из основного юнита. А следующая строка задаёт новое значение.

Без этой хуйни systemd бы просто добавил вторую команду, не заменив первую.

Думаю на этом можно закончить. Я неочевидную базу выдал, а ты стал еще сильнее. Изучай и ничего не бойся!

🛠 #linux #tricks #debug #systemd

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
156
Как разгребать гавно в юнитах

Ладно, не гавно, а override файлы, которых может быть нихуя не одна штука. Причем несколько для одного юнита.

Не знаю нахуя так делают, но временами такое встречается. Особенно на каких-то легаси серверах, которые работают со времен фидонета.

Короче, все что нам нужно это команда:

sudo systemctl revert nginx


Эта команда отменяет все локальные изменения юнита nginx и возвращает его к состоянию по умолчанию, заданному в оригинальных unit-файлах, поставляемых системой или пакетом.

Короче достаточно прикольная штука, когда нахуевертил, но не знаешь как откатиться.

Команда удаляем override-файлы в:

/etc/systemd/system/nginx.service.d/
/etc/systemd/system/nginx.service


И следом будет использован оригинальный unit-файл, который обычно расположен в /lib/systemd/system/nginx.service.

Когда это полезно?

Если ты вносил изменения через systemctl edit, вручную правил nginx.service, или добавлял drop-in'ы — и хочешь откатиться к дефолтной конфигурации, revert делает это безопасно.

Наглядные примеры приводить не буду, думаю и так все понятно, что было и что будет.

Ну а ты носи в это в голове и никогда не бойся экспериментировать. Любой факап — твой опыт.

Предыдущие посты на тему systemd ищи по тегу #systemd


🛠 #linux #tricks #debug #systemd

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
463
Почему в юнитах не работает Environment.

Сразу пример:

[Service]
Environment="FOO=bar"
ExecStart=/bin/bash /usr/local/sbin/bashdays.sh


Сделал юнит, но в скрипт bashdays.sh не передаётся переменная FOO. Хотя логически всё правильно.

Тут снова приколы.

➡️ 1. Скрипт запускается не напрямую, а через интерпретатор.

Если в ExecStart указан скрипт с shebang’ом (#!/bin/bash), systemd запускает его как отдельный процесс, и переменные окружения передаются.

Но если ты укажешь в ExecStart сам интерпретатор, вот так:

ExecStart=/bin/bash /usr/local/sbin/bashdays.sh


То переменная FOO, заданная через Environment=, не попадёт в подскрипт, потому что ExecStart запускает bash, а уже bash запускает — скрипт, и переменные окружения само собой нихуя не передаются.

Как правильно?

Вот так:

[Service]
Environment="FOO=bar"
ExecStart=/usr/local/sbin/bashdays.sh


А сам скрипт:

#!/bin/bash

echo "FOO is $FOO"


Всё! Теперь переменная из юнита будет корректно передаваться в скрипт.

➡️ 2. Использовать EnvironmentFile=

Если у тебя дохуя переменных, то выносим их в отдельный файл, например сюда /etc/bashdays-service.env.

FOO=bar
BAZ=qux


А в самом юните прописываем:

[Service]
EnvironmentFile=/etc/bashdays-service.env
ExecStart=/usr/local/sbin/bashdays.sh


Всё! Теперь переменные считываются из файла и скрипт их видит.

➡️ 3. Передавать переменные прямо в ExecStart

[Service]
ExecStart=/bin/bash -c 'FOO=bar exec /usr/local/sbin/bashdays.sh'


Тут особо и комментировать нечего, всё очевидно.

Как отладить и задебажить?

А вот так:

systemctl show nginx


Вместо nginx подставляешь свой сервис и наблюдаешь все переменные которые передались. В наших примерах увидишь Environment=FOO=bar.

Справедливо не только для собственных юнитов, но и для других, например для того же nginx или docker.

Как вариант, можешь добавить в скрипт такую хуйню:

env > /tmp/env.log


И смотришь, какие переменные реально передаются в твой скрипт.

Еще одна база выдана, вроде очевидные вещи, но так глубоко в это никто не лезет. Пользуйся.

🛠 #linux #tricks #debug #systemd #bash

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
786
Три скрытых героя systemd

А вот и сами герои: timer, path и socket

Это невидимые юниты systemd, которые могут заменить cron, inotify`и даже `xinetd.

➡️ 1. Timer

Про timer ты наверняка слышал. Это альтернатива crontab. Позволяет запускать сервис по расписанию. Вместо того чтобы писать крон-джобы, ты создаёшь .service и .timer.

Пример:

/etc/systemd/system/backup.service

[Unit]
Description=Backup job

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/backup.sh


/etc/systemd/system/backup.timer

[Unit]
Description=Run backup daily

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target


Активируем:

sudo systemctl enable --now backup.timer


Persistent=true гарантирует запуск пропущенной задачи, если система была выключена.

Всё! Никаких тебе легаси кронтабов, все работает на юнитах, запускается бэкапилка, тикает таймер.

➡️ 2. Path

Работает как inotifywait или File Watcher: следит за файлами/папками и запускает сервис при изменении.

Пример:

/etc/systemd/system/upload.path

[Unit]
Description=Watch upload folder

[Path]
PathModified=/var/www/bashdays/upload
Unit=process-upload.service

[Install]
WantedBy=multi-user.target


/etc/systemd/system/process-upload.service

[Unit]
Description=Process uploaded files

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/process-upload.sh


Теперь каждый раз когда в папке /var/www/bashdays/upload что-то меняется, автоматически запускается скрипт process-upload.sh.

Тут уже от твоих предпочтений зависит, как и с чем это скрещивать, но кейсов можно придумать довольно дохуя. Например, проверка антивирусом, или запуск какого-нибудь ffmpeg.


➡️ 3. Socket

Заменяет ручной systemctl start, активируя сервис при первом подключении к сокету. Аналог inetd/xinetd

Нихуя не понятно, но на примере сейчас всё прояснится.

Пример:

/etc/systemd/system/echo.socket

[Unit]
Description=Echo socket

[Socket]
ListenStream=12345
Accept=yes

[Install]
WantedBy=sockets.target


/etc/systemd/system/echo@.service

[Unit]
Description=Echo service

[Service]
ExecStart=/usr/bin/nc -l -p 12345 -e /bin/cat
StandardInput=socket


В примере, сервис НЕ работает постоянно, он стартует, только когда кто-то подключается к порту 12345.

Когда ты используешь .socket с Accept=yes, systemd открывает сокет сам и ждёт подключения. А когда подключение приходит — создаёт новый экземпляр сервиса, подставляя туда данные об этом соединении. После завершения — сервис умирает. Всё очень экономно и прозрачно.

Как понять, какой экземпляр стартует?

systemd запускает echo@<ID>.service, где <ID> — уникальный идентификатор подключения (например, PID или номер сокета).

journalctl -u echo@*


Зачем нужны скрытые юниты?

1. Не нужен cron, всё централизовано в systemd.
2. Не нужен inotify-tools.
3. Сервисы не висят без дела, запускаются только когда нужно
4. Журналирование через journalctl

Вот такие пироги. В повседневной работе я применяю timer и path, с сокетами как-то сильно не приходилось париться.

Ну а ты попробуй хотя бы перетащить свои кронджобы в timer и порадоваться бест-практикам.

🛠 #linux #tricks #debug #systemd #bash

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
9106
Продолжаем ковырять systemd

Для того чтобы потыкать systemd не обязательно обладать рутовскими правами или судой-мудой. Да, ты не ослышался, всё можно запускать под обычным юзером.

Делается это так:

systemctl --user enable bashdays.timer


Ключ --user означает, что команда применяется в пользовательском пространстве, а не на уровне всей системы.

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

Эта штука создаёт символическую ссылку в ~/.config/systemd/user/ в директории default.target.wants/

Если тебе нужно разово запустить такой юнит, то enable меняем на start. Вот и вся наука.

А как отлаживать?

➡️ 1. Сначала нужно убедиться что таймер активен:

systemctl --user status bashdays.timer


Если всё ок, то увидишь Active: active (waiting).

➡️ 2. Смотрим расписание запуска

systemctl --user list-timers


- NEXT — когда следующий запуск
- LEFT — сколько осталось времени
- LAST — когда запускался в прошлый раз
- UNIT — имя таймера

➡️ 3. Проверяем выполнялся ли сервис

journalctl --user-unit bashdays.service --since "1 hour ago"


Команда покажет логи выполнения скрипта. Можно сузить диапазон, например так:

journalctl --user-unit bashdays.service --since today


По дебагу это основное. Мелочи вроде синтаксических ошибок и т.п. рассматривать не будем, тут уже от кривизны рук все зависит.

Ну а так всё что нужно тебе знать. То есть у каждого пользователя могут быть свои юниты и сервисы, а не только у рута.

🛠 #linux #tricks #debug #systemd #bash

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
362
Продолжаем разбирать systemd на кирпичики. Сегодня про кандишены (условия/ситуации).

Директива ConditionPathExists используется в юнит-файлах systemd и позволяет задать условие для запуска юнита: он будет запущен только если указанный файл или директория существует.

Пример:

[Unit]
Description=Special Service
ConditionPathExists=/etc/bashdays-config

[Service]
ExecStart=/usr/local/bin/bashdays-handler


Этот юнит не запустится, если файл /etc/bashdays-config не существует. В статусе сервиса ты увидишь:

ConditionPathExists=/etc/bashdays-config was not met


Если путь НЕ существует, systemd не будет запускать юнит. Вместо этого он будет считаться пропущенным (skipped), а не проваленным.

Нахуя это надо?

1. Например, если bashdays-config существует, запускается сервис с особым поведением.

2. Можно создавать один юнит, который активируется только при наличии определённого модуля или плагина.

3. Иногда это используют в early boot-юнитах, чтобы запускать их только если что-то доступно (например, том LUKS).

Список основных Condition's (нажми на спойлер)

ConditionPathExists — файл или каталог существует
ConditionPathIsDirectory — путь существует и это каталог
ConditionPathIsMountPoint — путь является точкой монтирования
ConditionFileIsExecutable — файл существует и он исполняемый
ConditionKernelCommandLine — есть ли параметр ядра с указанным значением
ConditionPathExistsGlob — совпадает ли хотя бы один путь по glob-шаблону
ConditionPathIsSymbolicLink — является ли путь символической ссылкой
ConditionFileNotEmpty — существует ли файл и не пуст ли он
ConditionEnvironment — установлена ли переменная окружения
ConditionArchitecture — архитектура CPU (например, x86_64, aarch64)
ConditionVirtualization — nип виртуализации (например, kvm, docker)
ConditionHost — имя хоста
ConditionMachineID — cовпадает ли machine-id
ConditionControlGroupController — есть ли указанный cgroup controller (например, cpu, memory)
ConditionNeedsUpdate — нуждается ли в обновлении (/usr или /etc)
ConditionFirstBoot — Первый ли это запуск после установки
ConditionACPower — Подключено ли питание от сети (для ноутбуков)
ConditionSecurity — Активен ли определённый LSM (например, selinux)
ConditionUser — Запускается ли от указанного пользователя
ConditionGroup — Запускается ли от указанной группы
ConditionCapability — Имеет ли процесс определённую capability
ConditionNetwork — Есть ли сеть (online, configured)
ConditionMemory — Есть ли минимум указанного объёма памяти


Дополнительно

Если заменить Condition на Assert, условие не выполнено — юнит считается проваленным, а не пропущенным.

То есть берем к примеру директиву ConditionPathExists и меняем её на AssertPathExists.

AssertPathExists=/etc/bashdays.conf


И получается:

ConditionPathExists — юнит пропускается (не считается ошибкой)

AssertPathExists — юнит падает (считается ошибкой)

Assert полезен, когда ты строго требуешь, чтобы ресурс (например, внешний диск, NFS, или другой том) был смонтирован перед запуском сервиса. Если его нет — это ошибка, а не «ну и похуй».

[Unit]
Description=Start backup script only if /mnt/backup is mounted
AssertPathIsMountPoint=/mnt/backup

[Service]
ExecStart=/usr/local/bin/backup.sh


Если /mnt/backup не смонтирован, systemd выдаст ошибку, и сервис не запустится.

Статус будет такой:

systemd[1]: Starting backup.service...
systemd[1]: backup.service: Failed with result 'assert'.


Ну и все это дело можно комбинировать:

ConditionPathExists=/mnt/backup
AssertPathIsMountPoint=/mnt/backup


Если /mnt/backup не существует — юнит пропускается.

Если существует, но не смонтирован — юнит заваливается.

Короче systemd не такой уж простой, как кажется с первого взгляда. На нём можно прям заебись логику построить и получить желаемое. Так что недооценивать его явно не стоит, это прям заебись комбайн.

🛠 #linux #tricks #debug #systemd #bash

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
673
Сегодня будем убивать неугодные сервисы. Нет, тут будет не про kill и т.п. а будет все тот же systemd.

Короче в юните можно указать параметр RuntimeMaxSec, в нем задаём время жизни сервиса. Если сервис работает дольше указанного времени, то ему песда. Systemd принудительно завершит его.

Удобно применять для сервисов, которые не должны жить вечно. Нет ничего вечного! Например, временные задачи, вспомогательные демоны, скрипты и т.п.

[Service]
RuntimeMaxSec=30s


0s, 5min, 1h, 2d — интервалы
infinity — отключение лимита

А что будет если время вышло?

Как и написал выше — будет песда! Systemd пошлет SIGTERM. А если сервис сука живучий и не завершился, то в ход пойдет тяжелая артиллерия через TimeoutStopSec, тут уже будет послан SIGKILL. Но его нужно предварительно прописать.

TimeoutStopSec= — это время ожидания корректного завершения сервиса после того, как systemd послал ему сигнал SIGTERM.

[Service]
ExecStart=/usr/bin/python3 /opt/scripts/bashdays-task.py
RuntimeMaxSec=60


Этот сервис будет убит через 60 секунд после запуска — даже если скрипт ещё не завершился.

[Service]
ExecStart=/usr/bin/bashdays-daemon
RuntimeMaxSec=60
TimeoutStopSec=10


Если bashdays-daemon работает дольше 60 секунд → SIGTERM
Ждём до 10 секунд → если не завершился → SIGKILL

Частый паттерн

RuntimeMaxSec=300
TimeoutStopSec=5
Restart=on-failure


Сервис работает максимум 5 минут, и если завис — systemd подождёт 5 секунд на аккуратное завершение, а потом прибьёт его нахуй.

А нахуя тут Restart=on-failure?

Оно говорит — «если сервис завершился аварийно — перезапусти его» А завершение по SIGKILL из-за превышения времени жизни это и есть failure.

Если не указать TimeoutStopSec, будет использоваться значение по умолчанию: 90 секунд — порой это дохуя, поэтому предпочтительнее задать его руками.

Важный нюанс!

Если в юните используется Restart=always, то после убийства, сервис будет перезапущен и возможно сразу «помрёт» если не изменит своё поведение.

Такие дела, изучай!

🛠 #linux #tricks #debug #systemd #bash

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
661
reexec VS reload

Порой народ путает команды systemctl daemon-reload и systemctl daemon-reexec.

С виду вроде похожие, но нет. Спросил тут на досуге товарища — а ты знаешь чем они отличаются?

Да, ответил товариш, reexec это старая версия перечитывания сервисов и юнитов. Я обычно делаю так:

systemctl daemon-reexec
systemctl daemon-reload
systemctl enable node_exporter
systemctl start node_exporter


Неее… так не нужно! Это хуйня! По крайней мере первая команда тебе тут не нужна для перезапуска и обновления сервисов.

Команда systemctl daemon-reexec перезапускает сам systemd, это нужно например при обновлении бинарников systemd, но никак не для перезапуска юнитов и сервисов.

После редактирования *.service / *.timer / *.mount файлов, достаточно сделать daemon-reload, эта команда перечитает unit-файлы.

Обычно проходится по каталогам:

/etc/systemd/system/
/lib/systemd/system/
/usr/lib/systemd/system/
/run/systemd/system/


То есть она перезагружает только конфигурацию юнитов, без перезапуска сервисов.

Так что не путай, в большинстве случаев тебе достаточно daemon-reload.

🛠 #linux #tricks #debug #systemd

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
870
Если ты выполняешь план, то тебе повысят план, а не зарплату!

Пытался вчера подружить taskwarrior и s3 для синхронизации, из коробки оно вроде как только с AWS работает.

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

sync.backend=s3
sync.aws.region=ru-7
sync.aws.bucket=taskwarrior
sync.aws.access_key_id=aed38518013b4ab
sync.aws.secret_access_key=992570bad57
sync.aws.endpoint=s3.ru-7.storage.selcloud.ru


Проверяю task sync init, хуй там плавал, ошибка:

unhandled error: dispatch failure: io error: error trying to connect: dns error: failed to lookup address information: Name or service not known: dns error: failed to lookup address information: Name or service not known: failed to lookup address information: Name or service not known


Мде… Всёж правильно, эндпоинт пингуется, курлится, телнетится. Описывать весь момент дебага не буду, но там конкретный такой - метод тыка был.

Ну раз обычный «метод тыка» не помогает, расчехляем strace!

strace -s 200 -f -e trace=network,connect,sendto,recvfrom task sync


Что делает команда:

-s 200 — печатать до 200 байт строковых аргументов (по умолчанию strace режет строки до 32 байт). Это важно, чтобы увидеть полный URL/hostname, который передаётся в syscalls.

-f — следить не только за основным процессом, но и за всеми дочерними (fork/clone)

-e trace=network, connect, sendto, recvfrom — ограничиваем вывод только сетевыми вызовами: socket, connect → создание сокетов и подключения (TCP/UDP). sendto / recvfrom → передача данных (обычно видно DNS-запросы, HTTP-заголовки и т.д.).

И в выхлопе находим строчку: taskwarrior.s3.ru-7.amazonaws.com.

😀😃😄😁😅😂🤣😊
😇🙂🙃😉😌😍🥰😘
😗😙😚😋😛😝😜🤪
🤨🧐🤓😎🤩🥳😏😒
😞😔😟😕🙁☹️😣😖
😫😩🥺😢😭😤😠😡

Ну ёб твою мать! А нахуй я тогда все эти приседания с конфигом устраивал, если эта падла хуй положила и по task diagnostic никаких ошибок не вывело.

То есть настройка sync.aws.endpoint=… вообще не учитывается — клиент жёстко строит URL по схеме AWS.

Ну хоть проблему нашли. Strace все же достаточно пиздатый инструмент.

Отсюда вывод: с кастомным S3 напрямую taskwarrior работать не сможет. Даже если устроить подмену хостов или сделать хак через CNAME.

А как синхронизировать-то задачи? Ооо брат, я уже написал про это отдельный пост, чуть позже закину.

Хороших тебе выходных!

🛠 #strace #debug #taskwarrior

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
1356
Ищем баги с помощью strace

Предположим, крутится у тебя в проде какое-то приложение, это приложение было разработано криворукими обезьянами — на отъебись.

По итогу продакшен начинает троить и выжирать процессорное время. Хуита!

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

Запускаем:

strace -c python3 app.py


Через несколько секунд жмём Ctrl-C и получаем статистику:

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ------
99.82 0.413251 8 49431 write
0.07 0.000291 32 9 mmap
0.05 0.000207 25 8 mprotect
0.03 0.000129 21 6 openat
0.02 0.000090 30 3 close
......


Хм… эта падла активно пользуется системным вызовом write().

time — процент процессорного времени, потраченного на вызов.
usecs/call — среднее время на один вызов (в микросекундах).
calls — сколько раз вызов был сделан.

Виновника определили. То есть приложение постоянно что-то куда-то пишет, тем самым забивая 99% процессорного времени.

Важно понимать: strace показывает только то время, которое ядро тратит на обработку системных вызовов. Поэтому значения могут отличаться от того, что покажет команда time:

$ time python3 app.py

real 0m7.412s
user 0m1.102s
sys 0m6.184s


Здесь sys совпадёт с тем, что мы видели через strace -c.

Ну и теперь даже без доступа к исходникам можно быстро понять, где «утекают» ресурсы.


Исходники у нас есть, давай посмотрим:

with open("tmp.txt", "w") as f:
while True:
f.write("Привет супчики! Привет от BashDays!")
f.flush()


Что тут не так:

Из-за flush() Python гонит строку сразу в файловую систему, без буферизации.

Как пофиксить:

# fixed.py
with open("tmp.txt", "w", buffering=1024*1024) as f:
while True:
f.write("Привет супчики! Привет от BashDays!\n")


Теперь данные будут сбрасывать пачками, так как мы указали буферизацию, которая равна 1MB.

Проверяем до фикса:

$ strace -c python3 app.py
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ------
99.8 0.413251 8 49431 write


Проверяем после фикса:

$ strace -c python3 app-fixed.py
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ------
98.9 0.072111 450 160 write


Количество вызовов write() резко сократилось, нагрузка на ядро упала.

Как костыль и быстрофикс — сойдёт! Повторюсь — мы с тобой не обезьяны, чтобы вникать в код разработчиков и что-то в нем «правильно» править.

В большинстве случаев, ты просто находишь проблемы и уже с этими данными создаешь задачу на разработчика. Сам в код не лезь, целее будешь.

Ну и на закуску фикс, который сделали разработчики:

import io

buffer = io.StringIO()

with open("tmp.txt", "w") as f:
while True:
buffer.write("Привет супчики! Привет от BashDays\n")
if buffer.tell() > 1024 * 1024:
f.write(buffer.getvalue())
f.flush()
buffer.seek(0)
buffer.truncate(0)


Как это работает:

1. StringIO хранит текст в оперативной памяти.
2. Цикл гонит туда строки.
3. Когда накопится, например, 1 MB, содержимое сбрасывается в файл одной большой порцией (write + flush).
4. Буфер очищается и цикл продолжается.

Хуй знает на сколько это всё правильно, ну раз сделали через внутреннию буферизацию StringIO, значит так правильно.

Такие дела. Изучай.

🛠 #debug

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
10875
Как не стремись к автоматизации, всегда найдется какой-нибудь легаси сервис, который требует ручного обслуживания.

Был у меня такой сервис и работал он только тогда, когда все его файлы и каталоги принадлежали определенному пользователю.

Доступ к сервису имели многие, поэтому люди порой троили и запускали команды в каталоге сервиса от root. Сервису было на это поебать, но до момента его перезапуска.

Обычно это чинилось очень легко, через chown -R. Все это знали и никого это не смущало. Короче костыль ебаный.

Казалось бы, есть масса способов предотвратить такие ошибки: правильные права на файлы, ACL’ы, SELinux.

Но веселья в этом мало! Поэтому я решил заебенить свой собственный мониторинг файловой системы. Скиллов то предостаточно, хули нет.

Спойлер: Я залез в кроличью нору и знатно так хлебнул гавна.

Попытка намбер 1

В Linux есть API под названием fanotify, с его помощью можно получать события о действиях с файлами в юзерспейс.

Всё просто: инициализируем fanotify_init, указываем каталоги через fanotify_mark и читаем события из дескриптора.

Но тут же вылез огромный хуй:

- нельзя отслеживать каталоги рекурсивно (только целый маунт)
- anotify даёт только PID процесса, который что-то сделал. А чтобы узнать UID/GID — нужно лезть в /proc/<pid>/status. То есть на каждое событие приходится открывать и парсить файлы в /proc.

Решение вполне рабочее, но громоздкое. Я такие не люблю, этож думать надо, вайбкодингом не решается.

Попытка намбер 2

Вспоминаем что есть eBPF. Это штука позволяет запускать программы прямо в ядре Linux. Они компилируются в байткод, проходят проверку, а потом гоняются через JIT почти с нативной скоростью.

Что такое eBPF можешь почитать тут и тут.


В eBPF заебись то, что можно цепляться за разные функции ядра. Например, можно подцепиться к vfs_mkdir или vfs_create — это общий слой для работы с файлами.

То есть единая точка входа. Там можно отлавливать события и фильтровать их, не гоняя лишние переключения контекста.

Но и тут вылез хуй:

- kprobes на функции VFS нестабильны, в новых ядрах сигнатуры могут меняться или функции вообще исчезнуть.

- фильтрацию приходится писать прямо в eBPF, а там свои ограничения. Нет бесконечных циклов, стек всего ~512 байт.

Да блядь…

Как я победил рекурсивный обход

Чтобы понять, что именно меняется в каталоге сервиса, пришлось использовать структуру dentry и подниматься по дереву до родителя.

Но так как в eBPF нельзя сделать «бесконечный» цикл, я ограничил глубину с помощью MAX_DEPTH.

На практике этого вполне достаточно. Глубина каталогов мне известна. Ну и конечно, пришлось аккуратно работать с RCU-локами, чтобы дерево не поменялось в момент обхода.

➡️ Кусок кода в первом комментарии, сюда не влез.


Как можно улучшить

В идеале использовать не VFS-хуки, а LSM hooks (Linux Security Module).

Они стабильнее, понятнее и позволяют сразу работать с путями. Там можно красиво получить path и сразу преобразовать его в строку, а потом делать поиск подстроки.

Но в моём ядре этих хуков не было, хуй знает почему, видимо дистрибутив слишком древний. Надо попробовать на новых, чем черт не шутит.

Итоги

Эта поделка как и предполагалась, погрузила меня в печаль, душевные страдания, НО стала отличным тренажером для прокачки:

- Внутренностей Linux ядра
- Работы с eBPF
- И кучу другого с kernel-space

eBPF — мощнейший инструмент, но очень тонкий. Ошибёшься — будешь выебан в жопу.


Информации по нему много, но вся она разбросана. Собрать всё это в кучу было отдельным квестом.

Мораль?

Иногда самое простое решение — это chown -R. Но куда интереснее — написать свой велосипед и заглянуть в кроличью нору Linux ядра.

🛠 #linux #debug #dev

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
947
Настройка core dump в Docker

Цель этого поста — дать тебе общее руководство по включению и сбору core dumps для приложений, работающих в docker контейнере.

Настраиваем хост для сохранения дампов

Для начала надо сконфигурировать хостовую машину, чтобы сохранять такие дампы в нужное место. Нет, не в жопу.

Универсальный способ — задать шаблон core pattern. Шаблон определяет путь и информацию о процессе который наебнулся.

echo '/tmp/core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern


Кратенько:

%e — имя процесса
%p — pid процесса

Более подробно о конфигурации core pattern можешь почитать в man-странице ядра Linux.


Как вариант, можно настроить host изнутри контейнера через CMD или ENTRYPOINT. Но контейнер в этом случае должен запускаться в privileged mode, что хуева с точки зрения безопасности.

Пример хуёвого приложения

#include <cstdlib>
void foo() {
std::abort();
}

int main() {
foo();
return 0;
}


После компиляции и запуска, приложение наебнется с ошибкой.

Пишем Dockerfile для этого приложения

FROM ubuntu:22.04

# Install tools
RUN apt update \
&& apt -y install \
build-essential \
gdb \
&& rm -rf /var/lib/apt/lists/*

# Build the application
COPY ./ /src/
WORKDIR /src/
RUN g++ main.cpp -o app

CMD ["/src/app"]


Тот кто в теме, сможет его прочитать и понять. Если не понятно, гугли и разбирай, качнешь свой девопсовый скиллз.


Запускаем контейнер с нужными опциями:

docker run \
--init \
--ulimit core=-1 \
--mount type=bind,source=/tmp/,target=/tmp/ \
application:latest


Разбираем опции:

--init — гарантирует корректную обработку сигналов внутри контейнера

--ulimit — устанавливает неограниченный размер core dump для процессов внутри контейнера

--mount — монтирует /tmp из хоста в контейнер, чтобы дампы, создаваемые внутри контейнера, были доступны после его остановки или удаления

Здесь важно: путь source на хосте должен совпадать с тем, который задан в шаблоне core pattern.

После того как приложение наебнется, core dump будет сохранён на хостовой машине в директории /tmp.

ls /tmp/core*
# /tmp/core.app.5


Анализируем дамп с помощью gdb

Такс, мы получили core dump и он у нас лежит на хостовой машине, но рекомендуется открыть его внутри контейнера. Контейнер должен быть из того же образа, в котором компилилось приложение.

Это поможет убедиться, что все зависимости (библиотеки и прочая хуитень) доступны.

docker run \
-it \
--mount type=bind,source=/tmp/,target=/tmp/ \
application:latest \
bash


Если в образе нет исходного кода, можно допом смаунтить исходники:

docker run \
-it \
--mount type=bind,source=/tmp/,target=/tmp/ \
--mount type=bind,source=<путь_к_исходникам_на_хосте>,target=/src/ \
application:latest \
bash


Теперь внутри контейнера запускаем:

gdb app /tmp/core.app.5


После загрузки дампа можно выполнить команду bt (backtrace), чтобы увидеть трассировку стека:

(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51
#1 0x00007f263f378921 in __GI_abort () at abort.c:79
#2 0x000055f9a9d16653 in foo() ()
#3 0x000055f9a9d1665c in main ()


Команда поможет определить, где именно произошел факап.

Давай быстренько разберем ошибку.

#0 и #1 показывают, что процесс получил сигнал 6 (SIGABRT) и завершился через abort()

#2 — вызов произошёл внутри функции foo()

#3main() вызвал foo()

В исходнике было:

void foo() {
std::abort();
}


То есть ошибка здесь не баг компиляции или рантайма, а намеренно вставленный вызов std::abort(), который и приводит к аварийному завершению и генерации core dump.

Если у тебя docker-compose, то все флаги (--init, --ulimit, --mount и т.д.) применимы и для него. То есть отладку можно легко адаптировать.

Хуй знает чё еще написать, завтра тему дебага продолжим, чет в конце года много траблшутинга навалило разнообразного.

🛠 #linux #debug #dev

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
545
Здесь: я как-то поднимал проблему с торможением 1c на postgres.

🔤🔤🔥🔤🔤🔤🔤

Благодаря нашему коллеге @ovchinnikovmy, дело сдвинулось с мертвой точки. Спасибо ему большое за консультации и рекомендации по PG.

Мы начали попытки оптимизировать работу postgres для нашей задачи. И сразу столкнулись с проблемой. Ну, оптимизировали. А насколько?

Улучшение есть, а кто был виноват в тормозах PG или 1С?

Все может прекрасно работать в тестах, и становится колом, когда идет интенсивная работа в нескольких базах одновременно. Где горлышко бутылки - число ядер, частота или скорость диска, или может пора памяти добавить?

Там маленькая конторка. Фактически один сервак. Не будешь же zabbix ради этого ставить.

Онлайн можно посмотреть через nmon, top/htop. nmon даже позволяет записывать данные в лог, и есть программа, которая позволяет генерить html с отчетами, но там все интегрально. По системе. А хочется по процессам.

Остановился на пакете sysstat. Это такой консольный zabbix. Он позволяет собирать статистику по процессам. Анализировать можно память, проц, диск, стэк. Причем по каждому PID в отдельности и прямо в консоли. В общем, все, что нужно. Для большего удобства я набросал скрипт.

#!/bin/bash

# 20251005
# apt install sysstat gawk
# работа с 9 до 18, запись с 8:30 до 18:30
# запуск через cron
# 30 8 * * * /root/work/stat/stat.sh &

declare -x PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
declare -i INTERVAL_SEC=10
declare -i COUNT=3600 # итого 10 часов
declare -i WEEK_DAY;printf -v WEEK_DAY "%(%-u)T"
declare LOG="$0_${WEEK_DAY}.csv"

pidstat -r -l -d -H -h -U -u $INTERVAL_SEC $COUNT |
gawk 'NR<4;$2=="usr1cv83"||$2=="postgres"{$1=strftime("%Y%m%d_%H%M%S",$1);print}'>"$LOG"


Он собирает статистику каждые 10 сек по двум пользователям postgres (PG) и usr1cv83 (1С) в csv-лог (разделитель пробел, но это можно исправить).

Поскольку лог текстовый, дальше его можно вертеть с помощью awk/sort или просто в LibreOffice Calc.

pidstat ключи:

-r - память
-l - командная строка процесса
-d - диск
-h - табличный режим
-H - время unix
-U - username
-u - проц

gawk ключи:

NR<4 - заголовок (легенда) из трех строк
$2=="usr1cv83"||$2=="postgres" - фильтрация по username
$1=strftime("%Y%m%d_%H%M%S",$1) - удобный формат времени.

LOG="$0_${WEEK_DAY}.csv" - Недельная ротация. По одному на день.

🛠 #debug #linux

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
39
Фиксим кривой Exit Code в docker

Во время работы с docker контейнером наткнулся на неочевидный код выхода (exit code).

Про exit codes (коды выхода) писал тут


Суть проблемы: Когда программа внутри контейнера падает с abort(), Docker возвращает неправильный код выхода. Вместо ожидаемого 134 (128 + SIGABRT), контейнер отдаёт 139 (128 + SIGSEGV).

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

Давай проверим:

#include <cstdlib>

int main() {
std::abort();
return 0;
}


Пишем Dockerfile:

FROM ubuntu:22.04

RUN apt-get update \
&& apt-get -y install \
build-essential \
&& rm -rf /var/lib/apt/lists/*

COPY ./ /src/
WORKDIR /src/
RUN g++ main.cpp -o app

WORKDIR /
CMD ["/src/app"]


Собираем и запускаем:

docker build -f ./Dockerfile -t sigabort_test:latest .
docker run --name test sigabort_test:latest ; echo $?


А на выходе у нас код: 139.

В примере выше код выхода — 139 = 128 + 11, где 11 соответствует SIGSEGV (ошибка сегментации), а не 134 = 128 + 6, что был бы SIGABRT (аварийное завершение).

Чтобы это пофиксить, нужно захерачить хак:

CMD ["bash", "-c", "/src/app ; exit $(echo $?)"]


docker run --name test sigabort_test:latest ; echo $?
bash: line 1: 6 Aborted /src/app
134


После этого контейнер будет возвращать корректный код 134.

Вариант рабочий, но костыльный. Правильнее использовать ключ --init.

Если запустить контейнер с флагом --init, используя исходную команду CMD ["/src/app"], мы получим ожидаемый 134 код. Что нам и нужно.

docker run --init --name test sigabort_test:latest ; echo $?

134


Почему init все починил?

Давай копнём глубже. В Linux процесс с PID 1 (init) имеет нестандартную семантику сигналов:

- Если у PID 1 для сигнала стоит действие «по умолчанию» (никакого обработчика), то сигналы с действием terminate игнорируются ядром. Это сделано, чтобы случайным SIGTERM/SIGINT нельзя было «уронить» init.

- PID 1 должен забирать зомби-процессы (делать wait() за умершими детьми). Если этого не делать, накопятся зомби.

- PID 1 обычно пробрасывает сигналы дальше — тому «настоящему» приложению, которое оно запускает.

Когда мы запускаем контейнер без --init, приложение становится PID 1.

Большинство обычных приложений (на C/C++/Go/Node/Java и т.д.) не написаны как «инит-системы», они не настраивают обработку всех сигналов, не занимаются «реапингом» детей и не пробрасывают сигналы. В результате вылазиют баги.


Наш сценарий с abort() (который поднимает SIGABRT) упирается именно в правила для PID 1. abort() внутри процесса поднимает SIGABRT.

Для обычного процесса с PID ≠ 1 это приводит к завершению с кодом 128 + 6 = 134. Но если процесс — PID 1, ядро игнорирует «терминирующие» сигналы при действии по умолчанию. В результате стандартные ожидания вокруг SIGABRT ломаются.

Ну а дальше вступают в силу детали реализации рантайма/сишной библиотеки, как именно контейнерный рантайм считывает статус.

На практике это может приводить к тому, что ты видишь 139 (SIGSEGV) вместо ожидаемого 134 (SIGABRT).

И тут проблема не в docker, а в том, что приложение неожиданно оказалось в роли init-процесса и попало под его особые правила.

Вот и вся наука. Изучай.

🛠 #docker #devops #linux #debug

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
567