EnvironmentFile в systemd: почему переменная работает в терминале, но не в сервисе
В терминале команда source app.env задаёт путь к конфигурации, и приложение стартует. Тот же файл подключают через EnvironmentFile, но сервис пытается открыть путь с буквальным знаком доллара. Причина в том, что systemd читает файл назначений переменных, а не выполняет сценарий оболочки.
Различие особенно заметно у вычисляемых значений: ссылки на другую переменную, вызовы команд и export привычны в shell-профиле, но не являются универсальным форматом конфигурации окружения. Для systemd лучше передавать уже готовые значения и явно выбирать, где происходят вычисления.
Уберём неоднозначные подстановки
Файл, рассчитанный на shell, мог содержать APP_CONFIG=$APP_ROOT/config.yaml. EnvironmentFile не раскрывает здесь APP_ROOT. Запишите значение целиком в /etc/myapp/app.env:
APP_ROOT=/opt/myapp
APP_CONFIG=/etc/myapp/config.yaml
APP_LABEL="production api"
Файл содержит назначения по строкам без export и команд. Кавычки помогают оформить значение с пробелом, но правила разбора не полностью совпадают с shell. Сохраните UTF-8 и не добавляйте исполняемую логику. Конструкция $(command) останется содержимым значения, а не способом получить результат команды.
Подключим окружение к правильному unit
[Service]
EnvironmentFile=/etc/myapp/app.env
ExecStart=/opt/myapp/bin/server --config ${APP_CONFIG}
Это фрагмент service unit. В ExecStart systemd поддерживает собственную подстановку переменных окружения; форма ${APP_CONFIG} передаёт значение как один аргумент. Эта возможность не означает, что EnvironmentFile стал shell-скриптом. Путь к исполняемому файлу и полная команда должны оставаться корректными для правил ExecStart.
Если перед путём EnvironmentFile поставить минус, отсутствие файла будет игнорироваться. Это удобно для действительно необязательного дополнения, но может скрыть опечатку в обязательной production-конфигурации. Выберите поведение осознанно, чтобы сервис не запускался с неожиданными defaults.
Проверим, что читает manager
systemctl cat myapp.service systemctl show myapp.service -p EnvironmentFiles -p ExecStart -p User journalctl -u myapp.service -b --since '-10 min' --no-pager
Сопоставьте основной unit и drop-in. Другой файл может переопределять переменную или команду. Если указано несколько environment-файлов, порядок тоже важен: поздние значения могут перекрывать ранние. Само наличие строки в одном найденном файле не доказывает, что процесс получил её.
Изменение unit требует перечитать конфигурацию manager штатной процедурой. Изменение содержимого EnvironmentFile обычно не требует daemon-reload само по себе, но уже запущенный процесс не получает новые значения автоматически. Их читает следующий запуск; reload приложения помогает только если именно приложение умеет перечитать нужный источник.
Убедимся в результате без дампа секретов
Для диагностики выберите одно несекретное значение, например путь APP_CONFIG или режим работы, и подтвердите его в сообщении приложения при старте. Полный вывод окружения в общий журнал часто раскрывает лишние данные. Если нужен специальный диагностический запуск, ограничьте его тестовым сервисом и конкретным разрешённым полем.
Окружение процесса не заменяет права на файл конфигурации. Значение может быть правильным, а открытие завершаться Permission denied из-за пользователя unit, каталогов или sandbox-настроек. После подтверждения строки пути проверьте доступ именно из контекста сервиса.
Для секретов оцените механизм credentials systemd или предусмотренное приложением защищённое хранилище. Обычные environment variables распространяются дочерним процессам и не предназначены для любых секретов автоматически. Здесь важно выбрать способ доставки данных, соответствующий их назначению, а не переносить весь личный профиль shell в service.
После исправления выполните согласованный перезапуск в тестовой среде и реальный запрос приложения. Проверьте, что используется нужная конфигурация и повторный запуск даёт тот же результат. Так переменные становятся воспроизводимой частью развёртывания, а не зависимостью от открытого терминала администратора.
Ещё по теме
- Почему set -e не остановил Bash-функцию внутри if
- Файл удалён, а место не освободилось: открытые файлы в Linux
Продолжить изучение
Хотите перейти от отдельных команд к сопровождению Linux-серверов? Работа с системой, скриптами и сервисами входит в программу «Linux и Bash для DevOps-инженеров».
Продолжить на практике
- Linux и Bash для DevOps-инженеров ↗
Linux для работы с серверами: процессы, права, systemd, сеть, диски и логи. Автоматизируйте повседневные задачи на Bash и разберите типичные сбои сервиса — от занятого порта до переполненного диска.
