From 9bb7f6263feba3f8eb312335f1c4f62d1dc02b19 Mon Sep 17 00:00:00 2001 From: Tot Maxim Date: Sat, 18 Jul 2026 00:37:41 +0300 Subject: [PATCH] Initial commit: interview prep repo (resume, legend, stack cases, Kubernetes course) --- .gitattributes | 1 + .gitignore | 1 + AI_GUIDELINES.md | 62 +++ README.md | 48 ++ RESUME.md | 92 ++++ RESUME_RULES.md | 25 + interview/Alfa-Bank.md | 1 + interview/Bank VTB.md | 1 + interview/Bi.Zone.md | 1 + interview/Dit Moscow.md | 1 + interview/VK Cloud.md | 4 + interview/checklist.md | 36 ++ interview/common-questions.md | 83 ++++ interview/real-interviews.md | 64 +++ interview/study-plan.md | 68 +++ interview/Реалист банк.md | 1 + legend/CAPACITY.md | 72 +++ legend/DEPT_STACK.md | 55 +++ legend/LEGEND.md | 76 ++++ legend/STORY.md | 87 ++++ stack/ansible/CASE.md | 148 ++++++ stack/ansible/QUESTIONS.md | 59 +++ stack/ci-cd/CASE.md | 95 ++++ stack/ci-cd/QUESTIONS.md | 31 ++ stack/databases/CASE.md | 90 ++++ stack/databases/QUESTIONS.md | 43 ++ stack/docker/CASE.md | 118 +++++ stack/docker/QUESTIONS.md | 58 +++ stack/elk/CASE.md | 100 ++++ stack/elk/QUESTIONS.md | 35 ++ stack/kafka/CASE.md | 81 ++++ stack/kafka/QUESTIONS.md | 27 ++ stack/kubernetes/CASE.md | 169 +++++++ stack/kubernetes/LEARNING.md | 833 ++++++++++++++++++++++++++++++++++ stack/kubernetes/QUESTIONS.md | 43 ++ stack/languages/CASE.md | 22 + stack/linux-bash/CASE.md | 146 ++++++ stack/linux-bash/QUESTIONS.md | 79 ++++ stack/monitoring/CASE.md | 142 ++++++ stack/monitoring/QUESTIONS.md | 67 +++ stack/networking/CASE.md | 68 +++ stack/networking/QUESTIONS.md | 58 +++ stack/nginx/CASE.md | 111 +++++ stack/nginx/QUESTIONS.md | 39 ++ stack/testing/CASE.md | 21 + stack/vault/CASE.md | 67 +++ stack/vault/QUESTIONS.md | 19 + 47 files changed, 3548 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 AI_GUIDELINES.md create mode 100644 README.md create mode 100644 RESUME.md create mode 100644 RESUME_RULES.md create mode 100644 interview/Alfa-Bank.md create mode 100644 interview/Bank VTB.md create mode 100644 interview/Bi.Zone.md create mode 100644 interview/Dit Moscow.md create mode 100644 interview/VK Cloud.md create mode 100644 interview/checklist.md create mode 100644 interview/common-questions.md create mode 100644 interview/real-interviews.md create mode 100644 interview/study-plan.md create mode 100644 interview/Реалист банк.md create mode 100644 legend/CAPACITY.md create mode 100644 legend/DEPT_STACK.md create mode 100644 legend/LEGEND.md create mode 100644 legend/STORY.md create mode 100644 stack/ansible/CASE.md create mode 100644 stack/ansible/QUESTIONS.md create mode 100644 stack/ci-cd/CASE.md create mode 100644 stack/ci-cd/QUESTIONS.md create mode 100644 stack/databases/CASE.md create mode 100644 stack/databases/QUESTIONS.md create mode 100644 stack/docker/CASE.md create mode 100644 stack/docker/QUESTIONS.md create mode 100644 stack/elk/CASE.md create mode 100644 stack/elk/QUESTIONS.md create mode 100644 stack/kafka/CASE.md create mode 100644 stack/kafka/QUESTIONS.md create mode 100644 stack/kubernetes/CASE.md create mode 100644 stack/kubernetes/LEARNING.md create mode 100644 stack/kubernetes/QUESTIONS.md create mode 100644 stack/languages/CASE.md create mode 100644 stack/linux-bash/CASE.md create mode 100644 stack/linux-bash/QUESTIONS.md create mode 100644 stack/monitoring/CASE.md create mode 100644 stack/monitoring/QUESTIONS.md create mode 100644 stack/networking/CASE.md create mode 100644 stack/networking/QUESTIONS.md create mode 100644 stack/nginx/CASE.md create mode 100644 stack/nginx/QUESTIONS.md create mode 100644 stack/testing/CASE.md create mode 100644 stack/vault/CASE.md create mode 100644 stack/vault/QUESTIONS.md diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c5f206 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.claude/ diff --git a/AI_GUIDELINES.md b/AI_GUIDELINES.md new file mode 100644 index 0000000..091c78e --- /dev/null +++ b/AI_GUIDELINES.md @@ -0,0 +1,62 @@ +# Критерии и ограничения для ИИ + +Этот файл — компас для любой доработки репозитория. Перед тем как добавлять/менять кейсы, вопросы или легенду, свериться с этим документом. + +## Цель репозитория + +Подготовка к собеседованиям на позиции **«Инженер сопровождения»** и **«DevOps-инженер»** (уровень middle). Кандидат: [RESUME.md](RESUME.md) — Картман Эрик, 4 года 5 месяцев опыта, основной стек — мониторинг (Grafana/Prometheus/Mimir), Ansible, Docker, Nginx, ELK, Linux/Bash, CI/CD, немного тестирования (Selenium) и вспомогательные языки (Python, SQL, C/C++). Дополнительно — самостоятельная pet-практика (Kubernetes, PostgreSQL, сети, Kafka, Vault), см. раздел ниже. + +Подготовка опирается не только на резюме, но и на разбор реальных прошедших собеседований — [interview/real-interviews.md](interview/real-interviews.md) и сырые заметки в [interview/](interview/). Оценочные суждения из чужих заметок о конкретных людях/компаниях (личные впечатления автора заметок) не переносятся в кейсы или ответы — используется только фактическое содержание вопросов. + +## Ключевая рамка: кейсы должны быть реальными, а не выдуманными + +Это центральное ограничение всего репозитория. + +- **Знания по многим технологиям из резюме в основном теоретические.** Задача не в том, чтобы написать красивую историю, а в том, чтобы кандидат реально выполнил мини-лабу (домашний стенд, чаще всего на Docker/Docker Compose), получил настоящий опыт — и уже про этот опыт мог уверенно рассказывать и отвечать на уточняющие вопросы. +- Каждый `CASE.md` в `stack/*/` должен содержать **воспроизводимую инструкцию**: команды, конфиги, docker-compose, ожидаемый результат. Не абстрактное описание «как обычно это делается», а то, что можно взять и повторить на своей машине. +- **Не сочинять конкретные вымышленные факты** о работе в АО ТНИИС или ОКБ СУХОЙ (несуществующие цифры, инциденты, имена коллег, названия внутренних систем и т.п.). Разрешено обобщённо описывать типовые задачи роли («инженер сопровождения занимается тем-то») — это соответствует духу резюме, но детали должны опираться на то, что кандидат реально проделал в кейсе. +- Технологии, отсутствующие в опыте работы, но указанные в навыках резюме (Python, SQL, C/C++), — вписываются в легенду как вспомогательные инструменты внутри реальных задач (скрипты автоматизации, запросы к БД мониторинга и т.п.), тоже подкреплённые кейсом. +- **Технологии, которых нет вообще ни в одном рабочем месте (Kubernetes, PostgreSQL как СУБД, сетевая база, Kafka, Vault)** — согласованное расширение легенды по итогам разбора реальных собеседований ([interview/real-interviews.md](interview/real-interviews.md)). Вписываются **только** как честный pet-проект/домашняя лаборатория (раздел «Домашняя лаборатория / pet-проект» в [legend/LEGEND.md](legend/LEGEND.md)) — никогда не приписываются к опыту в АО ТНИИС или ОКБ СУХОЙ. На собеседовании позиционируются прямо: «в рабочем стеке этого не было, поднимал себе дома, чтобы разобраться». + +## Политика цифр нагрузки + +Вопросы вида «с каким трафиком сталкивался» / «как справишься с масштабом X» закрываются через [legend/CAPACITY.md](legend/CAPACITY.md) и три чётко разделённых источника цифр, которые нельзя смешивать: + +1. Публичные документированные пределы технологий (со ссылкой на характер источника). +2. Реальные замеры, полученные прогоном инструментов на собственных CASE.md-стендах. +3. Факты о реальном масштабе, заполняемые только пользователем лично. + +**Не сочинять «продовые» или «типичные для компании такого профиля» цифры нагрузки** — ни как метрики с работы, ни как параметры pet-лабы, выдаваемые за нечто большее, чем они есть. + +## Согласованность + +Все документы должны быть непротиворечивы друг другу: + +- Даты, названия компаний, стек — как в [RESUME.md](RESUME.md). +- Легенда ([legend/LEGEND.md](legend/LEGEND.md)) — единый источник истины про то, «где и зачем» применялась технология. Кейсы и ответы на вопросы ссылаются на неё, а не противоречат. +- Если кейс меняет понимание того, как технология использовалась — обновить LEGEND.md в этом же заходе. + +## Стиль + +- Все документы — на русском, в markdown. +- Ответы на вопросы в `QUESTIONS.md` — от первого лица, разговорным языком, как кандидат реально бы ответил, с опорой на кейс. Не копировать формулировки из документации слово в слово. +- Без канцелярита и не заученно — если ответ звучит как выученный наизусть параграф, это плохой ответ и его надо переписать. + +## Куда идти + +- Порядок проработки категорий следует частотности тем в реальных собеседованиях, зафиксированной в [interview/real-interviews.md](interview/real-interviews.md) и разложенной по шагам в [interview/study-plan.md](interview/study-plan.md) — не порядку строк резюме. +- Добавлять вопросы «от простого к сложному»: сначала база (что такое, зачем нужно), потом — сравнение с альтернативами, потом — как бы решал проблему X. +- При появлении новых разборов реальных собеседований — обновлять матрицу тем в [interview/real-interviews.md](interview/real-interviews.md) и маппить новые вопросы на существующие/новые категории, а не оставлять их непокрытыми. +- Держать README.md как актуальную карту готовности (что проработано, что нет). + +## Куда не идти + +- Не придумывать метрики/цифры, которых нет в резюме и не проверены кейсом («настроил 200 дашбордов» и т.п.). +- Не добавлять технологии, которых нет ни в резюме, ни в согласованном расширении легенды. +- Не делать кейсы избыточно сложными для уровня middle — стенд должен быть реалистичным для домашней проработки, а не production-инсталляцией. +- Не переписывать RESUME.md без явного запроса пользователя — правки резюме пользователь вносит/подтверждает сам. +- **Не закладывать в резюме или легенду техники обмана работодателя/площадки** — например, дублирующие профили с намеренно искажёнными данными (изменённые ФИО/возраст/контакты) для обхода отказов. Это не входит в понятие «подготовка к собеседованию». См. разбор в [RESUME_RULES.md](RESUME_RULES.md) → «Осознанно не применено». + +## Правила оформления резюме + +Отдельный применённый чек-лист внешних best practices по резюме — в [RESUME_RULES.md](RESUME_RULES.md). Он же фиксирует, что было сознательно отброшено и почему. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1a5a2a8 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# Подготовка к собеседованиям — Инженер сопровождения / DevOps + +Репозиторий для системной подготовки к собеседованиям на основе резюме [RESUME.md](RESUME.md) и разбора реальных прошедших собеседований [interview/real-interviews.md](interview/real-interviews.md). Правила и ограничения для доработки — в [AI_GUIDELINES.md](AI_GUIDELINES.md), обязательно к прочтению перед любыми изменениями. + +## Навигация + +- [RESUME.md](RESUME.md) — финальный текст резюме. +- [RESUME_RULES.md](RESUME_RULES.md) — применённый чек-лист правил написания резюме (что применено, что отброшено и почему). +- [AI_GUIDELINES.md](AI_GUIDELINES.md) — критерии и ограничения для доработки репозитория. +- [legend/LEGEND.md](legend/LEGEND.md) — единая легенда: таймлайн, роли, карта «технология → где применялась», раздел «Домашняя лаборатория / pet-проект». +- [legend/STORY.md](legend/STORY.md) — живой нарратив работы в АО ТНИИС: проект отдела, распорядок дня, состав команды, разбор инцидента, версионирование дашбордов. +- [legend/DEPT_STACK.md](legend/DEPT_STACK.md) — стек отдела целиком, включая инструменты без личной строки в резюме (MinIO, Nexus, Alertmanager и т.п.). +- [legend/CAPACITY.md](legend/CAPACITY.md) — справочник цифр нагрузки: публичные пределы технологий, лабные замеры, реальные цифры с работы. +- [interview/real-interviews.md](interview/real-interviews.md) — анализ шести реальных собеседований, матрица тем, разрывы в покрытии. +- [interview/study-plan.md](interview/study-plan.md) — пошаговый учебный план по частотности тем в реальных собеседованиях. +- [interview/common-questions.md](interview/common-questions.md) — базовые вопросы не по стеку (самопрезентация и т.п.). +- [interview/checklist.md](interview/checklist.md) — порядок и статус подготовки. + +## Стек по категориям + +Каждая категория — `CASE.md` (воспроизводимый домашний кейс/стенд) + `QUESTIONS.md` (вопросы с ответами из опыта). + +| Категория | Технологии | Статус | +|---|---|---| +| [stack/monitoring/](stack/monitoring/) | Grafana, Prometheus, Mimir | ✅ готово | +| [stack/ansible/](stack/ansible/) | Ansible, Molecule | ✅ готово | +| [stack/nginx/](stack/nginx/) | Nginx | ✅ готово | +| [stack/docker/](stack/docker/) | Docker | ✅ готово | +| [stack/linux-bash/](stack/linux-bash/) | Astra Linux, CentOS, Bash | ✅ готово | +| [stack/elk/](stack/elk/) | Elastic Stack (ELK) | ✅ готово | +| [stack/ci-cd/](stack/ci-cd/) | Git, Gitea, GitLab, TeamCity | ✅ готово | +| [stack/kubernetes/](stack/kubernetes/) | Kubernetes, Helm (pet) | ✅ готово | +| [stack/databases/](stack/databases/) | PostgreSQL (pet) | ✅ готово | +| [stack/networking/](stack/networking/) | Сети: DNS, TCP/IP, HTTP/TLS (pet) | ✅ готово | +| [stack/kafka/](stack/kafka/) | Kafka (pet) | ✅ готово | +| [stack/vault/](stack/vault/) | HashiCorp Vault (pet) | ✅ готово | +| [stack/testing/](stack/testing/) | Selenium, тест-дизайн | 🔲 TODO | +| [stack/languages/](stack/languages/) | Python, SQL, C/C++ | 🔲 TODO | + +Категории с пометкой «pet» — честная домашняя практика технологий, отсутствующих в рабочем стеке компаний (см. [legend/LEGEND.md](legend/LEGEND.md) → «Домашняя лаборатория / pet-проект»). «Готово» означает, что кейс и вопросы написаны — фактическая готовность по критериям (стенд реально прогнан руками) отслеживается отдельно в [interview/checklist.md](interview/checklist.md). + +Kubernetes с нуля не знаком — учебный курс [stack/kubernetes/LEARNING.md](stack/kubernetes/LEARNING.md) идёт до CASE.md: 14 модулей от установки инструментов до собственного Helm-чарта, каждый с объяснением «зачем» и практикой руками. + +Порядок проработки TODO-категорий и критерии готовности — в [interview/checklist.md](interview/checklist.md) и [interview/study-plan.md](interview/study-plan.md). + +## Как здесь всё связано + +`RESUME.md` — что написано в резюме → `legend/LEGEND.md` — как это согласуется в единую картину карьеры (включая честно обозначенную pet-практику) → `stack/*/CASE.md` — реально воспроизведённый опыт по каждой технологии, с цифрами в `legend/CAPACITY.md` там, где это уместно → `stack/*/QUESTIONS.md` — вопросы, на которые теперь есть честный ответ из первых рук, включая вопросы из реальных собеседований в `interview/real-interviews.md`. diff --git a/RESUME.md b/RESUME.md new file mode 100644 index 0000000..660fc3e --- /dev/null +++ b/RESUME.md @@ -0,0 +1,92 @@ +# Картман Эрик + +Мужчина, 28 лет, родился 18 апреля 1998 + +**Телефон:** 8 (988) 324-86-66 — предпочитаемый способ связи +**Telegram:** [добавить ссылку — мессенджеры дают заметно лучший отклик рекрутёров, чем email] +**WhatsApp:** [добавить, если отличается от основного телефона] +**Email:** p.maxim.mail@yandex.ru +**Проживает:** Таганрог +**Гражданство:** Россия, есть разрешение на работу: Россия +**Готов работать удалённо**, не готов к командировкам + +## Желаемая должность и зарплата + +**Инженер по сопровождению** + +- Специализации: Программист, разработчик +- Тип занятости: полная занятость +- Формат работы: удалённо, гибрид +- Желательное время в пути до работы: не имеет значения + +## Опыт работы — 4 года 5 месяцев + +### АО ТНИИС +**Сентябрь 2023 — настоящее время** (2 года 11 месяцев) +**Инженер по сопровождению** + +Стек отдела: Astra, Grafana, Prometheus, Docker, Ansible, Nginx, Jira, Gitea, Elastic Stack (ELK), Bash, Java + +**Достижения:** + +*Автоматизация и мониторинг:* +- Настроил 50 дашбордов в Grafana, интегрировав данные из Prometheus с унификацией форматов метрик, что сократило время на выявление и устранение неполадок, а также обеспечил доступ к дашбордам для всех членов команды через аутентификацию и обучение, повысив общую эффективность работы. +- Написал 15 скриптов с использованием Ansible, организовав управление конфигурациями через роли и модули, сотрудничая с командами разработки и DevOps для определения требований, и интегрировав тестирование в CI/CD-конвейеры с использованием фреймворка Molecule и TeamCity, что уменьшило время развертывания, снизило количество ошибок и улучшило стабильность сервиса, обеспечив быстрое восстановление в случае сбоев. + +**Обязанности:** + +*Оптимизация и настройка мониторинга:* +- Оптимизировал системы мониторинга с использованием Prometheus и Grafana, что позволило сократить время реагирования на инциденты. +- Реализовал настройку Mimir, что улучшило масштабируемость системы мониторинга. +- Установил и настроил систему Prometheus для сбора метрик из различных источников. +- Сконфигурировал Grafana для визуализации данных и создания дашбордов. + +*Автоматизация и конфигурация:* +- Автоматизировал установку и настройку серверов в части деплоя с помощью Ansible, что сократило время развертывания. +- Написал конфигурационные файлы Nginx для маршрутизации и балансировки нагрузки на сервисы. +- Создал роли и плейбуки Ansible, обновил скрипты для автоматизации развертывания и настройки инфраструктуры. + +*Управление проектами и задачами:* +- Использовал Jira для отслеживания задач, планирования спринтов и управления проектами в Agile-окружении. + +### ОКБ СУХОЙ +**Март 2022 — Сентябрь 2023** (1 год 7 месяцев) +**Инженер тестировщик** + +Стек отдела: CentOS, Selenium для автоматизации тестирования, GitLab + +**Достижения:** +- Разработаны эффективные тестовые сценарии для верификации встроенного программного обеспечения, а также автоматизированные модульные и интеграционные тесты, что повысило качество конечного продукта и сократило время на тестирование. + +**Обязанности:** +- Проводил тесты для выявления проблем в программном обеспечении на платформе CentOS. +- Создавал тестовые сценарии для комплексной проверки функциональности. +- Написал автоматизированные тесты на стендах интеграционного и приемочного тестирования. +- Идентифицировал и документировал проблемы в процессе тестирования. +- Подготовил отчеты о выявленных проблемах для разработчиков, включая описания и рекомендации по исправлению. + +## Образование + +**Магистр** +Южный федеральный университет, магистратура, 2024 +Инфокоммуникационные технологии и системы связи + +**Бакалавр** +Южный федеральный университет, бакалавриат, 2022 +Инфокоммуникационные технологии и системы связи + +## Навыки + +**Знание языков:** +- Русский — родной +- Английский — B2 (средне-продвинутый) + +**Ключевые навыки:** + +*Инфраструктура и ОС:* Linux · Astra Linux · CentOS · Bash · Docker +*Мониторинг:* Grafana · Prometheus · Mimir · Elastic Stack (ELK) +*Автоматизация и конфигурирование:* Ansible · Molecule · Nginx +*CI/CD и трекинг задач:* Git · GitLab · Gitea · TeamCity · Jira +*Тестирование:* Selenium +*Языки и БД:* Python · SQL · C/C++ · Java · PostgreSQL +*Дополнительно (самостоятельная практика):* Kubernetes · Helm · Kafka · HashiCorp Vault diff --git a/RESUME_RULES.md b/RESUME_RULES.md new file mode 100644 index 0000000..2c2662e --- /dev/null +++ b/RESUME_RULES.md @@ -0,0 +1,25 @@ +# Правила написания резюме — применённый чек-лист + +Синтез практик подбора резюме в IT (на основе разбора внешних материалов по резюме и HR-вопросам), отфильтрованный от лишнего/спорного. Что из этого применено к [RESUME.md](RESUME.md) — отмечено. Правила использования — часть общего компаса [AI_GUIDELINES.md](AI_GUIDELINES.md). + +## Применено + +- **Стек — первой строкой в блоке опыта.** Уже было в резюме («Стек отдела: …»), не трогали. +- **Достижения перед обязанностями для опыта 3+ года.** Уже было, не трогали. +- **Глаголы совершённого вида, прошедшее время** («настроил», а не «настраивал»). Уже было, не трогали. +- **Точное название желаемой должности**, а не общая категория. Уже было («Инженер по сопровождению»), не трогали. +- **Мессенджеры в контактах** (Telegram/WhatsApp) дают заметно лучший отклик, чем только телефон/email. Добавили поля-плейсхолдеры в шапку — заполнить реальными данными. +- **Широкий и полный список hard skills** (ориентир — 20+, без раздувания пустыми словами). Список был неполным (8 навыков) при том, что в описании опыта уже фигурируют Grafana, Prometheus, Mimir, ELK, Bash, Astra, CentOS, GitLab, Gitea, TeamCity, Molecule, Selenium, Java, Jira — это просто не было вынесено в раздел «Навыки». Свели воедино и сгруппировали по смыслу. +- **Не указывать зарплатные ожидания без уверенности в цифре** (риск отсева на скрининге при завышении/заниженности). В резюме цифры и не было — оставили как есть, это уже правильно. +- **Без фото** на российском IT-рынке — фото не даёт плюса для инженерных позиций. В резюме фото не было — оставили как есть. + +## Осознанно не применено + +- **«Растянуть» стаж, переквалифицировать нерелевантный опыт под IT-должность, скрыть онлайн-курсы как подозрительные».** Не актуально — у кандидата есть непрерывный релевантный опыт 4 года 5 месяцев, искусственно ничего дотягивать не нужно. +- **«Создать второй/третий профиль с изменёнными на 1 символ ФИО, ±1 год возраста, другим номером и почтой, чтобы обойти отказавших работодателей».** Отклонено полностью. Это не техника резюме, а введение работодателя в заблуждение и вероятное нарушение правил площадки — такая механика не будет закладываться ни в RESUME.md, ни в какие-либо кейсы. См. [AI_GUIDELINES.md](AI_GUIDELINES.md) → «Куда не идти». +- **«Убрать образование не по специальности / курсы младше 2-3 лет прятать».** У кандидата образование профильное (Инфокоммуникационные технологии и системы связи), скрывать нечего — правило неприменимо, не трогали. +- **Сопроводительное письмо не нужно для хардовых IT-специальностей.** Соответствует текущему состоянию — сопроводительного текста в RESUME.md никогда не было, отдельно ничего не меняли. + +## Правило, требующее решения пользователя (не применено автоматически) + +- **Обход ATS-детекции «ИИ-текста»** (шаблонные фразы вроде «ключевые навыки», «таким образом») — в блоках достижений АО ТНИИС встречаются длинные перечислительные конструкции («что сократило… а также обеспечил… через аутентификацию и обучение, повысив…») — это стоит сократить и раздробить на более короткие, конкретные пункты при следующей шлифовке резюме, но такая правка меняет формулировки достижений по сути, а не только структуру контактов/навыков — по правилу [AI_GUIDELINES.md](AI_GUIDELINES.md) («Не переписывать RESUME.md без явного запроса пользователя») это не тронуто, ждёт отдельного запроса. diff --git a/interview/Alfa-Bank.md b/interview/Alfa-Bank.md new file mode 100644 index 0000000..aef69ab --- /dev/null +++ b/interview/Alfa-Bank.md @@ -0,0 +1 @@ +
Компания: Альфа-Банк
Позиция: Инженер по сопровождению ПО
Кто проводит: Рук. направления, тимлид, x2 DevOps
Атмосфера:
Тухлятина токсичная ☢️ 
Тимлид: Адащук Дмитрий
Рук: Сметанина Ольга
Трудоустройство: ТК РФ
График: 5/2 (50% офис в неделю и 50% из дома, на испытательном 3 мес. в офисе на Технопарке)
Вакансия: Ниже в спойлере
HR: @OlchikWB и @victoria1892
Вилка: от 250к чистыми
Длительность: 45 мин.
✅Статус: Оффер
❗️Советы:
- Думаю не стоит идти туда работать ни при каких обстоятельствах.
- Мне не хватило знаний SQL это точно, они хотели чтобы знаний по языку было сильно больше и не в формальном виде, а чтобы я писал запросы выше среднего.
- Учить базу K8s: балансировка трафика, нагрузка, Helm, Configmap и прочее.
- Учить подробно как работает Hashicorp Vault: как вкладываются секреты и тд, как идет синхронизация с CI/CD.

Вопросы:

Тимлид (полутокс):
- Как пробить порт на удаленном сервере?
- Как найти процесс Linux?
- Что такое curl -v?
- Что такое wget?
- С каким мониторингом ты работал?
- Что такое переменные в Grafana?
- Как сохранить логи из контейнера?
- Что такое Namespace в K8S?
- Чем отличается Configmap от secret?
- Как наполняется файл секрета?
- В какой-то момент попытался меня поймать на процессе CI/CD, мол причем тут CI, если про код никакой речи не идет в данном контексте и тд. Мол зачем тебе править конфиги и сразу их пушить? Я говорю: таким образом вообще-то работает GitLab и процесс CI/CD - в общем друг друга вообще не поняли, видимо и работа на них будет такая же непонятная.

Руководитель (супертокс):
Характер:
- Обман с моим резюме, сказала, что Сбер на последнем месте, хотя по факту ОТП банк. Резюме у нее старое.
- Долбила вопросами зачем мне менять текущее место работы.
- На вопросы отвечает уклончиво и не на все заданные.
- Замолкает, что рушит атмосферу всего собеседования.
- Перебивает. Не дает вставить слово.
- Сказала в конце собеса, что я могу задавать любые вопросы, но я задал и она тут же сняла этот вопрос.

Вопросы:
- Расскажи об опыте подробнее?
- С чем работал? Стек?
- Где работаешь?
- Сколько человек в команде?
- Какую роль исполняешь?
- Ты решал инциденты?
- Что входит в обязанности инженера по сопровождению?
- Какой опыт с PostgreSQL?
- Какие виды join существуют и как работают?
- Что такое скоринг?
- Что такое кредитный конвейер?

Вакансия:
📌 Главный специалист MDM НСИ Core (Альфа-Банк), Москва или МО (гибрид)

Обязанности:
· Сопровождение системы: установка обновлений системы, конфигурирование и другие административные задачи, проведение системного, интеграционного и приемочного тестирования релизов;
· Участие в согласование архитектурной документации, создании регламентов по процессам в системе;
· Проведение работ по поддержанию высокого уровня доступности системы (участие в разработке и регулярной проверки DRP плана);
· Проактивный анализ возникновения проблем в системе и внешними потребителями (системы) с помощью систем мониторинга, постоянное улучшение системы мониторинга, системы и инфраструктуры;
· Решение вопросов пользователей промышленной системы и сотрудников контроля качества данных: консультация, анализ и регистрация дефектов, взаимодействие с системными аналитиками, предоставление временного решения проблемы, выгрузка данных по авторизованным запросам;
· Постоянная работа по улучшению качества системы, проактивная работа по выявлению ошибок;
· Проведение регламентных работ в системе;
· Решение вопросов пользователей в системе в рамках процессного подхода (ITIL);
· Отчетность руководителю по выполненным работам;
· Разработка внутренних инструкций и ведение базы знаний;
· Взаимодействие с другими ИТ-службами и внутренними, и внешними;
· Знание и соблюдение внутренних инструкций организации.

Требования:
· Уверенное знание языка запросов sql;
· Знания реляционных СУБД;
· Опыт работы с ОС Linux, ОС Windows;
· Опыт работы с кластерами K8S;
· Опыт работы с Helm, ArgoCD, Ansible;
· Опыт работы с ELK, Grafana;
· Опыт работы с Kafka;
· Опыт работы с Vault Hashicorp;
· Опыт работы с GitOps подходом;
· Spring Framework;
· HP Service Manager.
edited 09:31
\ No newline at end of file diff --git a/interview/Bank VTB.md b/interview/Bank VTB.md new file mode 100644 index 0000000..64b4fa7 --- /dev/null +++ b/interview/Bank VTB.md @@ -0,0 +1 @@ +
Компания: Банк ВТБ (он же Иннотех, он же T1)
Позиция: Прикладной администратор (L3), а по факту: Инженер по сопровождению ПО
Кто проводит: Руководитель направления, тимлид, HR + главный инженер
Атмосфера:
Хорошая
Адекватный рук
Адекватный главный
HR в фоне тоже ок
Трудоустройство: ТК РФ
График: 5/2 (неделя офиса и неделя дома на постоянной основе)
Вакансия: https://hh.ru/vacancy/133039720?from=share_ios
HR: Надя Смельчук / https://t.me/NVK23
Вилка: ~250к чистыми
Длительность: 20-30 мин.
✅Статус: Оффер на 250 тысяч чистыми (287.500 грязными), не включая премию (премия: 1 оклад в год в марте) + индексация 5-10% раз в год от ЗП
❗️Советы:
- Идти можно, атмосфера приятная и дружелюбная на собесе, даже хвалили за профессионализм.
- Очень тяжелая анкета безопасности (полные копии всех паспортов, вкл военник, включая список стран, в которых был за последние 5 лет).

Вопросы (да, даже так бывает, одна компания занимается техно-дрочью и дурью выше ☝️, а другим нужен специалист с закрытием потребностей здесь и сейчас):

- Только формальные, даже не буду озвучивать.
- Спросили только о себе (ну и так поболтали за жизнь и хобби, сказал, что играю на гитаре, занимаюсь сборкой ПК и пишу pet-проект CI/CD в свободное время).
- Взяли сразу без технических вопросов.

Технические вопросы:
- Отсутствуют.edited 16:21
\ No newline at end of file diff --git a/interview/Bi.Zone.md b/interview/Bi.Zone.md new file mode 100644 index 0000000..2768d82 --- /dev/null +++ b/interview/Bi.Zone.md @@ -0,0 +1 @@ +
Компания: Bi.Zone
Позиция: полу-DevOps/полу-админ Linux и мониторинга в направление SOC
Кто проводит: Тимлид (Дмитрий) и старший инженер Валера
Атмосфера: Хорошая, у тимлида норм характер, технарь, Валера - универсал во всех вопросах
Трудоустройство: ТК РФ
График: 5/2 (1 день офис еженедельно, 4 дня удаленка на постоянке)
Вакансия: HH
HR: @curlykatarin
Вилка: от 250к чистыми
Длительность: 1ч 13мин
❗️Советы:
- Мне не хватило знаний по масштабированию проекта на 300к серверов, а также по K8s (учить базу: балансировка трафика, нагрузка, Helm, Configmap и прочее).

Вопросы с собеседования:

Спросят про опыт работы (моя задача была переспросить встречным вопросом, а кого они ищут и далее подстроить свой ответ, чтобы залезть в шкуру того инженера, которого ищут они)

Вопросы:

- Как будешь менять параметры ядра Linux?
- Что такое systemd?
- Зачем нужна директория /etc/systemd/system - что там лежит и зачем?
- Как будешь проверять доступность порта?
- Как в облаке и по какому порту будешь делить две сети DNS?
- С какими облаками работал?
- Как в Ansible role переписать переменную?
- С каким CI/CD работал + пример пайпа?
- Что такое AT CI/CD, ET CI/CD (как оказалось, это имеет отношение к пайпу Kafka)?
- Работал с какими версиями Kafka и что это такое?
- Как будешь деплоить мониторинг 300.000 серверов архитектурно (ответил Grafana + Prometheus + Mimir + Minio и далее подробно развернул, что и как будет работать)
- У нас есть 300.000 метрик от кастомных агентов (мы их можем только получать), по какому формату и чем будешь их собирать с учетом того, что они JSON (ответил, что парсинг будет через JQ + кастомный сбор через Node Exporter textfile-collector в случае стандартной pull модели и в случае push модели через Pushgateway)
- С кубом работал? Какой опыт с Helm и с разворотом кластеров K8S?
- Со стеком ELK работал? Помимо filebeat что еще разворачивал?
- Ты разворачивал с нуля стек ELK с агентами?
- В чем разница между ELK и OpenSearch?
- Ты мониторил стандартными службами мониторинга стек ELK? Если да, то как?
- С каким алертингом работал и как он был реализован?edited 15:18
\ No newline at end of file diff --git a/interview/Dit Moscow.md b/interview/Dit Moscow.md new file mode 100644 index 0000000..392530a --- /dev/null +++ b/interview/Dit Moscow.md @@ -0,0 +1 @@ +
Компания: ДИТ Москвы (ГКУ Инфогород)
Позиция: Инженер по мониторингу (зонтичное направление) с элементами DevOps (K8s и Ansible)
Кто проводит: Руководитель направления, тимлид, HR + доп. рук
Атмосфера:
- Хорошая
- Адекватный тимлид Костиков
- HR в фоне тоже очень ок
Трудоустройство: ТК РФ
График: 5/2 без инцидентов и задержек (полная удаленка)
Вакансия:
https://hh.ru/vacancy/134159868
HR: Карина @KarinaGerasimovaHR
Вилка: 250к чистыми (должна быть годовая премия)
Длительность: 45 мин
✅Статус: предоффер + осталось согласование в виде собеса с главным директором ДИТ-а

Вопросы (в основном они рассказывали о себе и чем предстоит заниматься мне):

Расскажи о себе?
Какой опыт с Linux?
Что будешь делать если упал сервер?
Если сервер на VmWare, как будешь спасать VM? Какую диагностику будешь делать?
С Zabbix работал? Какой опыт?
Какой опыт с K8s?
Какой у тебя стек?
Что такое Monq? Какой опыт работы?

С чем предстоит тебе работать:
- Зонтичный мониторинг на основе Monq
- Писать скрипты на основе Monq для зонтичного мониторинга
- Иногда писать на C#
- Плотная работа с Zabbix и агентами
- В стеке у нас GitLab
- Ansible для автоматизации мониторинга
- Мы поддерживаем инфру Москвы16:29
\ No newline at end of file diff --git a/interview/VK Cloud.md b/interview/VK Cloud.md new file mode 100644 index 0000000..db4e280 --- /dev/null +++ b/interview/VK Cloud.md @@ -0,0 +1,4 @@ +Компания VK Cloud +Позиция: SRE-инженер +Кто проводит: Тимлид (Дмитрий) клауд платформы +Атмосфера: Вроде адекватная, а вроде с подъебоном, зато смеялись, у тимлида более менее характер, умеет хвалить, глубоко знает сетевую базу, слегка любит доебаться до истины
Трудоустройство: ТК РФ через интегратор Глобус в VK Cloud
Вилка: от 250к чистыми

Вопросы с собеседования (по факту дрочево и доебалово вплоть до того как изнутри работает каждая команда Linux либо сетевая утилита с полуподъебами и смешками, по факту собес прошел, как в атмосфере Сбера):

Спросят, что делал последние полгода

