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.

IThub

Administrators
  • Joined

  • Last visited

Everything posted by IThub

  1. Интернет вещей (IoT, Internet of Things) в промышленной среде получил отдельное направление — промышленный интернет вещей (IIoT, Industrial Internet of Things). Это комплекс аппаратных и программных решений, который объединяет датчики, оборудование, контроллеры и ИТ-системы в единую цифровую среду с возможностью обмена данными в режиме, близком к реальному времени. Внедрение IIoT позволяет: осуществлять непрерывный мониторинг оборудования; переходить от планового к прогнозирующему обслуживанию (Predictive Maintenance); снижать внеплановые простои; оптимизировать энергопотребление; повышать прозрачность производственных процессов; интегрировать производство с ERP- и MES-системами. Что такое IIoT и какие задачи он решаетПромышленный интернет вещей (IIoT) — это совокупность технологий, обеспечивающих подключение оборудования и датчиков к сети для сбора, передачи, обработки и анализа данных. Ключевая задача IIoT — переход от реактивной модели эксплуатации оборудования («сломалось — ремонтируем») к проактивной модели управления активами. Проблема традиционного подходаНа предприятии с большим количеством сложных машин: оборудование обслуживается по регламенту, а не по фактическому состоянию; поломки часто возникают внезапно; возникают внеплановые простои; требуется держать избыточный склад запчастей; увеличиваются эксплуатационные расходы (OPEX). Как IIoT меняет модель управленияКаждая единица оборудования оснащается датчиками, которые фиксируют: температуру, вибрацию, давление, ток и напряжение, скорость вращения, другие технологические параметры. Данные передаются на локальные серверы или в облачную инфраструктуру, где автоматически анализируются. При отклонении параметров от нормативных значений система: уведомляет оператора; формирует заявку на обслуживание; может инициировать автоматическое регулирование. Это позволяет: предотвратить аварии, сократить downtime, снизить стоимость владения оборудованием (TCO). Прогнозирующее обслуживание: практический примерОдин из наиболее показательных кейсов — авиационная промышленность. Производители двигателей устанавливают независимые каналы связи, предназначенные исключительно для передачи телеметрии наземным службам. На основе массивов данных строятся аналитические модели, которые прогнозируют: остаточный ресурс узлов; вероятность отказа; оптимальное время замены деталей. В промышленности аналогичный подход применяет, например, Siemens. На производственных площадках компания внедряет системы датчиков, которые контролируют: механические параметры станков, энергопотребление, режимы загрузки линий. Если линия простаивает, система автоматически снижает энергопотребление, что напрямую сокращает операционные затраты. История развития IoT и становление IIoTКонцептуальные предпосылки IoT появились еще в 1980-х годах с развитием сетевых технологий. Термин «Internet of Things» в 1999 году предложил Kevin Ashton. Он рассматривал применение RFID-меток для отслеживания товаров в цепочках поставок. Активная фаза развития IoT началась в 2010-х годах благодаря: удешевлению сенсоров; распространению беспроводной связи; развитию облачных платформ; росту вычислительных мощностей. В промышленности это привело к формированию концепции IIoT и «умных фабрик» (Smart Factory) в рамках парадигмы Industry 4.0. Архитектура IIoT: уровни и требованияТиповая архитектура IIoT представляет собой многоуровневую систему. 1. Уровень сенсоров и устройств (Device Layer)Физический уровень включает: датчики, исполнительные механизмы, контроллеры, производственное оборудование. Критические требования: устойчивость к температуре, пыли, влажности; виброустойчивость; низкое энергопотребление; промышленная степень защиты (IP, EMI/EMC). 2. Сеть передачи данных (Communication Layer)Обеспечивает передачу информации между устройствами, edge-узлами и центрами обработки. Используются: Ethernet; Wi-Fi; 4G/5G; LPWAN; промышленные протоколы. Требования: высокая пропускная способность; минимальная задержка; отказоустойчивость; защищенность передачи данных. Оборудование промышленного уровня поставляют такие компании, как: Schneider Electric Allied Telesis Moxa Hirschmann Automation and Control B&R Выбор оборудования должен учитывать реальные условия эксплуатации — перепады температур, влажность, вибрации и электромагнитные помехи. 3. Граничные устройства (Edge Layer)Edge-устройства обрабатывают данные непосредственно на объекте. Их задачи: локальная фильтрация и агрегация данных; анализ в реальном времени; снижение нагрузки на облако; обеспечение автономной работы при потере связи. Обычно поддерживаются протоколы MQTT, OPC UA и другие индустриальные стандарты. Важно разграничивать: Edge — анализ и предварительная обработка данных; PLC/контроллеры — управление оборудованием. 4. Уровень управления (Control Layer)Включает: PLC; SCADA-системы; MES; интеграцию с ERP. Системы класса SCADA обеспечивают диспетчеризацию и визуализацию процессов, а MES — управление производственными операциями. 5. Обработка данных и аналитика (Data & Analytics Layer)На этом уровне используются: Big Data; машинное обучение; предиктивная аналитика; цифровые двойники (Digital Twin). Цель — выявление закономерностей, оптимизация процессов и стратегическое управление активами. Применение IIoT в различных отрасляхПромышленность и машиностроениемониторинг станков; оптимизация производственных линий; снижение брака; предиктивное обслуживание. Особое значение имеют edge-вычисления и отказоустойчивые сети. Логистика и транспортотслеживание транспорта в реальном времени; контроль состояния грузов; автоматическое пополнение запасов. Ключевую роль играют мобильные сети и устойчивость связи при перемещении между зонами покрытия. Энергетикамониторинг генерации и распределения; контроль подстанций; управление распределенными энергоресурсами; интеграция ВИЭ. Системы должны быть масштабируемыми и устойчивыми к тяжелым условиям эксплуатации. Сельское хозяйствомониторинг влажности почвы; контроль микроклимата; управление сельхозтехникой; автоматизация полива. Основной акцент — энергоэффективные беспроводные технологии и автономность. Преимущества внедрения IIoTВнедрение промышленного интернета вещей позволяет: снизить внеплановые простои; сократить издержки на обслуживание; уменьшить энергопотребление; повысить прозрачность процессов; улучшить управляемость цепочек поставок; повысить общую операционную эффективность предприятия. IIoT становится фундаментом цифровой трансформации промышленности и ключевым элементом конкурентоспособности в условиях Industry 4.0.
  2. Сегодня сложно представить электронику без инфракрасных (ИК) технологий: пульты дистанционного управления, системы безопасности, промышленная автоматизация, медицинские приборы и робототехника — во всех этих решениях применяются ИК-приемники. Разберем подробно, что такое инфракрасный приемник, как он устроен, по какому принципу работает и как диагностировать его неисправности. Что такое ИК-приемникИК-приемник (инфракрасный приемник) — это электронное устройство, предназначенное для приема и декодирования сигналов, передаваемых в виде инфракрасного излучения от источника (обычно ИК-светодиода). Классический пример — пульт дистанционного управления телевизором или кондиционером. Передатчик формирует последовательность модулированных ИК-импульсов, а приемник: Улавливает инфракрасное излучение. Преобразует его в электрический сигнал. Демодулирует и передает данные в управляющую схему устройства. Где применяются инфракрасные приемники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 и других беспроводных технологий, ИК-приемники остаются экономически и технически оправданным решением во множестве задач.
  3. 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.
  4. ВведениеКорректный запуск процессов внутри контейнера — одна из ключевых тем при разработке 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 ≠ tiniBash как 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-образы, которые ведут себя предсказуемо и корректно в любой среде.
  5. Всем привет! Дорогие друзья кто мучается с прошивкой и софтом для полок СХД HPE 3par 7000, 7400, 8000, 8400 может и к другим подходиит, хз.. Если у вас есть вопросы по данным СХД или вы хотите обсудить проблемы HPE 3par заходите в наш телеграмм канал В общем нашел я оромный сборник с прошивками и софтом, ниже список сделаю, он не полный. HPE 3PAR INFORM OS 3 HPE Service Processor 4, 5 образы ISO HPE_3PAR_INFORM_OS 3.2.1 HPE_3PAR_INFORM_OS 3.2.2 MU4 HPE_3PAR_INFORM_OS 3.2.2 MU6 HPE_3PAR_INFORM_OS 3.3.1 MU1 HPE_3PAR_INFORM_OS 3.3.1 MU2 HPE_3PAR_INFORM_OS 3.3.1 MU3 HPE_3PAR_INFORM_OS 3.3.1 MU5 HPE_3PAR_INFORM_OS 3.3.2 MU1 Firmware Proliant SP 4 SP 5 SSMC Witness 3PAR_Management_Console_4.7.3_QR482-11188.iso 3PAR_StoreServ_NODE_Maintenance.pptx.pptx 3PAR_SW_Upgrade_Tool_U019_QR482-11400.iso HPE_3PAR_StoreServe_Management_Console_3.5_QR482-11398.iso HPE_3PAR_SW_Upgrade_Tool_024_QR482-11470.iso HPE_3PAR_SW_Upgrade_Tool_025_QR482-11477.iso HPE_3PAR_SW_Upgrade_Tool_026_QR482-11489.iso HPE_3PAR_SW_Upgrade_Tool_029_QR482-11507.iso HPE_3PAR_SW_Upgrade_Tool_030_QR482-11517.iso HPE_3PAR_SW_Upgrade_Tool_031_QR482-11520.iso ВОТ ЭТА ССЫЛКА
  6. Во время технического собеседования в крупную компанию мне задали простой вопрос: что такое 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 — это математическая модель сглаживания нагрузки, а не прямой счётчик занятости процессора.
  7. Есть команды, которые надо вводить с холодной головой и полным осознанием последствий. rm -rf / — очевидный пример. Но среди разработчиков и DevOps есть своя версия этой русской рулетки — git push --force. Я работал тогда в продуктовой компании — делали SaaS-платформу для управления проектами. Небольшая команда, человек двенадцать, хороший продукт, живые клиенты, нормальный процесс разработки. Мы использовали GitHub, feature-ветки, pull requests, code review — всё как у взрослых. Репозиторий был защищён: ветка main заблокирована от прямых пушей, обязательный PR с одобрением от двух ревьюеров. Всё серьёзно. Но была одна маленькая деталь, которую мы упустили: ветка develop — наша основная рабочая ветка, куда мерджились все фичи перед выходом в main — была защищена только от обычного push. Не от push --force. Это упущение жило полтора года. До того утра. Антон — старший разработчик, умный, опытный, пять лет в команде — в среду утром пришёл на работу в слегка раздражённом состоянии. Накануне вечером работал из дома, что-то переделывал в своей feature-ветке, несколько раз rebase'ил на свежий develop, история коммитов запуталась. Он хотел сделать красиво — squash коммиты, причесать историю, залить обратно. Он сделал git rebase -i HEAD~15. Причесал. Сделал git push origin feature/my-branch. Получил ошибку — ветка расходилась с remote из-за rebase. Это нормально. Написал git push --force origin feature/my-branch. И тут автодополнение в его терминале сыграло злую шутку. Он не заметил. Нажал Enter. Терминал отработал молниеносно. git push --force origin develop Ветка develop теперь содержала только его пятнадцать причёсанных коммитов. Полгода работы двенадцати разработчиков — восемьсот с лишним коммитов, десятки смёрджённых feature-веток — перестали существовать в remote. Первым заметил Кирилл — он в это время делал git pull origin develop и увидел странное сообщение о том, что его локальная ветка «впереди» remote на 847 коммитов. Написал в слак: «ребят, что-то странное с develop». Я в это время пил кофе. Зашёл в GitHub. Посмотрел на историю develop. Увидел пятнадцать коммитов за подписью Антона. Поставил кружку. Написал в общий чат: «стоп, никто не делайте git pull и не трогайте develop». Антон в этот момент только что дошёл до своего рабочего места — потому что через тридцать секунд в чате появилось его сообщение: «ребята, кажется, я что-то сломал». Восстановление заняло примерно двадцать минут и было почти триумфальным. Git — распределённая система. Это означает, что у каждого разработчика в локальном репозитории была актуальная копия develop на момент последнего git fetch. У Кирилла — который как раз делал git pull в момент инцидента — локальная ветка содержала все 847 коммитов. # На машине Кирилла: git log origin/develop..develop --oneline | wc -l # 847 # Пришлось временно снять все защиты с ветки в GitHub # и залить обратно правильную историю git push --force origin develop Через двадцать минут develop снова содержал все 847 коммитов. Все локальные репозитории сделали git fetch. Ничего не потерялось. Антон написал мне в личку: «Максим, я понимаю что случилось. Хочу объяснить». Мы созвонились. Он рассказал про автодополнение, про то, как перепутал ветки. Голос у него был как у человека, который ещё не понял, уволен он или нет. Я сказал ему три вещи. Первое: никто ничего не потерял, всё восстановлено, все живы. Второе: это системная ошибка, не личная — защита ветки была настроена неправильно, и это моя ответственность как DevOps. Третье: мы немедленно это исправим. В тот же день мы включили защиту от force push на develop. Добавили в onboarding раздел «Никогда не используй git push --force без явного указания feature-ветки». Везде заменили --force на --force-with-lease — он проверяет что remote не изменился с момента вашего последнего fetch, и отказывает если кто-то уже запушил. Антон остался в команде. Ещё через полгода стал тимлидом. Самый важный вывод из этой истории не технический. Технический прост: защищайте все ветки от force push, используйте --force-with-lease, настройте алиасы. Важный вывод другой: реакция команды на инцидент определяет культуру команды. Можно было устроить показательную порку. Вместо этого мы исправили систему — и получили разработчика, который с тех пор параноидально аккуратен с git и научил этому ещё троих новых. Ошибки надо исправлять в системе. Не в людях. А git push --force без явного указания ветки — это как ходить с заряженным пистолетом без предохранителя. Рано или поздно что-то нажмётся не то.
  8. Эта история не о катастрофе — она о тех моментах, когда система работает именно так, как ты настроил, но совсем не так, как ты хотел. Мы запускали новый высоконагруженный сервис — рекомендательный движок. Перед запуском нужно было провести нагрузочный тест: убедиться что сервис держит планируемые 500 rps. Я накануне настроил nginx с rate limiting: 100 rps с одного IP, burst 200. Это защита от DDoS. Всё правильно, всё продуманно. На следующий день Вася из QA запустил нагрузочный тест с помощью k6 с офисного сервера. Офисный сервер имеет один внешний IP-адрес. Тест начался. Вася смотрел на метрики k6 — ошибки сыпались сразу: 503 Service Unavailable. Он написал мне: «Максим, сервис не выдерживает нагрузку, уже при 150 rps всё падает». Я зашёл в логи nginx. Увидел километры строк: [error] limiting requests, excess: 102.840 by zone "api_limit" Наш офисный сервер, с которого шёл тест, получил rate limit — и все 500 запросов в секунду, начиная со 101-го получали 503. Сервис при этом работал абсолютно нормально — его никто по-настоящему не нагружал. Я минуту просто смотрел в экран. Потом засмеялся — впервые за долгое время по-настоящему засмеялся в рабочее время. Мы добавили IP офисного нагрузочного сервера в whitelist. Провели нормальный тест. Сервис держал 800 rps без проблем. Все были довольны. Но главный урок этой истории не технический. Главный урок — это то, что перед тем как диагностировать проблему, надо убедиться что смотришь в нужное место. Вася видел ошибки и думал что сервис не справляется. Я видел ошибки и знал что это rate limit. Разница — в том, что смотрели на разные части системы. Коммуникация между QA и DevOps о том, с каких IP будет идти нагрузочный тест — теперь обязательный пункт в чеклисте перед любым нагрузочным тестированием. На ithub.uno есть хорошая статья про то, как правильно организовать этот процесс — рекомендую.
  9. Есть комментарии в коде, которые являются либо руководством к действию, либо тихим криком о помощи. Комментарии с «TODO» — это обещания, которые редко выполняются. Денис, бэкенд-разработчик с которым я пересекался на одном проекте, рассказал мне эту историю как предупреждение. История произошла на его предыдущем месте работы. Был endpoint /api/debug/users — он возвращал список всех пользователей с email, именами и датами регистрации. Без всякой авторизации. Создан во время разработки, чтобы фронтенд-разработчик мог быстро проверять данные. В коде стоял комментарий: # TODO: УБРАТЬ ПЕРЕД ДЕПЛОЕМ В ПРОД!!! # Только для разработки, не для продакшна @app.route('/api/debug/users') def debug_users(): users = User.query.all() return jsonify([u.to_dict() for u in users]) Три восклицательных знака. Заглавные буквы. Всё честно. Разработчик сделал деплой. TODO не убрал — дедлайн, «потом». Потом не наступило. Endpoint жил в проде тихо и незаметно два года. Через два года при проведении security audit penetration tester обнаружил его за тридцать секунд работы с Burp Suite. База данных на тот момент содержала информацию о 340,000 пользователей. Всё это время данные были доступны любому, кто знал URL. Конца истории Денис не знает — его к тому времени уже не было в той компании. Знает только что был большой скандал и несколько уволенных. В нашей команде после этой истории появился CI-шаг: grep по всему коду на паттерны TODO.*прод, TODO.*prod, REMOVE BEFORE, DEBUG ONLY. Если находит — пайплайн падает с ошибкой. Работает без единого ложного срабатывания уже полтора года.
  10. Самый коварный вид отказа систем — когда всё выглядит как работает, но не работает. Бэкап-система в этом смысле особенно опасна: вы никогда не проверяете её по-настоящему, пока не нужно восстановиться. Мой коллега Паша — педантичный и аккуратный инженер — настраивал резервное копирование для CRM-системы. Написал скрипт на bash: каждую ночь mysqldump, gzip, upload на S3. Всё логируется. При успехе в Slack приходит уведомление «Backup completed: 2.3GB». Скрипт работал восемь месяцев. Каждую ночь в Slack приходило: «Backup completed: 2.3GB». Красиво. Надёжно. Профессионально. Потом упал продакшн-сервер. Физически — сгорел диск. Паша пошёл восстанавливаться из бэкапа. Достал последний архив с S3. Распаковал. Открыл. Внутри был SQL-дамп базы данных information_schema. Не crm_production. А information_schema — системная база MySQL с метаданными: список таблиц, колонок, индексов. Никаких реальных данных. Паша открыл скрипт. Нашёл строку: mysqldump -u backup -p$PASS $DATABASE | gzip > backup.sql.gz Переменная $DATABASE. Посмотрел где она определяется: DATABASE=${DB_NAME:-information_schema} Переменная DB_NAME должна была передаваться через environment. Но при настройке cron-job он забыл добавить эту переменную. Cron запускал скрипт без переменной — скрипт использовал дефолтное значение information_schema — дамп создавался, весил 2.3GB (много метаданных за восемь месяцев), улетал на S3. Полное видимое благополучие. Восемь месяцев данных CRM были потеряны безвозвратно. После этого у нас появилось правило: бэкап-скрипт обязан проверять содержимое архива после создания. Не просто «файл существует» — а «внутри есть CREATE TABLE для нужных таблиц, INSERT строк больше нуля». И раз в месяц — тестовое восстановление в отдельную базу с проверкой количества записей. Если вы сейчас читаете это и у вас есть бэкап-система, которую вы ни разу не проверяли восстановлением — сделайте это сегодня. Не завтра. Сегодня.
  11. Это история о том, почему production дашборды должны быть красного цвета, а staging — зелёного. И почему вкладки браузера надо называть. Пятница. Последний рабочий день перед длинными майскими. Я чистил старый staging кластер Kubernetes — там скопился мусор за несколько месяцев. Namespace за namespace, удаляю deployment'ы, PVC, сервисы. Всё идёт хорошо. В соседней вкладке открыт продакшн дашборд — краем глаза поглядываю на метрики. Не помню точно как это случилось — кажется, я переключился между вкладками не обратив внимания. Увидел список namespace в дашборде. Увидел namespace с именем legacy-services — его я и собирался удалить в staging. Нажал Delete. Подтвердил (да, там был confirm dialog — и я его подтвердил, потому что только что двадцать раз делал то же самое). Через тридцать секунд в слаке началось: #alerts: CRITICAL: payment-service down #alerts: CRITICAL: auth-service down #alerts: CRITICAL: user-service down Я перевёл взгляд на адресную строку. Там был URL продакшн кластера. legacy-services в продакшне — это было название, которое выбрал кто-то три года назад. В нём жило восемь критических сервисов. Дальше начался самый быстрый деплой в моей карьере. ArgoCD хранил все манифесты, кластер был жив — я удалил только namespace с содержимым, но не сам кластер. Запустил sync для всех приложений через ArgoCD. Kubernetes начал поднимать поды. Stateless сервисы встали за две-три минуты. Payment-service — за пять, потому что у него была initContainer-миграция. Auth-service — за четыре. Общее время даунтайма: семь минут двадцать секунд. Что изменилось: все дашборды теперь имеют цветовую маркировку — продакшн красным, staging жёлтым, dev зелёным. Это реализовано через плагин kubie — при переключении в prod-контекст терминал меняет цвет prompt на красный. И главное — удаление namespace теперь требует ввода имени namespace вручную. Никаких кнопок «Delete» без явного подтверждения текстом.
  12. Есть баги, которые тихо портят данные и обнаруживаются через месяцы. А есть такие, которые существуют годами, всех раздражают, но никто не может их воспроизвести — потому что они зависят от часового пояса. Та система занималась планированием задач. Пользователь создаёт задачу на «завтра в 10:00» — система её выполняет в нужное время. Казалось бы, простейшая логика. Жалобы начались в марте. Клиенты из Екатеринбурга писали: «задачи выполняются не вовремя, иногда с опозданием на два часа». Я смотрел на логи — по нашим данным задачи выполнялись точно в срок. Поддержка закрывала тикеты: «не воспроизводится». Клиенты злились. Я взялся за это в начале апреля. Провёл два дня, читая код планировщика. Нашёл несколько мест где работа с временем выглядела подозрительно, отрефакторил — баг остался. Третий день — ревью всех datetime операций в системе. Ничего. На четвёртый день Игорь из поддержки сказал фразу, которая сразу всё объяснила: — Слушай, я заметил, что жалуются только пользователи из Екатеринбурга, Омска и вот этих городов... Он показал список. Я посмотрел на карту. Это были города в часовых поясах UTC+5 и восточнее. Зашёл на продакшн-сервер. Ввёл date. Увидел: Thu Apr 4 14:23:11 UTC 2024. Сервер работал в UTC. Это нормально. Проблема была в другом: когда пользователь из Екатеринбурга (UTC+5) создавал задачу на «завтра 10:00» через браузер, фронтенд отправлял строку 2024-04-05 10:00:00 — без timezone offset. Backend принимал её как UTC и сохранял в базу. Потом выполнял задачу в 10:00 UTC — что для пользователя в UTC+5 было 15:00 по местному времени. Пять часов разницы. Это проявилось только когда начали приходить пользователи из регионов — потому что первые клиенты и команда разработки были из Москвы (UTC+3), и у них разница была три часа, а не пять. Фикс: фронтенд теперь всегда отправляет datetime с timezone offset. Backend парсит только aware datetime объекты. На Хабре есть статья «Никогда не используйте naive datetime» — я её знал. Просто не я писал тот фрагмент. Теперь у нас есть линтер-правило, которое не даёт мержить код с naive datetime объектами.
  13. Автоматизация — это прекрасно. Автоматизация без ограничений — это финансовая катастрофа. Я усвоил этот урок очень конкретным способом: через счёт от AWS на $23,000 за четыре ночных часа. Мы настраивали горизонтальный автоскейлинг для API сервиса. Логика была простая: если CPU выше 70% — добавляем инстансы. Работало замечательно в рабочие часы. Что мы не предусмотрели — верхний лимит. Мы установили minReplicas: 2 и забыли про maxReplicas. В Kubernetes HPA это означает «масштабируй сколько нужно». В 2:47 ночи наш сервис получил DDoS-атаку. Не особо сложную — просто поток запросов, каждый из которых немного нагружал CPU. Автоскейлер увидел рост CPU и начал добавлять поды. Поды поднимались, нагрузка на каждый снижалась — но общий поток атаки оставался постоянным. Автоскейлер видел всё ещё высокую нагрузку и добавлял ещё. И ещё. В 4:15 у нас работало 847 инстансов одного сервиса. Нода-группа в AWS автоматически масштабировала EC2 — тоже без ограничений. Именно в этот момент сработал billing alert и разбудил меня. Я зашёл в AWS console полусонный. Увидел цифру. Проснулся мгновенно. Мы остановили атаку через WAF (пришлось поднять и настроить с нуля, потому что «руки не доходили» раньше — за десять минут). Потом убили лишние инстансы. Написали в AWS поддержку — они вернули около $18,000 как «goodwill credit», потому что это был явно аномальный spike. $5,000 мы всё же заплатили. Что изменили: maxReplicas в каждом HPA, budget alerts с автоматическим отключением при превышении, WAF с базовыми rate-limit правилами — теперь это первое, что поднимается для нового сервиса. И чеклист «Перед включением автоскейлинга», который мы опубликовали в нашей вики и в комьюнити на ithub.uno.
  14. Я долго не понимал, почему Kubernetes такой педантичный. Зачем все эти liveness probes, resource limits, PodDisruptionBudget — когда можно просто запустить контейнер и пусть работает? Потом был один день, который изменил моё отношение радикально. Мы деплоили крупное обновление — новая версия API с переработанной системой авторизации. Дата релиза была согласована с бизнесом, пресс-релиз готов, маркетинг ждёт. Всё тщательно проверено на стейджинге. Я жму deploy. Kubernetes начинает rolling update. Первые поды поднимаются — и тут Kubernetes останавливает деплой. Просто стоп. Ни один новый под не создаётся, старые не удаляются. Открываю kubectl describe pod — там написано: Readiness probe failed. Злюсь. Открываю логи пода. Вижу ошибку подключения к базе данных. Думаю: ну и что, это временная ошибка при старте, он бы сам восстановился. Хочу вручную форсировать деплой. Но что-то заставляет меня сначала проверить само соединение с базой. Открываю dashboard PostgreSQL — и вижу, что на новой версии приложения миграция схемы прошла неправильно. Один из индексов создался с ошибкой, из-за чего конкретный запрос в /api/v2/auth/check — тот самый, который проверяет readiness probe — возвращал 500. Если бы Kubernetes не остановил деплой, то старые поды с рабочей авторизацией были бы убиты, а новые — со сломанной — встали бы вместо них. Все пользователи получили бы 500 при попытке войти. Прямо в день анонса. Kubernetes оказался умнее меня. Его педантичность — которая меня так раздражала — спасла релиз. Мы откатили миграцию, исправили скрипт, прогнали ещё раз на стейджинге, задержали деплой на два часа. Бизнес поворчал — потом сказал спасибо, когда я объяснил альтернативу. С того дня я стал большим фанатом readiness probes. Не просто /healthz с ответом 200 — а настоящая проверка: соединение с базой, доступность зависимостей, корректность конфигурации.
  15. Это история о том, как можно делать всё правильно — и всё равно облажаться. Потому что правильные действия, направленные не туда — это хуже бездействия. Два года назад мы запускали новый микросервис — агрегатор данных для аналитики. Я настроил мониторинг: Prometheus, Grafana, alertmanager, всё по классике. Дашборд выглядел прекрасно. Зелёный. Живой. Метрики бежали в реальном времени. Через неделю аналитики начали жаловаться: данные в отчётах иногда выглядят странно, какие-то пропуски. Я смотрел на дашборд — сервис работает, ошибок нет, очередь обрабатывается. Ещё через неделю жалобы участились. Я снова смотрел на мониторинг. Снова — всё хорошо. Начал думать, что проблема в данных источника. На четырнадцатый день Саша из аналитики подошла ко мне с конкретным примером: вот событие, которое должно было попасть в базу вчера в 14:32 — его нет. Вот ещё пять таких событий за последние две недели. Я зашёл непосредственно на сервер, посмотрел логи — и увидел сотни ошибок коннекта к базе данных. Каждую минуту. Все последние две недели. Но мониторинг показывал зелёный! Через десять минут я нашёл причину. При настройке мониторинга я указал IP-адрес сервера вручную. Потом — за день до запуска — инфраструктурная команда переехала на новые машины и IP поменялся. Я обновил конфиг сервиса, но забыл обновить конфиг Prometheus. Prometheus две недели радостно скрейпил метрики другого сервера, которому достался старый IP. Все эти две недели я смотрел на графики совершенно нормально работающего чужого сервера. Пока наш тихо терял данные. Пропущенные события восстановить не удалось. После этого я перешёл на service discovery в Prometheus — никаких статических IP. Только DNS-имена или автоматическое обнаружение. И добавил тест: alertmanager должен прислать тестовый алерт при старте — чтобы убедиться, что нотификации реально доходят.
  16. Есть категория менеджеров, которые искренне верят, что перезагрузка решает все проблемы. Эту веру они несут через годы опыта работы с Windows на домашнем компьютере. Столкновение этой веры с реальностью продакшн-сервера — зрелище одновременно трагическое и поучительное. Эту историю рассказал мне Антон — DevOps в среднем онлайн-ретейлере — в баре, после второго бокала, с видом человека, который прошёл терапию, но ещё не полностью. Была пятница, около шести вечера. Их основной сервер начал подтормаживать — latency выросла раза в три. Антон уже нашёл проблему: утечка соединений в пуле базы данных, накопившаяся за неделю. Требовалось примерно двадцать минут на аккуратный фикс без рестарта. Тут появился продакт-менеджер Геннадий Витальевич. — Антон, у нас тормозит. Долго ещё? У меня через час встреча с клиентом. — Геннадий Витальевич, я нашёл проблему, фикс займёт минут двадцать, перезагрузка не нужна— — Просто перезагрузите сервер. Всегда помогает. — Нет, правда, в данном случае лучше не надо, потому что после перезагрузки будет несколько минут простоя— — Антон. Перезагрузите. Сервер. Антон перезагрузил сервер. Что он не знал — и что Геннадий Витальевич знать не мог — так это то, что накануне ночью обновился GRUB через автоматические обновления Ubuntu. Обновление прошло нормально, но файл конфигурации GRUB получил неверный UUID для корневого раздела. Система прекрасно работала — до первой перезагрузки. Сервер ушёл на перезагрузку. И не вернулся. Антон пять минут смотрел на консоль Hetzner с ошибкой Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0). Потом ещё пять минут просто сидел, не двигаясь. Дальнейшее — двухчасовая спасательная операция через rescue mode, ручное восстановление GRUB, три созвона с поддержкой хостинга и один звонок от директора с вопросом «что происходит». Геннадий Витальевич на встречу с клиентом опоздал на час пятнадцать. После этого в их компании появилось официальное правило: любая перезагрузка продакшн-сервера должна быть согласована с дежурным инженером, который проверяет чеклист из восьми пунктов. Геннадий Витальевич правило подписал. Говорят, без энтузиазма.
  17. Эту историю я долго не хотел рассказывать. Не потому что она страшная — потому что она embarrassing. Но потом я прочитал пост на ithub.uno о том, что культура безопасности строится на открытости, а не на замалчивании. И решился. Было это в 2019-м. Я настраивал GitLab CI для небольшого проекта — сервис рассылки уведомлений. В процессе отладки пайплайна мне нужен был быстрый тест. Я создал файл .env.test с заглушками: DB_PASSWORD=test123, API_KEY=dummy_key_for_testing, SMTP_PASSWORD=test123. Потом написал .gitlab-ci.yml, который деплоил приложение, и указал в нём cp .env.test .env — чтобы для CI это работало. Commit. Push. Пайплайн прошёл. Всё работает. Я написал нормальный продакшн конфиг, залил через GitLab Secrets как положено — и на этом моя работа с тем проектом закончилась. Меня перевели на другой. Проект жил своей жизнью. Рассылки уходили. Никто не проверял, откуда именно берутся credentials. Через три года я вернулся на тот проект для аудита. Открыл конфиг на сервере. Увидел DB_PASSWORD=test123. Мне понадобилось секунд тридцать, чтобы понять что произошло. Оказывается, команда cp .env.test .env в пайплайне выполнялась после загрузки GitLab Secrets и перезаписывала их. Три года подряд, при каждом деплое, на сервер улетал файл с паролем test123. Самое парадоксальное: база данных была доступна только изнутри приватной сети, поэтому этот пароль де-факто ни на что не влиял. Но это — счастливое стечение обстоятельств, а не правильная архитектура. Я провёл полный security audit. Нашёл ещё две похожих проблемы — других инженеров, таких же «временных» решений, ставших постоянными. Написал документ «Как мы храним секреты» и внедрил pre-commit hook, который ищет в коде паттерны типа password=test, key=dummy, secret=123. Но test123 в том конфиге я помню с фотографической точностью. Три восклицательных знака. Заглавные буквы. Всё честно. Разработчик сделал деплой. TODO не убрал — дедлайн, «потом». Потом не наступило. Endpoint жил в проде тихо и незаметно два года. Через два года при проведении security audit penetration tester обнаружил его за тридцать секунд работы с Burp Suite. База данных на тот момент содержала информацию о 340,000 пользователей. Всё это время данные были доступны любому, кто знал URL. Конца истории Денис не знает — его к тому времени уже не было в той компании. Знает только что был большой скандал и несколько уволенных. В нашей команде после этой истории появился CI-шаг: grep по всему коду на паттерны TODO.*прод, TODO.*prod, REMOVE BEFORE, DEBUG ONLY. Если находит — пайплайн падает с ошибкой. Работает без единого ложного срабатывания уже полтора года.
  18. В этой индустрии есть негласный закон: если что-то может сломаться в самый неподходящий момент — оно сломается именно тогда. Новогодняя ночь — идеальный момент для проверки этого закона. Я работал в финтех-компании. Мы делали платёжный шлюз. Нагрузка в новый год — одна из пиковых: все переводят деньги, покупают подарки в последний момент, пьют шампанское и одновременно пытаются провести транзакцию. 31 декабря, примерно в 22:00 я сидел у родителей. Оливье, телевизор, ощущение что ты наконец человек, а не придаток к ноутбуку. Дежурство официально было у Димы. Я был «вторым уровнем». В 23:58 мне позвонил Дима. Голос у него был такой, что я сразу встал из-за стола и вышел в коридор. — Макс, у нас лежит. Всё. Payment gateway не отвечает. Метрики нормальные, сервисы запущены, но транзакции не проходят. Я открыл ноутбук прямо в коридоре, на тумбочке с телефонным аппаратом эпохи СССР. Зашёл в Grafana — всё зелёное. CPU нормальный, память нормальная, сетевой трафик... стоп. Входящий — есть. Исходящий — ноль. Абсолютный ноль. Сервисы запущены, слушают порты, принимают соединения — но ничего не отправляют в ответ. В это время в телевизоре начали бить куранты. Моя мама заглянула в коридор с бокалом шампанского. Я сделал жест «одну минуту» — что в нашей профессии означает «от тридцати минут до нескольких часов». Нашёл проблему через двадцать две минуты нового года. Оказалось, в 23:55 сработал cron-job, который запускался раз в год 31 декабря для «очистки годовых логов». Скрипт удалял log-файлы старше 365 дней — разумная идея. Но через glob-паттерн он также захватывал конфигурационный файл SSL-сертификатов. Сертификаты физически никуда не делись, но конфиг, который указывал на них — исчез. Nginx перечитал конфигурацию и тихо перестал устанавливать TLS-соединения с upstream банковским API, требовавшим mutual TLS. Фикс занял четыре минуты: восстановить конфиг из git, перезапустить nginx, убедиться что транзакции пошли. Я вернулся к столу в 00:31. Шампанское было тёплым. Оливье съели без меня. А тот cron-job мы заменили нормальным logrotate с явными паттернами. И добавили тест: после каждого cron-задания запускается smoke-test платёжной цепочки. Каждую ночь. Включая 31 декабря.ии. Знает только что был большой скандал и несколько уволенных. В нашей команде после этой истории появился CI-шаг: grep по всему коду на паттерны TODO.*прод, TODO.*prod, REMOVE BEFORE, DEBUG ONLY. Если находит — пайплайн падает с ошибкой. Работает без единого ложного срабатывания уже полтора года.
  19. Это был мой второй месяц на новом месте. Я ещё не до конца понимал архитектуру системы, но уже вполне уверенно держался — учился быстро, читал документацию запоем, задавал правильные вопросы. Тогда я считал, что всё идёт хорошо. Было обычное утро вторника. Технический директор Андрей зашёл ко мне с просьбой, которая казалась абсолютно невинной: — Макс, у нас заканчивается место на prod-db-01. Там где-то есть временные файлы от старых бэкапов, почисти, пожалуйста. — Хорошо, посмотрю. Я зашёл на сервер. Открыл df -h. Действительно — диск забит под 94%. Начал смотреть, где место. du -sh /* — ничего подозрительного. Тогда запустил find / -name "*.tmp" -type f — и вот оно, целая папка /var/backup/temp/ с файлами с расширением .tmp общим весом около 180 гигабайт. Временные файлы. Именно то, о чём говорил Андрей. Я выполнил rm -rf /var/backup/temp/. Получил ошибку прав. Повторил с sudo. Команда отработала молниеносно — что меня немного удивило: для 180 гигабайт это подозрительно быстро. Но я не придал этому значения. Через двадцать минут ко мне подошёл бэкенд-разработчик Слава с характерным выражением лица человека, которому только что сообщили о смерти любимого питомца: — Макс, у нас приложение упало. API не отвечает вообще. База говорит «connection refused». Я зашёл на сервер. Попробовал сделать простейший SELECT — и тут у меня похолодело внутри: ERROR: could not open file "base/16384/1259": No such file or directory Через три минуты до меня дошло: папка /var/backup/temp/ — это была не папка с временными бэкапами. Это был симлинк. Симлинк на /var/lib/postgresql/14/main/base/. Кто-то, видимо в процессе миграции полгода назад, создал символическую ссылку с историческим названием, и она там тихо жила. А я, такой молодец, удалил через неё всю директорию с данными PostgreSQL. Всю. До последнего файла. База данных была жива. Процесс работал. Но данных больше не существовало физически. Следующие два часа я провёл в состоянии, которое сложно описать словами. Это не паника — паника это когда хаотично двигаешься. Я наоборот — сидел совершенно неподвижно и методично восстанавливал базу из последнего бэкапа. Бэкап был. Слава богу, бэкап был. Последний — в 03:00 ночи. Мы потеряли восемь часов транзакций. Потом было долгое молчаливое совещание с Андреем. Он не кричал. Он вообще почти не говорил. Это было хуже крика. Я написал подробный post-mortem. Ввёл правило: перед любым rm на проде обязательно проверять ls -la и file — убедиться, что это не симлинк. Добавил в онбординг пункт о том, как работают симлинки в Linux. Восемь часов данных так и не восстановили. Клиенты получили извинения. Я остался работать — Андрей оказался человеком, который верит в то, что ошибки надо исправлять, а не наказывать за них. Но ту папку с симлинком я помню до сих пор.
  20. Кластер работает. Теперь начинается настоящая работа: выжать из него максимум производительности, не потерять данные при апгрейде и знать, что делать когда (не «если») что-то сломается. Часть 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: Продвинутые возможности TentacleSMB 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 железе. Это стоит вложенных усилий.
  21. В прошлой статье мы разобрались с теорией — теперь руки в землю. Будем разворачивать минимальный продакшн-кластер 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 VEProxmox имеет встроенную поддержку 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 PGsceph 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
  22. Почему 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). Архитектура BlueStoreOSD 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-поддержка через CephCeph теперь умеет создавать 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
  23. Резервное копирование в 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, ELKrsyslog: маршрутизация логов# /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: алгоритм поиска проблемМетодология USEUSE 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% проблем с производительностью — это диск, память или сеть, а не код.
  24. Сети в 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 — это айсберг. Большинство из нас видит только надводную часть: файлы, процессы, сеть. Но под водой — целый мир механизмов, которые делают его надёжной основой для миллиардов устройств. Понимание этих механизмов — отличие хорошего администратора от великого.
  25. «Как вы узнали о проблеме?» — «Пожаловались пользователи» — так работать нельзя. Правильный мониторинг означает, что вы знаете о проблеме раньше, чем её заметят пользователи. Эта статья о построении полноценного стека мониторинга для 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 и базовых алертов, постепенно добавляйте экспортеры для ваших сервисов.

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.