Служба получает не тот сокет из нескольких
Один сокет может объявлять несколько слушателей, и служба получает их все по порядку. Без имён различить, какой из них какой, можно только по порядку объявления — и любая правка описания ломает работу службы.
Вероятные причины
По порядку: сверху то, что встречается чаще.
-
Слушатели не имеют имён
Служба получает набор описателей и обычно берёт первый. Порядок задаётся описанием, и правка меняет соответствие.
-
Служба не умеет работать с несколькими описателями
Многие программы рассчитаны ровно на один переданный сокет. Остальные они игнорируют.
-
Порядок слушателей изменился при правке
Добавление строки в начало описания меняет порядок описателей, и служба начинает слушать не то, что нужно.
Диагностика
Команды идут в том порядке, в котором их стоит выполнять: каждая следующая проверяет то, что осталось после предыдущей.
Слушатели и имя набора описателей.
systemctl show myapp.socket -p ListenStream -p FileDescriptorNameКакие порты фактически слушаются.
sudo ss -tlnp | grep -E ":8080|:8443"Что служба сообщает о полученных сокетах.
journalctl -u myapp.service -n 20 --no-pagerРешение
Решение своё для каждой причины. Сначала определите, какая из них ваша, — иначе правки наложатся друг на друга.
- Почему происходит
- Служба получает набор описателей и обычно берёт первый. Порядок задаётся описанием, и правка меняет соответствие.
- Как проверить
-
Посмотрите объявленные слушатели и имя набора.
systemctl cat myapp.socket | grep -E "^(Listen|FileDescriptorName)" systemctl show myapp.socket -p FileDescriptorName
- Как исправить
-
Разделите слушатели по отдельным сокетам с именами: тогда служба получит их вместе с именами и порядок перестанет иметь значение.
[Socket] ListenStream=8080 FileDescriptorName=http
- Почему происходит
- Многие программы рассчитаны ровно на один переданный сокет. Остальные они игнорируют.
- Как проверить
-
Посмотрите число переданных описателей и поведение службы.
systemctl show myapp.socket -p NAccepted -p ListenStream journalctl -u myapp.service -n 20 --no-pager | grep -iE "listen|fd"
- Как исправить
- Передавайте службе ровно один слушатель. Если нужны разные порты, делайте отдельные сокеты и отдельные службы.
- Почему происходит
- Добавление строки в начало описания меняет порядок описателей, и служба начинает слушать не то, что нужно.
- Как проверить
-
Сравните порядок в описании и фактическое поведение.
systemctl cat myapp.socket | grep -nE "^Listen" sudo ss -tlnp | grep -E ":8080|:8443"
- Как исправить
- Задайте имена наборам описателей: тогда порядок в файле перестанет влиять на работу.
Пример вывода
Служба взяла первый описатель и слушает не тот порт. Пример показательный: он собран на тестовой машине специально для этой страницы, а не взят из чужого журнала.
$ systemctl cat myapp.socket | grep ^Listen
ListenStream=8443
ListenStream=8080
myapp[9300]: listening on inherited fd 3 (expected plain HTTP)
myapp[9300]: TLS handshake failed: first record does not look like a TLS handshake
Связанные ошибки
- Служба не получает сокет при сокет-активации Сокет активен, служба запускается, но не находит переданный дескриптор. Разбор: Accept, FileDescriptorName, соответствие имён.
- Сокет без Listen: unit загружен, но ничего не слушает В .socket не задан ни один параметр Listen…= — сокет не принимает соединения. Как объявить адрес прослушивания.
- Как проверить сокет-активацию до выкладки Отладка сокет-активации: systemd-socket-activate позволяет проверить программу без создания unit-файлов.
- Сокет активен, службы нет: обращения повисают Порт открыт, но обращения ничем не обслуживаются: соответствующая служба отсутствует или замаскирована.
- Bad file descriptor в журнале службы Ошибка 9 при операции с файлом: дескриптор закрыт или не тот. Разбор для служб и сокет-активации.
- File exists при создании файла или сокета Ошибка 17: объект уже существует. Разбор для сокетов, файлов блокировки и каталогов службы.
- MySQL: Can't connect to local server through socket Клиент не находит unix-сокет MySQL: сервер не запущен, путь к сокету другой, каталог в /run не создан.
- Puma слушает сокет, а веб-сервер получает отказ доступа Разъём между приложением и веб-сервером: сокет создаётся с правами приложения, читает его другой пользователь.
Источники
-
systemd.socket(5)
FileDescriptorName= и передача нескольких описателей. -
Воспроизведено на тестовой машине, systemd 255 (Ubuntu 24.04)
Проверено на systemd 255: описатели передаются в порядке объявления.