Обновление ядра на сервере с базой данных редко проходит бесследно для служб. Система перезагружается, ядро загружается новое, а служба PostgreSQL остаётся в состоянии сбоя: подключений нет, приложение сыплет ошибками, на мониторинге всплывает красная отметка. Само ядро почти никогда не виновато. Обновление лишь меняет условия, в которых служба стартует, и накопленные слабые места проявляются сразу после перезагрузки: порядок монтирования томов, метки мандатного контроля доступа, лимиты разделяемой памяти, версии базовых библиотек, права на каталог данных. Хорошая новость в том, что каждая причина оставляет след в журналах, и маршрут поиска укладывается в минуты, если идти от журнала службы к журналу базы и дальше по зависимостям. Ниже разобран весь путь от первого взгляда на systemctl status до ручного запуска процесса от имени пользователя postgres, с командами, типовым выводом и лечением каждой причины.

Симптомы после перезагрузки и первый осмотр состояния службы

Сцена знакома каждому, кто обслуживает серверы: пакетный менеджер поставил новое ядро, перезагрузка прошла штатно, машина отвечает по ssh, но база не принимает соединения. Первый осмотр занимает пару минут. Команда systemctl status postgresql -l показывает агрегированную картину: загружен ли юнит, когда он останавливался, с каким кодом возврата завершился главный процесс.

postgresql.service - PostgreSQL database server
     Loaded: loaded (/usr/lib/systemd/system/postgresql.service; enabled)
     Active: failed (Result: exit-code) since 04:15:22; 9min ago
   Main PID: 2345 (code=exited, status=1/FAILURE)

Если строка Active содержит слово failed, служба пыталась стартовать и прекратила работу с ошибкой. Рядом systemd печатает несколько последних строк журнала, но обрезает хвост, поэтому полную картину даёт journalctl. Сразу спросите систему о самочувствии целиком: systemctl is-system-running вернёт degraded, если после загрузки остались неработающие юниты, и перечислит их. Не лишней будет проверка включённости: systemctl is-enabled postgresql. После обновления или переустановки пакета включённость иногда сбрасывается, и служба не стартует при загрузке, хотя вручную поднимается без вопросов.

Дальше действует короткий маршрут, который покрывает большинство случаев после обновления ядра:

  1. журнал службы через journalctl с фильтром по текущей загрузке;
  2. собственный журнал PostgreSQL в каталоге данных;
  3. статус юнита со списком зависимостей и точек монтирования;
  4. журнал аудита с отказами мандатного контроля доступа;
  5. права и владелец каталога данных;
  6. ручной запуск от имени пользователя postgres, чтобы прочитать ошибку прямо в терминале.

Чтение журнала службы и собственного журнала базы для поиска точной причины

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

journalctl -u postgresql -b --no-pager
journalctl -u postgresql -b -n 50
journalctl -u postgresql --since "1 hour ago"
journalctl -u postgresql -b -p err

Ключ -n ограничивает вывод последними строками, --since задаёт окно времени, а -p err оставляет события с приоритетом ошибки и выше. В выводе находите момент старта: сначала строка Started от systemd, затем сообщения самого сервера, в конце строка Failed with result с итоговым кодом. Причина почти всегда спрятана в строках между ними, и остаётся её подтвердить.

PostgreSQL ведёт и собственный журнал, он содержит подробности, которых в журнале службы нет. Направление подскажет конфигурация:

grep -E "^\s*#?\s*(logging_collector|log_directory|log_filename)" /var/lib/pgsql/data/postgresql.conf

Если сборщик журнала включён, файлы лежат в подкаталоге внутри каталога данных: путь задаёт log_directory, шаблон имени файла задаёт log_filename. Если сборщик выключен, вывод уходит в стандартный поток ошибок и целиком попадает в journalctl -u postgresql, тогда отдельного файла просто нет.

tail -n 30 /var/lib/pgsql/data/log/postgresql-*.log

