Jump to content
View in the app

A better way to browse. Learn more.

T.M.I IThub

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

Articles

Our website articles
Введение: зачем вообще это ваше ООП?
Если ты только начинаешь путь программиста, то, скорее всего, уже слышал загадочные слова: ООП, классы, объекты, инкапсуляция, наследование… В этот момент многие новички испытывают лёгкую панику и желание вернуться к echo "Hello, world";.
Спокойно. Ты не один. ООП — это не магия и не заговор сеньоров. Это всего лишь способ думать о программе, чтобы:
код был понятнее;
код было легче расширять;
код было не стыдно показать другим (и себе через полгода).
Сегодня мы разберём ООП от и до, простым человеческим языком, с примерами на PHP, с шутками и жизненными аналогиями.
Что такое ООП простыми словами
ООП (объектно-ориентированное программирование) — это подход, при котором программа состоит из объектов, которые:
имеют данные (состояние);
умеют что‑то делать (поведение);
общаются друг с другом.
Аналогия из жизни (без неё никак)
Представь, что ты играешь в RPG.
Персонаж — это объект
Класс персонажа (воин, маг) — это класс
Здоровье, мана, сила — это свойства
Ударить мечом, кастануть огненный шар — это методы
Примерно так же всё работает и в коде.
Класс — это чертёж
Класс — это описание того, каким должен быть объект.
Пример класса в PHP
class Car { public string $brand; public string $color; public function drive() { echo "Машина едет"; } } Здесь мы описали:
что у машины есть бренд и цвет;
что машина умеет ехать.
Объект — это конкретная вещь
Объект — это экземпляр класса. То есть реальная штука, созданная по чертежу.
$car1 = new Car(); $car1->brand = 'BMW'; $car1->color = 'black'; $car1->drive(); Теперь:
$car1 — это конкретная машина;
у неё есть бренд BMW;
она умеет ехать (и едет).
Можно создать хоть тысячу машин:
$car2 = new Car(); $car2->brand = 'Toyota'; $car2->color = 'white'; Свойства и методы
Свойства
Свойства — это данные объекта.
public string $brand; Методы
Методы — это действия объекта.
public function drive() { echo "Машина едет"; } Конструктор: момент рождения объекта
Когда объект создаётся, мы часто хотим сразу задать ему начальные данные.
Для этого есть конструктор.
class Car { public string $brand; public string $color; public function __construct(string $brand, string $color) { $this->brand = $brand; $this->color = $color; } } Использование:
$car = new Car('Audi', 'red'); Инкапсуляция: не трогай, сломается
Инкапсуляция — это принцип, который говорит:
Зачем это нужно?
Чтобы:
объект нельзя было сломать случайно;
данные изменялись только правильным способом.
Модификаторы доступа
В PHP есть три основных:
public — доступно всем
protected — доступно классу и наследникам
private — доступно только внутри класса
class BankAccount { private int $balance = 0; public function deposit(int $amount) { $this->balance += $amount; } public function getBalance(): int { return $this->balance; } } Теперь нельзя сделать так:
$account->balance = 1000000; // ❌
нельзя
И это хорошо. Банк доволен. Ты доволен. Мир стабилен.
Наследование: не изобретай велосипед
Наследование позволяет создавать новые классы на основе существующих.
class Animal { public function speak() { echo "Животное издаёт звук"; } } class Dog extends Animal { public function speak() { echo "Гав!"; } } $dog = new Dog(); $dog->speak(); // Гав! Полиморфизм: один интерфейс — разное поведение
Полиморфизм — это когда разные объекты могут отвечать на один и тот же вызов по‑разному.
class Cat extends Animal { public function speak() { echo "Мяу"; } } $animals = [new Dog(), new Cat()]; foreach ($animals as $animal) { $animal->speak(); } Результат:
Гав! Мяу Абстракция: только главное
Абстракция — это когда мы описываем что объект должен делать, но не как именно.
abstract class Shape { abstract public function getArea(): float; } class Square extends Shape { public function __construct(private float $side) {} public function getArea(): float { return $this->side ** 2; } } Итог: вся суть ООП в одном месте
ООП держится на четырёх китах:
Инкапсуляция — скрываем лишнее
Наследование — переиспользуем код
Полиморфизм — один интерфейс, разное поведение
Абстракция — работаем с сутью, а не деталями
Если ты понял это — поздравляю, ты понял ООП.
Напоследок
ООП — это не цель, а инструмент. Он не делает код автоматически хорошим, но помогает писать его осознанно.
Если после этой статьи ты:
не боишься слова «класс»;
понимаешь, зачем нужны объекты;
можешь объяснить ООП другу на кухне;
значит, всё получилось.
А если нет — перечитай ещё раз. Программисты так делают постоянно.
Удачи в коде и поменьше фатальных ошибок.
Электроника и электрика: полное погружение
От щелчка выключателя до умных устройств, которые думают за нас
Введение: почему электроника и электрика — это важно
Мы живём в мире, который буквально питается электричеством. Свет в комнате, смартфон в руке, интернет, автомобили, заводы, «умные» дома — всё это работает благодаря двум тесно связанным дисциплинам:
электрике — науке и практике передачи и распределения электроэнергии;
электронике — управлению электричеством с помощью компонентов и схем.
Эта статья — не сухой учебник и не набор формул. Это глубокое, но понятное путешествие: от базовых принципов до современных технологий, с логикой, примерами и пониманием почему всё работает именно так.
Электрика и электроника: в чём разница
Начнём с путаницы, которая есть почти у всех новичков.
Электрика
Электрика отвечает за:
передачу энергии;
высокие токи и напряжения;
питание домов, зданий, оборудования.
Примеры:
розетки;
автоматы и УЗО;
кабели;
трансформаторы;
электродвигатели.
Электроника
Электроника отвечает за:
управление электричеством;
обработку сигналов;
логику, вычисления, автоматизацию.
Примеры:
резисторы и транзисторы;
микросхемы;
микроконтроллеры;
датчики;
платы Arduino, ESP32, Raspberry Pi.
Основы, без которых никуда
Электрический ток
Ток — это движение электронов.
Как вода в трубе:
напряжение — давление;
ток — количество воды;
сопротивление — узость трубы.
Напряжение (Вольты)
Напряжение показывает, насколько сильно электроны хотят двигаться.
1.5 В — батарейка
5 В — USB
220–230 В — розетка (опасно!)
Сопротивление (Омы)
Сопротивление ограничивает ток.
Без сопротивления:
перегрев;
короткое замыкание;
дым (тот самый, на котором работает электроника).
Закон Ома — фундамент всего
Закон Ома связывает три ключевые величины:
Где:
I — ток
U — напряжение
R — сопротивление
Если ты понял закон Ома — ты понял половину электроники.
Основные электронные компоненты
Резисторы
ограничивают ток;
делят напряжение;
защищают схемы.
Без них электроника превращается в фейерверк.
Конденсаторы
накапливают заряд;
сглаживают напряжение;
фильтруют шумы.
Используются в питании, таймерах, аудиосхемах.
Диоды
пропускают ток только в одну сторону;
защищают от переполюсовки;
выпрямляют переменный ток.
Транзисторы
Сердце электроники.
Транзистор может:
усиливать сигнал;
работать как электронный выключатель;
управлять большими токами малыми.
Электронные схемы: как читать и понимать
Схема — это язык электроники.
Хорошая схема:
показывает логику;
упрощает ремонт;
позволяет повторить устройство.
Важно научиться:
читать обозначения;
понимать путь тока;
видеть функциональные блоки.
Источники питания
Линейные блоки питания
простые;
надёжные;
тяжёлые и горячие.
Импульсные блоки питания
компактные;
эффективные;
сложные.
Именно они в зарядках, компьютерах и бытовой технике.
Микроконтроллеры: мозг современных устройств
Микроконтроллер — это маленький компьютер на одной микросхеме.
Он умеет:
читать датчики;
управлять выходами;
выполнять программы.
Популярные платформы
Arduino — идеально для старта
ESP8266 / ESP32 — Wi‑Fi и IoT
STM32 — промышленный уровень
Автоматизация и умные системы
Электроника давно вышла за пределы плат.
Сегодня она управляет:
освещением;
климатом;
безопасностью;
производством;
транспортом.
Основные элементы автоматизации:
датчики;
контроллеры;
исполнительные механизмы.
Безопасность: самое важное
Работа с электричеством требует уважения.
Основные правила:
не работать под напряжением;
использовать защиту;
проверять прибором;
понимать, что делаешь.
Как развиваться дальше
Для новичков
базовая теория;
простые схемы;
макетные платы;
Arduino.
Для продолжающих
расчёт схем;
PCB-дизайн;
силовая электроника;
микроконтроллеры.
Для профессионалов
промышленная автоматизация;
встраиваемые системы;
высокочастотная электроника;
энергоэффективность.
Заключение
Электроника и электрика — это не просто профессия или хобби. Это способ понимать мир, в котором мы живём.
Каждая схема — это логика.
Каждый провод — это путь энергии.
Каждое устройство — результат инженерного мышления.
Если ты начал разбираться в этом — значит, ты уже сделал первый шаг в мир, который управляет будущим.
И да, если что-то не заработало с первого раза — значит, ты всё делаешь правильно.
Объектно-ориентированное программирование (ООП) — это парадигма разработки программного обеспечения, в которой программная система строится из «объектов» — сущностей, объединяющих данные и операции над ними. В отличие от чисто процедурного стиля, где функции и данные существуют отдельно, ООП помогает моделировать предметную область ближе к реальности, улучшает модульность, повторное использование и поддержку кода.Хотя язык С (ANSI C) исторически не является объектно-ориентированным, он обладает инструментами, которые позволяют эмулировать многие свойства ООП (такие как инкапсуляция, полиморфизм и частично наследование) с помощью структур, указателей и функций.

Зачем нам применять ООП в C

ООП дает следующие важные преимущества:
Модульность и инкапсуляция. Код структурируется вокруг объектов, а не процедур, что облегчает разделение ответственности и управление сложностью.
Повторное использование компонентов. Объекты и функции, работающие с ними, можно многократно использовать в разных частях программы.
Упрощённая поддержка. В больших проектах изменения в одном объекте не требуют полномасштабной переработки остального кода.
Чёткие интерфейсы. Абстрагирование внутренних деталей объектов делает использование API понятным и безопасным.
Тем не менее, реализация ООП в C требует ручного управления памятью и структурой, а также дополнительных усилий для имитации механизмов, которые в других языках реализованы на уровне компилятора.

Основная идея: структура плюс функции

В традиционных объектно-ориентированных языках (например, Java или C++) класс содержит поля (состояние) и методы (поведение). В C аналог класса достигается через:
struct — описание набора данных, представляющего объект.

Функции, принимающие указатель на struct — имитация методов, которые работают с объектом
Указатели на функции в структуре — позволяют эмулировать полиморфизм и виртуальные методы.

Простой пример: объект «Point»
/* point.h */ #ifndef POINT_H #define POINT_H typedef struct Point Point; /* Конструктор */ Point* Point_new(int x, int y); /* «Методы» */ void Point_move(Point *self, int dx, int dy); void Point_print(const Point *self); /* Деструктор */ void Point_delete(Point *self); #endif
/* point.c */ #include "point.h" #include <stdlib.h> #include <stdio.h> struct Point { int x; int y; }; Point* Point_new(int x, int y) { Point *p = malloc(sizeof(Point)); if (p) { p->x = x; p->y = y; } return p; } void Point_move(Point *self, int dx, int dy) { if (self) { self->x += dx; self->y += dy; } } void Point_print(const Point *self) { if (self) { printf("Point at (%d, %d)\n", self->x, self->y); } } void Point_delete(Point *self) { free(self); }
/* main.c */ #include "point.h" int main(void) { Point *p = Point_new(10, 20); Point_print(p); Point_move(p, 5, -3); Point_print(p); Point_delete(p); return 0; }
В этом примере структура Point представляет объект, а функции Point_move, Point_print работают как методы. Объект создаётся через конструктор, а затем использует «методы». Такой подход соответствует эмуляции классов в C.

Инкапсуляция и сокрытие данных
Хотя C не обладает уровнями доступа (private, public), инкапсуляцию можно приблизить с помощью неполных типов (opaque type): в заголовке объявляется только указатель на структуру, а сама структура определена в .c-файле. Это скрывает внутренние поля объекта от пользователя API.

Полиморфизм с помощью указателей на функции
Для поддержки поведения, похожего на виртуальные методы, объект может содержать указатели на функции:
typedef struct { void (*speak)(void *); } Animal; void dogSpeak(void *self) { printf("Woof\n"); } void catSpeak(void *self) { printf("Meow\n"); } int main(void) { Animal dog = { dogSpeak }; Animal cat = { catSpeak }; dog.speak(&dog); cat.speak(&cat); }
Это позволяет разным объектам иметь разную реализацию поведения через динамическое связывание.

Наследование: структурное встраивание
Хотя прямого механизма наследования в C нет, его можно приблизить через встраивание структур:
typedef struct { int value; } Base; typedef struct { Base base; int extra; } Derived;
Это позволяет обращаться к общей части как к «базовому классу». Такие техники широко используются в практике написания крупных библиотек на C (например, в Linux kernel).

Когда стоит и когда не стоит использовать ООП в C

Когда стоит
Пишете сложные библиотеки или драйверы, где нужно чёткое разделение ответственности.
Проект должен быть расширяемым и легко поддерживаемым в долгосрочной перспективе.
Вы хотите приблизить архитектуру к индустриальным стандартам, но обязаны использовать ANSI C.
Когда не стоит
Проект очень мал, и усложнение архитектуры не оправдано.
Требуется высокая производительность и минимальные накладные расходы.
Вы работаете в команде, где стандартные C++ механизмы ООП предпочтительнее и доступны.