Спросят по сетям: разница авторитетного и рекурсивного dns (как resolving выполняется, как IP появляется)
Есть команда Traceroute. Вводим Traceroute 8.8.8.8 и получаем список IP адресов. Как Traceroute получает IP промежуточных хостов?
Что такое vlan и vxlan?
Есть load average в Linux - как считается?
HTTP/S / TLS/SSL - как работают эти протоколы и зачем нужны + методы HTTP (get, post, put, delete, patch)
TCP/UDP - что такое и как устанавливается соединение
Что такое MTU
Что такое пакет, кадр, фрейм
Утилита ping на каком протоколе работает и на каком уровне модели OSI?
Какая есть динамическая маршрутизация (протоколы), что это?
По сетям база: если ввожу vk.com и нажимаю enter - опиши весь путь до открытия страницы
когда запускаешь free, есть колонка free и available - чем они отличаются? если free на 0, а available много - это плохо?
Кейс: сервер тормозит, с каких 3-5 команд начнешь диагностику?
Как найти pid нужного процесса и убить?
Как найти порт нужного процесса и проверить открыт он или закрыт?
pid 1 - это какой процесс?
Команда kill и ее популярные сигналы
Как убить процесс: 2 способа
В Linux команды DF и DU - что они делают (с подвохом: у тебя df показывает, что место на диске закончилось, а du что всё ок - в чем причина (причина в дескрипторе который занял файл, который застрял внутри убитого процесса приложения)?
Если сделаешь ps -aux / ps -ef - что увидишь?
Как назначить владельца папки в Linux?
Как выдаются права? Что такое 777 и 755
Что делает awk и ее варианты использования?
У тебя есть большой лог: как найти все строки с ошибкой connection timeout?
Что такое cgroups и namespaces в Linux?
Как найти порт нужного приложения и проверить открыт он или закрыт?
Что такое inode?
Что такое дескрипторы?
Reference count в файловом дескрипторе
Ссылки жесткие и мягкие (хард и софт) - в чем разница и зачем нужны, что происходит при удалении файла

В чем отличия виртуализации от контейнеризации?
как докер (сама среда) понимает, что контейнер умер и помечает его как exit?
Что такое докер рантайм и зачем нужен (containerd)?
как зайти внутрь контейнера?
Как что-то скопировать в работающий контейнер?
Если убить процесс docker pid - запущенные контейнеры сдохнут?
Как работает сеть в докер?
много ли процессов внутри одного контейнера?
У контейнеров есть PID?
По docker: entrypoint vs cmd - разница

Назвать основные компоненты K8S
Как работает трафик в K8S

Повтори, как выглядит ansible inventory + роли + плэйбуки
В чем разница между ролью и плэйбуком Ansible
Могут ли существовать роли и плейбуки отдельно и что будет, если убрать один из них
Где в Ansible живут переменные: роли, плэйбуки или инвентари и зачем используются?
Далее он с подвохом выводит Ansible конфиг и нужно угадать что в нём (директивы с именами серверов), далее он выводит в терминал: ansible -m shell inventory.yml {{ansible_hostname}} и спрашивает, что делает эта команда с ключами поэтапно

Если работал с OpenStack (облако) спросят:
Сeph - что такое
Как происходит создание виртуальных машины в OpenStack

По базам данных: что такое кластер (в БД)?
Как происходит репликация базы данных
sql: есть таблица с именем orders - какая команда выведет количество записей, где id = 15?
Что такое quorum в мастер-мастер?
Рассказать про split brain, и почему quorum помогает? \ No newline at end of file diff --git a/interview/checklist.md b/interview/checklist.md new file mode 100644 index 0000000..c835ef7 --- /dev/null +++ b/interview/checklist.md @@ -0,0 +1,36 @@ +# Чек-лист подготовки + +Статус проработки по категориям — актуализировать по мере выполнения кейсов. Полная карта — в [../README.md](../README.md). Порядок проработки теперь ведётся по частотности тем в реальных собеседованиях — подробный пошаговый план с обоснованием каждого шага в [study-plan.md](study-plan.md), здесь — только статус. + +## Порядок проработки (см. обоснование в [study-plan.md](study-plan.md)) + +1. **[ ] Linux/Bash** ([stack/linux-bash/](../stack/linux-bash/)) — спрашивают почти на каждом собесе, фундамент для Docker/K8s. +2. **[ ] Сети** ([stack/networking/](../stack/networking/)) — весь техсобес VK Cloud построен на этом. +3. **[ ] Docker** ([stack/docker/](../stack/docker/)) — внутренности (containerd, namespaces/cgroups), фундамент для K8s. +4. **[ ] Kubernetes** ([stack/kubernetes/](../stack/kubernetes/)) — главный разрыв, спрашивали в 4 из 6 собесов. +5. **[x] Мониторинг** ([stack/monitoring/](../stack/monitoring/)) — база готова; дополнено вопросами про масштаб (300k серверов, jq/textfile collector) — перепройти новые вопросы. +6. **[ ] PostgreSQL/БД** ([stack/databases/](../stack/databases/)) — репликация sync/async, join'ы, split brain. +7. **[ ] CI/CD** ([stack/ci-cd/](../stack/ci-cd/)) — «нарисовать пайп» спрашивали в 3 из 6 собесов. +8. **[ ] ELK** ([stack/elk/](../stack/elk/)) — sizing логов vs метрик. +9. **[ ] Kafka** ([stack/kafka/](../stack/kafka/)) — реже, но встречается. +10. **[ ] Vault** ([stack/vault/](../stack/vault/)) — реже, но встречается. +11. **[x] Ansible** ([stack/ansible/](../stack/ansible/)) — база готова; дополнено вопросами про переменные и разбор CLI-команды из VK Cloud/Bi.Zone — перепройти новые вопросы. +12. **[x] Nginx** ([stack/nginx/](../stack/nginx/)) — готово, добавлена секция «Нагрузка и цифры». +13. **[ ] Тестирование** ([stack/testing/](../stack/testing/)) — TODO, более старый опыт (ОКБ СУХОЙ), не всплывал в реальных собесах, низкий приоритет. +14. **[ ] Языки (Python/SQL/C-C++)** ([stack/languages/](../stack/languages/)) — TODO, вспомогательный блок, низкий приоритет. +15. **[ ] Прогон по реальным собесам** — шаг 9 в [study-plan.md](study-plan.md), финальная проверка после закрытия всех категорий выше. + +## Отдельно + +- **[ ] Цифры нагрузки** ([../legend/CAPACITY.md](../legend/CAPACITY.md)) — прогнать замеры на своих стендах (nginx, Prometheus, pgbench, Kafka), при желании — заполнить реальные цифры со своего места работы. +- **[ ] Общие вопросы** ([common-questions.md](common-questions.md)) — дозаполнить персональные ответы (причина смены работы, слабые стороны, пример ошибки) перед реальными собеседованиями. +- **[ ] Легенда** ([../legend/LEGEND.md](../legend/LEGEND.md)) — перечитать целиком после закрытия всех TODO-категорий, проверить непротиворечивость с финальными кейсами, включая новый блок «Домашняя лаборатория». +- **[ ] Резюме** ([../RESUME.md](../RESUME.md)) — финальная шлифовка после того, как легенда и кейсы устоялись. + +## Как считать категорию «готовой» + +Категория готова, когда: +1. Стенд из `CASE.md` реально поднят и пройден руками (не только прочитан). +2. `QUESTIONS.md` можно закрыть, не подглядывая — ответы звучат как свои слова, а не пересказ документации. +3. Нет противоречий с [../legend/LEGEND.md](../legend/LEGEND.md). +4. Для pet-категорий (K8s, PostgreSQL, сети, Kafka, Vault) — честно проговаривается граница «это pet-проект, не рабочий опыт» без запинки. diff --git a/interview/common-questions.md b/interview/common-questions.md new file mode 100644 index 0000000..632b620 --- /dev/null +++ b/interview/common-questions.md @@ -0,0 +1,83 @@ +# Базовые вопросы собеседования (не по стеку) + +Опираются на [../RESUME.md](../RESUME.md) и [../legend/LEGEND.md](../legend/LEGEND.md). Ответы — черновые, от первого лица; дорабатывать своими словами перед реальным собеседованием, чтобы не звучало заученно (см. [../AI_GUIDELINES.md](../AI_GUIDELINES.md)). + +### Расскажите о себе + +Начинал с тестирования встроенного ПО в ОКБ СУХОЙ — писал тестовые сценарии, автоматизировал проверки на Selenium, работал на стендах на CentOS. Через полтора года перешёл в АО ТНИИС на позицию инженера по сопровождению — там уже занимался мониторингом (Grafana, Prometheus, Mimir), автоматизацией через Ansible и поддержкой инфраструктуры на Astra Linux. Сопровождаю внутренний контур предприятия — Java-сервисы за балансировкой Nginx на парке VM; предприятие режимное, поэтому детали продукта не раскрываю, но со стороны мониторинга, автоматизации и инцидентов могу рассказать всё в деталях (подробный нарратив — [../legend/STORY.md](../legend/STORY.md)). Сейчас ищу позицию инженера сопровождения или DevOps-инженера, где можно развивать именно эту сторону — автоматизацию и эксплуатацию. + +### Почему решили сменить место работы? + +Иду не «от», а «к»: текущее предприятие режимное, и это по своей природе закрывает ряд направлений, которые интересно развивать дальше — нет облаков, нет Kubernetes в проде, нет удалённого/гибридного формата (доступ к контуру только из офиса). Хочется расти в сторону современной эксплуатации и DevOps — облачная и контейнерная оркестрация, более гибкий формат работы — а это ограничено спецификой текущего места, а не желанием или возможностями команды. Полный разбор — [../legend/STORY.md](../legend/STORY.md) → «Причина ухода». + +*Как не надо:* ругать текущего/бывшего работодателя, жаловаться на коллег или начальника — интервьюер запомнит не причину, а то, что вы негативно отзываетесь о людях за глаза. + +### Почему вам интересна именно эта вакансия? + +[Заполнить под конкретную вакансию перед откликом — что конкретно в описании вакансии пересекается с реальным опытом: мониторинг, Ansible, Docker, Nginx.] + +### Расскажите о сложной задаче, которую вы решали + +Пример на основе легенды (АО ТНИИС): настройка Ansible-ролей с интеграцией тестирования в CI через Molecule и TeamCity — сложность была в том, чтобы роли были одновременно и рабочими, и покрытыми тестами, не ломающими деплой. Разбирался с идемпотентностью ролей и с тем, чтобы тесты в CI реально ловили проблемы до продакшена, а не просто формально проходили. (Проработать детальнее после выполнения кейса [../stack/ansible/CASE.md](../stack/ansible/CASE.md) — рассказывать нужно про то, что реально прогонялось руками.) + +*Как не надо:* выбирать пример, где сложность была не в самой задаче, а в конфликте с человеком, или отвечать абстрактно («было много сложных задач») без конкретики: что именно было сложно, какие варианты рассматривались, почему выбран именно этот. + +### В чём ваши сильные стороны применительно к этой роли? + +Опыт на стыке тестирования и эксплуатации — понимаю, как проверить, что решение реально работает, а не просто «задеплоено». Разбираюсь в мониторинге не только с точки зрения «настроить дашборд», но и с точки зрения «что эта метрика значит и когда она сигнализирует о проблеме». + +### В чём слабые стороны / что развиваете сейчас? + +[Заполнить честно — например, ELK/CI-CD проработаны пока меньше, чем мониторинг и Ansible, сейчас в процессе закрытия этого пробела (см. чек-лист [checklist.md](checklist.md)).] + +*Как не надо:* выдавать замаскированное достоинство за слабость («слишком перфекционист») — это штамп, который интервьюеры слышат постоянно и не воспринимают всерьёз. Лучше называть реальный, но не критичный для роли пробел и прямо говорить, что с ним делаете. + +### Как вы расставляете приоритеты при нескольких инцидентах одновременно? + +Смотрю на импакт: что сейчас реально влияет на пользователей/сервисы, а не на то, что «громче» по ощущениям. Мониторинг (Grafana/Prometheus) — первый источник, чтобы понять масштаб проблемы, логи (ELK) — чтобы найти причину. Сначала стабилизация (например, откат/рестарт), потом уже разбор первопричины. + +### Готовы ли к удалённой работе, гибриду, командировкам? + +Готов к удалённой работе и гибриду (см. [../RESUME.md](../RESUME.md)), к командировкам не готов — это указано в резюме и остаётся так. + +### Расскажите о ситуации, когда вы допустили ошибку + +[Заполнить конкретным честным примером, связанным с одним из проработанных кейсов — например, ошибка в конфиге, которую поймал Molecule/тест до продакшена, и что из этого вынес.] + +*Как не надо:* отрицать, что ошибки вообще были, или перекладывать вину на других — это читается как неспособность к рефлексии. Ценится не факт ошибки, а то, что из неё было сделано (какой вывод, что изменилось в процессе после). + +### С какими облаками работал? Есть опыт с OpenStack? + +Прямого рабочего опыта администрирования облачных платформ (AWS/GCP/Yandex Cloud/OpenStack) у меня нет — рабочий опыт (АО ТНИИС, ОКБ СУХОЙ) строился на собственной инфраструктуре компаний. При этом смежные концепции знакомы через pet-практику: домашний кластер Kubernetes ([../stack/kubernetes/CASE.md](../stack/kubernetes/CASE.md)) даёт понимание оркестрации и абстракций, на которых строятся облачные платформы (в том числе managed Kubernetes-сервисы в облаках). Если конкретно про OpenStack и Ceph — это отдельная область, с которой предметно не работал, и на собеседовании честно обозначил бы это, а не пытался бы изобразить опыт, которого нет. + +### Есть ли у вас вопросы к нам? + +[Подготовить 2-3 вопроса под конкретную вакансию: про стек, про процесс on-call/дежурств, про то, как устроен CI/CD в компании.] + +## Вопросы про отдел и процессы + +Опираются на [../legend/STORY.md](../legend/STORY.md) и [../legend/DEPT_STACK.md](../legend/DEPT_STACK.md) — черновые ответы, доработать своими словами, состав команды и бытовые детали подтвердить/поправить перед реальным собеседованием. + +### Кто ставил задачи? Какой был тасктрекер? + +Задачи ставил начальник отдела на дейлике, часть — приходила от смежных отделов через заявки. Тасктрекер — Jira, спринты в Agile-процессе отдела. + +### Сколько человек в команде и с кем вы работали? + +Небольшой отдел сопровождения и мониторинга — начальник отдела и порядка нескольких инженеров сопровождения/мониторинга плюс Java-разработчики со стороны сопровождаемых сервисов. Плотно взаимодействовал с группой Linux-администраторов (обслуживают парк VM) и с отделом разработки по инцидентам. Подробнее — [../legend/STORY.md](../legend/STORY.md) → «Состав отдела». + +### Где живёт ваша инфраструктура? Кто её обслуживает? + +On-premise: парк VM под Astra Linux, без внешних облаков — режимный контур предприятия. Обслуживает группа Linux-администраторов, я — со стороны мониторинга и автоматизации конфигурирования (Ansible). + +### Как проходило внедрение изменений/релизов? + +Конфиг или роль пишется и тестируется (Ansible + Molecule), пушится в Gitea, задача заводится в Jira с описанием, дальше раскатка через TeamCity — часто совместно с администраторами, если изменение затрагивает их зону ответственности. + +### Куда вы смотрите в Grafana? На какие дашборды? + +Node Exporter (загрузка железа по VM), Nginx (статус балансировщика), Blackbox Exporter (сроки TLS-сертификатов), дашборды под конкретные сопровождаемые сервисы — рестарты, ошибки, задержка ответа. + +### В каком формате выгружали дашборды? Как было устроено версионирование? + +JSON, версионировались в Gitea вместе с остальными конфигами отдела, раскатка — TeamCity. Подробнее — [../legend/STORY.md](../legend/STORY.md) → «Версионирование дашбордов Grafana». diff --git a/interview/real-interviews.md b/interview/real-interviews.md new file mode 100644 index 0000000..946558e --- /dev/null +++ b/interview/real-interviews.md @@ -0,0 +1,64 @@ +# Разбор реальных собеседований + +Шесть собеседований на позиции DevOps-инженер / инженер сопровождения / SRE, вилка везде ~250к+ чистыми. Разборы — сырые заметки в этой же папке: [Реалист банк.md](Реалист%20банк.md), [Bi.Zone.md](Bi.Zone.md), [Alfa-Bank.md](Alfa-Bank.md), [Bank%20VTB.md](Bank%20VTB.md), [Dit%20Moscow.md](Dit%20Moscow.md), [VK%20Cloud.md](VK%20Cloud.md). Этот файл — их анализ: что спрашивают чаще всего, какие темы репозиторий не покрывал, и что из этого значит для плана подготовки ([study-plan.md](study-plan.md)). + +## Матрица тем × компании + +| Тема | Реалист | Bi.Zone | Альфа | ВТБ | ДИТ Москвы | VK Cloud | Итого | +|---|---|---|---|---|---|---|---| +| Kubernetes | ✅ | ✅ | ✅ | — | — | ✅ | 4 | +| Linux-внутренности (df/du, inode, cgroups, sysctl...) | — | ✅ | — | — | — | ✅ | 2 | +| Сети (OSI, TCP/DNS/TLS, vlan/vxlan) | — | — | — | — | — | ✅ | 1 (но целиком собес) | +| Docker внутренности | ✅ | — | — | — | — | ✅ | 2 | +| Мониторинг (Prometheus/Grafana/Mimir, масштаб) | ✅ | ✅ | — | — | ✅ (Zabbix/Monq) | — | 3 | +| ELK / логи vs метрики | ✅ | ✅ | — | — | — | — | 2 | +| PostgreSQL/БД | ✅ | — | ✅ | — | — | ✅ | 3 | +| CI/CD (нарисовать пайп) | ✅ | ✅ | ✅ (частично) | — | ✅ (GitLab в стеке) | — | 3 | +| Ansible | — | ✅ | ✅ | — | ✅ (в стеке) | ✅ | 4 | +| Kafka | — | ✅ | ✅ (упомянут в требованиях) | — | — | — | 2 | +| Vault | — | — | ✅ | — | — | — | 1 | +| Собеса без техвопросов вообще | — | — | — | ✅ | — | — | 1 | + +**Вывод:** Kubernetes и Ansible — самые частые темы (4 из 6). Мониторинг, PostgreSQL, CI/CD — по 3. Сети и Linux-внутренности встречаются реже по числу компаний, но на VK Cloud составляют почти весь технический собес — то есть если такая тема всплывает, глубина спроса высокая. ВТБ показывает: не везде вообще будут технические вопросы — иногда решает просто адекватное человеческое общение и упоминание pet-проекта. + +## Разрывы с текущим покрытием репозитория (на момент этого разбора) + +- **Kubernetes** — не было ни в резюме, ни в легенде, ни в стеке, при этом 4 из 6 компаний спрашивали (включая «нарисовать 2 ЦОД под K8s», ConfigMap vs Secret, Helm, PostgreSQL на 2 нодах K8s). Закрыто: [../stack/kubernetes/](../stack/kubernetes/). +- **PostgreSQL/БД** — не было отдельной категории (SQL был вписан как вспомогательный язык, без кейса по самой СУБД). Закрыто: [../stack/databases/](../stack/databases/). +- **Сетевая база** — не было вообще ни как категория, ни в легенде. Закрыто: [../stack/networking/](../stack/networking/). +- **Kafka, Vault** — упоминались только как «желательно» в требованиях вакансий, не было категорий. Закрыто: [../stack/kafka/](../stack/kafka/), [../stack/vault/](../stack/vault/). +- **Linux/Docker внутренности** — категории существовали как TODO-заготовки без вопросов такого уровня (df vs du с подвохом дескриптора, cgroups/namespaces, containerd, entrypoint vs cmd). Доработаны. +- **Вопросы про масштаб/ёмкость** («сколько Гб логов при 15 Гб метрик», «мониторинг 300 000 серверов», «с каким трафиком сталкивался») — не было справочника цифр вообще. Закрыто: [../legend/CAPACITY.md](../legend/CAPACITY.md). + +## Паттерны вопросов, которые стоит знать заранее + +### «Нарисуй архитектуру» + +Реалист: два ЦОД под K8s с балансировщиком, на каких виртуалках что работает, etcd/consul на тех же виртуалках или отдельно. Bi.Zone: как задеплоить мониторинг на 300 000 серверов. Реалист: нарисовать CI/CD пайп от `git push` до продакшена, что в CI и что в CD. Это не вопрос «знаешь ли ты факт», а вопрос «умеешь ли ты рассуждать вслух про архитектуру» — важно проговаривать компоненты и их связи, а не пытаться вспомнить единственно верную схему. Тренировка: в [study-plan.md](study-plan.md) шаг «прогон» включает устное проговаривание архитектур из [../legend/CAPACITY.md](../legend/CAPACITY.md) и кейсов. + +### Живая работа в терминале + +Реалист и VK Cloud спрашивали не «расскажи», а «покажи»: `docker ps`, разница `-a`, `docker logs`, вывести поды и логи в K8s, посмотреть диск/ОЗУ/процессор в терминале, найти pid и порт процесса. Это прямое следствие того, что кейсы репозитория должны быть реально прогнаны руками — если стенд не поднят и команды не набраны вживую, такой вопрос на реальном собесе не пройти. + +### Вопросы на масштаб и цифры + +- Реалист: «сколько Гб логов будет в ELK, если в Prometheus уже 15 Гб метрик» — вопрос не имеет единственного числа-ответа, интервьюер проверяет понимание, что логи объёмнее метрик на порядок и почему (текст vs агрегированные ряды), плюс что в таком случае разумнее — логи или метрики. Разобрано в [../legend/CAPACITY.md](../legend/CAPACITY.md). +- Bi.Zone: «как задеплоишь мониторинг на 300 000 серверов архитектурно» — ответ, который был засчитан: Grafana + Prometheus + Mimir + MinIO (объектное хранилище под блоки Mimir) с горизонтальным шардированием. Зафиксировано в [../stack/monitoring/QUESTIONS.md](../stack/monitoring/QUESTIONS.md) и [../legend/CAPACITY.md](../legend/CAPACITY.md). +- Bi.Zone: сбор 300 000 JSON-метрик от кастомных агентов — предложенный на собесе ответ: `jq` для парсинга + `node_exporter` textfile collector (pull) либо Pushgateway (push). Тот же паттерн стоит прогнать на своей лабе в меньшем масштабе, чтобы говорить не только теоретически. + +### Поведенческие приёмы, которые сработали + +- **Bi.Zone** — на вопрос «расскажите про опыт» встречный вопрос «а кого вы, собственно, ищете?» и подстройка ответа под профиль — не подмена фактов, а расстановка акцентов на то, что реально релевантно роли. +- **ВТБ** — упоминание pet-проекта («пишу pet-проект CI/CD в свободное время») в неформальной части разговора сыграло в плюс и, по всей видимости, было воспринято как признак интереса к профессии сверх рабочих задач. Это прямо подтверждает решение вести K8s/Kafka/Vault/PostgreSQL как честные pet-лабы, а не выдавать их за рабочий опыт — сама рамка «pet-проект» работает в интервью, а не мешает. + +## Сверка покрытия + +Практически все технические вопросы из шести разборов замаплены на конкретные `QUESTIONS.md`: Kubernetes → [../stack/kubernetes/QUESTIONS.md](../stack/kubernetes/QUESTIONS.md), PostgreSQL/БД → [../stack/databases/QUESTIONS.md](../stack/databases/QUESTIONS.md), сети → [../stack/networking/QUESTIONS.md](../stack/networking/QUESTIONS.md), Docker → [../stack/docker/QUESTIONS.md](../stack/docker/QUESTIONS.md), Linux/Bash → [../stack/linux-bash/QUESTIONS.md](../stack/linux-bash/QUESTIONS.md), CI/CD → [../stack/ci-cd/QUESTIONS.md](../stack/ci-cd/QUESTIONS.md), ELK → [../stack/elk/QUESTIONS.md](../stack/elk/QUESTIONS.md), Kafka → [../stack/kafka/QUESTIONS.md](../stack/kafka/QUESTIONS.md), Vault → [../stack/vault/QUESTIONS.md](../stack/vault/QUESTIONS.md), Ansible → [../stack/ansible/QUESTIONS.md](../stack/ansible/QUESTIONS.md), мониторинг/масштаб → [../stack/monitoring/QUESTIONS.md](../stack/monitoring/QUESTIONS.md) + [../legend/CAPACITY.md](../legend/CAPACITY.md). + +**Сознательно не углублено:** вопросы VK Cloud про OpenStack и Ceph («если работал с OpenStack») — у кандидата нет облачного опыта вообще (см. честный ответ в [common-questions.md](common-questions.md)), поэтому вместо того чтобы выдумывать глубину, которой нет, зафиксирован прямой честный ответ «не работал, могу рассуждать только на уровне общих принципов» — это соответствует правилу честности кейсов в [../AI_GUIDELINES.md](../AI_GUIDELINES.md). По той же причине не проработан отдельно один нечёткий вопрос Bi.Zone («как в облаке и по какому порту будешь делить две сети DNS») — формулировка в исходной заметке неоднозначна, и правильная тактика на реальном собеседовании тут — переспросить, что именно имеется в виду, а не гадать. + +## Как использовать эти файлы дальше + +1. Перед конкретным собеседованием — перечитать разбор компании похожего профиля (банк → Альфа/Реалист/ВТБ, облако/инфраструктура → VK Cloud/Bi.Zone, госсектор → ДИТ) и держать в голове её характерные вопросы. +2. Не использовать оценочные комментарии из чужих заметок («тухлятина токсичная», имена интервьюеров) как материал для легенды или ответов — это личные впечатления автора заметок о конкретных людях/компаниях, не относящиеся к подготовке контента. +3. Полный список вопросов из всех шести собесов замаплен на конкретные ответы в `QUESTIONS.md` соответствующих категорий — см. таблицу покрытия в [study-plan.md](study-plan.md). diff --git a/interview/study-plan.md b/interview/study-plan.md new file mode 100644 index 0000000..d0e4625 --- /dev/null +++ b/interview/study-plan.md @@ -0,0 +1,68 @@ +# Пошаговый учебный план + +Порядок построен на частотности тем в реальных собеседованиях ([real-interviews.md](real-interviews.md)), а не на порядке категорий в резюме. Каждый шаг — фиксированный цикл: **теория → лаба руками → замер цифр (где применимо) → прогон вопросов → к каким собесам готовит**. Не переходить к следующему шагу, пока текущий не закрыт по критериям готовности из [checklist.md](checklist.md). + +## Шаг 1. Linux — база и внутренности + +- Теория + лаба: [../stack/linux-bash/CASE.md](../stack/linux-bash/CASE.md) — свои bash-скрипты, systemd timer, диагностический сценарий df/du. +- Прогон: [../stack/linux-bash/QUESTIONS.md](../stack/linux-bash/QUESTIONS.md). +- Готовит к: VK Cloud (почти весь техсобес), Bi.Zone, ДИТ Москвы (диагностика упавшего сервера). +- Почему первым: спрашивают практически везде, и это фундамент, на который опираются Docker/K8s/CI-CD вопросы (namespaces/cgroups и т.д.). + +## Шаг 2. Сети + +- Теория + практикум команд: [../stack/networking/CASE.md](../stack/networking/CASE.md) — `dig`, `traceroute`, `curl -v`, `ss`. +- Прогон: [../stack/networking/QUESTIONS.md](../stack/networking/QUESTIONS.md). +- Готовит к: VK Cloud (сетевая база — весь технический собес). +- Почему вторым: не самая частая тема по числу компаний, но если всплывает — спрашивают глубоко; и она нужна как фундамент для понимания трафика в K8s (шаг 4) и балансировки. + +## Шаг 3. Docker изнутри + +- Теория + лаба: [../stack/docker/CASE.md](../stack/docker/CASE.md) — multi-stage build, сети, OOM kill, диагностика. +- Прогон: [../stack/docker/QUESTIONS.md](../stack/docker/QUESTIONS.md). +- Готовит к: VK Cloud, Реалист банк, Альфа-Банк (базовые команды). +- Почему здесь: опирается на понимание namespaces/cgroups из шага 1, и сам является фундаментом для Kubernetes (шаг 4). + +## Шаг 4. Kubernetes — главный разрыв + +- Если K8s не знаком с нуля: пошаговый учебный курс [../stack/kubernetes/LEARNING.md](../stack/kubernetes/LEARNING.md) — 14 модулей от установки инструментов до собственного Helm-чарта, каждый модуль с объяснением «зачем» и практикой руками. +- Теория + лаба (конспект для повторения): [../stack/kubernetes/CASE.md](../stack/kubernetes/CASE.md) — pet-кластер на `kind`, Helm-деплой mониторинга, диагностика. +- Прогон: [../stack/kubernetes/QUESTIONS.md](../stack/kubernetes/QUESTIONS.md). +- Готовит к: Реалист банк, Bi.Zone, Альфа-Банк, VK Cloud — 4 из 6 собесов. +- Почему здесь: самый частый пробел в исходном покрытии репозитория; опирается на понимание Docker (шаг 3) и сетей (шаг 2). + +## Шаг 5. Мониторинг — углубление и архитектура масштаба + +- Кейс уже готов: [../stack/monitoring/CASE.md](../stack/monitoring/CASE.md) (переслушать/повторить руками, если давно не прогонялся). +- Новое: раздел «Нагрузка и цифры» в CASE.md + вопросы про масштаб 300 000 серверов, jq/textfile collector/Pushgateway, HTTP 400/500 в [../stack/monitoring/QUESTIONS.md](../stack/monitoring/QUESTIONS.md). +- Справочник цифр: [../legend/CAPACITY.md](../legend/CAPACITY.md) — заполнить лабные замеры и (по желанию) реальные цифры с работы. +- Готовит к: Реалист банк, Bi.Zone, ДИТ Москвы. + +## Шаг 6. PostgreSQL + +- Теория + лаба: [../stack/databases/CASE.md](../stack/databases/CASE.md) — master/replica, sync/async, `pgbench`. +- Прогон: [../stack/databases/QUESTIONS.md](../stack/databases/QUESTIONS.md). +- Готовит к: Реалист банк, Альфа-Банк, VK Cloud. + +## Шаг 7. CI/CD + ELK + +- CI/CD: [../stack/ci-cd/CASE.md](../stack/ci-cd/CASE.md) — Gitea + пайплайн, прогоняющий `molecule test` из шага Ansible. +- ELK: [../stack/elk/CASE.md](../stack/elk/CASE.md) — сбор логов уже готового Nginx-кейса. +- Прогон: [../stack/ci-cd/QUESTIONS.md](../stack/ci-cd/QUESTIONS.md), [../stack/elk/QUESTIONS.md](../stack/elk/QUESTIONS.md). +- Готовит к: Реалист банк («нарисовать CI/CD пайп»), Bi.Zone (ELK, sizing логов), Альфа-Банк. + +## Шаг 8. Kafka + Vault + +- Kafka: [../stack/kafka/CASE.md](../stack/kafka/CASE.md) — однонодовый брокер, топики, consumer group, замер throughput. +- Vault: [../stack/vault/CASE.md](../stack/vault/CASE.md) — dev-режим, KV, политики доступа. +- Прогон: [../stack/kafka/QUESTIONS.md](../stack/kafka/QUESTIONS.md), [../stack/vault/QUESTIONS.md](../stack/vault/QUESTIONS.md). +- Готовит к: Bi.Zone (Kafka), Альфа-Банк (Kafka в требованиях, Vault). +- Почему последними: самые редкие темы по числу компаний (1-2 из 6), и обе — новые, не входящие в рабочий стек pet-технологии. + +## Шаг 9. Прогон: mock-собес по каждому реальному разбору + +Перечитать каждый файл в [../interview/](.) ([Реалист банк.md](Реалист%20банк.md), [Bi.Zone.md](Bi.Zone.md), [Alfa-Bank.md](Alfa-Bank.md), [Bank VTB.md](Bank%20VTB.md), [Dit Moscow.md](Dit%20Moscow.md), [VK Cloud.md](VK%20Cloud.md)) и вслух, без подглядывания в QUESTIONS.md, ответить на все вопросы оттуда — если запинаешься, вернуться к соответствующему QUESTIONS.md, но не переписывать ответ, а сформулировать его заново своими словами. Дополнительно — вслух проговорить обе архитектурные схемы («2 ЦОД под K8s», «мониторинг на 300 000 серверов») с листом бумаги/доской, не подсматривая в текст. + +## Общие вопросы + +[common-questions.md](common-questions.md) — самопрезентация и поведенческие вопросы — прорабатываются параллельно с любым шагом, не завязаны на порядок технического стека. diff --git a/interview/Реалист банк.md b/interview/Реалист банк.md new file mode 100644 index 0000000..c482a97 --- /dev/null +++ b/interview/Реалист банк.md @@ -0,0 +1 @@ +Компания: Реалист банк
Позиция: DevOps-инженер
Кто проводит: Тимлид платформы (Александр, работал в ОТП банке, на позиции год +-) + директор направления (Иван Ратников, 34 года)
Атмосфера: Очень хорошая, даже смеялись, тимлид добрый и не доёбчивый, директор тоже огонь и легкий на подхват
Трудоустройство: ТК РФ напрямую в штат банка (офис м.Таганка)
График: полная удаленка 5/2 без дежурств и прочей дичи
Вилка: от 250к чистыми
✅Оффер: 261к чистыми (300к ГРОСС, но договорились, что буду развиваться и они повысят), премий нет, но во втором полугодии 2026 должны быть, если верить Ивану Ратникову, то переговоры проведены на 80%, ДМС оплачивают 50/50
❗️Советы: Не советую устраиваться, так как сказали одно, а по факту было другое (сказали будет вводная часть и бадди - ничего не было) и мой тимлид начал увольняться в день моего первого выхода (он же мой бадди), но так и не уволился - атмосфера: дичь полная.

Вопросы с собеседования:

- Нарисовать два ЦОД под K8s с балансировщиком и рассказать на каких виртуалках всё это будет работать
- etcd или consul будут на одной виртуалке с K8s? Из чего состоит K8s: daemonset и тд
- Нарисовать CI/CD и как проливается код через GitLab CI - что будет в процессе CI и что в CD, рассказать с этапа, когда код уже запушен в GitHub, а образы в Nexus
- У тебя есть ELK и мониторинг Prometheus - сколько Гб данных логов будет в ELK, если в Prometheus уже 15Гб лежит под ногами. В данном случае, как лучше реализовать мониторинг: через логи или метрики?
- Расскажи про синхронный и асинхронный режим работы PostgreSQL - в чем разница?
- Как будешь разворачивать PostgreSQL на две ноды K8s?
- В терминале пробить данные по диску, ОЗУ, процессору, показать папку, перейти в директорию
- В терминале Linux показать docker ps, в чем разница между -a, docker logs, показать images, показать контейнеры - в чем разница между ними?
- Выведи логи K8s, выведи поды и тд
- К тебе пришел разработчик с HTTP Requests 400/500 - как будешь мониторить: каким способом и почему - логи или метрики и как будешь их собирать?

Александр: я пошел попить чайку и дальше на встречу 😂edited 14:13 \ No newline at end of file diff --git a/legend/CAPACITY.md b/legend/CAPACITY.md new file mode 100644 index 0000000..e95e778 --- /dev/null +++ b/legend/CAPACITY.md @@ -0,0 +1,72 @@ +# Справочник цифр нагрузки + +Отвечает на класс вопросов «с каким трафиком/масштабом сталкивался» и «как бы ты справился с масштабом X». Домашняя лаба физически не может воспроизвести продовые нагрузки крупной компании — честный подход, зафиксированный в [../AI_GUIDELINES.md](../AI_GUIDELINES.md), состоит из трёх слоёв цифр, которые никогда не смешиваются между собой: + +1. **Публичные пределы технологий** — задокументированные производителем/сообществом ориентиры, не выдаваемые за личный опыт. +2. **Замеры на своей лабе** — реальные цифры, полученные прогоном инструментов на CASE.md-стендах этого репозитория. +3. **Реальные масштабы с работы** — заполняются пользователем лично, только фактами со своего места работы; ИИ эти значения не придумывает и не подставляет. + +## 1. Публичные пределы технологий + +| Технология | Ориентир | Источник/пояснение | +|---|---|---| +| Nginx | Десятки тысяч RPS на ядро на статике; на проксировании (с TLS termination, upstream) — обычно тысячи RPS на инстанс, сильно зависит от бэкенда | Известный порядок величины из документации/бенчмарков nginx; сильно варьируется от конфигурации, поэтому это ориентир, не гарантия | +| Prometheus (один инстанс) | Ориентировочно ~1–2 млн активных time series на инстанс, десятки-сотни тысяч samples/sec на приём | Официальная документация Prometheus описывает такой порядок как практический предел одного инстанса до необходимости шардирования/federation | +| Mimir / Thanos (горизонтальное масштабирование) | Сотни миллионов активных series суммарно за счёт шардирования ingester/distributor и объектного хранилища | Архитектурная особенность — компонентная модель Mimir специально спроектирована так, чтобы снять ограничение одного Prometheus | +| Elasticsearch (data-нода) | Обычно рекомендуемый размер шарда — десятки ГБ (типовой ориентир ~10–50 ГБ на шард), heap-память — не более ~30–32 ГБ на JVM-инстанс (ограничение compressed oops в JVM) | Общеизвестная эксплуатационная рекомендация Elastic | +| Kafka (один брокер) | Порядок сотен МБ/сек на брокер при последовательной записи на быстром диске, десятки-сотни тысяч сообщений/сек в зависимости от размера сообщения | Публично известный порядок из документации/бенчмарков Kafka; сильно зависит от диска, размера батча, репликации | +| PostgreSQL | От нескольких тысяч до нескольких десятков тысяч TPS на мощном железе с SSD при простых транзакциях; сильно зависит от синхронности commit, индексов, объёма данных | Общий ориентир, публично встречающийся в бенчмарках `pgbench` | +| Kubernetes | Официальный документированный лимит — до 5000 нод и до 110 подов на ноду на кластер | Официальные Kubernetes Considerations for Large Clusters | + +**Важно:** это ориентиры порядка величины, а не гарантированные числа под любую нагрузку — правильный ответ на собеседовании называет порядок и объясняет, от чего он зависит, а не выдаёт одно «магическое число». + +## 2. Замеры на своей лабе + +Инструкция и место для заполнения — прогнать инструмент на соответствующем CASE.md-стенде и вписать реальный результат. Пока не прогнано — стоит `[замерить]`, не выдуманное число. + +**Характеристики машины для замеров:** `[указать: CPU, RAM, диск (SSD/HDD), ОС]` — важно фиксировать, чтобы честно объяснять экстраполяцию на других мощностях. + +| Стенд | Инструмент | Команда | Результат | +|---|---|---|---| +| [../stack/nginx/CASE.md](../stack/nginx/CASE.md) | `wrk`/`hey` (не входит в текущий CASE.md, добавить: `hey -n 10000 -c 50 http://localhost:8080/`) | RPS, задержка p50/p99 | `[замерить]` | +| [../stack/monitoring/CASE.md](../stack/monitoring/CASE.md) | Prometheus, число активных series | `curl localhost:9090/api/v1/status/tsdb` | `[замерить]` | +| [../stack/databases/CASE.md](../stack/databases/CASE.md) | `pgbench -c 10 -j 2 -T 30 labdb` | TPS | `[замерить]` | +| [../stack/kafka/CASE.md](../stack/kafka/CASE.md) | `kafka-producer-perf-test.sh --num-records 100000` | records/sec, MB/sec | `[замерить]` | +| [../stack/kubernetes/CASE.md](../stack/kubernetes/CASE.md) | число подов/нод в pet-кластере | `kubectl get nodes`, `kubectl get pods -A \| wc -l` | 3 ноды (1 control-plane + 2 worker), `[число подов после деплоя]` | + +Смысл этих цифр не в том, чтобы впечатлить масштабом (лаба заведомо на несколько порядков меньше прод-инсталляций), а в том, чтобы иметь реальную точку отсчёта: «на своей лабе на таком-то железе получил такой-то результат» — это честный, проверяемый факт, в отличие от абстрактного «я знаю, что Prometheus быстрый». + +## 3. Реальные масштабы с работы + +**Заполняется только пользователем, фактами со своего реального места работы. ИИ не подставляет сюда числа — ни выдуманные, ни «типичные для профиля компании».** Это прямо запрещено правилом честности кейсов в [../AI_GUIDELINES.md](../AI_GUIDELINES.md). + +| Показатель | Значение | +|---|---| +| Число серверов в обслуживаемом парке | `[заполнить]` | +| Число targets/эндпоинтов в Prometheus | `[заполнить]` | +| Число дашбордов в Grafana (в резюме: 50) | 50 (уже зафиксировано в [../RESUME.md](../RESUME.md)) | +| Объём логов в сутки (если известно) | `[заполнить]` | +| Число внутренних пользователей систем мониторинга | `[заполнить]` | +| Другие известные показатели нагрузки | `[заполнить]` | + +**Про черновые числа в [STORY.md](STORY.md):** там для нарратива (состав отдела, число VM и сервисов) стоят ориентировочные заготовки, явно помеченные как черновик для подтверждения — это не то же самое, что факты этого раздела. Раздел 3 CAPACITY.md остаётся пустым до тех пор, пока сюда не будут вписаны подтверждённые самим кандидатом цифры; правило ИИ не подставлять их сюда самостоятельно действует независимо от черновиков в STORY.md. + +## Как отвечать на вопрос про масштаб/трафик + +Формула ответа, которая не скатывается ни в выдумывание, ни в беспомощное «не знаю»: + +1. **Реальный масштаб, если он есть и подтверждён** — если вопрос про то, с чем реально сталкивался на работе, называть цифры из раздела 3 выше (после того как пользователь их заполнит). +2. **Что замерял на своей лабе и что получил** — честно обозначить, что это домашний стенд, и назвать реальную цифру из раздела 2. Это лучше, чем неопределённое «наверное, справится». +3. **Знание публичных пределов технологии** — показать, что понимаешь порядок величины, на которую рассчитана технология в принципе (раздел 1), и от чего он зависит. +4. **Как масштабировал бы дальше** — если вопрос про гипотетический больший масштаб (как в Bi.Zone про 300 000 серверов), проговорить архитектурный путь: шардирование, горизонтальное масштабирование компонентов, что именно упрётся в лимит первым. + +### Разбор конкретного вопроса (Реалист банк): «сколько Гб логов в ELK, если в Prometheus уже 15 Гб метрик» + +Правильный ответ не содержит единственного числа — это вопрос на понимание природы данных, а не на память конкретной цифры. Метрики компактны и хорошо сжимаются (числовые ряды с повторяющимися паттернами), логи — произвольный текст без такой плотности. При сопоставимом количестве событий логи обычно занимают на порядок (иногда на два) больше места, чем метрики того же периода — то есть если метрики занимают 15 Гб, разумный ответ — «логи, скорее всего, будут занимать существенно больше, на порядок или больше, в зависимости от verbosity логирования» — с объяснением почему, а не с попыткой угадать точное число. Разбор этого же вопроса в контексте ELK — [../stack/elk/QUESTIONS.md](../stack/elk/QUESTIONS.md). + +## Правило непротиворечивости + +При добавлении новых цифр: +1. Всегда явно указывать, к какому из трёх слоёв (публичный предел / лабный замер / реальный факт с работы) относится число — никогда не смешивать формулировки так, чтобы лабная цифра выглядела как продовая, или публичный ориентир — как личный опыт. +2. Лабные замеры — обновлять по мере реального прогона инструментов, не оставлять `[замерить]` дольше, чем до следующего прогона соответствующего кейса. +3. Раздел 3 — трогать только по прямому запросу пользователя с реальными данными, никогда не заполнять предположениями. diff --git a/legend/DEPT_STACK.md b/legend/DEPT_STACK.md new file mode 100644 index 0000000..64b697c --- /dev/null +++ b/legend/DEPT_STACK.md @@ -0,0 +1,55 @@ +# Технический стек отдела + +Опорный документ для ответа на вопрос «расскажите про стек отдела» — по образцу чужой легенды (список стека + короткий аргумент, почему каждый инструмент здесь). В отличие от [LEGEND.md](LEGEND.md), где технология привязана к личной строке резюме, этот файл описывает **окружение целиком**, как его увидел бы инженер отдела — включая инструменты, с которыми не работал напрямую, но которые логично стоят рядом (см. [LEGEND.md](LEGEND.md) → «Инструменты отдела без личной строки в резюме»). Правило на такие инструменты то же, что в примере легенды: минимум 2-3 предложения по каждому, без «мы с этим не работали». + +## Язык бэкенда + +- **Java** — сопровождаемые сервисы внутреннего контура (детали продукта закрыты режимом, см. [STORY.md](STORY.md) → «Проект отдела»). Работа с этим слоем — эксплуатация и диагностика (логи, стектрейсы, health-эндпоинты), не разработка. + +## ОС + +- **Astra Linux SE** — основная серверная ОС парка отдела: сертифицированный дистрибутив для контуров с гособоронзаказом, естественный выбор для режимного предприятия. +- **Debian** — DEV-песочница для обкатки конфигов и ролей перед прод-контуром на Astra: логично, потому что Astra построена на базе Debian, и навыки/пакеты в основном переносимы 1:1. + +## Серверы и балансировка + +- **Nginx** — маршрутизация и балансировка нагрузки перед сервисами и компонентами мониторинга (кейс — балансировка x3 Mimir round-robin, см. [STORY.md](STORY.md)). +- **Kubernetes** — в рабочем стеке отдела нет (закрытый статичный парк VM не требует оркестрации, IaC-слой закрыт Ansible). Личный опыт с K8s — честный pet-кластер, см. [LEGEND.md](LEGEND.md) → «Домашняя лаборатория» и [../stack/kubernetes/CASE.md](../stack/kubernetes/CASE.md). + +## Хранилище и артефакты + +- **MinIO** — S3-совместимое объектное хранилище, служит бэкендом для блоков Mimir (Mimir архитектурно требует объектное хранилище под долгосрочные данные метрик — без него горизонтальное масштабирование не работает). Настраивали и администрировали в связке с админами, я — потребитель как часть стека мониторинга. +- **Nexus** — внутренний менеджер артефактов/бинарей в закрытом контуре без выхода вовне: то же хранилище, откуда я брал архив бинаря Node Exporter при установке (см. [STORY.md](STORY.md) → «Простая задача»). + +## Контроль версий + +- **Git** — система контроля версий. +- **Gitea** — внутренний git-хостинг: репозитории Ansible-ролей, конфигов, JSON дашбордов Grafana; встроенная **Wiki** — инструкции и постмортемы (см. [STORY.md](STORY.md)). + +## Мониторинг + +- **Grafana** — визуализация метрик, дашборды (50 в резюме). +- **Prometheus** — сбор метрик по pull-модели. +- **Mimir** — горизонтально масштабируемое хранилище метрик поверх Prometheus (remote_write), решение проблемы производительности одной федерации. +- **Alertmanager** — маршрутизация и группировка алертов из Prometheus (куда идут уведомления, кто дежурит) — часть связки, встроен в архитектуру, отдельно не настраивал с нуля, но понимаю, зачем он между Prometheus и получателем алерта. +- **Node Exporter** — метрики хоста (CPU/RAM/диск) — личный кейс установки, см. [STORY.md](STORY.md). +- **Blackbox Exporter** — проверка доступности эндпоинтов (HTTP/TCP/ICMP), в частности контроль сроков TLS-сертификатов. +- **Nginx Exporter** — метрики самого Nginx как балансировщика. +- **Process Exporter** — метрики отдельных процессов на VM. +- **Postgres Exporter** — метрики состояния БД, которые ведут разработчики/администраторы БД; я — потребитель метрик со стороны мониторинга, не администратор самой БД (администрирование PostgreSQL — честный pet, см. [../stack/databases/CASE.md](../stack/databases/CASE.md)). + +## Логи + +- **Elastic Stack (ELK)** — Elasticsearch (хранение/поиск), Logstash (обработка/парсинг), Kibana (дашборды и разбор логов при инциденте), **Filebeat** — лёгкий агент, доставляющий логи Java-сервисов с VM в Elasticsearch. + +## CI/CD и автоматизация + +- **TeamCity + Molecule** — CI-конвейер тестирования Ansible-ролей и раскатки конфигов/дашбордов. +- **Ansible** — управление конфигурацией: роли и плейбуки на установку/настройку серверов и сервисов. +- **Docker** — контейнеризация сервисов мониторинга и вспомогательных инструментов. +- **Terraform** — в рабочем стеке отдела нет: инфраструктура статична (фиксированный парк VM без облачного API для программного провижининга), поэтому весь IaC-слой закрыт конфигурационным управлением через Ansible, отдельный инструмент под провижининг не требовался. +- **Bash** — повседневная автоматизация и обвязка вокруг Ansible/Docker. + +## Как этим пользоваться на собеседовании + +Если спрашивают конкретно про MinIO/Nexus/Alertmanager/Postgres Exporter/Filebeat — ответ по формуле: «для чего инструмент нужен в нашей связке» + «кто именно его настраивал» + «как я с ним соприкасался» (правил конфиг / читал метрики / знаю архитектурную роль). Не выдавать это за уровень «настраивал с нуля самостоятельно», но и не говорить «не работал» — честная middle-позиция, как и с Terraform/Kubernetes выше. diff --git a/legend/LEGEND.md b/legend/LEGEND.md new file mode 100644 index 0000000..29d4c56 --- /dev/null +++ b/legend/LEGEND.md @@ -0,0 +1,76 @@ +# Легенда соискателя + +Единый источник истины о том, «где и зачем» применялась каждая технология из резюме. Все кейсы и ответы на вопросы должны быть непротиворечивы этому документу. См. правила проработки кейсов в [../AI_GUIDELINES.md](../AI_GUIDELINES.md). + +Живой нарратив поверх этой таблицы (проект отдела, распорядок дня, разбор инцидента, версионирование дашбордов) — в [STORY.md](STORY.md). Стек отдела целиком, включая инструменты без личной строки в резюме, — в [DEPT_STACK.md](DEPT_STACK.md). + +## Таймлайн + +| Период | Компания | Роль | Основной стек | +|---|---|---|---| +| Март 2022 — Сентябрь 2023 (1 г. 7 мес.) | ОКБ СУХОЙ | Инженер-тестировщик | CentOS, Selenium, GitLab, C/C++, Python | +| Сентябрь 2023 — н.в. (2 г. 11 мес.) | АО ТНИИС | Инженер по сопровождению | Astra Linux, Grafana, Prometheus, Mimir, Docker, Ansible, Nginx, ELK, Jira, Gitea, Bash, Java, SQL | + +Общий опыт — 4 года 5 месяцев. Логика перехода: от тестирования встроенного ПО (проверка качества готового продукта) — к сопровождению и эксплуатации инфраструктуры (обеспечение стабильности продукта в проде). Естественное развитие: тестировщик, который глубоко разбирался в стендах и автоматизации тестов, вырос в инженера, отвечающего за автоматизацию инфраструктуры и мониторинг. + +## ОКБ СУХОЙ — инженер-тестировщик (2022—2023) + +Контекст роли: верификация встроенного программного обеспечения на платформе CentOS — тестовые стенды интеграционного и приёмочного тестирования. Место работы — таганрогская площадка кооперации ОАК, не головной офис в Москве (снимает вопрос о совмещении с очной магистратурой ЮФУ в Таганроге, см. [STORY.md](STORY.md) → «Бытовые детали»). + +- **CentOS** — рабочая ОС тестовых стендов: развёртывание окружения, работа с сервисами и логами через консоль. +- **Selenium** — автоматизация функциональных проверок там, где у ПО был веб/UI-интерфейс для управления стендом; писал тестовые сценарии и автотесты. +- **GitLab** — хранение тестовых скриптов и сценариев, отслеживание версий тестовой документации, базовое знакомство с CI до глубокого погружения в TeamCity на следующей позиции. +- **C/C++** — контекст тестируемого встроенного ПО: чтение и понимание кода на C/C++ требовалось, чтобы грамотно ставить тестовые сценарии, писать баг-репорты с точной локализацией проблемы и общаться с разработчиками на их языке. +- **Python** — вспомогательные скрипты для автоматизации рутины тестировщика (подготовка тестовых данных, парсинг логов стендов, обвязка вокруг Selenium-тестов). + +## АО ТНИИС — инженер по сопровождению (2023 — н.в.) + +Контекст роли: сопровождение и эксплуатация инфраструктуры, мониторинг, автоматизация деплоя и конфигурирования. + +- **Astra Linux** — базовая ОС серверного парка отдела, администрирование и сопровождение сервисов. +- **Grafana + Prometheus + Mimir** — стек мониторинга: сбор метрик (Prometheus), построение дашбордов (Grafana), масштабирование хранения метрик на несколько инстансов (Mimir). +- **Docker** — контейнеризация сервисов мониторинга и вспомогательных инструментов, локальное воспроизведение окружений. +- **Ansible** — автоматизация установки/настройки серверов и конфигурирования сервисов, роли и плейбуки для деплоя. +- **Nginx** — маршрутизация и балансировка нагрузки перед сервисами (в частности перед компонентами мониторинга и внутренними веб-интерфейсами). +- **ELK (Elastic Stack)** — централизованный сбор и анализ логов в дополнение к метрикам из Prometheus. +- **Jira** — планирование спринтов, ведение задач в Agile-процессе отдела. +- **Gitea** — внутренний git-хостинг для хранения Ansible-ролей, конфигов, скриптов; встроенная Wiki используется под инструкции и постмортемы инцидентов (см. [STORY.md](STORY.md) → «Как решается инцидент»). +- **TeamCity + Molecule** — CI-конвейер для тестирования Ansible-ролей перед выкладкой. +- **Bash** — повседневная автоматизация на серверах: обвязка вокруг Ansible/Docker, скрипты обслуживания. +- **Java** — часть стека сопровождаемых сервисов отдела (эксплуатация/поддержка Java-приложений, чтение логов и стектрейсов при инцидентах), не основной инструмент разработки. +- **SQL** — запросы к базам данных сопровождаемых сервисов и/или к хранилищам метрик/логов при разборе инцидентов и подготовке отчётности. + +## Технологии без прямого кейса в опыте работы + +Указаны в разделе «Навыки» резюме, в легенду вписаны как сквозные инструменты внутри уже описанных задач (не отдельная линия опыта): + +- **Python** — автоматизация в обеих ролях (см. выше). +- **SQL** — работа с данными сопровождаемых сервисов в АО ТНИИС (см. выше). +- **C/C++** — чтение кода тестируемого ПО в ОКБ СУХОЙ (см. выше). + +## Домашняя лаборатория / pet-проект + +Разбор реальных собеседований ([../interview/real-interviews.md](../interview/real-interviews.md)) показал: Kubernetes, PostgreSQL, сетевая база, Kafka и Vault спрашивают часто, но их **нет** в рабочем стеке ни АО ТНИИС, ни ОКБ СУХОЙ. Вместо того чтобы дописывать их в опыт компаний (что нарушило бы правило честности кейсов из [../AI_GUIDELINES.md](../AI_GUIDELINES.md)), они выделены в отдельную честную категорию — домашнюю лабораторию, аналогично тому, как на собеседовании в ВТБ упоминание pet-проекта сработало в плюс (см. разбор в [../interview/real-interviews.md](../interview/real-interviews.md)). + +| Технология | Кейс | Статус | +|---|---|---| +| Kubernetes | [../stack/kubernetes/CASE.md](../stack/kubernetes/CASE.md) | pet, поверх рабочего Docker/Ansible | +| PostgreSQL | [../stack/databases/CASE.md](../stack/databases/CASE.md) | pet, поверх рабочего SQL | +| Сети | [../stack/networking/CASE.md](../stack/networking/CASE.md) | pet, поверх рабочего Linux-администрирования | +| Kafka | [../stack/kafka/CASE.md](../stack/kafka/CASE.md) | pet, самостоятельно | +| Vault | [../stack/vault/CASE.md](../stack/vault/CASE.md) | pet, поверх рабочего ansible-vault | + +**Как это позиционируется на собеседовании:** честно и прямо — «в рабочем стеке этого не было, но интересно было разобраться, поэтому поднял себе дома». Не выдаётся за продовый опыт, не привязывается к конкретным задачам АО ТНИИС/ОКБ СУХОЙ. Цифры нагрузки для pet-кейсов — только лабные замеры и публичные ориентиры, без утверждений о продовых масштабах (правило и таблица цифр — [../legend/CAPACITY.md](../legend/CAPACITY.md)). + +## Инструменты отдела без личной строки в резюме + +Полный стек отдела АО ТНИИС — в [DEPT_STACK.md](DEPT_STACK.md). Часть инструментов туда попадает по логике окружения (то, чем пользуется отдел или что стоит рядом с моими задачами), но в резюме отдельной строкой не идёт, потому что я с ними не работал напрямую как с основным инструментом — только на уровне «знаю, зачем стоит, могу поддержать разговор»: **MinIO** (объектное хранилище под блоки Mimir), **Nexus** (внутреннее хранилище артефактов/бинарей), **Alertmanager** (маршрутизация алертов из Prometheus), **Postgres Exporter** (снимает метрики с БД, которые ведут разработчики/администраторы БД), **Filebeat** (агент доставки логов в ELK). При вопросе про них на собеседовании — честный формат из [../AI_GUIDELINES.md](../AI_GUIDELINES.md): рассказываю, для чего инструмент нужен и как он встроен в наш стек, не выдавая это за уровень «настраивал сам с нуля». + +## Правило непротиворечивости + +При добавлении нового кейса в `stack/*/CASE.md`: +1. Сверить, что компания/период/стек совпадают с таблицей выше. +2. Если кейс раскрывает технологию по-новому — обновить соответствующий пункт здесь же. +3. Не добавлять технологии, которых нет ни в [../RESUME.md](../RESUME.md), ни в этом файле. +4. Pet-технологии (раздел выше) никогда не переносятся в блоки опыта компаний — только в раздел «Навыки» резюме и в раздел «Домашняя лаборатория» здесь. +5. Новые истории и детали быта/процессов сверять с [STORY.md](STORY.md), стек отдела — с [DEPT_STACK.md](DEPT_STACK.md), чтобы не появлялось противоречий между документами. diff --git a/legend/STORY.md b/legend/STORY.md new file mode 100644 index 0000000..6e156fd --- /dev/null +++ b/legend/STORY.md @@ -0,0 +1,87 @@ +# Нарратив работы в АО ТНИИС + +Живой рассказ о работе поверх сухого таймлайна из [LEGEND.md](LEGEND.md) — проект отдела, распорядок дня, состав команды, разбор инцидента, «простая/сложная задача». Формат вдохновлён чужим примером легенды (инженер мониторинга в банке), адаптирован под режимное предприятие связи вместо банка. См. правила честности кейсов в [../AI_GUIDELINES.md](../AI_GUIDELINES.md) — этот файл не добавляет новых технологий сверх [LEGEND.md](LEGEND.md) и [../RESUME.md](../RESUME.md), только контекст вокруг уже согласованного стека. + +**Важно про цифры ниже.** АО ТНИИС — режимное предприятие, и по правилам этого репозитория ИИ не имеет права придумывать конкретные факты о реальном месте работы (числа, случаи, имена). Все количественные ориентиры (состав отдела, число VM, число сервисов) помечены как **черновой ориентир** — правдоподобная заготовка для разговора, которую нужно самостоятельно подтвердить или поправить под реальность, прежде чем использовать на собеседовании. Это тот же принцип, что уже применяется в [CAPACITY.md](CAPACITY.md) к цифрам нагрузки. + +## Проект отдела: «режимный щит» + +Отдел мониторинга и сопровождения обеспечивает стабильность внутреннего контура предприятия: набор Java-сервисов, которые обрабатывают и хранят данные испытательных стендов и обмениваются между собой по REST за балансировкой Nginx, разворачиваются на парке VM под Astra Linux. + +**Как говорить о деталях продукта на собеседовании:** предприятие режимное (отраслевой НИИ связи), поэтому детали о том, что именно делают сервисы и какие данные обрабатывают, не раскрываются — это нормальная и понятная интервьюеру причина, а не уклонение от ответа. Формула ответа: «Это внутренний контур предприятия, работающего по гособоронзаказу — по режимным ограничениям не могу раскрывать, что именно обрабатывают сервисы, но со стороны инфраструктуры, мониторинга и эксплуатации могу рассказать всё в деталях: как разворачивали, как мониторили, как реагировали на инциденты». После этой фразы разговор переводится в техническую плоскость (мониторинг, автоматизация, инциденты), где ограничений нет — именно там и находится весь реальный опыт. + +Эта же рамка объясняет весь стек отдела логически: +- **Astra Linux** вместо обычного Ubuntu/CentOS — сертифицированная ОС для гособоронзаказа. +- **Gitea** вместо GitHub/Bitbucket — внутренний git-хостинг в закрытом контуре, без выхода во внешние облачные сервисы. +- **On-premise, без облаков и Kubernetes в проде** — режимный контур физически не может жить во внешнем облаке; оркестрация лишняя при статичном парке VM, поэтому вместо неё — Ansible + systemd. +- **ELK внутри контура** — центральный сбор логов без внешних SaaS-решений. + +## Состав отдела (черновой ориентир, подтвердить/поправить) + +Отдел сопровождения и мониторинга — небольшая команда: начальник отдела и порядка 6–7 инженеров: 1–2 инженера сопровождения (включая меня), 1–2 инженера мониторинга, 1–2 Java-разработчика на стороне сопровождаемых сервисов. Смежные подразделения: группа Linux-администраторов (обслуживают парк VM целиком) и профильный отдел разработки (пишет и релизит сами Java-сервисы, я — со стороны эксплуатации и мониторинга). + +**Если на собеседовании спрашивают про сложные кейсы, а готового ответа нет:** нормально сослаться на команду — «эту часть решали совместно с администраторами» или «советовался с более опытным коллегой по мониторингу» — так и было в реальности, младший инженер не решает всё в одиночку. + +## Распорядок дня + +Утро начинается с короткого дейлика (15–30 минут): начальник отдела опрашивает по текущим задачам и инцидентам, задачи ведутся в Jira. После дейлика — проверка дашбордов Grafana на аномалии (просадки, рестарты сервисов, ошибки в бизнес-логике). Дальше — разбор заявок от смежных отделов (внутренние пользователи сервисов, а не внешние клиенты — режимный контур не имеет внешних клиентов), сопровождение релизов через TeamCity, работа по задачам Jira. + +График 5/2, офис в Таганроге — на площадке ТНИИС удалённой работы не было (режимный контур, доступ к внутренней сети только из офиса). Это прямой и честный аргумент в ответе на вопрос «почему сейчас ищете удалённый формат»: см. раздел «Причина ухода» ниже. + +## Взаимодействие с другими командами + +- **С Linux-администраторами** — совместная автоматизация обновления пакетов Astra Linux через Ansible-роли (уже в [LEGEND.md](LEGEND.md)); я пишу и тестирую роли, раскатку в части случаев ведут вместе. +- **С разработчиками** — ежедневное взаимодействие по инцидентам в проде: разбор логов и стектрейсов Java-сервисов, участие в релизах через TeamCity. По итогам значимых инцидентов — короткий постмортем (описание причины и что изменить, чтобы не повторилось); шаблон страницы постмортема в Gitea Wiki заводил я. +- **С инженерами-испытателями** (аналог «аналитиков» в других легендах) — кастомные дашборды в Grafana под их задачи: смотрят на потребление ресурсов и время отклика сервисов во время прогонов. +- **Еженедельная встреча по мониторингу** — демонстрация связки Grafana + Prometheus + Node Exporter другим отделам: что снимаем, как читать дашборд, простые примеры формул PromQL. Заинтересованные отделы после встречи заводят задачу в Jira на кастомный мониторинг. + +## Как решается инцидент — пошагово + +Универсальная формула, которую стоит воспроизводить на любом похожем вопросе интервьюера («как вы действуете при инциденте», «расскажите про сложный баг»): + +1. **Замечаю аномалию** на дашборде Grafana — просадка трафика, рестарт сервиса, рост ошибок. +2. **Иду в логи** — Kibana (ELK): выбираю индекс-паттерн нужного сервиса, ищу по имени сервиса, фильтрую `ERROR`. +3. **Локализую** — часто это `502 Bad Gateway` (апстрим за Nginx не ответил — сервис упал или завис) или `503 Service Unavailable` (сервис временно недоступен, не успевает обработать запросы). +4. **Проверяю сервис на VM** — `systemctl status ` / `journalctl -u -n 100`, при наличии health-эндпоинта (Spring Boot Actuator у Java-сервисов) — `curl http://host:port/actuator/health`. +5. **Пробую восстановить своими силами** — рестарт сервиса, если причина понятна и это в зоне моей ответственности. +6. **Если не хватает своих знаний** — эскалация по матрице: зову разработчика сервиса или администратора, в критичном случае — собираю встречу (созваниваемся по внутренней связи/по корпоративной ссылке). +7. **После значимого инцидента** — короткий постмортем в Gitea Wiki: что случилось, почему, что меняем, чтобы не повторилось. + +## Простая и сложная задача (готовые истории с синтаксисом) + +**Простая — установка Node Exporter.** Ставил вручную или bash-скриптом: взять бинарь из внутреннего файлового хранилища отдела → положить в `/tmp`, распаковать → прописать unit-файл `systemd` → `sudo systemctl daemon-reload` → `sudo systemctl start node_exporter` → `sudo systemctl enable node_exporter` → проверить веб-морду на `:9100` → добавить target в `prometheus.yml` → проверить метрики `node_...` на `:9090`. + +**Сложная — балансировка Nginx для Mimir.** Писал `nginx.conf` с балансировкой по алгоритму round-robin (запросы идут на бэкенды по очереди) на три инстанса Mimir — горизонтальное масштабирование хранения метрик Prometheus: + +```nginx +upstream mimir_backend { + server mimir1.internal:9009; + server mimir2.internal:9009; + server mimir3.internal:9009; +} + +server { + listen 80; + server_name mimir.internal; + + location / { + proxy_pass http://mimir_backend; + } +} +``` + +Применял конфиг через `sudo systemctl reload nginx`, проверял `sudo systemctl status nginx`. Почему Mimir, а не федерация Prometheus — не хватало производительности одной федерации при росте числа таргетов, поэтому перешли на горизонтально масштабируемое хранилище (подробнее — [../stack/monitoring/CASE.md](../stack/monitoring/CASE.md)). + +## Версионирование дашбордов Grafana + +Дашборды выгружаются в формате JSON, хранятся в репозитории Gitea вместе с остальными конфигами отдела, раскатка — джобой TeamCity. Один CI-инструмент на компанию — если на собеседовании случайно всплывёт GitLab (он был у ОКБ СУХОЙ), это легко объяснить сменой места работы, а не путаницей: у каждой компании свой инструмент CI/CD. + +## Причина ухода + +Иду не «от», а «к»: режимный контур ТНИИС по своей природе закрыт от многого, что интересно развивать дальше — нет облаков, нет Kubernetes в проде, нет удалённой работы. Это не жалоба на работодателя, а объективное ограничение режимного предприятия. Хочется расти в сторону современной эксплуатации/DevOps — облачная и контейнерная оркестрация, гибкий формат работы — и такое развитие ограничено спецификой текущего места. Деньги как причину не упоминаем. + +## Бытовые детали + +- **ОКБ СУХОЙ** — работал на таганрогской площадке кооперации ОАК (в Таганроге исторически работает авиастроительная площадка, связанная с программами ОКБ Сухого), не в головном офисе в Москве — снимает вопрос «как совмещали с учёбой в ЮФУ, где физически был офис». +- Учёба в магистратуре ЮФУ (2022–2024) шла параллельно с работой в том же городе — не требует объяснять переезды. +- **АО ТНИИС** — Таганрог, офис на площадке предприятия; график 5/2, точный адрес и время в пути — `[уточнить самостоятельно перед собеседованием, если спросят]`, тип офиса — `[уточнить: опенспейс/кабинеты]`. diff --git a/stack/ansible/CASE.md b/stack/ansible/CASE.md new file mode 100644 index 0000000..cd4fcc8 --- /dev/null +++ b/stack/ansible/CASE.md @@ -0,0 +1,148 @@ +# Кейс: роль Ansible + тестирование в Molecule + +Связь с легендой: [../../legend/LEGEND.md](../../legend/LEGEND.md) — раздел «АО ТНИИС», автоматизация установки/настройки серверов, роли и плейбуки, тестирование через Molecule перед CI (TeamCity). + +## Что нужно реально сделать (домашний стенд) + +Написать Ansible-роль, разворачивающую и конфигурирующую Nginx, и протестировать её через Molecule (Docker-драйвер) — без реальных серверов, но с реальным прогоном роли и проверкой результата. + +### 1. Структура + +``` +ansible-lab/ +├── ansible.cfg +├── inventory/ +│ └── hosts.ini +├── playbook.yml +└── roles/ + └── nginx_deploy/ + ├── defaults/main.yml + ├── tasks/main.yml + ├── handlers/main.yml + ├── templates/nginx.conf.j2 + └── molecule/ + └── default/ + ├── molecule.yml + ├── converge.yml + └── verify.yml +``` + +### 2. roles/nginx_deploy/defaults/main.yml + +```yaml +nginx_worker_connections: 1024 +nginx_server_name: lab.local +``` + +### 3. roles/nginx_deploy/tasks/main.yml + +```yaml +- name: Установить nginx + ansible.builtin.package: + name: nginx + state: present + +- name: Развернуть конфиг nginx из шаблона + ansible.builtin.template: + src: nginx.conf.j2 + dest: /etc/nginx/nginx.conf + mode: "0644" + notify: reload nginx + +- name: Убедиться, что nginx запущен и в автозагрузке + ansible.builtin.service: + name: nginx + state: started + enabled: true +``` + +### 4. roles/nginx_deploy/handlers/main.yml + +```yaml +- name: reload nginx + ansible.builtin.service: + name: nginx + state: reloaded +``` + +### 5. roles/nginx_deploy/templates/nginx.conf.j2 + +```nginx +events { + worker_connections {{ nginx_worker_connections }}; +} + +http { + server { + listen 80; + server_name {{ nginx_server_name }}; + + location / { + return 200 'ok'; + } + } +} +``` + +### 6. molecule/default/molecule.yml + +```yaml +driver: + name: docker +platforms: + - name: nginx-instance + image: geerlingguy/docker-ubuntu2204-ansible:latest + pre_build_image: true +provisioner: + name: ansible +verifier: + name: ansible +``` + +### 7. molecule/default/converge.yml + +```yaml +- name: Converge + hosts: all + roles: + - role: nginx_deploy +``` + +### 8. molecule/default/verify.yml + +```yaml +- name: Verify + hosts: all + tasks: + - name: Проверить, что nginx слушает 80 порт + ansible.builtin.wait_for: + port: 80 + timeout: 5 + + - name: Проверить HTTP-ответ + ansible.builtin.uri: + url: http://localhost/ + status_code: 200 +``` + +### 9. Шаги воспроизведения + +1. `pip install molecule molecule-plugins[docker] ansible`. +2. `cd roles/nginx_deploy && molecule test` — это прогоняет полный цикл: create (поднять Docker-инстанс) → converge (применить роль) → idempotence (повторный прогон роли не должен ничего менять) → verify (проверки из verify.yml) → destroy. +3. Специально сломать что-то в шаблоне (например, опечатку в директиве nginx) и посмотреть, как `molecule test` падает на этапе converge или verify — это и есть демонстрация того, зачем нужен этот шаг перед CI. +4. Убедиться, что повторный прогон роли (idempotence check) не репортит изменений — если репортит, значит роль не идемпотентна, и это тоже частый вопрос на собеседовании. + +### 10. Что это даёт в разговоре с интервьюером + +- Реальное понимание, зачем нужен Molecule: тестировать роль в изолированном контейнере до того, как она попадёт в реальную инфраструктуру через CI. +- Понимание идемпотентности — краеугольного принципа Ansible. +- Практика с handlers, templates, defaults — стандартной структурой роли. +- Понимание, как Molecule встраивается в CI-конвейер (у меня локально `molecule test`, в TeamCity — тот же шаг, но в build step с прогоном перед деплоем). + +## Нагрузка и цифры + +Ansible сам по себе не «нагрузочная» технология в смысле RPS/TPS — релевантная метрика тут время прогона плейбука на N хостов (параллелизм через `forks` в `ansible.cfg`). Цифры не зафиксированы отдельно — общий подход к честным цифрам масштаба (три слоя: публичные ориентиры / лабные замеры / реальные факты с работы) описан в [../../legend/CAPACITY.md](../../legend/CAPACITY.md). + +## Как это ложится в легенду + +В реальной работе (АО ТНИИС) — 15 скриптов/ролей Ansible с управлением конфигурациями через роли и модули, тестирование в CI/CD через Molecule и TeamCity. Домашний кейс воспроизводит тот же цикл в миниатюре на одной роли вместо полного набора инфраструктурных ролей. diff --git a/stack/ansible/QUESTIONS.md b/stack/ansible/QUESTIONS.md new file mode 100644 index 0000000..c472050 --- /dev/null +++ b/stack/ansible/QUESTIONS.md @@ -0,0 +1,59 @@ +# Вопросы: Ansible + +Опираются на кейс: [CASE.md](CASE.md). + +### Что такое идемпотентность и почему это важно в Ansible? + +Идемпотентность — свойство операции давать один и тот же результат при повторном запуске, не ломая и не дублируя изменения. В Ansible это значит, что повторный прогон плейбука на уже настроенном хосте не должен ничего менять (задачи должны показывать `ok`, а не `changed`). Я это проверял на своей роли nginx_deploy через `molecule test` — там есть отдельный шаг idempotence, который прогоняет converge второй раз и падает, если что-то опять помечено как changed. + +### Чем роль (role) отличается от плейбука (playbook)? + +Плейбук — это конкретный сценарий: какие хосты, какие роли/задачи применить и в каком порядке. Роль — переиспользуемый, самодостаточный блок автоматизации со своей структурой папок (tasks, handlers, templates, defaults, vars), который можно подключать в разные плейбуки. У меня playbook.yml просто вызывает роль nginx_deploy — так удобнее переиспользовать её в других плейбуках без копирования кода. + +### Зачем нужны handlers и чем они отличаются от обычных задач? + +Handler — задача, которая выполняется только по уведомлению (`notify`) от другой задачи, и только один раз в конце плейбука, даже если её нотифицировали несколько раз. Типичный пример — перезагрузка сервиса только если поменялся конфиг: в моей роли задача `template` нотифицирует хендлер `reload nginx`, и nginx перезагружается только тогда, когда конфиг реально изменился, а не при каждом прогоне. + +### Что такое defaults/vars и в чём разница между ними по приоритету? + +`defaults/main.yml` — переменные с самым низким приоритетом, задуманы как значения по умолчанию, которые легко переопределить снаружи роли (в inventory, playbook, group_vars). `vars/main.yml` — переменные роли с более высоким приоритетом, обычно используются для внутренних констант роли, которые не предполагается менять снаружи. В своей роли я вынес `nginx_worker_connections` и `nginx_server_name` в defaults именно потому, что это то, что вызывающий плейбук может захотеть переопределить. + +### Зачем нужен Molecule, если можно просто запустить playbook на тестовом сервере? + +Molecule автоматизирует весь цикл проверки роли: поднимает изолированное окружение (у меня — Docker-контейнер), прогоняет роль, проверяет идемпотентность, гоняет verify-тесты и уничтожает окружение — и всё это одной командой, без ручного создания и удаления тестовых серверов. Это то, что можно встроить в CI (в моей практике — TeamCity), чтобы роль проверялась автоматически на каждый коммит до попадания в прод. + +### Чем отличается модуль `command`/`shell` от специализированных модулей вроде `service`, `package`, `template`? + +Специализированные модули декларативны и идемпотентны из коробки — они сами проверяют текущее состояние и меняют его только при необходимости (например, `service` не будет перезапускать уже запущенный сервис). `command`/`shell` выполняют произвольную команду безусловно и по умолчанию всегда репортят `changed`, что ломает идемпотентность, если не добавлять вручную `creates`/`changed_when`. В своей роли я использовал `package`, `template`, `service` именно чтобы не терять идемпотентность. + +### Как Ansible понимает, какие хосты — цель для выполнения? + +Через inventory — статический файл (INI/YAML со списком хостов и групп) или динамический inventory-скрипт/плагин, который генерирует список из внешнего источника (облако, CMDB и т.п.). В playbook секция `hosts:` указывает, на какую группу/хост из inventory применять роль. В лабе у меня Molecule сам генерирует временный inventory на Docker-инстанс, в проде — обычно статический или динамический inventory отдела. + +### Как безопасно хранить секреты (пароли, токены) в Ansible? + +Через `ansible-vault` — шифрование файлов с чувствительными переменными, которые потом расшифровываются на лету при прогоне плейбука с паролем/ключом vault. Секреты не должны лежать в открытом виде в репозитории с ролями. + +### Что произойдёт, если задача в роли завершится с ошибкой на середине выполнения? + +По умолчанию Ansible останавливает выполнение плейбука на этом хосте (fail fast) — дальнейшие задачи для этого хоста не выполняются, но выполнение на других хостах продолжается независимо (если запуск на несколько хостов). Это поведение можно переопределить через `ignore_errors`, `block/rescue` для обработки ошибок или `any_errors_fatal` для остановки всего прогона. + +### Как вы тестировали конкретно свою роль и что бы произошло, если бы шаблон был некорректным? + +Я специально ломал `nginx.conf.j2` (опечатка в директиве) и запускал `molecule test` — прогон падал на этапе converge, потому что Ansible применяет конфиг, а nginx не стартует/не резолвит синтаксис, что видно в выводе `service` модуля. Это и есть основная ценность прогона перед CI — ошибка находится сразу, а не после деплоя на реальный сервер. + +### Где в Ansible живут переменные — в ролях, плейбуках или инвентарях, и зачем это разделение? + +Переменные могут жить на всех трёх уровнях, и у каждого — своя роль. В роли (`defaults/main.yml`, `vars/main.yml`) — значения, специфичные для самой роли (см. вопрос про defaults/vars выше). В плейбуке (`vars:` секция) — значения, специфичные для конкретного сценария применения роли. В inventory (`group_vars/`, `host_vars/`) — значения, специфичные для конкретной группы хостов или отдельного хоста (например, разный `nginx_server_name` для разных окружений). Разделение нужно, чтобы одна и та же роль оставалась переиспользуемой: логика роли не меняется, а конкретные значения приходят снаружи в зависимости от того, где и для чего роль применяется. + +### Как переопределить переменную роли? + +Проще всего — передать её на более высоком приоритете, чем `defaults/main.yml` (самый низкий приоритет намеренно, чтобы легко переопределять): через `group_vars`/`host_vars` в inventory, через `vars:` в самом плейбуке при вызове роли, или через `-e` в командной строке (`ansible-playbook playbook.yml -e "nginx_worker_connections=2048"` — `-e` имеет наивысший приоритет из всех источников). В своей роли `nginx_deploy` я именно поэтому вынес `nginx_worker_connections` в `defaults`, а не в `vars` — чтобы вызывающий плейбук/inventory мог его свободно переопределить без правки самой роли. + +### Могут ли роль и плейбук существовать по отдельности, и что будет, если убрать один из них? + +Плейбук без роли — вполне рабочий сценарий: можно писать задачи прямо в плейбуке (`tasks:` напрямую), просто без переиспользуемой структуры роли — так делают для одноразовых/простых сценариев. Роль без плейбука сама по себе не выполнится — роль не имеет собственного понятия «на каких хостах запускаться», это просто набор задач/шаблонов/дефолтов; ей обязательно нужен плейбук (или `ansible-console`/adhoc-вызов через `include_role` из другого плейбука), который укажет `hosts:` и подключит роль. То есть роль — переиспользуемый строительный блок, плейбук — то, что решает, где и когда этот блок применить. + +### Разбор команды: `ansible -m shell -i inventory.yml {{ansible_hostname}}` — что делает эта команда с ключами поэтапно? + +По шагам: `ansible` — adhoc-режим (разовая команда без плейбука, в отличие от `ansible-playbook`); `-i inventory.yml` — указывает, какой inventory-файл использовать для определения хостов и их переменных; `-m shell` — указывает модуль `shell` (выполнить произвольную команду через шелл на целевом хосте, в отличие от специализированных идемпотентных модулей — см. вопрос про command/shell выше); `{{ ansible_hostname }}` в этом месте выглядит некорректно синтаксически как есть — Jinja2-конструкции `{{ }}` предназначены для использования внутри YAML/шаблонов и аргументов модулей, а не как позиционный аргумент-паттерн хостов в CLI-вызове `ansible` (туда обычно подставляется паттерн хостов из inventory, например `all` или имя группы). На такой вопрос честный ответ — указать на это несоответствие и уточнить у интервьюера, что именно должно было стоять на этом месте, а не пытаться выдумать интерпретацию. diff --git a/stack/ci-cd/CASE.md b/stack/ci-cd/CASE.md new file mode 100644 index 0000000..8875156 --- /dev/null +++ b/stack/ci-cd/CASE.md @@ -0,0 +1,95 @@ +# Кейс: CI/CD (Git, Gitea, GitLab, TeamCity) + +Связь с легендой: [../../legend/LEGEND.md](../../legend/LEGEND.md) — GitLab (ОКБ СУХОЙ, хранение тестовых скриптов), Gitea + TeamCity (АО ТНИИС, хранение Ansible-ролей и CI-конвейер с Molecule). + +## Что нужно реально сделать (домашний стенд) + +Поднять Gitea локально, запушить туда роль Ansible из [../ansible/CASE.md](../ansible/CASE.md), и настроить пайплайн через GitHub Actions (доступная бесплатная альтернатива TeamCity для домашней практики — принцип pipeline/stage/job идентичен, конкретный синтаксис отличается; на собеседовании это стоит явно проговаривать как «дома тренировался на GitHub Actions, на текущей работе — TeamCity, принцип тот же»). + +### 1. Gitea в Docker Compose + +```yaml +version: "3.8" + +services: + gitea: + image: gitea/gitea:1.22 + container_name: gitea + environment: + - GITEA__database__DB_TYPE=sqlite3 + - GITEA__server__ROOT_URL=http://localhost:3001/ + ports: + - "3001:3000" + - "2222:22" + volumes: + - gitea_data:/data + +volumes: + gitea_data: +``` + +```bash +docker compose up -d +# зайти на localhost:3001, создать администратора, создать репозиторий "ansible-nginx-role" +git remote add gitea http://localhost:3001//ansible-nginx-role.git +git push gitea main +``` + +### 2. Пайплайн, прогоняющий molecule test на каждый push + +```yaml +# .github/workflows/molecule.yml (для GitHub как доступной альтернативы; на реальном месте — эквивалент в TeamCity) +name: Molecule Test + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Install dependencies + run: pip install ansible molecule molecule-plugins[docker] docker + - name: Run molecule test + run: molecule test +``` + +Смысл именно в том, чтобы это был реальный прогон уже готовой роли из [../ansible/CASE.md](../ansible/CASE.md): пайплайн скачивает код, ставит зависимости и выполняет тот же `molecule test`, который до этого прогонялся вручную — то есть проверка идемпотентности и корректности роли теперь происходит автоматически при каждом push, без ручного запуска. + +### 3. Модель веток + +```bash +git checkout -b feature/add-ssl-support +# правки роли +git push gitea feature/add-ssl-support +# создать Pull Request в Gitea UI: feature/add-ssl-support -> main +``` + +Protected branch (`main`) в настройках репозитория Gitea — запрет прямого push, обязательное прохождение пайплайна (CI status check) и минимум одного ревью перед merge. Смысл: код не может попасть в основную ветку, минуя автоматическую проверку и код-ревью. + +### 4. Базовые операции Git на практике + +```bash +git rebase main # переносит коммиты ветки поверх актуального main, история линейная +git merge main # создаёт merge-коммит, сохраняет реальную историю параллельной разработки +git cherry-pick # переносит один конкретный коммит в текущую ветку +git bisect start +git bisect bad # текущий коммит содержит баг +git bisect good <старый-коммит> # этот коммит был рабочим +# git автоматически предлагает коммиты между ними для бинарного поиска, где баг появился +``` + +## Что это даёт в разговоре с интервьюером + +- Реально запушенный в Gitea код и реально прогнанный через CI пайплайн, проверяющий Ansible-роль — а не абстрактное «умею писать YAML». +- Понимание разницы rebase/merge не как определения, а с пониманием, когда какой подход уместен (rebase — для чистки локальной истории перед PR, merge — для сохранения факта параллельной разработки). +- Практическое понимание protected branches как механизма, а не просто слов. +- Умение нарисовать и объяснить типовой pipeline: lint → test/molecule → deploy — то, что реально спрашивали на нескольких собеседованиях (см. [../../interview/real-interviews.md](../../interview/real-interviews.md)). + +## Как это ложится в легенду + +Git-хостинги и CI — сквозная инфраструктура вокруг уже проработанного Ansible-кейса. Здесь не отдельный «продукт» для тестирования — кейс встраивает существующую роль в реальный пайплайн, замыкая рассказ «написал роль → протестировал локально через Molecule → теперь это же автоматически проверяется в CI на каждый push». diff --git a/stack/ci-cd/QUESTIONS.md b/stack/ci-cd/QUESTIONS.md new file mode 100644 index 0000000..ef2a1ff --- /dev/null +++ b/stack/ci-cd/QUESTIONS.md @@ -0,0 +1,31 @@ +# Вопросы: CI/CD + +Опираются на кейс: [CASE.md](CASE.md). Источники — [Реалист банк.md](../../interview/Реалист%20банк.md), [Bi.Zone.md](../../interview/Bi.Zone.md), [Alfa-Bank.md](../../interview/Alfa-Bank.md). + +### В чём разница между CI и CD? + +CI (Continuous Integration) — автоматическая проверка кода при каждом изменении: сборка, линтинг, тесты — цель поймать проблему как можно раньше, до слияния с основной веткой. CD (Continuous Delivery/Deployment) — автоматизация доставки уже проверенного кода до среды исполнения (staging/production) — сборка артефакта/образа, публикация в registry, применение на целевой инфраструктуре. Граница на практике: CI заканчивается там, где код признан «годным» (все тесты прошли, собран артефакт), CD начинается с того, что этот артефакт доставляется дальше. + +### Нарисуй CI/CD и как проливается код через GitLab CI — что будет в процессе CI и что в CD (с этапа, когда код уже запушен в GitHub, а образы в Nexus)? + +От push в GitHub: (1) webhook/триггер запускает pipeline в GitLab CI; (2) CI-часть — стадии `lint` (статический анализ) → `test` (юнит/интеграционные тесты, в моей практике — `molecule test` для Ansible-ролей) → `build` (сборка Docker-образа); (3) стадия публикации — образ пушится в Nexus как container registry с тегом (обычно commit SHA или семантическая версия); (4) CD-часть начинается с деплоя — pipeline берёт собранный образ именно из Nexus (не пересобирает заново) и применяет его на целевом окружении (`kubectl apply`/`helm upgrade` при K8s, или через Ansible-плейбук деплоя при классической инфраструктуре) — сначала обычно на staging, дальше по ручному approve или автоматически на production. Ключевой принцип: то, что задеплоено в prod — это тот же самый бинарный артефакт (образ), что прошёл все проверки CI, без пересборки на каждом этапе. + +### Что такое pipeline/stage/job? + +Pipeline — весь процесс целиком, от триггера (push/MR) до конечного результата (успешный деплой или явный fail). Stage — логическая фаза внутри pipeline (например, `build`, `test`, `deploy`) — stages выполняются последовательно. Job — конкретная выполняемая задача внутри stage (например, в stage `test` могут быть параллельно запущены job `unit-tests` и job `molecule-test`) — job'ы внутри одного stage обычно выполняются параллельно, если нет явных зависимостей. + +### Зачем нужны protected branches? + +Защита основной ветки (обычно `main`/`master`) от прямых push и слияний без прохождения обязательных проверок — требует, чтобы изменения проходили через Merge/Pull Request с обязательным прохождением CI (status checks) и, как правило, минимум одного ревью. Смысл — предотвратить попадание непроверенного или сломанного кода прямо в ветку, с которой разворачивается прод. + +### Как откатить плохой деплой? + +Зависит от инфраструктуры: при K8s — `kubectl rollout undo deployment/` (возврат к предыдущей ревизии Deployment) или `helm rollback ` при Helm-релизах. При классическом деплое через Ansible/образы — передеплоить предыдущий проверенный тег образа (тот самый принцип «в prod идёт конкретный собранный артефакт из registry» из вопроса про GitLab CI выше — откат это просто повторный деплой более старого тега). Важно, чтобы откат был таким же автоматизированным процессом через тот же pipeline, а не ручными правками на проде. + +### Чем отличается git merge --no-ff от fast-forward merge? + +Fast-forward merge происходит, когда в целевой ветке не было новых коммитов после создания feature-ветки — Git просто «продвигает» указатель ветки вперёд, без создания отдельного merge-коммита, история выглядит линейной, как будто feature-ветки не было вовсе. `git merge --no-ff` принудительно создаёт merge-коммит, даже если fast-forward был бы возможен — это сохраняет в истории явный факт, что была отдельная ветка разработки, что удобно для читаемости истории и для возможности откатить весь набор изменений одним `revert` merge-коммита. + +### Как выглядит типичный pipeline для инфраструктурного репозитория? + +`lint` (например, `ansible-lint` для проверки стиля и распространённых ошибок в ролях) → `test` (`molecule test` — идемпотентность и корректность роли в изолированном окружении, см. [../ansible/CASE.md](../ansible/CASE.md)) → `deploy` (применение роли/плейбука на целевую инфраструктуру, обычно с разделением на staging и production stage с ручным approve перед production). Именно эту цепочку я воспроизвёл в [CASE.md](CASE.md) — начиная с `push` в Gitea и заканчивая автоматическим прогоном `molecule test` в пайплайне. diff --git a/stack/databases/CASE.md b/stack/databases/CASE.md new file mode 100644 index 0000000..499bec5 --- /dev/null +++ b/stack/databases/CASE.md @@ -0,0 +1,90 @@ +# Кейс: PostgreSQL (репликация и базовые запросы) + +Связь с легендой: [../../legend/LEGEND.md](../../legend/LEGEND.md) — раздел «Домашняя лаборатория / pet-проект». SQL как язык запросов уже вписан в легенду как сквозной инструмент (работа с данными сопровождаемых сервисов в АО ТНИИС), но отдельного опыта администрирования/репликации самой СУБД в рабочей практике не было — это тоже честный pet-кейс поверх рабочего опыта с запросами. + +## Что нужно реально сделать (домашний стенд) + +Поднять master + одну streaming-реплику PostgreSQL в Docker Compose, попробовать синхронный и асинхронный режим репликации, прогнать `pgbench` для базовых цифр производительности и потренировать SQL-запросы на простой учебной схеме. + +### 1. docker-compose.yml — master + replica + +```yaml +version: "3.8" + +services: + pg-master: + image: postgres:16 + container_name: pg-master + environment: + POSTGRES_PASSWORD: labpass + POSTGRES_USER: labuser + POSTGRES_DB: labdb + command: > + postgres + -c wal_level=replica + -c max_wal_senders=5 + -c max_replication_slots=5 + -c synchronous_commit=on + -c synchronous_standby_names='FIRST 1 (replica1)' + volumes: + - ./master-init.sql:/docker-entrypoint-initdb.d/init.sql + ports: + - "5432:5432" + + pg-replica: + image: postgres:16 + container_name: pg-replica + environment: + POSTGRES_PASSWORD: labpass + PGUSER: labuser + depends_on: + - pg-master + ports: + - "5433:5432" + entrypoint: > + bash -c " + until pg_basebackup -h pg-master -D /var/lib/postgresql/data -U labuser -Fp -Xs -P -R --slot=replica1 --create-slot; + do echo waiting for master; sleep 2; done; + echo \"primary_conninfo = 'host=pg-master port=5432 user=labuser application_name=replica1'\" >> /var/lib/postgresql/data/postgresql.auto.conf; + exec postgres" +``` + +### 2. master-init.sql — учебная схема + +```sql +CREATE TABLE customers ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + customer_id INT REFERENCES customers(id), + amount NUMERIC(10,2), + status TEXT +); + +INSERT INTO customers (name) VALUES ('Иванов'), ('Петров'), ('Сидоров'); +INSERT INTO orders (customer_id, amount, status) +VALUES (1, 1500.00, 'paid'), (1, 300.00, 'pending'), (2, 4200.00, 'paid'); +``` + +### 3. Шаги воспроизведения + +1. `docker compose up -d` — поднять master, дождаться, пока replica пройдёт `pg_basebackup` и подключится (`docker compose logs -f pg-replica`). +2. Проверить репликацию: `psql -h localhost -p 5432 -U labuser labdb -c "INSERT INTO customers (name) VALUES ('Новый клиент')"`, затем `psql -h localhost -p 5433 -U labuser labdb -c "SELECT * FROM customers"` — строка должна появиться на реплике. +3. Проверить статус на master: `SELECT * FROM pg_stat_replication;` — увидеть `replica1`, `state = streaming`, `sync_state = sync` (при заданном `synchronous_standby_names`). +4. Переключить на асинхронный режим: убрать `synchronous_standby_names` из command, перезапустить master, повторить `pg_stat_replication` — `sync_state` сменится на `async`. Разница на практике: при `sync` транзакция на master не считается закоммиченной, пока реплика не подтвердила запись (гарантия нуля потерянных данных при падении master ценой задержки commit); при `async` master коммитит сразу, не дожидаясь реплики (быстрее, но при падении master возможна потеря последних транзакций). +5. Погонять `pgbench` для базовых цифр: `pgbench -h localhost -p 5432 -U labuser -i labdb` (инициализация), `pgbench -h localhost -p 5432 -U labuser -c 10 -j 2 -T 30 labdb` (10 клиентов, 30 секунд) — записать TPS в [../../legend/CAPACITY.md](../../legend/CAPACITY.md). +6. Потренировать запросы на схеме `customers`/`orders`: JOIN, агрегации, `WHERE id = N` — см. [QUESTIONS.md](QUESTIONS.md). + +## Что это даёт в разговоре с интервьюером + +- Практическое понимание разницы sync/async репликации не как определения, а как наблюдаемого поведения (`pg_stat_replication`, разная задержка commit). +- Понимание streaming-репликации через WAL и `pg_basebackup` — как реплика вообще получает данные. +- Базовые цифры TPS со своей лабы — честная отправная точка для разговора про производительность (см. [../../legend/CAPACITY.md](../../legend/CAPACITY.md)). +- Уверенное владение основными типами JOIN и агрегатными запросами на конкретной схеме. + +## Как это ложится в легенду + +PostgreSQL как СУБД — pet-кейс, не приписанный к опыту в компаниях. SQL как язык запросов к данным сопровождаемых сервисов остаётся в легенде как рабочий навык (см. [../../legend/LEGEND.md](../../legend/LEGEND.md)); этот кейс добавляет к нему более глубокое, честно обозначенное pet-понимание того, как устроена сама СУБД под капотом. diff --git a/stack/databases/QUESTIONS.md b/stack/databases/QUESTIONS.md new file mode 100644 index 0000000..b790c17 --- /dev/null +++ b/stack/databases/QUESTIONS.md @@ -0,0 +1,43 @@ +# Вопросы: PostgreSQL / базы данных + +Опираются на кейс: [CASE.md](CASE.md). Источники реальных вопросов — [Реалист банк.md](../../interview/Реалист%20банк.md), [Alfa-Bank.md](../../interview/Alfa-Bank.md), [VK Cloud.md](../../interview/VK%20Cloud.md). + +### Расскажи про синхронный и асинхронный режим работы PostgreSQL — в чём разница? + +При синхронной репликации (`synchronous_commit=on` + `synchronous_standby_names`) master не подтверждает клиенту commit транзакции, пока хотя бы одна указанная реплика не подтвердила запись WAL — это гарантирует нулевую потерю данных при падении master ценой более медленного commit. При асинхронной репликации master коммитит сразу и отправляет WAL реплике фоном — быстрее, но при падении master прямо перед репликацией последние транзакции можно потерять. У себя в стенде я явно переключал этот параметр и видел разницу в `pg_stat_replication.sync_state` (`sync` → `async`), а не просто читал про это. + +### Как будешь разворачивать PostgreSQL на две ноды? + +Классическая схема — master + streaming replica: реплика инициализируется через `pg_basebackup` с master и дальше получает изменения через WAL-стриминг по replication slot. Дальше поверх этого нужен failover-механизм (например, Patroni + etcd/Consul для автоматического переключения при падении master) — сам failover-контроллер я не поднимал, но принцип базовой репликации прогнал в Docker Compose на две ноды (master + replica) и вижу, из каких частей состоит более сложная HA-схема поверх этого. + +### Какие виды JOIN существуют и как они работают? + +`INNER JOIN` — только строки, где есть совпадение в обеих таблицах. `LEFT JOIN` — все строки левой таблицы + совпадения справа (NULL, если совпадения нет). `RIGHT JOIN` — зеркально, все строки правой таблицы. `FULL OUTER JOIN` — все строки из обеих таблиц, с NULL там, где нет совпадения. На своей учебной схеме `customers`/`orders`: `SELECT c.name, o.amount FROM customers c LEFT JOIN orders o ON o.customer_id = c.id` покажет всех клиентов, включая тех, у кого нет заказов (у меня в примере таких не было, но проверял именно так, добавляя клиента без заказов). + +### Что такое кластер (в контексте баз данных)? + +В контексте PostgreSQL термин многозначный: (1) «кластер БД» на уровне процесса — это весь набор баз данных, которыми управляет один экземпляр `postgres` (директория данных, `initdb`); (2) в контексте отказоустойчивости — группа из нескольких инстансов (master + реплики), обеспечивающая доступность и/или масштабирование чтения. У себя в лабе я поднял именно второй случай — master + reплика как единый логический кластер с общими данными. + +### Как происходит репликация базы данных? + +Master пишет все изменения в WAL (write-ahead log) до применения к данным. Реплика подключается к master через replication slot, получает поток WAL-записей (streaming replication) и применяет их у себя, воспроизводя то же состояние с небольшой задержкой (или без задержки — при sync-режиме). Начальное состояние реплика получает через `pg_basebackup` — полную копию данных на момент старта, дальше — только дельты через WAL. + +### Что такое quorum в конфигурации master-master (или в отказоустойчивом кластере вообще)? + +Кворум — минимальное количество узлов, которое должно быть согласно/доступно, чтобы кластер считал своё решение валидным (обычно больше половины от общего числа узлов). Нужен, чтобы избежать одновременного принятия противоречащих решений разными частями кластера при разрыве сети — при потере кворума меньшая часть кластера не имеет права принимать решения (например, выбирать нового master), что и предотвращает split brain. + +### Расскажи про split brain и почему кворум помогает? + +Split brain — ситуация, когда из-за разрыва сети кластер разделяется на две части, и обе считают себя главными/рабочими одновременно (например, две ноды одновременно думают, что они master и принимают записи) — это ведёт к расхождению данных. Кворум решает эту проблему: только та часть кластера, где узлов больше половины от общего числа, имеет право принимать решения (выбирать нового лидера, подтверждать транзакции); меньшая часть автоматически переходит в read-only или отказывает в обслуживании, не считая себя валидным большинством. + +### Есть таблица orders — какая команда выведет количество записей, где id = 15? + +```sql +SELECT COUNT(*) FROM orders WHERE id = 15; +``` + +Так как `id` — первичный ключ, результат либо 0, либо 1; если бы вопрос был про количество заказов конкретного клиента, это было бы `SELECT COUNT(*) FROM orders WHERE customer_id = 15;`. + +### Что такое скоринг и кредитный конвейер? (контекст банковских вакансий) + +Скоринг — процесс автоматической оценки кредитоспособности клиента по набору параметров (доход, кредитная история и т.д.), результат — числовой балл или решение одобрить/отклонить. Кредитный конвейер — сквозной автоматизированный процесс обработки заявки на кредит от подачи до решения: сбор данных → проверки (антифрод, скоринг, внешние бюро) → решение → оформление. Для инженера сопровождения/DevOps практический смысл в том, что это высоконагруженный и критичный по SLA процесс — отсюда повышенные требования к мониторингу и отказоустойчивости систем, которые его обслуживают. diff --git a/stack/docker/CASE.md b/stack/docker/CASE.md new file mode 100644 index 0000000..f8ba1f1 --- /dev/null +++ b/stack/docker/CASE.md @@ -0,0 +1,118 @@ +# Кейс: Docker + +Связь с легендой: [../../legend/LEGEND.md](../../legend/LEGEND.md) — раздел «АО ТНИИС», контейнеризация сервисов мониторинга и вспомогательных инструментов. Практика опирается на уже готовые стенды [../monitoring/CASE.md](../monitoring/CASE.md) и [../nginx/CASE.md](../nginx/CASE.md) — оба используют Docker Compose. + +## Что нужно реально сделать + +### 1. Dockerfile для собственного простого сервиса (multi-stage build) + +```dockerfile +# build stage +FROM golang:1.22-alpine AS build +WORKDIR /src +COPY . . +RUN go build -o /app ./... + +# итоговый образ — только бинарник, без всей тулчейна сборки +FROM alpine:3.19 +COPY --from=build /app /app +ENTRYPOINT ["/app"] +``` + +``` +# .dockerignore +.git +*.md +node_modules +``` + +Multi-stage build: первый этап содержит весь тяжёлый тулчейн сборки (компилятор, зависимости), второй — только финальный артефакт. Итоговый образ в разы меньше и не тащит в прод лишние инструменты сборки. Слои кешируются построчно — `COPY . .` перед `RUN build` означает, что при изменении любого файла кеш слоя сборки инвалидируется; на реальных проектах порядок команд специально выстраивают так, чтобы редко меняющиеся зависимости копировались раньше исходного кода. + +### 2. Volumes: bind mount vs named volume + +```bash +# bind mount — конкретный путь на хосте, удобно для конфигов и разработки +docker run -v /host/path/nginx.conf:/etc/nginx/nginx.conf:ro nginx + +# named volume — управляется самим Docker, удобно для данных (БД, персистентное состояние) +docker volume create pgdata +docker run -v pgdata:/var/lib/postgresql/data postgres +``` + +Bind mount используется в кейсах [../monitoring/CASE.md](../monitoring/CASE.md) и [../nginx/CASE.md](../nginx/CASE.md) для конфигов (`prometheus.yml`, `nginx.conf`) — удобно редактировать файл на хосте и сразу видеть изменения в контейнере. Named volume лучше подходит для данных, которые не нужно редактировать руками с хоста и которые должны переживать пересоздание контейнера (в [../databases/CASE.md](../databases/CASE.md) для этого пригодился бы именно named volume под `/var/lib/postgresql/data`). + +### 3. Сети Docker: как контейнеры находят друг друга + +```bash +docker network ls +docker network inspect _default +``` + +В Docker Compose по умолчанию создаётся отдельная bridge-сеть на проект, и все сервисы внутри неё резолвят друг друга по имени сервиса через встроенный DNS Docker — именно поэтому в [../monitoring/CASE.md](../monitoring/CASE.md) Prometheus обращается к `node-exporter:9100`, а не по IP: имя сервиса из `docker-compose.yml` работает как hostname внутри этой сети. + +### 4. Ограничение ресурсов и что происходит при превышении + +```yaml +services: + app: + image: myapp + deploy: + resources: + limits: + cpus: "0.5" + memory: 256M +``` + +```bash +docker run --memory=256m --cpus=0.5 myapp +``` + +При превышении лимита памяти ядро (через cgroups, см. [../linux-bash/QUESTIONS.md](../linux-bash/QUESTIONS.md)) убивает процесс через OOM killer — контейнер завершается с кодом 137 (128 + сигнал 9 SIGKILL). Воспроизвести: запустить в контейнере с `--memory=50m` процесс, который выделяет память сверх лимита (`stress --vm 1 --vm-bytes 100M`), и увидеть код выхода 137 через `docker inspect --format '{{.State.ExitCode}}'`. + +### 5. Диагностика упавшего контейнера + +```bash +docker logs +docker logs --tail 50 -f +docker exec -it sh +docker inspect +docker inspect --format '{{.State.ExitCode}} {{.State.OOMKilled}}' +``` + +### 6. Внутренности: виртуализация vs контейнеризация, containerd, PID контейнера + +Виртуализация (VM) — эмулирует полное отдельное «железо» с собственным ядром ОС через гипервизор (VMware/KVM/Hyper-V) — тяжелее, но полная изоляция вплоть до ядра. Контейнеризация — все контейнеры на хосте используют одно и то же ядро хоста, изоляция обеспечивается namespaces (что процесс видит) и ограничение ресурсов — cgroups (сколько он может использовать), без отдельного гостевого ядра — отсюда контейнеры легче и стартуют быстрее VM. + +`containerd` — низкоуровневый контейнерный рантайм, которым Docker Engine пользуется под капотом для реального управления жизненным циклом контейнеров (запуск, остановка, управление образами) — сам Docker CLI/демон — более высокоуровневая обвязка поверх containerd с удобным UX (`docker build`, `docker compose` и т.п.). Docker понимает, что контейнер «умер», потому что containerd отслеживает главный процесс контейнера (PID 1 внутри контейнера) — как только этот процесс завершается (сам или из-за ошибки), containerd фиксирует это событие и помечает контейнер как `Exited`. + +```bash +docker inspect --format '{{.State.Pid}}' # PID процесса контейнера на хосте +ps aux | grep <тот же PID> # виден и на хосте — контейнер это просто изолированный процесс хоста +``` + +### 7. Entrypoint vs CMD + +```dockerfile +ENTRYPOINT ["myapp"] +CMD ["--config", "/etc/myapp/default.yml"] +``` + +`ENTRYPOINT` задаёт неизменяемую (без явного `--entrypoint` при запуске) главную команду контейнера. `CMD` задаёт аргументы по умолчанию к ней, которые легко переопределить при `docker run myapp --config /other.yml` — эта строка заменит именно `CMD`, не трогая `ENTRYPOINT`. Если задан только `CMD` без `ENTRYPOINT` — вся строка `CMD` целиком заменяется аргументами `docker run`. + +### 8. Копирование файлов в работающий контейнер + +```bash +docker cp ./local-file.txt :/app/file.txt +docker cp :/app/output.log ./output.log +``` + +## Что это даёт в разговоре с интервьюером + +- Понимание, что контейнер технически — обычный процесс хоста с изоляцией через namespaces/cgroups, а не мини-VM. +- Практика multi-stage build и осознанного слоёного кеширования, а не просто «Dockerfile работает». +- Знание разницы bind mount/named volume не абстрактно, а с привязкой к тому, где какой тип реально применён в других кейсах репозитория. +- Воспроизведённый вживую OOM kill (код 137) — понимание на практике, а не только в теории. + +## Как это ложится в легенду + +В реальной работе (АО ТНИИС) Docker упоминается в стеке отдела как средство контейнеризации сервисов сопровождения. Этот кейс показывает собственный опыт сборки, сетевого взаимодействия и диагностики контейнеров поверх уже готовых стендов мониторинга и nginx. diff --git a/stack/docker/QUESTIONS.md b/stack/docker/QUESTIONS.md new file mode 100644 index 0000000..79e6128 --- /dev/null +++ b/stack/docker/QUESTIONS.md @@ -0,0 +1,58 @@ +# Вопросы: Docker + +Опираются на кейс: [CASE.md](CASE.md). Источники — [VK Cloud.md](../../interview/VK%20Cloud.md), [Реалист банк.md](../../interview/Реалист%20банк.md). + +### В чём разница между образом (image) и контейнером? + +Образ — неизменяемый шаблон: слоёная файловая система + метаданные (какая команда запускается по умолчанию, какие переменные окружения и т.д.), результат `docker build`. Контейнер — запущенный (или остановленный) экземпляр образа с добавленным поверх write-слоем, где живут любые runtime-изменения — по сути процесс на хосте плюс собственная изолированная файловая система, сеть и т.д. Один образ может породить много независимых контейнеров. + +### В чём отличия виртуализации от контейнеризации? + +Виртуализация эмулирует отдельное «железо» через гипервизор — у каждой VM своё полноценное ядро ОС, изоляция максимальная, но накладные расходы выше (полный загруженный образ ОС, медленнее старт). Контейнеризация — все контейнеры хоста делят одно и то же ядро хоста; изоляция достигается через namespaces (что процесс видит — свои PID, сеть, файловую систему) и ограничение ресурсов через cgroups (сколько он может использовать), см. [../linux-bash/QUESTIONS.md](../linux-bash/QUESTIONS.md). Контейнеры легче и стартуют за секунды, но изоляция менее строгая (общее ядро — потенциальная поверхность атаки при уязвимостях в ядре). + +### Как Docker понимает, что контейнер умер, и помечает его как exit? + +Внутри Docker Engine работает `containerd` — реальный рантайм, управляющий процессами контейнеров. Он отслеживает главный процесс контейнера (PID 1 внутри контейнерного namespace); как только этот процесс завершается (штатно или с ошибкой), это фиксируется как событие завершения, и статус контейнера меняется на `Exited` с соответствующим exit-кодом (`docker inspect --format '{{.State.ExitCode}}'`). + +### Что такое Docker runtime и зачем нужен containerd? + +Runtime — компонент, который непосредственно создаёт и управляет изолированными процессами контейнеров на уровне ОС (namespaces, cgroups, файловая система из слоёв образа). `containerd` — низкоуровневый рантайм, на котором построен сам Docker Engine: Docker CLI/демон — это удобная надстройка (сборка образов, compose, registry-взаимодействие) поверх `containerd`, который непосредственно исполняет работу по управлению жизненным циклом контейнеров. Это разделение позволяет другим системам (например, Kubernetes через CRI) использовать `containerd` напрямую, без полного Docker Engine. + +### Как зайти внутрь контейнера? + +```bash +docker exec -it sh # или bash, если есть в образе +``` + +`exec` запускает новый процесс внутри уже работающего контейнера (в отличие от `docker run`, который создаёт новый контейнер). Если контейнер уже остановлен, `exec` не сработает — нужно либо запустить его заново, либо использовать `docker cp` для файлов без запуска процесса внутри. + +### Как скопировать файл в работающий контейнер? + +```bash +docker cp ./local-file.txt :/path/in/container +docker cp :/path/in/container ./local-file.txt # и в обратную сторону, из контейнера на хост +``` + +### Если убить процесс Docker (сам демон) — запущенные контейнеры сдохнут? + +Нет, не обязательно — сами контейнеры управляются `containerd`, а не напрямую демоном `dockerd`; при перезапуске `dockerd` уже запущенные контейнеры продолжают работать (это осознанное архитектурное решение, начиная с версии Docker, где Docker Engine был разделён с `containerd` — раньше, в старых версиях, где всё было завязано на монолитный демон, перезапуск демона действительно ронял контейнеры). После рестарта `dockerd` он заново подключается к уже работающим контейнерам через `containerd` и восстанавливает их видимость в `docker ps`. + +### Как работает сеть в Docker? + +По умолчанию Docker создаёт bridge-сеть (`docker0` для отдельных контейнеров, отдельная сеть на проект в Docker Compose), контейнеры получают виртуальные сетевые интерфейсы, подключённые к этому мосту, и приватные IP внутри него. Docker поднимает встроенный DNS внутри пользовательских (non-default) bridge-сетей — контейнеры резолвят друг друга по имени сервиса/контейнера, а не по IP. Наружу трафик пробрасывается через `-p host_port:container_port`, что настраивает NAT-правила (iptables) для перенаправления с порта хоста на IP контейнера внутри моста. + +### Много ли процессов внутри одного контейнера? У контейнеров есть PID? + +Технически контейнер может запускать сколько угодно процессов (например, если ENTRYPOINT — скрипт, который стартует несколько дочерних процессов), но общепринятая практика — один основной процесс на контейнер (принцип "один контейнер — одна ответственность"), чтобы жизненный цикл контейнера чётко соответствовал жизненному циклу этого процесса. У контейнера есть PID — как у любого обычного процесса хоста (см. `docker inspect --format '{{.State.Pid}}'` в кейсе), плюс внутри контейнера, благодаря PID namespace, главный процесс видит себя как PID 1 в своём изолированном пространстве. + +### Entrypoint vs CMD — в чём разница? + +`ENTRYPOINT` — фиксированная главная команда контейнера, `CMD` — аргументы к ней по умолчанию, которые можно переопределить, просто передав другие аргументы в `docker run image <новые аргументы>`. Если задан только `CMD` без `ENTRYPOINT`, вся команда целиком переопределяется тем, что передано в `docker run`. Практическая польза разделения — можно сделать образ, у которого фиксирована сама программа (`ENTRYPOINT ["myapp"]`), но легко менять флаги запуска без пересборки образа. + +### Разница docker ps, docker ps -a, docker logs, docker images? + +`docker ps` — только работающие контейнеры. `docker ps -a` — вообще все контейнеры, включая остановленные/завершившиеся. `docker logs ` — стандартный вывод (stdout/stderr) процесса внутри контейнера, накопленный с момента старта (`-f` — читать в реальном времени, как `tail -f`). `docker images` — список локально скачанных/собранных образов (в отличие от контейнеров — это шаблоны, а не запущенные экземпляры). + +### Как сохранить логи из контейнера? + +`docker logs > output.log` — перенаправить stdout вывод команды в файл на хосте. Если логи пишутся не в stdout, а в файл внутри контейнера — `docker cp :/path/to/log.file ./log.file`. Для постоянного сохранения логов за пределами жизни контейнера в проде обычно настраивают logging driver (например, `json-file` с ротацией, или отправку в централизованную систему — см. [../elk/CASE.md](../elk/CASE.md)) вместо ручного копирования. diff --git a/stack/elk/CASE.md b/stack/elk/CASE.md new file mode 100644 index 0000000..1ae659c --- /dev/null +++ b/stack/elk/CASE.md @@ -0,0 +1,100 @@ +# Кейс: Elastic Stack (ELK) + +Связь с легендой: [../../legend/LEGEND.md](../../legend/LEGEND.md) — раздел «АО ТНИИС», централизованный сбор и анализ логов в дополнение к метрикам из Prometheus. + +## Что нужно реально сделать (домашний стенд) + +Docker Compose стенд Elasticsearch + Kibana + Filebeat, собирающий логи уже готового кейса [../nginx/CASE.md](../nginx/CASE.md) — реальная связка «есть логи → собрали → увидели в Kibana», а не абстрактный пример. + +### 1. docker-compose.yml + +```yaml +version: "3.8" + +services: + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0 + container_name: elasticsearch + environment: + - discovery.type=single-node + - xpack.security.enabled=false + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + ports: + - "9200:9200" + + kibana: + image: docker.elastic.co/kibana/kibana:8.13.0 + container_name: kibana + environment: + - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 + ports: + - "5601:5601" + depends_on: + - elasticsearch + + filebeat: + image: docker.elastic.co/beats/filebeat:8.13.0 + container_name: filebeat + user: root + volumes: + - ./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro + - nginx_logs:/var/log/nginx:ro + depends_on: + - elasticsearch + +volumes: + nginx_logs: +``` + +Чтобы подключить реальные логи из кейса [../nginx/CASE.md](../nginx/CASE.md), в его `docker-compose.yml` у сервиса `proxy` нужно смонтировать тот же named volume `nginx_logs` на `/var/log/nginx` — тогда Filebeat читает те же файлы логов, что пишет живой nginx. + +### 2. filebeat.yml + +```yaml +filebeat.inputs: + - type: log + enabled: true + paths: + - /var/log/nginx/access.log + fields: + service: nginx-lab + +output.elasticsearch: + hosts: ["elasticsearch:9200"] + index: "nginx-logs-%{+yyyy.MM.dd}" + +setup.template.name: "nginx-logs" +setup.template.pattern: "nginx-logs-*" +``` + +### 3. Шаги воспроизведения + +1. В [../nginx/CASE.md](../nginx/CASE.md) настроить `access_log` в формате, откуда легко выделить статус-код (стандартный combined-формат nginx это уже делает). +2. `docker compose up -d` для обоих стендов (или объединить в один docker-compose.yml). +3. Сгенерировать трафик через nginx-стенд (`curl` в цикле из шагов [../nginx/CASE.md](../nginx/CASE.md)), чтобы появились строки в access.log. +4. Проверить, что данные дошли: `curl http://localhost:9200/nginx-logs-*/_search?pretty`. +5. В Kibana (`localhost:5601`) создать index pattern `nginx-logs-*`, зайти в Discover — увидеть живые записи логов. +6. Построить простую визуализацию: количество запросов по HTTP-статус-коду (парсинг статуса из строки лога через встроенный `nginx` module Filebeat или простой grok-подобный dissect-фильтр). + +### 4. Простой parse-фильтр для нестандартных логов + +```yaml +processors: + - dissect: + tokenizer: '%{client_ip} - - [%{@timestamp}] "%{method} %{url} %{http_version}" %{status_code} %{bytes}' + field: "message" + target_prefix: "" +``` + +`dissect` — более простой и быстрый аналог `grok` для логов со стабильным, предсказуемым форматом (не требует регулярных выражений, работает через явные разделители) — здесь разбирает стандартную строку access-лога nginx на отдельные поля (`status_code`, `method`, `url`), которые дальше можно фильтровать и агрегировать в Kibana. + +## Что это даёт в разговоре с интервьюером + +- Реально собранная связка «источник логов (nginx) → shipper (Filebeat) → хранилище/поиск (Elasticsearch) → визуализация (Kibana)». +- Понимание, зачем нужен index pattern и что такое shard/index на базовом уровне (см. [QUESTIONS.md](QUESTIONS.md)). +- Понимание разницы Filebeat (лёгкий shipper, просто пересылает строки/файлы) vs Logstash (тяжёлый обработчик с богатыми фильтрами — grok, mutate, конвертация форматов). +- Практика простого dissect/parse-фильтра для структурирования сырых текстовых логов. + +## Как это ложится в легенду + +ELK упоминается в стеке отдела АО ТНИИС как часть инфраструктуры сопровождения, дополняющая метрики. Кейс замыкает цепочку с уже проработанным Nginx-кейсом — те же логи, которые нужно было бы разбирать вручную, теперь централизованно собираются и доступны для поиска и визуализации. diff --git a/stack/elk/QUESTIONS.md b/stack/elk/QUESTIONS.md new file mode 100644 index 0000000..1982443 --- /dev/null +++ b/stack/elk/QUESTIONS.md @@ -0,0 +1,35 @@ +# Вопросы: Elastic Stack (ELK) + +Опираются на кейс: [CASE.md](CASE.md). Источники — [Реалист банк.md](../../interview/Реалист%20банк.md), [Bi.Zone.md](../../interview/Bi.Zone.md). + +### Зачем нужен централизованный сбор логов? + +Без централизации при инциденте пришлось бы вручную заходить на каждый сервер и грепать локальные файлы — на масштабе больше пары серверов это нереально быстро. Централизованный сбор (Filebeat/Logstash → Elasticsearch → Kibana) даёт единую точку поиска по всем источникам сразу, с фильтрами, временными диапазонами и агрегациями — я это прочувствовал даже на своём мини-стенде: логи одного nginx-контейнера искать через Kibana удобнее, чем `docker logs | grep` вручную, а на масштабе десятков сервисов разница становится критичной. + +### Что такое index и shard в Elasticsearch на базовом уровне? + +Index — логическая единица хранения данных в Elasticsearch, аналог таблицы/базы в привычных СУБД (у меня — `nginx-logs-2026.07.17`, с ротацией по дням). Shard — физическая часть индекса: Elasticsearch разбивает индекс на несколько shard'ов, которые могут распределяться по разным нодам кластера — это даёт горизонтальное масштабирование (шардов больше — можно параллельно обрабатывать запросы/индексацию на нескольких машинах) и отказоустойчивость через replica-шарды (копии основных шардов на других нодах). На однонодовом dev-стенде я это не мог продемонстрировать в полную силу (реплики некуда класть при single-node), но структуру index → shards видел через `_cat/indices` и `_cat/shards`. + +### В чём разница между Logstash и Filebeat? + +Filebeat — лёгкий shipper: минимум ресурсов, задача — надёжно прочитать файл/поток и переслать данные дальше (в Elasticsearch напрямую или через Logstash), с минимальной трансформацией (у меня — простой `dissect` для разбора строки access-лога). Logstash — тяжёлый обработчик с богатым набором фильтров (`grok`, `mutate`, `date`, конвертация форматов, обогащение из внешних источников) — уместен, когда нужна серьёзная трансформация/нормализация разнородных логов перед индексацией. Типичная практика — Filebeat на источнике (лёгкий агент на каждом хосте) → опционально Logstash в середине для тяжёлой обработки → Elasticsearch. В своём стенде обошёлся без Logstash, потому что формат nginx access-лога стабильный и `dissect` в самом Filebeat справился. + +### Чем отличается ELK от OpenSearch? + +OpenSearch — форк Elasticsearch и Kibana (под открытой лицензией, после того как Elastic сменил лицензирование части своего стека) — архитектурно и по API очень близок к Elasticsearch на момент форка, дальше эти проекты развиваются раздельно и постепенно расходятся в фичах. С точки зрения повседневной эксплуатации на базовом уровне (индексация, поиск, дашборды) отличия для рядового пользователя минимальны — выбор между ними чаще определяется лицензионной политикой компании (открытая лицензия OpenSearch vs текущее лицензирование Elastic), а не техническими возможностями на базовом уровне. + +### Ты мониторил стандартными службами мониторинга сам стек ELK? Если да, то как? + +Elasticsearch отдаёт собственные метрики (использование JVM heap, длина очередей индексации, состояние шардов, задержки запросов) через встроенный API (`_cluster/health`, `_nodes/stats`) — это можно собирать тем же Prometheus через `elasticsearch_exporter` и визуализировать в Grafana, аналогично стенду [../monitoring/CASE.md](../monitoring/CASE.md). Сам ELK-стек — это тоже сервис, которому нужен мониторинг (не только логи снаружи собирает, но и сам может деградировать — например, при переполнении диска или нехватке heap), поэтому в проде разумно смотреть на него теми же инструментами, что и на остальную инфраструктуру, а не только через собственный Kibana Stack Monitoring. + +### Разворачивал ELK с нуля с агентами? + +Да, стенд в [CASE.md](CASE.md) — Elasticsearch + Kibana + Filebeat с нуля через Docker Compose, с Filebeat, читающим реальные логи работающего nginx-контейнера из отдельного кейса, а не тестовые/синтетические данные. Настроил index pattern в Kibana и простую визуализацию по статус-кодам запросов nginx. + +### Сколько Гб данных логов будет в ELK, если в Prometheus уже 15 Гб метрик? + +Однозначного числа тут нет — вопрос проверяет понимание порядка и природы различия между логами и метриками, а не конкретную формулу. Метрики — компактные числовые ряды с фиксированной структурой (timestamp + значение + лейблы), которые к тому же хорошо сжимаются за счёт повторяемости паттернов (Prometheus TSDB использует специальное сжатие временных рядов). Логи — произвольный текст, часто многословный (стектрейсы, JSON с вложенностью, человекочитаемые сообщения), без такой плотной структуры — при сопоставимом количестве событий объём логов обычно на порядок (иногда на два) больше объёма метрик. Отсюда и практический вывод, который я бы озвучил на этом вопросе: если стоит выбор, что мониторить в первую очередь при ограниченных ресурсах хранения — метрики обычно дешевле держать долго и по ним быстрее строить алерты по порогам, а логи — глубже для расследования причины конкретного инцидента, но дороже в хранении; поэтому разумная схема — метрики для алертинга и трендов + логи с более коротким retention для расследований (см. также [../monitoring/QUESTIONS.md](../monitoring/QUESTIONS.md) про выбор между логами и метриками для конкретного инцидента). + +### Что такое retention логов и зачем он нужен? + +Retention — период, в течение которого логи хранятся, прежде чем автоматически удаляются/архивируются. Нужен, потому что логи растут в объёме быстрее метрик (см. вопрос выше) и бесконечное хранение экономически и технически нецелесообразно — retention настраивают через Index Lifecycle Management в Elasticsearch (например, hot-фаза несколько дней на быстрых дисках для активного поиска, потом warm/cold фазы на более дешёвом хранилище, потом delete). Конкретный срок — компромисс между стоимостью хранения и требованиями (иногда регуляторными) держать историю логов для расследования инцидентов задним числом. diff --git a/stack/kafka/CASE.md b/stack/kafka/CASE.md new file mode 100644 index 0000000..3d379d9 --- /dev/null +++ b/stack/kafka/CASE.md @@ -0,0 +1,81 @@ +# Кейс: Kafka (домашняя однонодовая лаба) + +Связь с легендой: [../../legend/LEGEND.md](../../legend/LEGEND.md) — раздел «Домашняя лаборатория / pet-проект». Kafka упоминалась только в требованиях вакансии Альфа-Банка и в вопросах Bi.Zone — в рабочем стеке компаний из резюме её нет, честный pet-кейс. + +## Что нужно реально сделать (домашний стенд) + +Однонодовый Kafka-брокер в режиме KRaft (без ZooKeeper — так проще для домашней лабы и это актуальная архитектура в новых версиях) в Docker, создать топик, погонять продюсера/консюмера через встроенные CLI-утилиты и замерить пропускную способность через `kafka-producer-perf-test`. + +### 1. docker-compose.yml + +```yaml +version: "3.8" + +services: + kafka: + image: apache/kafka:3.7.0 + container_name: kafka-lab + ports: + - "9092:9092" + environment: + KAFKA_NODE_ID: 1 + KAFKA_PROCESS_ROLES: broker,controller + KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 + KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER + KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka-lab:9093 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 +``` + +### 2. Создать топик и погонять продюсера/консюмера + +```bash +docker compose up -d + +docker exec -it kafka-lab /opt/kafka/bin/kafka-topics.sh \ + --create --topic orders-events --bootstrap-server localhost:9092 \ + --partitions 3 --replication-factor 1 + +# консюмер в одном терминале +docker exec -it kafka-lab /opt/kafka/bin/kafka-console-consumer.sh \ + --topic orders-events --bootstrap-server localhost:9092 --group demo-group + +# продюсер в другом терминале +docker exec -it kafka-lab /opt/kafka/bin/kafka-console-producer.sh \ + --topic orders-events --bootstrap-server localhost:9092 +``` + +Набрать несколько сообщений в продюсере — увидеть их в консюмере в реальном времени. Открыть второй консюмер с той же `--group demo-group` — увидеть, как партиции топика (3 штуки) распределяются между консюмерами внутри одной consumer group (каждое сообщение читается только одним консюмером из группы — это и есть горизонтальное масштабирование обработки). + +### 3. Партиции и оффсеты + +```bash +docker exec -it kafka-lab /opt/kafka/bin/kafka-topics.sh \ + --describe --topic orders-events --bootstrap-server localhost:9092 + +docker exec -it kafka-lab /opt/kafka/bin/kafka-consumer-groups.sh \ + --describe --group demo-group --bootstrap-server localhost:9092 +``` + +Второй вывод показывает `CURRENT-OFFSET`/`LOG-END-OFFSET`/`LAG` на партицию — практическое понимание, как Kafka отслеживает, что консюмер уже прочитал, а что ещё нет. + +### 4. Замер пропускной способности + +```bash +docker exec -it kafka-lab /opt/kafka/bin/kafka-producer-perf-test.sh \ + --topic orders-events --num-records 100000 --record-size 200 \ + --throughput -1 --producer-props bootstrap.servers=localhost:9092 +``` + +Записать полученные `records/sec` и `MB/sec` в [../../legend/CAPACITY.md](../../legend/CAPACITY.md) — честная лабная цифра для разговора про пропускную способность. + +## Что это даёт в разговоре с интервьюером + +- Понимание топика, партиции, consumer group, оффсета не как определений, а как наблюдаемого поведения (`--describe` показывает реальное распределение и лаг). +- Понимание, зачем нужны партиции — параллельная обработка внутри одной consumer group. +- Лабная цифра пропускной способности как честная база для разговора о производительности. + +## Как это ложится в легенду + +Kafka — pet-кейс, не приписанный к опыту в компаниях. Упоминается в резюме только в навыках как самостоятельно проработанная технология, без привязки к конкретному рабочему проекту. diff --git a/stack/kafka/QUESTIONS.md b/stack/kafka/QUESTIONS.md new file mode 100644 index 0000000..3e77033 --- /dev/null +++ b/stack/kafka/QUESTIONS.md @@ -0,0 +1,27 @@ +# Вопросы: Kafka + +Опираются на кейс: [CASE.md](CASE.md). Источники — [Bi.Zone.md](../../interview/Bi.Zone.md), [Alfa-Bank.md](../../interview/Alfa-Bank.md) (упомянута в требованиях вакансии). + +### Что такое Kafka? + +Распределённая платформа для потоковой передачи событий (event streaming) — по сути, отказоустойчивый распределённый лог сообщений. Продюсеры пишут сообщения в топики, консюмеры читают их независимо друг от друга и в своём темпе; в отличие от классической очереди сообщения не удаляются сразу после прочтения, а хранятся заданное время (retention), поэтому несколько разных консюмеров могут читать один и тот же поток данных с разной скоростью или начинать читать заново. + +### С какими версиями Kafka работал? + +В домашней лабе поднимал актуальную версию (3.7) в режиме KRaft — то есть без ZooKeeper, это более новая архитектура, где консенсус по метаданным кластера реализован внутри самой Kafka через `controller`-роль вместо отдельного ZooKeeper-кластера. Более старые продовые инсталляции (до Kafka 3.x/4.x) чаще всего используют классическую схему с ZooKeeper — если на конкретном месте работы Kafka завязана на ZooKeeper, стоит явно уточнить это у интервьюера, чтобы не путать архитектуры. + +### Что такое партиции и оффсеты? + +Топик физически делится на партиции — это единица параллелизма и хранения. Каждое сообщение внутри партиции получает последовательный номер — оффсет, и порядок сообщений гарантирован только внутри одной партиции (не между партициями топика). Консюмер отслеживает, до какого оффсета он уже прочитал каждую партицию — это позволяет ему продолжить с того же места после перезапуска. У себя в лабе создавал топик с 3 партициями и через `kafka-consumer-groups.sh --describe` явно видел `CURRENT-OFFSET`/`LOG-END-OFFSET`/`LAG` по каждой партиции. + +### Что такое consumer group и как балансируются партиции внутри неё? + +Consumer group — логическое объединение нескольких консюмеров, которые вместе обрабатывают топик так, что каждое сообщение читается только одним консюмером из группы (а не всеми) — это механизм горизонтального масштабирования обработки. Партиции топика распределяются между консюмерами группы; если консюмеров больше, чем партиций, часть консюмеров останется без партиций и будет простаивать. У себя в лабе с топиком на 3 партиции и двумя консюмерами в одной группе увидел, как каждый консюмер забрал себе часть партиций. + +### Что такое AT/ET CI/CD в контексте Kafka? (формулировка с собеседования Bi.Zone) + +В контексте связки Kafka с CI/CD-пайплайнами это, по всей видимости, отсылка к семантике доставки сообщений — **at-least-once** (сообщение гарантированно доставлено, но может задублироваться при retry) и **exactly-once** (доставка ровно один раз, более строгая и дорогая гарантия, у Kafka реализуется через идемпотентного продюсера и транзакции). В пайплайнах обработки данных выбор между этими семантиками определяет, нужна ли дедупликация на стороне консюмера. Честно: на самом собеседовании формулировка вопроса была нестандартной, и это моя интерпретация вероятного смысла — по этой конкретной формулировке лучше переспросить интервьюера, что именно имеется в виду, а не гадать молча. + +### Как замерить пропускную способность Kafka? + +Встроенная утилита `kafka-producer-perf-test.sh` — задаёшь число записей, размер записи и лимит throughput (`-1` — без ограничения, максимальная скорость) и получаешь `records/sec`/`MB/sec` на конкретном железе. Я гонял такой замер у себя на однонодовом брокере — конкретные цифры и то, как они соотносятся с публично известными продовыми показателями, зафиксированы в [../../legend/CAPACITY.md](../../legend/CAPACITY.md). diff --git a/stack/kubernetes/CASE.md b/stack/kubernetes/CASE.md new file mode 100644 index 0000000..2299603 --- /dev/null +++ b/stack/kubernetes/CASE.md @@ -0,0 +1,169 @@ +# Кейс: Kubernetes (домашний pet-кластер) + +Если K8s незнаком — сначала пройти учебный курс с нуля [LEARNING.md](LEARNING.md), этот файл — компактный конспект результата для повторения перед собеседованием, без пошаговых объяснений «зачем». + +Связь с легендой: [../../legend/LEGEND.md](../../legend/LEGEND.md) — раздел «Домашняя лаборатория / pet-проект». **Важно: K8s не входит в рабочий стек ни АО ТНИИС, ни ОКБ СУХОЙ** — это честный pet-проект поверх рабочего опыта с Docker/Ansible/мониторингом, а не часть легенды о работе в конкретной компании. На собеседовании так и позиционируется: «в проде на текущей работе K8s не используем, но поднимал себе дома кластер, чтобы разобраться» (этот же приём в разборе [ВТБ](../../interview/Bank%20VTB.md) сработал в плюс — см. [../../interview/real-interviews.md](../../interview/real-interviews.md)). + +## Что нужно реально сделать (домашний стенд) + +Локальный кластер через `kind` (Kubernetes IN Docker) — не требует отдельной виртуалки, поднимается поверх уже установленного Docker. Цель — своими руками пройти путь «манифест → под → сервис → доступ снаружи», задеплоить кусок стека мониторинга через Helm и прогнать диагностические команды, которые реально спрашивали на собеседованиях. + +### 1. Установка и создание кластера + +```bash +# kind и kubectl (Windows, через choco; либо скачать бинарники с GitHub releases) +choco install kind kubernetes-cli kubernetes-helm + +# кластер из 1 control-plane + 2 worker-нод — чтобы было что рисовать при вопросе про архитектуру +``` + +```yaml +# kind-config.yaml +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +nodes: + - role: control-plane + - role: worker + - role: worker +``` + +```bash +kind create cluster --name lab --config kind-config.yaml +kubectl cluster-info +kubectl get nodes -o wide +``` + +### 2. Деплой простого приложения: Deployment + Service + ConfigMap + +```yaml +# app.yaml +apiVersion: v1 +kind: Namespace +metadata: + name: demo + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: app-config + namespace: demo +data: + GREETING: "Привет из ConfigMap" + +--- +apiVersion: v1 +kind: Secret +metadata: + name: app-secret + namespace: demo +type: Opaque +stringData: + API_KEY: "lab-secret-value" + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: demo-app + namespace: demo +spec: + replicas: 3 + selector: + matchLabels: + app: demo-app + template: + metadata: + labels: + app: demo-app + spec: + containers: + - name: demo-app + image: nginxdemos/hello:latest + ports: + - containerPort: 80 + envFrom: + - configMapRef: + name: app-config + - secretRef: + name: app-secret + +--- +apiVersion: v1 +kind: Service +metadata: + name: demo-app-svc + namespace: demo +spec: + selector: + app: demo-app + ports: + - port: 80 + targetPort: 80 + type: ClusterIP +``` + +```bash +kubectl apply -f app.yaml +kubectl -n demo get pods -o wide +kubectl -n demo rollout status deployment/demo-app +``` + +### 3. Доступ снаружи: port-forward и Ingress + +```bash +# быстрый способ проверить сервис +kubectl -n demo port-forward svc/demo-app-svc 8080:80 +curl http://localhost:8080 +``` + +Для полноценного Ingress — установить NGINX Ingress Controller (kind предоставляет готовый манифест под себя) и завести `Ingress`-ресурс с host-based роутингом — так на собеседовании про Реалист спрашивали про «два ЦОД под K8s с балансировщиком»: балансировщик снаружи (в лабе — сам kind/NGINX Ingress) → Service (ClusterIP, виртуальный балансировщик по подам через kube-proxy) → поды на worker-нодах. + +### 4. Helm: разворачиваем kube-prometheus-stack + +Практическая связка с уже готовым кейсом [../monitoring/CASE.md](../monitoring/CASE.md) — тот же принцип «Prometheus + Grafana», но теперь через Helm-чарт в кластере, а не через голый docker-compose. + +```bash +helm repo add prometheus-community https://prometheus-community.github.io/helm-charts +helm repo update + +helm install monitoring prometheus-community/kube-prometheus-stack \ + --namespace monitoring --create-namespace + +kubectl -n monitoring get pods +kubectl -n monitoring port-forward svc/monitoring-grafana 3000:80 +``` + +Это даёт прямой ответ на вопрос Bi.Zone «как задеплоишь мониторинг на 300 000 серверов»: не поднимать вручную, а Helm-чартом с overrides под масштаб (`values.yaml`: количество реплик, remote_write в Mimir/Thanos, ресурсы). См. цифры и архитектуру масштаба в [../../legend/CAPACITY.md](../../legend/CAPACITY.md). + +### 5. Диагностика руками (то, что реально спрашивали «показать в терминале») + +```bash +kubectl -n demo get pods +kubectl -n demo describe pod +kubectl -n demo logs +kubectl -n demo logs --previous # логи упавшего предыдущего инстанса контейнера +kubectl -n demo exec -it -- sh +kubectl -n demo get events --sort-by=.lastTimestamp +kubectl get daemonset -A # например, kube-proxy — daemonset на каждой ноде +``` + +Специально сломать под (например, указать несуществующий образ в `image:`) и посмотреть на `ImagePullBackOff`/`CrashLoopBackOff` через `describe` — реальная практика диагностики, а не просто описание. + +### 6. Разбор компонентов кластера (для вопроса «из чего состоит K8s») + +- **control-plane**: `kube-apiserver` (единая точка входа для всех запросов), `etcd` (хранилище состояния кластера, key-value), `kube-scheduler` (назначает поды на ноды), `kube-controller-manager` (следит, что желаемое состояние совпадает с реальным). +- **worker-нода**: `kubelet` (агент, который управляет подами на ноде), `kube-proxy` (сетевые правила для Service), container runtime (`containerd`). +- **etcd отдельно от control-plane или нет** — в проде для отказоустойчивости etcd часто выносят на отдельные ноды (нечётное количество, 3/5, из-за кворума), в маленьких/pet-инсталляциях (как kind) он живёт на той же control-plane ноде. В `kind` это видно напрямую: `kubectl get pods -n kube-system` покажет `etcd-lab-control-plane`. + +## Что это даёт в разговоре с интервьюером + +- Понимание базовых объектов: Pod, Deployment, Service, Namespace, ConfigMap, Secret, Ingress — и что каждый из них реально содержит (проверено `kubectl get -o yaml`). +- Понимание разницы ConfigMap (несекретные конфиги) и Secret (base64-кодированные чувствительные данные — не шифрование, а кодирование; для реальной защиты нужны внешние решения типа Sealed Secrets/Vault, см. [../vault/CASE.md](../vault/CASE.md)). +- Практика с Helm — установка чарта, `values.yaml`, `helm upgrade`. +- Живой опыт диагностики через `kubectl describe`/`logs`/`events`. +- Честная граница: это pet-кластер из 3 нод на своей машине, а не эксплуатация продового HA-кластера — расширение до multi-datacenter/etcd-кворума описывается на уровне понимания архитектуры, не личного опыта эксплуатации на этом масштабе. + +## Как это ложится в легенду + +K8s **не** приписывается к опыту в АО ТНИИС или ОКБ СУХОЙ — там его не было и в резюме он не должен появляться в блоках компаний. В «Ключевые навыки» резюме и в легенду он входит как отдельный, честно обозначенный pet-опыт: практическое знакомство с базовыми объектами и Helm поверх домашнего кластера, дополняющее рабочий опыт с Docker и Ansible. diff --git a/stack/kubernetes/LEARNING.md b/stack/kubernetes/LEARNING.md new file mode 100644 index 0000000..0c294a2 --- /dev/null +++ b/stack/kubernetes/LEARNING.md @@ -0,0 +1,833 @@ +# Учебный курс: Kubernetes с нуля + +Это учебный документ, а не легенда — здесь нет утверждений «делал на работе». Цель: пройти путь от нуля до уверенного pet-уровня, объясняя себе на каждом шаге «что это и зачем», а не просто копируя команды. Итоговая позиция на собеседовании не меняется: K8s — честная домашняя практика поверх рабочего опыта с Docker/Ansible/мониторингом (см. [../../legend/LEGEND.md](../../legend/LEGEND.md) → «Домашняя лаборатория / pet-проект»). + +**Как этим пользоваться:** проходить модули по порядку, не пропускать практику — каждый следующий модуль опирается на состояние кластера, оставленное предыдущим. Когда весь курс пройден руками один раз, для повторения перед конкретным собеседованием используй более компактный [CASE.md](CASE.md) — он про то же самое, но без объяснений, как шпаргалка. Вопросы с готовыми ответами — в [QUESTIONS.md](QUESTIONS.md). + +**Формат каждого модуля:** зачем это → теория-минимум → практика руками → самопроверка → как это в реальных проектах. + +**Окружение курса** (одно на все модули, то же, что в CASE.md): Windows + Docker Desktop, кластер `kind` с именем `lab`, 1 control-plane + 2 worker. Не пересоздавай кластер между модулями без необходимости — большинство модулей продолжают работать в одном и том же кластере и в одних и тех же namespace (`demo`, `monitoring`), это ближе к тому, как выглядит реальная работа с уже существующим кластером. + +--- + +## Модуль 0. Установка инструментов + +### Зачем это + +Три разных инструмента решают три разные задачи, и важно не путать их роли: **Docker** — где физически крутятся контейнеры, **kind** — как за секунды поднять тестовый K8s-кластер поверх Docker без отдельных виртуалок, **kubectl** — как разговаривать с любым K8s-кластером (хоть pet, хоть прод), **Helm** — как ставить готовые наборы манифестов одной командой вместо ручного `kubectl apply` на десяток файлов. + +### Теория-минимум + +`kind` = Kubernetes IN Docker: каждая «нода» кластера — это на самом деле Docker-контейнер, внутри которого запущен полноценный K8s. Это не то, как выглядит прод (там ноды — реальные VM/железо), но API и поведение объектов идентичны — то, что выучено на kind, переносится на любой кластер. + +### Практика руками + +```bash +# Docker Desktop должен быть уже установлен и запущен — это фундамент, без него ничего не работает + +# Windows, через choco (одной командой ставит все три инструмента) +choco install kind kubernetes-cli kubernetes-helm + +# проверить, что каждый инструмент реально встал +docker --version +kind --version +kubectl version --client +helm version +``` + +### Самопроверка + +- Все четыре команды `--version` отвечают без ошибок. +- Понимаешь своими словами разницу между Docker, kind, kubectl и Helm — если нет, перечитай «Зачем это» выше. + +### Как это в реальных проектах + +В проде kind не используют (это чисто локальный/CI инструмент для тестов) — реальные кластеры разворачивают managed-сервисами облаков (EKS/GKE/managed K8s) или `kubeadm`/аналогами on-premise. kubectl и Helm — те же самые, что и в pet-кластере, это и есть смысл разработки на kind: инструменты один в один. + +--- + +## Модуль 1. Зачем вообще Kubernetes + +### Зачем это + +Прежде чем учить объекты K8s, важно понимать проблему, которую он решает — иначе все дальнейшие абстракции выглядят как искусственная сложность ради сложности. + +### Теория-минимум + +Без оркестратора эксплуатация выглядит так: `docker run` руками на конкретной VM, и если контейнер упал — никто, кроме человека или самопального скрипта, его не перезапустит; если VM легла — сервис просто недоступен, пока кто-то не отреагирует. K8s переворачивает модель: администратор описывает **desired state** («хочу 3 реплики этого образа, слушающие порт 8080») декларативно в YAML, а control-plane кластера постоянно сверяет фактическое состояние с желаемым и сам исправляет расхождение — это и называется **self-healing**. Ключевая идея: не «выполни команду один раз», а «поддерживай вот такое состояние постоянно». + +### Практика руками + +```bash +kind create cluster --name lab +kubectl run demo --image=nginxdemos/hello --restart=Always +kubectl get pods +# демо self-healing: убиваем под руками +kubectl delete pod demo +kubectl get pods +# NAME ... — под пересоздан автоматически (другой RESTARTS/AGE), потому что desired state требует, чтобы под существовал +``` + +### Самопроверка + +- Своими словами: что такое desired state и чем декларативный подход отличается от «выполнить команду»? +- Почему после `kubectl delete pod demo` под появился снова? + +*(Забегая вперёд: на самом деле голый `kubectl run` пересоздаётся не всегда — это будет явно разобрано в модуле 4 про Deployment. Здесь цель — просто увидеть идею self-healing на практике.)* + +### Как это в реальных проектах + +Эта идея — причина, почему K8s вообще существует: чем больше сервисов и нод, тем дороже человеку вручную следить за их состоянием. В резюме и легенде это прямая параллель с системами вроде systemd (`Restart=always` — тот же self-healing, но на уровне одной VM, не кластера) — see [../linux-bash/CASE.md](../linux-bash/CASE.md). + +--- + +## Модуль 2. Архитектура кластера + +### Зачем это + +Вопрос «из чего состоит Kubernetes» — один из самых частых на собеседованиях (см. [QUESTIONS.md](QUESTIONS.md)). Нельзя понимать объекты (Pod, Service и т.д.), не понимая, кто внутри кластера отвечает за их существование. + +### Теория-минимум + +Кластер делится на **control-plane** (мозг) и **worker-ноды** (мышцы, где реально крутятся приложения): + +- `kube-apiserver` — единственная точка входа для всех запросов (в том числе от `kubectl`); всё остальное общается только через него, напрямую в обход API никто ничего не меняет. +- `etcd` — key-value хранилище, единственный источник правды о состоянии кластера (все объекты, их спеки и статусы). +- `kube-scheduler` — решает, на какую worker-ноду поставить новый под (по ресурсам, тегам, ограничениям). +- `kube-controller-manager` — набор контроллеров, каждый из которых следит за своим типом объектов и подгоняет реальность к желаемому состоянию (это и есть механизм self-healing из модуля 1). +- `kubelet` (на каждой worker-ноде) — агент, который реально запускает/останавливает контейнеры на своей ноде через container runtime. +- `kube-proxy` (на каждой worker-ноде) — настраивает сетевые правила, чтобы трафик до Service доходил до нужных подов. +- `containerd` — сам container runtime, который непосредственно тянет образы и стартует контейнеры (аналог Docker engine, но легче). + +### Практика руками + +```bash +# пересоздаём кластер с явной топологией — 1 control-plane + 2 worker, чтобы было что показать на схеме +cat > kind-config.yaml << 'EOF' +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +nodes: + - role: control-plane + - role: worker + - role: worker +EOF + +kind delete cluster --name lab +kind create cluster --name lab --config kind-config.yaml +kubectl cluster-info +kubectl get nodes -o wide + +# все компоненты control-plane живут как поды в системном namespace kube-system +kubectl get pods -n kube-system -o wide +``` + +Найди в выводе `kube-apiserver-lab-control-plane`, `etcd-lab-control-plane`, `kube-scheduler-lab-control-plane`, `kube-controller-manager-lab-control-plane` — все живьём, не абстракция. `kube-proxy` увидишь как DaemonSet (по одному поду на каждую ноду — забегая вперёд к модулю 9 про DaemonSet). + +### Самопроверка + +- Назвать вслух 4 компонента control-plane и 2 компонента worker-ноды, объяснить роль каждого без подсматривания. +- Почему `etcd` в pet-кластере находится на той же ноде, что и control-plane, а в проде его часто выносят отдельно? (Кворум при отказоустойчивости — нечётное число нод: 3 или 5, при потере части живой кворум продолжает работать, если жива больше половины.) + +### Как это в реальных проектах + +В managed-кластерах (EKS/GKE) весь control-plane скрыт от пользователя облаком — вы видите только worker-ноды. В on-premise-кластерах (ближе к тому, что было бы в закрытом контуре без облаков) control-plane разворачивается и обслуживается вручную или через `kubeadm`, и вопрос про размещение `etcd` становится реальным архитектурным решением, а не абстракцией. + +--- + +## Модуль 3. Pod и kubectl-база + +### Зачем это + +Pod — минимальная единица деплоя в K8s. Нельзя запустить «просто контейнер» напрямую через API K8s — всегда через Pod. Здесь же нарабатывается базовый набор команд `kubectl`, которым пользуешься постоянно. + +### Теория-минимум + +Pod — это один или несколько контейнеров, которые всегда живут и умирают вместе, на одной ноде, с общей сетью (localhost между контейнерами внутри пода) и опционально общими volume. В большинстве случаев в поде один контейнер — многоконтейнерные поды (sidecar-паттерн) нужны реже, когда вспомогательный процесс должен жить бок о бок с основным (например, агент логирования). + +Анатомия любого манифеста K8s — четыре ключа верхнего уровня: +- `apiVersion` — версия API, к которой относится объект (у Pod это `v1`, у более сложных объектов часто `apps/v1` и т.д.). +- `kind` — тип объекта (`Pod`, `Deployment`, `Service`...). +- `metadata` — имя, namespace, labels, аннотации — «паспорт» объекта. +- `spec` — собственно желаемое состояние: что именно нужно запустить. + +### Практика руками + +```yaml +# pod.yaml +apiVersion: v1 +kind: Namespace +metadata: + name: demo +--- +apiVersion: v1 +kind: Pod +metadata: + name: demo-pod + namespace: demo + labels: + app: demo +spec: + containers: + - name: hello + image: nginxdemos/hello:latest + ports: + - containerPort: 80 +``` + +```bash +kubectl apply -f pod.yaml + +kubectl -n demo get pods # список подов +kubectl -n demo get pods -o wide # + на какой ноде, IP пода +kubectl -n demo describe pod demo-pod # полная инфа: события, статус, причина проблем +kubectl -n demo logs demo-pod # логи контейнера +kubectl -n demo exec -it demo-pod -- sh # зайти внутрь контейнера интерактивно +kubectl -n demo get pod demo-pod -o yaml # реальный полный манифест объекта (K8s дополнил своими полями) + +kubectl -n demo delete pod demo-pod # удалить +``` + +### Самопроверка + +- Из четырёх ключей манифеста назвать, какой отвечает за «что мы хотим получить», а какой — за «как объект называется и где живёт». +- Чем `logs` отличается от `describe`? (logs — вывод приложения; describe — метаданные/события/статус от самого K8s, годится для диагностики, почему под не стартует). +- Что покажет `kubectl -n demo get pod demo-pod -o yaml` такого, чего нет в исходном `pod.yaml`? (K8s подставляет статус, IP, дополнительные системные поля — разница между тем, что ты запросил, и тем, что реально существует). + +### Как это в реальных проектах + +Голые Pod'ы в проде почти никогда не создают напрямую (кроме короткоживущих задач) — если под упадёт, его никто не пересоздаст, потому что за него никто не отвечает (в отличие от модуля 1, где `kubectl run` создавал под через неявный контроллер). Правильный способ — через Deployment, это модуль 4. + +--- + +## Модуль 4. Deployment и ReplicaSet + +### Зачем это + +Это самый частый способ запускать stateless-приложения в K8s — и то, что даёт настоящий self-healing и rolling-обновления без даунтайма. + +### Теория-минимум + +**Deployment** управляет **ReplicaSet**, а ReplicaSet управляет подами — трёхуровневая иерархия. Deployment описывает: сколько реплик (`replicas`), как найти «свои» поды (`selector`), и шаблон, из которого их создавать (`template`, внутри — точно такой же `spec`, как у голого Pod). Если поменять образ в Deployment, он не убивает все поды разом — создаёт новый ReplicaSet и постепенно переключает трафик со старого на новый (rolling update), это и есть обновление без даунтайма. + +### Практика руками + +```yaml +# deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: demo-app + namespace: demo +spec: + replicas: 3 + selector: + matchLabels: + app: demo-app + template: + metadata: + labels: + app: demo-app + spec: + containers: + - name: demo-app + image: nginxdemos/hello:latest + ports: + - containerPort: 80 +``` + +```bash +kubectl apply -f deployment.yaml +kubectl -n demo get deployment demo-app +kubectl -n demo get replicaset # Deployment создал ReplicaSet +kubectl -n demo get pods -o wide # ReplicaSet создал 3 пода + +# масштабирование +kubectl -n demo scale deployment demo-app --replicas=5 +kubectl -n demo get pods + +# self-healing по-настоящему: убиваем один под +kubectl -n demo delete pod <имя-любого-пода> +kubectl -n demo get pods # ReplicaSet тут же пересоздал под — реплик снова 5 + +# rolling update +kubectl -n demo set image deployment/demo-app demo-app=nginxdemos/hello:plain-text +kubectl -n demo rollout status deployment/demo-app +kubectl -n demo rollout history deployment/demo-app + +# откат, если что-то пошло не так +kubectl -n demo rollout undo deployment/demo-app +``` + +### Самопроверка + +- Нарисовать иерархию Deployment → ReplicaSet → Pod своими словами. +- Что произойдёт с количеством ReplicaSet после `set image`? (появится новый ReplicaSet под новую версию, старый останется с 0 репликами — это и даёт `rollout undo`). +- Чем rolling update отличается от простого «убить все старые поды и поднять новые»? (нет даунтайма — старые поды продолжают обслуживать трафик, пока новые не станут готовы). + +### Как это в реальных проектах + +Deployment — рабочая лошадка для 90% приложений в проде (stateless-сервисы). Rolling update с постепенным переключением — прямая параллель с тем, зачем в принципе нужен `readiness probe` (модуль 8): K8s не переключает трафик на новый под, пока тот не сообщит о готовности. + +--- + +## Модуль 5. Service и сеть + +### Зачем это + +У подов IP-адреса нестабильны (под пересоздали — IP другой). Нужен стабильный способ достучаться до набора подов, не завязываясь на их конкретные IP — это и есть Service. + +### Теория-минимум + +**Service** — это стабильный виртуальный IP + DNS-имя, стоящее перед набором подов, отобранных по `selector` (по тем же labels, что использует Deployment). Три основных типа: +- `ClusterIP` (по умолчанию) — доступен только внутри кластера, для внутреннего взаимодействия сервисов. +- `NodePort` — открывает порт на каждой worker-ноде наружу кластера, для простого внешнего доступа без облачного балансировщика. +- `LoadBalancer` — просит облако выдать внешний балансировщик (в pet-кластере kind не работает без эмуляции, поэтому для локального внешнего доступа используем `port-forward` или Ingress из модуля 6). + +Внутри кластера любой под может обратиться к Service по DNS-имени вида `..svc.cluster.local` (или просто ``, если в том же namespace) — `kube-proxy` на каждой ноде транслирует это имя в конкретный IP пода по правилам iptables/IPVS, с балансировкой между всеми подходящими подами. + +### Практика руками + +```yaml +# service.yaml +apiVersion: v1 +kind: Service +metadata: + name: demo-app-svc + namespace: demo +spec: + selector: + app: demo-app + ports: + - port: 80 + targetPort: 80 + type: ClusterIP +``` + +```bash +kubectl apply -f service.yaml +kubectl -n demo get svc demo-app-svc +kubectl -n demo get endpoints demo-app-svc # реальные IP подов, на которые сейчас указывает Service + +# проверка снаружи через port-forward +kubectl -n demo port-forward svc/demo-app-svc 8080:80 +curl http://localhost:8080 # в отдельном терминале + +# проверка балансировки изнутри кластера — заходим в один под и стучимся в Service несколько раз +kubectl -n demo run curl-test --image=curlimages/curl -it --rm -- sh +# внутри: curl demo-app-svc.demo.svc.cluster.local, повторить несколько раз +``` + +### Самопроверка + +- Почему нельзя просто обращаться напрямую к IP пода в проде? (IP меняется при каждом пересоздании пода — Service даёт стабильную точку входа). +- Чем `endpoints` Service отличаются от самого Service? (endpoints — динамический список реальных IP подходящих подов, обновляется автоматически при масштабировании/пересоздании). +- В чём разница ClusterIP / NodePort / LoadBalancer одним предложением каждая. + +### Как это в реальных проектах + +Это ровно то, что подразумевается под вопросом «как работает трафик в K8s» (см. [QUESTIONS.md](QUESTIONS.md)): Service + kube-proxy — обязательный слой между внешним балансировщиком/Ingress и конкретными подами, в любой архитектуре, от pet-кластера до мультидатацентрового прода. + +--- + +## Модуль 6. Ingress + +### Зачем это + +NodePort/port-forward годятся для лабы, но не масштабируются на реальный внешний трафик с доменными именами и путями (`/api`, `/app` на одном IP). Ingress — стандартный способ завести весь HTTP(S)-трафик в кластер через один вход с роутингом по host/path. + +### Теория-минимум + +**Ingress** — это правило роутинга (host/path → Service), но само по себе оно ничего не делает — нужен **Ingress Controller** (отдельное приложение внутри кластера, например NGINX Ingress Controller), которое читает Ingress-ресурсы и реально проксирует трафик. Полная цепочка запроса: клиент → Ingress Controller (по внешнему IP/порту) → на основе host/path выбирает нужный Service → Service выбирает под по `selector` → под отвечает. + +### Практика руками + +```bash +# kind поставляется с готовым манифестом NGINX Ingress Controller под себя +kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml +kubectl -n ingress-nginx wait --for=condition=ready pod --selector=app.kubernetes.io/component=controller --timeout=120s +``` + +```yaml +# ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: demo-ingress + namespace: demo + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / +spec: + ingressClassName: nginx + rules: + - host: demo.local + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: demo-app-svc + port: + number: 80 +``` + +```bash +kubectl apply -f ingress.yaml +kubectl -n demo get ingress + +# добавить demo.local в hosts-файл (Windows: C:\Windows\System32\drivers\etc\hosts) → 127.0.0.1 +curl http://demo.local +``` + +### Самопроверка + +- Своими словами: чем Ingress отличается от Ingress Controller? (Ingress — декларативное правило-манифест; Controller — программа, которая это правило исполняет). +- Нарисовать полную цепочку «клиент → под» через Ingress, включая все промежуточные звенья. + +### Как это в реальных проектах + +Это прямой ответ на реальный вопрос с собеседования «нарисуй два ЦОД под K8s с балансировщиком» (Реалист банк, см. [QUESTIONS.md](QUESTIONS.md)): внешний L4/L7-балансировщик перед ЦОДами → внутри каждого ЦОД Ingress Controller/Service → поды. Личная параллель с рабочим опытом — Nginx как балансировщик перед сервисами (см. [../nginx/CASE.md](../nginx/CASE.md), [../../legend/STORY.md](../../legend/STORY.md) → nginx.conf для x3 Mimir): Ingress Controller здесь и есть, по сути, Nginx, только управляемый декларативно через K8s-объекты, а не ручным редактированием `nginx.conf`. + +--- + +## Модуль 7. ConfigMap и Secret + +### Зачем это + +Образ контейнера должен быть одинаковым во всех окружениях (dev/stage/prod) — конфигурация, которая между окружениями отличается, не должна быть зашита в образ. ConfigMap и Secret — стандартный способ передать конфиг снаружи образа. + +### Теория-минимум + +**ConfigMap** — для несекретных данных (URL, флаги, конфиг-файлы), **Secret** — для чувствительных значений (пароли, токены, ключи). Оба можно подключить к поду двумя способами: как переменные окружения (`envFrom`/`env`) или как смонтированный файл (`volumeMounts`). Важный технический нюанс: Secret по умолчанию хранит значения в **base64 — это кодирование, а не шифрование**. Любой, у кого есть доступ к API/etcd, может декодировать значение одной командой — реальная защита требует либо шифрования etcd at rest, либо внешних систем вроде Vault (см. [../vault/CASE.md](../vault/CASE.md)) или Sealed Secrets. + +### Практика руками + +```yaml +# config-secret.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: app-config + namespace: demo +data: + GREETING: "Привет из ConfigMap" +--- +apiVersion: v1 +kind: Secret +metadata: + name: app-secret + namespace: demo +type: Opaque +stringData: + API_KEY: "lab-secret-value" +``` + +Подключаем к Deployment из модуля 4 через `envFrom`: + +```yaml + # добавить в spec.template.spec.containers[0] demo-app из deployment.yaml + envFrom: + - configMapRef: + name: app-config + - secretRef: + name: app-secret +``` + +```bash +kubectl apply -f config-secret.yaml +kubectl apply -f deployment.yaml # обновлённый с envFrom + +# проверка, что переменные реально попали в под +kubectl -n demo exec -it <имя-пода> -- env | grep -E "GREETING|API_KEY" + +# ключевая демонстрация: base64 — не шифрование +kubectl -n demo get secret app-secret -o yaml +# скопировать значение data.API_KEY и декодировать: +echo "" | base64 -d +# получится исходный "lab-secret-value" — открытым текстом +``` + +### Самопроверка + +- Почему нельзя просто зашить конфиг в Dockerfile/образ? (тогда под каждое окружение нужен свой образ — теряется смысл «один образ везде»). +- Объяснить вслух, почему `kubectl get secret -o yaml` + `base64 -d` — не взлом, а штатная возможность любого, у кого есть доступ к API. + +### Как это в реальных проектах + +Прямая параллель с рабочим опытом: `ansible-vault` шифрует секреты в Ansible-ролях (см. [../ansible/CASE.md](../ansible/CASE.md)) примерно с той же целью, ради которой в проде K8s-секреты часто заводят не как голый `Secret`, а через внешний Vault-интеграцию (модуль pet — [../vault/CASE.md](../vault/CASE.md)) или `Sealed Secrets`, чтобы решить проблему base64 ≠ шифрование. + +--- + +## Модуль 8. Probes и ресурсы + +### Зачем это + +K8s не знает, «готово» ли приложение внутри контейнера, просто по факту, что процесс запущен — процесс может быть жив, но зависшим, или ещё прогревающимся. Probes дают K8s способ реально спросить приложение о его состоянии. + +### Теория-минимум + +Три вида проверок: +- **livenessProbe** — «жив ли процесс вообще?» Если проверка не проходит несколько раз подряд — K8s убивает и пересоздаёт контейнер. +- **readinessProbe** — «готов ли принимать трафик прямо сейчас?» Если не проходит — под не убивают, но Service временно перестаёт слать в него трафик (именно это делает rolling update из модуля 4 безопасным). +- **startupProbe** — для медленно стартующих приложений: пока не пройдёт, liveness/readiness не проверяются (чтобы долгий старт не приняли за зависание). + +**Ресурсы** (`requests`/`limits`): `requests` — сколько CPU/RAM гарантированно резервируется под контейнер (scheduler использует это число, чтобы решить, на какую ноду его поставить), `limits` — потолок, выше которого контейнер не может подняться (превышение по памяти → **OOMKilled**, превышение по CPU → троттлинг, не убийство). Комбинация requests/limits определяет **QoS-класс** пода (`Guaranteed`/`Burstable`/`BestEffort`) — от него зависит, что убьют первым при нехватке ресурсов на ноде. + +### Практика руками + +```yaml +# probes.yaml — добавить в spec.template.spec.containers[0] demo-app + readinessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 3 + periodSeconds: 5 + livenessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 10 + periodSeconds: 10 + resources: + requests: + cpu: "100m" + memory: "64Mi" + limits: + cpu: "250m" + memory: "128Mi" +``` + +```bash +kubectl apply -f deployment.yaml +kubectl -n demo describe pod <имя-пода> # секция Liveness/Readiness видна прямо в describe + +# демо OOMKilled: временно поставить memory limit заведомо ниже, чем нужно приложению +# (например memory: "8Mi"), применить, посмотреть: +kubectl -n demo get pods +kubectl -n demo describe pod <имя-пода> # Last State: Terminated, Reason: OOMKilled +``` + +### Самопроверка + +- В чём разница между liveness и readiness одним предложением каждая — и что K8s делает в каждом случае при провале проверки. +- Что произойдёт, если задать `limits.memory` меньше, чем реально требуется приложению? (контейнер убьют по OOM, и он уйдёт в CrashLoopBackOff при повторных попытках — мост к модулю 9). + +### Как это в реальных проектах + +Правильно настроенные probes — то, что отличает «работающий на бумаге» деплой от реально безопасного rolling update: без readinessProbe K8s может начать слать трафик на под, который ещё не прогрелся, и получить ошибки у пользователей в момент релиза. + +--- + +## Модуль 9. Диагностика поломок + +### Зачем это + +Это самый практический навык для собеседования: «покажи, как будешь диагностировать» — частый формат реальных вопросов (см. [QUESTIONS.md](QUESTIONS.md)). Здесь специально ломаем всё, что можно сломать, чтобы увидеть диагностику вживую, а не в теории. + +### Теория-минимум + +Три частых состояния поломанного пода: +- **ImagePullBackOff** — K8s не может скачать указанный образ (опечатка в имени, несуществующий тег, нет доступа к registry). +- **CrashLoopBackOff** — контейнер стартует и тут же падает, K8s пытается перезапустить снова и снова с растущей паузой между попытками. +- **Pending** — под не может быть запланирован ни на одну ноду (не хватает ресурсов под `requests`, или не подходит ни одна нода по ограничениям). + +### Практика руками + +```bash +# 1. ImagePullBackOff — несуществующий образ +kubectl -n demo run broken1 --image=nginxdemos/this-image-does-not-exist:latest +kubectl -n demo get pods +kubectl -n demo describe pod broken1 # Events внизу прямо назовут причину + +# 2. CrashLoopBackOff — команда, которая тут же завершается с ошибкой +kubectl -n demo run broken2 --image=busybox --restart=Always -- sh -c "exit 1" +kubectl -n demo get pods -w # смотрим, как RESTARTS растёт +kubectl -n demo logs broken2 --previous # логи упавшего перед текущим рестартом контейнера +kubectl -n demo describe pod broken2 + +# 3. Pending — запросить заведомо нереальные ресурсы +kubectl -n demo run broken3 --image=nginx --requests='cpu=100,memory=100Gi' --restart=Always +kubectl -n demo get pods # STATUS: Pending +kubectl -n demo describe pod broken3 # Events: "0/3 nodes are available: insufficient cpu/memory" + +# общая хронология по namespace — полезно, когда непонятно, с чего начать +kubectl -n demo get events --sort-by=.lastTimestamp + +# уборка после экспериментов +kubectl -n demo delete pod broken1 broken2 broken3 +``` + +### Самопроверка + +Чек-лист «под не работает — что смотреть по порядку» (проговорить вслух на реальном примере из практики выше): +1. `kubectl get pods` — какой статус? (ImagePullBackOff / CrashLoopBackOff / Pending / Running-но-не-Ready). +2. `kubectl describe pod` — секция Events внизу почти всегда прямо называет причину. +3. Если контейнер стартовал и упал — `kubectl logs --previous`, посмотреть, что вывело само приложение перед падением. +4. Если Pending — смотреть Events на предмет нехватки ресурсов или несовпадения ограничений с нодами. +5. Если ничего не понятно — `kubectl get events --sort-by=.lastTimestamp` по всему namespace, искать связанные события. + +### Как это в реальных проектах + +Это прямая параллель с рабочим инцидент-флоу из [../../legend/STORY.md](../../legend/STORY.md) → «Как решается инцидент»: там формула «дашборд → логи (Kibana) → диагностика (systemctl/journalctl) → рестарт/эскалация», здесь та же логика, но инструмент — `kubectl describe`/`logs`/`events` вместо `systemctl`/`journalctl`. Смысл вопроса на собеседовании «как будешь диагностировать под» — не проверить знание конкретной команды, а проверить, есть ли системный порядок действий. + +--- + +## Модуль 10. Хранилище: PV, PVC и StatefulSet + +### Зачем это + +Поды по умолчанию **эфемерны** — при пересоздании пода локальные данные внутри контейнера теряются. Для приложений с состоянием (базы данных, очереди) это неприемлемо, и K8s даёт отдельный слой абстракций для постоянного хранилища. + +### Теория-минимум + +- `emptyDir` — временный volume, живущий столько же, сколько под (переживает рестарт контейнера внутри пода, но не пересоздание самого пода) — полезен для промежуточных данных между контейнерами одного пода. +- `PersistentVolume` (PV) — реальный кусок хранилища в кластере (диск/NFS/облачный volume), `PersistentVolumeClaim` (PVC) — запрос пода «дай мне столько-то места», который K8s сопоставляет с подходящим PV. `StorageClass` — шаблон, по которому PV создаются автоматически по запросу PVC (динамическое провижининг вместо ручного создания PV заранее). +- **StatefulSet** vs **Deployment**: Deployment создаёт взаимозаменяемые поды с случайными именами и общим PVC (или без него) — годится для stateless. StatefulSet даёт каждому поду стабильное имя (`app-0`, `app-1`, ...) и **свой собственный** PVC, который переживает пересоздание конкретного пода — обязательно для баз данных и любых систем, где реплики не взаимозаменяемы (например, знают свою роль master/replica). + +### Практика руками + +```yaml +# statefulset-demo.yaml — учебный пример на минимальном приложении, не на реальной БД +apiVersion: v1 +kind: Service +metadata: + name: demo-headless + namespace: demo +spec: + clusterIP: None # headless-сервис — нужен StatefulSet для стабильных DNS-имён подов + selector: + app: demo-stateful + ports: + - port: 80 +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: demo-stateful + namespace: demo +spec: + serviceName: demo-headless + replicas: 2 + selector: + matchLabels: + app: demo-stateful + template: + metadata: + labels: + app: demo-stateful + spec: + containers: + - name: demo + image: nginxdemos/hello:latest + volumeMounts: + - name: data + mountPath: /usr/share/nginx/html/data + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 100Mi +``` + +```bash +kubectl apply -f statefulset-demo.yaml +kubectl -n demo get pods -l app=demo-stateful # demo-stateful-0, demo-stateful-1 — стабильные имена, не случайные +kubectl -n demo get pvc # у каждого пода свой PVC +kubectl -n demo get pv + +# показать, что PVC переживает пересоздание своего пода +kubectl -n demo delete pod demo-stateful-0 +kubectl -n demo get pods -l app=demo-stateful # пересоздался с тем же именем demo-stateful-0 и тем же PVC +``` + +### Самопроверка + +- Своими словами: почему для базы данных в K8s используют StatefulSet, а не Deployment? +- Что произойдёт с данными в `emptyDir`, если под пересоздан целиком (не просто перезапущен контейнер внутри него)? (данные потеряются — emptyDir живёт не дольше самого пода). + +### Как это в реальных проектах + +Это прямой ответ на реальный вопрос с собеседования «как будешь разворачивать PostgreSQL на две ноды K8s» (см. [QUESTIONS.md](QUESTIONS.md)): StatefulSet + PVC на каждую реплику + обычно готовый оператор (CloudNativePG, Zalando Postgres Operator), который поверх StatefulSet настраивает master-replica топологию и Service, разделяющий трафик на запись/чтение. Принцип репликации сам по себе прогнан отдельно в Docker Compose — см. [../databases/CASE.md](../databases/CASE.md); K8s здесь — просто уровень оркестрации поверх той же логики. + +--- + +## Модуль 11. Helm, часть 1 — пользователь чартов + +### Зачем это + +Реальные приложения (особенно готовые open-source системы вроде Prometheus/Grafana) состоят из десятков манифестов. Helm — золотой стандарт для их установки и обновления одной командой вместо ручного `kubectl apply -f` на каждый файл. + +### Теория-минимум + +- **Chart** — упакованный набор шаблонизированных манифестов K8s (аналог пакета в apt/npm). +- **Release** — конкретный установленный в кластер экземпляр чарта (можно поставить один и тот же чарт несколько раз под разными именами релиза — например, две независимые инсталляции Prometheus). +- **Repository** — источник, откуда качаются чарты (аналог apt-репозитория или npm registry). +- `values.yaml` — файл с параметрами, которые чарт подставляет в свои шаблоны при установке — то, чем конкретная инсталляция отличается от дефолтной. + +Ключевые команды: `helm repo add/update` (подключить и обновить репозиторий), `helm search repo` (найти чарт), `helm install` (поставить новый релиз), `helm upgrade` (обновить существующий релиз новыми values/версией), `helm rollback` (откатить релиз к прошлой ревизии — аналог `kubectl rollout undo`, но на уровне всего чарта), `helm uninstall` (удалить релиз). + +### Практика руками + +```bash +helm repo add prometheus-community https://prometheus-community.github.io/helm-charts +helm repo update +helm search repo prometheus-community/kube-prometheus-stack + +# посмотреть дефолтные values перед установкой — не ставить вслепую +helm show values prometheus-community/kube-prometheus-stack > default-values.yaml + +# ставим с переопределением — уменьшаем ресурсы под pet-кластер +cat > my-values.yaml << 'EOF' +grafana: + adminPassword: "lab-admin" +prometheus: + prometheusSpec: + resources: + requests: + cpu: 200m + memory: 512Mi +EOF + +helm install monitoring prometheus-community/kube-prometheus-stack \ + --namespace monitoring --create-namespace \ + -f my-values.yaml + +kubectl -n monitoring get pods +helm list -n monitoring +helm status monitoring -n monitoring + +# обновление релиза — меняем values и применяем поверх существующей инсталляции +helm upgrade monitoring prometheus-community/kube-prometheus-stack \ + --namespace monitoring \ + --set grafana.adminPassword=new-lab-admin \ + -f my-values.yaml + +helm history monitoring -n monitoring +# при необходимости: helm rollback monitoring 1 -n monitoring + +kubectl -n monitoring port-forward svc/monitoring-grafana 3000:80 +``` + +### Самопроверка + +- Своими словами объяснить разницу между Chart и Release (пакет vs конкретная установленная копия пакета). +- Зачем перед `helm install` смотреть `helm show values`, а не сразу ставить с дефолтами вслепую? +- Чем `helm upgrade` отличается от повторного `helm install`? (upgrade применяет изменения к существующему релизу с сохранением истории ревизий, install создаст конфликт с уже существующим релизом). + +### Как это в реальных проектах + +Это прямой ответ на реальный вопрос с собеседования Bi.Zone «как задеплоишь мониторинг на 300 000 серверов» (см. [../../legend/CAPACITY.md](../../legend/CAPACITY.md)): не вручную, а Helm-чартом с `values.yaml`, где параметризуются количество реплик, ресурсы, remote_write в Mimir — тот же принцип «Prometheus + Grafana», что и в рабочем стеке (см. [../monitoring/CASE.md](../monitoring/CASE.md)), но развёрнутый декларативно и параметризуемо, а не через docker-compose с ручными правками. + +--- + +## Модуль 12. Helm, часть 2 — автор чарта + +### Зачем это + +Пользоваться готовыми чартами (модуль 11) — половина навыка. Уметь упаковать своё приложение в чарт — то, что отличает «ставил через Helm» от «понимаю, как Helm устроен изнутри», и именно это спрашивают на более глубоких технических собеседованиях. + +### Теория-минимум + +Структура чарта, созданного `helm create`: +- `Chart.yaml` — метаданные чарта (имя, версия чарта, версия приложения). +- `values.yaml` — дефолтные параметры, доступные в шаблонах как `.Values.*`. +- `templates/` — сами манифесты K8s, но с шаблонизацией Go-template: `{{ .Values.replicaCount }}` подставит значение из values.yaml, `{{ include "chart.fullname" . }}` — переиспользуемый фрагмент из `_helpers.tpl`, `{{- if .Values.ingress.enabled }}...{{- end }}` — условное включение блока манифеста. +- `_helpers.tpl` — вспомогательные именованные шаблоны (чтобы не дублировать логику вроде генерации имени релиза в каждом файле). +- `NOTES.txt` — текст, который Helm печатает пользователю сразу после `helm install` (подсказка, как получить доступ к приложению). + +### Практика руками + +```bash +helm create demo-chart +``` + +Разобрать построчно, что `helm create` сгенерировал по умолчанию — открыть `demo-chart/Chart.yaml`, `demo-chart/values.yaml`, `demo-chart/templates/deployment.yaml`, `demo-chart/templates/_helpers.tpl` и сопоставить с манифестами, которые писались руками в модулях 3–7: `templates/deployment.yaml` — это тот же Deployment из модуля 4, но `replicas`, `image.repository`, `image.tag` заменены на `{{ .Values.replicaCount }}`, `{{ .Values.image.repository }}` и т.д. + +```bash +# проверка шаблона без реальной установки — что синтаксис корректен +helm lint demo-chart + +# рендер шаблонов локально — увидеть итоговый YAML, который получится после подстановки values +helm template demo-chart + +# рендер с переопределёнными values — увидеть разницу +helm template demo-chart --set replicaCount=3 --set image.repository=nginxdemos/hello +``` + +Адаптировать `values.yaml` и `templates/deployment.yaml` под приложение из модулей 3–7 (образ `nginxdemos/hello`, свой ConfigMap/Secret) и поставить своим чартом вместо ручных `kubectl apply`: + +```bash +helm install demo-release ./demo-chart --namespace demo --create-namespace +kubectl -n demo get all -l app.kubernetes.io/instance=demo-release +helm uninstall demo-release -n demo +``` + +### Самопроверка + +- Открыть `templates/deployment.yaml` сгенерированного чарта и найти в нём 3 места, где вместо жёстко заданного значения подставляется `.Values.*` — объяснить, зачем каждое из них параметризовано. +- Чем `helm template` отличается от `helm install`? (template — просто рендерит YAML локально, ничего не применяет к кластеру; install — рендерит и сразу применяет). +- Зачем нужен `_helpers.tpl`, если можно было бы просто продублировать `{{ .Release.Name }}-demo-chart` в каждом файле шаблонов? + +### Как это в реальных проектах + +Собственные чарты пишут, когда приложение — своё (не готовый open-source продукт вроде Prometheus), и его нужно раскатывать в несколько окружений с разными параметрами (dev/stage/prod) без копипаста манифестов — это прямая параллель с тем, как в рабочем стеке Ansible-роли параметризуются через переменные вместо копирования плейбуков под каждое окружение (см. [../ansible/CASE.md](../ansible/CASE.md)): тот же принцип «шаблон + параметры», другой уровень (конфигурация ОС/сервисов vs манифесты K8s). + +--- + +## Модуль 13. Как это устроено в реальных проектах + +### Зачем это + +Финальный модуль — не новая техническая тема, а сборка всех модулей в целостную картину «как это выглядит в реальной команде», плюс честная граница pet-опыта. + +### Теория-минимум + +Типичный процесс в компании, использующей K8s+Helm: +- Чарты (свои и сторонние) хранятся в git-репозитории вместе с манифестами инфраструктуры — это прямая параллель с рабочим опытом хранения Ansible-ролей в Gitea (см. [../../legend/LEGEND.md](../../legend/LEGEND.md)). +- Раскатка идёт не руками с ноутбука разработчика, а через CI/CD-пайплайн (`helm upgrade` как шаг джобы) — параллель с TeamCity-раскаткой ролей/дашбордов из рабочего опыта (см. [../../legend/STORY.md](../../legend/STORY.md) → «Версионирование дашбордов»). +- Разные `values-dev.yaml` / `values-staging.yaml` / `values-prod.yaml` под одно и то же приложение — тот же чарт, разные параметры на окружение. +- **GitOps** (инструменты ArgoCD/Flux) — следующий шаг развития идеи: вместо того чтобы CI сам выполнял `helm upgrade`, в git-репозитории просто описывается желаемое состояние кластера, а отдельный контроллер внутри кластера постоянно сверяет его с реальностью и сам подтягивает изменения (тот же принцип desired state из модуля 1, но применённый к самому процессу деплоя). Здесь достаточно понимания идеи, не практики — pet-кластер для этого не поднимался. +- **Umbrella-чарт** — чарт, который сам ничего не разворачивает напрямую, а просто объявляет зависимости от нескольких других чартов (например, «наше приложение» + «его конфигурация Redis» одним релизом) — используется, когда несколько связанных сервисов нужно ставить и версионировать вместе. + +### Практика руками + +Практики в этом модуле нет — это сборочный/рефлексивный модуль. Вместо неё: выписать на бумаге для своего pet-кластера полный путь «от `git commit` в репозитории чарта до работающего пода», используя реальные названия компонентов, которые прошли в модулях 0–12 (Deployment, Service, Helm release и т.д.) вместо абстрактных слов. + +### Самопроверка + +- Объяснить своими словами идею GitOps и чем она отличается от «CI-пайплайн просто выполняет helm upgrade». +- Честно сформулировать для себя одним предложением, что из модуля 13 — реальный pet-опыт (структура чартов, values, helm upgrade/rollback), а что — только понимание на уровне «знаю, что это и зачем» (ArgoCD/Flux, продовые GitOps-пайплайны). + +### Как это в реальных проектах + +Это тот самый честный формат ответа на собеседовании, уже принятый в этом репозитории для других инструментов «на границе опыта» (см. [../../legend/DEPT_STACK.md](../../legend/DEPT_STACK.md) → «Как этим пользоваться на собеседовании»): по GitOps — «сам не разворачивал, но понимаю идею и зачем она нужна», без попытки выдать это за практический опыт. + +--- + +## Выходной контроль + +Курс пройден, когда пройдены оба пункта: + +1. **Устный прогон [QUESTIONS.md](QUESTIONS.md)** — вслух, без подглядывания в готовые ответы; если запинаешься — вернуться к соответствующему модулю выше (не к готовому ответу), разобраться и сформулировать заново своими словами. +2. **Две схемы на бумаге без подсказок**: архитектура кластера (control-plane/worker, модуль 2) и путь трафика (клиент → под, модуль 5–6). + +### Таблица соответствия: вопрос из QUESTIONS.md → модуль курса + +| Вопрос из QUESTIONS.md | Модуль | +|---|---| +| Из чего состоит Kubernetes? | 2 | +| etcd будет на одной виртуалке с K8s или отдельно? | 2 | +| Что такое DaemonSet? | 2 (kube-proxy как DaemonSet) | +| Нарисуй два ЦОД под K8s с балансировщиком | 5, 6 | +| Как будешь разворачивать PostgreSQL на две ноды K8s? | 10 | +| Что такое Namespace и зачем он нужен? | 3 | +| Чем отличается ConfigMap от Secret? | 7 | +| Какой у тебя опыт с Helm и разворотом кластеров K8s? | 11, 12 | +| Как работает трафик в K8s? | 5, 6 | +| Как проверить логи и поды в кластере? | 3, 9 | + +После прохождения курса — переходи к [CASE.md](CASE.md) как к компактному конспекту для повторения перед конкретным собеседованием. diff --git a/stack/kubernetes/QUESTIONS.md b/stack/kubernetes/QUESTIONS.md new file mode 100644 index 0000000..0703652 --- /dev/null +++ b/stack/kubernetes/QUESTIONS.md @@ -0,0 +1,43 @@ +# Вопросы: Kubernetes + +Опираются на кейс: [CASE.md](CASE.md). Источники реальных вопросов — [Реалист банк.md](../../interview/Реалист%20банк.md), [Bi.Zone.md](../../interview/Bi.Zone.md), [Alfa-Bank.md](../../interview/Alfa-Bank.md), [VK Cloud.md](../../interview/VK%20Cloud.md), см. сводку в [../../interview/real-interviews.md](../../interview/real-interviews.md). + +### Из чего состоит Kubernetes? + +Control-plane — `kube-apiserver` (единая точка входа для всех операций), `etcd` (хранилище состояния кластера), `kube-scheduler` (распределяет поды по нодам), `kube-controller-manager` (следит за соответствием реального состояния желаемому). На worker-нодах — `kubelet` (управляет подами на своей ноде) и `kube-proxy` (сетевые правила доступа к Service), плюс container runtime (`containerd`). У себя в кластере (`kind`, 1 control-plane + 2 worker) я это видел напрямую через `kubectl get pods -n kube-system`. + +### etcd будет на одной виртуалке с K8s или отдельно? + +В маленьких/pet-инсталляциях (у меня в `kind`) etcd живёт на той же control-plane ноде. В проде для отказоустойчивости etcd обычно выносят на отдельные ноды нечётным числом (3 или 5) — так работает кворум: при потере части узлов кластер etcd продолжает принимать решения, если жива больше половины. Consul в этом контексте — альтернатива service discovery/конфиг-хранилища, не замена etcd как хранилища состояния самого K8s. + +### Что такое DaemonSet? + +Контроллер, который гарантирует, что на каждой (или на подмножестве по селектору) ноде кластера запущена ровно одна копия пода — используется для агентов уровня ноды: `kube-proxy`, CNI-плагин, node-exporter для мониторинга. В отличие от Deployment, DaemonSet не масштабируется числом реплик — он масштабируется вместе с числом нод: добавили ноду — на ней автоматически появился под DaemonSet. + +### Нарисуй два ЦОД под K8s с балансировщиком — на каких виртуалках это будет работать? + +Внешний балансировщик (L4/L7, например HAProxy/облачный LB) перед двумя ЦОД → в каждом ЦОД — набор worker-нод под control-plane (control-plane лучше растянуть между ЦОД нечётным числом инстансов ради кворума etcd, например 3: 2 в одном ЦОД + 1 в другом, или 3+2 при пяти) → внутри кластера трафик до пода идёт через Service (ClusterIP/NodePort) и `kube-proxy`, который резолвит запрос в конкретный под по iptables/IPVS-правилам. Каждый ЦОД — набор физических/виртуальных worker-нод с kubelet и containerd. Я такую схему целиком не эксплуатировал (моя лаба — 3 ноды на одной машине), но принцип балансировки и роль каждого компонента понимаю и воспроизвёл в миниатюре. + +### Как будешь разворачивать PostgreSQL на две ноды K8s? + +Через `StatefulSet` (не Deployment — под'ам БД нужны стабильные имена и отдельные PersistentVolume на каждую реплику) с master-replica топологией: обычно готовый оператор (например, CloudNativePG или Zalando Postgres Operator) заводит StatefulSet, PersistentVolumeClaim на каждую ноду и Service, разделяющий трафик на запись (к master) и на чтение (к репликам). Сам оператор в кластере не поднимал — но принцип репликации PostgreSQL прогнан отдельно в Docker Compose, см. [../databases/CASE.md](../databases/CASE.md); K8s тут просто уровень оркестрации поверх той же логики репликации. + +### Что такое Namespace и зачем он нужен? + +Логическая изоляция ресурсов внутри одного кластера — разделение по окружениям (dev/stage/prod) или командам, без поднятия отдельных кластеров. Объекты в разных namespace могут называться одинаково и не конфликтуют; ресурсы вроде Node и PersistentVolume — не namespaced, они общие на весь кластер. В своей лабе завёл отдельный namespace `demo` под тестовое приложение и отдельный `monitoring` под kube-prometheus-stack — чтобы не мешать их друг другу и явно видеть границу через `kubectl -n `. + +### Чем отличается ConfigMap от Secret? + +ConfigMap — для несекретных конфигурационных данных (переменные окружения, конфиг-файлы), Secret — для чувствительных значений (пароли, токены, ключи). Технически Secret по умолчанию хранит значения в base64 — это кодирование, а не шифрование, то есть сам по себе Secret не «защищает» данные от того, кто имеет доступ к API/etcd. Для реальной защиты нужны либо шифрование etcd at rest, либо внешние системы вроде Vault (см. [../vault/CASE.md](../vault/CASE.md)) или Sealed Secrets. В своём манифесте завёл и ConfigMap, и Secret и явно проверил разницу через `kubectl get secret app-secret -o yaml` — значение там закодировано base64, не зашифровано. + +### Какой у тебя опыт с Helm и разворотом кластеров K8s? + +Helm — пакетный менеджер для K8s: чарт описывает набор шаблонизированных манифестов с параметрами в `values.yaml`, `helm install`/`upgrade` разворачивает/обновляет релиз одной командой вместо ручного `kubectl apply` на десяток файлов. У себя разворачивал `kube-prometheus-stack` через `helm install` в отдельный namespace — это дало сразу Prometheus + Grafana + Alertmanager + нужные ServiceMonitor-ресурсы с рабочими дефолтами, которые я после точечно переопределял через `--set`/`values.yaml`. + +### Как работает трафик в K8s? + +Клиент → внешний балансировщик/Ingress-контроллер (роутинг по host/path) → Service (стабильный виртуальный IP/DNS-имя, абстракция над множеством подов) → `kube-proxy` на нодах транслирует обращение к Service в адрес конкретного пода по правилам iptables/IPVS, с балансировкой между подами, которые матчатся по `selector` в Service. Между подами внутри кластера — плоская сеть через CNI-плагин, любой под может обратиться к любому другому по его Pod IP или через Service DNS-имя (`..svc.cluster.local`). + +### Как проверить логи и поды в кластере? + +`kubectl get pods -A` — все поды по всем namespace; `kubectl -n logs ` — логи контейнера, `--previous` — логи упавшего перед текущим рестартом контейнера; `kubectl -n describe pod ` — события и причина, если под не стартует (`ImagePullBackOff`, `CrashLoopBackOff`); `kubectl get events --sort-by=.lastTimestamp` — хронология событий по всему namespace. Специально ронял под (указывал несуществующий образ) и разбирал `describe`, чтобы увидеть, как выглядит диагностика в реальности, а не в теории. diff --git a/stack/languages/CASE.md b/stack/languages/CASE.md new file mode 100644 index 0000000..e6c8630 --- /dev/null +++ b/stack/languages/CASE.md @@ -0,0 +1,22 @@ +# Кейс: вспомогательные языки (Python, SQL, C/C++) + +> Заготовка. Статус: TODO — требует полной проработки по правилам [../../AI_GUIDELINES.md](../../AI_GUIDELINES.md). Эти технологии не имеют отдельной линии опыта в резюме — они вписаны в легенду как сквозные инструменты (см. [../../legend/LEGEND.md](../../legend/LEGEND.md), раздел «Технологии без прямого кейса в опыте работы»). Кейсы здесь должны конкретно привязываться к задачам из уже проработанных категорий, а не существовать отдельно. + +## Python — автоматизация + +- [ ] Написать реальный скрипт, который решает задачу из уже сделанных кейсов: например, скрипт на Python, который парсит логи Nginx из [../nginx/CASE.md](../nginx/CASE.md) и считает количество 5xx ответов, либо скрипт, дёргающий Prometheus HTTP API из [../monitoring/CASE.md](../monitoring/CASE.md) и печатающий текущее значение метрики. +- [ ] Вопросы: разница списков и кортежей, GIL на базовом уровне, работа с виртуальным окружением (venv), как оформлен typical requirements.txt. + +## SQL — работа с данными + +- [ ] Поднять локально PostgreSQL/MySQL в Docker, создать простую таблицу (например, лог инцидентов: id, дата, сервис, описание), написать несколько реальных запросов: выборка с фильтром и сортировкой, `GROUP BY` с агрегатом (количество инцидентов по сервису), `JOIN` с таблицей сервисов. +- [ ] Вопросы: разница INNER/LEFT JOIN, что такое индекс и когда он не помогает, разница WHERE и HAVING, что такое транзакция и ACID на базовом уровне. + +## C/C++ — чтение кода + +- [ ] Не писать production-код с нуля (не основной инструмент), но разобрать небольшой пример на C/C++ (например, простую программу с багом — segfault от null pointer или утечкой памяти) и показать, что кандидат может прочитать код, найти проблему и объяснить её словами — это то, что реально требовалось тестировщику embedded ПО в ОКБ СУХОЙ. +- [ ] Вопросы: разница между стеком и кучей, что такое segmentation fault, зачем нужен valgrind/аналог, разница между указателем и ссылкой. + +## Как это ложится в легенду + +Все три языка — не основная специализация, а обвязка вокруг основных задач (мониторинг, тестирование embedded ПО). Кейсы должны это подчёркивать: не «я Python-разработчик», а «я писал скрипты на Python, чтобы автоматизировать конкретную рутину сопровождения». diff --git a/stack/linux-bash/CASE.md b/stack/linux-bash/CASE.md new file mode 100644 index 0000000..8d63026 --- /dev/null +++ b/stack/linux-bash/CASE.md @@ -0,0 +1,146 @@ +# Кейс: Linux (Astra/CentOS) и Bash — диагностика и внутренности + +Связь с легендой: [../../legend/LEGEND.md](../../legend/LEGEND.md) — Astra Linux (АО ТНИИС) как базовая ОС серверного парка, CentOS (ОКБ СУХОЙ) как ОС тестовых стендов, Bash — повседневная автоматизация в обеих ролях. + +## Что нужно реально сделать + +Практикум на Linux-контейнере/WSL: живые bash-скрипты для обслуживания и прогон диагностических сценариев, которые реально спрашивали на собеседованиях («сервер тормозит — с чего начнёшь», «упал сервер — что делать»). + +### 1. Реальные bash-скрипты для обслуживания + +**Ротация и архивация логов:** +```bash +#!/usr/bin/env bash +set -euo pipefail + +LOG_DIR="/var/log/myapp" +ARCHIVE_DIR="/var/log/myapp/archive" +DAYS_TO_KEEP=7 + +mkdir -p "$ARCHIVE_DIR" +find "$LOG_DIR" -maxdepth 1 -name "*.log" -mtime +1 -exec gzip {} \; +find "$LOG_DIR" -maxdepth 1 -name "*.log.gz" -exec mv {} "$ARCHIVE_DIR" \; +find "$ARCHIVE_DIR" -name "*.log.gz" -mtime +"$DAYS_TO_KEEP" -delete +``` + +**Проверка места на диске с алертом в лог:** +```bash +#!/usr/bin/env bash +set -euo pipefail + +THRESHOLD=85 +USAGE=$(df -h / | awk 'NR==2 {gsub("%","",$5); print $5}') + +if [ "$USAGE" -ge "$THRESHOLD" ]; then + echo "$(date '+%Y-%m-%d %H:%M:%S') WARNING: disk usage ${USAGE}% >= ${THRESHOLD}%" >> /var/log/disk-check.log + exit 1 +fi +``` + +**Обвязка перезапуска стенда из [../monitoring/CASE.md](../monitoring/CASE.md):** +```bash +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" +docker compose down +docker compose up -d +sleep 5 +docker compose ps --filter "status=running" | grep -q prometheus || { + echo "prometheus не поднялся" >&2 + exit 1 +} +``` + +### 2. Systemd: unit-файл + таймер вместо cron + +```ini +# /etc/systemd/system/disk-check.service +[Unit] +Description=Проверка места на диске + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/disk-check.sh +``` + +```ini +# /etc/systemd/system/disk-check.timer +[Unit] +Description=Запуск disk-check каждые 15 минут + +[Timer] +OnBootSec=5min +OnUnitActiveSec=15min + +[Install] +WantedBy=timers.target +``` + +```bash +systemctl daemon-reload +systemctl enable --now disk-check.timer +systemctl list-timers | grep disk-check +journalctl -u disk-check.service +``` + +### 3. Диагностический сценарий «сервер тормозит — первые 3–5 команд» + +Порядок, который реально прогонял на своих стендах: + +1. `uptime` — load average за 1/5/15 минут, первое ощущение масштаба проблемы. +2. `top` (или `htop`) — какой процесс ест CPU/память прямо сейчас. +3. `free -h` — сколько реально свободной памяти (`available`, а не `free`, см. QUESTIONS.md). +4. `df -h` — не закончилось ли место на диске (частая причина зависаний записи). +5. `iostat -x 1` / `vmstat 1` — I/O-нагрузка на диск, если `top` не показал явного виновника по CPU. + +### 4. Диагностический сценарий «df показывает, что место закончилось, а du — что всё в порядке» + +Воспроизвести самому: +```bash +# терминал 1: занимаем место большим файлом внутри работающего процесса +dd if=/dev/zero of=/tmp/bigfile bs=1M count=2000 & +PID=$! +sleep 1 +rm /tmp/bigfile # удаляем файл, пока процесс его ещё держит открытым + +df -h /tmp # место всё ещё занято +du -sh /tmp # du не видит файл — его уже нет в дереве каталогов + +ls -l /proc/$PID/fd | grep bigfile # но дескриптор в /proc//fd всё ещё жив +wait $PID +df -h /tmp # только теперь место освобождается +``` + +Причина: `du` считает по дереву каталогов (файла там уже нет — он удалён), а `df` считает по факту занятых блоков на файловой системе, которые освобождаются только когда закрывается последний файловый дескриптор, ссылающийся на inode. Пока процесс держит файл открытым, место физически занято, хотя ссылки на файл в каталоге уже нет. + +### 5. Права, владение, поиск в логах + +```bash +chmod 755 /usr/local/bin/disk-check.sh +chown appuser:appgroup /var/log/myapp + +# поиск всех строк с ошибкой в большом логе +grep -c "connection timeout" /var/log/myapp/app.log +awk '/connection timeout/ {print $0}' /var/log/myapp/app.log | tail -20 +``` + +### 6. Сигналы и процессы + +```bash +ps aux | grep myapp +kill -15 # SIGTERM — вежливая просьба завершиться +kill -9 # SIGKILL — принудительное убийство, процесс не может его перехватить/игнорировать +pkill -f myapp # найти и убить по имени/паттерну команды +``` + +## Что это даёт в разговоре с интервьюером + +- Реальные, лично написанные и прогнанные bash-скрипты для обслуживания, а не абстрактные примеры. +- Практический опыт systemd unit + timer как альтернативы cron. +- Воспроизведённый вживую «фокус» df vs du — конкретный, запоминающийся ответ вместо зазубренной теории. +- Уверенная диагностическая последовательность на вопрос «сервер тормозит». + +## Как это ложится в легенду + +Linux и Bash — сквозной инструмент в обеих ролях (тестировщик на CentOS, инженер сопровождения на Astra). Скрипты и диагностика в этом кейсе — обобщённая практика повседневного обслуживания серверов, характерная для роли инженера сопровождения, без привязки к вымышленным деталям конкретных внутренних систем компаний. diff --git a/stack/linux-bash/QUESTIONS.md b/stack/linux-bash/QUESTIONS.md new file mode 100644 index 0000000..6c61959 --- /dev/null +++ b/stack/linux-bash/QUESTIONS.md @@ -0,0 +1,79 @@ +# Вопросы: Linux и Bash + +Опираются на кейс: [CASE.md](CASE.md). Источники — [VK Cloud.md](../../interview/VK%20Cloud.md) (основной объём), [Dit Moscow.md](../../interview/Dit%20Moscow.md), [Alfa-Bank.md](../../interview/Alfa-Bank.md). + +### Есть load average в Linux — как считается? + +Скользящее экспоненциально взвешенное среднее числа процессов в состоянии "runnable" (готовы выполняться/выполняются) плюс процессов в непрерываемом ожидании ввода-вывода (D-state), усреднённое за 1/5/15 минут. Число выше количества ядер не всегда значит перегрузку CPU — часто виноват диск или сеть (процессы висят в D-state), поэтому смотрю load average вместе с `top`/`vmstat`, а не отдельно. + +### Free и available в выводе free — чем отличаются, и если free почти 0, а available много — это плохо? + +`free` — буквально свободная, ничем не занятая память. `available` — память, реально доступная для новых приложений без свопинга, с учётом того, что ядро может быстро освободить часть кеша страниц (page cache) и буферов при необходимости. Linux агрессивно использует свободную память под кеш файловой системы — это не потеря, а разумное использование ресурса. Поэтому если `free` близко к 0, а `available` большое — это нормально и даже хорошо: система эффективно использует память под кеш, который мгновенно отдаст приложениям по запросу. Плохой признак — низкий именно `available` вместе с активным использованием swap. + +### Кейс: сервер тормозит, с каких 3-5 команд начнёшь диагностику? + +`uptime` (load average, масштаб проблемы) → `top`/`htop` (кто ест CPU/память прямо сейчас) → `free -h` (память) → `df -h` (не кончилось ли место, особенно если есть запись логов) → `iostat -x 1`/`vmstat 1` (I/O, если по CPU и памяти виновник не найден). Порядок именно такой — от общей картины к частностям, чтобы не тратить время на глубокую диагностику не в том направлении. + +### Как найти pid нужного процесса и убить его? + +`ps aux | grep <имя>` или `pgrep -f <паттерн>` — найти pid. Убить: `kill -15 ` (SIGTERM, вежливое завершение, процесс может перехватить сигнал и корректно закрыть ресурсы) или `kill -9 ` (SIGKILL, безусловное принудительное убийство ядром — процесс не может его ни перехватить, ни проигнорировать). Второй способ — `pkill -f <паттерн>` находит и убивает сразу по паттерну имени команды, без отдельного поиска pid. + +### Команда kill и её популярные сигналы, 2 способа убить процесс + +Основные сигналы: `SIGTERM` (15, по умолчанию у `kill`) — просьба завершиться корректно; `SIGKILL` (9) — принудительное безусловное завершение ядром; `SIGHUP` (1) — исторически «повесить трубку», многие демоны используют его как сигнал перечитать конфиг без полного рестарта; `SIGINT` (2) — то же, что Ctrl+C. Два способа убить: `kill ` (по идентификатору процесса) и `pkill -f <паттерн>` (по имени/паттерну командной строки, без предварительного поиска pid). + +### Что делают df и du — с подвохом (df говорит место закончилось, du — что всё в порядке) + +`df` считает занятое место по факту использованных блоков файловой системы. `du` считает по дереву каталогов, суммируя размеры файлов, которые там реально видны. Расхождение возникает, когда файл удалён (`rm`), но его всё ещё держит открытым какой-то процесс — файла больше нет в дереве каталогов (`du` его не видит), но место физически не освобождается, пока не закроется последний файловый дескриптор, ссылающийся на inode этого файла (`df` показывает место как занятое). Я воспроизвёл это вживую: `dd` создаёт большой файл в фоне, `rm` его удаляет, пока процесс жив — `df` и `du` расходятся, пока процесс не завершится (`ls -l /proc//fd` при этом показывает "живой" дескриптор на уже удалённый файл). + +### Что покажет ps -aux / ps -ef? + +Оба показывают список всех процессов в системе, но в разном формате: `ps aux` — BSD-стиль (USER, PID, %CPU, %MEM, VSZ, RSS, TTY, STAT, START, TIME, COMMAND), `ps -ef` — System V-стиль (UID, PID, PPID — родительский процесс явно виден, C, STIME, TTY, TIME, CMD). `ps -ef` удобнее, когда нужно явно видеть иерархию процессов через PPID; `aux` привычнее для быстрой оценки потребления ресурсов. + +### Как назначить владельца папки в Linux? Как выдаются права, что такое 777 и 755? + +`chown user:group /path/to/dir` — сменить владельца (пользователя) и группу; `chown -R` — рекурсивно для содержимого. Права задаются тремя цифрами (владелец/группа/остальные), каждая — сумма: read=4, write=2, execute=1. `755` = владелец rwx (7), группа и остальные r-x (5) — типично для исполняемых скриптов и директорий, куда нужен доступ на чтение всем. `777` = rwx всем без ограничений — практически всегда небезопасно на проде, разрешает любому пользователю системы читать, менять и исполнять файл. + +### Что делает awk и варианты использования? + +Текстовый процессор, работающий построчно и разбивающий каждую строку на поля по разделителю (по умолчанию — пробел/таб): `$1`, `$2` — первое/второе поле, `$0` — вся строка, `NR` — номер текущей строки. Типичное использование: `awk '{print $1}' file` — вывести первую колонку, `awk -F: '{print $1}' /etc/passwd` — сменить разделитель на `:`, `awk 'NR==2 {print $5}'` — взять конкретное поле конкретной строки (я так парсил вывод `df -h` в скрипте disk-check из [CASE.md](CASE.md)), `awk '/pattern/ {print}'` — фильтрация строк по регулярке, аналог grep, но с доступом к полям. + +### У тебя большой лог — как найти все строки с ошибкой connection timeout? + +`grep -c "connection timeout" file.log` — посчитать количество вхождений; `grep -n "connection timeout" file.log` — вывести с номерами строк; для действительно больших файлов и потоковой обработки в реальном времени — `tail -f file.log | grep --line-buffered "connection timeout"`. Если нужен контекст вокруг ошибки — `grep -B2 -A2 "connection timeout" file.log` (2 строки до и после). + +### Что такое cgroups и namespaces в Linux? + +Namespaces — механизм ядра, изолирующий, что процесс видит: свой PID namespace (процесс видит только «свои» процессы, начиная нумерацию с 1), сетевой namespace (свои интерфейсы, таблицы маршрутизации), mount namespace (своя файловая система), UTS (своё hostname) и т.д. Cgroups (control groups) — механизм, ограничивающий и учитывающий, сколько ресурсов процесс может использовать (CPU, память, I/O). Вместе это и есть технический фундамент контейнеризации: контейнер — это обычный процесс на хосте, которому через namespaces ограничили видимость системы, а через cgroups — доступные ресурсы (без отдельной виртуализации ядра, в отличие от полноценных VM). Подробнее в контексте Docker — [../docker/QUESTIONS.md](../docker/QUESTIONS.md). + +### Что такое inode? + +Структура данных файловой системы, хранящая метаданные файла (владелец, права, размер, время изменения, указатели на блоки данных на диске) — но не само имя файла. Имя файла — это просто запись-ссылка в каталоге, указывающая на inode. Отсюда и разница между df/du из вопроса выше: пока хотя бы один процесс держит открытым файловый дескриптор, указывающий на inode, место на диске не освобождается, даже если все имена (ссылки в каталогах) на этот inode уже удалены. + +### Что такое дескрипторы и reference count в файловом дескрипторе? + +Файловый дескриптор — небольшое целое число, которым процесс ссылается на открытый файл/сокет/пайп внутри ядра (0, 1, 2 — stdin/stdout/stderr зарезервированы). Reference count в контексте inode — счётчик количества ссылок на него: это и жёсткие ссылки в каталогах (`ln`), и открытые файловые дескрипторы процессов. Данные на диске реально освобождаются только тогда, когда reference count падает до нуля — то есть удалены все имена (hard links) и закрыты все процессы, державшие файл открытым. + +### Ссылки жёсткие и мягкие (hard/soft) — в чём разница и что происходит при удалении файла? + +Жёсткая ссылка (`ln`) — ещё одно имя, указывающее прямо на тот же inode: у файла и его hard link одинаковый inode-номер, и оба «равноправны» — удаление одного из имён не трогает данные, пока жива хотя бы одна ссылка (см. reference count выше). Мягкая/символическая ссылка (`ln -s`) — отдельный маленький файл, который просто хранит путь к другому файлу; если оригинал удалить, symlink останется, но будет указывать в никуда («битая ссылка»). Hard link не может пересекать границы файловых систем и не может указывать на директорию (за редкими системными исключениями), symlink может — это одно из отличий на практике. + +### Как проверить, что порт открыт/слушается, и как найти порт нужного процесса? + +`ss -tulpn` (современный аналог `netstat`) — покажет все слушающие TCP/UDP порты и какой процесс их держит. Для конкретного процесса: `ss -tulpn | grep ` либо через `/proc//net/tcp` (низкоуровнево). Для проверки доступности порта на удалённом хосте: `nc -zv host port` или `curl -v telnet://host:port`. + +### Как будешь менять параметры ядра Linux? + +Через `sysctl`: временно — `sysctl -w vm.swappiness=10` (действует до перезагрузки), постоянно — правка `/etc/sysctl.conf` или файла в `/etc/sysctl.d/*.conf` с последующим `sysctl -p` для применения без перезагрузки. Типичные параметры, которые встречаются на практике: `vm.swappiness` (насколько активно система уходит в своп), `net.core.somaxconn` (размер очереди входящих TCP-соединений), `fs.file-max` (лимит открытых файловых дескрипторов на систему). + +### Что такое systemd? Зачем нужна директория /etc/systemd/system? + +Systemd — современная система инициализации и менеджер служб в большинстве дистрибутивов Linux (включая Astra и современные CentOS/RHEL) — управляет запуском, остановкой, зависимостями и мониторингом состояния сервисов через unit-файлы. `/etc/systemd/system` — стандартное место для unit-файлов, добавленных администратором вручную (в отличие от `/usr/lib/systemd/system`, куда unit-файлы кладут пакеты дистрибутива) — сюда я клал свои unit и timer для скрипта disk-check из [CASE.md](CASE.md). + +### Что такое zombie/orphan процесс? + +Zombie — процесс, который уже завершился, но его родитель ещё не вызвал `wait()`, чтобы забрать его код возврата — запись о процессе висит в таблице процессов (виден в `ps` со статусом `Z`), но сам процесс уже не потребляет ресурсов кроме этой записи. Orphan — процесс, чей родитель завершился раньше него; такой процесс автоматически «усыновляется» init-процессом (PID 1 или ближайшим subreaper), который и заберёт его код возврата, когда тот завершится — то есть orphan сам по себе не проблема, в отличие от накопления zombie-процессов, что говорит об ошибке в родительском процессе (не вызывает wait()). + +### Что будешь делать, если упал сервер? Если сервер на VMware — как будешь спасать VM и какую диагностику проведёшь? + +Порядок действий: сначала проверить, отвечает ли хост вообще (ping, SSH) — если нет, для VM это часто означает проблему на уровне гипервизора, а не гостевой ОС. Дальше — зайти через консоль гипервизора (у VMware это vSphere/ESXi console, доступ к которой не зависит от сетевого стека самой VM) и посмотреть состояние виртуалки: зависла ли она (нет отклика на клавиатуру в консоли), ушла в панику ядра (виден текст паники на консоли), или процесс просто не отвечает по сети из-за исчерпания ресурсов. Дальнейшая диагностика по возможности зайти в саму гостевую ОС: `dmesg`/`journalctl -xb` на предмет OOM killer или паники ядра, проверка диска (не забился ли под 100%, что часто останавливает запись логов и сервисов), проверка сети гипервизора (не отвалился ли виртуальный сетевой адаптер). Если гостевая ОС совсем не отвечает — крайняя мера восстановления это перезапуск VM средствами гипервизора (soft reset через ACPI-сигнал, если возможно, иначе hard reset) с последующим разбором причины по логам, которые сохранились до сбоя. Личного опыта администрирования именно VMware у меня нет — это разбор на уровне общей логики диагностики сбоя виртуальной машины, применимой к любому гипервизору. diff --git a/stack/monitoring/CASE.md b/stack/monitoring/CASE.md new file mode 100644 index 0000000..2c44798 --- /dev/null +++ b/stack/monitoring/CASE.md @@ -0,0 +1,142 @@ +# Кейс: стек мониторинга (Prometheus + Grafana + Mimir) + +Связь с легендой: [../../legend/LEGEND.md](../../legend/LEGEND.md) — раздел «АО ТНИИС». Задача инженера сопровождения — собирать метрики, визуализировать их и держать масштабируемое хранилище для истории метрик. + +## Что нужно реально сделать (домашний стенд) + +Разворачиваем локально Prometheus + Mimir + Grafana + node-exporter через Docker Compose. Цель — своими руками пройти путь «метрика с хоста → Prometheus → remote_write в Mimir → дашборд в Grafana», чтобы честно говорить об этом на собеседовании. + +### 1. Структура стенда + +``` +monitoring-lab/ +├── docker-compose.yml +├── prometheus/ +│ └── prometheus.yml +├── mimir/ +│ └── mimir.yml +└── grafana/ + └── provisioning/ + └── datasources/ + └── datasource.yml +``` + +### 2. docker-compose.yml + +```yaml +version: "3.8" + +services: + node-exporter: + image: prom/node-exporter:latest + container_name: node-exporter + ports: + - "9100:9100" + + prometheus: + image: prom/prometheus:latest + container_name: prometheus + volumes: + - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml + command: + - "--config.file=/etc/prometheus/prometheus.yml" + ports: + - "9090:9090" + depends_on: + - node-exporter + + mimir: + image: grafana/mimir:latest + container_name: mimir + command: ["-config.file=/etc/mimir/mimir.yml"] + volumes: + - ./mimir/mimir.yml:/etc/mimir/mimir.yml + ports: + - "9009:9009" + + grafana: + image: grafana/grafana:latest + container_name: grafana + volumes: + - ./grafana/provisioning:/etc/grafana/provisioning + ports: + - "3000:3000" + depends_on: + - mimir + - prometheus +``` + +### 3. prometheus/prometheus.yml + +```yaml +global: + scrape_interval: 15s + +scrape_configs: + - job_name: "node" + static_configs: + - targets: ["node-exporter:9100"] + +remote_write: + - url: "http://mimir:9009/api/v1/push" +``` + +### 4. mimir/mimir.yml (минимальный single-binary конфиг для лабы) + +```yaml +target: all + +blocks_storage: + backend: filesystem + filesystem: + dir: /data/blocks + bucket_store: + sync_dir: /data/tsdb-sync + +compactor: + data_dir: /data/compactor + +ingester: + ring: + replication_factor: 1 + +limits: + ingestion_rate: 100000 +``` + +### 5. grafana/provisioning/datasources/datasource.yml + +```yaml +apiVersion: 1 + +datasources: + - name: Mimir + type: prometheus + access: proxy + url: http://mimir:9009/prometheus + isDefault: true +``` + +### 6. Шаги воспроизведения + +1. `docker compose up -d` — поднять весь стенд. +2. Открыть Prometheus UI (`localhost:9090/targets`) — убедиться, что target `node` в статусе `UP`. +3. Открыть Grafana (`localhost:3000`, admin/admin), проверить, что источник данных `Mimir` отвечает (Explore → запрос `up`). +4. Создать дашборд вручную: панель CPU (`rate(node_cpu_seconds_total{mode="idle"}[5m])`), панель памяти (`node_memory_MemAvailable_bytes`), панель дисков. +5. Настроить alert rule в Grafana (например, «CPU idle < 20% в течение 5 минут») и проверить, что алерт срабатывает при нагрузке (`stress` внутри контейнера или на хосте). +6. Остановить `node-exporter` и убедиться, что Prometheus помечает target как `DOWN`, а в Grafana это видно на дашборде (пропуск данных) — так на практике выглядит инцидент. + +### 7. Что это даёт в разговоре с интервьюером + +- Понимание разницы **Prometheus vs Mimir**: Prometheus — сбор + локальное краткосрочное хранение + alerting-движок; Mimir — горизонтально масштабируемое долгосрочное хранилище метрик, совместимое с PromQL, принимает данные через `remote_write`. +- Понимание модели pull (Prometheus сам ходит в `/metrics`) в отличие от push-систем. +- Практическое понимание, что такое target, job, scrape_interval, label. +- Опыт настройки datasource и дашборда в Grafana руками, а не только просмотр готовых. + +## Нагрузка и цифры + +Один node-exporter — не показатель нагрузки; честная оценка масштаба и вопросы про «как мониторить 300 000 серверов» разобраны в [../../legend/CAPACITY.md](../../legend/CAPACITY.md). На этом стенде можно замерить число активных series (`curl localhost:9090/api/v1/status/tsdb`) и вписать результат в таблицу лабных замеров там же. + +## Как это ложится в легенду + +В реальной работе (АО ТНИИС) — 50 дашбордов в Grafana и настройка Mimir для масштабирования, унификация метрик из разных источников. Домашний кейс воспроизводит тот же путь данных в миниатюре: один источник (node-exporter) вместо десятков, но тот же принцип — Prometheus scrape → remote_write → Mimir → Grafana dashboard. diff --git a/stack/monitoring/QUESTIONS.md b/stack/monitoring/QUESTIONS.md new file mode 100644 index 0000000..1d2c0b2 --- /dev/null +++ b/stack/monitoring/QUESTIONS.md @@ -0,0 +1,67 @@ +# Вопросы: мониторинг (Prometheus / Grafana / Mimir) + +Опираются на кейс: [CASE.md](CASE.md). + +### Чем Prometheus отличается от Zabbix/Nagios? + +Prometheus тянет метрики сам (pull-модель) с эндпоинта `/metrics` у сервиса, хранит их как временные ряды с лейблами и даёт мощный язык запросов PromQL. Классические Zabbix/Nagios исторически больше про push-агентов и чек-скрипты с бинарным статусом «ок/не ок». Я поднимал у себя стенд с node-exporter — Prometheus сам ходит и забирает метрики по расписанию (`scrape_interval`), это и есть pull. + +### Что такое target, job, label в Prometheus? + +Target — конкретный адрес, откуда собираются метрики (например, `node-exporter:9100`). Job — логическая группа таргетов одного типа (в моём конфиге — job `node`). Label — пара ключ-значение, которая добавляется к метрике и позволяет фильтровать/группировать в PromQL (`instance`, `job`, и кастомные). + +### Зачем нужен remote_write и чем Mimir отличается от обычного Prometheus? + +Prometheus по умолчанию хранит данные локально и недолго (по умолчанию TSDB держит порядка 15 дней). Если нужно долгосрочное хранение и горизонтальное масштабирование под много Prometheus-инстансов, метрики отправляют через `remote_write` во внешнее хранилище — у меня это Mimir. Mimir совместим с PromQL, поэтому Grafana обращается к нему так же, как к обычному Prometheus-источнику, но данные хранятся отдельно и масштабируемо (у меня — на файловой системе, в проде обычно объектное хранилище типа S3). + +### Как устроен путь метрики от хоста до дашборда? + +node-exporter отдаёт метрики на `/metrics` → Prometheus по scrape_interval их забирает → одновременно пушит их через remote_write в Mimir → Grafana ходит в Mimir как в datasource и рисует панели через PromQL-запросы. + +### Что делать, если target в Prometheus в статусе DOWN? + +Проверить: доступен ли хост/порт сетевым способом (в моём случае — контейнер node-exporter упал или сеть Docker недоступна), отдаёт ли эндпоинт `/metrics` корректный ответ, не изменился ли путь/порт в конфиге scrape_config. Я специально гасил node-exporter в стенде и смотрел, как это отражается в Prometheus targets и на дашборде (пропуски данных). + +### Как написать alert rule и куда он «стреляет»? + +Alert rule — это условие на PromQL-выражение с длительностью (например, «CPU idle < 20% в течение 5 минут» → алерт). В Grafana я настраивал такое правило на дашборде; в проде алерты обычно уходят дальше в Alertmanager/мессенджеры — этого в лабе я не поднимал, но принцип понятен: правило проверяется на интервале evaluation_interval, и если условие держится дольше `for`, алерт переходит из pending в firing. + +### Чем отличается rate() от irate() в PromQL? + +Обе функции считают скорость роста counter-метрики, но `rate()` усредняет по всему интервалу окна (более гладкий график, подходит для алертов и дашбордов), а `irate()` считает по двум последним точкам (более «дёрганый», подходит для быстрого реагирования на всплески). В своих запросах CPU я использовал `rate()`, потому что нужен был сглаженный тренд, а не мгновенный всплеск. + +### Зачем нужна унификация форматов метрик, если данные идут из разных источников? + +Если разные экспортеры называют одну и ту же сущность по-разному (разные названия лейблов, разные единицы измерения), дашборды и алерты становится сложно переиспользовать и сравнивать между сервисами. Унификация — это соглашение об именовании метрик и лейблов, чтобы один и тот же дашборд можно было применить к разным сервисам без переделки запросов. + +### Как организовать доступ к дашбордам для команды? + +В Grafana это роли и права на уровне организации/папок дашбордов плюс подключение аутентификации (LDAP/OAuth/встроенные пользователи). Права разграничиваются так, чтобы просмотр был у всех, а редактирование — у ограниченного круга, чтобы не ломали чужие дашборды. + +### В чём разница между метриками и логами и почему нужны оба? + +Метрики — агрегированные числовые ряды во времени (быстро смотреть тренды, ставить алерты по порогам). Логи — детальные текстовые события конкретного момента (нужны, чтобы понять, что именно произошло внутри инцидента). Метрика в Grafana покажет, что вырос p99 задержки, но причину чаще всего покажут логи — поэтому мониторинг обычно закрывается связкой метрики (Prometheus/Mimir) + логи (ELK, см. [../elk/](../elk/)). + +### Как задеплоил бы мониторинг архитектурно для очень большого парка серверов (например, 300 000)? + +Grafana + Prometheus + Mimir + объектное хранилище (MinIO/S3) под блоки Mimir. Один Prometheus не рассчитан на такой масштаб (публичный ориентир — до ~1–2 млн активных series на инстанс, см. [../../legend/CAPACITY.md](../../legend/CAPACITY.md)), поэтому схема — множество Prometheus-инстансов, шардированных по группам серверов/сервисов (например, по датацентру или по команде), каждый scrape'ит свою часть парка и отправляет данные через `remote_write` в Mimir. Mimir горизонтально масштабируется (ingester/distributor/querier как отдельные компоненты) и хранит блоки в объектном хранилище — это снимает ограничение на объём долгосрочного хранения метрик с одного диска. Grafana обращается к Mimir как к единому PromQL-совместимому источнику, не зная о шардировании снизу. У себя в лабе я воспроизвёл этот же принцип в миниатюре — Prometheus → remote_write → Mimir → Grafana (см. [CASE.md](CASE.md)), просто с одним источником вместо тысяч, и в кластере — [../kubernetes/CASE.md](../kubernetes/CASE.md), где тот же стек развёрнут Helm-чартом `kube-prometheus-stack`. + +### У тебя есть 300 000 метрик от кастомных агентов в формате JSON — как будешь их собирать? + +Раз агенты сами по себе пушат данные и их формат (JSON) нельзя поменять, два реалистичных варианта: (1) pull-модель — написать текстовый файл-адаптер для `node_exporter` через его textfile collector: отдельный процесс парсит входящие JSON (`jq` или скрипт на Python) и периодически перезаписывает `.prom`-файл в формате метрик Prometheus, который `node_exporter` дальше просто отдаёт по scrape; (2) push-модель — если агенты сами инициируют отправку, а не ждут scrape, использовать Pushgateway: агент (или обвязка вокруг него) конвертирует JSON в формат Prometheus и пушит через HTTP API Pushgateway, откуда данные уже забирает Prometheus обычным scrape. Выбор между вариантами зависит от того, кто инициирует передачу — если агенты только отдают данные по запросу, то pull через textfile collector; если сами шлют данные без внешнего опроса — Pushgateway. + +### К тебе пришёл разработчик с жалобой на HTTP 400/500 — как будешь мониторить и почему? + +Начал бы с метрик — они быстрее покажут масштаб и динамику проблемы: в Grafana смотрю долю 4xx/5xx от общего числа запросов во времени (`rate` по статус-коду, если это есть в лейблах метрики — например, у nginx через `nginx-prometheus-exporter` или у самого приложения) — это отвечает на вопросы «когда началось», «это массово или единичный случай», «на каком именно сервисе». Дальше для выяснения точной причины конкретных ошибок — логи (ELK): по временному окну, где метрики показали всплеск, ищу в логах конкретные запросы с этим статус-кодом и смотрю стектрейс/сообщение об ошибке. То есть метрики — для обнаружения и оценки масштаба, логи — для диагностики конкретной причины; в стенде [../nginx/CASE.md](../nginx/CASE.md) я специально проверял, как выглядит 503 от `limit_req` в логах nginx и как это же отразилось бы на метрике доли ошибочных ответов. + +### Работал с Zabbix? Чем он отличается от Prometheus и что такое зонтичный мониторинг? + +Практического опыта с Zabbix у меня нет, но принцип понимаю: Zabbix — классическая push/агентская модель мониторинга с центральным сервером, который получает данные от агентов на хостах (или сам их опрашивает по SNMP/скриптам), и с готовым UI из коробки, включая triggers/actions для алертинга — исторически более «всё в одном», чем связка Prometheus+Grafana, которая собирается из отдельных модульных компонентов. Зонтичный мониторинг — верхнеуровневая система, агрегирующая сигналы (алерты/статусы) от нескольких нижележащих систем мониторинга разных команд/доменов в единую панель для дежурных/руководства — не подменяет специализированные системы мониторинга конкретных сервисов, а сводит их в общую картину состояния на уровне всей организации. + +### Что такое Monq? + +Российская платформа для зонтичного (umbrella) мониторинга и AIOps — агрегирует события/алерты из нижележащих систем мониторинга разных команд (включая Zabbix, Prometheus и другие источники) в единую панель, с возможностью писать сценарии обработки событий (корреляция, автоматические действия по алертам). Личного опыта работы с конкретно Monq у меня нет — знаю его как класс продукта (зонтичный мониторинг, см. вопрос про Zabbix выше) по описанию вакансии, а не по практике; на собеседовании честно обозначил бы это и попросил рассказать подробнее о том, как именно он используется в их процессах. + +### Что такое переменные в Grafana? + +Параметры дашборда, которые можно менять через выпадающий список сверху (например, `$instance`, `$job`, `$datacenter`), не редактируя сами запросы панелей — сами PromQL-запросы ссылаются на переменную (`up{instance="$instance"}`), и при смене значения в выпадающем списке весь дашборд пересчитывается под выбранный контекст. Это то, что позволяет держать один шаблонный дашборд вместо копии на каждый сервис/инстанс — прямое продолжение темы унификации метрик, которая уже разбиралась выше. diff --git a/stack/networking/CASE.md b/stack/networking/CASE.md new file mode 100644 index 0000000..f8aff72 --- /dev/null +++ b/stack/networking/CASE.md @@ -0,0 +1,68 @@ +# Кейс: сетевая база (практикум команд) + +Связь с легендой: [../../legend/LEGEND.md](../../legend/LEGEND.md) — раздел «Домашняя лаборатория / pet-проект». Сети как отдельная тема не входили ни в резюме, ни в рабочий стек явно — но это базовые знания, без которых не воспринимается ни один инфраструктурный собес (весь собес [VK Cloud](../../interview/VK%20Cloud.md) построен на сетевой базе). Здесь не docker-стенд, а практикум: реальные команды, прогнанные на своей машине, с записанным выводом. + +## Что нужно реально сделать + +Не разворачивать отдельную инфраструктуру, а вживую прогнать диагностические команды и зафиксировать, что именно они показывают — это то, что на собеседовании часто просят показать в терминале. + +### 1. DNS: резолвинг и разница authoritative/recursive + +```bash +dig vk.com # полный ответ: ANSWER SECTION, TTL, тип записи A +dig +trace vk.com # путь от корневых DNS-серверов до authoritative-сервера домена +nslookup vk.com +``` + +Рекурсивный резолвер (например, DNS провайдера или публичный 8.8.8.8) сам обходит цепочку: корневые сервера → TLD-сервера (.com) → authoritative-сервер зоны vk.com, и возвращает клиенту готовый ответ, кешируя его на своё TTL. Authoritative-сервер — тот, у кого хранится реальная зона домена и который выдаёт финальный, «истинный» ответ по этому домену. + +### 2. Traceroute — как получает IP промежуточных хостов + +```bash +traceroute 8.8.8.8 # Linux/macOS +tracert 8.8.8.8 # Windows +``` + +Traceroute шлёт пакеты с постепенно увеличивающимся TTL (1, 2, 3...). Каждый маршрутизатор на пути, получив пакет с TTL, уменьшает его на 1; когда TTL становится 0, маршрутизатор отбрасывает пакет и отправляет обратно ICMP Time Exceeded с указанием своего IP. Так по очереди «подсвечивается» каждый хоп на пути — сначала ближайший роутер (TTL=1 истекает у него), потом следующий (TTL=2) и так далее, пока пакет не дойдёт до цели. + +### 3. HTTP/S, TLS, методы + +```bash +curl -v https://vk.com # -v показывает весь handshake: TCP connect, TLS handshake, заголовки запроса/ответа +curl -I https://vk.com # только заголовки ответа +openssl s_client -connect vk.com:443 -brief # детали TLS-сертификата и версии протокола +``` + +Прогнать вручную запросы разными методами на тестовый сервис (например, `httpbin.org`): `curl -X GET/POST/PUT/DELETE/PATCH https://httpbin.org/anything` — увидеть, как сервис получает метод и тело запроса. + +### 4. Порты и процессы + +```bash +ss -tulpn # какие порты слушаются и каким процессом (Linux) +netstat -ano | findstr :80 # Windows-аналог +Test-NetConnection -ComputerName example.com -Port 443 # PowerShell: проверка доступности порта на удалённом хосте +nc -zv example.com 443 # аналог на Linux +``` + +### 5. Load average и сетевые утилиты диагностики + +```bash +uptime # load average за 1/5/15 минут +ping -c 4 8.8.8.8 +``` + +Load average — среднее число процессов, находящихся в состоянии "runnable" (выполняются или ждут CPU) либо в непрерываемом ожидании ввода-вывода (D-state), усреднённое за 1/5/15 минут. Число выше количества ядер CPU не всегда означает перегрузку процессора — часто это диск/сеть (процессы висят в D-state в ожидании I/O), поэтому load average нужно смотреть вместе с `top`/`vmstat`, а не изолированно. + +### 6. Прогон полного пути запроса + +Разобрать по шагам, что происходит при вводе `vk.com` в браузере и Enter — от DNS-резолвинга до отрисовки страницы (см. ответ в [QUESTIONS.md](QUESTIONS.md)) — и свериться с реальным выводом `curl -v` из пункта 3, где видны все те же этапы вживую (TCP connect, TLS handshake, HTTP-запрос/ответ). + +## Что это даёт в разговоре с интервьюером + +- Уверенное владение диагностическими утилитами (`dig`, `traceroute`, `curl -v`, `ss`) — не только знание теории, но и что конкретно смотреть в выводе команды. +- Понимание TCP/UDP, HTTP/TLS не абстрактно, а с привязкой к реальному выводу `curl -v`/`openssl s_client`. +- Способность разобрать сетевой путь запроса по шагам под живым вопросом на собеседовании. + +## Как это ложится в легенду + +Сетевая база — pet-проработка теории, не привязанная к конкретному эпизоду в компаниях (сети явно не выделены как отдельная строка стека ни в АО ТНИИС, ни в ОКБ СУХОЙ). На собеседовании это честно позиционируется как фундаментальные знания, которые нарабатывались параллельно с администрированием Linux-серверов (Astra/CentOS) в обеих ролях — там сетевая диагностика неизбежно возникала как часть повседневной работы, здесь она отдельно систематизирована и углублена. diff --git a/stack/networking/QUESTIONS.md b/stack/networking/QUESTIONS.md new file mode 100644 index 0000000..df1040b --- /dev/null +++ b/stack/networking/QUESTIONS.md @@ -0,0 +1,58 @@ +# Вопросы: сети + +Опираются на кейс: [CASE.md](CASE.md). Источник — почти целиком [VK Cloud.md](../../interview/VK%20Cloud.md), см. сводку в [../../interview/real-interviews.md](../../interview/real-interviews.md). + +### В чём разница авторитетного и рекурсивного DNS? + +Рекурсивный резолвер (обычно у провайдера или публичный, например 8.8.8.8) берёт на себя всю работу: получает запрос от клиента и сам обходит цепочку от корневых серверов до нужной зоны, кешируя результат. Авторитетный сервер — тот, у кого реально хранится зона конкретного домена; он не занимается рекурсией для чужих запросов, а просто отдаёт финальный, «истинный» ответ по своей зоне. Я это проверял через `dig +trace vk.com` — видно всю цепочку: root → `.com` TLD → авторитетный сервер зоны `vk.com`. + +### Как Traceroute получает IP промежуточных хостов? + +Отправляет пакеты с постепенно растущим TTL, начиная с 1. Каждый маршрутизатор на пути уменьшает TTL на 1; когда TTL достигает 0, маршрутизатор отбрасывает пакет и шлёт обратно ICMP Time Exceeded со своим IP-адресом в качестве отправителя. Так каждый следующий хоп «раскрывается» по очереди — TTL=1 покажет первый роутер, TTL=2 — второй и так далее, до момента, когда пакет наконец дойдёт до цели. + +### Что такое vlan и vxlan? + +VLAN (Virtual LAN) — логическое разделение одной физической L2-сети на несколько изолированных широковещательных доменов через тегирование кадров (802.1Q, VLAN ID в заголовке), без физического разделения проводов/свитчей. VXLAN — способ инкапсулировать L2-трафик внутрь UDP-пакетов поверх L3-сети, что снимает ограничение VLAN на 4096 идентификаторов (24-битный VNI даёт ~16 млн сегментов) и позволяет растягивать один логический L2-сегмент через маршрутизируемую сеть, в том числе между дата-центрами — характерно для облачных/контейнерных платформ, где нужно много изолированных виртуальных сетей поверх общей физической инфраструктуры. + +### Как считается load average в Linux? + +Это скользящее экспоненциально взвешенное среднее числа процессов в состоянии "runnable" (готовы выполняться или уже выполняются) плюс процессов в непрерываемом ожидании ввода-вывода (D-state), усреднённое за интервалы 1/5/15 минут. Важный нюанс: число выше количества ядер CPU не автоматически значит «процессор перегружен» — часто это диск или сеть (много процессов ждут I/O в D-state), поэтому load average нужно интерпретировать вместе с `top`/`vmstat`/`iostat`, а не как самостоятельный показатель нагрузки CPU. + +### Что такое curl -v? Чем отличается от wget? + +`curl -v` — verbose-режим curl, показывает весь процесс запроса пошагово: разрешение DNS, установку TCP-соединения, TLS handshake (если HTTPS), отправленные и полученные заголовки. `wget` — тоже инструмент для HTTP(S)/FTP-запросов из терминала, но исторически ориентирован на скачивание файлов (умеет рекурсивно скачивать по ссылкам, докачивать прерванную закачку из коробки), тогда как `curl` изначально заточен под гибкую работу с самим протоколом (произвольные методы, заголовки, тело запроса) и чаще используется для отладки API и HTTP-взаимодействия, а не просто для сохранения файла на диск. + +### Как работают HTTP/S и TLS/SSL, зачем нужны? Методы HTTP? + +HTTP — прикладной протокол запрос-ответ поверх TCP: клиент отправляет метод + путь + заголовки (+ тело), сервер отвечает статус-кодом + заголовками (+ телом). HTTPS — тот же HTTP, но поверх TLS-соединения: сначала происходит TLS handshake (обмен сертификатами, согласование шифров, выработка сессионного ключа), и уже внутри зашифрованного канала идёт обычный HTTP-обмен — это защищает от перехвата и подмены данных по пути. Методы: GET (получить ресурс, без побочных эффектов), POST (создать/отправить данные), PUT (полностью заменить ресурс), PATCH (частично обновить), DELETE (удалить). Проверял вживую через `curl -v https://vk.com` — там виден отдельно TCP connect, отдельно TLS handshake, отдельно сам HTTP-запрос/ответ. + +### Что такое TCP/UDP и как устанавливается соединение? + +TCP — соединение-ориентированный протокол с гарантией доставки и порядка байт: соединение устанавливается через three-way handshake (SYN → SYN-ACK → ACK), дальше идёт передача с подтверждениями (ACK) и повторной отправкой потерянных сегментов. UDP — протокол без установления соединения и без гарантий: пакеты (датаграммы) отправляются «как есть», без подтверждения доставки и порядка — используется там, где важна скорость больше надёжности (стриминг, DNS-запросы, некоторые виды мониторинга). + +### Что такое MTU? + +Maximum Transmission Unit — максимальный размер пакета (в байтах), который может быть передан за один раз без фрагментации на конкретном сетевом интерфейсе/канале. Стандартный Ethernet MTU — 1500 байт. Если пакет больше MTU канала, он либо фрагментируется (IPv4), либо отбрасывается с ICMP «Fragmentation needed» при выставленном флаге DF (актуально, например, при туннелировании — VPN/VXLAN добавляют заголовки поверх, съедая часть MTU, и это частая причина странных обрывов соединений именно на больших пакетах). + +### Что такое пакет, кадр, фрейм? + +Кадр (frame) — единица данных на канальном уровне (L2), с MAC-адресами отправителя/получателя (Ethernet-кадр). Пакет (packet) — единица данных на сетевом уровне (L3), с IP-адресами, инкапсулируется внутрь кадра при передаче по конкретному каналу. Термин «фрейм» обычно синоним кадра (тот же L2). Ha практике: один и тот же IP-пакет при прохождении через разные сетевые сегменты каждый раз заново упаковывается в новый кадр L2 (например, Ethernet-кадр в локальной сети, PPP-кадр на другом участке), а сам пакет остаётся неизменным до конечного получателя. + +### На каком протоколе работает ping и на каком уровне модели OSI? + +Ping использует ICMP (Internet Control Message Protocol) — протокол сетевого уровня (L3 в модели OSI), формально «поверх» IP, но не транспортный протокол в привычном смысле (нет портов, как у TCP/UDP). Отправляет Echo Request и ждёт Echo Reply от целевого хоста. + +### Какая есть динамическая маршрутизация, что это? + +Динамическая маршрутизация — протоколы, по которым маршрутизаторы автоматически обмениваются информацией о доступных сетях и строят/обновляют таблицы маршрутизации без ручной настройки каждого маршрута. Основные: OSPF и IS-IS (link-state протоколы внутри автономной системы, строят полную карту топологии), BGP (протокол между автономными системами — то, на чём фактически держится маршрутизация в интернете), RIP (более старый, distance-vector, для небольших сетей). + +### Ввожу vk.com и нажимаю Enter — опиши весь путь до открытия страницы + +1. Браузер проверяет DNS-кеш; если пусто — идёт запрос к рекурсивному резолверу, который резолвит домен в IP через цепочку root → TLD → authoritative-сервер. +2. Браузер устанавливает TCP-соединение с полученным IP на порт 443 (three-way handshake: SYN → SYN-ACK → ACK). +3. Поверх TCP — TLS handshake: обмен сертификатами, проверка цепочки доверия, согласование шифров, выработка сессионного ключа. +4. Внутри зашифрованного канала браузер отправляет HTTP-запрос (GET /), сервер отвечает статус-кодом и телом (HTML). +5. Браузер парсит HTML, находит ссылки на дополнительные ресурсы (CSS, JS, картинки) и повторяет для них аналогичный цикл (возможно, переиспользуя уже установленное TCP/TLS-соединение через keep-alive/HTTP2 multiplexing). +6. Браузер рендерит DOM, применяет CSS, выполняет JS — страница становится видимой и интерактивной. + +Каждый из этих этапов я лично видел раздельно через `curl -v` — команда явно печатает моменты TCP connect, TLS handshake и HTTP-обмена по отдельности. diff --git a/stack/nginx/CASE.md b/stack/nginx/CASE.md new file mode 100644 index 0000000..451cb66 --- /dev/null +++ b/stack/nginx/CASE.md @@ -0,0 +1,111 @@ +# Кейс: Nginx как reverse proxy и балансировщик нагрузки + +Связь с легендой: [../../legend/LEGEND.md](../../legend/LEGEND.md) — раздел «АО ТНИИС», конфигурационные файлы Nginx для маршрутизации и балансировки нагрузки перед сервисами. + +## Что нужно реально сделать (домашний стенд) + +Поднять два бэкенд-сервиса и один Nginx перед ними, который распределяет нагрузку и отдаёт статус бэкендов, чтобы своими руками увидеть балансировку, health-check-подобное поведение и базовую защиту (rate limiting). + +### 1. Структура + +``` +nginx-lab/ +├── docker-compose.yml +├── nginx/ +│ └── nginx.conf +└── backend/ + └── index.html +``` + +### 2. docker-compose.yml + +```yaml +version: "3.8" + +services: + backend1: + image: nginx:alpine + container_name: backend1 + volumes: + - ./backend/index.html:/usr/share/nginx/html/index.html + environment: + - INSTANCE=backend1 + + backend2: + image: nginx:alpine + container_name: backend2 + volumes: + - ./backend/index.html:/usr/share/nginx/html/index.html + environment: + - INSTANCE=backend2 + + proxy: + image: nginx:alpine + container_name: proxy + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro + ports: + - "8080:80" + depends_on: + - backend1 + - backend2 +``` + +### 3. nginx/nginx.conf + +```nginx +events { + worker_connections 1024; +} + +http { + limit_req_zone $binary_remote_addr zone=req_limit:10m rate=5r/s; + + upstream backend_pool { + least_conn; + server backend1:80 max_fails=3 fail_timeout=10s; + server backend2:80 max_fails=3 fail_timeout=10s; + } + + server { + listen 80; + + location / { + limit_req zone=req_limit burst=10 nodelay; + + proxy_pass http://backend_pool; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /health { + return 200 'ok'; + add_header Content-Type text/plain; + } + } +} +``` + +### 4. Шаги воспроизведения + +1. `docker compose up -d`. +2. `curl http://localhost:8080/` несколько раз подряд — убедиться, что ответы приходят от backend1 и backend2 попеременно (балансировка `least_conn`; чтобы увидеть разницу в теле ответа, положить в `index.html` разные значения через `INSTANCE` или просто проверить по логам `docker compose logs backend1 backend2`, какой контейнер получил запрос). +3. Остановить один бэкенд (`docker compose stop backend1`) и убедиться, что Nginx продолжает отдавать 200, перенаправляя весь трафик на backend2 — практика `max_fails`/`fail_timeout` в деле. +4. Прогнать быстрый цикл запросов (`for i in $(seq 1 20); do curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/; done`) и увидеть `503` от `limit_req`, когда запросы превышают `rate=5r/s` — понять, как работает rate limiting на практике. +5. Проверить `curl http://localhost:8080/health` — отдельный location без проксирования, как обычно делают health-check эндпоинт. + +### 5. Что это даёт в разговоре с интервьюером + +- Понимание `upstream`-блока и алгоритмов балансировки (`round-robin` по умолчанию, `least_conn`, `ip_hash`). +- Понимание, зачем нужны `proxy_set_header` (чтобы бэкенд видел реальный IP и Host клиента, а не адрес самого Nginx). +- Практика rate limiting (`limit_req_zone`/`limit_req`) как базовой защиты от перегрузки/abuse. +- Понимание `max_fails`/`fail_timeout` как механизма пассивного health-check upstream-серверов. + +## Нагрузка и цифры + +Замерить пропускную способность своего стенда: `hey -n 10000 -c 50 http://localhost:8080/` (или `wrk -t2 -c50 -d30s http://localhost:8080/`) — записать полученный RPS и задержку p50/p99. Сравнение с публичными ориентирами по nginx (десятки тысяч RPS на статике, тысячи на проксировании — сильно зависит от конфигурации) и таблица для собственных замеров — в [../../legend/CAPACITY.md](../../legend/CAPACITY.md). + +## Как это ложится в легенду + +В реальной работе (АО ТНИИС) — конфиги Nginx для маршрутизации и балансировки перед сервисами, в том числе перед компонентами мониторинга. Домашний кейс воспроизводит тот же принцип на двух простых бэкендах вместо реального внутреннего сервиса. diff --git a/stack/nginx/QUESTIONS.md b/stack/nginx/QUESTIONS.md new file mode 100644 index 0000000..a62a892 --- /dev/null +++ b/stack/nginx/QUESTIONS.md @@ -0,0 +1,39 @@ +# Вопросы: Nginx + +Опираются на кейс: [CASE.md](CASE.md). + +### Чем reverse proxy отличается от обычного proxy? + +Обычный (forward) proxy работает на стороне клиента — скрывает клиента от сервера. Reverse proxy стоит перед сервером(ами) и скрывает их от клиента — клиент обращается к одному адресу, а Nginx уже сам решает, на какой бэкенд отправить запрос. В своём кейсе я как раз настроил Nginx как reverse proxy перед двумя бэкендами через `upstream` + `proxy_pass`. + +### Какие алгоритмы балансировки нагрузки есть в Nginx и в чём разница? + +По умолчанию — round-robin (запросы по очереди). `least_conn` — на сервер с наименьшим числом активных соединений (я использовал именно его в лабе, чтобы не заливать один инстанс, если он медленнее отвечает). `ip_hash` — привязывает клиента к одному и тому же серверу по хешу IP, что полезно для sticky-сессий, если приложение не умеет в shared-сессии. + +### Зачем нужны proxy_set_header в конфиге? + +По умолчанию бэкенд видит IP и Host самого Nginx, а не реального клиента, потому что запрос проксируется. `proxy_set_header X-Real-IP $remote_addr` и `X-Forwarded-For` передают реальный IP клиента дальше, а `Host $host` сохраняет исходный заголовок Host — это важно, если бэкенд как-то зависит от домена или логирует IP клиентов. + +### Как Nginx понимает, что бэкенд «упал», и что делает в этом случае? + +Через пассивные health-check параметры в `upstream`: `max_fails` — сколько неудачных попыток допустимо, `fail_timeout` — на сколько сервер помечается недоступным после превышения max_fails. В моём кейсе я гасил один из бэкендов и видел, что Nginx перестаёт направлять на него трафик и весь поток идёт на оставшийся, без ошибок для клиента. + +### Что такое rate limiting и как он настроен в Nginx? + +`limit_req_zone` объявляет зону в разделяемой памяти, ключ (обычно `$binary_remote_addr`, то есть IP клиента) и допустимую скорость запросов (`rate=5r/s` в моём случае). `limit_req` в конкретном location применяет эту зону, а `burst` разрешает кратковременный всплеск сверх лимита (запросы ставятся в очередь) — `nodelay` убирает искусственную задержку для burst-запросов, но всё равно возвращает 503 при превышении burst. Я специально прогонял 20 запросов подряд и видел 503 после исчерпания лимита. + +### В чём разница между location / и отдельным location /health? + +`location` определяет, как обрабатывается конкретный путь URL. Я вынес `/health` в отдельный блок, который отвечает статикой (`return 200`) без проксирования на бэкенд — так делают health-check эндпоинты, чтобы проверка живости самого Nginx/прокси-слоя не зависела от состояния бэкендов. + +### Чем отличается $host от $http_host и от server_name? + +`server_name` — значение в конфиге, по которому Nginx выбирает нужный `server`-блок для запроса. `$host` — переменная времени выполнения: значение из заголовка Host запроса (без порта), либо server_name, если заголовка нет. `$http_host` — заголовок Host как есть, включая порт, если он был передан клиентом. На практике для проксирования обычно используют `$host`, чтобы не тащить порт туда, где он не нужен. + +### Как бы вы отдавали статику через Nginx, а не проксировали на бэкенд? + +Через `location` с директивой `root`/`alias`, указывающей на директорию со статикой, вместо `proxy_pass` — тогда Nginx сам отдаёт файлы, не дёргая бэкенд, что быстрее и снимает нагрузку с приложения. + +### Что произойдёт, если в upstream все сервера окажутся недоступны? + +Nginx вернёт ошибку (обычно 502 Bad Gateway или 504 Gateway Timeout, в зависимости от того, где именно произошёл сбой — на этапе соединения или ожидания ответа) — в моём кейсе, если бы я остановил оба backend1 и backend2, curl к proxy начал бы получать 502. diff --git a/stack/testing/CASE.md b/stack/testing/CASE.md new file mode 100644 index 0000000..a619e86 --- /dev/null +++ b/stack/testing/CASE.md @@ -0,0 +1,21 @@ +# Кейс: тестирование (Selenium, тест-дизайн) + +> Заготовка. Статус: TODO — требует полной проработки по правилам [../../AI_GUIDELINES.md](../../AI_GUIDELINES.md). + +Связь с легендой: [../../legend/LEGEND.md](../../legend/LEGEND.md) — раздел «ОКБ СУХОЙ», тестовые сценарии, автоматизированные модульные и интеграционные тесты, работа на CentOS-стендах. + +## Что нужно проработать + +- [ ] Написать простой Selenium-тест (Python + selenium + pytest) на любой публичный/локальный тестовый сайт (например, свой же локальный сервис из [../nginx/CASE.md](../nginx/CASE.md) с простой HTML-страницей). +- [ ] Структурировать тест по Page Object Model — паттерн, который часто спрашивают отдельно. +- [ ] Написать тест-кейсы (тест-дизайн) на бумаге/в markdown для гипотетической фичи — с шагами, ожидаемым результатом, приоритетом — чтобы показать не только автоматизацию, но и ручной тест-дизайн, упомянутый в резюме. +- [ ] Разобрать разницу unit / integration / e2e тестов применительно к своему опыту (модульные и интеграционные тесты в резюме). +- [ ] Оформить пример баг-репорта (шаги воспроизведения, ожидаемое/фактическое поведение, окружение) — это то, что реально делал инженер-тестировщик. + +## QUESTIONS.md + +- [ ] Составить вопросы: что такое Page Object Model и зачем он нужен, разница smoke/regression тестирования, как приоритизировать тесты при ограниченном времени, что такое flaky test и как с ним бороться, разница между verification и validation. + +## Как это ложится в легенду + +Роль в ОКБ СУХОЙ — тестировщик встроенного ПО. Кейс должен показать связку ручного тест-дизайна и автоматизации, а не только Selenium-скрипт в отрыве от контекста. diff --git a/stack/vault/CASE.md b/stack/vault/CASE.md new file mode 100644 index 0000000..95751b9 --- /dev/null +++ b/stack/vault/CASE.md @@ -0,0 +1,67 @@ +# Кейс: HashiCorp Vault (dev-режим + интеграция с CI) + +Связь с легендой: [../../legend/LEGEND.md](../../legend/LEGEND.md) — раздел «Домашняя лаборатория / pet-проект». Vault спрашивали в Альфа-Банке («как работает Hashicorp Vault: как вкладываются секреты, синхронизация с CI/CD») — в рабочем стеке компаний из резюме секреты хранятся через `ansible-vault` (см. [../ansible/QUESTIONS.md](../ansible/QUESTIONS.md)), Vault — отдельный честный pet-кейс поверх этого опыта. + +## Что нужно реально сделать (домашний стенд) + +Vault в dev-режиме (single binary, in-memory storage — не для прода, но для понимания механики более чем достаточно), положить секрет через KV-движок, прочитать его через CLI и через HTTP API, завести простую политику доступа, и на уровне паттерна разобрать, как секрет из Vault попадает в CI/CD-пайплайн. + +### 1. Запуск + +```bash +docker run --cap-add=IPC_LOCK -d --name vault-lab \ + -p 8200:8200 \ + -e 'VAULT_DEV_ROOT_TOKEN_ID=lab-root-token' \ + hashicorp/vault server -dev +``` + +Dev-режим сразу распечатан (unsealed), с готовым root-токеном — специально упрощённый режим для локальной практики, в проде Vault разворачивается с реальным storage backend (Consul/Raft) и требует ручного unseal через Shamir's Secret Sharing (несколько ключей, из которых нужен кворум для распечатывания). + +### 2. Положить и прочитать секрет через KV + +```bash +export VAULT_ADDR='http://127.0.0.1:8200' +export VAULT_TOKEN='lab-root-token' + +vault kv put secret/ci/db-credentials username=labuser password=labpass +vault kv get secret/ci/db-credentials +vault kv get -field=password secret/ci/db-credentials +``` + +### 3. То же самое через HTTP API (то, что реально дёргает CI-раннер) + +```bash +curl -s -H "X-Vault-Token: lab-root-token" \ + http://127.0.0.1:8200/v1/secret/data/ci/db-credentials | jq .data.data +``` + +### 4. Политика доступа (принцип наименьших привилегий) + +```hcl +# ci-readonly-policy.hcl +path "secret/data/ci/*" { + capabilities = ["read"] +} +``` + +```bash +vault policy write ci-readonly ci-readonly-policy.hcl +vault token create -policy="ci-readonly" -ttl=1h +``` + +Полученным ограниченным токеном (не root) повторить `vault kv get` — сработает (есть право read); попробовать `vault kv put` этим же токеном — должно быть отказано (Permission denied), так как в политике только `read`. Это ядро модели Vault: не «все или ничего», а точечные политики на конкретные пути. + +### 5. Паттерн интеграции с CI/CD (концептуально, без реального раннера) + +Типичная схема: CI-раннер (GitLab CI/TeamCity) на старте job аутентифицируется в Vault не статическим токеном в переменных окружения, а через auth-метод (например, AppRole или JWT/OIDC-аутентификация от самого CI, если поддерживается) — получает короткоживущий токен с ограниченной политикой → читает нужные секреты через API → использует их в рамках job → токен истекает сам по TTL. Ключевое отличие от простого хранения секрета в CI-переменных: секрет не лежит статично в конфигурации пайплайна, доступ временный и аудируемый (Vault логирует каждое обращение). + +## Что это даёт в разговоре с интервьюером + +- Практическое понимание KV secrets engine — как секрет реально «вкладывается» и читается, а не абстрактно. +- Понимание политик доступа Vault (HCL, path-based capabilities) на реальном примере — не просто «там есть права», а конкретный сценарий read-only токена. +- Понимание разницы между dev-режимом (для лабы) и продовым Vault (unseal, storage backend, HA) — честная граница того, что реально прогнано. +- Осмысленное представление о паттерне интеграции с CI/CD через short-lived токены, а не статичные секреты в переменных. + +## Как это ложится в легенду + +Vault — pet-кейс, не приписанный к опыту в компаниях. В резюме и легенде реальный опыт хранения секретов зафиксирован через `ansible-vault` в АО ТНИИС; Vault добавляется отдельно как самостоятельно проработанная технология в навыках, без утверждения, что она использовалась на рабочем месте. diff --git a/stack/vault/QUESTIONS.md b/stack/vault/QUESTIONS.md new file mode 100644 index 0000000..6db9680 --- /dev/null +++ b/stack/vault/QUESTIONS.md @@ -0,0 +1,19 @@ +# Вопросы: HashiCorp Vault + +Опираются на кейс: [CASE.md](CASE.md). Источник — [Alfa-Bank.md](../../interview/Alfa-Bank.md). + +### Как работает Hashicorp Vault: как вкладываются секреты? + +Секреты хранятся через secrets engine — самый базовый и распространённый вариант KV (key-value): секрет кладётся по конкретному пути (`vault kv put secret/ci/db-credentials username=... password=...`) и читается тем же путём (`vault kv get`). Под капотом Vault шифрует данные перед записью в storage backend (в моей лабе — in-memory dev-режим, в проде обычно Raft или Consul) собственным мастер-ключом, который в продовом режиме получается через unseal-процесс (Shamir's Secret Sharing — несколько частей ключа, кворум которых нужен, чтобы распечатать хранилище после старта). Помимо статичного KV есть и динамические secrets engines (например, для БД) — Vault может сам генерировать короткоживущие учётные данные на лету, но этого я на своей лабе не поднимал, только статичный KV. + +### Как наполняется файл секрета? + +Секрет не «файл» в файловой системе в привычном смысле — это запись в хранилище Vault по определённому пути, доступ к которой идёт через CLI (`vault kv put/get`) или HTTP API. Если приложению/пайплайну нужен именно файл (например, `.env` или конфиг с паролем), это обычно результат отдельного шага: агент/раннер запрашивает секрет через API и материализует его во временный файл на своей стороне непосредственно перед использованием, не храня его в репозитории или в постоянном хранилище. + +### Как идёт синхронизация с CI/CD? + +CI-раннер аутентифицируется в Vault (в идеале — не статичным root-токеном, а через auth-метод вроде AppRole или через встроенную OIDC/JWT-аутентификацию от самой CI-системы, если она поддерживается), получает короткоживущий токен с ограниченной политикой доступа, читает нужные секреты через API в рамках выполнения job, и токен истекает сам по TTL после использования. Я на своей лабе прогнал часть этой схемы вручную: завёл политику `ci-readonly` с правом только `read` на путь `secret/data/ci/*`, выписал по ней ограниченный токен и убедился, что им можно читать секрет, но нельзя его перезаписать (`vault kv put` тем же токеном отдаёт Permission denied) — сам CI-раннер (GitLab CI/TeamCity) я в эту схему не встраивал, это на уровне понимания паттерна. + +### Чем это отличается от того, что секрет просто лежит в переменных CI? + +Секрет в переменных CI-системы статичен: один раз задан — лежит вечно, доступен всем job без разбора, изменения/ротация требуют ручного вмешательства, и нет отдельного лога, кто и когда именно его читал. Vault даёт: точечные политики доступа (кто и к какому пути может обращаться), короткоживущие токены вместо постоянных секретов, аудит-лог обращений, и возможность динамически генерировать секреты вместо хранения статичных значений.