Что искать в строках: недопустимые права на каталог данных, невозможность выделить разделяемую память, занятый порт 5432, существующий файл postmaster.pid. Последний пункт заслуживает отдельного упоминания: после некорректной остановки сервера lock-файл остаётся на месте, и при следующем старте база отказывается подниматься, пока не убедится, что прошлого процесса нет.

ps -ef | grep "[p]ostgres"
rm /var/lib/pgsql/data/postmaster.pid

Файл удаляют только после проверки, что процессов postgres действительно не осталось. Наконец, самый прямой приём: запустить сервер вручную и прочитать сообщение своими глазами.

systemctl stop postgresql
runuser -u postgres -- postgres -D /var/lib/pgsql/data

Процесс стартует на переднем плане и печатает сообщения прямо в терминал, остановка выполняется по Ctrl+C. От root сервер стартовать отказывается, поэтому runuser обязателен. Ручной запуск экономит часы: ошибка прав выглядит как ошибка прав, недостающая библиотека называется по имени, а быстрое тихое завершение отправляет смотреть каталог данных.

Порядок загрузки системы и зависимость юнита от точки монтирования

Частая раскладка на серверах с базой: каталог данных живёт на отдельном разделе или LVM-томе, подключение описано в /etc/fstab. При старте systemd поднимает службы параллельно, и между точкой монтирования тома и запуском postgresql возникает состязание. Обновление ядра сдвигает окно: новый initramfs, проверка файловой системы после нештатной остановки, инициализация устройств задерживают монтирование, а служба уже пытается открыть каталог данных и не находит его.

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

findmnt /var/lib/pgsql/data
systemctl list-units --type=mount | grep -E "pgsql|var"
systemctl show -p After postgresql
systemctl cat postgresql

Команда systemctl show -p After печатает юниты, после которых стартует служба. Если точки монтирования в списке нет, состязание подтверждено. Лечение помещается в drop-in-файл юнита, который создаёт команда systemctl edit postgresql:

[Unit]
RequiresMountsFor=/var/lib/pgsql/data

Директива требует, чтобы путь был смонтирован к моменту старта: systemd сам вычисляет юнит монтирования, который покрывает каталог, и добавляет зависимости After и Requires на него и на родительские точки. После правки конфигурацию перечитывают и проверяют цепочку порядка:

systemctl daemon-reload
systemctl restart postgresql
systemd-analyze critical-chain postgresql

Альтернатива с явным указанием After=имя.mount требует знать имя юнита монтирования, а systemd строит его из пути по правилам экранирования: дефис в имени превращается в последовательность \x2d, и ошибиться в записи легко. RequiresMountsFor надёжнее, потому что принимает обычный путь. А вот лечить состязание паузой перед стартом не стоит: сон в ExecStartPre задерживает загрузку всегда, а зависимость срабатывает только когда том реально не готов.

Контексты SELinux и восстановление доступа к каталогу данных по журналу аудита

В системах семейства RPM работает SELinux, механизм мандатного контроля доступа. У каждого процесса есть тип, у каждого файла метка контекста, и операция проходит только тогда, когда политика разрешает её явно. Процесс сервера носит тип postgresql_t, файлы каталога данных помечены postgresql_db_t. Обновление ядра в одной партии с пакетом политики контроля даёт два типовых сценария сбоя: политика начинает применять правила по-новому либо во время работ каталог переносили или разворачивали из копии, и метки сбились.

Признак узнаваем: база отказывается стартовать с сообщением "Permission denied" на собственные файлы, хотя обычные права Unix на месте и владелец правильный. Причину показывает журнал аудита, и читать его удобно через ausearch: ключ -ts recent даёт события за последние минуты, -ts today открывает окно с начала суток.

ausearch -m avc -ts recent
ausearch -m avc -ts today

Пример строки отказа, которую выдаёт поиск:

type=AVC msg=audit(1712345678.123:456): avc: denied { read } for pid=2345 comm="postgres" name="PG_VERSION" dev="sdb1" ino=817263 scontext=system_u:system_r:postgresql_t:s0 tcontext=unconfined_u:object_r:default_t:s0 tclass=file

