🚀 У нас теперь есть блог. Да, я охуел от этой идеи тоже
Без текста
Без текста
Рубрика: от подписчиков 😀
🎯 GiST индексы: когда B-tree сдох, а GIN переборGiST (Generalized Search Tree) — индекс для нестандартной хуйни, которую обычные индексы не переваривают. Геоданные, диапазоны, полнотекст, кастомные типы. Если данные не укладываются в "больше-меньше-равно", твой выбор.🗺️ PostGIS: координаты не работают с B-treeЗадача: найти все кафе в радиусе 1км. B-tree на lat/lng = Seq Scan и пиздец.-- Не делай так (B-tree на координатах):CREATE INDEX idx_fail ON locations(lat, lng);SELECT * FROM locations WHERE lat BETWEEN 55.74 AND 55.76 AND lng BETWEEN 37.60 AND 37.62;-- Найдет квадрат, не круг. Кафе из соседнего района тоже попадут-- Правильно (GiST + PostGIS):CREATE EXTENSION postgis;ALTER TABLE locations ADD COLUMN geom geography(POINT, 4326);UPDATE locations SET geom = ST_SetSRID(ST_MakePoint(lng, lat), 4326);CREATE INDEX idx_locations_geom ON locations USING GIST (geom);SELECT * FROM locations WHERE ST_DWithin(geom, ST_MakePoint(37.617635, 55.755826)::geography, 1000);-- Execution: 12ms на 5M точек vs 4500ms Seq ScanБенчмарк: 5M точек, радиус 1км от центра Москвы. B-tree квадрат: 180ms + ложные срабатывания. GiST круг: 12ms, точные результаты.🐕 Первое сравнение: геометрияПёс ищет еду в квадратной комнате — проверяет все углы (B-tree квадрат). Умный пёс ищет в радиусе от своей миски — только актуальная зона (GiST круг). Второй находит быстрее и без лишнего.📝 Полнотекстовый поиск: GiST vs GINGIN быстрее для поиска. GiST меньше весит и быстрее обновляется.CREATE TABLE articles (id SERIAL, title TEXT, content TEXT, tsv tsvector);-- GIN: быстрый поискCREATE INDEX idx_gin ON articles USING GIN (tsv);-- Размер: 450MB | Поиск: 8ms | INSERT: 120ms-- GiST: быстрая вставка CREATE INDEX idx_gist ON articles USING GIST (tsv);-- Размер: 280MB (на 38% меньше!) | Поиск: 25ms | INSERT: 45msSELECT * FROM articles WHERE tsv @@ to_tsquery('russian', 'postgresql & индексы');Когда GiST: частые INSERT/UPDATE (логи, новости, чаты), размер индекса критичен.Когда GIN: поиск важнее вставок, read-heavy база."GIN всегда лучше для полнотекста"Реальность: Новостной сайт, 500 статей/час. GIN обновление 120ms × 500 = 16 секунд CPU/час. GiST: 45ms = 6 секунд. Экономия 240 часов CPU/месяц ="Геоданные сложные, используем два WHERE на lat/lng"Реальность: Квадрат вместо круга = пользователь видит кафе в 5км, но не видит в 900м за углом. Конверсия -15%, потому что Иванов И.И. "упростил".```sql-- Бронирование переговорок:CREATE TABLE bookings ( id SERIAL PRIMARY KEY, room_id INT, time_range tstzrange);CREATE INDEX idx_bookings_time ON bookings USING GIST (time_range);-- Найти пересечения:SELECT * FROM bookings WHERE room_id = 5 AND time_range && tstzrange('2025-11-14 14:00', '2025-11-14 16:00');-- EXCLUSION: физически запретить двойное бронированиеCREATE TABLE bookings ( room_id INT, time_range tstzrange, EXCLUDE USING GIST (room_id WITH =, time_range WITH &&));-- База сама гарантирует непересечение, двойное бронирование невозможно```Спрашиваешь собаку "ты свободен 14:00-16:00?" B-tree проверяет начало и конец отдельно, путается. GiST понимает диапазоны целиком: пёс занят 15:00-17:00 = пересечение есть, слот занят. Логика правильная.
🔨 Hash индексы: от "не используй никогда" до "ну ладно, иногда можно"Hash индексы в PostgreSQL — как та бывшая, которая была токсичной, исправилась, но все равно никто не доверяет. И правильно делают в 95% случаев.💀 История позора: PostgreSQL 9.6 и нижеДо версии 10 Hash индексы были говном:Не логировались в WAL → крашнулась база = индекс сгнилПосле сбоя: REINDEX вручную или Seq ScanНе реплицировались на слейвыНе работали с CONCURRENTLY-- PostgreSQL 9.6:CREATE INDEX idx_hash ON sessions USING HASH (token);-- База упала → индекс INVALID-- Продакшн лёг в 3 утраВывод до 2017: Hash индексы = профессиональное самоубийство.🐕 собака vs Hash индексы до PG10пес помнит где его миска даже после того как отключили свет. Hash индекс до PG10 забывал все после сбоя питания. пес надежнее.✅ PostgreSQL 16: Hash индексы вырослиЧто изменилось с PG10:WAL logging — выживают после крашаРепликация работаетCONCURRENTLY поддерживаетсяВ PG16: оптимизация коллизий (+15% скорость)Технически:CREATE INDEX idx_sessions_token ON sessions USING HASH (session_token);-- PG16: -- - Логируется в WAL ✓-- - Реплицируется ✓ -- - Не умирает при краше ✓-- - Быстрее чем в PG15 на 15%💰 Бенчмарки: Hash vs B-tree на миллиардахТестовая база:1 миллиард строкКолонка: UUID токены (уникальные)Запросы: точные поиски WHERE token = '...'Железо: 16CPU, 128GB RAM, NVMe-- Создаем индексы:CREATE INDEX idx_btree ON huge(token); -- B-tree: 42GBCREATE INDEX idx_hash ON huge(token) USING HASH; -- Hash: 38GB-- Запрос: точный поискSELECT * FROM huge WHERE token = 'cafebabe-1234-5678-90ab-cdef12345678';Результаты (1000 запросов, кэш прогрет):B-tree:Avg: 0.42msP95: 0.68msIndex size: 42GBHash:Avg: 0.38msP95: 0.52msIndex size: 38GBРазница: 10-12% в пользу Hash. Охуенно, правда? За эти 10% ты потерял:Диапазонные запросы (не работают)Сортировки (не работают)Частичные совпадения (не работают)🐕 B-tree vs HashB-tree — швейцарский нож: режет, открывает бутылки, подпиливает ногти.Hash — штопор: открывает бутылки охуенно, но только бутылки.Твой выбор: швейцарский нож за 100₽ или штопор за 90₽ который ничего кроме бутылок не умеет?⚡ Когда Hash реально быстрее (спойлер: почти никогда)Hash выигрывает ТОЛЬКО когда:Таблица >100M строк (иначе разница незаметна)Только точные поиски (=)Колонка уникальная или почти уникальная (UUID, токены)Никогда не нужны: диапазоны, сортировки, LIKE-- Идеальный кейс:CREATE TABLE sessions ( session_id BIGSERIAL PRIMARY KEY, token UUID UNIQUE NOT NULL, user_id BIGINT, created_at TIMESTAMPTZ);-- Hash для токенов (только = поиск):CREATE INDEX idx_token_hash ON sessions USING HASH (token);-- B-tree для остального:CREATE INDEX idx_user_created ON sessions(user_id, created_at);Реальная статистика использования:SELECT indexrelname, idx_scan, idx_tup_readFROM pg_stat_user_indexes WHERE indexrelname LIKE '%hash%';-- У 99% проектов: idx_scan = 0-- Потому что забыли или побоялись💩 Отмазка | Реальность"Hash быстрее, переделаю все индексы на Hash"Реальность: Первый же запрос WHERE created_at > NOW() - INTERVAL '1 day' ляжет в Seq Scan. Продакшн лег, ты уволен."Hash экономит место"Реальность: 38GB vs 42GB на таблице в 1TB. Экономия 10% места за потерю 90% функциональности. Как отрезать ноги чтобы меньше жрать."В PG16 Hash прокачали, теперь можно"Реальность: Прокачали скорость на 15%, но он все равно умеет только =. Это как научить мопса бегать быстрее — он все равно мопс, не борзая.🐕 сравнение для тупыхПес умеет: сидеть, лежать, дать лапу, принести мяч, охранять дом.Hash индекс умеет: =.Кого ты возьмешь домой? Собаню или одноразовый инструмент?🎯 Практический чеклистИспользуй Hash ТОЛЬКО если:✅ Таблица >100M строк✅ Колонка уникальная (UUID, токены, хэши)✅ Запросы ТОЛЬКО с =✅ Никогда не нужны: >, <, ORDER BY, LIKE✅ Проверил что B-tree реально тормозитНЕ используй Hash если:❌ Хоть раз нужен диапазон❌ Хоть раз нужна сортировка❌ Таблица <100M строк (разница незаметна)❌ Колонка с
🔬 B-tree в PostgreSQL 16: от "я умею CREATE INDEX" до "блять, вот как оно работает"Знаешь что общего между B-tree индексом и твоим пониманием баз данных? Оба выглядят простыми снаружи, но внутри — охуенно сложная штука, которую ты игнорируешь. Разница в том, что B-tree реально работает.🌳 Как B-tree работает внутри (и почему ты это игнорил)B-tree — это дерево блоков. Не путай с бинарным деревом из учебника, где каждая нода хранит один ключ. В PostgreSQL каждая страница индекса (8KB) хранит сотни ключей. Это как книжная полка: на одной полке (странице) лежит дохуя книг (ключей).Структура:Корневая страница: указатели на веткиВнутренние страницы: еще указателиЛистовые страницы: ключи + TID (Tuple ID - физический адрес строки)-- Проверим структуру индексаCREATE EXTENSION IF NOT EXISTS pageinspect;SELECT * FROM bt_metap('idx_users_email');-- magic: 340322 (идентификатор B-tree)-- version: 4 (в PG16)-- root: номер корневой страницы-- level: высота дерева (обычно 2-4)-- fastroot, fastlevel: оптимизация для частых вставокПочему это важно:Высота 3 = 3 чтения с диска (если нет в кэше)На 10M строк: высота обычно 3На 1B строк: высота обычно 4Разница между 3ms и 4ms может стоить $500/месяц🔥 Дедупликация в PostgreSQL 16: наконец-то не жрет местоДо PG13: одинаковые значения дублировались. Колонка status с 3 значениями на 10M строк = 10M записей в индексе.В PostgreSQL 16:CREATE INDEX idx_orders_status ON orders(status);-- До PG13: -- Индекс: 180MB (10M записей × ~18 bytes)-- PostgreSQL 16 с дедупликацией:-- Индекс: 110MB (экономия 38%!)-- Механизм: группирует одинаковые ключи + массив TIDКак проверить дедупликацию:SELECT pg_size_pretty(pg_relation_size('idx_orders_status')) as index_size, pg_stat_get_tuples_inserted('idx_orders_status'::regclass) as inserts;-- Если inserts >> unique values = дедупликация работает🐕 WoofWoof сравнение: собака vs твой индексПес помнит где лежит его миска. Одна миска = одна локация в памяти.Твой индекс БЕЗ дедупликации: 10 миллионов записей "status=pending". Это как если бы Рекс хранил 10 миллионов копий информации "миска на кухне". Песька не дебил, он помнит один раз. PostgreSQL 16 научился делать так же.⚡ Составные индексы: порядок колонок = жизнь или смертьЭто НЕ одно и то же:-- Вариант А:CREATE INDEX idx_orders_user_created ON orders(user_id, created_at);-- Вариант Б:CREATE INDEX idx_orders_created_user ON orders(created_at, user_id);Работает оптимально:-- Индекс А (user_id первый):SELECT * FROM orders WHERE user_id = 123 AND created_at > '2025-01-01';SELECT * FROM orders WHERE user_id = 123; -- РАБОТАЕТSELECT * FROM orders WHERE user_id = 123 ORDER BY created_at; -- ИДЕАЛЬНО-- Индекс Б (created_at первый):SELECT * FROM orders WHERE created_at > '2025-01-01' AND user_id = 123;SELECT * FROM orders WHERE created_at > '2025-01-01'; -- РАБОТАЕТSELECT * FROM orders WHERE user_id = 123; -- НЕ ИСПОЛЬЗУЕТ ИНДЕКС, ЕБАТЬПравило: первая колонка = самое частое условие WHERE. Вторая = сортировка или доп фильтр.💩 Отмазка | Реальность: составные индексы"Я создам два индекса: на user_id и на created_at"Реальность: PostgreSQL может использовать только один индекс на запрос (до PG16, Bitmap Index Scan помогает, но это медленно). Два индекса = 2× места, 2× время на INSERT/UPDATE, хуевая производительность."Порядок не важен, PostgreSQL сам разберется"Реальность: WHERE user_id = 123 с индексом (created_at, user_id) = Seq Scan на 5M строк. Execution time: 800ms вместо 3ms. Поздравляю, твой продакшн лег.🎯 Partial индексы: когда не нужно индексировать все-- Плохо: индекс на все 10M строкCREATE INDEX idx_users_email ON users(email);-- Размер: 450MB-- Хорошо: только активные (20% от всех)CREATE INDEX idx_active_users_email ON users(email) WHERE is_active = true;-- Размер: 90MB (экономия 80%!)-- Скорость: та же, но только для активныхКогда использовать:Колонка с перекосом (90% null, 10% не null)Логическое удаление (deleted_at IS NULL)Статусы (status IN ('pending',
🔥 Индексы в PostgreSQL: почему твой продакшн тормозит как Windows VistaЗнаешь, что общего между твоей базой данных и трехлетним ребенком? Оба умеют орать посреди ночи, и в обоих случаях это твоя вина. Только ребенка ты кормил и менял памперсы, а в базу индексы не завез. Поздравляю, теперь твой SELECT работает 8 секунд вместо 3 миллисекунд.🐕 Почему даже собака умнее твоего тимлидаПредставь: у тебя книга на 500 страниц, и тебе нужно найти слово "ебанутый". Что сделает нормальный человек? Откроет предметный указатель сзади. Что сделает твоя база без индексов? Прочитает ВСЕ 500 СТРАНИЦ от начала до конца.Моя собака понимает, что миска с едой стоит в углу кухни – он не обыскивает всю квартиру каждый раз. А твой PostgreSQL при запросе WHERE email = 'ivan@corp.com' сканирует 5 миллионов строк. Каждый. Ебаный. Раз.Стоимость твоей тупости:Seq Scan по 5M строк: ~800msIndex Scan: ~3msРазница: в 250 раз медленнееПользователи ушли: пока ты читаешь этот текст💩 Отмазка | Реальность"У нас всего 100К записей, индексы не нужны"Реальность: Даже на 10K строк разница между 50ms и 2ms имеет значение, когда запрос выполняется 1000 раз в минуту. Это 48 секунд CPU времени VS 2 секунды. За месяц ты платишь на $300 больше за инстанс, потому что "всего 100K записей"."Мы добавили индекс, но ничего не изменилось"Реальность: Ты добавил индекс на колонку created_at, но запрос использует WHERE DATE(created_at) = '2025-01-15'. Функция DATE() делает индекс бесполезным, ебать тебя в космос. PostgreSQL не может использовать индекс, если ты трансформируешь колонку.🔍 Как узнать, что ты накосячилОткрой консоль и запусти:```sql-- Проверяем запросEXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@mail.ru';-- Видишь "Seq Scan on users"? ПОЗДРАВЛЯЮ, ТЫ ПРОСРАЛ-- cost=0.00..145000.00 - это пиздец, друг-- Должно быть: Index Scan, cost около 0.43..8.45Или проверь индексы, которые НИКТО НЕ ИСПОЛЬЗУЕТ:sqlSELECT schemaname, tablename, indexname, idx_scanFROM pg_stat_user_indexes WHERE idx_scan = 0 AND indexname NOT LIKE 'pg_toast%';-- idx_scan = 0 значит индекс жрет место и никому не нужен-- Как твой проджект-менеджер```🏆 Реальный кейс от Петрова П.П.Компания "ХуевыРешения Inc" делала выборку пользователей по статусу:```sql-- Код долбоеба:SELECT * FROM orders WHERE status IN ('pending', 'processing')ORDER BY created_at DESC LIMIT 50;-- Execution time: 4500ms-- БЛЯТЬ, КТО ЭТО НАПИСАЛ?!```Проблемы:Нет индекса на statusНет индекса на created_atPostgreSQL сканирует 8M строкПосле того как выебали всех:```sql-- Создаем составной индекс (порядок ВАЖЕН!)CREATE INDEX idx_orders_status_created ON orders(status, created_at DESC) WHERE status IN ('pending', 'processing');-- Execution time: 12ms-- Экономия: 4488ms на запрос-- Запросов в день: ~50K-- Сэкономили: 62 часа CPU времени в день```Стоимость косяка: $400/месяц на оверпрайснутый RDS инстанс. Потому что Петров П.П. не знает что такое индексы.✅ Что делать прямо сейчасПроверь эти запросы:SELECT с WHERE на колонках без индексовJOIN на колонках без индексов (особенно foreign keys)ORDER BY на колонках без индексовДобавь индексы:```sql-- На foreign keys (да, PostgreSQL их НЕ индексирует автоматически)CREATE INDEX idx_orders_user_id ON orders(user_id);-- На поля для поискаCREATE INDEX idx_users_email ON users(email);-- Partial индексы для частых условийCREATE INDEX idx_active_users ON users(id) WHERE is_active = true;```🐕 Заключение: собака vs твой сеньорМоя собака запоминает где лежит его игрушка после одного раза. Твой сеньор разработчик третий год пишет запросы без индексов. Собака приносит мяч за 5 секунд. Твой запрос работает 5 секунд на табличке в 100K строк.Вопрос: кого из них стоит повысить?Итого:Индексы = предметный указатель в книгеБез индексов = читать всю книгу каждый разСобаки умнее некоторых разработчиковТвой продакшн тормозит потому что ты ленивыйP.S. В следующей статье разберем VACUUM и почему твоя база в 3 часа ночи легла. Спойлер: ты, блять, забыл про autovacuum.
Доброго вечера, коллеги (с)В прошлы раз мы разобрали почему docker swarm это плохо, в этой статье покажу как сделать чтобы нивелировать большинство его проблем https://telegra.ph/Kak-PRAVILNO-nastroit-Service-Discovery-v-Docker-Swarm-10-26P.S Да-да опять не могу ужать информацию до одного поста в телеге (Продолжение: В следующей части разберём, почему Kubernetes DNS ещё хуже)
🔥 Service Discovery в Docker Swarm: Как мы проебали prod в 3 ночиВступление для тех, кто ещё не в курсеПоздравляю, ты дошёл до момента, когда твои 5 контейнеров больше не могут найти друг друга через localhost:3000. Теперь тебе нужен Service Discovery. И ты, нагуглил Docker Swarm.Спойлер: твоя собака настроит отказоустойчивый кластер быстрее, чем ты разберёшься с overlay networks.🐕 Что такое Service DiscoveryКогда контейнер api-gateway хочет достучаться до user-service, ему нужно знать: IP этого сервиса, порт, и что сервис вообще жив В Swarm это работает через встроенный DNS на 127.0.0.11:53 и overlay network. Звучит охуенно, правда?💀 Как это работает (технически)Overlay Network — твой новый ад```docker network create \ --driver overlay \ --attachable \ my-fucking-network```Что здесь важно:--driver overlay — сетка между нодами. Без этого контейнеры на разных хостах НЕ УВИДЯТ друг друга--attachable — чтобы можно было подключить контейнер руками (для дебага в 3 ночи)DNS Resolution — магия, которая не работаетSwarm поднимает встроенный DNS внутри каждого контейнера:```yamlversion: "3.8"services: api: image: api:latest networks: - backend deploy: replicas: 3 db: image: postgres:15 networks: - backend```Обращение внутри контейнера api:```javascript // РАБОТАЕТ (если ты не долбоёб)fetch('http://db:5432')// НЕ РАБОТАЕТ (догадайся почему)fetch('http://localhost:5432') // БЛЯТЬ, ЭТО ЖЕ КОНТЕЙНЕР```🔴 КРИТИЧЕСКИЙ КОСЯК №1: Round-robin DNS — лотерея смертиКогда у тебя 3 реплики api, Swarm отдаёт IP по очереди:```bash# Запрос 1dig api # -> 10.0.1.5# Запрос 2 dig api # -> 10.0.1.6# Запрос 3dig api # -> 10.0.1.7 (УЖЕ МЁРТВАЯ РЕПЛИКА)```Проблема: TTL = 0, но HTTP-клиенты кэшируют соединения. Результат — 30% запросов в труп.Решение (которое ты не применишь):```javascript// Правильноconst agent = new http.Agent({ keepAlive: false // Да, убиваем перформанс});// Как у тебя сейчас (НЕПРАВИЛЬНО)const agent = new http.Agent({ keepAlive: true, maxSockets: 50 // 50 КОННЕКТОВ В МЁРТВЫЙ КОНТЕЙНЕР, ГЕНИЙ});```🔴 КРИТИЧЕСКИЙ КОСЯК №2: Health checks — иллюзия контроля```yamlhealthcheck: test: ["CMD", "curl", "-f", "http://localhost/health"] interval: 10s timeout: 3s retries: 3```Отмазка CTO Петрова А.С.: "У нас есть health checks!"Реальность:Проверка идёт внутри контейнера (localhost)Сетевые проблемы НЕ ДЕТЕКТЯТСЯМежду unhealthy и удалением из DNS — 30-60 секундЗа эти 60 секунд ты получишь несколько сотен 503-ошибок. Поздравляю!🔴 КРИТИЧЕСКИЙ КОСЯК №3: Split-brain при деплоеКогда ты делаешь docker service update:Поднимается новая реплика (10.0.1.8)DNS сразу добавляет её в пулПриложение ещё грузится (20 секунд)💥 50% запросов в 503Пруф:```bashdocker service ps api# ID IMAGE NODE DESIRED CURRENT STATE# abc123 api:v2 node1 Running Starting 3 seconds ago ← БЛЯТЬ, НЕ ГОТОВА# def456 api:v1 node2 Running Running 2 hours ago```🐶 Собака vs Твой Service DiscoveryСобака: Понимает команды с первого разаТвой код: 4 дня настройки DNSСобака: Виляет хвостом при встречеТвой код: 503 Service UnavailableСобака: Приносит тапкиТвой код: Приносит алерты в 3 ночи🔥 Минимально рабочий конфиг ```yamlversion: "3.8"services: api: image: api:latest networks: - backend deploy: replicas: 3 update_config: parallelism: 1 delay: 30s # ПОДОЖДИ, ПОКА КОНТЕЙНЕР ПРОГРЕЕТСЯ failure_action: rollback restart_policy: condition: on-failure delay: 5s max_attempts: 3 healthcheck: test: ["CMD", "wget", "--spider", "-q", "http://localhost:8080/health"] interval: 5s timeout: 2s retries: 3 start_period: 40s # 40 СЕКУНД НА СТАРТ, НЕ 10networks: backend: driver: overlay driver_opts: encrypted: "true" # А ТО СНИФФЕРЯТ```📊 Что в итоге✅ Работает из коробки — для pet-проектов❌ Production — нужен Consul/etcd или хотя бы правильная настройка
🔥 Yarn 4: Слои Docker или почему твой билд идет 15 МинутВчера научился делать Dockerfile для Yarn 4, сегодня каждый коммит собирается по 15 минут? Поздравляю, ты открыл для себя мир Docker layer cache. Или точнее — мир его отсутствия.🐕 Тест на умность собаки #1Собака понимает, что если каждый раз приносить всю еду из магазина, это долго. Она запоминает, где лежит миска. Ты копируешь все файлы проекта перед yarn install и удивляешься, почему кэш не работает.⚡ Как Docker кэширует слои (для тех, кто прогулял)Docker кэширует каждый слой (команду). Если файлы в COPY изменились — все последующие слои пересобираются. Вся магия в порядке команд.❌ Плохо:```FROM node:20-alpineCOPY . . # Изменился компонент → инвалидацияRUN yarn install # Пересборка 5 минутRUN yarn build # Пересборка 3 минуты```✅ Хорошо:```FROM node:20-alpineCOPY package.json yarn.lock ./ # Изменились зависимости → инвалидацияRUN yarn install # Только если поменялись зависимостиCOPY . . # Изменился кодRUN yarn build # Только код пересобирается```🐕 Тест на умность собаки #2Собака не идет в магазин за кормом, если корм еще есть дома. Ты переустанавливаешь 800MB зависимостей, потому что поменял цвет кнопки.✅ Правильная организация слоев```dockerfileFROM node:20-alpine AS depsRUN corepack enable && corepack prepare yarn@4.1.0 --activateRUN apk add --no-cache python3 g++ make gitWORKDIR /app# Отдельный stage для зависимостейCOPY package.json yarn.lock .yarnrc.yml ./COPY .yarn/releases ./.yarn/releasesRUN yarn install --immutable# Stage для сборкиFROM node:20-alpine AS builderRUN corepack enable && corepack prepare yarn@4.1.0 --activateWORKDIR /app# Копируем установленные зависимостиCOPY --from=deps /app/node_modules ./node_modulesCOPY --from=deps /app/package.json /app/yarn.lock ./# Теперь кодCOPY . .RUN yarn buildFROM nginx:alpineCOPY --from=builder /app/dist /usr/share/nginx/html```Профит: Если Docker registry поддерживает cache, stage deps кэшируется между разными ветками.📁 .dockerignore (который ты забыл)
🔥 Yarn 4 + Alpine: Когда Документация Врет ( или как я yarn c CI дружил ) Вступление для оптимистовРешил запустить React на Alpine в CI? Поздравляю, ты только что открыл портал в мир "интересных" сюрпризов. Yarn 4 и Alpine дружат примерно как кошка с водой — технически возможно, но удовольствия ноль.🐕 Тест на умность собаки #1Даже дворняга понимает, что если в документации написано "works everywhere", нужно проверить. А ты поверил на слово и уже коммитишь в мастер.💩 Ожидание vs РеальностьОжидание: "Zero Installs работает из коробки"Реальность: Нативные модули крашатся на Alpine из-за musl вместо glibc, и ты проведешь вечер в GitHub Issues от 2022 года с @swc/core.Ожидание: "Просто добавь nodeLinker: node-modules"Реальность: А build-tools? А python3 для компиляции? Сюрприз, они тоже нужны.Ожидание: "PnP — это будущее"Реальность: Будущее, где треть пакетов не запускается, а ты объясняешь PM почему "легкий фикс" занял 4 часа.💀 Классический Dockerfile (не делай так)dockerfileFROM node:20-alpineWORKDIR /appCOPY . .RUN yarn install# Стоп. А где corepack?# А build-tools для нативных модулей?# Вопросы, вопросы...RUN yarn buildЧто не так:Alpine использует musl, не glibc — половина нативных модулей сломаетсяNode.js 20 включает только npm — Yarn нужно активировать через corepackНет build-tools для sharp, bcrypt и компанииКопируешь всё сразу → кэш Docker не работает → каждый билд по 15 минут🐕 Тест на умность собаки #2Собака учится на ошибках с первого раза. Ты будешь гуглить "yarn alpine docker" три часа, найдешь решение для yarn 1 со stackoverflow 2019 года, и удивишься почему не работает.✅ Рабочий вариант (проверено болью)dockerfileFROM node:20-alpine AS builder# Активируем Yarn 4 через corepackRUN corepack enable && corepack prepare yarn@4.1.0 --activate# Ставим зависимости для нативных модулейRUN apk add --no-cache \ python3 \ g++ \ make \ git# Да, все это нужно для "легковесного" образа# Welcome to realityWORKDIR /app# Копируем СНАЧАЛА только файлы зависимостей (для кэша)COPY package.json yarn.lock .yarnrc.yml ./COPY .yarn/releases ./.yarn/releases# ☝️ .yarn/releases критически важен — там лежит сам Yarn 4# Это .cjs файл на 2MB, который ДОЛЖЕН быть в репеRUN yarn install --immutable --inline-builds# Теперь копируем остальноеCOPY . .RUN yarn build# Multi-stage: берем только собранное, без node_modulesFROM nginx:alpineCOPY --from=builder /app/dist /usr/share/nginx/html# 50MB вместо 1.1GB🐕 Тест на умность собаки #3Собака понимает multi-stage builds интуитивно: поел на кухне — зачем тащить миску в будку? А ты тащишь в прод 800MB node_modules "на всякий случай".🎯 Топ проблем (которые точно встретишь)#1: Забыл про .yarnrc.ymlyamlnodeLinker: node-modules enableGlobalCache: falseБез этого получишь PnP, который работает локально, но падает в CI с загадочной ошибкой.#2: Не скопировал .yarn/releasesYarn 4 хранит себя в репе как .cjs бандл в .yarn/releases/yarn-4.x.x.cjs. Забыл скопировать? Получи ошибку "Cannot find module" и час удивления.#3: Игнорируешь --immutableКто-то изменит lock-файл в CI, и билды станут недетерминированными. Удачи с дебагом.#4: Копируешь все файлы сразуПри изменении одного компонента пересобирается весь node_modules. Docker cache работает только если правильно расположить COPY.#5: Забыл про нативные модулиSharp, bcrypt, @swc/core требуют python3 и g++. Без них — крэш с "Error: Cannot find module".#6: На Alpine нужен musl, а не glibcНекоторые пакеты (например @swc/core) требуют явной настройки через supportedArchitectures в .yarnrc.yml для работы с musl.📊 Размеры образов (для тех, кто любит метрики)❌ FROM node:20 — 1.1GB└─ "Работает же!" (но жрет место)❌ FROM node:20-alpine без multi-stage — 400MB└─ Лучше, но можно еще✅ Multi-stage на nginx:alpine — 50MB└─ Node.js в проде не нужен, зачем его тащить?P.S.Если сейчас у тебя в проде nodeLinker: pnp на Alpine — есть шанс, что все упадет в 3 ночи после обновления зависимости. Проверено.Специальный дайждест для либеральных граждан
Ну что-же, можно сказать выговорился 😃😃Потихоньку буду снижать градус агрессии и выводить темы в более продуктивное русло. Перечитал свои статейки и понял что пора бы отдохнуть)--------------В этот раз я переплюнул сам себя - даже телеграф сказал что мой пост слишком большой 💀посему пришлось разбить: ----https://telegra.ph/Spisok-sovetov-kotoryh-ya-hotel-by-poluchit-let-tak-10-nazad-chast-1-10-04https://telegra.ph/Spisok-sovetov-kotoryh-ya-hotel-by-poluchit-let-tak-10-nazad-chast-2-10-04----IT — охуенная индустрия. Мы создаём то, что меняет мир. Мы решаем проблемы. Мы учимся каждый день.Но IT — это ещё и индустрия, где легко сгореть. Где токсичность маскируется под "корпоративную культуру". Где менеджеры путают сроки с реальностью.Береги себя. Уважай своё время. Не бойся говорить "нет". Требуй адекватных условий.Ты — не ресурс. Ты — человек, который пишет код.P.S. Если ты дочитал до конца — ты молодец. Если узнал себя хотя бы в 3 пунктах — задумайся. Если узнал себя в 10+ — нам срочно нужно поговорить о твоей карьере.Удачи, коллеги. Пишите хороший код. И не забывайте жить.
🔥 HTTP/3: Когда HTTP/2 было недостаточно ебать мозгиВнутри:Почему собаки умнее вашего CTOКак просрать 2.5 млн на "инновации"Реальная статистика: 67% случаев HTTP/2 быстрееКод с комментариями "// БЛЯТЬ, КТО ЭТО НАПИСАЛ"Формула: Тупость × Власть = Пиздец²Для тех кто устал делать вид что модные технологии решают ваши проблемы.https://telegra.ph/HTTP3-Kogda-HTTP2-bylo-nedostatochno-ebat-mozgi-10-02P.S. Собака сейчас смотрит на меня и думает: "Хозяин, почему люди внедряют технологии которые они не понимают?"Я не знаю, девочка. Я просто не знаю.P.P.S. Для тех кто собирается писать в комментах "а вот у нас HTTP/3 прекрасно работает!" — пиздите вы. Либо не работает, либо не измеряете, либо настолько специфичный кейс что вы исключение подтверждающее правило.( готов написать опровержение - если докажете )#HTTP3ЭтоПиздец
🔥 Почему ваш сайт лежит, а вы вините разработчиков (спойлер: дело в DNS)Знаете, что общего у дрессировки собаки и настройки DNS? Собаку можно научить командам за неделю. А вы за 15 лет не можете понять, почему после смены IP половина пользователей видит старый сайт.Очередной понедельник, очередной даунтайм, очередное "КАК ТАК ВЫШЛО?!" от бизнеса. А вышло просто: TTL был 86400 секунд, миграцию делали в пятницу вечером, и никто не снизил его заранее. Теперь сайт лежит для 50% пользователей на 24 часа, убытки $50K, а виноваты "криворукие разработчики".🎯 О чём этот пост (и почему вам стоит его прочитать)Это не очередная техническая статья в стиле "DNS для чайников". Это вскрытие реальных проблем, с которыми я сталкивался 15 лет. Здесь:✅ Реальные кейсы — как компания потеряла $2M в контрактах из-за MX-записи на localhost✅ Конкретные цифры — 82% компаний сталкиваются с DNS-проблемами ежегодно (Verisign, 2023)✅ Примеры кода — с комментариями в стиле "кто это написал, блять?"✅ Диагностика — инструменты, которые вы не используете, но должны✅ Чеклисты — что проверить перед тем, как всё упадёт в 3 часа ночи💀 Что вы узнаетеПочему "просто поменяй IP" приводит к даунтайму на суткиКак CNAME-запись может положить всю почтуПочему фраза "DNS propagation занимает 24-48 часов" — это пиздёжКак круговая зависимость в DNS валяла сайт 6 часов, пока никто не догадался проверить конфигЧто такое TTL и почему это единственная цифра, которую вы должны знать наизустьПочему собаки умнее 90% DevOps-инженеров (серьёзно, там сравнительный анализ)🐕 Главный инсайтСобака после 10 повторений понимает причинно-следственные связи.Вы после 10 одинаковых инцидентов всё ещё делаете те же ошибки.Может, проблема не в технологии?Перевожу: половина ваших проблем из-за штуки, которую вы не понимаете, узнаёте об этом через час, чините ещё три часа, и теряете кучу денег.🔗 Полная статья (для тех, кто готов к правде)👉 DNS: Или почему ваш сайт лежит, а вы даже не подозреваете отчего⚠️ Предупреждение: В статье много мата, цинизма и реальных примеров, которые заставят вас почувствовать себя некомфортно. Если вы PMили менеджер — приготовьте валерьянку. Если вы DevOps — приготовьте резюме на обновление.✨ Главные выводы (если лень читать всё)TTL — это не аббревиатура, которую можно игнорировать. Это время жизни DNS-записи в кеше. Снижайте его до 300 секунд за 48 часов до любых изменений. Иначе будете объяснять бизнесу, почему сайт лежит сутки.CNAME не сосуществует с другими записями. Прописали mail.example.com IN CNAME и mail.example.com IN MX одновременно? Поздравляю, почта не работает."DNS propagation 24-48 часов" — это оправдание, а не факт. DNS обновляется мгновенно. Проблема в кеше, который живёт столько, сколько указано в TTL. Который вы не снизили заранее. Потому что не читали эту статью.Локальный IP в публичной DNS-зоне — это не "временное решение", это объяснение начальству, почему сайт не открывается ни у кого, кроме вас.Проверяйте ВСЕ NS-серверы. Если один из трёх лежит полгода, вы узнаете об этом, когда второй упадёт под нагрузкой, и 50% трафика пойдёт в никуда.Мониторинг домена ≠ мониторинг сайта. Домен может истечь. DNS-записи удалятся. Сайт и почта перестанут работать. И вы узнаете об этом от клиентов, а не от систем мониторинга.Собаки понимают сложные команды быстрее, чем вы — базовые принципы DNS. Это не оскорбление, это наблюдение после 15 лет в индустрии.🎯 Для кого эта статьяДля разработчиков: которые пишут const API_URL = 'http://192.168.1.1' и удивляются, почему не работает у пользователейДля DevOps: которые делают миграцию в пятницу вечером и не снижают TTLДля менеджеров: которые говорят "просто добавь CNAME" и не понимают, что это сломаетДля всех: кто хотя бы раз слышал фразу "у меня работает" в ответ на жалобу пользователя💣 Бонус: чеклист перед катастрофой Все NS-серверы отвечают на запросы TTL снижен минимум за 48 часов до изменений Нет круговых зависимостей в CNAME MX-записи не указывают на localhost В публичной зоне нет приватных IP (192.168.x.x, 10.x.x.x, 172.16-31.x.x) Домен продлён
А теперь — конкретика по PHP-FPM 👇[ЧИТАТЬ: PHP-FPM — почему твои файлы не закрываются](ссылка на вторую статью)Здесь уже без философии:- Как найти file descriptor leak- Почему pm.max_requests спасает жизни- 4 типа утечек и как их чинить- Конкретные bash-команды для диагностики- Формула расчёта pm.max_childrenДля тех, кто хочет решить проблему, а не читать про собак.#PHPFPMLeakHunt#ЗакрывайFopenБлять#PmMaxRequestsСпасаетЖизни
🔥 Для тех, кто спрашивает "почему файл не закрывается"Вчера @Jebbyk спросил про незакрытые файлы в PHP-FPM.Я подумал, что он не понимает базу. Написал гневную портянку про веб-серверы на 5000 слов. С матом, собаками и сарказмом.Оказалось — он про другое спрашивал.😅Но знаете что? Текст оставляю. Потому что если вы:Не знаете разницу между blocking/non-blocking I/OДумаете, что while True — это нормально без контекстаПишете open() без close() и надеетесь на лучшееСчитаете, что "процесс завершился" = "всё почистилось"То вам сюда 👇[ЧИТАТЬ: Веб-сервер для тех, кто даже собаку не смог обучить]Ну а для @Jebbyk - обещаю написать версию для php-fpmP.S. Да, там много мата. Нет, я не извиняюсь. Это образовательный контент для технической аудитории, а не детский сад.#вебсерверы
💀⚰️🔥 Файловые дескрипторы: часть 2 — как всё идёт по пиздеДобро пожаловать во вторую часть нашего образовательного пиздеца! Теперь, когда мы знаем, что такое файловые дескрипторы, пора разобрать, как именно совет мудрецов умудряется превратить стабильную систему в утекающую развалину.Правда жизни: понимать теорию — это одно, а не обосраться на практике — совсем другое. И именно здесь большинство архитекторов высшего разума показывают свою истинную природу.Текст большой - https://telegra.ph/Fajlovye-deskriptory-chast-2--kak-vsyo-idyot-po-pizde-09-28💀 Последние слова выгоревшего техлидаДля совета мудрецов: Заткнитесь и не лезьте в архитектурные решения. Нанимайте людей, которые знают разницу между soft и hard лимитами.Для разработчиков: Валите, пока не поздно — жизнь одна, а legacy-код вечен. Или изучайте системное программирование и становитесь теми, кто фиксит пиздец, а не создаёт его.Для DevOps: Автоматизируйте всё. Мониторьте всё. Алертьте на всё. И помните: если что-то может утечь — оно утечёт.P.S. Если думаешь "это точно не про нас" — поздравляю, это именно про вас, блядь.P.P.S. Пёс лает на воров — его хвалят. Инженер предупреждает об утечках — его увольняют за "негативное мышление".P.P.P.S. Помните: каждый незакрытый дескриптор — это гвоздь в гроб вашей системы. И забивают эти гвозди обычно самые "опытные" разработчики.#ЗакрывайДескрипторыИлиСдохни #МониторингВсегоИВся #ТехДолгЭтоДиагноз #EMFILE_Means_GameOver #SystemProgrammingMatters
💀⚰️🔥 Файловые дескрипторы: часть 1 — что это за херня и нахуя она нужнаЕбать, поздравляю! Случился грандиозный образовательный пиздец. 90% "разработчиков" не знают, что такое файловые дескрипторы, а остальные 10% думают, что это "что-то про файлы, наверное". Блядь, это как ебашить код и не знать, что такое память.Истина жизни: даже бездомная псина понимает базовые принципы — взял кость, сгрыз, выбросил объедки. А программисты хватают системные ресурсы и держат их до скончания веков, как последние долбоёбы.---## 🔥 Что такое файловые дескрипторы: ликбез для тех, кто проебал основыФайловый дескриптор (FD) — это охренительно важная штука в Unix/Linux, блядь. Это просто число (обычно от 0 до `65535`), которое ядро выдаёт процессу как "пропуск" для работы с ресурсами.### Аналогия на пальцах (для особо тупых):Представь бордель с номерками:- Пришёл, заплатил за девку- Получил номер комнаты (это и есть твой FD) - Работаешь с девкой через этот номер- Кончил — освобождаешь комнатуВ системе аналогично:- Процесс просит у ядра ресурс ("дай файлик")- Ядро выдаёт номер-пропуск (FD)- Процесс долбит ресурс через этот номер- Закончил — отдаёт номер обратноТолько если в борделе забыл освободить комнату — заплатишь за всю ночь. А здесь забыл закрыть FD — система сдохнет от нехватки ресурсов, сука.---## 🤖 Основные типы FD (чтобы не путались, блядь)### Что можно открыть и как это выглядит:#### 1. Обычные файлы (для чайников)# Открыли файл — получили номерок 3exec 3< /etc/passwd # FD 3 = файл /etc/passwdread line <&3 # Читаем через FD 3 exec 3<&- # Закрыли FD 3 (ВАЖНО!)#### 2. Сокеты (сетевая хрень)# TCP — когда нужно поговорить с серверомnc google.com 80 # FD для TCP соединения с гуглом# UDP — когда похуй на доставкуnc -u 8.8.8.8 53 # FD для UDP сокета (DNS)# Unix socket — локальная перепискаnc -U /tmp/socket # FD для локального сокета#### 3. Pipes (когда процессы болтают друг с другом)# Анонимный pipe (классика)echo "пошли в пивбар" | grep "пиво" # 2 FD: писать|читать# Именованный pipe (для упоротых)mkfifo /tmp/chatecho "привет, долбоёб" > /tmp/chat &cat < /tmp/chat # Получит сообщение#### 4. Устройства (системная магия)echo "hello" > /dev/tty # FD к терминалуecho "в жопу" > /dev/null # FD к чёрной дыре системы head -c 10 /dev/urandom # FD к генератору случайных байт---## 📊 Стандартные дескрипторы: основы для тех, кто в танкеКаждый процесс автоматически получает 3 FD (как в комплекте "стартовый набор долбоёба"):# FD 0 — stdin (куда ты вводишь херню)read input # Читает с клавиатуры (FD 0)# FD 1 — stdout (куда выводится результат твоей работы) echo "всё ок" # Выводит в терминал (FD 1)# FD 2 — stderr (куда летят твои ошибки)echo "всё хуёво" >&2 # Выводит ошибку (FD 2)### Перенаправление дескрипторов (чтобы не орать в терминал):# Результат в файл (чтобы сохранить)echo "результат работы" > output.txt # FD 1 → файл# Ошибки в файл (чтобы потом посмотреть, что накосячил) ./my_script.sh 2> errors.log # FD 2 → файл ошибок# Ошибки туда же, где и результат (всё в одну кучу)./my_script.sh 2>&1 # FD 2 → FD 1# Заткнуть ошибки (когда похуй на них)./my_script.sh 2>/dev/null # FD 2 → в жопу# Открыть свой FD (для продвинутых извращений)exec 3> myfile.txt # FD 3 → файлecho "секретные данные" >&3exec 3>&- # Закрыли FD 3---## 🔍 Как глянуть, что творится с дескрипторами (чтобы понять масштаб бедствия)
🖕 ТЕХНИЧЕСКИЙ ДОЛГ: ПОЧЕМУ ЭТОТ ВАШ БИЗНЕС - НЕ УМНЕЕ СОБАКИ⚰️ Спойлер: Ты сам виноватТехнический долг — это не метафора. Это раковая опухоль твоего продукта, которая жрёт бюджет быстрее, чем школьники блейзер.📊 Размер долбоебизма :42% времени разработчики тратят на борьбу с говнокодом (Stripe, 2018)Это $85 МИЛЛИАРДОВ В ГОД на планете. Твоя доля там есть, поздравляю.Менеджмент: "Фичи! Продажи! Рост!" Реальность: Система горит, клиенты уходят, разрабы увольняются Менеджмент: surprised_pikachu.jpg💀 "Бизнесу" похуй на техническую реализациюНо именно она делает ВСЕ твои деньгиПредставь: ты водишь Феррари, но заправляешь ослиной мочой, потому что "бензин — это для техников, а я БИЗНЕСМЕН".Техническая реализация это:✅ Каждая транзакция (да, те самые деньги)✅ Каждый клик пользователя (который ещё не ушёл)✅ Каждая ебучая копейка прибыли"Бизнес" это:❌ PowerPoint презентации❌ Митинги о митингах❌ "Стратегическое видение" (читай: хуйня)Собака не срёт там, где ест. Ты срёшь прямо в код и удивляешься вони.🤡 Цирк уродов: Когда "бизнес" лезет в кодХит-парад ебанутых решений:🏆 Product Owner: "Просто добавь колонку в базу, делов-то!" Результат: База упала, данные потеряны, DBA плачет в туалете🥈 CEO после YouTube-туториала: "Микросервисы! Kubernetes! Blockchain! AI!" Результат: Было приложение — стало 47 проблем🥉 Sales Manager: "Клиент хочет как у Google. Сделаем?" Результат: У Google 200,000 инженеров. У вас — Вася с похмелья.⚠️ Золотое правило:Собака не пилотирует самолёт. Менеджер не проектирует архитектуру. Заткнись и дай работать профессионалам.🎯 НЕТ НИКАКОГО "БИЗНЕСА"Есть конкретные мудаки с именамиХватит прятаться за "бизнес решил". Кто, блять, решил?ОтмазкаРеальность"Бизнес не выделил бюджет"Петров зажал 50k на рефакторинг"Изменились требования"Сидорова 20 раз передумала"Нет времени на техдолг"Иванов обещал клиенту нереальные срокиКогда всё полыхнёт в продакшене:Не абстрактный "бизнес" виноватПетров, Сидорова и Иванов виноватыС конкретными именами, фамилиями и должностямиПротокол позора: "15.03.2024 — Иванов И.И. отказался фиксить критический баг" "22.03.2024 — Система упала. Потери: $500,000" "23.03.2024 — Иванов: 'Кто ж знал?'"📈 Математика для идиотовФормула ебли:Игнор техдолга × Время = Пиздец²Реальные цифры:🔴 1 час рефакторинга сегодня = 10 часов экономии через месяц🔴 $1 на инфраструктуру = $4 экономии на костылях🔴 1 уволившийся сеньор = 6 месяцев просрания и $150,000 на поискКейс из жизни:// 2019: "Временный костыль"if (user && user.type && user.type == "admin") { // TRIPLE CHECK, БЛЯТЬ // 500 строк копипасты // TODO: переписать (с) Вася}// 2024: // Вася уволился// Код поддерживает стажёр// 17 багов в неделю// Клиенты в ахуе🔥 Репутация: Как просрать всёСтатистика для тех, кто не верит:88% удаляют приложение после первого бага (UX Research)95% расскажут друзьям, что у вас говно100% не вернутся никогдаСобака: Обожглась — запомнила Пользователь: Увидел ошибку 500 — удалил нахуй Ты: "Почему отток 80%?"💊 Рецепт от доктора ОчевидностьДля топов (Иванов, Петров и прочие):1. Выдели 20% на техздоровьеИли получишь зомби, который сожрёт 80% бюджета2. Считай в деньгахТехдолг = $1.65M/год на 50 разрабовТвоя премия меньше? Подумай.3. Заткнись и слушайКогда техлид говорит "код гниёт" — это не нытьё. Это предсмертный хрип системы.4. Не лезь, сукаТы же не учишь хирурга оперировать? Вот и с кодом так же.Для разработчиков:🛠 Говори на языке денег❌ "Нужен рефакторинг" ✅ "Без рефакторинга потеряем $50k/месяц"🛠 Документируй саботажДата: 15.03.2024Кто: Иванов И.И.Что: Отказ фиксить CVE-критикалРиск: $500k - $2M при эксплойтеСтатус: ПРОИГНОРИРОВАНО🛠 Рефактори партизанскиНе "перепишем всё", а тихо чистишь при каждой задаче🛠 Веди статистику"За Q3: 47 часов на костыли = $18,800 зарплаты в унитаз"🏆 Netflix vs Вася-стартапNetflix:3 месяца на оптимизациюРезультат: -50% время загрузкиПрофит: +миллионы пользователейВася-стартап:"Нахуй оптимизацию!"Результат: приложение тормозитФинал: банкротство, Вася на самокатах
Как я полночи занимался сексом, причем пассивным, с HashiCorp Vault, чтобы не хранить пароли в докере как дебилИли почему "PASSWORD=admin123" в docker-compose.yml — это не best practice (но мы все равно так делаем)Короче, сидел я как-то, смотрел на свой docker-compose-develop.yml и мозолила мне глаза конструкция такого вида:configs: - source: EXTERNAL DOCKER CONFIG target: /var/www/html/.env Конфиги докера штука хорошая, спору нет, но для доставки переменных среды подходит со скрипом, ввиду того что изменять конфиг нельзя — только создавать новый. Это как научить собаку команде "сидеть", но каждый раз когда она садится, нужно придумывать новое слово для команды.Docker Swarm Configs: История одного пиздецаВот как выглядит типичный день с Docker Swarm configs:# Создаем конфигecho "DB_PASSWORD=sobaka123" | docker config create my-env-v1 -# Ой, пароль поменялсяecho "DB_PASSWORD=sobaka456" | docker config create my-env-v2 -# Теперь танцы с бубномdocker service update \ --config-rm my-env-v1 \ --config-add source=my-env-v2,target=/app/.env \ my-service# А теперь удаляем старый конфигdocker config rm my-env-v1# Error: config is in use by service my-service# СУКА!Причем каждый новый конфиг — это новое имя. Через месяц у тебя:- my-env-v1- my-env-v2- my-env-v3-final- my-env-v3-final-FINAL- my-env-v3-final-FINAL-блять- my-env-prod-do-not-delete- my-env-prod-do-not-delete-2Собака быстрее выучит квантовую физику, чем ты запомнишь, какой конфиг актуальный.Enter HashiCorp VaultVault — это такой сейф для паролей, только не такой, какой был у твоей бабушки под кроватью. Это сейф, который требует, чтобы ты сначала нашел три ключа, потом станцевал ритуальный танец, и только потом, может быть, даст тебе пароль.Почему Vault, а не просто .env файл?- .env файл рано или поздно закоммитит джун (или ты сам в пятницу вечером)- Docker secrets работают как собака-поводырь для слепого программиста — вроде помогают, но ты все равно в стену въебешься- Переменные окружения видны через docker inspect любому, у кого есть доступ к докер хосту (а это обычно пол-офиса)- А Vault — это модно, стильно, молодёжно (и главное — можно на резюме написать)Как я это поднимал в Swarm (спойлер: хотел повеситься)Шаг 1: Нужен Consul (потому что одной проблемы мало)# docker-stack-consul.ymlversion: '3.8'services: consul: image: consul:latest command: agent -server -bootstrap-expect=3 -ui -client=0.0.0.0 environment: CONSUL_BIND_INTERFACE: eth0 deploy: replicas: 3 placement: constraints: - node.role == manager # Только на менеджерах, как элитная собака networks: - vault-netДа, нужно 3 инстанса. Почему? Потому что "консенсус". Это как спросить трех собак, где закопана кость — две должны согласиться.Шаг 2: Запускаем Vault в Swarm# docker-stack-vault.ymlversion: '3.8'services: vault: image: vault:latest environment: VAULT_ADDR: 'http://0.0.0.0:8200' VAULT_LOCAL_CONFIG: '{"backend": {"consul": {"address": "consul:8500"}}, "listener": {"tcp": {"address": "0.0.0.0:8200", "tls_disable": 1}}}' command: server deploy: replicas: 1 # Больше — больше гемора с unseal placement: constraints: - node.labels.vault == true # Чтобы знать, где эта собака зарыта secrets: - source: vault-unseal-keys target: /tmp/unseal # Да, я знаю, что это плохо networks: - vault-netШаг 3: Инициализация (тут начинается треш)
Кто я и почему мне можно доверять (спойлер: нет)Привет, я Егор. 32 года. Всеолог™.Это когда ты вроде Senior, но по факту:- DevOps? Егор разберется- Backend сломался? Егор пофиксит - Фронт не работает? Егор, глянь- Сеть упала? Егор, ты же умный- Кофемашина сломалась? Егор, ну ты же айтишникМой стек - это шизофрения:- Go (когда хочу быть модным)- Python/FastAPI (когда нужно быстро)- Laravel/PHP (да, я из тех динозавров)- YAML (я ебаный YAML-developer, 80% времени)Ежедневная рутина:while true; do kubectl get pods | grep -v Running docker logs что-то-там 2>&1 | grep ERROR git blame (спойлер: это был я) ansible-playbook please-work.yml --praydoneМоя DevOps экспертиза:- Умею в Docker (cli/buildx/context)- Умею в K8s (helm)- Умею в CI/CD (пайплайн из 20 костылей чтобы собрать nextjs 😄 )- Умею в мониторинг (смотрю, когда упало)О чем мечтаю: Хочу жить в мире ТАЛАНТЛИВО-ОРИЕНТИРОВАННЫХ компаний. Знаете, таких, где:- Не надо объяснять, почему нельзя хранить пароли в коде- Люди понимают разницу между git pull и git clone- Менеджеры знают, что они менеджерятА не это вот всё, где ты одновременно:- Поводырь для слепого тимлида- Костыль для безрукого джуна - Переводчик с менеджерского на человеческий- Психотерапевт для выгоревших- Экстрасенс ("а почему не работает?" - "дай погадаю")Собака vs Я:- Собака: верная, не предает- Я: git push --force- Собака: всегда рада тебя видеть- Я: "не пишите мне до 11 утра"- Собака: защищает дом- Я: открываю 22 порт всему интернетуВывод: Собака не может в kubectl debug. Я могу. Пока что это всё, что отделяет меня от четвероногого друга. Ах да, еще собака не пишет говнокод в 3 ночи под дедлайн.#introduction #about #whoami #devops #backend #yaml #developer #allstack #собака #честность #цинизм #32года #программист
УРОК #1: OSI модель — что разработчику реально нужно знатьНачинаем серию "Умнее собаки". Сегодня разберем OSI модель с точки зрения разработчика, а не сетевика. Без воды, только то, что пригодится в продакшене.⚠️ SPOILER: Вам нужно понимать только L3, L4 и L7. Остальное — проблемы DevOps.🔧 ЧТО РЕАЛЬНО ВАЖНО ДЛЯ РАЗРАБОТЧИКА:Layer 7: Application — ваш код живет здесь• HTTP/HTTPS, WebSocket, gRPC• REST API, GraphQL • Здесь ваши ошибки 400, 404, 500• Здесь же CORS, cookies, JWT токеныТипичные проблемы:- "405 Method Not Allowed" — забыли разрешить POST- "431 Request Header Too Large" — напихали в cookies мегабайт- "504 Gateway Timeout" — ваш endpoint тормозитLayer 4: Transport — TCP vs UDPTCP: socket.socket(AF_INET, SOCK_STREAM) — гарантированная доставкаUDP: socket.socket(AF_INET, SOCK_DGRAM) — скорость важнее надежностиКогда какой:- REST API, базы данных → TCP- Метрики, логи, DNS, игры → UDP- Video streaming → UDP (или QUIC)Layer 3: Network — IP адресация и роутингping api.service.com # ICMP, проверка L3telnet api.service.com 443 # проверка L3+L4curl -I https://api.service.com # проверка L3+L4+L7💀 РЕАЛЬНЫЕ КЕЙСЫ ИЗ ПРОДАКШЕНА:Case 1: "Почему тормозит API?"❌ for (let i = 0; i < 1000; i++) { await fetch('https://api.example.com/data') // новое TCP соединение}✅ const agent = new https.Agent({ keepAlive: true, maxSockets: 50 })Проблема: TCP handshake (L4) + TLS handshake = +100-200ms латенси на каждый запросCase 2: "Too many connections"❌ conn = psycopg2.connect(...) # не закрыли соединение✅ with psycopg2.connect(...) as conn: # автоматическое закрытиеПроблема на L4: исчерпание портов (max ~64K) и файловых дескрипторовCase 3: "CORS не работает"❌ Access-Control-Allow-Origin: localhost:3000✅ Access-Control-Allow-Origin: http://localhost:3000Проблема на L7: браузер блокирует из-за неправильных заголовков🔍 ОТЛАДКА ПО СЛОЯМ (снизу вверх):L3: ping 8.8.8.8 # есть сеть вообще?L4: nc -zv google.com 443 # порт открыт?L7: curl -v https://api.example.com # что отвечает сервер?Если ping работает, но curl не работает → проблема на L4-L7Если ping не работает → проблема на L1-L3 (зовите DevOps)📊 СТАТИСТИКА ПРОБЛЕМ В ПРОДЕ:• 40% — неправильные HTTP заголовки/методы (L7)• 30% — connection pool / таймауты (L4)• 20% — файрволы / security groups (L3-L4)• 10% — реальные сетевые проблемы (L1-L3)🐕 ПОДХОД СОБАКИ VS РАЗРАБОТЧИКА:Собака: Нюхает порт 443 → Работает/Не работает → Идет спатьРазработчик: Tcpdump → Wireshark → 3 часа анализа → "Забыл добавить localhost в CORS"🎯 ГЛАВНОЕ ЗА СЕГОДНЯ:1. OSI — 7 слоев, но вам нужны только 3: Network (L3), Transport (L4), Application (L7)2. Большинство проблем на L7 (ваш код) или L4 (соединения)3. Connection pooling решает 50% проблем производительности4. Если ping работает, но приложение нет — проблема в вашем коде5. Собака debuggit быстрее, потому что не усложняет📝 ДОМАШНЕЕ ЗАДАНИЕ:Запустите tcpdump -i any port 443 и откройте любой HTTPS сайт. Попробуйте найти:- TCP 3-way handshake (SYN, SYN-ACK, ACK)- TLS ClientHello- HTTP запрос (если повезет)Завтра: TCP vs UDP — почему ваш REST API использует протокол 1981 года с гарантиями доставки для запросов, которые вы все равно ретраите.#УмнееСобаки #OSI #NetworkingForDevs #L7Problems