Введение: Linux в продакшене — это не просто установить и забыть
Поставить Ubuntu, запустить приложение и назвать это "продакшеном" — рецепт будущей катастрофы. Настоящий production Linux — это слоёный пирог из правильного выбора дистрибутива, hardening-а, тюнинга ядра, настроенного мониторинга и чётких процедур обслуживания.
Статья написана для тех, кто уже умеет работать с Linux и хочет понять почему нужно делать именно так, а не просто скопировать команды.
Часть 1: Выбор дистрибутива
Критерии для production-выбора
Прежде чем смотреть на дистрибутивы, определитесь с приоритетами:
Стабильность vs свежесть: старые проверенные пакеты или последние версии?
Коммерческая поддержка: нужен SLA от вендора или достаточно community?
Цикл жизни: как долго дистрибутив будет получать security-патчи?
Экосистема: есть ли готовые пакеты для вашего стека?
Команда: что знает ваша команда? Переучивание дорого стоит.
Ubuntu LTS Server
Цикл поддержки: 5 лет standard, 10 лет ESM (Extended Security Maintenance).
Текущий LTS: 24.04 (Noble Numbat), следующий — 26.04.
Ubuntu — безусловный лидер по распространённости в cloud-среде. AWS, GCP, Azure — везде Ubuntu является первым выбором по умолчанию. Огромная документация, большинство DevOps-инструментов тестируются на Ubuntu в первую очередь.
Подходит для:
Startups и компании без выделенного Linux-администратора
Kubernetes workers и cloud-native окружения
Быстрое развёртывание новых стеков
Осторожно:
Canonical меняет политики (snap-пакеты вместо deb без предупреждения)
Обновления между LTS требуют тщательного тестирования
ESM платный для > 5 машин в организации
# Проверка версии и EOL даты
lsb_release -a
ubuntu-advantage status # Статус ESM подписки
# Отключить snap если не нужен (спорно, но часто делают)
sudo systemctl disable snapd --now
sudo apt purge snapd
Debian Stable
Цикл поддержки: ~3 года основная поддержка + 2 года LTS.
Текущий: Debian 12 "Bookworm" (до 2028).
Debian — "бабушка" большинства дистрибутивов. Её репутация: консервативная, предсказуемая, надёжная. Пакеты в Stable могут быть на 1–2 года старше апстрима, но зато они досконально протестированы. Никаких сюрпризов в 3 часа ночи.
Подходит для:
Серверы с длинным жизненным циклом (БД, хранилища)
Инфраструктура, где стабильность важнее новых фич
Встраиваемые и промышленные серверы
Осторожно:
Старые пакеты могут не поддерживать новые возможности (TLS 1.3 и т.п. уже везде есть, но крайние версии — нет)
community-поддержка без коммерческого SLA
# Debian: правильные sources.list для продакшена
# Только stable, никакого testing/sid!
cat /etc/apt/sources.list
# deb http://deb.debian.org/debian bookworm main contrib non-free-firmware
# deb http://security.debian.org/debian-security bookworm-security main
# deb http://deb.debian.org/debian bookworm-updates main
# Backports только для конкретных пакетов, не массово
# deb http://deb.debian.org/debian bookworm-backports main
RHEL / Rocky Linux / AlmaLinux
RHEL (Red Hat Enterprise Linux) — корпоративный стандарт в Enterprise-сегменте, особенно там где есть compliance требования (PCI DSS, HIPAA, FedRAMP). Платная подписка, но включает полный enterprise support от Red Hat.
Rocky Linux и AlmaLinux — бинарно-совместимые клоны RHEL, бесплатные. После смерти CentOS 8 (2021) это лучшая замена для тех, кто хочет RHEL-совместимость без платы.
Цикл поддержки: RHEL 9 → до 2032 (10 лет!). Rocky/Alma — аналогично.
Подходит для:
Enterprise-окружения с compliance требованиями
Компании с контрактами Red Hat (получают support + Satellite)
Там где нужен SELinux из коробки в полном объёме
Серверы баз данных Oracle (Oracle Linux — тоже RHEL-клон)
# Rocky Linux: подписка на обновления безопасности (бесплатно)
sudo dnf install epel-release
sudo dnf update
# Проверка SELinux статуса
getenforce # Enforcing / Permissive / Disabled
sestatus
# RHEL-специфика: subscription-manager
sudo subscription-manager status
sudo subscription-manager repos --list-enabled
Alpine Linux
Цикл: rolling release (stable branches с ~2 годами поддержки).
Alpine — минималистичный дистрибутив: musl libc вместо glibc, busybox вместо GNU coreutils, базовый образ 5 МБ. Создан для контейнеров и встраиваемых систем.
Подходит для:
Docker-образы (но есть нюансы с musl!)
Edge-узлы и шлюзы с ограниченными ресурсами
Безопасные "голые" серверы с минимальной поверхностью атаки
Осторожно:
musl libc != glibc: некоторые Go/C программы ведут себя иначе
Не для тех, кто не знает что делает: меньше инструментов отладки
Не для продакшена с незнакомым стеком
Итоговая таблица выбора
Ubuntu LTS | Debian Stable | Rocky/Alma | Alpine | |
|---|---|---|---|---|
Cloud-native | ★★★★★ | ★★★★ | ★★★ | ★★★★ |
Enterprise | ★★★ | ★★★ | ★★★★★ | ★★ |
Стабильность пакетов | ★★★★ | ★★★★★ | ★★★★★ | ★★★ |
Свежесть пакетов | ★★★★ | ★★★ | ★★★ | ★★★★★ |
Безопасность (OOB) | ★★★★ | ★★★★ | ★★★★★ | ★★★★ |
Поддержка сообщества | ★★★★★ | ★★★★★ | ★★★★ | ★★★ |
Простота обслуживания | ★★★★★ | ★★★★ | ★★★ | ★★ |
Рекомендация: для большинства команд без специфических требований — Ubuntu 24.04 LTS или Debian 12. Для enterprise с compliance — Rocky Linux 9. Для контейнеров — Alpine или Distroless.
Часть 2: Первичная настройка надёжности
Базовое обновление и автоматические security-патчи
# === Ubuntu / Debian ===
sudo apt update && sudo apt upgrade -y
# Автоматические security-обновления (unattended-upgrades)
sudo apt install unattended-upgrades apt-listchanges
sudo dpkg-reconfigure unattended-upgrades
# Конфиг /etc/apt/apt.conf.d/50unattended-upgrades
# Оставляем только security, НЕ автоустанавливаем всё подряд
sudo tee /etc/apt/apt.conf.d/50unattended-upgrades > /dev/null <<'EOF'
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
Unattended-Upgrade::MinimalSteps "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false"; // НЕ перезагружаем автоматически!
Unattended-Upgrade::Mail "ops@yourcompany.com";
EOF
# === Rocky / AlmaLinux ===
sudo dnf install dnf-automatic
sudo systemctl enable --now dnf-automatic-install.timer
# /etc/dnf/automatic.conf: apply_updates = yes, upgrade_type = security
Правильная настройка NTP
Время — фундамент для логов, TLS-сертификатов, Kerberos, баз данных. Неправильное время = необъяснимые баги.
# Современный стандарт: systemd-timesyncd (простой) или chrony (сложные сети)
# chronyd рекомендован для серверов — точнее и поддерживает аппаратные часы
sudo apt install chrony # Ubuntu/Debian
# или: sudo dnf install chrony # RHEL-based
# /etc/chrony.conf
sudo tee /etc/chrony.conf > /dev/null <<'EOF'
# Используем несколько источников из разных пулов
pool 0.ru.pool.ntp.org iburst
pool 1.ru.pool.ntp.org iburst
pool 2.europe.pool.ntp.org iburst
pool 3.pool.ntp.org iburst
# Внутренний NTP-сервер (если есть) — даём максимальный приоритет
# server 192.168.1.1 prefer iburst
# Разрешаем step при большом расхождении (только при старте)
makestep 1.0 3
# Дрейф файла
driftfile /var/lib/chrony/drift
# Логирование
logdir /var/log/chrony
# Синхронизация RTC (hardware clock)
rtcsync
EOF
sudo systemctl restart chrony
chronyc tracking # Статус синхронизации
chronyc sources -v # Источники времени
systemd: сделать сервисы по-настоящему надёжными
Большинство используют systemd только для start/stop/enable. Но в нём есть мощные механизмы для production-надёжности.
# /etc/systemd/system/myapp.service — production-grade unit
[Unit]
Description=My Production Application
Documentation=https://docs.mycompany.com/myapp
# Зависимости — стартуем ПОСЛЕ того как сеть полностью готова
After=network-online.target postgresql.service redis.service
Wants=network-online.target
Requires=postgresql.service
# Условие запуска — только если файл конфига существует
ConditionPathExists=/etc/myapp/config.yaml
[Service]
Type=notify # Приложение сигнализирует о готовности через sd_notify
NotifyAccess=main
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
# Переменные окружения из защищённого файла (права 600, владелец root)
EnvironmentFile=/etc/myapp/environment
ExecStartPre=/opt/myapp/bin/validate-config # Валидация конфига перед стартом
ExecStart=/opt/myapp/bin/myapp --config /etc/myapp/config.yaml
ExecReload=/bin/kill -HUP $MAINPID # Graceful reload по сигналу HUP
# ── Restart политика ──────────────────────────────────────────────────────────
Restart=on-failure # Перезапускаем только при сбое (не при systemctl stop)
RestartSec=5s # Ждём 5 секунд перед перезапуском
StartLimitIntervalSec=120s # Окно для подсчёта неудачных стартов
StartLimitBurst=5 # Максимум 5 перезапусков за 120 секунд
# После исчерпания — юнит в Failed. Оповестить алертинг!
# ── Watchdog ─────────────────────────────────────────────────────────────────
# Приложение ДОЛЖНО вызывать sd_notify("WATCHDOG=1") каждые WatchdogSec/2
WatchdogSec=30s
# ── Таймауты ─────────────────────────────────────────────────────────────────
TimeoutStartSec=60s # Максимальное время на инициализацию
TimeoutStopSec=30s # Максимальное время на graceful shutdown
KillMode=mixed # Сначала SIGTERM главному процессу, потом SIGKILL всей группе
KillSignal=SIGTERM
# ── Лимиты ресурсов ──────────────────────────────────────────────────────────
LimitNOFILE=65536 # Максимум открытых файлов
LimitNPROC=4096 # Максимум процессов/потоков
LimitMEMLOCK=infinity # Для приложений с mlock (JVM, некоторые БД)
# ── Безопасность (sandboxing) ─────────────────────────────────────────────────
NoNewPrivileges=true # Запрет повышения привилегий
PrivateTmp=true # Изолированный /tmp
PrivateDevices=true # Запрет доступа к /dev (кроме basic)
ProtectSystem=strict # /usr, /boot, /etc только на чтение
ProtectHome=true # Нет доступа к /home, /root
ReadWritePaths=/var/lib/myapp /var/log/myapp # Разрешаем запись только сюда
CapabilityBoundingSet=CAP_NET_BIND_SERVICE # Только если нужен порт < 1024
# ── Логирование ──────────────────────────────────────────────────────────────
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
[Install]
WantedBy=multi-user.target
# Анализ конфигурации unit (покажет ошибки и предупреждения)
systemd-analyze verify /etc/systemd/system/myapp.service
# Просмотр "дерева" зависимостей
systemd-analyze critical-chain myapp.service
# Лимиты запущенного сервиса
cat /proc/$(systemctl show -p MainPID --value myapp)/limits
# История перезапусков
journalctl -u myapp --since "7 days ago" | grep -E "Started|Stopped|Failed"
Настройка ulimits и системных лимитов
# /etc/security/limits.conf — лимиты для пользователей
# Для высоконагруженного приложения (например, веб-сервер, БД)
myapp soft nofile 65536
myapp hard nofile 65536
myapp soft nproc 65536
myapp hard nproc 65536
# Для Elasticsearch / Kafka / других Java-приложений
elasticsearch soft memlock unlimited
elasticsearch hard memlock unlimited
elasticsearch soft nofile 65536
elasticsearch hard nofile 65536
# /etc/systemd/system.conf — глобальные лимиты для systemd-процессов
# DefaultLimitNOFILE=65536
# DefaultLimitNPROC=65536
# Проверка текущих лимитов процесса
cat /proc/$(pgrep myapp | head -1)/limits
Часть 3: Тюнинг ядра для продакшена
Параметры sysctl: сеть
Это самое важное для highload-серверов. По умолчанию Linux оптимизирован для десктопа, не для сервера.
# /etc/sysctl.d/99-production.conf
sudo tee /etc/sysctl.d/99-production.conf > /dev/null <<'EOF'
# ════════════════════════════════════════════════════════════════════
# СЕТЬ — TCP/IP стек
# ════════════════════════════════════════════════════════════════════
# Буферы сокетов (receive/send)
# Для highload: 128 МБ (default ~212 КБ — катастрофически мало!)
net.core.rmem_max = 134217728 # 128 МБ
net.core.wmem_max = 134217728
net.core.rmem_default = 31457280 # 30 МБ
net.core.wmem_default = 31457280
# TCP-специфичные буферы (min, default, max)
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
# Очередь подключений (backlog)
# Должна совпадать с параметром backlog в listen() вашего приложения
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 65535
# SYN-очередь (защита от SYN flood + highload accept)
net.ipv4.tcp_max_syn_backlog = 65535
# TIME_WAIT: ускоряем переиспользование сокетов
net.ipv4.tcp_tw_reuse = 1 # Переиспользовать TIME_WAIT сокеты (безопасно)
net.ipv4.tcp_fin_timeout = 15 # Сократить FIN_WAIT таймаут (default 60 сек)
# Диапазон эфемерных портов (для outbound соединений)
net.ipv4.ip_local_port_range = 1024 65535
# Keepalive — обнаружение мёртвых соединений
net.ipv4.tcp_keepalive_time = 120 # Начинаем проверку после 120 сек простоя
net.ipv4.tcp_keepalive_intvl = 10 # Интервал между пробами
net.ipv4.tcp_keepalive_probes = 6 # Количество проб
# Алгоритм управления перегрузкой (congestion control)
# BBR — современный алгоритм Google, значительно лучше для WAN и highload
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
# Быстрый открытый TCP (TFO) — уменьшает latency на 1 RTT
# (убедитесь что клиенты поддерживают)
net.ipv4.tcp_fastopen = 3 # 1=клиент, 2=сервер, 3=оба
# Защита от мусорных RST пакетов
net.ipv4.tcp_rfc1337 = 1
# ════════════════════════════════════════════════════════════════════
# ПАМЯТЬ
# ════════════════════════════════════════════════════════════════════
# vm.swappiness: насколько агрессивно использовать swap
# 0 = swap только при крайней необходимости (для БД и latency-критичных)
# 10 = рекомендуется для production-серверов
# 60 = default (слишком агрессивный для сервера)
vm.swappiness = 10
# Dirty pages: когда сбрасывать на диск
# Уменьшаем для предсказуемой latency (не даём накопиться большому flush)
vm.dirty_ratio = 10 # Начинаем sync при 10% RAM dirty (default 20%)
vm.dirty_background_ratio = 3 # Фоновый flush начинается при 3% (default 10%)
vm.dirty_writeback_centisecs = 500 # Интервал фонового flush (5 сек, default 5 сек)
vm.dirty_expire_centisecs = 3000 # Страницы считаются "старыми" через 30 сек
# Overcommit (для Java/JVM и других приложений с большими heap)
# 0 = эвристика ядра (default)
# 1 = всегда overcommit (опасно, но нужно для некоторых БД)
# 2 = строгий лимит (CommitLimit = swap + vm.overcommit_ratio% от RAM)
vm.overcommit_memory = 0
# OOM Killer: штраф для критических процессов
# Устанавливается через /proc или systemd OOMPolicy
# (ниже — пример для PostgreSQL)
# ════════════════════════════════════════════════════════════════════
# ФАЙЛОВАЯ СИСТЕМА
# ════════════════════════════════════════════════════════════════════
# Максимум открытых файлов в системе (не путать с per-process ulimit)
fs.file-max = 2097152
# Inotify: для приложений, следящих за файлами (Docker, K8s, IDE)
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 512
# AIO: асинхронный ввод-вывод (PostgreSQL, некоторые БД)
fs.aio-max-nr = 1048576
# ════════════════════════════════════════════════════════════════════
# БЕЗОПАСНОСТЬ
# ════════════════════════════════════════════════════════════════════
# Запрет разыменования символических ссылок в /tmp (защита от атак)
fs.protected_symlinks = 1
fs.protected_hardlinks = 1
# Запрет записи в память выполняемых файлов
fs.protected_regular = 2
fs.protected_fifos = 2
# ASLR (Address Space Layout Randomization)
kernel.randomize_va_space = 2 # Полная рандомизация
# Запрет SysRq (кроме sync/reboot — оставляем для экстренного случая)
kernel.sysrq = 16 # 16 = только sync
# Защита от Spectre/Meltdown через ptrace
kernel.yama.ptrace_scope = 1
# Core dumps только в определённую директорию
kernel.core_pattern = /var/crash/core.%e.%p.%t
kernel.core_uses_pid = 1
EOF
# Применить без перезагрузки
sudo sysctl --system
# Проверить конкретный параметр
sysctl net.ipv4.tcp_congestion_control
Hugepages: критично для баз данных
HugePages уменьшают нагрузку на TLB (Translation Lookaside Buffer) при работе с большими объёмами памяти. PostgreSQL, Oracle, MySQL InnoDB, Elasticsearch, Redis — все выигрывают от HugePages.
# Проверить текущее состояние
cat /proc/meminfo | grep -i huge
# HugePages_Total: 0 ← не настроены
# Hugepagesize: 2048 kB ← 2 МБ каждая страница
# Рассчитать сколько нужно:
# Для PostgreSQL: shared_buffers + прочая SHM / 2 МБ
# Например: 16 ГБ shared_buffers → 16384 МБ / 2 МБ = 8192 страниц
# /etc/sysctl.d/99-hugepages.conf
echo "vm.nr_hugepages = 8192" | sudo tee /etc/sysctl.d/99-hugepages.conf
# Или прозрачные HugePages (THP) — автоматически, но с latency spikes!
# Для БД (особенно Redis) — ОТКЛЮЧИТЬ THP:
echo never | sudo tee /sys/kernel/mm/transparent_hugepage/enabled
echo never | sudo tee /sys/kernel/mm/transparent_hugepage/defrag
# Сделать постоянным через rc.local или systemd service:
sudo tee /etc/systemd/system/disable-thp.service > /dev/null <<'EOF'
[Unit]
Description=Disable Transparent Huge Pages
DefaultDependencies=false
After=sysinit.target local-fs.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'echo never > /sys/kernel/mm/transparent_hugepage/enabled'
ExecStart=/bin/sh -c 'echo never > /sys/kernel/mm/transparent_hugepage/defrag'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable --now disable-thp
Планировщик I/O: под задачу
# Посмотреть текущий планировщик для диска
cat /sys/block/sda/queue/scheduler
# [mq-deadline] kyber bfq none
# Выбор планировщика:
# none (noop) — для NVMe SSD и виртуальных дисков (SSD сами управляют очередью)
# mq-deadline — универсальный, хорош для смешанных нагрузок
# bfq — для десктопов и интерактивных задач (не для продакшена)
# kyber — для NVMe с очень низкой latency
# Для NVMe SSD — none:
echo none | sudo tee /sys/block/nvme0n1/queue/scheduler
# Для SATA SSD — mq-deadline:
echo mq-deadline | sudo tee /sys/block/sda/queue/scheduler
# Через udev (постоянно):
sudo tee /etc/udev/rules.d/60-ioschedulers.rules > /dev/null <<'EOF'
# NVMe
ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/scheduler}="none"
# SATA/SAS SSD (по ROTATIONAL=0)
ACTION=="add|change", KERNEL=="sd[a-z]|xvd[a-z]", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="mq-deadline"
# HDD (по ROTATIONAL=1)
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="bfq"
EOF
# Дополнительные параметры очереди диска:
# Глубина очереди запросов (для NVMe можно больше)
cat /sys/block/nvme0n1/queue/nr_requests # default 64
echo 1024 | sudo tee /sys/block/nvme0n1/queue/nr_requests
# Read-ahead (предварительное чтение)
# Для последовательного чтения (логи, backup): увеличить
# Для random I/O (OLTP БД): уменьшить
blockdev --setra 256 /dev/sda # 128 КБ (256 × 512 байт)
OOM Killer: кого убивать в последнюю очередь
# OOM killer убивает процессы когда RAM исчерпана.
# Значение oom_score_adj: от -1000 (никогда не убивать) до +1000 (убить первым)
# Защитить критические процессы (PostgreSQL, основное приложение):
echo -1000 | sudo tee /proc/$(pgrep postgres | head -1)/oom_score_adj
# В systemd unit — правильный способ:
# [Service]
# OOMScoreAdjust=-900 # -1000 зарезервирован для ядра
# Для throaway-процессов (worker, task runner) — пусть убивают первыми:
# OOMScoreAdjust=500
# Включить расширенное логирование OOM kill:
echo 1 | sudo tee /proc/sys/vm/oom_dump_tasks
# Посмотреть oom_score всех процессов
for p in /proc/[0-9]*/; do
pid=$(basename $p)
comm=$(cat $p/comm 2>/dev/null)
score=$(cat $p/oom_score 2>/dev/null)
adj=$(cat $p/oom_score_adj 2>/dev/null)
[ -n "$score" ] && echo "$score $adj $pid $comm"
done | sort -rn | head -20
Часть 4: Файловые системы и хранилище
Выбор файловой системы
ext4 — проверенный стандарт, но без поддержки checksums данных (только метаданных).
XFS — отличная производительность на больших файлах и параллельном I/O. Default в RHEL. Нельзя уменьшить (только увеличить).
Btrfs — copy-on-write, встроенные checksums, снапшоты. Хорош для систем где нужны снапшоты без LVM. В продакшене требует RAID-конфигурации для надёжности.
ZFS — самая надёжная ФС с end-to-end checksums, встроенным RAID-Z, дедупликацией. Требует отдельной установки на Linux. Рекомендуется для хранилищ данных.
# Монтирование ext4 с оптимальными опциями для продакшена:
# /etc/fstab:
# /dev/sdb1 /data ext4 defaults,noatime,lazytime,errors=remount-ro 0 2
# noatime — не обновлять atime при каждом чтении (значительно снижает I/O)
# lazytime — обновлять временны́е метки только при flush (компромисс)
# errors=remount-ro — при ошибке ФС переходит в read-only вместо паники
# XFS с journalling только на metadata (для производительности):
# /dev/sdb1 /data xfs defaults,noatime,logbsize=256k 0 2
# Проверка здоровья файловой системы:
sudo tune2fs -l /dev/sdb1 | grep -E "Last checked|Mount count|Max mount count"
sudo xfs_info /data
sudo xfs_repair -n /data # Проверка XFS без исправления
LVM: гибкое управление томами
LVM (Logical Volume Manager) — обязателен для production-серверов. Позволяет расширять разделы без downtime, создавать снапшоты для бэкапов.
# Создание LVM структуры:
# 1. Physical Volumes (PV)
sudo pvcreate /dev/sdb /dev/sdc
# 2. Volume Group (VG)
sudo vgcreate data_vg /dev/sdb /dev/sdc
sudo vgs # Проверка
# 3. Logical Volumes (LV)
sudo lvcreate -L 100G -n postgres_lv data_vg # 100 ГБ для PostgreSQL
sudo lvcreate -L 50G -n logs_lv data_vg # 50 ГБ для логов
sudo lvcreate -l 100%FREE -n backup_lv data_vg # Остаток под бэкапы
# Форматирование
sudo mkfs.xfs /dev/data_vg/postgres_lv
sudo mkfs.ext4 /dev/data_vg/logs_lv
# Расширение без downtime (XFS умеет расти онлайн):
sudo lvextend -L +50G /dev/data_vg/postgres_lv # Добавить 50 ГБ к LV
sudo xfs_growfs /var/lib/postgresql # Расширить ФС
# Снапшот для онлайн-бэкапа:
sudo lvcreate -L 10G -s -n postgres_snap /dev/data_vg/postgres_lv
sudo mount -o ro /dev/data_vg/postgres_snap /mnt/backup_snap
# rsync с /mnt/backup_snap → бэкап без остановки БД
sudo umount /mnt/backup_snap
sudo lvremove -f /dev/data_vg/postgres_snap
Часть 5: Мониторинг и observability
Встроенные инструменты диагностики
# ── Производительность системы ───────────────────────────────────────────────
# top на стероидах: htop
sudo apt install htop
# Статистика I/O по процессам
sudo iotop -o # -o = только активные процессы
# Сетевые соединения (замена netstat)
ss -tunap # TCP/UDP, номера, приложения, pid
ss -s # Сводная статистика
ss 'state established' # Только ESTABLISHED
# Дисковый I/O в реальном времени
iostat -xz 1 # Расширенная статистика, без нулей, обновление 1с
# Нагрузка на сеть по интерфейсам
sar -n DEV 1 # Из пакета sysstat
# Сколько памяти реально свободно
free -h
cat /proc/meminfo | grep -E "MemAvailable|MemFree|Cached|SwapUsed"
# ── Анализ производительности ────────────────────────────────────────────────
# perf: профилировщик ядра (какие системные вызовы тормозят)
sudo perf top -g # Онлайн profiling
sudo perf stat -p $(pgrep myapp) sleep 10 # Статистика за 10 секунд
# strace: что делает процесс (дорого, только для диагностики)
sudo strace -p $(pgrep myapp) -e trace=network,file -T 2>&1 | head -50
# lsof: открытые файлы и сокеты
sudo lsof -p $(pgrep myapp) | wc -l # Сколько открыто
sudo lsof -i :8080 # Кто слушает порт 8080
# ── Диагностика сети ─────────────────────────────────────────────────────────
# Статистика TCP ошибок (ретрансмиты, dropped пакеты)
netstat -s | grep -E "retransmit|failed|overflow|listen"
# Или через nstat (более детально):
nstat -az | grep -i -E "retrans|drop|overflow|fail"
# Потери пакетов на интерфейсе
ip -s link show eth0
# RX errors/dropped/overrun — проблемы приёма
# TX errors/dropped — проблемы передачи
# Traceroute с временами (для диагностики latency)
mtr --report --report-cycles 20 8.8.8.8
# TCP-дамп для анализа (осторожно с нагрузкой!)
sudo tcpdump -i eth0 -n port 5432 -c 1000 -w /tmp/postgres.pcap
# Анализ в Wireshark
Настройка централизованного логирования
# journald — настройка хранения логов
sudo tee /etc/systemd/journald.conf.d/99-production.conf > /dev/null <<'EOF'
[Journal]
Storage=persistent # Хранить на диске (не только в RAM)
Compress=yes # Сжатие
SystemMaxUse=2G # Максимум 2 ГБ для системных логов
SystemKeepFree=500M # Оставлять 500 МБ свободными
MaxRetentionSec=30day # Хранить не дольше 30 дней
MaxFileSec=1day # Ротация ежедневно
ForwardToSyslog=no # Не дублировать в rsyslog (если не нужно)
RateLimitBurst=1000 # Лимит: 1000 сообщений
RateLimitIntervalSec=30s # за 30 секунд на единицу
EOF
sudo systemctl restart systemd-journald
# Полезные запросы journalctl:
journalctl --since "1 hour ago" -p err # Ошибки за последний час
journalctl -u myapp -f --output=json # Поток логов в JSON
journalctl --disk-usage # Сколько занимают логи
journalctl --vacuum-time=7d # Удалить старше 7 дней
# rsyslog → файлы (для совместимости с legacy-инструментами):
# /etc/rsyslog.d/99-production.conf
sudo tee /etc/rsyslog.d/99-production.conf > /dev/null <<'EOF'
# Высокопроизводительный режим
$ActionQueueType LinkedList
$ActionQueueSize 10000
$ActionResumeRetryCount -1 # Бесконечный retry при недоступности цели
# Отправка в centralized syslog (Loki, Graylog, Splunk)
*.* @@syslog.internal.example.com:514 # @@ = TCP (надёжнее UDP)
EOF
Prometheus Node Exporter: метрики для Grafana
# Установка Node Exporter (собирает 1000+ метрик системы)
wget https://github.com/prometheus/node_exporter/releases/latest/download/node_exporter-*.linux-amd64.tar.gz
tar xf node_exporter-*.tar.gz
sudo mv node_exporter-*/node_exporter /usr/local/bin/
sudo chmod +x /usr/local/bin/node_exporter
# systemd unit для Node Exporter
sudo tee /etc/systemd/system/node_exporter.service > /dev/null <<'EOF'
[Unit]
Description=Prometheus Node Exporter
After=network.target
[Service]
Type=simple
User=nobody
ExecStart=/usr/local/bin/node_exporter \
--collector.systemd \
--collector.processes \
--collector.interrupts \
--collector.tcpstat \
--collector.diskstats \
--web.listen-address=:9100 \
--web.telemetry-path=/metrics
Restart=on-failure
RestartSec=5s
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable --now node_exporter
# Проверка
curl -s http://localhost:9100/metrics | head -20
Ключевые метрики для алертов (Prometheus rules):
# /etc/prometheus/rules/node-alerts.yml
groups:
- name: node-alerts
rules:
- alert: HighCPULoad
expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
for: 10m
labels:
severity: warning
annotations:
summary: "CPU > 85% на {{ $labels.instance }} в течение 10 мин"
- alert: LowMemory
expr: node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes * 100 < 10
for: 5m
labels:
severity: critical
annotations:
summary: "Доступно < 10% RAM на {{ $labels.instance }}"
- alert: DiskSpaceCritical
expr: node_filesystem_avail_bytes{fstype!~"tmpfs|fuse.lxcfs"} / node_filesystem_size_bytes * 100 < 10
for: 5m
labels:
severity: critical
annotations:
summary: "Диск {{ $labels.mountpoint }} заполнен на 90%+"
- alert: HighDiskIOUtilization
expr: rate(node_disk_io_time_seconds_total[5m]) * 100 > 80
for: 10m
labels:
severity: warning
annotations:
summary: "Диск {{ $labels.device }} загружен на 80%+"
- alert: HighNetworkErrorRate
expr: rate(node_network_receive_errs_total[5m]) + rate(node_network_transmit_errs_total[5m]) > 10
for: 5m
labels:
severity: warning
- alert: SystemdServiceFailed
expr: node_systemd_unit_state{state="failed"} == 1
for: 1m
labels:
severity: critical
annotations:
summary: "Сервис {{ $labels.name }} в состоянии Failed"
Часть 6: Безопасность в продакшене
SSH: только так и никак иначе
# /etc/ssh/sshd_config.d/99-hardening.conf
sudo tee /etc/ssh/sshd_config.d/99-hardening.conf > /dev/null <<'EOF'
# Только ключи, никаких паролей
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
PermitEmptyPasswords no
# Запрет root-логина
PermitRootLogin no
# Только конкретные пользователи или группы
AllowGroups sshusers sudo
# Современные алгоритмы (убираем слабые)
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
Ciphers aes256-gcm@openssh.com,aes128-gcm@openssh.com,chacha20-poly1305@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
# Таймаут неактивной сессии (30 минут)
ClientAliveInterval 300
ClientAliveCountMax 6
# Защита от брутфорса: максимум 3 попытки аутентификации
MaxAuthTries 3
MaxSessions 10
LoginGraceTime 30s
# Отключить X11 forwarding и агент forwarding на серверах
X11Forwarding no
AllowAgentForwarding no
# Логировать все подключения
LogLevel VERBOSE
EOF
sudo sshd -t # Проверка синтаксиса
sudo systemctl reload sshd
UFW / nftables: файрвол
# UFW — простой и читаемый интерфейс к iptables/nftables
sudo apt install ufw
# Политика по умолчанию: всё запрещено входящее, всё разрешено исходящее
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Разрешаем только нужное
sudo ufw allow from 10.0.0.0/8 to any port 22 # SSH только из внутренней сети
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow from 10.0.0.0/8 to any port 9100 # Node Exporter только изнутри
# Rate limiting для SSH (защита от брутфорса)
sudo ufw limit ssh
sudo ufw enable
sudo ufw status verbose
# Логирование отклонённых пакетов
sudo ufw logging on
fail2ban: автоматическая блокировка атак
sudo apt install fail2ban
# /etc/fail2ban/jail.local
sudo tee /etc/fail2ban/jail.local > /dev/null <<'EOF'
[DEFAULT]
bantime = 3600 # Блокировка на 1 час
findtime = 600 # Окно анализа: 10 минут
maxretry = 5 # Максимум попыток
# Уведомление по email
destemail = ops@yourcompany.com
sender = fail2ban@yourserver.example.com
action = %(action_mwl)s # Ban + email с логами
# Белый список (никогда не блокировать)
ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 192.168.0.0/16
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
maxretry = 3 # SSH: только 3 попытки
[nginx-http-auth]
enabled = true
maxretry = 5
[nginx-limit-req]
enabled = true
maxretry = 10
[nginx-botsearch]
enabled = true
maxretry = 2
EOF
sudo systemctl enable --now fail2ban
sudo fail2ban-client status # Общий статус
sudo fail2ban-client status sshd # Статус SSH jail
auditd: аудит действий на сервере
sudo apt install auditd
# /etc/audit/rules.d/99-production.rules
sudo tee /etc/audit/rules.d/99-production.rules > /dev/null <<'EOF'
# Удаляем все текущие правила
-D
# Максимальный буфер (для highload)
-b 8192
# Отказоустойчивость: при ошибке — продолжать (не паниковать)
-f 1
# ── Критические файлы ────────────────────────────────────────────────────────
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/sudoers -p wa -k sudo_changes
-w /etc/ssh/ -p wa -k ssh_config
# Системные бинарники
-w /usr/bin/sudo -p x -k privileged
-w /usr/bin/su -p x -k privileged
-w /bin/chmod -p x -k privileged
-w /bin/chown -p x -k privileged
# Изменения конфигурации cron
-w /etc/cron.d/ -p wa -k cron
-w /var/spool/cron/ -p wa -k cron
# Загрузка/выгрузка модулей ядра
-w /sbin/insmod -p x -k kernel_modules
-w /sbin/rmmod -p x -k kernel_modules
-a always,exit -F arch=b64 -S init_module -k kernel_modules
# Сетевые изменения
-a always,exit -F arch=b64 -S sethostname -S setdomainname -k system-locale
-w /etc/hosts -p wa -k hosts
-w /etc/network/ -p wa -k network
# Неудачные системные вызовы (попытки эскалации привилегий)
-a always,exit -F arch=b64 -S open -F exit=-EACCES -k access_denied
-a always,exit -F arch=b64 -S open -F exit=-EPERM -k access_denied
# Мониторинг директории приложения
-w /opt/myapp/bin/ -p x -k app_exec
EOF
sudo augenrules --load # Загрузить правила
sudo auditctl -l # Проверить активные правила
ausearch -k sudo_changes --start today # Посмотреть события sudo
aureport --summary # Сводный отчёт
Часть 7: Процедуры и автоматизация обслуживания
Скрипт проверки здоровья сервера
#!/bin/bash
# /usr/local/bin/health-check.sh — ежедневный health check
# Запускать через cron: 0 7 * * * /usr/local/bin/health-check.sh
set -euo pipefail
REPORT_FILE="/var/log/health-check-$(date +%Y%m%d).log"
ALERT_EMAIL="ops@yourcompany.com"
ISSUES=0
log() { echo "[$(date '+%H:%M:%S')] $*" | tee -a "$REPORT_FILE"; }
warn() { log "⚠️
WARN: $*"; ((ISSUES++)); } err() { log "❌ ERROR: $*"; ((ISSUES++)); } ok() { log "✅ OK: $*"; } log "=== Health Check: $(hostname) === $(date) ===" # ── Свободное место на дисках ──────────────────────────────────────────────── log "--- Дисковое пространство ---" while IFS= read -r line; do usage=$(echo "$line" | awk '{print $5}' | tr -d '%') mount=$(echo "$line" | awk '{print $6}') [ "$usage" -ge 90 ] && err "Диск $mount заполнен на $usage%" && continue [ "$usage" -ge 80 ] && warn "Диск $mount заполнен на $usage%" && continue ok "Диск $mount: $usage%" done < <(df -h --output=pcent,target -x tmpfs -x devtmpfs | tail -n +2) # ── Память ─────────────────────────────────────────────────────────────────── log "--- Память ---" available_mb=$(awk '/MemAvailable/ {printf "%.0f", $2/1024}' /proc/meminfo) total_mb=$(awk '/MemTotal/ {printf "%.0f", $2/1024}' /proc/meminfo) used_pct=$(( (total_mb - available_mb) * 100 / total_mb )) swap_used=$(awk '/SwapTotal/{t=$2} /SwapFree/{f=$2} END{printf "%.0f", (t-f)/1024}' /proc/meminfo) [ "$used_pct" -ge 90 ] && err "RAM: использовано $used_pct%" [ "$used_pct" -ge 80 ] && warn "RAM: использовано $used_pct%" ok "RAM: $used_pct% использовано ($available_mb МБ свободно)" [ "$swap_used" -gt 100 ] && warn "Swap: используется ${swap_used} МБ" # ── Failed сервисы ─────────────────────────────────────────────────────────── log "--- Systemd сервисы ---" failed=$(systemctl list-units --state=failed --no-legend --no-pager | awk '{print $1}') if [ -n "$failed" ]; then for svc in $failed; do err "Сервис в Failed: $svc" done else ok "Все сервисы работают" fi # ── Нагрузка ───────────────────────────────────────────────────────────────── log "--- CPU загрузка ---" cpu_cores=$(nproc) load_avg=$(cut -d' ' -f1 /proc/loadavg) load_int=$(echo "$load_avg" | cut -d. -f1) [ "$load_int" -ge "$((cpu_cores * 2))" ] && err "Load average $load_avg при $cpu_cores ядрах" [ "$load_int" -ge "$cpu_cores" ] && warn "Load average $load_avg при $cpu_cores ядрах" ok "Load average: $load_avg (ядер: $cpu_cores)" # ── SSL-сертификаты ────────────────────────────────────────────────────────── log "--- SSL сертификаты ---" for cert_file in /etc/letsencrypt/live/*/cert.pem /etc/ssl/certs/*.pem; do [ -f "$cert_file" ] || continue expiry=$(openssl x509 -in "$cert_file" -noout -enddate 2>/dev/null \ | cut -d= -f2) expiry_epoch=$(date -d "$expiry" +%s 2>/dev/null || continue) days_left=$(( (expiry_epoch - $(date +%s)) / 86400 )) [ "$days_left" -le 7 ] && err "Сертификат $cert_file истекает через $days_left дней!" [ "$days_left" -le 30 ] && warn "Сертификат $cert_file истекает через $days_left дней" [ "$days_left" -gt 30 ] && ok "Сертификат $cert_file: осталось $days_left дней" done # ── Итог ───────────────────────────────────────────────────────────────────── log "" log "=== Итог: $ISSUES проблем(а) ===" if [ "$ISSUES" -gt 0 ]; then mail -s "⚠️ Health Check FAILED: $(hostname) — $ISSUES проблем" \ "$ALERT_EMAIL" < "$REPORT_FILE" fi exit $((ISSUES > 0 ? 1 : 0))
Автоматизированный бэкап с проверкой целостности
#!/bin/bash
# /usr/local/bin/backup.sh — ежедневный инкрементальный бэкап
set -euo pipefail
BACKUP_DIR="/var/backup"
REMOTE="backup-user@backup-server.example.com:/backups/$(hostname)"
DATE=$(date +%Y%m%d-%H%M)
LOG="/var/log/backup.log"
RETENTION_DAYS=30
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; }
log "=== Бэкап начат: $DATE ==="
# ── Конфигурация ─────────────────────────────────────────────────────────────
rsync -avz --delete \
--exclude='*.tmp' \
--exclude='*.log' \
--exclude='/proc' \
--exclude='/sys' \
--exclude='/dev' \
--exclude='/run' \
--exclude='/tmp' \
--link-dest="$REMOTE/latest" \
/ \
"$REMOTE/$DATE/" \
--log-file="$LOG" \
2>&1
# Обновляем симлинк на последний бэкап
ssh backup-user@backup-server.example.com \
"ln -sfn /backups/$(hostname)/$DATE /backups/$(hostname)/latest"
# ── Проверка целостности (checksums) ─────────────────────────────────────────
log "Генерация checksums..."
find /etc /opt/myapp /var/lib -type f -newer /var/backup/.last_backup \
-exec sha256sum {} \; > "/var/backup/checksums-$DATE.txt" 2>/dev/null || true
rsync -avz "/var/backup/checksums-$DATE.txt" \
"$REMOTE/checksums/$DATE.txt"
touch /var/backup/.last_backup
# ── Очистка старых бэкапов ───────────────────────────────────────────────────
log "Очистка бэкапов старше $RETENTION_DAYS дней..."
ssh backup-user@backup-server.example.com \
"find /backups/$(hostname) -maxdepth 1 -type d -mtime +$RETENTION_DAYS \
! -name 'latest' -exec rm -rf {} + 2>/dev/null; echo done"
log "=== Бэкап завершён успешно ==="
Быстрая шпаргалка: что сделать на каждом новом сервере
#!/bin/bash
# Минимальный чеклист для нового production-сервера
echo "=== 1. Обновить систему ==="
apt update && apt upgrade -y # или dnf update -y
echo "=== 2. Создать admin-пользователя ==="
adduser deploy
usermod -aG sudo deploy
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys
echo "=== 3. Настроить SSH ==="
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
systemctl reload sshd
echo "=== 4. Файрвол ==="
ufw --force enable
ufw default deny incoming
ufw allow from 10.0.0.0/8 to any port 22
ufw allow 80 443
echo "=== 5. NTP ==="
apt install -y chrony
systemctl enable --now chrony
echo "=== 6. Применить sysctl тюнинг ==="
# (скопировать /etc/sysctl.d/99-production.conf из шаблона)
sysctl --system
echo "=== 7. Node Exporter ==="
# (установить и включить — см. раздел выше)
echo "=== 8. fail2ban ==="
apt install -y fail2ban
systemctl enable --now fail2ban
echo "=== 9. Unattended upgrades ==="
apt install -y unattended-upgrades
dpkg-reconfigure -f noninteractive unattended-upgrades
echo "=== 10. Базовый мониторинг диска ==="
# Добавить в cron: 0 7 * * * /usr/local/bin/health-check.sh
echo "✅ Базовая настройка завершена.
Не забыть:" echo " - Настроить бэкап"
echo " - Подключить к Prometheus/Grafana"
echo " - Добавить в систему конфигурационного управления (Ansible/Salt)"
echo " - Задокументировать сервер в CMDB"
Заключение
Production Linux — это не дистрибутив, это дисциплина. Правильный выбор дистрибутива даёт фундамент. Тюнинг ядра и sysctl — производительность. systemd с правильными политиками — надёжность. Мониторинг — видимость. Безопасность — защиту. А автоматизация обслуживания — предсказуемость.
Самые частые провалы в продакшене:
Не настроены лимиты (
ulimit,systemd LimitNOFILE) — приложение падает при нагрузкеНет мониторинга диска — о заполнении узнают по жалобам пользователей
SSH доступен по паролям — вопрос не "взломают ли", а "когда"
Не тестируются бэкапы — они есть, но не работают когда нужны
Нет процедуры обновления — серверы не обновляются годами
Используйте Ansible или Terraform для воспроизводимости: каждая настройка из этой статьи должна быть в коде, а не только в голове у одного администратора. Сервер должен разворачиваться автоматически — это единственная гарантия того, что в 3 часа ночи вы сможете его поднять заново.
Create an account or sign in to leave a review
There are no reviews to display.