Поля читаются просто: scontext показывает тип процесса, tcontext сообщает метку файла, name называет файл, к которому отказ. Метка default_t достаётся путям, для которых в политике нет правила: типичная картина после переноса каталога на новый том. Дальше сверяйте ожидание с реальностью:

matchpathcon /var/lib/pgsql/data
semanage fcontext -l | grep pgsql

Для штатного пути политика содержит правило с типом postgresql_db_t. Если каталог переехал, правило добавляют сами и применяют к файлам:

semanage fcontext -a -t postgresql_db_t "/srv/pgdata(/.*)?"
restorecon -RFv /srv/pgdata

Первая команда пишет постоянное правило в базу контекстов файлов, вторая расставляет метки по правилам: -R обходит подкаталоги, -F сбрасывает метки принудительно, -v печатает каждое изменение. После лечения служба стартует обычным systemctl restart postgresql.

Соблазн решить вопрос командой chcon или отключением контроля стоит обойти. chcon меняет метку точечно, правила в политике не создаёт, и при следующей пересборке контекстов правка откатится. Перевод в разрешающий режим через setenforce 0 годится только как минутный тест: если после него служба стартовала, диагноз подтверждён, и режим сразу возвращают командой setenforce 1. Постоянное отключение контроля прячет причину и снимает со сервера слой защиты, который не вернуть никакими правами Unix.

Ещё деталь: если служба аудита не запущена, отказы всё равно попадают в буфер ядра и видны через journalctl -k или dmesg, а пакет setroubleshoot-server, если установлен, превращает их в текстовые подсказки в журнале под идентификатором setroubleshoot.

Параметры ядра для разделяемой памяти после перезагрузки сервера

При старте PostgreSQL просит у ядра область разделяемой памяти под буферы и внутренние структуры. Ядро держит для этого лимиты: kernel.shmmax ограничивает размер одного сегмента в байтах, kernel.shmall задаёт общий объём в страницах, kernel.shmmni ограничивает число сегментов. Если параметр shared_buffers в postgresql.conf запросит больше, чем разрешает shmmax, старт завершится сообщением о невозможности выделить сегмент, и подсказка в журнале прямо назовёт параметр ядра.

Типовая история после обновления ядра выглядит так: лимиты когда-то подняли на живом сервере командой sysctl -w, в конфигурацию не сохранили, месяцами всё работало, а перезагрузка вернула значения по умолчанию. Новое ядро ни при чём: оно честно применило настройки, которые нашло в файлах.

sysctl kernel.shmmax kernel.shmall kernel.shmmni
sysctl -a | grep "^kernel.shm"
ipcs -l
ipcs -m

Утилита ipcs с ключом -l показывает лимиты, с ключом -m выделенные сегменты, где заметны и брошенные области после неудачных стартов. Лечение сводится к постоянному файлу в /etc/sysctl.d, который переживёт любые перезагрузки:

# /etc/sysctl.d/90-pgsql.conf
kernel.shmmax = 4294967295
kernel.shmall = 2097152

Значения подбирают под размер shared_buffers и объём памяти сервера, ориентир простой: shmmax не меньше того, что база запрашивает при старте. Применяют и проверяют:

sysctl --system
systemctl restart postgresql

Нюанс, который экономит время: свежие выпуски PostgreSQL на Linux часто просят анонимную разделяемую память через mmap, и тогда лимиты семейства shm на старт не влияют, а в журнале фигурируют другие ключевые слова. Ориентируйтесь на текст ошибки: он называет либо shmget и SHMMAX, либо mmap и нехватку памяти, и во втором случае смотрят на настройки overcommit и свободный объём.

Обновление базовых библиотек и перезапуск чувствительных служб