Заключение
Объектно-ориентированное программирование в C возможно и полезно в задачах, где требуется структурирование больших систем и разделение интерфейса от реализации. Хотя язык не предоставляет встроенных механик классов, наследования и полиморфизма, эти свойства можно эффективно имитировать через структуры, указатели и хорошо продуманные API. Такой подход усиливает поддерживаемость, модульность и переиспользуемость кода в серьёзных проектах на С.
Процесс загрузки Linux: подробный разбор по этапам
Загрузка операционной системы Linux — это многоэтапный процесс, в ходе которого система проходит путь от включения питания до запуска пользовательской среды. Каждый этап выполняет строго определённую функцию и критически важен для корректной и стабильной работы ОС. Ниже подробно рассмотрены все ключевые стадии загрузки Linux в правильной последовательности.
1. Включение питания и инициализация BIOS / UEFI
Процесс загрузки начинается в момент подачи питания на компьютер. Управление получает микропрограмма материнской платы — BIOS (Basic Input/Output System) или его современный аналог UEFI (Unified Extensible Firmware Interface).
На этом этапе выполняются следующие задачи:
Инициализация базовых аппаратных компонентов: процессора, оперативной памяти, контроллеров ввода-вывода.
Проведение POST (Power-On Self Test) — самотестирования оборудования для выявления критических ошибок.
Определение доступных загрузочных устройств в соответствии с заданным приоритетом (SSD, HDD, USB-накопители, сетевой загрузчик).
Передача управления загрузчику операционной системы, расположенному на выбранном устройстве.
UEFI отличается от классического BIOS поддержкой графического интерфейса, GPT-разметки, Secure Boot и более гибкой архитектурой загрузки.
2. Загрузчик операционной системы
После завершения инициализации BIOS/UEFI управление передаётся загрузчику. В экосистеме Linux наиболее распространённым является GRUB2 (Grand Unified Bootloader), однако также могут использоваться LILO, Syslinux, systemd-boot и другие решения.
Основные функции загрузчика:
Загрузка ядра Linux (vmlinuz) в оперативную память.
Загрузка и передача ядру параметров командной строки.
(Опционально) Отображение меню выбора операционной системы, версии ядра или режима загрузки (обычный, recovery, single-user).
Загрузка начального RAM-диска (initramfs или initrd).
В системах с UEFI загрузчик запускается через EFI-механизм и представляет собой .efi-приложение.
3. Инициализация ядра Linux
После загрузки ядра в память управление полностью переходит к нему. Ядро Linux является центральным компонентом операционной системы и отвечает за взаимодействие между программным обеспечением и аппаратной частью.
На этапе инициализации ядра выполняются:
Настройка процессора, планировщика задач и управления памятью.
Обнаружение и инициализация доступного оборудования.
Запуск драйверов, встроенных в ядро или загружаемых динамически.
Монтирование временной корневой файловой системы (initramfs).
Поиск и монтирование основной корневой файловой системы.
Запуск первого пользовательского процесса с PID 1 — init.
Если ядро не может найти корневую файловую систему или необходимые драйверы, загрузка завершается ошибкой (kernel panic).
4. init-система и запуск пользовательского пространства
Процесс с идентификатором PID 1 — это основа пользовательского пространства Linux. Исторически это был init (SysVinit), однако в современных дистрибутивах его роль чаще всего выполняет systemd.
Функции init-системы:
Чтение конфигурации загрузки (runlevels или targets).
Монтирование файловых систем.
Запуск и управление системными сервисами и демонами (udev, networking, logging).
Обеспечение корректного порядка запуска служб с учётом зависимостей.
Перевод системы в целевое состояние (например, multi-user.target или graphical.target).
Systemd также отвечает за логирование (journald), управление устройствами и мониторинг состояния сервисов.
5. Запуск графической среды (при наличии)
Если система предназначена для работы в графическом режиме, следующим этапом становится запуск графической подсистемы.
На этом шаге происходит:
Запуск дисплейного сервера (Xorg или Wayland).
Инициализация менеджера входа в систему (GDM, SDDM, LightDM и др.).
Подготовка графической оболочки (GNOME, KDE Plasma, XFCE, Cinnamon и т. д.).
Для серверных систем этот этап обычно отсутствует — загрузка завершается на уровне текстовой консоли.
6. Пользовательский сеанс
Заключительный этап загрузки начинается после аутентификации пользователя. В этот момент система полностью готова к работе.
Запускаются:
Пользовательские конфигурационные файлы и настройки среды.
Фоновые процессы и пользовательские службы.
Графические или консольные приложения, необходимые для работы.
Рабочее окружение с доступом к файловой системе, сети и периферии.
На этом этапе Linux переходит в стабильное рабочее состояние.
FAQ — Часто задаваемые вопросы
Что делать, если Linux не загружается?
Проверьте настройки BIOS/UEFI, порядок загрузки устройств и попробуйте восстановить загрузчик с помощью Live USB (например, через grub-install).
Чем GRUB отличается от других загрузчиков?
GRUB поддерживает большое количество файловых систем, гибкую конфигурацию, меню выбора ОС и сложные сценарии мультизагрузки.
Можно ли обойтись без initramfs?
Да, но только при статической сборке драйверов в ядро. Это усложняет обновление и снижает универсальность системы.
Что делать, если загрузка зависает на этапе init/systemd?
Используйте режим восстановления, проанализируйте логи командой journalctl -xb и проверьте конфигурацию сервисов.
Как ускорить загрузку Linux?
Отключите ненужные сервисы (systemctl disable), оптимизируйте targets, используйте SSD и файловые системы с быстрой инициализацией.
Заключение
Процесс загрузки Linux представляет собой строго последовательную цепочку этапов, начиная с инициализации оборудования и заканчивая запуском пользовательской среды. BIOS или UEFI подготавливают аппаратную платформу и передают управление загрузчику, который загружает ядро. Ядро, в свою очередь, инициализирует систему и запускает init-процесс, управляющий всеми остальными компонентами. После старта системных служб и (при необходимости) графической среды пользователь получает полностью готовую к работе операционную систему.
Понимание этих этапов критически важно для диагностики проблем, оптимизации загрузки и администрирования Linux-систем.
1. Сколько соединений может обработать один воркер
В NGINX каждый воркер‑процесс способен обслуживать определённое число одновременных соединений. Это задаётся директивой:
worker_connections 1024; Здесь учитываются все дескрипторы, включая клиентские соединения и прокси‑сессии к бэкендам.
По умолчанию NGINX использует 768 соединений, но для серьёзных нагрузок лучше поднять до 1024+, не забывая про лимит открытых файлов в ОС (ulimit -n).
Расчёт максимального числа клиентов:
max_clients=worker_processes×worker_connections\text{max\_clients} = \text{worker\_processes} \times \text{worker\_connections}max_clients=worker_processes×worker_connections
Пример:
worker_processes = 4 worker_connections = 1024 max_clients = 4 × 1024 = 4096 То есть сервер может обслуживать до 4096 соединений одновременно (минус коннекты к upstream).
Пример конфигурации events.conf:
events { worker_connections 1024; # максимум соединений на воркер # accept_mutex on; # равномерное принятие коннектов (опционально) } 2. Ограничение одновременных соединений: limit_conn
Чтобы защитить сервер от «дружелюбной» перегрузки, например, когда бот‑краулер открывает сотни соединений, используется ngx_http_limit_conn_module:
limit_conn_zone — создаёт область памяти для хранения текущих соединений (обычно по IP).
limit_conn — задаёт лимит одновременных соединений.
Пример настройки:
http { limit_conn_zone $binary_remote_addr zone=perip:10m; server { location / { limit_conn perip 10; # не больше 10 соединений на IP limit_conn_status 429; # выдаём 429 вместо дефолтного 503 limit_conn_log_level info; # уровень логирования при отказе } } } 10m зоны на 64-битной платформе хранят примерно 16 000 уникальных IP.
При переполнении NGINX сразу отдаёт ошибку (503/429).
Dry-run и логирование:
limit_conn_dry_run on; # не блокирует, а только логирует limit_conn_log_level notice; Так можно тестировать лимиты, не блокируя клиентов.
3. Ограничение скорости запросов: limit_req
Если нужно контролировать частоту запросов, применяется ngx_http_limit_req_module.
Механизм основан на «leaky bucket» — запросы попадают в бакет и «вытекают» с заданной скоростью.
http { limit_req_zone $binary_remote_addr zone=login:10m rate=5r/s; server { location /login { limit_req zone=login burst=10 nodelay; limit_req_status 429; limit_req_log_level warn; } } } burst — пиковое количество запросов, которое сервер пропускает сверх лимита.
nodelay — отказ сразу при превышении лимита, без задержки.
Без nodelay лишние запросы будут ожидать своей очереди.
4. Где хранится состояние
В NGINX есть shared memory zones — специальная общая память для лимитов:
Slab-пул разбивает память на одинаковые блоки для быстрого выделения и освобождения.
Для limit_conn используется хеш-таблица или сбалансированное дерево по IP.
Для limit_req — красно‑чёрное дерево + очередь «протекающего ведра».
Доступ защищён встроенным мьютексом — нет гонок даже при сотнях воркеров.
Механизм работы:
При новом запросе NGINX берёт ключ (IP), ищет запись в зоне.
Если записи нет — создаёт новую.
Проверяет счётчики и решает, пропускать запрос или отказать.
5. Реакция сервера при превышении лимита
По умолчанию:
limit_conn → 503 Service Unavailable
limit_req → 503 Service Unavailable
Лучше отдавать 429 Too Many Requests, чтобы клиенты понимали причину.
Пример логов
2025/04/04 13:45:12 [warn] 12345#0: *67890 limiting requests, excess: 5 by zone "login" 2025/04/04 13:45:12 [info] 12345#0: *67890 a client request is temporarily blocked by zone "perip" 6. Практические нюансы
Размер зоны: 1 MB ≈ 16 000 записей; для 100 000 IP/сутки потребуется 10–12 MB.
burst vs delay: для login/API можно применять разные подходы:
login: burst=1; nodelay
API: burst=10; delay 10
Разные зоны для разных endpoint: чтобы лимиты не конфликтовали.
Исключения: через map или geo можно исключить внутренние IP:
map $remote_addr $limit { 10.0.0.0/8 ""; default $binary_remote_addr; } limit_req_zone $limit zone=api:20m rate=20r/s; 7. Сторонние модули и NGINX Plus
7.1 ngx_brotli — снижение трафика
Brotli‑сжатие уменьшает объём передаваемых данных, разгружая лимиты по трафику:
http { brotli on; brotli_comp_level 6; brotli_types text/html text/css application/javascript; } 7.2 ngx_http_limit_traffic_ratefilter_module — лимит по байтам
Позволяет ограничить скорость передачи, например 100 KB/s на IP:
http { limit_traffic_rate_zone $binary_remote_addr zone=bytetraf:10m; server { location /downloads/ { limit_traffic_rate zone=bytetraf rate=100k; } } } 7.3 NGINX Plus — расширенные возможности
Синхронизация зон между нодами — zone_sync.
Адаптивное ограничение: скорость передачи зависит от метрик, например:
map $upstream_response_time $dyn_rate { "~^[0-9]\.[0-1]" 200k; # быстрые ответы — больше скорости default 50k; # медленные — ограничение } server { location /stream/ { limit_rate $dyn_rate; } }
Введение
Администратор Linux-систем отвечает за поддержание стабильной работы программного обеспечения, мониторинг состояния системы, управление аппаратными ресурсами и резервное копирование. В этой статье мы рассмотрим основные команды и принципы работы системного администратора, а также приведём практические примеры, которые помогут новичкам освоить базовое администрирование Linux.
Базовые настройки Linux
Установка имени хоста
Чтобы изменить имя компьютера (hostname), откройте терминал и выполните команду:
sudo hostnamectl set-hostname your_hostname Примечание:
Замените your_hostname на желаемое имя.
Команда hostnamectl изменяет имя хоста постоянно. В отличие от hostname, которая действует только в текущей сессии.
Настройка часового пояса
Чтобы установить часовой пояс, создайте символическую ссылку на нужную зону:
sudo ln -sf /usr/share/zoneinfo/Asia/Kolkata /etc/localtime Примечание:
Путь /usr/share/zoneinfo/Asia/Kolkata замените на ваш регион.
Эта команда обновляет системное время в соответствии с выбранной зоной.
Управление файлами в Linux
В Linux всё является файлом: устройства, каталоги, конфигурации и программы. Владение базовыми командами управления файлами критически важно.
Команда
Назначение
cd
Переход в другой каталог
ls
Просмотр списка файлов и папок
vi
Консольный редактор для конфигурационных файлов
nano
Удобный редактор с подсказками
touch
Создание нового файла или обновление временной метки
cp
Копирование файлов и каталогов
mv
Перемещение и переименование файлов
rm
Удаление файлов и папок
fdisk
Управление таблицами разделов диска
mount
Монтирование файловой системы
Примеры:
Создание нового файла:
touch example.txt Копирование файла:
cp example.txt backup.txt Переименование файла:
mv backup.txt old_backup.txt Удаление файла:
rm old_backup.txt Сетевые команды
Сетевые инструменты позволяют администратору проверять соединение, настраивать интерфейсы и удалённо управлять системой.
Команда
Назначение
ping
Проверка доступности сервера
traceroute
Отслеживание маршрута пакетов
nslookup
Проверка DNS-записей
ifconfig / ip
Настройка сетевых интерфейсов
ssh
Удалённый доступ и туннелирование
scp
Копирование файлов по SSH
curl
Передача данных на сервер или с него
nmap
Сканирование сети и аудит безопасности
Пример проверки соединения:
ping google.com Пример копирования файла на удалённый сервер:
scp example.txt user@remote:/home/user/ Управление пользователями и группами
В Linux есть три типа пользователей:
Root — суперпользователь с UID 0.
Обычные пользователи — повседневные пользователи с уникальными UID.
Системные пользователи — для работы служб, например mysql или www-data.
Основные команды управления пользователями
Команда
Назначение
adduser
Создание нового пользователя
useradd
Низкоуровневая команда для создания пользователя
passwd
Установка или изменение пароля
deluser / userdel
Удаление пользователя
usermod
Изменение свойств пользователя
groups
Просмотр групп пользователя
groupadd / groupdel
Создание и удаление групп
Пример:
Создание нового пользователя с паролем:
sudo adduser newuser Добавление пользователя в группу sudo:
sudo usermod -aG sudo newuser Диагностика и мониторинг системы
Эффективный администратор следит за состоянием системы и производительностью.
Команда
Назначение
top
Просмотр процессов в реальном времени
htop
Улучшенная версия top с цветным интерфейсом
vmstat
Статистика процессов, памяти и CPU
iostat
Мониторинг загрузки дисков
lsof
Список открытых файлов
nmon
Комплексный мониторинг системы
Работа с логами
Логи позволяют быстро находить ошибки и анализировать работу системы.
Команда
Назначение
dmesg
Сообщения ядра
tail
Последние строки файла
journalctl
Управление логами systemd
Пример просмотра последних 20 строк системного журнала:
journalctl -n 20 Заключение
Администрирование Linux требует понимания работы с файлами, пользователями, сетью, логами и системными ресурсами. Освоение приведённых команд позволяет:
Настраивать и поддерживать систему;
Контролировать пользователей и права доступа;
Проводить диагностику и мониторинг;
Обеспечивать безопасность и стабильность работы системы.
Практическое использование этих команд формирует фундаментальные навыки системного администратора, необходимые для работы в реальной среде Linux.
Ошибки в сетевой инфраструктуре: от планирования до эксплуатации
Ошибки, которые могут допустить IT-специалисты, практически неисчерпаемы. Некоторые из них незаметны обычным пользователям — например, отсутствие настроенного журналирования событий. Даже взлом, произошедший из-за этого, может остаться незамеченным, пока об этом не напишут в новостях.
Другие ошибки становятся очевидными сразу: проблемы в работе сети замечает каждый сотрудник. В этой статье разберём наиболее частые ошибки при проектировании и эксплуатации сетей и дадим практические рекомендации, как их избежать.
1. Планирование сети: превыше всего
Часто сети строятся без должного планирования. Причины бывают разные: экономия средств, отсутствие компетенций у проектировщиков, поспешные решения руководства.
Пример: интегратор готовит детальную спецификацию сети, а заказчик «подрезает» её, чтобы сэкономить — в итоге сеть строится с недочётами, которые потом приходится исправлять дорого и долго.
Правильное планирование включает:
оценку количества рабочих мест и будущего расширения;
разработку документации и схем сети;
расчёт емкости портов на коммутаторах и маршрутизаторах;
планирование отказоустойчивости и резервирования.
2. Кабельная инфраструктура: детали имеют значение
Даже при массовом использовании Wi‑Fi проводные соединения остаются критически важными.
Ошибки:
Недостаток розеток и кабелей.
Плохая организация кабельных каналов.
Попытка «все на Wi‑Fi», что может привести к перегрузке сети.
Решение:
Планируйте количество розеток с запасом (+4–6 на кабинет).
Разделяйте кабели по цветам:
Жёлтые — кроссы,
Красные — серверы и NAS,
Синие — настенные разъёмы и коммутаторы,
Чёрные — инфраструктурные соединения,
Зелёные — временные подключения.
Маркируйте оба конца кабеля, чтобы быстро находить подключение к устройству.
3. Коммутаторы: порты не должны заканчиваться
Ошибка многих сетевых администраторов — неправильный расчёт портовой емкости. Если все порты заняты «впритык», в критический момент новые устройства подключить будет невозможно.
Совет:
Для этажных коммутаторов рассчитывайте запас портов.
Для коммутаторов ядра — учитывайте отказоустойчивость и настройку Spanning Tree.
4. Маршрутизаторы: не пытайтесь сделать всё
Современные маршрутизаторы умеют почти всё: VPN, DHCP, файрволл, межсетевой экран.
Но перегружать маршрутизатор всеми функциями не стоит:
Он станет точкой отказа.
Пропускная способность упадёт.
Риски безопасности увеличатся.
Рекомендация: оставьте маршрутизатор только для маршрутизации, VPN и файрволл — на отдельные устройства.
5. Серверная: порядок и скорость
Ошибки:
Перепутанные кабели, отсутствие маркировки.
Серверы, размещённые далеко от магистралей сети, что замедляет работу.
Пример:
Представьте автостраду и деревенскую дорогу. Магистраль позволяет двигаться быстро без остановок, а локальные дороги замедляют движение. Так же работает и сеть: чем больше устройств между пользователем и сервером, тем медленнее передача данных.
Решение:
Сразу организуйте кабели аккуратно и с маркировкой.
Размещайте серверы так, чтобы минимизировать количество промежуточных устройств.
6. Документирование: бумажки — это важно
Без документации можно потерять контроль над сетью: свободные порты окажутся заняты, кабели не будут подписаны, схемы станут нечитаемыми.
Советы:
Ведите журнал портов на коммутаторах.
Создавайте схемы сети на уровнях L1/L2/L3, не перегружая одну схему всеми деталями.
Обновляйте документацию регулярно.
Заключение
Правильное проектирование и эксплуатация сети — это не только вопрос техники, но и организации процессов. Соблюдение этих рекомендаций помогает минимизировать ошибки, повышает стабильность и скорость работы сети.
Даже если что-то осталось за кадром — избегая этих типичных ошибок, вы уже делаете сеть более надёжной.
Введение
Событийно-ориентированные архитектуры (EDA) на бумаге выглядят идеальными: продюсеры и консюмеры отделены друг от друга, потоки асинхронны, а система легко масштабируется. Но реальность часто оказывается сложнее.
Представьте распродажу на «Чёрную пятницу»: ваша система обработки платежей получает в 5 раз больше трафика. В этот момент серверлесс-функции запускаются «холодно», очереди SQS переполняются, а DynamoDB начинает троттлить. Результат: сбои заказов клиентов. И это не гипотетический сценарий — с этим сталкиваются многие команды eCommerce, SaaS и FinTech.
Система EDA в высокоуровневом виде состоит из трёх компонентов: продюсер → буфер/очередь → консюмер. При проектировании важно учитывать не только непрерывную работу, но и предсказуемость системы под нагрузкой. Пиковые нагрузки могут быть вызваны интеграциями, узкими местами потребителей или бесконечными повторными попытками сообщений — всё это проверяет архитектуру на прочность.
Задержка — не единственная проблема
Когда говорят о производительности EDA, обычно имеют в виду задержку. Но для отказоустойчивых систем важны также:
Пропускная способность
Эффективное использование ресурсов
Надёжная передача данных между компонентами
Пример:
Если сервис зависит от SQS и трафик резко возрастает, downstream-системы могут перегрузиться. Это приводит к повторным попыткам, росту задержек и искажению метрик мониторинга. Даже продуманный DLQ, экспоненциальное затухание и троттлинг не решат проблему, если не учитывать контракты между компонентами.
Вывод: задержка — это сигнал о «давлении» в системе. Её нужно воспринимать как индикатор накопления нагрузки, а не только минимизировать.
Паттерны проектирования для масштабируемости и отказоустойчивости
1. Шардирование и перемешивающее шардирование
Разделяйте клиентов или события на несколько шардов, чтобы шумный клиент не перегружал всю систему.
Пример:
В очереди SQS несколько клиентов могут быть хэшированы на одну очередь. Если один клиент начинает генерировать пик событий, он влияет на всех остальных. Перемешивающее шардирование уменьшает вероятность этого, распределяя клиентов случайным образом по разным очередям.
2. Предварительное выделение ресурсов для критических задач
Для задач с высокой чувствительностью к задержке (например, обнаружение мошенничества в FinTech) заранее выделяйте ресурсы.
Пример:
Для AWS Lambda используйте provisioned concurrency или авто-масштабирование с выделенной параллельностью. Это гарантирует быструю обработку критических событий, сохраняя экономичность при изменении нагрузки.
Паттерны инфраструктуры
1. Очереди и буферы
Очереди SQS, Kafka, Kinesis и EventBridge действуют как буферы между продюсерами и консюмерами, поглощая резкие всплески нагрузки.
Пример:
Реальное время кликов на рекламной платформе → Kinesis (шардирование по региону)
Выставление счетов → FIFO SQS для гарантии порядка и предотвращения дублирования
2. Быстрый сбой и предсказуемый отказ
Если консюмер не может обработать событие (например, база данных недоступна), лучше завершить операцию с ошибкой сразу, чем блокировать очередь на длительное время.
Пример:
Контейнер Lambda зависал на аутентификации 30 секунд → добавили тайм-аут 5 секунд и явное завершение с ошибкой → очередь перестала накапливать сообщения.
Распространённые ошибки и как их избежать
Переоценка средней нагрузки:
Систему нужно тестировать под резкие пики (p95, p99), а не под средние значения.
Повторные попытки как панацея:
Бесконтрольные повторные попытки могут создать петли трафика и троттлинг. Используйте экспоненциальное затухание с джиттером и разделяйте ошибки на повторяемые и нет.
Недостаточная наблюдаемость:
Метрики должны показывать не только ошибки и время отклика, но и глубину очередей, повторные попытки и масштабируемость компонентов.
Одинаковое обращение со всеми событиями:
Событие оплаты ≠ событие логирования. Разделяйте критические и низкоприоритетные события с помощью отдельных очередей или маршрутизации в разные Lambdas.
Заключение
Отказоустойчивость — это не попытка создать «идеальную систему», а способность выдерживать удары и продолжать работу. Основные принципы:
Эластичность и буферы, поглощающие пики нагрузки
Умные повторные попытки
Предсказуемые режимы отказа
Наблюдаемость, позволяющая подтверждать работоспособность системы
С чего начать:
Создайте простое событийно-ориентированное приложение на SQS и Lambda. Попробуйте DLQ, обработку сбоев и маршрутизацию событий через EventBridge. Постепенно добавляйте шардирование, авто-масштабирование и сложные паттерны.
Отказоустойчивость — это подход, который строится шаг за шагом. Начните с малого, изучайте поведение системы и постепенно добавляйте сложность.
Введение
Перенос базы данных PostgreSQL — задача непростая, особенно для больших проектов. Часто это один из самых крупных и ответственных процессов для разработчиков и администраторов. Основные сценарии переноса включают:
обновление до новой версии PostgreSQL;
перенос базы на другой сервер или хостинг;
миграция с минимальным временем простоя.
В зависимости от размера базы и ограничений инфраструктуры есть три основных подхода.
1. Перенос с помощью pg_dump и pg_restore
pg_dump позволяет создать дамп всей базы, включая схемы, таблицы и специальные объекты. Для небольших баз (50–150 ГБ) это часто самый простой вариант.
Пример использования:
pg_dump -Fc $SOURCE_DB_URI > dump_file.dump pg_restore --no-acl --no-owner -d $TARGET_DB_URI dump_file.dump Плюсы:
Надёжно и просто;
Полный дамп базы, включая схему и данные.
Минусы:
При больших базах (сотни ГБ и выше) процесс может занять часы;
Требуется время на восстановление и минимизация простоя.
2. Использование WAL (Write-Ahead Logging)
Если у вас настроено резервное копирование на основе WAL, например через pgBackRest, WAL-G или WAL-E, можно выполнить масштабную миграцию:
Создаётся полная резервная копия базы;
Настраивается потоковая передача WAL на новый сервер;
После завершения первичной синхронизации можно переключить приложение на новую базу с минимальным простоем.
Плюсы:
Подходит для терабайтных баз;
Минимизирует простой.
Минусы:
Требует доступа к WAL (не поддерживается, например, в Amazon RDS).
3. Логическая миграция PostgreSQL
Логическая репликация позволяет переносить данные на новый сервер без доступа к WAL.
Принцип работы: текущая база (publisher) передаёт изменения новой базе (subscriber);
Репликация распространяется на данные таблиц, но не переносит схему, индексы и последовательности;
С помощью дополнительных шагов можно выполнить полную миграцию.
Основные шаги логической миграции
Шаг 1: Перенос схемы
Сначала необходимо создать на новом сервере структуру базы:
pg_dump -Fc -s $SOURCE_DB_URI | pg_restore --no-acl --no-owner -d $TARGET_DB_URI При активной разработке изменений схемы: синхронизируйте изменения и на подписчике.
Шаг 2: Настройка издателя (старый сервер)
Включите логическую репликацию:
ALTER SYSTEM SET wal_level = logical; Настройте параметры слотов репликации:
max_replication_slots max_wal_senders max_logical_replication_workers max_worker_processes max_sync_workers_per_subscription Убедитесь, что сеть разрешает подключения с нового сервера.
Создайте пользователя для репликации:
CREATE ROLE elizabeth WITH REPLICATION LOGIN PASSWORD 'my_password'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO elizabeth; Определите таблицы без первичных ключей:
select tab.table_schema, tab.table_name from information_schema.tables tab left join information_schema.table_constraints tco on tab.table_schema = tco.table_schema and tab.table_name = tco.table_name and tco.constraint_type = 'PRIMARY KEY' where tab.table_type = 'BASE TABLE' and tab.table_schema not in ('pg_catalog', 'information_schema') and tco.constraint_name is null order by table_schema, table_name; Для таких таблиц используйте уникальный индекс или REPLICA IDENTITY FULL:
ALTER TABLE tablename REPLICA IDENTITY USING INDEX idx_unique_index; -- или ALTER TABLE tablename REPLICA IDENTITY FULL; Создайте публикацию всех таблиц:
CREATE PUBLICATION bridge_migration FOR ALL TABLES; SELECT * FROM pg_publication_tables; Шаг 3: Настройка подписчика (новый сервер)
Создаём подписку на публикацию:
CREATE SUBSCRIPTION bridge_migration CONNECTION 'host={host} port=5432 dbname={database} user={login} password={password}' PUBLICATION bridge_migration; Для больших баз можно ограничить число одновременно синхронизируемых таблиц через max_sync_workers_per_subscription.
Шаг 4: Мониторинг первичной загрузки
Проверяем прогресс через:
SELECT * FROM pg_stat_subscription; SELECT * FROM pg_subscription_rel; Состояния таблиц:
i — инициализация
d — копирование данных
f — копирование завершено
s — синхронизация выполнена
r — обычная репликация
Шаг 5: Тестирование и переключение
Остановите запись на исходной базе;
Проверьте данные на новом сервере;
Переключите приложение на новую базу.
Шаг 6: Синхронизация последовательностей
Логическая репликация не переносит последовательности. Используйте команды setval:
SELECT 'SELECT setval(' || quote_literal(quote_ident(n.nspname) || '.' || quote_ident(c.relname)) || ', ' || s.last_value || ');' FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace JOIN pg_sequences s ON s.schemaname = n.nspname AND s.sequencename = c.relname WHERE c.relkind = 'S'; Выполните результат на новом сервере, чтобы синхронизировать все последовательности.
Заключение
Логическая репликация — безопасный и эффективный способ миграции PostgreSQL, особенно при крупных базах и ограничениях по доступу к WAL.
Данные остаются согласованными, если схема подписчика идентична схеме издателя;
Репликация однонаправленная, без конфликтующих записей на подписчике;
Можно минимизировать простой при переходе на новый сервер.
Введение: что мы создаём в Go
В Go есть два больших семейства типов:
Value types — обычные значения: int, float64, bool, struct, массивы.
Reference types — ссылочные структуры: slice, map, chan.
Value types можно размещать где угодно: в стеке, в куче или внутри других объектов.
Reference types — это конструкции уровня рантайма с внутренними механизмами: простое объявление через var даст nil и вызовет панику при попытке использовать. Именно поэтому Go использует new и make по-разному.
new в Go: простое выделение памяти
Функция new(T) выделяет память под тип T, обнуляет её и возвращает указатель на T.
type Config struct { Enabled bool Count int } cfg := new(Config) // cfg имеет тип *Config // cfg.Enabled == false // cfg.Count == 0 Внутри вызывается runtime.newobject, который делает malloc нужного размера и очищает память нулями.
Подходит для value types: int, string, bool, struct, array.
new(T) vs &T{}
type User struct { Name string Age int } u1 := new(User) // выделяет объект в куче u2 := &User{} // может быть в стеке или куче Разница:
new(T) всегда аллоцирует в куче.
&T{} может остаться в стеке, если компилятор считает это безопасным (escape analysis).
Используйте &T{} для высокоэффективного кода с минимальными аллокациями.
Когда new действительно нужен
Generic‑код: тип T неизвестен на этапе компиляции.
func NewPointer[T any]() *T { return new(T) } Явная аллокация в куче: иногда нужно гарантировать объект в heap.
pool := sync.Pool{ New: func() any { return new(MyStruct) }, } Опциональные значения через nil:
type Options struct { RetryCount *int } o := Options{} o.RetryCount = new(int) *o.RetryCount = 3 Ограничения new
new([]int) вернёт *[]int с nil — использовать как полноценный slice нельзя:
s := new([]int) fmt.Println(*s == nil) // true (*s)[0] = 1 // panic Аналогично с map и chan.
make в Go: инициализация runtime‑структур
make не просто выделяет память. Он создаёт рабочие slice, map и chan с полностью инициализированными внутренними структурами.
Slice: указатель, длина, вместимость
s := make([]int, 10, 100) Создаёт slice header:
type sliceHeader struct { Data uintptr // указатель на массив Len int Cap int } Slice готов к использованию и может расширяться до cap без новых аллокаций.
var s []int даст nil-слайс — любое обращение к элементу вызовет панику.
Map: работа с бакетами
m := make(map[string]int, 100) Инициализирует внутреннюю структуру hmap:
count, flags, бакеты, старые бакеты для перестройки.
new(map[string]int) создаст только nil-указатель — использовать его нельзя.
Chan: синхронизация и буфер
c := make(chan int, 5) Создаёт полноценную очередь сообщений с буфером и счетчиками.
var c chan int даст nil-канал — операции блокируют навсегда.
Когда использовать make, а когда new
Сценарий
Выбор
Почему
Slice, map, chan
make
Создаёт рабочую структуру, готовую к использованию
Value type, generic
new
Получение указателя на zero-value
Явная куча для value type
new
Гарантированное выделение в heap
Опциональные значения
new
Возможность различать nil и заданное значение
Вывод
new: простое выделение памяти и указатель на ноль.
make: инициализация runtime‑структур, готовых к работе.
Понимание разницы между ними — обязательный минимум для любого разработчика на Go. Используйте make для slice, map и chan, а new — для value types и generic-кода.
Линус Торвальдс и его отказ от C++ для ядра Linux
Линус Торвальдс, создатель Linux и «великодушный диктатор» проекта, известен своей критикой языка C++. Он не отвергает его просто так — он приводит убедительные технические и практические аргументы против его применения в ядре Linux.
В чем же причина неприятия C++? Давайте разберем ключевые доводы Линуса.
Почему C++ не подходит для ядра Linux
C и C++ похожи, но не идентичны. C++ — это объектно-ориентированное расширение C, добавляющее классы, конструкторы, деструкторы, шаблоны, обработку исключений, пространства имен и перегрузку операторов. Эти «улучшения» приносят новые парадигмы, но и новые риски.
1. Обработка исключений
В C ошибки обрабатываются через возвращаемые значения, что делает поведение программы предсказуемым. В C++ используются исключения, которые могут возникнуть в любой части кода. Для ядра с миллионами строк кода это неприемлемо:
Исключения трудно отлаживать;
Они меняют парадигму обработки ошибок;
Могут привести к нестабильности ядра.
Для пользовательских приложений вроде GNOME редактора это мелочь, но для ядра Linux — реальный риск.
2. Управление памятью и RAII
C++ использует RAII (Resource Acquisition Is Initialization), автоматическое управление ресурсами через деструкторы. В ядре Linux память контролируется тонко и вручную, чтобы обеспечить максимальную производительность. Автоматизация в C++:
Увеличивает зависимость от компилятора;
Может снижать скорость работы модулей;
Увеличивает вероятность багов.
3. Объектно-ориентированное программирование
Линус считает, что ООП полезно, но C++ вносит слишком много лишнего. ООП можно реализовать и на чистом C через структуры и функции. Пример «класса» на C:
#include <stdio.h> #include <stdlib.h> typedef struct { int value; void (*increment)(struct Person *self); } Person; void increment_person(Person *self) { self->value++; } Person* person_new(int initial_value) { Person *p = (Person*)malloc(sizeof(Person)); p->value = initial_value; p->increment = increment_person; return p; } void person_free(Person *p) { free(p); } int main() { Person *p = person_new(5); printf("Initial value: %d\n", p->value); p->increment(p); printf("After increment: %d\n", p->value); person_free(p); return 0; } Это доказывает, что функциональность ООП доступна и в чистом C.
4. Стабильность библиотек и зависимостей
Пользовательские библиотеки вроде STL или Boost могут быть стабильными для приложений, но не для ядра. Любая новая зависимость:
Увеличивает риски безопасности;
Требует поддержки и обновлений;
Может снизить производительность.
Пример — уязвимость CVE-2024–3094 в liblzma, когда сторонний бэкдор повлиял на безопасность.
5. Сложные абстракции
ОП-паттерны могут быть удобны для приложений, но в ядре Linux они опасны:
Большие иерархии классов трудно поддерживать;
Ошибки в логике абстракций могут разрушить архитектуру;
Потребуется переработка больших частей кода.
Итог: почему ядро Linux на чистом C
Аргумент Линуса: разработка ядра требует предсказуемости, производительности и стабильности. C++ добавляет много возможностей, но они создают риски, которые ядро не может себе позволить.
Использование чистого C привлекает разработчиков, сосредоточенных на аппаратных и системных задачах, а не на сложностях ООП.
Будущее: Rust, Go или Java?
Линус открыто обсуждал Rust для ядра. Вопросы применения Go, Java или C# повторяют дискуссию о C++. Основной принцип остается прежним: ядро должно оставаться максимально стабильным и оптимизированным.
Выводы для разработчиков
Выбор языка и инструментов влияет на стабильность и производительность;
Любые зависимости требуют оценки долгосрочных последствий;
Иногда простота C — лучший выбор для системного программирования;
Разработчики должны балансировать эргономику и стабильность, особенно в критичных проектах.
Ключевой урок:
Линус демонстрирует, что при разработке системного программного обеспечения важнее стабильность и предсказуемость, чем удобство и новые парадигмы. Иногда лучше использовать проверенные, простые инструменты, чтобы гарантировать работу миллионов устройств по всему миру.
Когда вы разворачиваете новый Linux-сервер, он похож на только что купленный смартфон — всё работает из коробки, но настройки безопасности далеки от идеала. SSH открыт для всего мира, root логинится напрямую, лишние сервисы весело крутятся в фоне. Эта статья — пошаговый гайд по hardening'у: от первого входа до полноценно защищённого продакшн-сервера.
Шаг 1. Первый вход и базовая гигиена
Обновление системы — это не опционально
Первое, что делаем после подключения:
apt update && apt upgrade -y # Debian/Ubuntu dnf update -y # RHEL/CentOS/Fedora Звучит банально, но большинство взломов происходит через известные уязвимости, для которых патчи уже давно вышли. Настройте автоматические обновления безопасности:
# Ubuntu/Debian apt install unattended-upgrades -y dpkg-reconfigure --priority=low unattended-upgrades Файл конфигурации /etc/apt/apt.conf.d/50unattended-upgrades — проверьте, что там включены только security-обновления, а не всё подряд. Автоапгрейд всего и сразу на продакшне — риск, обновления безопасности — необходимость.
Создание непривилегированного пользователя
Работать под root — это как ходить с заряженным пистолетом в кармане без предохранителя. Создаём обычного пользователя и даём ему sudo:
adduser deploy usermod -aG sudo deploy # Или на RHEL-системах useradd -m -G wheel deploy passwd deploy Проверяем, что sudo работает:
su - deploy sudo whoami # должен вернуть root Шаг 2. Настройка SSH — главные ворота на сервер
SSH-демон — самое атакуемое место на любом публичном сервере. Посмотрите на логи нового сервера через сутки после запуска:
grep "Failed password" /var/log/auth.log | wc -l Тысячи попыток — это норма. Потому что боты сканируют интернет 24/7.
Ключи вместо паролей
Генерируем ключ на локальной машине (если ещё нет):
ssh-keygen -t ed25519 -C "your_email@example.com" # ed25519 современнее и быстрее RSA-4096, при той же безопасности Копируем публичный ключ на сервер:
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@your-server-ip Или вручную:
mkdir -p ~/.ssh chmod 700 ~/.ssh echo "ваш_публичный_ключ" >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys Конфигурация sshd
Редактируем /etc/ssh/sshd_config:
# Порт — меняем с дефолтного 22 # Это не security через obscurity, а просто уменьшение шума в логах Port 2222 # Запрещаем root-логин категорически PermitRootLogin no # Только ключи, никаких паролей PasswordAuthentication no ChallengeResponseAuthentication no UsePAM no # Только наш пользователь AllowUsers deploy # Отключаем X11 forwarding если не нужен X11Forwarding no # Ограничиваем попытки аутентификации MaxAuthTries 3 MaxSessions 5 # Таймаут для неактивных сессий (секунды) ClientAliveInterval 300 ClientAliveCountMax 2 # Более современные алгоритмы KexAlgorithms curve25519-sha256,diffie-hellman-group14-sha256 Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com MACs hmac-sha2-512,hmac-sha2-256 Перезапускаем sshd — но сначала проверяем конфиг:
sshd -t # тест конфига без перезапуска systemctl restart sshd Важно: не закрывайте текущую сессию, пока не убедитесь, что можете подключиться с новыми настройками в отдельном окне терминала.
Двухфакторная аутентификация через Google Authenticator
Для особо важных серверов добавляем TOTP:
apt install libpam-google-authenticator -y # Запускаем от имени пользователя deploy su - deploy google-authenticator Отвечаем на вопросы (рекомендую: yes, yes, no, yes), сохраняем QR-код и backup-коды.
В /etc/pam.d/sshd добавляем:
auth required pam_google_authenticator.so В sshd_config:
ChallengeResponseAuthentication yes AuthenticationMethods publickey,keyboard-interactive Шаг 3. Firewall — iptables vs nftables vs ufw
UFW — простой вариант для Ubuntu
apt install ufw -y # Политика по умолчанию — всё запрещено входящее ufw default deny incoming ufw default allow outgoing # Разрешаем только то, что нужно ufw allow 2222/tcp # наш SSH порт ufw allow 80/tcp # HTTP ufw allow 443/tcp # HTTPS # Включаем ufw enable ufw status verbose nftables — современный и мощный подход
nftables — замена iptables, доступная в современных дистрибутивах. Конфиг в /etc/nftables.conf:
#!/usr/sbin/nft -f flush ruleset table inet filter { chain input { type filter hook input priority 0; policy drop; # Разрешаем established соединения ct state established,related accept # Loopback iif "lo" accept # ICMP (ping) — ограниченно ip protocol icmp icmp type echo-request limit rate 5/second accept # SSH на нашем порту только с определённых IP ip saddr 1.2.3.4 tcp dport 2222 accept # HTTP/HTTPS tcp dport { 80, 443 } accept # Логируем всё остальное перед дропом log prefix "nftables-drop: " level warn drop } chain forward { type filter hook forward priority 0; policy drop; } chain output { type filter hook output priority 0; policy accept; } } systemctl enable nftables systemctl start nftables nft list ruleset # проверяем Защита от брутфорса — fail2ban
fail2ban анализирует логи и банит IP-адреса, которые ломятся брутфорсом:
apt install fail2ban -y Создаём /etc/fail2ban/jail.local:
[DEFAULT] bantime = 3600 ; 1 час бана findtime = 600 ; окно в 10 минут maxretry = 3 ; 3 неудачных попытки # Белый список ignoreip = 127.0.0.1/8 ::1 ваш_офисный_ip [sshd] enabled = true port = 2222 filter = sshd logpath = /var/log/auth.log maxretry = 3 bantime = 86400 ; сутки для особо настойчивых [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/error.log [nginx-limit-req] enabled = true port = http,https filter = nginx-limit-req logpath = /var/log/nginx/error.log maxretry = 10 systemctl enable fail2ban systemctl start fail2ban fail2ban-client status # проверяем статус fail2ban-client status sshd # статус конкретного jail Шаг 4. Минимизация поверхности атаки
Отключаем ненужные сервисы
Смотрим, что запущено:
systemctl list-units --type=service --state=running Типичные кандидаты на отключение на серверах без GUI:
systemctl disable --now avahi-daemon # mDNS — нужен только в локалке systemctl disable --now cups # принтеры — зачем на сервере? systemctl disable --now bluetooth # очевидно systemctl disable --now ModemManager # модемы на сервере? Удаляем ненужные пакеты
apt autoremove --purge apt purge telnet rsh-client rsh-redone-client # старые небезопасные протоколы Ограничиваем права на критичные файлы
# Проверяем SUID/SGID файлы — потенциальные векторы privilege escalation find / -perm /4000 -o -perm /2000 2>/dev/null | grep -v proc # Устанавливаем строгие права на критичные файлы chmod 600 /etc/shadow chmod 644 /etc/passwd chmod 644 /etc/group chmod 600 /boot/grub/grub.cfg Настройка sysctl — параметры ядра
Редактируем /etc/sysctl.d/99-security.conf:
# Защита от IP spoofing net.ipv4.conf.all.rp_filter = 1 net.ipv4.conf.default.rp_filter = 1 # Отключаем принятие source routing net.ipv4.conf.all.accept_source_route = 0 net.ipv6.conf.all.accept_source_route = 0 # Игнорируем ICMP redirect net.ipv4.conf.all.accept_redirects = 0 net.ipv4.conf.all.secure_redirects = 0 net.ipv6.conf.all.accept_redirects = 0 # Защита от SYN flood net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_max_syn_backlog = 2048 net.ipv4.tcp_synack_retries = 2 # Логируем подозрительные пакеты net.ipv4.conf.all.log_martians = 1 # Отключаем IPv6 если не используется # net.ipv6.conf.all.disable_ipv6 = 1 # ASLR включён по умолчанию, но проверяем kernel.randomize_va_space = 2 # Ограничиваем дамп core только root fs.suid_dumpable = 0 # Защита от переполнения буфера kernel.exec-shield = 1 # если поддерживается sysctl -p /etc/sysctl.d/99-security.conf Шаг 5. Аудит и мониторинг безопасности
auditd — аудит системных вызовов
apt install auditd audispd-plugins -y # Пример правил в /etc/audit/rules.d/audit.rules -w /etc/passwd -p wa -k user-modify -w /etc/shadow -p wa -k user-modify -w /etc/sudoers -p wa -k sudo-modify -w /var/log/auth.log -p wa -k auth-log -a always,exit -F arch=b64 -S execve -k command-exec AIDE — контроль целостности файлов
AIDE создаёт базу данных хешей критичных файлов и сигнализирует об изменениях:
apt install aide -y aideinit # инициализируем базу (занимает время) cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db # Проверка (запускаем по крону ежедневно) aide --check Добавляем в cron:
0 4 * * * root /usr/bin/aide --check | mail -s "AIDE check $(hostname)" admin@example.com Проверяем открытые порты и соединения
# Смотрим что слушает ss -tlnp # лучше чем netstat # или netstat -tlnp # Открытые соединения ss -tnp state established # Проверяем на rootkits apt install rkhunter chkrootkit rkhunter --update rkhunter --check chkrootkit Шаг 6. AppArmor и SELinux — мандатный контроль доступа
AppArmor (Ubuntu/Debian)
AppArmor ограничивает приложения набором разрешённых действий:
apt install apparmor apparmor-utils -y systemctl enable apparmor aa-status # проверяем статус профилей # Переводим профили в enforce mode aa-enforce /etc/apparmor.d/* # Создаём профиль для приложения aa-genprof /usr/sbin/nginx SELinux (RHEL/CentOS)
# Проверяем статус getenforce # Enforcing/Permissive/Disabled # Включаем если выключен setenforce 1 # В /etc/selinux/config: SELINUX=enforcing # Просматриваем нарушения audit2why < /var/log/audit/audit.log # Создаём разрешающий модуль из нарушений (осторожно!) audit2allow -a -M mypolicy semodule -i mypolicy.pp Шаг 7. Шифрование и безопасность данных
Шифрование диска — LUKS
Лучше делать при установке, но можно и на существующей системе (с резервным копированием!):
# Создаём зашифрованный контейнер cryptsetup luksFormat /dev/sdb1 # Открываем cryptsetup luksOpen /dev/sdb1 encrypted_data # Монтируем mkfs.ext4 /dev/mapper/encrypted_data mount /dev/mapper/encrypted_data /mnt/secure Безопасное хранение секретов
Никогда не кладите пароли и ключи в переменные окружения напрямую или в конфиги в git. Варианты:
# HashiCorp Vault vault kv put secret/myapp db_password="supersecret" vault kv get secret/myapp # Systemd credentials (systemd 250+) systemd-creds encrypt --name=db-password - > /etc/credstore/db-password.cred # В unit-файле: # LoadCredential=db-password:/etc/credstore/db-password.cred Чеклист: что проверить перед деплоем
Финальный чеклист для продакшн-сервера:
[ ] Система обновлена, настроены автоматические security-патчи
[ ] Создан непривилегированный пользователь, root-вход запрещён
[ ] SSH: только ключи, нестандартный порт, AllowUsers
[ ] Firewall настроен, открыты только нужные порты
[ ] fail2ban запущен и настроен
[ ] Отключены ненужные сервисы
[ ] sysctl параметры безопасности установлены
[ ] auditd запущен, правила аудита настроены
[ ] AIDE инициализирован, проверка по расписанию
[ ] AppArmor/SELinux в enforcing mode
[ ] Секреты не хранятся в открытом виде
[ ] Настроено оповещение о нарушениях безопасности
Инструменты автоматической проверки
# Lynis — комплексный аудит безопасности apt install lynis lynis audit system # OpenSCAP — проверка соответствия стандартам (CIS Benchmarks) apt install libopenscap8 ssg-base oscap xccdf eval --profile xccdf_org.ssgproject.content_profile_cis \ /usr/share/xml/scap/ssg/content/ssg-ubuntu2004-ds.xml # Nmap — сканируем себя снаружи nmap -sV -p- --script vuln your-server-ip Безопасность — это не состояние, это процесс. Регулярно проверяйте логи, обновляйте систему, пересматривайте правила по мере изменения инфраструктуры. И помните: самое уязвимое звено всегда находится между монитором и креслом.
Сервер работает, но медленно. Страницы грузятся с задержкой, база данных тормозит, под нагрузкой всё падает. Прежде чем покупать новое железо или мигрировать на более дорогой тариф — убедитесь, что вы используете текущее на 100%. Эта статья о том, как профилировать, диагностировать и оптимизировать Linux-сервер под реальную нагрузку.
Методология: сначала измеряем, потом меняем
Главная ошибка при оптимизации — начинать с изменений без понимания проблемы. «Давайте увеличим swap» или «добавим RAM» — это стрельба вслепую. Правильный подход: измерить → найти узкое место → устранить → снова измерить.
Инструментарий делится на уровни детализации:
системный уровень → top, htop, vmstat, iostat, sar процессный уровень → perf, strace, ltrace, lsof сетевой уровень → ss, netstat, iftop, nethogs, tcpdump дисковый уровень → iotop, blktrace, fio профилировщики → perf, bpftrace, flame graphs CPU: диагностика и оптимизация
Что жрёт процессор
# Базовый top с сортировкой по CPU top -o %CPU # htop — интерактивный, красивый htop # Детальная картина по ядрам mpstat -P ALL 1 5 # 5 измерений каждую секунду # pidstat — статистика по процессам pidstat -u 1 10 # каждую секунду, 10 раз Смотрите на us (userspace), sy (kernel), wa (iowait), st (steal — для виртуалок). Высокий wa — проблема с диском или сетью, а не с CPU.
Приоритеты процессов — nice и ionice
# Запустить процесс с низким приоритетом nice -n 19 ./heavy_backup.sh # Изменить приоритет запущенного процесса renice -n 10 -p 1234 # Приоритет I/O (для компилятора в фоне) ionice -c 3 -p 1234 # класс idle — работает только когда диск свободен # Совместно ionice -c 2 -n 7 nice -n 19 make -j$(nproc) Привязка процессов к ядрам — CPU affinity
Для highload-сервисов критично избегать перепрыгивания между ядрами (cache miss):
# Запустить nginx worker на ядрах 0-3 taskset -c 0-3 nginx # Изменить для запущенного процесса taskset -pc 0-3 $(pgrep nginx | head -1) # Посмотреть текущую привязку taskset -p 1234 perf — профилировщик ядра
# Установка apt install linux-tools-generic linux-tools-$(uname -r) # Общая статистика производительности perf stat ./your_program # Найти горячие функции (10 секунд) perf top -p $(pgrep php-fpm | head -1) # Запись для flame graph perf record -F 99 -g -p $(pgrep nginx) -- sleep 10 perf script > perf.out # Flame graph (нужен Brendan Gregg's flamegraph) git clone https://github.com/brendangregg/FlameGraph ./FlameGraph/stackcollapse-perf.pl perf.out | ./FlameGraph/flamegraph.pl > flame.svg Flame graph — это визуализация стека вызовов. Широкие «языки пламени» = много времени в этой функции.
Память: диагностика и управление
Понимаем использование памяти
# Читаем правильно — free показывает не совсем то, что думаем free -h # "available" — реально доступная память, не "free" # Детальная информация cat /proc/meminfo # Кто сколько жрёт ps aux --sort=-%mem | head -20 # Smem — более точные данные (учитывает shared memory) apt install smem smem -tk -s uss | tail -5 Virtual Memory: tuning через sysctl
/etc/sysctl.d/99-vm.conf:
# vm.swappiness — насколько агрессивно использовать swap # 0 = почти не свопировать (но не отключить совсем) # 10 = для серверов с достаточной RAM — хорошее значение # 60 = дефолт, нормально для десктопа vm.swappiness = 10 # vm.dirty_ratio — процент RAM, при котором начинается синхронный flush на диск # Для серверов с SSD можно увеличить vm.dirty_ratio = 20 vm.dirty_background_ratio = 5 # Размер таблицы vnodes vm.vfs_cache_pressure = 50 # меньше = кешируем дольше # Отключаем OOM killer для критичных процессов # vm.overcommit_memory = 2 # осторожно, лучше знать что делаете Huge Pages для баз данных
PostgreSQL, MySQL и Java-приложения хорошо работают с huge pages (2MB вместо 4KB):
# Проверяем текущее состояние grep -i huge /proc/meminfo # Transparent Huge Pages (THP) — автоматически cat /sys/kernel/mm/transparent_hugepage/enabled # Для баз данных THP лучше отключить! echo never > /sys/kernel/mm/transparent_hugepage/enabled echo never > /sys/kernel/mm/transparent_hugepage/defrag # Статические huge pages для PostgreSQL # В /etc/sysctl.conf: # vm.nr_hugepages = 1024 # 1024 * 2MB = 2GB # В PostgreSQL # huge_pages = on # в postgresql.conf Диски: I/O оптимизация
Диагностика дисковой подсистемы
# Общая нагрузка на диски в реальном времени iostat -xz 1 # Ключевые метрики: # %util — насколько занят диск (>80% = проблема) # await — среднее время ожидания запроса (мс) # r/s, w/s — операции чтения/записи в секунду # Кто именно читает/пишет iotop -o # только активные процессы # Детальная трассировка blktrace -d /dev/sda -o - | blkparse -i - Планировщик I/O
# Посмотреть текущий scheduler cat /sys/block/sda/queue/scheduler # Выбор scheduler: # none/mq-deadline — для NVMe SSD (hardware очередь хороша) # mq-deadline — для SATA SSD и HDD # bfq — для десктопа, справедливое распределение echo mq-deadline > /sys/block/sda/queue/scheduler # Закрепить через udev /etc/udev/rules.d/60-scheduler.rules: ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="0", \ ATTR{queue/scheduler}="none" ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", \ ATTR{queue/scheduler}="mq-deadline" Параметры очереди
# Размер очереди запросов (для NVMe можно больше) cat /sys/block/nvme0n1/queue/nr_requests echo 1024 > /sys/block/nvme0n1/queue/nr_requests # Read-ahead: сколько данных читать заранее # Для баз данных — уменьшаем, для стриминга файлов — увеличиваем blockdev --setra 256 /dev/sda # 256 * 512 = 128KB Тестирование производительности дисков
# fio — промышленный стандарт для тестирования I/O apt install fio # Тест случайного чтения 4K (как у базы данных) fio --name=randread --ioengine=libaio --iodepth=32 \ --rw=randread --bs=4k --direct=1 --size=1G \ --numjobs=4 --runtime=60 --group_reporting # Последовательная запись (как у лога) fio --name=seqwrite --ioengine=libaio --iodepth=8 \ --rw=write --bs=1M --direct=1 --size=10G \ --numjobs=1 --runtime=60 --group_reporting Сеть: тюнинг стека TCP/IP
Диагностика сетевой подсистемы
# Текущие соединения и их состояния ss -s # краткая статистика ss -tnp # все TCP соединения с процессами # Ширина полосы в реальном времени iftop -i eth0 nethogs eth0 # по процессам # Статистика сетевого интерфейса ip -s link show eth0 # Ошибки и дропы ethtool -S eth0 | grep -i drop cat /proc/net/dev Тюнинг TCP стека
/etc/sysctl.d/99-network.conf:
# Размеры буферов сокетов net.core.rmem_max = 134217728 net.core.wmem_max = 134217728 net.core.rmem_default = 65536 net.core.wmem_default = 65536 # TCP буферы (min, default, max в байтах) net.ipv4.tcp_rmem = 4096 87380 134217728 net.ipv4.tcp_wmem = 4096 65536 134217728 # BBR — современный алгоритм контроля перегрузки Google # Значительно улучшает пропускную способность при потерях net.core.default_qdisc = fq net.ipv4.tcp_congestion_control = bbr # TIME_WAIT sockets net.ipv4.tcp_tw_reuse = 1 # переиспользуем TIME_WAIT сокеты для новых соединений net.ipv4.tcp_fin_timeout = 15 # уменьшаем время ожидания FIN # Очередь соединений net.core.somaxconn = 65535 net.ipv4.tcp_max_syn_backlog = 65535 # Keep-alive для обнаружения мёртвых соединений net.ipv4.tcp_keepalive_time = 300 net.ipv4.tcp_keepalive_intvl = 30 net.ipv4.tcp_keepalive_probes = 5 # Диапазон портов для исходящих соединений net.ipv4.ip_local_port_range = 1024 65535 # Проверяем что BBR загружен sysctl net.ipv4.tcp_congestion_control lsmod | grep bbr Настройка сетевого интерфейса
# Проверяем настройки карты ethtool eth0 # Включаем offloading (перекладываем работу на NIC) ethtool -K eth0 tso on gso on gro on # TCP/Generic segmentation offload # Ring buffers — увеличиваем для highload ethtool -G eth0 rx 4096 tx 4096 # IRQ affinity — привязываем прерывания к ядрам cat /proc/interrupts | grep eth0 # Пишем скрипт привязки через /proc/irq/N/smp_affinity Профилирование с bpftrace и eBPF
Современный инструментарий для глубокого анализа без влияния на производительность:
# Установка apt install bpftrace # Топ системных вызовов bpftrace -e 'tracepoint:syscalls:sys_enter_* { @[probe] = count(); }' # Медленные запросы к диску (>10мс) bpftrace -e 'tracepoint:block:block_rq_complete { if (args->nr_sector > 0) { $latency = (nsecs - @start[args->sector]) / 1000000; if ($latency > 10) { printf("Slow I/O: %d ms\n", $latency); } } }' # Трассировка TCP retransmits bpftrace -e 'tracepoint:tcp:tcp_retransmit_skb { @[comm] = count(); }' Профилирование PHP-FPM специфически
Раз уж мы работаем с PHP:
# Статус PHP-FPM (если включён status page) curl http://localhost/fpm-status?full # Медленные запросы через slow log # В php-fpm pool config: # slowlog = /var/log/php-fpm/slow.log # request_slowlog_timeout = 5s # Анализ slow log sort -t$'\t' -k1 -rn /var/log/php-fpm/slow.log | head -20 # strace на php-fpm worker strace -c -p $(pgrep php-fpm | head -1) 2>&1 | head -50 Система мониторинга производительности
sar — System Activity Reporter
apt install sysstat systemctl enable sysstat # Статистика за сегодня sar -u # CPU sar -r # память sar -b # I/O sar -n DEV # сеть # За конкретное время sar -u -s 10:00:00 -e 11:00:00 # Сохранить историю в файл sar -A > /tmp/system_report_$(date +%Y%m%d).txt vmstat — обзор системы одной командой
# Запускаем каждую секунду vmstat 1 # Колонки: # procs: r(run queue), b(blocked) # memory: swpd, free, buff, cache # io: bi(blocks in), bo(blocks out) # cpu: us, sy, id, wa, st Если r > числа CPU — CPU перегружен. Если wa > 20% — проблема с диском.
Тюнинг NUMA-систем
Для серверов с несколькими CPU-сокетами:
# Проверяем топологию NUMA numactl --hardware numastat # Запуск на конкретном NUMA узле numactl --cpunodebind=0 --membind=0 ./your_app # Для Redis на NUMA-системе numactl --interleave=all redis-server # Автоматический балансировщик NUMA echo 1 > /proc/sys/kernel/numa_balancing Чеклист оптимизации
Порядок важен — сначала находим, потом чиним:
Измерить базовые метрики (top, iostat, free, ss)
Определить узкое место (CPU / RAM / Disk I/O / Network)
Детальное профилирование проблемной подсистемы
Применить изменение
Снова измерить — убедиться в улучшении
Применить к продакшну через конфиги (sysctl, udev, systemd)
Помните: преждевременная оптимизация — корень всех зол. Оптимизируйте то, что реально тормозит, с данными в руках.
Bash — это не «просто командная строка». Это полноценный скриптовый язык, на котором написаны тысячи скриптов деплоя, мониторинга, бэкапов и автоматизации по всему миру. Проблема в том, что большинство bash-скриптов написаны наспех, без понимания подводных камней, и падают в самый неподходящий момент. Эта статья о том, как писать bash-скрипты, которым можно доверять.
Основы: заголовок, который должен быть в каждом скрипте
#!/usr/bin/env bash # Описание: что делает скрипт # Автор: your@email.com # Версия: 1.0.0 set -euo pipefail IFS=$'\n\t' Разберём set -euo pipefail — это не магия, это защита:
set -e — скрипт останавливается при ошибке любой команды
set -u — ошибка при обращении к неустановленной переменной
set -o pipefail — ошибка в пайпе не скрывается (без этого false | true вернёт 0)
IFS=$'\n\t' — разделитель полей только перевод строки и таб, не пробел
Без этих строк скрипт будет молча продолжаться после ошибок, что приводит к катастрофическим последствиям на продакшне.
Работа с переменными и параметрами
Правильное использование переменных
# Всегда кавычки вокруг переменных! name="John Doe" echo "$name" # правильно echo $name # сломается если пробел в имени # Значения по умолчанию database="${DB_NAME:-myapp_production}" timeout="${TIMEOUT:-30}" # Проверка обязательного параметра : "${API_KEY:?'API_KEY is required but not set'}" # Массивы servers=("web01" "web02" "web03") for server in "${servers[@]}"; do echo "Processing $server" done # Ассоциативные массивы (bash 4+) declare -A ports ports[web]=80 ports[mysql]=3306 ports[redis]=6379 for service in "${!ports[@]}"; do echo "$service: ${ports[$service]}" done Обработка аргументов командной строки
#!/usr/bin/env bash set -euo pipefail # Продвинутая обработка аргументов с getopts usage() { cat <<EOF Usage: $(basename "$0") [OPTIONS] <environment> Options: -h, --help Show this help -v, --verbose Verbose output -n, --dry-run Dry run, don't make changes -c, --config FILE Config file path (default: /etc/myapp/config.conf) Environment: staging Deploy to staging production Deploy to production Examples: $(basename "$0") -v staging $(basename "$0") --dry-run production EOF exit 1 } # Разбор длинных опций через getopt OPTS=$(getopt -o hvnc: --long help,verbose,dry-run,config: -n "$(basename "$0")" -- "$@") eval set -- "$OPTS" VERBOSE=false DRY_RUN=false CONFIG="/etc/myapp/config.conf" while true; do case "$1" in -h|--help) usage ;; -v|--verbose) VERBOSE=true; shift ;; -n|--dry-run) DRY_RUN=true; shift ;; -c|--config) CONFIG="$2"; shift 2 ;; --) shift; break ;; *) echo "Unknown option: $1"; usage ;; esac done ENVIRONMENT="${1:-}" [[ -z "$ENVIRONMENT" ]] && { echo "Error: environment required"; usage; } [[ "$ENVIRONMENT" != "staging" && "$ENVIRONMENT" != "production" ]] && { echo "Error: environment must be 'staging' or 'production'" exit 1 } $VERBOSE && echo "Config: $CONFIG" $VERBOSE && echo "Environment: $ENVIRONMENT" $DRY_RUN && echo "DRY RUN MODE - no changes will be made" Логирование — правильный подход
# Цвета для терминала readonly RED='\033[0;31m' readonly GREEN='\033[0;32m' readonly YELLOW='\033[1;33m' readonly BLUE='\033[0;34m' readonly NC='\033[0m' # No Color # Лог-файл LOG_FILE="/var/log/myapp/deploy-$(date +%Y%m%d-%H%M%S).log" mkdir -p "$(dirname "$LOG_FILE")" log() { local level="$1" shift local message="$*" local timestamp timestamp=$(date '+%Y-%m-%d %H:%M:%S') # В файл — без цветов echo "[$timestamp] [$level] $message" >> "$LOG_FILE" # В терминал — с цветами case "$level" in INFO) echo -e "${GREEN}[INFO]${NC} $message" ;; WARN) echo -e "${YELLOW}[WARN]${NC} $message" ;; ERROR) echo -e "${RED}[ERROR]${NC} $message" >&2 ;; DEBUG) $VERBOSE && echo -e "${BLUE}[DEBUG]${NC} $message" ;; esac } # Использование log INFO "Starting deployment to $ENVIRONMENT" log WARN "Skipping health check in dry-run mode" log ERROR "Failed to connect to database" log DEBUG "Connection string: $DB_URL" Обработка ошибок и cleanup
trap — всегда убирай за собой
#!/usr/bin/env bash set -euo pipefail # Временные файлы TEMP_DIR=$(mktemp -d) LOCK_FILE="/tmp/myapp.lock" # Функция очистки — выполняется при любом выходе cleanup() { local exit_code=$? log INFO "Cleaning up..." rm -rf "$TEMP_DIR" rm -f "$LOCK_FILE" if [[ $exit_code -ne 0 ]]; then log ERROR "Script failed with exit code $exit_code" # Отправляем уведомление send_alert "Deployment failed on $(hostname)" "$LOG_FILE" fi exit $exit_code } # Обработка сигналов error_handler() { local line_number="$1" log ERROR "Error on line $line_number" } trap cleanup EXIT trap 'error_handler $LINENO' ERR trap 'log WARN "Interrupted by user"; exit 130' INT TERM # Защита от параллельного запуска через lockfile acquire_lock() { if ! mkdir "$LOCK_FILE" 2>/dev/null; then local pid pid=$(cat "$LOCK_FILE/pid" 2>/dev/null || echo "unknown") log ERROR "Another instance is running (PID: $pid)" exit 1 fi echo $$ > "$LOCK_FILE/pid" log DEBUG "Lock acquired" } acquire_lock Retry-логика для нестабильных операций
# Универсальная функция retry retry() { local max_attempts="$1" local delay="$2" local description="$3" shift 3 local cmd=("$@") local attempt=1 while true; do log INFO "[$attempt/$max_attempts] Trying: $description" if "${cmd[@]}"; then log INFO "Success: $description" return 0 fi if [[ $attempt -ge $max_attempts ]]; then log ERROR "All $max_attempts attempts failed: $description" return 1 fi log WARN "Attempt $attempt failed, retrying in ${delay}s..." sleep "$delay" ((attempt++)) # Экспоненциальная задержка (удваиваем каждый раз, макс 60 сек) delay=$(( delay * 2 > 60 ? 60 : delay * 2 )) done } # Использование retry 5 2 "Health check" curl -sf http://localhost/health retry 3 5 "Database backup" pg_dump myapp > /backup/dump.sql Параллельное выполнение задач
# Простой способ — запуск в background + wait deploy_to_servers() { local servers=("$@") local pids=() local failed=0 for server in "${servers[@]}"; do log INFO "Starting deploy on $server" ( ssh "$server" 'cd /app && git pull && systemctl restart myapp' log INFO "Deploy done on $server" ) & pids+=($!) done # Ждём все процессы и собираем статусы for i in "${!pids[@]}"; do if ! wait "${pids[$i]}"; then log ERROR "Deploy failed on ${servers[$i]}" ((failed++)) fi done return $failed } # С ограничением параллелизма (не более N одновременно) run_parallel() { local max_jobs="$1" shift local items=("$@") local running=0 local pids=() for item in "${items[@]}"; do # Ждём если достигнут лимит while [[ $running -ge $max_jobs ]]; do # Проверяем завершившиеся процессы local new_pids=() for pid in "${pids[@]}"; do if kill -0 "$pid" 2>/dev/null; then new_pids+=("$pid") else ((running--)) fi done pids=("${new_pids[@]}") sleep 0.1 done process_item "$item" & pids+=($!) ((running++)) done wait } Работа с файлами и текстом
Безопасное чтение конфигурации
# Загрузка .env файла load_env() { local env_file="${1:-.env}" [[ -f "$env_file" ]] || { log WARN "No $env_file found"; return 0; } # Безопасная загрузка: только KEY=VALUE строки, без выполнения кода while IFS='=' read -r key value; do # Пропускаем комментарии и пустые строки [[ "$key" =~ ^[[:space:]]*# ]] && continue [[ -z "$key" ]] && continue # Удаляем пробелы вокруг ключа key="${key//[[:space:]]/}" # Удаляем кавычки из значения value="${value%\"}" value="${value#\"}" value="${value%\'}" value="${value#\'}" # Экспортируем только валидные имена переменных if [[ "$key" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then export "$key=$value" fi done < "$env_file" } Манипуляции со строками
# Встроенные операции (без fork, быстрее) string="Hello, World!" # Длина echo "${#string}" # 13 # Подстрока (offset:length) echo "${string:7:5}" # World # Замена (первое вхождение) echo "${string/Hello/Hi}" # Hi, World! # Замена (все вхождения) path="/usr/local/bin:/usr/bin:/bin" echo "${path//:/ }" # /usr/local/bin /usr/bin /bin # Upper/Lower case (bash 4+) echo "${string^^}" # HELLO, WORLD! echo "${string,,}" # hello, world! # Trim (удаление пробелов) trim() { local str="$*" str="${str#"${str%%[![:space:]]*}"}" str="${str%"${str##*[![:space:]]}"}" echo "$str" } # Разбивка строки на массив IFS=',' read -ra parts <<< "one,two,three" for part in "${parts[@]}"; do echo "$part"; done Реальные примеры: скрипты для продакшна
Скрипт деплоя с zero-downtime
#!/usr/bin/env bash set -euo pipefail DEPLOY_DIR="/var/www/myapp" RELEASES_DIR="$DEPLOY_DIR/releases" CURRENT_LINK="$DEPLOY_DIR/current" SHARED_DIR="$DEPLOY_DIR/shared" KEEP_RELEASES=5 deploy() { local release_dir release_dir="$RELEASES_DIR/$(date +%Y%m%d%H%M%S)" log INFO "Creating release directory: $release_dir" mkdir -p "$release_dir" # Клонируем или обновляем код log INFO "Deploying code..." if [[ -d "$DEPLOY_DIR/repo" ]]; then git -C "$DEPLOY_DIR/repo" fetch origin git -C "$DEPLOY_DIR/repo" archive HEAD | tar -x -C "$release_dir" else git clone --depth 1 "$GIT_REPO" "$DEPLOY_DIR/repo" git -C "$DEPLOY_DIR/repo" archive HEAD | tar -x -C "$release_dir" fi # Линкуем shared директории ln -sfn "$SHARED_DIR/uploads" "$release_dir/public/uploads" ln -sfn "$SHARED_DIR/.env" "$release_dir/.env" # Устанавливаем зависимости log INFO "Installing dependencies..." composer install --no-dev --optimize-autoloader -d "$release_dir" # Прогреваем кэш log INFO "Warming up cache..." php "$release_dir/spark" cache:clear # Переключаем симлинк атомарно log INFO "Switching to new release..." ln -sfn "$release_dir" "${CURRENT_LINK}.new" mv -Tf "${CURRENT_LINK}.new" "$CURRENT_LINK" # Перезагружаем PHP-FPM (graceful) kill -USR2 $(cat /var/run/php/php8.2-fpm.pid) # Очищаем старые релизы cleanup_old_releases log INFO "Deploy completed successfully!" } cleanup_old_releases() { local releases releases=$(ls -1t "$RELEASES_DIR") local count count=$(echo "$releases" | wc -l) if [[ $count -gt $KEEP_RELEASES ]]; then echo "$releases" | tail -n +"$((KEEP_RELEASES + 1))" | while read -r release; do log INFO "Removing old release: $release" rm -rf "$RELEASES_DIR/$release" done fi } rollback() { local previous previous=$(ls -1t "$RELEASES_DIR" | sed -n '2p') if [[ -z "$previous" ]]; then log ERROR "No previous release to rollback to" exit 1 fi log INFO "Rolling back to: $previous" ln -sfn "$RELEASES_DIR/$previous" "${CURRENT_LINK}.rollback" mv -Tf "${CURRENT_LINK}.rollback" "$CURRENT_LINK" kill -USR2 $(cat /var/run/php/php8.2-fpm.pid) log INFO "Rollback completed" } case "${1:-deploy}" in deploy) deploy ;; rollback) rollback ;; *) echo "Usage: $0 [deploy|rollback]"; exit 1 ;; esac Скрипт мониторинга и уведомлений
#!/usr/bin/env bash set -euo pipefail # Пороговые значения CPU_THRESHOLD=85 MEM_THRESHOLD=90 DISK_THRESHOLD=85 LOAD_THRESHOLD=4.0 ALERT_EMAIL="ops@example.com" WEBHOOK_URL="${SLACK_WEBHOOK:-}" send_alert() { local subject="$1" local body="$2" # Email echo "$body" | mail -s "[$HOSTNAME] ALERT: $subject" "$ALERT_EMAIL" # Slack webhook if [[ -n "$WEBHOOK_URL" ]]; then curl -s -X POST "$WEBHOOK_URL" \ -H 'Content-type: application/json' \ -d "{\"text\":\":warning: *[$HOSTNAME]* $subject\n\`\`\`$body\`\`\`\"}" fi } check_cpu() { local cpu_usage cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1 | cut -d',' -f1) cpu_usage=${cpu_usage%.*} # целая часть if [[ $cpu_usage -gt $CPU_THRESHOLD ]]; then local top_processes top_processes=$(ps aux --sort=-%cpu | head -6 | tail -5) send_alert "High CPU: ${cpu_usage}%" "CPU usage: ${cpu_usage}%\n\nTop processes:\n$top_processes" fi } check_memory() { local mem_total mem_used mem_percent mem_total=$(free -m | awk '/^Mem:/{print $2}') mem_used=$(free -m | awk '/^Mem:/{print $3}') mem_percent=$(( mem_used * 100 / mem_total )) if [[ $mem_percent -gt $MEM_THRESHOLD ]]; then local top_processes top_processes=$(ps aux --sort=-%mem | head -6 | tail -5) send_alert "High Memory: ${mem_percent}%" "Memory: ${mem_used}MB / ${mem_total}MB (${mem_percent}%)\n\n$top_processes" fi } check_disk() { while IFS= read -r line; do local usage mount usage=$(echo "$line" | awk '{print $5}' | tr -d '%') mount=$(echo "$line" | awk '{print $6}') if [[ $usage -gt $DISK_THRESHOLD ]]; then send_alert "High Disk: $mount at ${usage}%" \ "Disk usage on $mount: ${usage}%\n\n$(df -h "$mount")" fi done < <(df -h | grep -E '^/dev/' | grep -v tmpfs) } check_services() { local services=("nginx" "php8.2-fpm" "mysql" "redis") for service in "${services[@]}"; do if ! systemctl is-active --quiet "$service"; then send_alert "Service DOWN: $service" \ "Service $service is not running!\n\n$(systemctl status "$service" --no-pager | tail -20)" # Попытка перезапуска log WARN "Attempting to restart $service..." systemctl restart "$service" && \ send_alert "Service RECOVERED: $service" "Service $service was restarted successfully" fi done } # Запускаем все проверки check_cpu check_memory check_disk check_services Лучшие практики
Используйте shellcheck — это ESLint для bash:
apt install shellcheck shellcheck myscript.sh # В CI/CD: find . -name "*.sh" -exec shellcheck {} + Тестируйте скрипты с bats:
apt install bats # test.bats: @test "cleanup removes old files" { touch /tmp/testfile run cleanup /tmp/testfile [ "$status" -eq 0 ] [ ! -f /tmp/testfile ] } bats test.bats Документируйте через heredoc:
show_help() { cat <<'EOF' ...документация... EOF } Bash — это мощный инструмент, но он требует дисциплины. Скрипт, написанный правильно — это надёжный автоматизированный сотрудник. Написанный небрежно — бомба с часовым механизмом.
Systemd — это уже не просто init-система. Это целая экосистема для управления сервисами, логами, сетью, таймерами, точками монтирования и многим другим. Большинство администраторов использует 20% его возможностей, не подозревая об остальных 80%. Эта статья закроет пробелы.
Архитектура systemd: что за чем стоит
systemd ├── systemd-journald — централизованное логирование ├── systemd-networkd — управление сетью ├── systemd-resolved — DNS resolver ├── systemd-timesyncd — синхронизация времени ├── systemd-logind — управление сессиями ├── systemd-udevd — управление устройствами └── systemd-tmpfilesd — управление временными файлами Всё взаимодействует через D-Bus и сокеты. Это важно понимать для отладки.
Unit-файлы: анатомия сервиса
Базовая структура
[Unit] Description=MyApp Web Service Documentation=https://myapp.example.com/docs After=network.target mysql.service redis.service Requires=mysql.service Wants=redis.service [Service] Type=simple User=www-data Group=www-data WorkingDirectory=/var/www/myapp ExecStart=/usr/bin/php /var/www/myapp/server.php ExecReload=/bin/kill -HUP $MAINPID ExecStop=/bin/kill -TERM $MAINPID Restart=on-failure RestartSec=5s [Install] WantedBy=multi-user.target Секция [Unit]: зависимости и порядок
Разница между After/Before и Requires/Wants критически важна:
# ПОРЯДОК ЗАПУСКА (не зависимость!) After=network.target # запускаемся после network.target Before=nginx.service # запускаемся до nginx # ЗАВИСИМОСТИ (и порядок) Requires=mysql.service # жёсткая: если mysql упал — мы тоже падаем Wants=redis.service # мягкая: пробуем запустить redis, но не критично BindsTo=device.mount # как Requires, но реагирует на остановку устройства PartOf=app.service # stop/restart parent = stop/restart нас # Конфликты Conflicts=apache2.service # не запускаться вместе с apache2 Типы сервисов — Type=
Type=simple # процесс запущен = сервис готов (дефолт) Type=exec # как simple, но ждёт exec() (systemd 240+) Type=forking # главный процесс форкает дочерний и завершается (nginx, apache) Type=oneshot # выполняется и завершается (для скриптов) Type=notify # процесс сигнализирует systemd о готовности через sd_notify() Type=dbus # готовность через D-Bus Type=idle # запускается после других, когда нет других задач Для PHP-FPM используем Type=notify — он поддерживает sd_notify:
[Service] Type=notify ExecStart=/usr/sbin/php-fpm8.2 --nodaemonize Продвинутые настройки сервиса
Переменные окружения и секреты
[Service] # Прямо в unit-файле (не для секретов!) Environment=APP_ENV=production Environment=LOG_LEVEL=info # Из файла (безопаснее) EnvironmentFile=/etc/myapp/environment EnvironmentFile=-/etc/myapp/local.env # дефис = не ошибка если нет файла # Systemd credentials (systemd 250+ — самый безопасный способ) LoadCredential=db-password:/etc/credstore/db-password.cred # Доступно в: /run/credentials/myapp.service/db-password Файл /etc/myapp/environment:
DB_HOST=localhost DB_PORT=3306 DB_NAME=myapp DB_USER=myapp DB_PASSWORD=supersecret REDIS_HOST=127.0.0.1 chmod 600 /etc/myapp/environment chown root:www-data /etc/myapp/environment Изоляция и безопасность сервиса
[Service] # Запуск от непривилегированного пользователя User=www-data Group=www-data # Динамический пользователь (создаётся на время жизни сервиса) DynamicUser=yes # Файловая система PrivateTmp=yes # отдельный /tmp PrivateDevices=yes # нет доступа к /dev (кроме базовых) ProtectHome=yes # нет доступа к /home /root /run/user ProtectSystem=strict # / только для чтения ReadWritePaths=/var/www/myapp/storage /var/log/myapp # Сеть PrivateNetwork=no # нужна для веб-сервиса RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX # Процессы NoNewPrivileges=yes # нет setuid/setgid после старта ProtectKernelTunables=yes ProtectKernelModules=yes ProtectControlGroups=yes # Системные вызовы (белый список) SystemCallFilter=@system-service # разрешённые для сервисов SystemCallFilter=~@privileged # запрещаем привилегированные SystemCallErrorNumber=EPERM # Capabilities CapabilityBoundingSet=CAP_NET_BIND_SERVICE # только для порта <1024 AmbientCapabilities=CAP_NET_BIND_SERVICE # Ограничение ресурсов LimitNOFILE=65535 # открытые файловые дескрипторы LimitNPROC=512 # процессы MemoryLimit=2G # максимум памяти CPUQuota=50% # не более 50% CPU Политики перезапуска
[Service] Restart=on-failure # только при ненулевом коде возврата Restart=always # всегда (кроме systemctl stop) Restart=unless-stopped # всегда, даже при ненулевом коде RestartSec=5s # пауза перед перезапуском # Ограничение перезапусков (не более 3 за 30 секунд) StartLimitIntervalSec=30s StartLimitBurst=3 StartLimitAction=none # что делать при достижении лимита: none|reboot|poweroff # Watchdog — если нет heartbeat за N секунд — перезапуск WatchdogSec=30s systemctl: продвинутое использование
# Основные команды systemctl start myapp systemctl stop myapp systemctl restart myapp systemctl reload myapp # отправить SIGHUP (graceful reload) systemctl status myapp # Включить/выключить автозапуск systemctl enable myapp systemctl disable myapp systemctl enable --now myapp # включить и сразу запустить # Маскировка (нельзя запустить даже вручную) systemctl mask cups systemctl unmask cups # Информация о юните systemctl cat myapp # показать содержимое unit-файла systemctl show myapp # все свойства (машиночитаемо) systemctl show myapp -p Restart,RestartSec # конкретные свойства # Зависимости systemctl list-dependencies myapp systemctl list-dependencies --reverse myapp # кто зависит от нас # Все юниты systemctl list-units --type=service systemctl list-units --state=failed systemctl list-unit-files # Применение изменений без потери сервиса (systemd 230+) systemctl daemon-reload systemctl try-reload-or-restart myapp systemd Timers: замена cron
Таймеры systemd мощнее cron: есть логирование, управление зависимостями, можно запустить вручную.
Создаём пару service + timer
/etc/systemd/system/backup.service:
[Unit] Description=Daily Database Backup After=network.target mysql.service [Service] Type=oneshot User=backup ExecStart=/usr/local/bin/backup-database.sh StandardOutput=journal StandardError=journal /etc/systemd/system/backup.timer:
[Unit] Description=Daily Database Backup Timer Requires=backup.service [Timer] # Каждый день в 2:30 OnCalendar=*-*-* 02:30:00 # После загрузки системы (если пропустили) Persistent=true # Случайная задержка до 5 минут (чтобы не все запускались одновременно) RandomizedDelaySec=5min [Install] WantedBy=timers.target systemctl enable --now backup.timer systemctl list-timers # все таймеры и когда запустятся systemctl start backup.service # запустить вручную без ожидания таймера Синтаксис OnCalendar
OnCalendar=Mon-Fri *-*-* 09:00:00 # каждый будний день в 9:00 OnCalendar=weekly # каждую неделю (понедельник 0:00) OnCalendar=monthly # первый день месяца OnCalendar=*-*-* *:00/15:00 # каждые 15 минут OnCalendar=2024-03-* # каждый день в марте 2024 OnCalendar=Sat,Sun 12:00:00 # субботу и воскресенье в полдень Проверка синтаксиса:
systemd-analyze calendar "Mon-Fri *-*-* 09:00:00" journald: работа с логами
Базовые команды
# Логи конкретного сервиса journalctl -u nginx journalctl -u nginx -n 100 # последние 100 строк journalctl -u nginx -f # следим в реальном времени (как tail -f) # По времени journalctl --since "2024-01-15 10:00" --until "2024-01-15 11:00" journalctl --since "1 hour ago" journalctl --since yesterday # По приоритету journalctl -p err # только ошибки journalctl -p warning..err # от warning до err # 0=emerg 1=alert 2=crit 3=err 4=warning 5=notice 6=info 7=debug # Форматы вывода journalctl -u myapp -o json # JSON journalctl -u myapp -o json-pretty # читаемый JSON journalctl -u myapp -o short-precise # с микросекундами # Загрузки системы journalctl --list-boots journalctl -b 0 # текущая загрузка journalctl -b -1 # предыдущая загрузка journalctl -b -1 -p err # ошибки при предыдущей загрузке Настройка journald
/etc/systemd/journald.conf:
[Journal] # Максимальный размер хранилища SystemMaxUse=2G SystemKeepFree=500M # Максимальный размер одного файла журнала SystemMaxFileSize=200M # Хранить журналы N дней MaxRetentionSec=1month # Сжатие Compress=yes # Пересылка в syslog (если нужен rsyslog) ForwardToSyslog=no ForwardToWall=no # Максимальный размер сообщения LineMax=48K Структурированное логирование из приложения
# Systemd-cat echo "Application started" | systemd-cat -t myapp -p info # С полями для фильтрации systemd-cat -t myapp <<< "MESSAGE=User login SUCCESSFUL USER_ID=42 REMOTE_IP=192.168.1.100" # Фильтрация по custom полям journalctl SYSLOG_IDENTIFIER=myapp journalctl _SYSTEMD_UNIT=myapp.service USER_ID=42 Drop-in конфиги: расширяем без изменения оригинала
Никогда не редактируйте файлы в /lib/systemd/system/ — они перезапишутся при обновлении. Используйте drop-in:
# Создаём директорию mkdir -p /etc/systemd/system/nginx.service.d/ # Создаём override cat > /etc/systemd/system/nginx.service.d/override.conf <<EOF [Service] # Добавляем переменные окружения к nginx Environment=APP_ENV=production # Увеличиваем лимит файловых дескрипторов LimitNOFILE=65535 # Рестарт при падении (дефолт nginx — no) Restart=on-failure RestartSec=5s EOF # Или через systemctl edit (открывает редактор автоматически) systemctl edit nginx systemctl daemon-reload cgroups v2 через systemd
Современные версии systemd используют cgroups v2 для изоляции ресурсов:
# Статистика ресурсов для сервиса systemctl status myapp # или детальнее systemd-cgtop # Запустить с ограничениями на лету (transient unit) systemd-run --unit=my-temp-task --slice=user.slice \ -p MemoryMax=512M -p CPUQuota=25% \ /usr/bin/python3 heavy_script.py # Ограничить группу пользователя systemctl set-property user-1000.slice MemoryMax=4G Анализ времени загрузки
# Общее время загрузки systemd-analyze # Детально по сервисам systemd-analyze blame # Визуализация в SVG systemd-analyze plot > boot.svg # Критический путь загрузки systemd-analyze critical-chain # Проверка unit-файла на ошибки systemd-analyze verify /etc/systemd/system/myapp.service Полезные паттерны для PHP-приложений
Сервис PHP-FPM с worker isolation
[Service] # Мягкий рестарт (дождаться завершения текущих запросов) ExecReload=/bin/kill -USR2 $MAINPID # Watchdog через sd_notify WatchdogSec=60s NotifyAccess=main # Логи в journald напрямую StandardOutput=journal StandardError=journal SyslogIdentifier=php-fpm # Автоматический сбор core dump LimitCORE=infinity Slice=web.slice # группировка в slice для общих лимитов Slice для группировки сервисов
# /etc/systemd/system/web.slice [Unit] Description=Web Services Slice Before=slices.target [Slice] MemoryMax=8G CPUQuota=200% Все сервисы в web.slice суммарно не превысят 8GB RAM и 200% CPU (2 ядра).
Отладка: когда что-то пошло не так
# Быстрая диагностика systemctl --failed # упавшие сервисы journalctl -p err -b # ошибки с последней загрузки journalctl -xe # последние записи с расшифровкой # Подробный запуск в debug-режиме SYSTEMD_LOG_LEVEL=debug systemd-analyze verify myapp.service # Проверка прав и доступов systemd-run --unit=debug-shell -p PrivateTmp=yes bash # Запускает оболочку с теми же ограничениями, что и сервис # Strace сервиса через systemd systemctl set-environment STRACE_OPTS="-f -e trace=network" systemctl restart myapp Systemd — мощный инструмент, который при правильном использовании превращает управление сервисами из искусства в инженерию. Используйте drop-in конфиги, настраивайте изоляцию, пишите таймеры вместо cron — и ваша система станет предсказуемой и управляемой.
«Как вы узнали о проблеме?» — «Пожаловались пользователи» — так работать нельзя. Правильный мониторинг означает, что вы знаете о проблеме раньше, чем её заметят пользователи. Эта статья о построении полноценного стека мониторинга для Linux-инфраструктуры: от сбора метрик до умных алертов.
Архитектура: что и зачем
Серверы Мониторинг Визуализация [node_exporter] ──────► [Prometheus] ──────► [Grafana] [php-fpm_exporter] │ │ [mysql_exporter] │ алерты дашборды [nginx_exporter] ▼ [redis_exporter] [Alertmanager] │ [Email/Slack/PagerDuty] Prometheus — это time-series база данных с pull-моделью сбора данных. Exporters на серверах открывают HTTP endpoint с метриками в формате Prometheus, и сервер Prometheus их периодически «скрейпит».
Node Exporter: метрики операционной системы
Установка
# Через пакет apt install prometheus-node-exporter # Ubuntu # или скачиваем бинарник # Проверяем endpoint curl http://localhost:9100/metrics | head -50 Что собирает node_exporter
# CPU node_cpu_seconds_total{cpu="0",mode="idle"} node_cpu_seconds_total{cpu="0",mode="user"} node_cpu_seconds_total{cpu="0",mode="system"} node_cpu_seconds_total{cpu="0",mode="iowait"} # Память node_memory_MemTotal_bytes node_memory_MemAvailable_bytes node_memory_SwapUsed_bytes # Диски node_disk_read_bytes_total{device="sda"} node_disk_written_bytes_total{device="sda"} node_disk_io_time_seconds_total{device="sda"} # Сеть node_network_receive_bytes_total{device="eth0"} node_network_transmit_bytes_total{device="eth0"} node_network_receive_errs_total{device="eth0"} # Файловая система node_filesystem_avail_bytes{mountpoint="/"} node_filesystem_size_bytes{mountpoint="/"} # Нагрузка node_load1 # средняя нагрузка за 1 минуту node_load5 node_load15 Кастомные метрики через textfile collector
# Создаём директорию для textfile mkdir -p /var/lib/node_exporter/textfile_collector # Запускаем node_exporter с collector /usr/bin/prometheus-node-exporter \ --collector.textfile.directory=/var/lib/node_exporter/textfile_collector # Скрипт для метрик приложения (запускаем по cron) cat > /usr/local/bin/app-metrics.sh << 'EOF' #!/bin/bash METRICS_FILE="/var/lib/node_exporter/textfile_collector/app.prom" # Количество PHP-FPM процессов fpm_workers=$(ps aux | grep php-fpm | grep -v grep | wc -l) # Количество MySQL соединений mysql_connections=$(mysql -u monitoring -ppassword -e "SHOW STATUS LIKE 'Threads_connected';" | awk 'NR==2{print $2}') # Место в очереди Redis redis_queue_size=$(redis-cli llen myapp:jobs) cat > "$METRICS_FILE" << METRICS # HELP myapp_fpm_workers Number of PHP-FPM worker processes # TYPE myapp_fpm_workers gauge myapp_fpm_workers $fpm_workers # HELP myapp_mysql_connections Active MySQL connections # TYPE myapp_mysql_connections gauge myapp_mysql_connections $mysql_connections # HELP myapp_queue_size Redis job queue size # TYPE myapp_queue_size gauge myapp_queue_size $redis_queue_size METRICS EOF chmod +x /usr/local/bin/app-metrics.sh # Добавляем в cron (каждую минуту) echo "* * * * * root /usr/local/bin/app-metrics.sh" > /etc/cron.d/app-metrics Установка Prometheus
# Создаём пользователя useradd --no-create-home --shell /bin/false prometheus # Создаём директории mkdir -p /etc/prometheus /var/lib/prometheus chown prometheus:prometheus /var/lib/prometheus # Скачиваем (проверьте актуальную версию) cd /tmp wget https://github.com/prometheus/prometheus/releases/download/v2.50.1/prometheus-2.50.1.linux-amd64.tar.gz tar xvf prometheus-*.tar.gz cp prometheus-*/prometheus /usr/local/bin/ cp prometheus-*/promtool /usr/local/bin/ cp -r prometheus-*/consoles /etc/prometheus/ cp -r prometheus-*/console_libraries /etc/prometheus/ chown prometheus:prometheus /usr/local/bin/prometheus /usr/local/bin/promtool Конфигурация Prometheus
/etc/prometheus/prometheus.yml:
global: scrape_interval: 15s # как часто собираем метрики evaluation_interval: 15s # как часто оцениваем правила алертов scrape_timeout: 10s # Правила алертов rule_files: - /etc/prometheus/rules/*.yml # Куда отправлять алерты alerting: alertmanagers: - static_configs: - targets: - localhost:9093 # Источники метрик scrape_configs: # Сам Prometheus - job_name: 'prometheus' static_configs: - targets: ['localhost:9090'] # Node exporters — наши серверы - job_name: 'node' static_configs: - targets: - 'web01:9100' - 'web02:9100' - 'db01:9100' # Добавляем метки для группировки relabel_configs: - source_labels: [__address__] target_label: instance # Статические метки static_configs: - targets: ['web01:9100'] labels: env: production role: web - targets: ['db01:9100'] labels: env: production role: database # MySQL exporter - job_name: 'mysql' static_configs: - targets: ['localhost:9104'] # Nginx exporter - job_name: 'nginx' static_configs: - targets: ['localhost:9113'] # Redis exporter - job_name: 'redis' static_configs: - targets: ['localhost:9121'] # PHP-FPM — через статус страницу - job_name: 'php-fpm' static_configs: - targets: ['localhost:9253'] # Service discovery через файлы (удобно для динамической инфраструктуры) - job_name: 'dynamic-servers' file_sd_configs: - files: - /etc/prometheus/targets/*.yml refresh_interval: 30s Systemd unit для Prometheus
[Unit] Description=Prometheus Monitoring Wants=network-online.target After=network-online.target [Service] User=prometheus Group=prometheus Type=simple ExecStart=/usr/local/bin/prometheus \ --config.file=/etc/prometheus/prometheus.yml \ --storage.tsdb.path=/var/lib/prometheus \ --storage.tsdb.retention.time=30d \ --storage.tsdb.retention.size=10GB \ --web.enable-lifecycle \ --web.enable-admin-api Restart=on-failure RestartSec=5s [Install] WantedBy=multi-user.target PromQL: язык запросов
PromQL — мощный язык для работы с time-series. Основные паттерны:
# Мгновенные значения node_memory_MemAvailable_bytes # Использование памяти в % (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 # CPU usage (rate нужен для счётчиков) 100 - (avg by (instance) ( rate(node_cpu_seconds_total{mode="idle"}[5m]) ) * 100) # Disk I/O latency rate(node_disk_io_time_seconds_total[5m]) # Свободное место на диске в % (node_filesystem_avail_bytes / node_filesystem_size_bytes) * 100 # Количество TCP соединений по состоянию node_netstat_Tcp_CurrEstab # Nginx requests per second rate(nginx_http_requests_total[5m]) # 95-й перцентиль времени ответа histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]) ) # Агрегация по серверам sum by (instance) (rate(node_cpu_seconds_total{mode!="idle"}[5m])) # Топ 5 серверов по CPU topk(5, 100 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100 ) Правила алертов
/etc/prometheus/rules/linux.yml:
groups: - name: linux_nodes rules: # CPU - alert: HighCPUUsage expr: | 100 - (avg by (instance) ( rate(node_cpu_seconds_total{mode="idle"}[5m]) ) * 100) > 85 for: 5m labels: severity: warning annotations: summary: "High CPU on {{ $labels.instance }}" description: "CPU usage is {{ printf \"%.1f\" $value }}% on {{ $labels.instance }}" - alert: CriticalCPUUsage expr: | 100 - (avg by (instance) ( rate(node_cpu_seconds_total{mode="idle"}[5m]) ) * 100) > 95 for: 2m labels: severity: critical annotations: summary: "CRITICAL CPU on {{ $labels.instance }}" # Память - alert: HighMemoryUsage expr: | (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 90 for: 5m labels: severity: warning annotations: summary: "High memory on {{ $labels.instance }}" description: "Memory usage is {{ printf \"%.1f\" $value }}%" # Диск - alert: DiskSpaceLow expr: | (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 < 15 for: 2m labels: severity: warning annotations: summary: "Low disk space on {{ $labels.instance }}" description: "Only {{ printf \"%.1f\" $value }}% disk space remaining" - alert: DiskSpaceCritical expr: | (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 < 5 for: 1m labels: severity: critical annotations: summary: "CRITICAL: Disk almost full on {{ $labels.instance }}" # Инод - alert: DiskInodesLow expr: | (node_filesystem_files_free / node_filesystem_files) * 100 < 10 for: 2m labels: severity: warning # Сервер недоступен - alert: InstanceDown expr: up == 0 for: 1m labels: severity: critical annotations: summary: "Instance {{ $labels.instance }} is DOWN" # Load average - alert: HighLoadAverage expr: node_load1 > (count by (instance)(node_cpu_seconds_total{mode="idle"}) * 2) for: 5m labels: severity: warning # OOM Killer - alert: OOMKillerActive expr: increase(node_vmstat_oom_kill[5m]) > 0 labels: severity: critical annotations: summary: "OOM Killer active on {{ $labels.instance }}" # Много TIME_WAIT соединений - alert: HighTimeWaitConnections expr: node_sockstat_TCP_tw > 10000 for: 5m labels: severity: warning Alertmanager: умная маршрутизация уведомлений
/etc/alertmanager/alertmanager.yml:
global: smtp_smarthost: 'smtp.gmail.com:587' smtp_from: 'alerts@example.com' smtp_auth_username: 'alerts@example.com' smtp_auth_password: 'password' slack_api_url: 'https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK' # Шаблоны уведомлений templates: - /etc/alertmanager/templates/*.tmpl # Маршрутизация route: group_by: ['alertname', 'instance'] group_wait: 30s # ждём перед первым уведомлением group_interval: 5m # интервал между повторными уведомлениями группы repeat_interval: 4h # когда повторить если не решено receiver: 'slack-warnings' routes: # Критические — немедленно в PagerDuty - match: severity: critical receiver: 'pagerduty-critical' group_wait: 0s repeat_interval: 1h # Ночью тишина для warnings - match: severity: warning receiver: 'slack-warnings' mute_time_intervals: - nights-and-weekends # Отдельный канал для базы данных - match: job: mysql receiver: 'slack-dba-channel' # Время тишины time_intervals: - name: nights-and-weekends time_intervals: - weekdays: [saturday, sunday] - times: - start_time: '22:00' end_time: '08:00' # Получатели receivers: - name: 'slack-warnings' slack_configs: - channel: '#alerts' icon_emoji: ':warning:' title: '{{ .GroupLabels.alertname }}' text: | {{ range .Alerts }} *Instance:* {{ .Labels.instance }} *Description:* {{ .Annotations.description }} {{ end }} send_resolved: true - name: 'pagerduty-critical' pagerduty_configs: - service_key: 'YOUR_PAGERDUTY_KEY' - name: 'slack-dba-channel' slack_configs: - channel: '#dba-alerts' Grafana: визуализация
# Установка apt-get install -y apt-transport-https software-properties-common wget -q -O - https://packages.grafana.com/gpg.key | gpg --dearmor | \ tee /usr/share/keyrings/grafana.gpg > /dev/null echo "deb [signed-by=/usr/share/keyrings/grafana.gpg] \ https://packages.grafana.com/oss/deb stable main" | \ tee /etc/apt/sources.list.d/grafana.list apt-get update && apt-get install grafana -y systemctl enable --now grafana-server Provisioning дашбордов через код
/etc/grafana/provisioning/datasources/prometheus.yaml:
apiVersion: 1 datasources: - name: Prometheus type: prometheus access: proxy url: http://localhost:9090 isDefault: true jsonData: timeInterval: "15s" /etc/grafana/provisioning/dashboards/default.yaml:
apiVersion: 1 providers: - name: default orgId: 1 folder: '' type: file disableDeletion: false updateIntervalSeconds: 30 options: path: /var/lib/grafana/dashboards Готовые дашборды
На grafana.com/dashboards есть тысячи готовых дашбордов. Популярные ID для импорта:
1860 — Node Exporter Full
7362 — MySQL Overview
763 — Redis Dashboard
12708 — PHP-FPM Dashboard
11074 — Node Exporter for Prometheus
blackbox_exporter: мониторинг снаружи
Для мониторинга HTTP, TCP, DNS, ICMP с внешней точки зрения:
# /etc/blackbox_exporter/config.yml modules: http_2xx: prober: http timeout: 5s http: valid_http_versions: ["HTTP/1.1", "HTTP/2.0"] valid_status_codes: [] # 2xx follow_redirects: true tls_config: insecure_skip_verify: false http_post_2xx: prober: http http: method: POST headers: Content-Type: application/json body: '{"probe": "check"}' tcp_connect: prober: tcp timeout: 5s ssl_expiry: prober: http timeout: 5s http: fail_if_ssl: false fail_if_not_ssl: true tls_config: insecure_skip_verify: false В prometheus.yml добавляем:
- job_name: 'blackbox' metrics_path: /probe params: module: [http_2xx] static_configs: - targets: - https://myapp.example.com/health - https://api.example.com/status relabel_configs: - source_labels: [__address__] target_label: __param_target - source_labels: [__param_target] target_label: instance - target_label: __address__ replacement: localhost:9115 # Алерт на SSL - alert: SSLCertExpiringSoon expr: probe_ssl_earliest_cert_expiry - time() < 86400 * 30 labels: severity: warning annotations: summary: "SSL cert expires in {{ $value | humanizeDuration }}" Правильный мониторинг — это инвестиция, которая окупается при первом же инциденте, когда вы знаете о проблеме за 10 минут до того, как позвонят пользователи. Начните с node_exporter и базовых алертов, постепенно добавляйте экспортеры для ваших сервисов.
Сети в Linux: от основ до продвинутой диагностики
ip и iproute2: современная работа с сетью
Команда ifconfig устарела. Всё что нужно — в пакете iproute2:
# Интерфейсы и адреса ip addr show ip addr show eth0 ip -4 addr # только IPv4 ip -6 addr # только IPv6 # Добавить/удалить IP ip addr add 192.168.1.100/24 dev eth0 ip addr del 192.168.1.100/24 dev eth0 # Маршруты ip route show ip route add default via 192.168.1.1 ip route add 10.0.0.0/8 via 10.100.0.1 dev eth1 ip route del 10.0.0.0/8 # ARP таблица ip neigh show ip neigh flush all # Статистика интерфейсов ip -s link show eth0 # Состояние соединений ss -tnp # TCP с процессами ss -tlnp # только слушающие ss -u # UDP ss -x # Unix sockets ss -tnp state established # установленные ss -tnp state time-wait # TIME_WAIT ss -s # сводная статистика Диагностика сетевых проблем
# Трассировка маршрута traceroute -n google.com # числовые адреса, быстрее mtr --report google.com # интерактивный traceroute # DNS диагностика dig google.com dig @8.8.8.8 google.com # конкретный DNS dig google.com MX # MX записи dig -x 8.8.8.8 # reverse lookup nslookup google.com host google.com # Захват трафика tcpdump -i eth0 port 80 tcpdump -i eth0 host 10.0.0.1 tcpdump -i eth0 -w capture.pcap # запись в файл tcpdump -r capture.pcap # чтение из файла # Тест пропускной способности iperf3 -s # сервер iperf3 -c server-ip # клиент iperf3 -c server-ip -P 4 # 4 параллельных потока # Latency ping -c 10 -i 0.2 server-ip hping3 -S -p 80 -c 10 server-ip # TCP ping Виртуальные сети: bridge, vlan, veth
# VLAN ip link add link eth0 name eth0.100 type vlan id 100 ip addr add 192.168.100.1/24 dev eth0.100 ip link set dev eth0.100 up # Bridge (для виртуальных машин) ip link add br0 type bridge ip link set eth0 master br0 ip link set br0 up ip addr add 192.168.1.10/24 dev br0 # veth пара (для контейнеров) ip link add veth0 type veth peer name veth1 ip link set veth1 netns container-ns # Bonding (агрегация каналов) ip link add bond0 type bond mode active-backup ip link set eth0 master bond0 ip link set eth1 master bond0 ip link set bond0 up nftables: полный конфиг для веб-сервера
#!/usr/sbin/nft -f flush ruleset define WEB_PORTS = { 80, 443 } define SSH_PORT = 2222 define TRUSTED_IPS = { 10.0.0.0/8, 192.168.0.0/16 } table inet filter { # Счётчики для статистики counter ssh_allowed {} counter web_traffic {} counter dropped {} set banned_ips { type ipv4_addr flags timeout timeout 1d } chain input { type filter hook input priority 0; policy drop; # Заблокированные IP ip saddr @banned_ips drop # Established/related ct state established,related accept ct state invalid drop # Loopback iif "lo" accept # ICMP (ограниченно) ip protocol icmp icmp type { echo-request, echo-reply, destination-unreachable, time-exceeded } limit rate 10/second accept ip6 nexthdr icmpv6 accept # SSH с ограничением скорости (защита от brute-force) tcp dport $SSH_PORT ct state new limit rate 3/minute counter name ssh_allowed accept tcp dport $SSH_PORT ct state new counter name dropped drop # HTTP/HTTPS tcp dport $WEB_PORTS counter name web_traffic accept # Внутренние сервисы только с доверенных IP ip saddr $TRUSTED_IPS tcp dport { 3306, 6379, 9100 } accept log prefix "nft-drop: " flags all } chain forward { type filter hook forward priority 0; policy drop; } chain output { type filter hook output priority 0; policy accept; } } # Rate limiting для защиты от DDoS table inet mangle { chain prerouting { type filter hook prerouting priority -150; # SYN flood protection tcp flags syn tcp option maxseg size 1-500 drop # Новые TCP соединения: не более 100 в секунду с одного IP tcp flags syn ct state new \ meter syn_flood { ip saddr limit rate 100/second } \ accept tcp flags syn ct state new drop } } Файловые системы Linux: выбор, настройка, оптимизация
Обзор файловых систем
ФС
Сильные стороны
Слабые стороны
Применение
ext4
Стабильность, скорость
Нет встроенного RAID
Общее назначение
XFS
Большие файлы, параллелизм
Нельзя уменьшить
Медиа, базы данных
Btrfs
Снэпшоты, сжатие, RAID
Сложность
Разработка, NAS
ZFS
Надёжность, снэпшоты
Память, лицензия
Продакшн NAS
tmpfs
Скорость (RAM)
Нет персистентности
/tmp, кэш
Монтирование и /etc/fstab
# Посмотреть текущие точки монтирования df -hT mount | column -t findmnt # дерево монтирований # Временное монтирование mount -t ext4 /dev/sdb1 /mnt/data mount -o remount,ro / # перемонтировать только для чтения # /etc/fstab — правильный формат: # UUID=xxx /data ext4 defaults,noatime,nodiratime 0 2 # # Важные опции: # noatime — не обновлять время доступа (производительность!) # nodiratime — то же для директорий # relatime — компромисс: обновляем только если mtime новее atime # nodev — нет устройств (безопасность для /home) # nosuid — нет suid бит (безопасность) # noexec — нет выполнения (для /var/tmp) # Получить UUID blkid /dev/sdb1 # Монтирование tmpfs (RAM-диск) # tmpfs /tmp tmpfs defaults,noatime,nosuid,size=2G 0 0 Производительность ext4
# Опции при создании (важно для SSD!) mkfs.ext4 -b 4096 -E stride=128,stripe-width=128 /dev/sdb1 # stride = chunk_size / block_size (для RAID) # Настройка журналирования # data=writeback — самый быстрый, менее безопасный # data=ordered — дефолт, баланс # data=journal — самый безопасный, самый медленный tune2fs -o journal_data_writeback /dev/sdb1 # Отключение fsck при загрузке (для SSD) tune2fs -i 0 -c 0 /dev/sdb1 # Дефрагментация ext4 (редко нужно) e4defrag -c /dev/sdb1 # только анализ e4defrag /dev/sdb1 # дефрагментация # Resize2fs — изменение размера resize2fs /dev/sdb1 20G # уменьшить (только для ext2/3/4) resize2fs /dev/sdb1 # расширить до максимума Btrfs: продвинутые функции
# Создание с несколькими устройствами mkfs.btrfs -d raid1 /dev/sdb /dev/sdc # RAID1 для данных # Создание снэпшота btrfs subvolume create /data/subvol btrfs subvolume snapshot /data/subvol /data/subvol-snap-$(date +%Y%m%d) # Только для чтения (для бэкапов) btrfs subvolume snapshot -r /data/subvol /data/backups/snap-$(date +%Y%m%d) # Отправка снэпшота на другой сервер btrfs send /data/backups/snap-20240101 | ssh backup-server "btrfs receive /backups/" # Инкрементальный бэкап btrfs send -p /data/backups/snap-20240101 /data/backups/snap-20240102 | \ ssh backup-server "btrfs receive /backups/" # Сжатие btrfs filesystem defragment -r -czstd /data # сжать существующее # В fstab: compress=zstd:3 # Статистика btrfs filesystem show btrfs filesystem df /data btrfs scrub start /data # проверка целостности LVM: гибкое управление томами
# Структура: Physical Volumes → Volume Groups → Logical Volumes # Создание PV pvcreate /dev/sdb /dev/sdc pvdisplay pvs # Volume Group vgcreate vg_data /dev/sdb /dev/sdc vgdisplay vgs # Logical Volume lvcreate -L 50G -n lv_mysql vg_data lvcreate -l 100%FREE -n lv_data vg_data # всё свободное место lvdisplay lvs # Форматирование и монтирование mkfs.ext4 /dev/vg_data/lv_mysql # Расширение онлайн! lvextend -L +20G /dev/vg_data/lv_mysql resize2fs /dev/vg_data/lv_mysql # для ext4 # Снэпшоты LVM (для backup без даунтайма) lvcreate -L 5G -s -n lv_mysql_snap /dev/vg_data/lv_mysql mount /dev/vg_data/lv_mysql_snap /mnt/backup # Делаем бэкап из /mnt/backup lvremove /dev/vg_data/lv_mysql_snap # Добавить новый диск в VG pvcreate /dev/sdd vgextend vg_data /dev/sdd Docker и контейнеризация в Linux
Пространства имён: основа изоляции
Контейнеры — это не виртуальные машины, это изоляция через Linux namespaces:
# Типы namespaces # pid — изоляция процессов # net — изоляция сети # mnt — изоляция точек монтирования # uts — hostname и domainname # ipc — IPC (очереди сообщений, семафоры) # user — пользователи и группы # cgroup — изоляция cgroups # Создать отдельное сетевое пространство (вручную, как делает Docker) ip netns add myns ip netns exec myns ip link list ip netns exec myns bash # shell внутри namespace Docker: производительность и безопасность
# Просмотр ресурсов контейнеров docker stats --no-stream docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}" # Ограничение ресурсов docker run -d \ --name myapp \ --memory="512m" \ --memory-swap="1g" \ --cpus="0.5" \ --pids-limit=100 \ myapp:latest # Security options docker run -d \ --security-opt no-new-privileges:true \ --security-opt seccomp=/etc/docker/seccomp.json \ --cap-drop ALL \ --cap-add NET_BIND_SERVICE \ --read-only \ --tmpfs /tmp:size=100m \ myapp:latest Docker daemon оптимизация
/etc/docker/daemon.json:
{ "log-driver": "json-file", "log-opts": { "max-size": "100m", "max-file": "5" }, "storage-driver": "overlay2", "storage-opts": ["overlay2.override_kernel_check=true"], "default-ulimits": { "nofile": { "Name": "nofile", "Hard": 65536, "Soft": 65536 } }, "live-restore": true, "userland-proxy": false, "experimental": false, "metrics-addr": "127.0.0.1:9323", "max-concurrent-downloads": 5 } Многоэтапные Dockerfile для PHP
# Stage 1: Зависимости FROM composer:2 AS deps WORKDIR /app COPY composer.json composer.lock ./ RUN composer install --no-dev --optimize-autoloader --no-scripts # Stage 2: Production image FROM php:8.2-fpm-alpine # Устанавливаем только нужные расширения RUN apk add --no-cache \ libpng-dev \ libjpeg-turbo-dev \ libwebp-dev \ && docker-php-ext-configure gd --with-jpeg --with-webp \ && docker-php-ext-install -j$(nproc) \ pdo_mysql \ redis \ gd \ opcache \ intl \ && rm -rf /var/cache/apk/* # Копируем зависимости из предыдущего этапа COPY --from=deps /app/vendor /var/www/html/vendor # Копируем только нужные файлы COPY --chown=www-data:www-data app/ /var/www/html/app/ COPY --chown=www-data:www-data public/ /var/www/html/public/ # PHP конфиг для продакшна COPY docker/php.ini /usr/local/etc/php/conf.d/99-production.ini COPY docker/www.conf /usr/local/etc/php-fpm.d/www.conf # Непривилегированный пользователь USER www-data EXPOSE 9000 CMD ["php-fpm"] Docker Compose для dev-среды
version: '3.9' services: app: build: context: . dockerfile: Dockerfile target: deps # используем только dev зависимости volumes: - .:/var/www/html:cached # cached ускоряет macOS - /var/www/html/vendor # исключаем vendor из маппинга environment: APP_ENV: local DB_HOST: db depends_on: db: condition: service_healthy redis: condition: service_started nginx: image: nginx:alpine ports: - "8080:80" volumes: - ./docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro - .:/var/www/html:cached depends_on: - app db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: secret MYSQL_DATABASE: myapp volumes: - mysql_data:/var/lib/mysql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 5s timeout: 3s retries: 10 redis: image: redis:7-alpine command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru volumes: mysql_data: SSH: продвинутые возможности
SSH туннели: три типа
Local forwarding — пробрасываем удалённый порт локально
# Доступ к MySQL на сервере через локальный порт ssh -L 3307:localhost:3306 user@remote-server # Теперь подключаемся локально mysql -h 127.0.0.1 -P 3307 -u myapp -p # В фоне, без терминала ssh -fNL 3307:localhost:3306 user@remote-server Remote forwarding — пробрасываем локальный порт на сервер
# Даём серверу доступ к нашему локальному сервису (для демо, CI/CD) ssh -R 8080:localhost:3000 user@remote-server # На сервере: curl http://localhost:8080 → ваш локальный порт 3000 Dynamic forwarding — SOCKS5 прокси
# Весь трафик через сервер ssh -D 1080 user@remote-server # Настраиваем браузер на SOCKS5 proxy: localhost:1080 # В автоматическом режиме ssh -fND 1080 user@remote-server ~/.ssh/config: удобная конфигурация
# Глобальные настройки Host * ServerAliveInterval 60 ServerAliveCountMax 3 ControlMaster auto ControlPath ~/.ssh/sockets/%r@%h-%p ControlPersist 10m # переиспользовать соединение 10 минут AddKeysToAgent yes IdentityFile ~/.ssh/id_ed25519 # Продакшн Host prod-web01 HostName 203.0.113.10 User deploy Port 2222 IdentityFile ~/.ssh/id_ed25519_prod Host prod-db01 HostName 10.0.0.5 # внутренний IP User dbadmin ProxyJump prod-web01 # прыгаем через web01 # Staging Host staging HostName staging.example.com User deploy LocalForward 3307 localhost:3306 # автоматический туннель к MySQL # Wildcard для dev-серверов Host dev-* User vagrant StrictHostKeyChecking no UserKnownHostsFile /dev/null # не сохраняем fingerprint для dev # Мультиплексирование — одно TCP соединение для нескольких сессий mkdir -p ~/.ssh/sockets # Теперь второй ssh к тому же хосту использует уже открытое соединение ssh prod-web01 # открывает новое соединение ssh prod-web01 # переиспользует существующее (мгновенно!) ssh prod-web01 ls /var/log # команда через существующее соединение SSH-агент и управление ключами
# Запуск агента eval $(ssh-agent -s) # Добавляем ключ с таймаутом ssh-add -t 3600 ~/.ssh/id_ed25519 # 1 час # Просмотр ключей в агенте ssh-add -l # Переброска агента на удалённый сервер ssh -A user@server # ForwardAgent yes в конфиге # Или через конфиг: # Host prod-* # ForwardAgent yes # Hardware token (YubiKey) # ssh-keygen -t ecdsa-sk # создать ключ на YubiKey Копирование файлов: rsync через SSH
# Основной синтаксис rsync -avz --progress source/ user@server:/destination/ # Полезные опции rsync -avz \ --delete \ # удалять на приёмнике то, чего нет в источнике --exclude='*.log' \ --exclude='.git' \ --exclude='node_modules' \ --checksum \ # сравнивать по контрольной сумме, не по времени --backup \ # бэкап изменённых файлов --backup-dir=/backup/$(date +%Y%m%d) \ /var/www/myapp/ user@backup-server:/backups/myapp/ # Синхронизация через нестандартный порт rsync -avz -e "ssh -p 2222" source/ user@server:/dest/ # Dry run — что будет сделано без фактических изменений rsync -avzn source/ user@server:/dest/ Ядро Linux: мониторинг и тюнинг
/proc и /sys: окна в ядро
# Информация о системе cat /proc/version cat /proc/cpuinfo | grep -E "processor|model name|cpu MHz" | head -20 cat /proc/meminfo cat /proc/loadavg cat /proc/uptime # Текущие сетевые соединения ядра cat /proc/net/tcp # hex, неудобно, лучше ss # Статистика прерываний cat /proc/interrupts watch -n1 cat /proc/interrupts # I/O статистика cat /proc/diskstats # Параметры ядра (sysctl) sysctl -a | grep vm sysctl -a | grep net.ipv4.tcp # /sys — дерево устройств ls /sys/class/net/ # сетевые интерфейсы ls /sys/block/ # блочные устройства cat /sys/block/sda/queue/scheduler echo 512 > /sys/block/sda/queue/nr_requests # изменяем параметр Настройка планировщика задач CPU
# Текущий планировщик cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor # Доступные governors cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors # conservative ondemand userspace powersave performance schedutil # Для сервера: performance или schedutil echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor # Закрепляем через /etc/default/grub: # GRUB_CMDLINE_LINUX="cpufreq.default_governor=performance" # NUMA балансировка cat /proc/sys/kernel/numa_balancing echo 1 > /proc/sys/kernel/numa_balancing # Transparent Huge Pages cat /sys/kernel/mm/transparent_hugepage/enabled echo madvise > /sys/kernel/mm/transparent_hugepage/enabled # Watchdog (отключаем на виртуалках для экономии) echo 0 > /proc/sys/kernel/nmi_watchdog Параметры ядра для highload (полный sysctl)
# /etc/sysctl.d/99-highload.conf ########################## # VM ########################## vm.swappiness = 10 vm.dirty_ratio = 20 vm.dirty_background_ratio = 5 vm.dirty_writeback_centisecs = 500 vm.dirty_expire_centisecs = 3000 vm.overcommit_memory = 1 # для Redis: разрешаем overcommit vm.max_map_count = 262144 # для Elasticsearch ########################## # NET: буферы ########################## net.core.rmem_default = 262144 net.core.wmem_default = 262144 net.core.rmem_max = 134217728 net.core.wmem_max = 134217728 net.core.netdev_max_backlog = 65535 net.core.somaxconn = 65535 ########################## # NET: TCP ########################## net.ipv4.tcp_rmem = 4096 87380 134217728 net.ipv4.tcp_wmem = 4096 65536 134217728 net.ipv4.tcp_mem = 786432 1048576 26777216 net.ipv4.tcp_congestion_control = bbr net.core.default_qdisc = fq net.ipv4.tcp_max_syn_backlog = 65535 net.ipv4.tcp_max_tw_buckets = 1440000 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_fin_timeout = 15 net.ipv4.tcp_keepalive_time = 300 net.ipv4.tcp_keepalive_intvl = 30 net.ipv4.tcp_keepalive_probes = 5 net.ipv4.ip_local_port_range = 1024 65535 # ECN — явное уведомление о перегрузке net.ipv4.tcp_ecn = 1 # Fast Open (повторное использование рукопожатия) net.ipv4.tcp_fastopen = 3 ########################## # Безопасность ########################## net.ipv4.conf.all.rp_filter = 1 net.ipv4.conf.default.rp_filter = 1 net.ipv4.tcp_syncookies = 1 net.ipv4.conf.all.log_martians = 1 kernel.randomize_va_space = 2 sysctl -p /etc/sysctl.d/99-highload.conf # Проверяем что применилось sysctl net.ipv4.tcp_congestion_control sysctl vm.swappiness Анализ паник и OOM
# Последняя паника ядра journalctl -k -b -1 | grep -A 50 "Oops:" # OOM killer journalctl -k | grep -i "oom\|out of memory" dmesg | grep -i oom # Kernel panic dump (kdump) apt install kdump-tools # После паники дамп будет в /var/crash/ # Анализ crash dump apt install crash crash /usr/lib/debug/boot/vmlinux-$(uname -r) /var/crash/vmcore # Команды внутри crash: # bt — backtrace # log — kernel log buffer # ps — процессы в момент паники Linux — это айсберг. Большинство из нас видит только надводную часть: файлы, процессы, сеть. Но под водой — целый мир механизмов, которые делают его надёжной основой для миллиардов устройств. Понимание этих механизмов — отличие хорошего администратора от великого.
Резервное копирование в Linux: стратегии и инструменты
Правило 3-2-1
Любая стратегия резервного копирования должна начинаться с правила 3-2-1:
3 копии данных
2 разных типа носителей
1 копия вне офиса
Rsync: умное инкрементальное копирование
#!/usr/bin/env bash # Скрипт резервного копирования с ротацией BACKUP_SOURCE="/var/www" BACKUP_DEST="/mnt/backup" RETAIN_DAYS=30 DATE=$(date +%Y%m%d_%H%M%S) # Создаём снэпшот через hard links (не дублируем неизменённые файлы) rsync -avz --delete \ --link-dest="$BACKUP_DEST/latest" \ --exclude="*.log" \ --exclude="cache/" \ --exclude="tmp/" \ "$BACKUP_SOURCE/" \ "$BACKUP_DEST/$DATE/" # Обновляем симлинк на последний бэкап ln -sfn "$BACKUP_DEST/$DATE" "$BACKUP_DEST/latest" # Удаляем старые бэкапы find "$BACKUP_DEST" -maxdepth 1 -type d -mtime +$RETAIN_DAYS -exec rm -rf {} + echo "Backup completed: $BACKUP_DEST/$DATE" du -sh "$BACKUP_DEST/$DATE" Borg: дедупликация и шифрование
Borg — продвинутый инструмент с дедупликацией (одинаковые блоки хранятся один раз):
# Установка apt install borgbackup # Инициализация репозитория с шифрованием borg init --encryption=repokey-blake2 user@backup-server:/backups/myserver # Создание бэкапа borg create \ --verbose \ --filter AME \ --list \ --stats \ --show-rc \ --compression lz4 \ --exclude-caches \ --exclude '/home/*/.cache/*' \ --exclude '/var/cache/*' \ --exclude '/var/tmp/*' \ user@backup-server:/backups/myserver::myserver-$(date +%Y%m%d_%H%M) \ /etc \ /var/www \ /home \ /var/lib/mysql # осторожно с активной БД! # Список архивов borg list user@backup-server:/backups/myserver # Проверка целостности borg check user@backup-server:/backups/myserver # Восстановление cd /tmp/restore borg extract user@backup-server:/backups/myserver::myserver-20240115_0300 \ var/www/myapp/public # только конкретная директория # Ротация (хранить: 7 ежедневных, 4 недельных, 12 ежемесячных) borg prune \ --keep-daily=7 \ --keep-weekly=4 \ --keep-monthly=12 \ user@backup-server:/backups/myserver Бэкап MySQL без блокировок
#!/usr/bin/env bash # Бэкап MySQL с минимальным влиянием на продакшн DB_USER="backup" DB_PASS="backup_password" BACKUP_DIR="/var/backups/mysql" DATE=$(date +%Y%m%d_%H%M) mkdir -p "$BACKUP_DIR" # Создаём пользователя для бэкапа (только необходимые права) # GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER, PROCESS ON *.* TO 'backup'@'localhost'; # Бэкап всех баз mysqldump \ --user="$DB_USER" \ --password="$DB_PASS" \ --single-transaction \ --routines \ --triggers \ --events \ --all-databases \ --master-data=2 \ | gzip > "$BACKUP_DIR/full-$DATE.sql.gz" # Проверяем что файл не пустой size=$(stat -c%s "$BACKUP_DIR/full-$DATE.sql.gz") if [[ $size -lt 1000 ]]; then echo "ERROR: Backup file too small ($size bytes)" >&2 rm "$BACKUP_DIR/full-$DATE.sql.gz" exit 1 fi echo "Backup created: $BACKUP_DIR/full-$DATE.sql.gz ($size bytes)" # Ротация — удаляем старше 7 дней find "$BACKUP_DIR" -name "full-*.sql.gz" -mtime +7 -delete # XtraBackup для горячего бэкапа InnoDB (без --single-transaction ограничений) # apt install percona-xtrabackup-80 # xtrabackup --backup --user="$DB_USER" --password="$DB_PASS" \ # --target-dir="$BACKUP_DIR/xtrabackup-$DATE" Проверка восстановления — самое важное
Бэкап без проверки восстановления — не бэкап. Автоматизируйте:
#!/usr/bin/env bash # Тест восстановления MySQL (запускать еженедельно) BACKUP_FILE=$(ls -t /var/backups/mysql/full-*.sql.gz | head -1) TEST_DB="restore_test_$(date +%s)" echo "Testing restore of $BACKUP_FILE" # Создаём тестовую базу mysql -e "CREATE DATABASE $TEST_DB" # Восстанавливаем zcat "$BACKUP_FILE" | mysql "$TEST_DB" # Проверяем количество таблиц table_count=$(mysql -sN -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$TEST_DB'") echo "Tables restored: $table_count" # Удаляем тестовую базу mysql -e "DROP DATABASE $TEST_DB" if [[ $table_count -gt 0 ]]; then echo "Restore test PASSED" else echo "Restore test FAILED!" >&2 exit 1 fi Nginx: тюнинг и продвинутая конфигурация
Производительность nginx
# /etc/nginx/nginx.conf user www-data; # Одни worker per CPU core worker_processes auto; # Привязываем к ядрам (снижаем context switch) worker_cpu_affinity auto; # Максимум соединений = worker_processes * worker_connections events { worker_connections 4096; use epoll; # лучший I/O multiplexer для Linux multi_accept on; # принимаем все соединения за один раз } http { # Базовые оптимизации sendfile on; tcp_nopush on; # отправлять заголовки и начало файла вместе tcp_nodelay on; # отключить Nagle для активных соединений # Таймауты keepalive_timeout 65; keepalive_requests 1000; client_header_timeout 15; client_body_timeout 15; send_timeout 15; # Буферы client_body_buffer_size 128k; client_max_body_size 50M; client_header_buffer_size 1k; large_client_header_buffers 4 16k; # Сжатие gzip on; gzip_vary on; gzip_min_length 1024; gzip_comp_level 5; gzip_types text/plain text/css text/javascript application/javascript application/json application/xml image/svg+xml; # Кэширование статики open_file_cache max=10000 inactive=20s; open_file_cache_valid 30s; open_file_cache_min_uses 2; open_file_cache_errors on; # Безопасность server_tokens off; more_clear_headers Server; # если установлен nginx-extras # Rate limiting limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m; limit_conn_zone $binary_remote_addr zone=perip:10m; } Virtual host для PHP-приложения
# /etc/nginx/sites-available/myapp.conf # Upstream pool с health checks upstream php_fpm { least_conn; # балансировка по наименее загруженному server 127.0.0.1:9000 weight=5 max_fails=3 fail_timeout=30s; server 127.0.0.1:9001 weight=5 max_fails=3 fail_timeout=30s; keepalive 32; # постоянные соединения к FPM } # Кэш для FastCGI ответов fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=php_cache:100m max_size=2g inactive=60m use_temp_path=off; server { listen 80; server_name myapp.example.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name myapp.example.com; root /var/www/myapp/public; index index.php; # SSL ssl_certificate /etc/letsencrypt/live/myapp.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/myapp.example.com/privkey.pem; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; ssl_stapling on; ssl_stapling_verify on; # Безопасность add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # Логи access_log /var/log/nginx/myapp-access.log combined buffer=512k flush=1m; error_log /var/log/nginx/myapp-error.log warn; # Ограничения limit_conn perip 20; # Статика с долгим кешированием location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ { expires 1y; add_header Cache-Control "public, immutable"; log_not_found off; access_log off; } # API с rate limiting location /api/ { limit_req zone=api burst=20 nodelay; try_files $uri $uri/ /index.php?$query_string; } location /api/auth { limit_req zone=login burst=5 nodelay; try_files $uri $uri/ /index.php?$query_string; } # PHP-FPM location ~ \.php$ { fastcgi_pass php_fpm; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; include fastcgi_params; # Кеширование (осторожно — только для некэшируемого поставьте X-Cache-Bypass) fastcgi_cache php_cache; fastcgi_cache_key "$scheme$request_method$host$request_uri"; fastcgi_cache_valid 200 302 60m; fastcgi_cache_valid 404 1m; fastcgi_cache_bypass $http_pragma $http_authorization $cookie_PHPSESSID; fastcgi_no_cache $http_pragma $http_authorization; add_header X-Cache-Status $upstream_cache_status; # Буферизация fastcgi_buffer_size 128k; fastcgi_buffers 4 256k; fastcgi_busy_buffers_size 256k; # Таймаут для долгих запросов fastcgi_read_timeout 300; } location / { try_files $uri $uri/ /index.php?$query_string; } # Запрещаем служебные файлы location ~ /\.(ht|git|env) { deny all; return 404; } } Централизованное логирование: rsyslog, loki, ELK
rsyslog: маршрутизация логов
# /etc/rsyslog.conf — продвинутая конфигурация # Шаблоны template(name="FileFormat" type="string" string="%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n" ) # JSON формат для Logstash/Loki template(name="JSONFormat" type="list") { constant(value="{") constant(value="\"timestamp\":\"") property(name="timereported" dateFormat="rfc3339") constant(value="\",\"host\":\"") property(name="hostname") constant(value="\",\"severity\":\"") property(name="syslogseverity-text") constant(value="\",\"facility\":\"") property(name="syslogfacility-text") constant(value="\",\"program\":\"") property(name="programname") constant(value="\",\"pid\":\"") property(name="procid") constant(value="\",\"message\":\"") property(name="msg" format="json") constant(value="\"}\n") } # Маршрутизация по приоритету *.emerg :omusrmsg:* # все терминалы при критической ошибке auth,authpriv.* /var/log/auth.log mail.* -/var/log/mail.log # дефис = буферизованная запись cron.* /var/log/cron.log *.warn /var/log/warnings.log # Отдельный файл для nginx if $programname == 'nginx' then { action(type="omfile" file="/var/log/nginx/error.log" template="FileFormat") stop } # Пересылка на центральный сервер *.* action(type="omfwd" target="log-server.internal" port="514" protocol="tcp" template="JSONFormat" action.resumeRetryCount="-1" queue.type="linkedList" queue.size="50000" queue.filename="rsyslog_queue" queue.saveonshutdown="on" ) Loki + Promtail: современный стек
Loki — это "Prometheus для логов", хранит логи как метрики с метками:
# /etc/promtail/promtail-config.yaml server: http_listen_port: 9080 positions: filename: /var/log/positions.yaml clients: - url: http://loki:3100/loki/api/v1/push scrape_configs: - job_name: nginx static_configs: - targets: [localhost] labels: job: nginx env: production __path__: /var/log/nginx/access.log pipeline_stages: - regex: expression: '^(?P<remote_addr>\S+) - (?P<remote_user>\S+) \[(?P<time>[^\]]+)\] "(?P<method>\S+) (?P<path>[^\s"]+)[^"]*" (?P<status>\d+) (?P<body_bytes>\d+)' - labels: method: status: - metrics: http_requests_total: type: Counter description: "Total HTTP requests" source: status config: action: inc - job_name: php-app static_configs: - targets: [localhost] labels: job: php-app __path__: /var/www/myapp/storage/logs/*.log pipeline_stages: - multiline: firstline: '^\[\d{4}-\d{2}-\d{2}' max_wait_time: 3s - regex: expression: '^\[(?P<time>[^\]]+)\] (?P<env>\w+)\.(?P<level>[A-Z]+): (?P<message>.+)' - labels: level: env: Запросы LogQL (язык Loki)
# Все ошибки nginx {job="nginx"} |= "error" # HTTP 500 ошибки за последний час {job="nginx"} | regex `status=(?P<status>\d+)` | status="500" # Медленные запросы (>1 секунды) {job="nginx"} | regex `request_time=(?P<rt>[0-9.]+)` | rt > 1.0 # Топ URL по количеству запросов topk(10, sum by (path) (rate({job="nginx"} | json [5m]))) # Уровень ошибок в приложении sum(rate({job="php-app", level="ERROR"}[5m])) by (level) Ansible: управление конфигурациями Linux-серверов
Структура Ansible-проекта
ansible/ ├── ansible.cfg ├── inventory/ │ ├── production/ │ │ ├── hosts.yml │ │ └── group_vars/ │ │ ├── all.yml │ │ ├── web.yml │ │ └── db.yml │ └── staging/ │ └── hosts.yml ├── roles/ │ ├── common/ │ ├── nginx/ │ ├── php/ │ └── mysql/ └── playbooks/ ├── site.yml ├── deploy.yml └── update.yml ansible.cfg
[defaults] inventory = inventory/production remote_user = deploy private_key_file = ~/.ssh/id_ed25519 host_key_checking = False retry_files_enabled = False stdout_callback = yaml callback_whitelist = timer, profile_tasks forks = 20 [ssh_connection] pipelining = True ssh_args = -o ControlMaster=auto -o ControlPersist=60s Роль для hardening
# roles/common/tasks/main.yml --- - name: Update and upgrade apt packages apt: upgrade: dist update_cache: yes cache_valid_time: 3600 - name: Install required packages apt: name: - ufw - fail2ban - unattended-upgrades - logrotate - htop - curl - git state: present - name: Configure sysctl security settings sysctl: name: "{{ item.key }}" value: "{{ item.value }}" state: present reload: yes loop: "{{ sysctl_settings }}" - name: Configure UFW ufw: state: enabled policy: deny direction: incoming - name: Allow SSH ufw: rule: allow port: "{{ ssh_port }}" proto: tcp - name: Configure fail2ban template: src: jail.local.j2 dest: /etc/fail2ban/jail.local owner: root group: root mode: '0644' notify: restart fail2ban - name: Configure SSH template: src: sshd_config.j2 dest: /etc/ssh/sshd_config validate: 'sshd -t -f %s' owner: root group: root mode: '0600' notify: restart sshd Идемпотентность: делаем правильно
# Создание пользователя (идемпотентно) - name: Create deploy user user: name: deploy groups: www-data shell: /bin/bash create_home: yes state: present # Копируем SSH ключ - name: Set authorized keys authorized_key: user: deploy state: present key: "{{ lookup('file', 'files/deploy_key.pub') }}" exclusive: yes # удалить другие ключи # Изменение конфига только если нужно - name: Configure PHP-FPM template: src: php-fpm-pool.conf.j2 dest: /etc/php/8.2/fpm/pool.d/www.conf owner: root group: root mode: '0644' notify: reload php-fpm # Handlers (выполняются только если что-то изменилось) # roles/php/handlers/main.yml - name: reload php-fpm service: name: php8.2-fpm state: reloaded - name: restart php-fpm service: name: php8.2-fpm state: restarted Деплой приложения через Ansible
# playbooks/deploy.yml --- - name: Deploy MyApp hosts: web serial: "30%" # Rolling update: 30% серверов одновременно vars: app_dir: /var/www/myapp git_repo: git@github.com:company/myapp.git git_branch: "{{ branch | default('main') }}" tasks: - name: Pull latest code git: repo: "{{ git_repo }}" dest: "{{ app_dir }}" version: "{{ git_branch }}" force: yes - name: Install Composer dependencies composer: command: install working_dir: "{{ app_dir }}" no_dev: yes optimize_autoloader: yes - name: Run migrations command: php spark migrate --all args: chdir: "{{ app_dir }}" run_once: true # только на одном сервере - name: Clear application cache command: php spark cache:clear args: chdir: "{{ app_dir }}" - name: Reload PHP-FPM (graceful) service: name: php8.2-fpm state: reloaded - name: Warm up cache uri: url: "https://{{ inventory_hostname }}/health" status_code: 200 retries: 5 delay: 2 Диагностика Linux: алгоритм поиска проблем
Методология USE
USE Method (Brendan Gregg): для каждого ресурса проверяем:
Utilization — использование (в %)
Saturation — насыщение (очереди, ожидание)
Errors — ошибки
# CPU Utilization mpstat -P ALL 1 3 # CPU Saturation (очередь на выполнение) vmstat 1 | awk '{print $1}' # r - run queue # Memory Utilization free -h # Memory Saturation (swapping) vmstat 1 | awk '{print $7, $8}' # si/so - swap in/out # Disk Utilization iostat -xz 1 | grep -E "Device|sd|nvme" # Disk Saturation (await > service time) iostat -xz 1 | awk 'NR>3 {print $1, $16}' # %util # Network Utilization sar -n DEV 1 5 60-секундный анализ сервера
# Быстрый обзор за 60 секунд (по Brendan Gregg) uptime # load average dmesg -T | tail -5 # ошибки ядра vmstat -SM 1 3 # VM, CPU, I/O обзор mpstat -P ALL 1 3 # CPU по ядрам pidstat 1 3 # процессы iostat -xz 1 3 # I/O дисков free -m # память sar -n DEV 1 3 # сеть sar -n TCP,ETCP 1 3 # TCP метрики top # интерактивно Диагностика "сервер завис"
# 1. Можем ли мы что-то делать? # Если не отвечает по SSH - физический доступ или IPMI/iLO # 2. Что не отвечает? ping server-ip # сеть живая? nc -zv server-ip 22 # SSH порт открыт? nc -zv server-ip 80 # HTTP открыт? # 3. Загрузка uptime # load: 0.5 — норма # load: = CPU cores — занят # load: > CPU cores * 2 — перегружен # 4. Кто виноват? top -bn1 | head -20 ps auxwf | head -30 # 5. Есть ли OOM? dmesg | grep -i "oom\|killed process" journalctl -k --since "1 hour ago" | grep -i oom # 6. Диск переполнен? df -h du -sh /var /tmp /home # кто занял место # 7. Иноды кончились? df -i # 8. Что происходит с сетью? ss -s # статистика сокетов ss -tnp state time-wait | wc -l # TIME_WAIT netstat -i # ошибки на интерфейсах # 9. Дисковые проблемы dmesg | grep -i "error\|fail\|i/o" smartctl -H /dev/sda # здоровье диска # 10. Полная картина за последний час sar -A 1 10 # всё что собрал sar strace и ltrace: что делает процесс
# Что делает процесс прямо сейчас strace -p $(pgrep nginx | head -1) # Только конкретные системные вызовы strace -e trace=open,read,write,network -p PID # Статистика системных вызовов за 5 секунд strace -c -p PID -e trace=all & sleep 5 kill %1 # ltrace — вызовы библиотечных функций ltrace -p PID # Запустить и трейсить strace -e trace=network curl google.com 2>&1 | grep connect # Дочерние процессы тоже strace -f -p PID -o /tmp/strace.log Анализ производительности с perf
# Профиль за 10 секунд (нужен linux-tools-generic) perf record -F 99 -g -p $(pgrep php-fpm | head -1) -- sleep 10 perf report --stdio | head -50 # Hotspot функции perf top -K -p $(pgrep nginx | head -1) # Счётчики производительности perf stat -p PID -- sleep 5 # cache-misses, branch-misses, context-switches # Flame graph (установить FlameGraph от Brendan Gregg) perf record -F 99 -ag -- sleep 10 perf script | stackcollapse-perf.pl | flamegraph.pl > flame.svg Диагностика сетевых задержек
# Измеряем задержки на разных уровнях # 1. ICMP (сеть) ping -c 100 server-ip | tail -3 # 2. TCP handshake (OS + сеть) hping3 -S -c 100 -p 80 server-ip | tail -5 # 3. HTTP time_to_first_byte (приложение) curl -o /dev/null -s -w " dns: %{time_namelookup}s connect: %{time_connect}s tls: %{time_appconnect}s ttfb: %{time_starttransfer}s total: %{time_total}s " https://myapp.example.com # 4. Детальная трассировка HTTP curl -v --trace-time https://myapp.example.com 2>&1 | head -50 # 5. Tcpdump для анализа конкретного запроса tcpdump -i eth0 -w request.pcap host client-ip and port 443 # Открываем в Wireshark для детального анализа # 6. Статистика задержки на уровне сокета ss -ti # socket timing info Инструменты для экстренной диагностики: шпаргалка
# Процессы ps auxwf # дерево процессов pstree -pu # красивое дерево pgrep -a nginx # найти процессы lsof -p PID # файлы процесса lsof -i :80 # кто слушает порт 80 fuser -n tcp 80 # pid процесса на порту # Файловая система lsof +D /var/log # кто держит файлы в директории inotifywait -m /etc/passwd # слежка за изменениями find / -newer /tmp/stamp -type f 2>/dev/null # что изменилось с timestamp # Сеть tcpdump -i any port 80 -nn -q conntrack -L | wc -l # количество трекируемых соединений nmap -sV localhost # сканируем себя # История команд в случае инцидента history | grep -i "rm\|mv\|chmod\|dd" | tail -20 last | head -20 # последние логины lastb | head -10 # неудачные логины who # кто сейчас залогинен w # что они делают Диагностика — это смесь знаний, методологии и опыта. Самые ценные навыки: не паниковать, следовать методологии USE, измерять прежде чем делать выводы, и помнить что 90% проблем с производительностью — это диск, память или сеть, а не код.
Почему Ceph, а не просто NAS или SAN?
Представьте ситуацию: у вас 50 серверов, каждый с несколькими терабайтами данных, виртуальные машины, S3-хранилище для бэкапов, общий файловый ресурс для кластера Kubernetes — и всё это нужно хранить надёжно, быстро и так, чтобы смерть одного (или нескольких) серверов не привела к потере данных и даунтайму.
Традиционные решения здесь ломаются. NAS — единая точка отказа. SAN — дорого, сложно, проприетарно. RAID — не масштабируется за пределы одной машины. Ceph решает эту задачу радикально иначе: он распределяет данные по всем дискам всех серверов одновременно, и любой узел может умереть прямо сейчас, пока вы это читаете, — вы ничего не потеряете.
Ceph используют CERN (те самые, что ищут бозон Хиггса), крупнейшие облачные провайдеры, Proxmox, OpenStack — в общем, люди, которым нельзя терять данные. Давайте разберёмся, как это устроено.
Три уровня хранения в одном кластере
Ceph — это не одна технология, это три совершенно разных интерфейса хранения, построенных поверх одного движка:
┌─────────────────────────────────────────────────┐ │ Приложения и клиенты │ ├──────────────┬──────────────┬───────────────────┤ │ RBD │ CephFS │ RGW (S3/Swift) │ │ Блочное │ Файловая │ Объектное │ │ хранилище │ система │ хранилище │ ├──────────────┴──────────────┴───────────────────┤ │ RADOS │ │ (Reliable Autonomic Distributed │ │ Object Store) │ ├─────────────────────────────────────────────────┤ │ OSD OSD OSD OSD OSD OSD OSD │ │ (физические диски/SSD/NVMe) │ └─────────────────────────────────────────────────┘ RBD (RADOS Block Device) — виртуальный блочный диск. С точки зрения виртуальной машины или Kubernetes pod — это просто диск. Внутри он разбит на объекты по 4 МБ (по умолчанию) и размазан по всему кластеру. Размер — до 16 эксабайт.
CephFS — POSIX-совместимая распределённая файловая система. Монтируется как обычная папка, понимает права доступа, символические ссылки, всё как у людей. Метаданные хранит отдельно от данных через специальный демон MDS.
RGW (RADOS Gateway) — HTTP-интерфейс объектного хранилища, совместимый с Amazon S3 и OpenStack Swift. Загружаете файлы через API, получаете бакеты, версионирование, lifecycle-политики — всё как в S3.
Самое красивое: всё три интерфейса используют один и тот же кластер RADOS. Вы можете одновременно монтировать CephFS на NFS-сервере, раздавать RBD-диски виртуалкам Proxmox и гонять бэкапы в RGW — и все они делят одни и те же физические диски.
Архитектура: четыре типа демонов
Ceph-кластер — это набор демонов, каждый со своей ролью. Никаких монолитов, никакого единого «сервера хранилища».
MON — Monitor (мозг кластера)
MON1 MON2 MON3 \ | / \ | / кластерная карта (cluster map) Мониторы хранят карту кластера — полное описание топологии: какие OSD существуют, где они физически расположены, здоровы ли они. Это не данные, это метаданные. Мониторы работают по протоколу Paxos и требуют кворума: нужно нечётное число, минимум 3 в продакшне.
Без кворума мониторов — нет записи (но чтение может работать). Мониторы не хранят пользовательские данные вообще — они лёгкие, их можно держать даже на небольших VM.
OSD — Object Storage Daemon (мышцы кластера)
Один OSD = один физический диск (или раздел). OSD хранит данные, обслуживает запросы чтения/записи, участвует в репликации, сам находит соседей для репликации по карте кластера.
Типичный сервер в кластере: 12 дисков = 12 OSD-процессов + небольшой SSD для BlueStore WAL/DB.
OSD общаются напрямую — без центрального сервера хранения. Если клиент пишет данные в pool с репликацией 3x, primary OSD сам синхронно реплицирует на двух соседей и только потом отвечает клиенту «записано».
MDS — Metadata Server
Нужен только для CephFS. Хранит иерархию директорий и метаданные файлов (права, размеры, время). Данные файлов хранятся в обычных RADOS-объектах — MDS только помогает по пути /my/dir/file.txt найти нужные объекты.
Можно запустить несколько MDS для параллелизма — активный-активный режим (multi-MDS).
MGR — Manager
Менеджер собирает статистику, запускает модули (dashboard, prometheus-экспортер, балансировщик), обрабатывает оркестровку через cephadm. Нужно минимум 2 для отказоустойчивости (один active, один standby).
CRUSH: как Ceph решает, куда положить данные
Вот где начинается самое интересное. В обычном RAID контроллер знает: «диск 1, 2, 3». В Ceph нет центрального индекса «где лежит файл» — это было бы узким местом в огромном кластере.
Вместо этого используется алгоритм CRUSH (Controlled Replication Under Scalable Hashing). Зная только имя объекта и карту кластера, CRUSH детерминированно вычисляет, на каких OSD хранить данные — без запросов к какому-либо серверу метаданных.
object "my_file_chunk_0042" │ ▼ pg_id = hash(object_name) % pg_count │ ▼ CRUSH(pg_id, crush_map) → [OSD.7, OSD.23, OSD.41] Когда приходит запрос «где лежит объект X» — любой клиент, зная карту кластера, сам вычисляет ответ и идёт напрямую к нужному OSD. Без промежуточных серверов. Это и есть причина масштабируемости.
Placement Groups (PG): промежуточный уровень
Объектов в кластере могут быть миллиарды. Если бы каждый объект CRUSH маппил напрямую на OSD — карта кластера была бы гигантской. Поэтому объекты сначала группируются в Placement Groups (PG), а уже PG маппятся на OSD.
Объект → PG (группа объектов) → OSD Число PG на pool — важный параметр настройки. Слишком мало — неравномерное распределение, узкое место. Слишком много — накладные расходы. Золотое правило: ~100 PG на OSD в pool.
CRUSH Map: физическая топология
CRUSH знает физику вашего датацентра:
datacenter DC1 ├── rack Rack-A │ ├── host server-01 │ │ ├── osd.0 (weight 1.0) │ │ ├── osd.1 (weight 1.0) │ │ └── osd.2 (weight 1.0) │ └── host server-02 │ ├── osd.3 │ └── osd.4 └── rack Rack-B └── host server-03 ├── osd.5 └── osd.6 Правило репликации может звучать так: «три копии, каждая на отдельном rack'е». Тогда при смерти целого стойки ни одна PG не потеряет больше одной копии данных.
BlueStore: почему Ceph не использует ext4 или XFS
До Ceph 12 OSD хранил данные на обычной файловой системе (FileStore). Это работало, но было медленно: каждая запись проходила через XFS/ext4 со всеми их накладными расходами, двойным кешированием, лишними syscall'ами.
С Ceph 12 появился BlueStore — кастомный бэкенд хранения, который работает напрямую с блочным устройством, минуя файловую систему. FileStore официально удалён начиная с Reef (18.x).
Архитектура BlueStore
OSD Process ├── BlueStore │ ├── RocksDB (метаданные объектов, omap) │ │ └── хранится на быстром SSD/NVMe (BlueFS) │ ├── WAL (write-ahead log) │ │ └── тоже лучше на SSD │ └── данные объектов │ └── на основном диске (HDD или SSD) └── BlueFS (микрофайловая система для RocksDB) В Tentacle (20.x) BlueStore получил улучшенное сжатие и новый, более быстрый WAL — это не маркетинг, а реальные измеримые улучшения для workload'ов с частой записью.
Ключевые преимущества BlueStore:
Полный контроль над I/O без лишних слоёв
Атомарные транзакции без двойного буферирования
Встроенное сжатие (zlib, snappy, zstd, lz4)
Checksums для данных и метаданных (обнаружение битрот)
Эффективный omap для небольших значений ключ-значение
Репликация vs. Erasure Coding: выбираем стратегию
Репликация (Replication)
Простейший вариант: каждый объект хранится в N копиях на N разных OSD.
Запись "hello.txt": [OSD.5 — первичная копия] ├── реплицирует → [OSD.12 — копия 2] └── реплицирует → [OSD.31 — копия 3] Плюсы: простота, низкая latency, любой OSD может обслужить чтение. Минусы: 3x overhead по дисковому пространству.
Для продакшна стандарт — size=3, min_size=2. Это значит: нормальный режим — 3 копии, деградированный (когда один OSD умер) — 2 копии, меньше 2 — запись заблокирована.
Erasure Coding (EC)
EC — это как RAID 5/6, но распределённый. Данные разбиваются на K кусков, добавляются M паритетных кусков. Всего K+M кусков на K+M OSD. Для восстановления нужно любые K из K+M кусков.
Пример EC 4+2: chunk0 chunk1 chunk2 chunk3 | parity0 parity1 OSD.1 OSD.2 OSD.3 OSD.4 OSD.5 OSD.6 При смерти OSD.2 и OSD.5 — данные восстанавливаются из оставшихся 4 из 6. Плюсы: экономия места. EC 4+2 даёт overhead 1.5x против 3x для репликации. Минусы: сложнее, выше latency, CPU overhead на кодирование/декодирование.
EC оптимально для холодного хранилища, S3-бэкапов, больших объектов. Для горячих IOPS-нагруженных данных (БД, VM) — репликация.
FastEC в Tentacle: революция для Erasure Coding
В Ceph Tentacle (20.2.0) появилась долгожданная функция FastEC — принципиально новая реализация I/O для EC пулов с поддержкой partial reads и partial writes.
До FastEC: запись небольшого объекта в EC-пул требовала читать все K кусков, обновлять данные, пересчитывать все паритеты и писать всё обратно. Это называется Read-Modify-Write (RMW) — катастрофа для производительности при мелких записях.
FastEC оптимизирует именно этот случай. По словам разработчиков и независимым тестам, на определённых workload'ах FastEC обгоняет даже репликацию 3x по производительности — при вдвое меньшем расходе места.
Важно: FastEC включается явно на уровне пула командой allow_ec_optimizations:
ceph osd pool set mypool allow_ec_optimizations true Существующие пулы можно мигрировать без пересоздания данных — достаточно обновить OSD и MON до Tentacle.
Что нового в Ceph Tentacle (20.2.0)
Tentacle вышел 18 ноября 2025 года и является 20-м стабильным релизом Ceph. Это значительный релиз, не косметический. Вот главное:
FastEC — новый движок Erasure Coding
Уже разобрали выше. Переключение плагина по умолчанию с устаревшего Jerasure на ISA-L (Intel ISA-L library) — более быстрый, активно поддерживаемый. Jerasure больше не обслуживается авторами.
SMB-поддержка через Ceph
Ceph теперь умеет создавать SMB-шары прямо из кластера через новый модуль mgr. Технически это Samba поверх CephFS с автоматическим управлением через cephadm. Поддерживает Active Directory и standalone. Работает в кластерном режиме через CTDB.
ceph smb cluster create mysmb active-directory DC=corp,DC=example,DC=com \ --domain-realm corp.example.com mgmt-gateway: единая точка входа для управления
Новый сервис mgmt-gateway — nginx reverse proxy с TLS, который объединяет Dashboard, Prometheus, Grafana, Alertmanager под одним адресом. Никаких «зайди на порт 8443 для дашборда, 9090 для Prometheus, 3000 для Grafana».
Плюс интеграция с OAuth 2.0/OIDC для SSO. Настраивается через cephadm в пару команд.
certmgr: автоматические TLS-сертификаты
Подсистема управления сертификатами. Ceph теперь сам выступает корневым CA, выпускает сертификаты для своих сервисов, обновляет их автоматически, предупреждает об истечении. Никаких самоподписанных сертификатов вручную.
Data Availability Score
Новая команда для мониторинга доступности данных:
ceph osd pool availability-status Показывает «score» для каждого пула — сколько данных доступно прямо сейчас. Пул считается недоступным если любая PG не в состоянии active или есть unfound объекты.
Crimson OSD + SeaStore (Tech Preview)
Crimson — полностью переписанный OSD на основе Seastar (асинхронный, без блокирующих операций). В Tentacle к нему добавили развёртывание SeaStore — нового бэкенда хранения рядом с Crimson. Это всё ещё tech preview, в продакшне не используем — но прогресс виден.
Удаление устаревших модулей
Модули mgr/restful и mgr/zabbix официально удалены. Они были deprecated с 2020 года и имели уязвимости в зависимостях (CVE-2023-46136). Переходите на Dashboard API и Prometheus.
Когда Ceph — правильный выбор
Ceph имеет смысл когда у вас:
Минимум 3 физических сервера (иначе нет смысла в распределённости)
Объём данных от нескольких терабайт
Потребность в нескольких типах хранилища одновременно (block + object + file)
Нужна горизонтальная масштабируемость: добавил серверы → ёмкость и производительность выросли
Нужна отказоустойчивость без дорогого проприетарного железа
Когда Ceph — не правильный выбор:
Один сервер или только два — берите ZFS/BTRFS
Небольшой проект: overhead на управление не окупится
Нужна очень низкая latency (< 1ms) для транзакционной БД — NVMe All-Flash Array или local SSD в приоритете
Итог: ключевые концепции для запоминания
Концепция
Коротко
RADOS
Нижний уровень — distributed object store
CRUSH
Алгоритм распределения данных без метасервера
OSD
1 демон = 1 диск
PG
Группа объектов, единица репликации
MON
Кворумный регистр карты кластера
BlueStore
Нативный бэкенд OSD без ФС
RBD
Блочный диск поверх RADOS
CephFS
POSIX-ФС поверх RADOS + MDS
RGW
S3/Swift API поверх RADOS
FastEC
Быстрый Erasure Coding в Tentacle
В следующей статье мы разворачиваем реальный кластер с нуля через cephadm, настраиваем пулы и подключаем RBD к Proxmox.

Далее читай - Часть #2
В прошлой статье мы разобрались с теорией — теперь руки в землю. Будем разворачивать минимальный продакшн-кластер Ceph Tentacle (20.2.x) через cephadm — официальный инструмент оркестровки, который умеет всё: установку, конфигурирование, обновление, добавление узлов.
Что мы будем строить
Минимальная продакшн-конфигурация:
┌─────────────────────────────────────────────────────┐ │ ceph-node1 │ ceph-node2 │ ceph-node3 │ │ │ │ │ │ MON + MGR │ MON + MGR │ MON │ │ OSD.0 │ OSD.3 │ OSD.6 │ │ OSD.1 │ OSD.4 │ OSD.7 │ │ OSD.2 │ OSD.5 │ OSD.8 │ │ │ │ │ │ /dev/sdb │ /dev/sdb │ /dev/sdb │ │ /dev/sdc │ /dev/sdc │ /dev/sdc │ │ /dev/sdd │ /dev/sdd │ /dev/sdd │ └─────────────────────────────────────────────────────┘ Требования к каждому узлу:
OS: Ubuntu 22.04 LTS или Debian 12 (рекомендуется), RHEL 9 тоже ок
RAM: минимум 16 GB (рекомендуется 32+ GB для продакшна)
CPU: 4+ ядра
Сеть: минимум 1 GbE, лучше 10 GbE; отдельная сеть для репликации — хорошая идея
Диски: минимум 1 диск для OSD (не системный!), лучше SSD или NVMe
Важно: диски для OSD должны быть пустыми — без разделов, без файловых систем. BlueStore сам их форматирует.
Шаг 1: Подготовка всех узлов
Выполняем на каждом из трёх узлов.
Обновление системы и базовые пакеты
apt update && apt upgrade -y apt install -y \ chrony \ curl \ python3 \ python3-pip \ lvm2 \ podman \ # или docker ntp Почему chrony важен: Ceph очень чувствителен к рассинхронизации времени. Разница > 5 секунд между узлами вызывает предупреждения и может дестабилизировать кластер. Убедитесь что NTP работает:
timedatectl status chronyc tracking Настройка hostname и /etc/hosts
# На ceph-node1: hostnamectl set-hostname ceph-node1 # На всех трёх узлах добавляем в /etc/hosts: cat >> /etc/hosts << 'EOF' 192.168.10.11 ceph-node1 192.168.10.12 ceph-node2 192.168.10.13 ceph-node3 EOF SSH ключи: cephadm общается через SSH
Генерируем ключ на первом узле (bootstrap узел) и распространяем:
# На ceph-node1: ssh-keygen -t ed25519 -N "" -f /root/.ssh/id_ed25519 # Копируем на все узлы (включая node1 самого себя): for node in ceph-node1 ceph-node2 ceph-node3; do ssh-copy-id -i /root/.ssh/id_ed25519.pub root@$node done # Проверяем: for node in ceph-node1 ceph-node2 ceph-node3; do echo "=== $node ===" ssh root@$node "hostname && uname -r" done Подготовка дисков: убеждаемся что они чистые
# Проверяем состояние дисков lsblk fdisk -l /dev/sdb wipefs -a /dev/sdb # если нужно очистить # cephadm сам зачистит диски при добавлении — если они "чистые" # (без LVM, без партиций, без файловой системы) # Принудительно зачистить: ceph-volume lvm zap /dev/sdb --destroy # после установки ceph Шаг 2: Bootstrap первого узла
Устанавливаем cephadm
# На ceph-node1: curl --silent --remote-name --location \ https://github.com/ceph/ceph/raw/reef/src/cephadm/cephadm chmod +x cephadm # Устанавливаем в систему ./cephadm install # Добавляем репозиторий Tentacle cephadm add-repo --release tentacle # Устанавливаем ceph-common (для команды ceph) cephadm install ceph-common Bootstrap кластера
cephadm bootstrap \ --mon-ip 192.168.10.11 \ --cluster-network 192.168.20.0/24 \ --initial-dashboard-user admin \ --initial-dashboard-password 'YourStrongPassword!123' \ --allow-fqdn-hostname \ --skip-monitoring-stack # добавим мониторинг позже отдельно Что делает эта команда за кулисами:
Создаёт директории конфигурации /etc/ceph/
Генерирует ключи аутентификации
Поднимает первый MON в контейнере
Поднимает MGR
Активирует модуль Dashboard
Пишет /etc/ceph/ceph.conf и /etc/ceph/ceph.client.admin.keyring
После успешного выполнения вы увидите URL дашборда:
Ceph Dashboard is now available at: URL: https://ceph-node1:8443/ User: admin Password: YourStrongPassword!123 Параметр --cluster-network: Это сеть для трафика репликации между OSD. Если у вас только одна сеть — уберите этот параметр. Но если есть выделенная сеть — обязательно используйте её, это критично для производительности публичной сети.
Шаг 3: Добавляем узлы в кластер
Проверяем первый узел
ceph status # Должны увидеть: mon: 1 mons at quorum... # health: HEALTH_WARN (это нормально на старте) ceph orch status # Оркестратор должен быть активен Добавляем ceph-node2 и ceph-node3
# На ceph-node1 — добавляем public SSH ключ cephadm в авторизованные на узлах ceph cephadm get-pub-key > /tmp/ceph.pub ssh root@ceph-node2 "mkdir -p /root/.ssh && \ cat >> /root/.ssh/authorized_keys" < /tmp/ceph.pub ssh root@ceph-node3 "mkdir -p /root/.ssh && \ cat >> /root/.ssh/authorized_keys" < /tmp/ceph.pub # Добавляем хосты в кластер ceph orch host add ceph-node2 192.168.10.12 ceph orch host add ceph-node3 192.168.10.13 # Проверяем ceph orch host ls Шаг 4: Добавляем MON и MGR
# По умолчанию cephadm хочет 5 MON — для нас 3 достаточно ceph orch apply mon 3 # Проверяем что MON есть на всех трёх узлах ceph orch ps --daemon-type mon # Добавляем второй MGR (для failover) ceph orch apply mgr 2 Ждём пока cephadm автоматически запустит MON на node2 и node3. Следим:
watch ceph status # Ждём: mon: 3 mons at quorum ceph-node1,ceph-node2,ceph-node3 Шаг 5: Добавляем OSD — сердце кластера
Инвентаризация доступных дисков
# Смотрим что cephadm видит на всех узлах ceph orch device ls # Вывод покажет диски и их статус: # HOST PATH TYPE SIZE AVAILABLE REFRESHED # ceph-node1 /dev/sdb hdd 2TiB Yes 12s ago # ceph-node1 /dev/sdc hdd 2TiB Yes 12s ago # ... Диск помечен как AVAILABLE если он полностью пустой. Если нет — смотрим причину в колонке REJECT REASONS.
Автоматическое добавление всех доступных дисков
# Самый простой способ — использовать все доступные диски ceph orch apply osd --all-available-devices # Следим за прогрессом watch ceph osd tree Ручное добавление конкретных дисков (рекомендуется для продакшна)
# Добавляем по одному — больше контроля ceph orch daemon add osd ceph-node1:/dev/sdb ceph orch daemon add osd ceph-node1:/dev/sdc ceph orch daemon add osd ceph-node1:/dev/sdd ceph orch daemon add osd ceph-node2:/dev/sdb ceph orch daemon add osd ceph-node2:/dev/sdc ceph orch daemon add osd ceph-node2:/dev/sdd ceph orch daemon add osd ceph-node3:/dev/sdb ceph orch daemon add osd ceph-node3:/dev/sdc ceph orch daemon add osd ceph-node3:/dev/sdd OSD Service Spec для воспроизводимой конфигурации
Для инфраструктуры-как-код создаём spec-файл:
# osd-spec.yaml service_type: osd service_id: default placement: host_pattern: 'ceph-node*' data_devices: paths: - /dev/sdb - /dev/sdc - /dev/sdd # Если есть отдельные SSD для WAL/DB: # db_devices: # paths: # - /dev/nvme0n1 # wal_devices: # paths: # - /dev/nvme1n1 ceph orch apply -i osd-spec.yaml Шаг 6: Проверяем здоровье кластера
После добавления OSD кластер начнёт балансировку данных (backfill). Это нормально и займёт время. Следим:
# Общий статус ceph status # Подробный статус OSD ceph osd stat ceph osd df # использование дискового пространства # Статус PG ceph pg stat # Потребление ресурсов ceph df detail # Дерево OSD с весами ceph osd tree Хорошее состояние:
cluster: id: a7f64266-0894-4f1e-a635-d0aeaca0e993 health: HEALTH_OK services: mon: 3 daemons, quorum ceph-node1,ceph-node2,ceph-node3 mgr: ceph-node1.xxx(active), ceph-node2.xxx(standby) osd: 9 osds: 9 up (since 5m), 9 in (since 5m) data: pools: 1 pools, 1 pgs objects: 0 objects, 0 B usage: 450 MiB used, 54 TiB / 54 TiB avail pgs: 1 active+clean Шаг 7: Создаём пулы хранения
Пул — логический контейнер для данных. Каждый пул имеет свою политику репликации/EC, количество PG и другие параметры.
Пул с репликацией 3x (для VM, баз данных)
# Создаём пул ceph osd pool create vmpool 32 32 # 32 PG # Настраиваем репликацию ceph osd pool set vmpool size 3 # 3 копии ceph osd pool set vmpool min_size 2 # минимум 2 для записи # Тип пула - для RBD ceph osd pool application enable vmpool rbd # Инициализируем для RBD rbd pool init vmpool # Проверяем ceph osd pool ls detail Сколько PG нужно?
Формула: PG = (OSDs * 100) / pool_size
Для нашего кластера (9 OSD, репликация 3):
PG = (9 * 100) / 3 = 300 — но возьмём ближайшую степень 2 = 256
# Изменить количество PG (только увеличение) ceph osd pool set vmpool pg_num 64 ceph osd pool set vmpool pgp_num 64 С Ceph Luminous появился PG autoscaler — он сам подбирает оптимальное число PG:
# Включаем автоскалер для пула ceph osd pool set vmpool pg_autoscale_mode on # Глобально включить автоскалер ceph mgr module enable pg_autoscaler ceph config set global osd_pool_default_pg_autoscale_mode on Пул с Erasure Coding (для S3/бэкапов)
# Создаём EC-профиль # k=4 data chunks, m=2 parity chunks = 6 OSD минимум # overhead = 1.5x против 3x у репликации ceph osd erasure-code-profile set myec \ k=4 m=2 \ plugin=isa \ crush-failure-domain=host # Просматриваем профиль ceph osd erasure-code-profile get myec # Создаём пул с EC ceph osd pool create ecpool 32 32 erasure myec # Включаем FastEC оптимизации (Tentacle 20.x+) ceph osd pool set ecpool allow_ec_optimizations true # Для работы RGW с EC нужен overlay pool ceph osd pool create ecpool-index 16 # репликация для индексов ceph osd pool application enable ecpool rgw ceph osd pool application enable ecpool-index rgw Шаг 8: Подключаем RBD — блочное хранилище
Создаём RBD-образ
# Создаём образ диска 100 GB rbd create --size 102400 vmpool/myvm-disk01 # Смотрим информацию rbd info vmpool/myvm-disk01 # Листинг образов в пуле rbd ls vmpool # Размер всех образов rbd du vmpool Монтируем на Linux через kernel driver
# Маппируем образ как блочное устройство rbd device map vmpool/myvm-disk01 --id admin \ --keyring /etc/ceph/ceph.client.admin.keyring # Видим устройство rbd device list # /dev/rbd0 → vmpool/myvm-disk01 # Форматируем и монтируем mkfs.xfs /dev/rbd0 mkdir /mnt/rbd-data mount /dev/rbd0 /mnt/rbd-data # Авто-монтирование через /etc/fstab через rbdmap # /etc/ceph/rbdmap: # vmpool/myvm-disk01 id=admin,keyring=/etc/ceph/ceph.client.admin.keyring systemctl enable rbdmap RBD для Proxmox VE
Proxmox имеет встроенную поддержку Ceph. Добавляем через GUI или:
# На Proxmox хосте устанавливаем ceph-клиент apt install ceph-common # Копируем конфиг и ключ с Ceph кластера scp root@ceph-node1:/etc/ceph/ceph.conf /etc/pve/ceph.conf scp root@ceph-node1:/etc/ceph/ceph.client.admin.keyring \ /etc/pve/priv/ceph/ # Или создаём отдельного пользователя с ограниченными правами ceph auth get-or-create client.proxmox \ mon 'profile rbd' \ osd 'profile rbd pool=vmpool' \ mgr 'profile rbd pool=vmpool' \ > /tmp/ceph.client.proxmox.keyring # Добавляем Ceph storage в Proxmox pveceph pool create vmpool --pg_num 64 --pg_autoscale_mode on Шаг 9: CephFS — распределённая файловая система
# Создаём CephFS (автоматически создаёт metadata и data пулы) ceph fs volume create myfs --placement="3" # Проверяем статус MDS ceph mds stat ceph fs status myfs # Монтируем через FUSE (для тестов) apt install ceph-fuse mkdir /mnt/cephfs ceph-fuse /mnt/cephfs -m ceph-node1:6789 # Или через kernel driver (лучше производительность) # Получаем ключ ceph auth get-key client.admin | base64 # Монтируем: mount -t ceph ceph-node1:6789:/ /mnt/cephfs \ -o name=admin,secret=<base64-key> # В /etc/fstab: # ceph-node1:6789,ceph-node2:6789:/ /mnt/cephfs ceph \ # name=admin,secretfile=/etc/ceph/admin.secret,noatime 0 0 Subvolumes для Kubernetes
# Создаём subvolume group ceph fs subvolumegroup create myfs k8s # Создаём subvolume (persistent volume) ceph fs subvolume create myfs pvc-001 --group-name k8s --size 10G # Получаем путь ceph fs subvolume getpath myfs pvc-001 --group-name k8s # /volumes/k8s/pvc-001/... Шаг 10: RGW — S3-совместимое объектное хранилище
# Разворачиваем RGW через cephadm ceph orch apply rgw myrgw --placement="2 ceph-node1 ceph-node2" \ --port=8080 # Проверяем статус ceph orch ps --daemon-type rgw # Создаём пользователя radosgw-admin user create \ --uid=s3user \ --display-name="S3 User" \ --email=s3user@example.com # Получаем ключи radosgw-admin user info --uid=s3user # access_key и secret_key для S3 клиентов # Тестируем через s3cmd или mc (MinIO client) apt install s3cmd s3cmd --configure # вводим access_key, secret_key, endpoint # Или через AWS CLI aws configure # вводим ключи aws --endpoint-url http://ceph-node1:8080 s3 mb s3://mybucket aws --endpoint-url http://ceph-node1:8080 s3 ls aws --endpoint-url http://ceph-node1:8080 s3 cp /tmp/test.txt s3://mybucket/ Шаг 11: Стек мониторинга
Ceph Tentacle поставляет готовый стек мониторинга через cephadm. В Tentacle появился новый mgmt-gateway — единая точка входа:
# Разворачиваем полный стек мониторинга ceph orch apply prometheus ceph orch apply grafana ceph orch apply alertmanager ceph orch apply node-exporter # Новый в Tentacle: mgmt-gateway (nginx reverse proxy + TLS) cat > mgmt-gateway.yaml << 'EOF' service_type: mgmt-gateway placement: count: 2 # HA — два инстанса spec: port: 443 enable_auth: true # требовать аутентификацию EOF ceph orch apply -i mgmt-gateway.yaml # Проверяем ceph orch ps --daemon-type mgmt-gateway Теперь Dashboard, Grafana, Prometheus — всё доступно через один HTTPS endpoint на порту 443.
Встроенные Grafana-дашборды Ceph показывают:
OSD latency и throughput
Pool utilization
MON quorum status
PG состояния
Алерты
Полезные команды для ежедневной работы
# === КЛАСТЕР === ceph status # общий статус ceph health detail # детали о проблемах ceph df # использование пространства ceph versions # версии всех демонов # === OSD === ceph osd tree # топология ceph osd df # место по OSD ceph osd perf # latency метрики ceph osd dump # полный дамп карты OSD # Вывести из эксплуатации OSD (graceful) ceph osd out osd.5 ceph osd drain osd.5 # ждём пока PG переедут # === PG === ceph pg stat # статус всех PG ceph pg dump | grep -v active+clean # проблемные PG ceph pg repair 1.a3 # принудительный repair конкретной PG # === Логи === ceph log last 20 # последние записи кластерного лога journalctl -u ceph-osd@0 -f # лог конкретного OSD # === Оркестратор === ceph orch ls # список сервисов ceph orch ps # список демонов с состоянием ceph orch events # события оркестратора Типичные проблемы при развёртывании
HEALTH_WARN: too few PGs
ceph health detail # HEALTH_WARN too few PGs per OSD # Увеличиваем PG для затронутых пулов ceph osd pool set vmpool pg_num 64 ceph osd pool set vmpool pgp_num 64 OSD не добавляется: диск не определяется как доступный
# Смотрим причины отказа ceph orch device ls --wide # Часто причина: старые сигнатуры на диске # Зачищаем через ceph-volume cephadm shell -- ceph-volume lvm zap /dev/sdb --destroy # Или более агрессивно wipefs -a /dev/sdb dd if=/dev/zero of=/dev/sdb bs=4M count=10 clock skew detected
# Проверяем время на всех узлах for node in ceph-node1 ceph-node2 ceph-node3; do echo "$node: $(ssh root@$node date)" done # На проблемном узле — синхронизируем немедленно chronyc makestep timedatectl set-ntp true Кластер застрял в rebalancing надолго
# Смотрим прогресс ceph progress # Ускоряем (только во время обслуживания, не в бою) ceph tell osd.* injectargs --osd-max-backfills 8 ceph tell osd.* injectargs --osd-recovery-max-active 8 # После — возвращаем в норму ceph tell osd.* injectargs --osd-max-backfills 3 В следующей, финальной статье: производительность и тюнинг кластера, стратегии апгрейда с предыдущих версий, disaster recovery и продвинутые сценарии использования.

Далее читай - Часть #3
Кластер работает. Теперь начинается настоящая работа: выжать из него максимум производительности, не потерять данные при апгрейде и знать, что делать когда (не «если») что-то сломается.
Часть 1: Планирование железа — правильный старт
Перед тем как тюнить — убедитесь что железо подобрано правильно. Никакой тюнинг не исправит плохую архитектуру.
Сети: разделяйте публичную и кластерную
Худшее что можно сделать — смешать пользовательский трафик и репликацию в одну сеть.
Публичная сеть (client network): клиенты → MON/OSD Кластерная сеть (cluster network): OSD → OSD (репликация) Рекомендация: - Публичная: 10 GbE minimum - Кластерная: 25 GbE или bond из двух 10 GbE Конфигурируем при bootstrap:
cephadm bootstrap \ --mon-ip 192.168.10.11 \ --cluster-network 192.168.20.0/24 # или после: ceph config set global cluster_network 192.168.20.0/24 Размещение BlueStore компонентов
BlueStore — три уровня данных с разными требованиями:
Компонент
Что хранит
Требования
Рекомендация
DATA
Тела объектов
Ёмкость
HDD или SSD
DB (RocksDB)
Метаданные объектов
IOPS, latency
NVMe SSD
WAL
Write-Ahead Log
Высокие IOPS
NVMe SSD
Если все компоненты на одном диске — они конкурируют за I/O. Выносим DB и WAL на NVMe:
# osd-spec-nvme.yaml service_type: osd service_id: nvme-optimized placement: host_pattern: 'ceph-node*' data_devices: paths: - /dev/sdb # HDD для данных - /dev/sdc db_devices: paths: - /dev/nvme0n1 # NVMe для DB (разделяется между несколькими OSD) wal_devices: paths: - /dev/nvme1n1 # отдельный NVMe для WAL Золотое правило: один NVMe может обслуживать WAL/DB для 4-6 HDD OSD.
Расчёт оптимального числа OSD на сервер
Больше OSD — больше параллелизма, но больше RAM. Один OSD потребляет:
~1 GB RAM (HDD OSD, небольшие данные)
~2-4 GB RAM (SSD/NVMe OSD под нагрузкой)

BlueStore cache: по умолчанию 1/4 RAM на все OSD
# Проверяем потребление памяти OSD ceph daemon osd.0 dump_mempools ceph daemon osd.0 perf dump | grep -i mem Часть 2: Тюнинг производительности
BlueStore: кэш и компрессия
# Размер BlueStore кэша — главный параметр # По умолчанию: 1/4 от общей RAM (авто) # Можно задать явно для SSD/NVMe (они меньше нуждаются в кэше) ceph config set osd bluestore_cache_size_ssd 1073741824 # 1 GB для SSD OSD # HDD нуждаются в бОльшем кэше ceph config set osd bluestore_cache_size_hdd 536870912 # 512 MB для HDD OSD # Компрессия — включаем для cold data ceph osd pool set mypool compression_mode aggressive # сжимать всегда # или ceph osd pool set mypool compression_mode passive # сжимать если выгодно ceph osd pool set mypool compression_algorithm zstd # лучший ratio # или snappy — быстрее, но меньше сжимает # или lz4 — самый быстрый, минимальное сжатие ceph osd pool set mypool compression_min_blob_size 8192 # мин. размер для сжатия Настройка очереди I/O — mclock
С Ceph Pacific появился планировщик mclock, дающий QoS на уровне OSD:
# Проверяем текущий планировщик ceph config get osd osd_op_queue # должен быть: mclock_scheduler # Приоритеты для workload'ов: # client — пользовательские операции # recovery — восстановление данных # scrub — фоновая проверка # Для HDD-кластера снижаем агрессивность recovery ceph config set osd osd_mclock_scheduler_client_res 1 ceph config set osd osd_mclock_scheduler_recovery_res 1 ceph config set osd osd_mclock_scheduler_scrub_res 1 В Tentacle добавили защиту от нереалистичных значений IOPS capacity для mclock — теперь если измеренное значение IOPS слишком низкое (< 50 для HDD, < 1000 для SSD), планировщик использует последнее валидное значение.
Оптимизация для конкретных workload'ов
Для виртуальных машин (много случайных мелких I/O):
# Увеличиваем число OSD threads ceph config set osd osd_op_num_shards 8 ceph config set osd osd_op_num_threads_per_shard 2 # Write pipeline ceph config set osd bluestore_throttle_bytes 67108864 # 64 MB ceph config set osd bluestore_throttle_deferred_bytes 134217728 # 128 MB # Для NVMe — отключаем оверхед на большие буферы ceph config set osd bluestore_max_blob_size_ssd 65536 Для больших последовательных записей (S3, медиа):
# Увеличиваем объект для EC ceph config set osd osd_max_write_size 512 # MB # RGW chunk size ceph config set client rgw_obj_stripe_size 8388608 # 8 MB Для read-heavy workload'ов:
# Увеличиваем BlueStore cache для чтения ceph config set osd bluestore_cache_meta_ratio 0.4 # 40% для метаданных ceph config set osd bluestore_cache_kv_ratio 0.4 # 40% для RocksDB # Readahead на уровне BlueStore ceph config set osd bluestore_default_buffered_read true Настройка RBD для Kubernetes / Proxmox
# Включаем RBD кеширование на стороне клиента cat >> /etc/ceph/ceph.conf << 'EOF' [client] rbd cache = true rbd cache size = 134217728 # 128 MB rbd cache max dirty = 100663296 # 96 MB rbd cache target dirty = 67108864 # 64 MB rbd cache max dirty age = 5.0 rbd cache writethrough until flush = true EOF # Для diskless систем — через librbd rbd config image set vmpool/myvm-disk01 rbd_cache true rbd config image set vmpool/myvm-disk01 rbd_cache_size 134217728 Часть 3: FastEC — как правильно использовать
FastEC — главная фича Tentacle для EC пулов. Разберём как мигрировать и что получаем.
Создаём новый EC пул с FastEC
# Профиль с ISA-L (новый дефолт в Tentacle) ceph osd erasure-code-profile set fastec-profile \ k=4 m=2 \ plugin=isa \ technique=reed_sol_van \ crush-failure-domain=host # Создаём пул ceph osd pool create fastec-pool 64 64 erasure fastec-profile # Включаем FastEC оптимизации ceph osd pool set fastec-pool allow_ec_optimizations true # Проверяем что включилось ceph osd pool get fastec-pool allow_ec_optimizations Миграция существующего EC пула
Если у вас был EC пул с Jerasure — миграция возможна без пересоздания данных:
# Обновляем OSD и MON до Tentacle (см. раздел Upgrade) # После апгрейда — включаем оптимизации ceph osd pool set oldpool allow_ec_optimizations true # Следим за состоянием пула во время активации watch ceph pg stat Бенчмарк: насколько быстрее FastEC?
Сравниваем производительность:
# Устанавливаем инструменты apt install ceph-common # Тест записи в EC пул с FastEC rados bench -p fastec-pool 60 write --no-cleanup # Тест чтения rados bench -p fastec-pool 60 seq # Сравниваем с репликацией rados bench -p vmpool 60 write --no-cleanup rados bench -p vmpool 60 seq # Очищаем после теста rados bench -p fastec-pool 60 cleanup rados bench -p vmpool 60 cleanup По данным разработчиков и независимым тестам (blog nuvotex.de, 42on.com): FastEC при workload'е с преобладанием чтения и объектами среднего размера (1-4 MB) может превысить производительность репликации 3x при вдвое меньшем расходе места.
Часть 4: Апгрейд с Squid (19.x) до Tentacle (20.x)
Подготовка к апгрейду
Это самый важный раздел. Апгрейд Ceph — процедура, требующая внимания.
# 1. Проверяем здоровье — ОБЯЗАТЕЛЬНО перед началом ceph status ceph health detail # Кластер ДОЛЖЕН быть в HEALTH_OK или HEALTH_WARN (без critical) # НЕ начинайте при: OSD down, degraded PGs, incomplete PGs # 2. Проверяем версии клиентов ceph features # показывает connected clients и их версии # 3. Делаем снэпшот всех RBD образов (опционально, но разумно) for pool in $(ceph osd pool ls); do for image in $(rbd ls $pool 2>/dev/null); do rbd snap create $pool/$image@pre-upgrade-$(date +%Y%m%d) done done # 4. Отключаем PG autoscaler на время апгрейда ceph osd pool set noautoscale # 5. Устанавливаем noout флаг (предотвращает rebalancing при рестарте OSD) ceph osd set noout Апгрейд через cephadm (рекомендуется)
# Запускаем апгрейд — cephadm делает всё сам, rolling update ceph orch upgrade start --image quay.io/ceph/ceph:v20.2.0 # Мониторим прогресс ceph orch upgrade status # Детальный лог ceph -W cephadm # В реальном времени watch ceph versions Cephadm обновляет в правильном порядке:
MGR (сначала standby, потом active)
MON (по одному, ждёт quorum)
OSD (по одному, ждёт чистых PG после каждого)
MDS, RGW, другие сервисы
Вы можете поставить на паузу и возобновить:
ceph orch upgrade pause ceph orch upgrade resume Апгрейд вручную (для не-cephadm кластеров)
# Порядок строго важен! # 1. MON for mon_host in ceph-node1 ceph-node2 ceph-node3; do echo "Upgrading MON on $mon_host" ssh root@$mon_host "apt update && apt install -y ceph-mon" ssh root@$mon_host "systemctl restart ceph-mon.target" # Ждём возврата quorum sleep 30 ceph mon stat done # Проверяем что все MON обновились ceph mon dump | grep min_mon_release # Должно показать: min_mon_release 20 (tentacle) # 2. MGR for mgr_host in ceph-node1 ceph-node2; do ssh root@$mgr_host "apt install -y ceph-mgr" ssh root@$mgr_host "systemctl restart ceph-mgr.target" sleep 10 done # 3. OSD (по одному за раз!) for osd_id in $(ceph osd ls); do osd_host=$(ceph osd find $osd_id | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['crush_location']['host'])") echo "Upgrading OSD.$osd_id on $osd_host" # Устанавливаем новый пакет ssh root@$osd_host "apt install -y ceph-osd" # Рестартуем OSD ssh root@$osd_host "systemctl restart ceph-osd@$osd_id" # Ждём пока OSD поднимется sleep 30 # Проверяем что OSD up и PGs чистые while ceph pg stat | grep -q "degraded\|recovering"; do echo "Waiting for PGs to recover..." sleep 30 done echo "OSD.$osd_id upgraded successfully" done # 4. После всех OSD — финализация ceph osd require-osd-release tentacle Финализация апгрейда
# Снимаем noout ceph osd unset noout # Включаем PG autoscaler обратно ceph osd pool unset noautoscale # Проверяем что все демоны на новой версии ceph versions # Убеждаемся что все фичи Tentacle включены ceph osd dump | grep require_osd_release # Включаем новые возможности Tentacle ceph osd pool set mypool allow_ec_optimizations true # если EC пул Часть 5: Disaster Recovery — что делать когда всё плохо
Сценарий 1: OSD упал
# Смотрим что произошло ceph health detail ceph osd tree | grep -i down # Оценка: сколько времени OSD уже down? ceph osd info osd.5 | grep "last_clean_epoch" # Быстрый рестарт (если проблема временная) systemctl restart ceph-osd@5 # или через cephadm: ceph orch daemon restart osd.5 # Если OSD не стартует — смотрим логи journalctl -u ceph-osd@5 -n 100 --no-pager # OSD сломан физически — нужно заменить # Помечаем как out (начнётся rebalancing) ceph osd out osd.5 # Ждём завершения rebalancing watch ceph pg stat # ждём active+clean # Удаляем из кластера ceph osd purge osd.5 --yes-i-really-mean-it # Меняем диск, зачищаем и добавляем обратно ceph orch daemon add osd ceph-node2:/dev/sdc Сценарий 2: Целый хост упал
# Если хост не вернётся — убираем его OSD # Для примера: умер ceph-node2 с OSD 3,4,5 # Помечаем все OSD хоста как out ceph osd host-down-out ceph-node2 # если есть команда # или вручную: for osd in 3 4 5; do ceph osd out osd.$osd; done # После rebalancing — удаляем for osd in 3 4 5; do ceph osd purge osd.$osd --yes-i-really-mean-it done # Удаляем MON если он был на этом хосте ceph mon remove ceph-node2 # Удаляем хост из оркестратора ceph orch host drain ceph-node2 ceph orch host rm ceph-node2 --force # Проверяем здоровье после ceph status Сценарий 3: PG застряла в inconsistent/corrupt
# Находим проблемные PG ceph pg dump | grep -v "active+clean" # Запускаем repair ceph pg repair 3.1a # Если repair не помогает — более агрессивно ceph osd set nodeep-scrub # временно отключаем deep-scrub # Смотрим детали PG ceph pg 3.1a query # OSD с повреждёнными данными ceph osd tree ceph pg 3.1a get # какие OSD участвуют # Принудительное восстановление из другой реплики # (осторожно! только если уверены что данные на primary повреждены) ceph pg force-recovery 3.1a Сценарий 4: MON потерял quorum
# Проверяем статус MON ceph mon stat ceph mon dump # Если 1 из 3 MON не отвечает — quorum ещё есть (2 из 3) # Рестартуем проблемный systemctl restart ceph-mon@ceph-node2 # Если quorum потерян (0 из 3 доступны) — режим аварийного восстановления # Это серьёзная ситуация # На одном живом MON: ceph-mon -i ceph-node1 --extract-monmap /tmp/monmap monmaptool --print /tmp/monmap # Удаляем недостижимые MON из карты monmaptool --rm ceph-node2 /tmp/monmap monmaptool --rm ceph-node3 /tmp/monmap # Инжектируем исправленную monmap ceph-mon -i ceph-node1 --inject-monmap /tmp/monmap # Запускаем с одним MON ceph-mon -i ceph-node1 # Добавляем новые MON после стабилизации ceph orch apply mon ceph-node1,ceph-node2,ceph-node3 Сценарий 5: Восстановление удалённого RBD образа
# Если образ удалён — проверяем trash rbd trash ls vmpool # Восстанавливаем из trash (образы там держатся delay_seconds) rbd trash restore vmpool/trash-id # Если включён rbd-mirror с журналированием — восстановление из журнала # В крайнем случае — восстановление из снэпшота rbd snap ls vmpool/myvm-disk01 rbd snap rollback vmpool/myvm-disk01@pre-upgrade-20241201 # Восстановление из бэкапа через export rbd export vmpool/myvm-disk01 /mnt/backup/myvm-disk01.raw # Восстановление: rbd import /mnt/backup/myvm-disk01.raw vmpool/myvm-disk01-restored Часть 6: Продвинутые возможности Tentacle
SMB shares из CephFS
# Создаём SMB кластер (Active Directory интеграция) ceph smb cluster create mysmb \ active-directory \ --domain DC=corp,DC=example,DC=com \ --realm CORP.EXAMPLE.COM \ --dns-server 192.168.1.10 # Добавляем CephFS share ceph smb share create mysmb myshare \ --cephfs-volume myfs \ --cephfs-path /shares/myshare # Проверяем ceph smb cluster ls ceph smb share ls # Через Dashboard — аналогично с GUI RBD Live Migration — новинка Tentacle
Мгновенный импорт образов из других кластеров без копирования данных:
# Импорт из другого Ceph кластера (native format) rbd migration prepare \ --source-spec '{"type":"native","cluster_name":"src-cluster","pool_name":"vmpool","image_name":"myvm"}' \ dstpool/myvm-imported # Импорт через NBD (из любого источника) rbd migration prepare \ --source-spec '{"type":"nbd","uri":"nbd://192.168.1.100:10809/disk"}' \ dstpool/imported-disk # Запускаем миграцию (фоновая копия данных) rbd migration execute dstpool/myvm-imported # Когда завершится — фиксируем rbd migration commit dstpool/myvm-imported Магия в том, что образ доступен для чтения и записи немедленно — пока данные копируются в фоне, читаются напрямую с источника.
Data Availability Score — новый инструмент мониторинга
# Включаем tracking ceph config set global enable_availability_tracking true # Проверяем score для каждого пула ceph osd pool availability-status # Вывод: # POOL AVAILABLE SCORE # vmpool yes 1.00 # ecpool yes 0.99 ← одна PG в не-clean состоянии # Очищаем статус для пула после устранения проблемы ceph osd pool clear-availability-status vmpool Scrub: планирование глубоких проверок
# Принудительный scrub для конкретной PG ceph pg scrub 1.a3 ceph pg deep-scrub 1.a3 # Scrub всего пула ceph osd pool scrub vmpool # Планирование — ограничиваем scrub нерабочим временем ceph config set osd osd_scrub_begin_hour 1 # с 1:00 ceph config set osd osd_scrub_end_hour 6 # до 6:00 ceph config set osd osd_scrub_min_interval 86400 # не чаще раза в день ceph config set osd osd_deep_scrub_interval 604800 # deep-scrub раз в неделю # Статус scrub ceph pg dump | awk '{print $1, $16, $17}' | head -30 # PG_ID | LAST_SCRUB | LAST_DEEP_SCRUB Часть 7: Capacity planning и масштабирование
Добавление нового хоста и OSD
# Добавляем хост ceph orch host add ceph-node4 192.168.10.14 # Добавляем OSD ceph orch daemon add osd ceph-node4:/dev/sdb ceph orch daemon add osd ceph-node4:/dev/sdc # Автоматическая ребалансировка начнётся сразу # Следим за прогрессом ceph progress watch ceph df Расчёт сырого хранилища
Полезное место = (Общий объём) / overhead_factor Репликация 3x: overhead = 3.0 EC 4+2: overhead = 1.5 EC 6+3: overhead = 1.5 EC 8+3: overhead = 1.375 Для кластера с 9 × 4TB HDD и репликацией 3x: - Сырое: 36 TB - Полезное: 36 / 3 = 12 TB (минус ~10% overhead Ceph = ~10.8 TB) Правило большого пальца: не заполняйте более 80% полезного места! При 80%+ производительность падает из-за фрагментации и задержек recovery. Мониторинг через Prometheus
# ceph exporter уже встроен, prometheus конечные точки: # http://ceph-node1:9283/metrics - MGR prometheus module # Ключевые метрики для алертов: # ceph_health_status != 0 — нездоровый кластер # ceph_osd_in == 0 — OSD out # ceph_pg_degraded > 0 — деградированные PG # ceph_osd_available_bytes < 20% — заканчивается место # ceph_osd_apply_latency_ms > 50 — высокая задержка записи # Пример alertmanager rule: cat >> /etc/prometheus/rules/ceph.yml << 'EOF' groups: - name: ceph rules: - alert: CephHealthError expr: ceph_health_status == 2 for: 1m labels: severity: critical annotations: summary: "Ceph cluster is in ERROR state" - alert: CephOSDDown expr: ceph_osd_up == 0 for: 2m labels: severity: warning - alert: CephDiskAlmostFull expr: (ceph_osd_stat_bytes_used / ceph_osd_stat_bytes) > 0.80 for: 5m labels: severity: warning annotations: summary: "Ceph OSD {{ $labels.ceph_daemon }} is {{ $value | humanizePercentage }} full" EOF Чеклист: Ceph в продакшне
До разворачивания:
[ ] Минимум 3 физических хоста (лучше 5+ для отказоустойчивости)
[ ] Отдельные сети для публичного и кластерного трафика (10 GbE+)
[ ] NTP синхронизирован на всех узлах
[ ] SSD/NVMe для BlueStore DB и WAL
[ ] Резервные диски наготове для горячей замены
[ ] CRUSH map настроен с учётом физической топологии (стойки, ЦОД)
Оперативный мониторинг:
[ ] Prometheus + Grafana с Ceph дашбордами
[ ] Алерты на HEALTH_ERR, OSD down, PG degraded, диск >80%
[ ] mgmt-gateway настроен (Tentacle 20.x)
[ ] certmgr управляет TLS сертификатами
Регулярные процедуры:
[ ] Ежедневная проверка ceph status
[ ] Еженедельный deep-scrub (автоматически через cron)
[ ] Тестирование восстановления из снэпшотов раз в квартал
[ ] Обновления безопасности: следим за ceph-announce
Перед апгрейдом:
[ ] Кластер в HEALTH_OK
[ ] Снэпшоты критичных RBD образов
[ ] ceph osd set noout
[ ] Тест в staging среде
[ ] Откат-план: как вернуться на предыдущую версию (downgrade невозможен, нужен rollback через снэпшоты)
Где учиться дальше
Официальная документация:
docs.ceph.com — эталонная документация, всегда актуальная
ceph.io/en/news/blog — официальный блог с release notes и углублёнными техническими статьями
Сообщество:
ceph-users@ceph.io — рассылка для пользователей
irc.oftc.net #ceph — IRC канал
Cephalocon — ежегодная конференция сообщества
Практика:
Vagrant + VirtualBox: поднимите тестовый кластер на ноутбуке (cephadm работает в VM)
Rook — Ceph оператор для Kubernetes, хороший способ изучить интеграцию
Proxmox VE имеет встроенный Ceph — отличная песочница
Ceph — это не инструмент «поставил и забыл». Это живая система, требующая понимания и регулярного внимания. Но когда вы научитесь с ней работать — получаете petabyte-scale хранилище корпоративного уровня на обычном commodity железе. Это стоит вложенных усилий.
Во время технического собеседования в крупную компанию мне задали простой вопрос: что такое Load Average?
Формально ответить несложно — это «средняя загрузка системы за 1, 5 и 15 минут». Но если копнуть глубже, возникает ряд неудобных вопросов:
Что именно усредняется?
С какой частотой происходит измерение?
Какие процессы считаются «ожидающими ресурсы»?
Почему при кратковременных пиках мы не видим резких скачков?
Почему Load Average = 1 соответствует 100% загрузке одноядерной системы?
Если вас интересует не бытовое, а точное техническое понимание, разберёмся детально — с опорой на исходный код ядра Linux.
Что такое Load Average (LA)
В системах Linux и UNIX Load Average — это показатель среднего количества процессов:
находящихся в состоянии RUNNING (исполняются или готовы к выполнению),
находящихся в состоянии UNINTERRUPTIBLE (обычно ожидание I/O).
Три значения, которые показывает команда uptime, соответствуют окнам:
1 минута
5 минут
15 минут
Важно: Load Average — это не процент загрузки CPU.
Это среднее количество активных (или ожидающих) задач.
Для одноядерной системы:
LA = 1 → процессор полностью занят
LA < 1 → процессор простаивает часть времени
LA > 1 → есть очередь процессов
Где «подвох» в стандартном объяснении
1. Это не арифметическое среднее
Если бы LA считался как обычное среднее арифметическое, возникал бы вопрос о частоте дискретизации:
считаем каждую секунду?
каждые 10 мс?
раз в минуту?
Чем выше частота измерения — тем меньше получилось бы среднее значение.
Но в Linux используется экспоненциальное сглаживание, а не классическое среднее.
2. Кто такие «ожидающие ресурсы»?
Согласно исходному коду ядра Linux, учитываются процессы в состояниях:
TASK_RUNNING TASK_UNINTERRUPTIBLEТо есть:
задачи, выполняющиеся на CPU;
задачи, ожидающие завершения операций ввода-вывода (например, медленный диск или NFS).
Именно поэтому высокий Load Average может быть при низкой загрузке CPU — если система «застряла» на I/O.
Как именно считается Load Average в Linux
Реализация находится в ядре Linux (например, в версии 2.4 — timer.c и sched.h).
Ключевые факты:
Измерение происходит каждые 5 секунд
Используется фиксированная точка (fixed-point arithmetic)
Применяется формула экспоненциального затухания
Константы:
#define LOAD_FREQ (5*HZ) /* интервал 5 секунд */ #define EXP_1 1884 /* коэффициент для 1 минуты */ #define EXP_5 2014 #define EXP_15 2037Формула расчёта
В упрощённом виде:
Lnew=Lold⋅e−Δt/T+n⋅(1−e−Δt/T)L_{new} = L_{old} \cdot e^{-Δt/T} + n \cdot (1 - e^{-Δt/T})Lnew=Lold⋅e−Δt/T+n⋅(1−e−Δt/T)
где:
LLL — текущее значение Load Average
nnn — число активных задач
TTT — окно усреднения (1, 5, 15 минут)
Δt=5Δt = 5Δt=5 секунд
Это дискретная форма экспоненциального сглаживания.
Почему используется экспонента
Формула основана на законе экспоненциального распада:
dLdt=−1T(L−n)\frac{dL}{dt} = -\frac{1}{T}(L - n)dtdL=−T1(L−n)
Смысл:
если процессов больше текущего LA → показатель растёт
если меньше → показатель экспоненциально уменьшается
чем больше окно (15 минут), тем медленнее реакция
Это обеспечивает:
сглаживание кратковременных пиков
устойчивость к «шуму»
предсказуемую динамику
Почему не видно резких скачков?
Представим, что вы запустили 100 коротких процессов.
Логично ожидать, что LA резко взлетит.
Но этого не происходит, потому что:
измерение идёт раз в 5 секунд
используется экспоненциальное сглаживание
старые значения затухают постепенно
Экспонента выполняет роль фильтра низких частот.
Почему LA = 1 означает 100% загрузку одноядерной системы
При постоянном числе процессов nnn:
если n>Ln > Ln>L → LA растёт к n
если n<Ln < Ln<L → LA уменьшается к n
Если на одноядерной системе:
в каждый момент времени активен ровно 1 процесс,
очереди нет,
то система полностью загружена — и LA стабилизируется на 1.
Если LA > 1 — появляется очередь.
Важные нюансы
1. Load Average учитывает I/O
Если процессы ждут диск или NFS, LA растёт, даже если CPU простаивает.
2. На многоядерных системах
Если у вас 8 ядер:
LA = 8 → система полностью загружена
LA > 8 → есть очередь
3. Это не мгновенная метрика
LA показывает тренд, а не текущую загрузку.
Ограничения модели
Экспоненциальная модель предполагает:
плавное изменение нагрузки
отсутствие «жёстких» ограничений пропускной способности
В реальности же:
CPU имеет конечную пропускную способность
I/O может быть узким местом
высокие значения LA не всегда означают CPU-bound систему
Поэтому интерпретировать Load Average нужно вместе с:
top
htop
iostat
vmstat
Выводы
Load Average — это экспоненциально сглаженное среднее количества активных процессов.
Измерение происходит каждые 5 секунд.
Учитываются процессы в состояниях RUNNING и UNINTERRUPTIBLE.
Это не процент загрузки CPU.
Значение > числа ядер означает наличие очереди.
Главное:
Load Average — это математическая модель сглаживания нагрузки, а не прямой счётчик занятости процессора.
Введение
Корректный запуск процессов внутри контейнера — одна из ключевых тем при разработке Docker-образов. Формально всё описано в документации Docker, однако на практике регулярно возникают неоднозначные ситуации:
контейнер не останавливается корректно;
сигналы не доходят до приложения;
появляются zombie-процессы;
PID 1 ведёт себя неожиданно.
В этой статье разберём:
Разницу между ENTRYPOINT и CMD.
Отличие exec и shell форм.
Почему критически важно, какой процесс имеет PID 1.
Как правильно писать docker-entrypoint.sh.
Когда и зачем использовать tini.
Материал ориентирован на практическое применение и реальные сценарии.
1. ENTRYPOINT и CMD: фундаментальная разница
В Dockerfile существуют две директивы для запуска процессов:
ENTRYPOINT
CMD
Обе участвуют в формировании итоговой команды запуска контейнера, но выполняют разные роли.
Логическая модель
Можно представить их так:
ENTRYPOINT + CMD = финальная команда контейнераРекомендуемая практика
ENTRYPOINT — фиксированная команда (исполняемый файл или скрипт).
CMD — аргументы по умолчанию, которые можно переопределить.
Exec-форма и Shell-форма
Docker поддерживает два синтаксиса.
1️⃣ Exec-форма (рекомендуется)
ENTRYPOINT ["/bin/ping"] CMD ["it-lux.ru"]Особенности:
Не используется shell.
Нет подстановки переменных.
Процесс запускается напрямую.
Корректная обработка сигналов.
После сборки:
docker run pingВнутри контейнера выполнится:
/bin/ping it-lux.ruПереопределение аргументов:
docker run ping google.comТеперь выполнится:
/bin/ping google.comЭто правильная архитектура: один образ — разные параметры запуска.
2️⃣ Shell-форма (менее предпочтительна)
ENTRYPOINT ping it-lux.ruФактически Docker запустит:
/bin/sh -c "ping it-lux.ru"Минусы:
Появляется промежуточный shell.
Сигналы могут не дойти до целевого процесса.
PID 1 становится shell.
Shell-форма допустима, но требует понимания последствий.
2. Проблема PID 1
В Linux процесс с PID 1 — особый.
Особенности:
Он не имеет обработчиков сигналов по умолчанию.
Он ответственен за "усыновление" осиротевших процессов.
Он должен корректно обрабатывать SIGTERM.
Docker при остановке контейнера выполняет:
docker stop → отправляет SIGTERM → PID 1Если PID 1:
не обрабатывает сигнал,
не передаёт его дочерним процессам,
то контейнер завершится некорректно (force kill через SIGKILL спустя timeout).
3. Ошибка с docker-entrypoint.sh
Типичный пример:
FROM centos:7 COPY docker-entrypoint.sh /usr/bin ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]Содержимое:
#!/bin/bash ping ya.ruЧто происходит?
PID 1 — это:
/bin/bash /usr/bin/docker-entrypoint.shА ping — дочерний процесс.
При docker stop:
SIGTERM получает bash
bash может не передать сигнал дальше
ping зависает
появляются zombie-процессы
Это некорректная архитектура контейнера.
4. Правильное решение — exec
В bash существует встроенная команда exec.
Она:
заменяет текущий процесс
передаёт ему PID
не создаёт дополнительный уровень
Правильный вариант:
#!/bin/bash exec ping ya.ruТеперь:
PID 1 → pingКонтейнер завершится корректно.
5. Использование CMD внутри entrypoint
Более гибкий вариант:
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"] CMD ["ya.ru"]Скрипт:
#!/bin/bash # подготовительные действия set -- ping "$@" exec "$@"Разбор:
$@ — все аргументы контейнера.
set -- — формирует новую команду.
exec "$@" — запускает её как PID 1.
Запуск:
docker run ping google.comРезультат:
PID 1 → ping google.comЭто production-подход.
6. Когда одного exec недостаточно
Теперь усложним сценарий.
Допустим, запускается:
Jenkins
Apache
Zabbix server
Такие системы активно создают дочерние процессы.
Примеры:
Jenkins
Zabbix
Apache HTTP Server
Если дочерние процессы:
завершаются некорректно,
остаются "осиротевшими",
то PID 1 должен их "подчищать".
Но большинство приложений:
не реализуют init-поведение,
не умеют корректно reaping zombie-процессов.
7. Решение — tini
Здесь используется tini.
Минималистичный init для контейнеров.
Корректно проксирует сигналы.
Убирает zombie-процессы.
Работает как PID 1.
Название — это "init" наоборот.
Как подключить tini
Пример Dockerfile:
FROM debian:stable RUN apt-get update && apt-get install -y tini ENTRYPOINT ["/usr/bin/tini", "--"] CMD ["your-app"]Теперь:
PID 1 → tini PID 7 → your-appЧто делает tini:
Получает SIGTERM.
Передаёт сигнал дочернему процессу.
Reap'ит zombie-процессы.
Корректно завершает контейнер.
Это production best practice.
8. Почему bash ≠ tini
Bash как PID 1
tini как PID 1
Не проксирует сигналы корректно
Проксирует
Не предназначен как init
Предназначен
Может терять SIGTERM
Корректно передаёт
Не чистит zombie
Чистит
Это принципиально разные роли.
9. Итоговые рекомендации (Best Practices)
Используйте exec-форму всегда, когда возможно.
В docker-entrypoint.sh обязательно применяйте exec.
Разделяйте:
ENTRYPOINT — исполняемый файл
CMD — аргументы по умолчанию
Если приложение создаёт дочерние процессы — используйте tini.
Проверяйте, кто имеет PID 1:

docker exec -it container ps auxЗаключение
На простых примерах всё работает и без этих нюансов. Однако при усложнении логики контейнера:
появляются проблемы с остановкой,
теряются сигналы,
возникают zombie-процессы,
контейнер завершает работу некорректно.
Docker упрощает деплой, но не отменяет фундаментальные принципы работы процессов в Linux.
Понимание:
роли PID 1,
различий exec и shell,
корректного построения entrypoint,
необходимости tini
позволяет создавать production-ready Docker-образы, которые ведут себя предсказуемо и корректно в любой среде.
1. Введение
Анализ сигналов, распространяющихся в протяжённых средах (например, атмосфере или океане), осложняется наличием:
фоновых шумов;
квазирегулярных структурных неоднородностей;
случайных флуктуаций;
локальных аномалий, нарушающих равновесное состояние среды.
Классические методы линейной фильтрации ориентированы на подавление шума за счёт сглаживания сигнала. Однако при этом часто теряется информация о локальных скачках — именно тех особенностях, которые соответствуют аномальным неоднородностям.
Предлагаемый подход основан на нелинейной обработке дискретизированного сигнала, при которой:
периодические и квазирегулярные структуры сглаживаются;
локальные разовые перепады усиливаются;
обеспечивается высокая чувствительность к границам аномалий.
Метод устраняет противоречие между необходимостью подавления фона и сохранением (или усилением) диагностически значимых перепадов сигнала.
2. Цель работы
Представить результаты численного моделирования оригинального метода нелинейной фильтрации временной структуры сложных негармонических сигналов, предназначенного для выявления аномалий в протяжённых средах.
3. Научная новизна
Предложен алгоритм нелинейной фильтрации, позволяющий:
выявлять аномальные неоднородности в частично организованной структуре сигнала;
определять положение границ таких неоднородностей;
обеспечивать устойчивость к шумам и псевдорегулярным структурам.
Метод сочетает свойства медианных, рекурсивных, итеративных и робастных фильтров, однако обладает рядом принципиальных отличий.
4. Методология
4.1. Общая идея алгоритма
Пусть задан дискретный сигнал
S={s1,s2,...,sN}S = \{s_1, s_2, ..., s_N\}S={s1,s2,...,sN}
Алгоритм включает последовательность операций накопления, сравнения и логарифмического преобразования интегральных характеристик сигнала.
Основной принцип — сравнение поведения сигнала на симметричных участках трассы с использованием скользящих накопленных сумм.
4.2. Базовый алгоритм фильтрации
Этап 1. Деление текущего интервала
Текущий временной интервал регистрации сигнала делится пополам с шагом, равным шагу дискретизации.
Этап 2. Логарифм отношения
Для нарастающих интервалов вычисляется:
ln⁡(S1S2)\ln \left( \frac{S_1}{S_2} \right)ln(S2S1)
где S1S_1S1 и S2S_2S2 — интегральные значения сигнала на соответствующих полуинтервалах.
Шаг приращения равен удвоенному шагу дискретизации.
Этап 3. Нормировка
Полученный ряд умножается на отношение:
Nn\frac{N}{n}nN
где
NNN — общее число отсчётов,
nnn — число отсчётов в текущем интервале.
Этап 4. Выделение аномалии
Аномалия определяется как:
выраженный провал во временном ходе отфильтрованного сигнала;
чем уже и глубже провал — тем контрастнее неоднородность.
5. Формирование модельной структуры неоднородности
Для точного определения положения границ используется более детальная процедура.
5.1. Формирование двух накопительных последовательностей
Из исходных отсчётов формируются:
Последовательность типа A — накопление от начала к концу.
Последовательность типа B — накопление от конца к началу.
Шаг приращения соответствует требуемому разрешению.
5.2. Формирование дополнительных отсчётов
Для каждой накопительной величины:
для типа A формируется AD — сумма последующих отсчётов;
для типа B формируется BD — сумма предшествующих отсчётов.
5.3. Нормированное логарифмическое преобразование
Вычисляется:
ln⁡(min⁡(AD,A)max⁡(AD,A))\ln \left( \frac{\min(AD, A)}{\max(AD, A)} \right)ln(max(AD,A)min(AD,A))
Аналогично для пары B и BD.
Результат нормируется на число исходных отсчётов, формируя последовательности:
IAn,IBnIA_n, \quad IB_nIAn,IBn
Эти величины пропорциональны средним коэффициентам ослабления среды на соответствующих участках.
5.4. Поиск общей части Δ
Рассматриваются отношения:
IA1IBn,IA2IBn−1,…,IAnIB1\frac{IA_1}{IB_n}, \frac{IA_2}{IB_{n-1}}, \dots, \frac{IA_n}{IB_1}IBnIA1,IBn−1IA2,…,IB1IAn
при условии, что соответствующие участки имеют общую часть:
Δ=12 шага дискретизации\Delta = \frac{1}{2} \text{ шага дискретизации}Δ=21 шага дискретизации
Находится глобальный минимум:
IAk/IBn−k+1IA_k / IB_{n-k+1}IAk/IBn−k+1
5.5. Формирование основной цифровой последовательности
Основная последовательность строится:
из IAnIA_nIAn при n>kn > kn>k;
из IBnIB_nIBn при n≤kn \le kn≤k.
5.6. Определение границ аномалии
Максимальный перепад основной последовательности → ближняя граница.
Вторичный перепад → дальняя граница.
6. Численное моделирование
Проведено сравнение предложенной фильтрации с:
линейной фильтрацией (LINF),
медианной фильтрацией (MEDF).
Моделирование выполнялось для экспоненциально спадающего сигнала длиной 50 отсчётов с перепадом в центре выборки.
Использовались три отношения сигнал : перепад : шум:
SIG1 — 10:1:1
SIG2 — 10:1:5
SIG3 — 10:1:10
7. Критерий эффективности
Использовалось отношение дисперсий:
σфильтр2σисходный2\frac{\sigma^2_{\text{фильтр}}}{\sigma^2_{\text{исходный}}}σисходный2σфильтр2
Отдельно анализировались участки до и после перепада.
Для предложенной фильтрации (GRAN):
значения DISP1 и DISP2 существенно превышают 1;
это указывает на нелинейное усиление локального перепада;
чувствительность возрастает при увеличении шумовой компоненты.
Линейная и медианная фильтрации не обеспечивают сопоставимого выделения перепада при высоком уровне шума.
8. Сопоставление с известными методами
Предлагаемая фильтрация имеет общие черты со следующими методами:
8.1. Медианная фильтрация
подавление повторяющихся выбросов;
сохранение одиночных перепадов.
8.2. Рекурсивная фильтрация
прогнозирование гладкости;
зависимость от порядка фильтра.
8.3. Итеративная обработка
последовательное изменение порога дискриминации.
8.4. Робастные методы
слабая чувствительность к малым возмущениям входных данных.
9. Преимущества предложенного метода
Исключается усиление случайных мелких перепадов.
Порядок фильтрации автоматически возрастает с длиной интервала.
Используется адаптивный порог выявления перепадов.
Отсутствуют отрицательные значения сигнала.
Не требуется частотное подавление шумов.
Робастность возрастает от центра выборки к её краям.
Слабо зависит от длины выборки.
Прост в аппаратурной реализации.
Обеспечивает высокое быстродействие.
10. Выводы
Численное моделирование показало, что предложенный метод нелинейной фильтрации:
эффективно выделяет локальные перепады сигнала;
устойчив к шуму;
подавляет псевдорегулярную структуру;
превосходит линейные и медианные фильтры в условиях высокого уровня шума.
Метод особенно перспективен для анализа сигналов обратного рассеяния в протяжённых средах, включая атмосферные приложения.
11. Литература
Полканов Ю.А. Способ определения положения оптической неоднородности атмосферы. Авт. свид-во СССР №1448907, кл. G01W1/00, 1988.
Полканов Ю.А. Выявление аномальной неоднородности на фоне псевдорегулярной структуры сложного спадающего сигнала. Вестник БГУ, Серия 1, 1991.
Полканов Ю.А. Об одной возможности выделения аномальной неоднородности атмосферы (Метод нелинейной фильтрации). Оптика атмосферы и океана, 1992.
Сегодня сложно представить электронику без инфракрасных (ИК) технологий: пульты дистанционного управления, системы безопасности, промышленная автоматизация, медицинские приборы и робототехника — во всех этих решениях применяются ИК-приемники. Разберем подробно, что такое инфракрасный приемник, как он устроен, по какому принципу работает и как диагностировать его неисправности.
Что такое ИК-приемник
ИК-приемник (инфракрасный приемник) — это электронное устройство, предназначенное для приема и декодирования сигналов, передаваемых в виде инфракрасного излучения от источника (обычно ИК-светодиода).
Классический пример — пульт дистанционного управления телевизором или кондиционером. Передатчик формирует последовательность модулированных ИК-импульсов, а приемник:
Улавливает инфракрасное излучение.
Преобразует его в электрический сигнал.
Демодулирует и передает данные в управляющую схему устройства.
Где применяются инфракрасные приемники
1. Бытовая электроника
Пульты ДУ (телевизоры, аудиосистемы, кондиционеры).
Медиаплееры и приставки.
Ранее — передача данных в мобильных телефонах и ПК (до распространения Bluetooth и Wi-Fi, скорость достигала ~4 Мбит/с).
2. Освещение и автоматизация зданий
Датчики движения в подъездах, коридорах, санузлах.
Автоматическое включение/выключение освещения.
Энергосберегающие системы «умного дома».
3. Системы безопасности
Охранные датчики движения.
Инфракрасные барьеры периметра.
Контроль несанкционированного доступа.
4. Бесконтактные устройства
Диспенсеры мыла и воды.
Сенсорные смесители.
Бесконтактные мусорные контейнеры.
5. Медицинская техника и носимая электроника
Бесконтактные термометры.
Пульсометры и фитнес-трекеры (анализ кровотока по отраженному ИК-сигналу).
Применение ИК-приемников в промышленности
Простота, надежность и низкая стоимость делают ИК-датчики востребованными в промышленной среде.
Конвейерные линии
Детекция присутствия объекта.
Контроль положения детали.
Синхронизация этапов сборки и упаковки.
Системы безопасности
Инфракрасные барьеры.
Контроль складов и производственных зон.
Защита оборудования.
Контроль качества
Инфракрасная спектроскопия для анализа состава материалов.
Выявление дефектов.
Контроль смесей в химическом и пищевом производстве.
Энергетика и металлургия
Пирометры и тепловизоры.
Контроль температуры печей, трубопроводов, реакторов.
Предотвращение перегрева и аварий.
Логистика
Системы учета и сортировки.
Взаимодействие со сканерами штрихкодов и RFID.
Машиностроение и робототехника
Обнаружение препятствий.
Навигация автономных систем.
Системы точного позиционирования.
Принцип работы инфракрасного приемника
ИК-приемники работают в диапазоне длин волн 700 нм – 1 мм, однако в бытовых и промышленных системах чаще используется диапазон 850–950 нм.
Основные этапы работы:
Прием излучения
Чувствительный элемент (фотодиод или фототранзистор) реагирует на ИК-свет.
Преобразование в электрический сигнал
При попадании излучения генерируется ток.
Фильтрация
Оптический фильтр подавляет помехи (солнечный свет, лампы).
Модуляция и демодуляция
Сигнал передается импульсами (обычно 36–38 кГц).
Приемник выделяет именно эту частоту, игнорируя фон.
Передача в контроллер
Демодулированный сигнал поступает в микроконтроллер.
Устройство ИК-приемника
Типовой ИК-модуль включает:
фотодиод;
усилитель;
полосовой фильтр;
демодулятор;
формирователь цифрового сигнала.
Основные характеристики
Параметр
Описание
Длина волны
850–950 нм (типично)
Частота модуляции
36–38 кГц (иногда 56 кГц)
Чувствительность
Минимальная мощность сигнала (мкВт)
Угол обзора
30–90°
Дальность
Зависит от мощности передатчика
Время отклика
От миллисекунд
Тип выхода
Аналоговый или цифровой
Пример: Vishay TSOP4456
Рассмотрим популярный модуль — TSOP4456 компании Vishay Intertechnology.
Основные параметры:
Питание: 2,5–5,5 В
Частота модуляции: 56 кГц
Потребляемый ток: ~0,4 мА
Угол обзора: ~45°
Дальность приема: до 45 м
Температура: –25…+85 °C
Выход: цифровой (активный низкий уровень)
Модуль применяется в системах дистанционного управления, совместим с протоколами RCA и другими распространенными стандартами.
Факторы, влияющие на работу ИК-приемника
В промышленной эксплуатации необходимо учитывать:
запыленность;
задымленность;
влажность;
вибрации;
температурные перепады;
электромагнитные помехи.
В сложных условиях применяются модули в герметичных корпусах с промышленным исполнением.
Устранение неисправностей ИК-приемника
Диагностика проводится поэтапно.
1. Проверка передатчика
Навести пульт на камеру смартфона и нажать кнопку — мигающий свет указывает на исправность ИК-диода.
2. Осмотр приемника
Проверить загрязнение линзы.
Осмотреть корпус на механические повреждения.
3. Проверка питания
Измерить напряжение мультиметром.
Проверить цепь питания и предохранители.
4. Проверка пайки
Осмотреть контакты.
При необходимости перепаять соединения.
5. Проверка сигнала
Использовать осциллограф.
Убедиться в наличии корректных импульсов.
6. Диагностика контроллера
Если приемник исправен, проблема может быть в микроконтроллере или основной плате.
Преимущества ИК-приемников
Низкая стоимость
Простота интеграции
Энергоэффективность
Высокая помехоустойчивость (при модуляции)
Надежность
Широкий диапазон рабочих температур
Несмотря на развитие Bluetooth, Wi-Fi и других беспроводных технологий, ИК-приемники остаются экономически и технически оправданным решением во множестве задач.

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.