Сервер базы тянет за собой системные библиотеки: glibc, OpenSSL, zlib, ICU и другие. Когда обновление приносит новые версии файлов, работающие процессы продолжают исполнять старый код из памяти: образ загруженной библиотеки остаётся у процесса, даже когда файл на диске заменён. Часть пакетов при обновлении перезапускает свои службы автоматически, но PostgreSQL в такие списки обычно не попадает, потому что базу не перезапускают без ведома администратора, чтобы не разрывать соединения приложений.

В результате на сервере живут одновременно старые и новые версии библиотек: главные процессы базы держат в памяти прежний код, а новые модули и утилиты загружают свежий. Рассинхрон копится тихо и всплывает в неожиданных местах, от странных ошибок в журнале до отказа расширений при загрузке. Перезагрузка после обновления ядра как раз выравнивает процессы и файлы. Отсюда практическое правило: после крупного обновления базовых библиотек базу перезапускают в плановом порядке, не дожидаясь симптомов.

В системах семейства RPM список служб, которым нужен рестарт, подскажет утилита из пакета dnf-utils:

dnf install dnf-utils
needs-restarting -r
needs-restarting -s

Ключ -r отвечает на вопрос, нужна ли перезагрузка, ключ -s печатает службы, которые держат заменённые или удалённые файлы и требуют рестарта. Если старт базы падает с сообщением о недостающей библиотеке, проверяют связи бинарника и кэш линковщика:

ldd /usr/bin/postgres | grep -E "ssl|crypto|icu"
ldconfig -p | grep libssl

Первая команда показывает, какие библиотеки подтягивает бинарник сервера, вторая отвечает, зарегистрированы ли они в кэше линковщика. После установки библиотек вручную кэш обновляют командой ldconfig, а затем повторяют запуск службы.

Права и владелец каталога данных после восстановления из копии

Сервер базы строго проверяет каталог данных при старте и отказывается работать, если права шире необходимых. Утилита initdb создаёт каталог с правами 0700, и именно такой режим сервер считает безопасным: доступ посторонних пользователей к файлам базы он расценивает как ошибку конфигурации и завершает запуск. После миграции на новый том или восстановления из копии картинка часто ломается: архив разворачивали от root, копию переносили под обычным пользователем, и владелец поменялся.

Проверка занимает одну команду:

ls -ld /var/lib/pgsql/data
getfacl /var/lib/pgsql/data 2>/dev/null

Ожидаемый результат: drwx------ и владелец postgres. Расширенные списки доступа смотрят отдельно: лишние записи, оставшиеся после работ, снимают командой setfacl -b. Затем полезно убедиться, что перед вами развёрнутый кластер, а не пустой каталог:

cat /var/lib/pgsql/data/PG_VERSION
pg_controldata /var/lib/pgsql/data

Файл PG_VERSION хранит номер основной версии кластера, а утилита pg_controldata печатает контрольную информацию, включая состояние при последней остановке. Если контрольная информация читается без ошибок, копия развёрнута корректно, и остаётся вернуть владение и права:

chown -R postgres:postgres /var/lib/pgsql/data
chmod 700 /var/lib/pgsql/data
systemctl restart postgresql

Копирование от root с сохранением владельца в rsync или cp -a переносит владение корректно, копия под обычным пользователем переписывает владельца на него. В ветках Linux вне семейства RPM пути и имена пользователей отличаются, но проверка прав, владельца и контрольной информации работает одинаково. Готовность после старта проверяют утилитой pg_isready -p 5432, она отвечает, принимает ли сервер соединения на нужном порту.

Практика сводится к короткому правилу: обновление ядра меняет условия запуска, а журнал службы в первых строках называет конкретную причину. Дальше работает маршрут из журналов, зависимостей, аудита и прав в порядке от простого к сложному. Служба, которая стартует после перезагрузки так же уверенно, как вручную, экономит администратору ночное время и нервы. Сухой остаток простой: почти вся диагностика после обновления ядра укладывается в journalctl, ausearch, systemctl и ls -ld, надо лишь внимательно читать то, что система сама охотно рассказывает.