Перейти к основному содержимому

DevOps Ida Project - Senior от 250+ тыс. / Реальное собеседование

· 110 мин. чтения

Сегодня мы разберем живое техническое собеседование DevOps-инженера, в котором интервьюеры последовательно проверяют опыт кандидата в Linux, Ansible, CI/CD, мониторинге и контейнеризации, параллельно рассказывая о специфике проекта и роли. Кандидат демонстрирует уверенный практический бэкграунд, честно обозначает пробелы и задает зрелые вопросы о процессах, ответственности и архитектуре, что позволяет увидеть не только его уровень, но и реальный формат работы в команде.

Вопрос 1. Какой уровень владения Linux, Ansible и сетевыми технологиями ожидается для работы с виртуальными машинами без контейнеров?

Таймкод: 00:02:18

Ответ собеседника: неполный. Уверенно работает с Linux и Ansible, есть опыт написания playbook'ов для деплоя и конфигурации серверов. В сетях ориентируется на уровне базовой настройки и отладки, без глубокого погружения в маршрутизацию (BGP, OSPF и т.п.).

Правильный ответ:

Для работы с неконтейнерной инфраструктурой на виртуальных машинах ожидается уверенный, практический уровень владения Linux, Ansible и сетевыми технологиями, достаточный для самостоятельного проектирования, развертывания, отладки и поддержки продакшн-систем. Важна не только способность "пользоваться", но и понимание того, как все устроено под капотом.

Основные ожидания по каждому направлению:

  1. Уровень Linux
  • Уверенное администрирование:
    • Понимание архитектуры: процессы, systemd, сигналы, users/groups, permissions (chmod/chown/setuid/umask), cgroups, namespaces (как основа контейнеризации, даже если сейчас без контейнеров).
    • Работа с файловыми системами:
      • Разметка дисков (fdisk/parted), LVM, RAID.
      • Монтаж, авто-монтаж (fstab), работа с ext4/xfs/btrfs (базово).
    • Управление сервисами:
      • systemctl: запуск/остановка, enable/disable, просмотр логов через journalctl.
    • Пользователи и безопасность:
      • sudo, ssh-ключи, базовые политики, ограничение доступа, fail2ban, firewall (firewalld/iptables/nftables).
    • Пакетные менеджеры:
      • apt/yum/dnf/zypper: установка, обновления, фиксация версий, работа с репозиториями.
  • Диагностика и troubleshooting:
    • Анализ загрузки: top/htop, ps, vmstat, iostat, sar.
    • Работа с логами: journalctl, /var/log (syslog, nginx, app-логи).
    • Анализ проблем CPU/RAM/IO/диска/места, поиск "узких мест".
  • Автоматизация и скрипты:
    • Уверенный bash: циклы, условия, pipes, xargs, awk/sed/grep для обработки логов и файлов.
    • Написание утилит/health-check'ов, интеграция с мониторингами.

Пример: простой health-check для Go-сервиса и его systemd unit:

#!/usr/bin/env bash
set -e

URL="http://localhost:8080/health"
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$URL")

if [ "$STATUS" != "200" ]; then
echo "Service unhealthy, status: $STATUS"
exit 1
fi

echo "OK"
[Unit]
Description=My Go Service
After=network.target

[Service]
ExecStart=/usr/local/bin/my-service
Restart=always
RestartSec=5
User=app
Group=app
Environment=ENV=prod
ExecStartPre=/usr/local/bin/my-service-migrate

[Install]
WantedBy=multi-user.target
  1. Уровень Ansible

Ожидание: умение не просто писать плейбуки “на коленке”, а строить поддерживаемую инфраструктуру.

Ключевые моменты:

  • Структура:
    • Использование roles, inventory (в т.ч. group_vars/host_vars), шаблонов.
    • Разделение по окружениям: dev/stage/prod.
  • Практика:
    • Создание idempotent задач (повторный запуск не ломает среду).
    • Работа с handlers, notify, when, tags, templates (Jinja2).
    • Работа с секретами: Ansible Vault.
  • Интеграция с CI/CD:
    • Возможность использовать Ansible для деплоя Go-сервисов: выкладка бинаря, шаблоны конфигов, перезапуск systemd-сервиса.
  • Ошибки и отладка:
    • ansible-playbook --check, --diff, ограниченный запуск по tags/hosts.

Пример: базовый деплой Go-сервиса с Ansible:

- hosts: app_servers
become: yes
vars:
service_name: my-go-service
binary_url: "https://example.com/builds/my-go-service-linux-amd64"
dest_path: "/usr/local/bin/my-go-service"
tasks:
- name: Ensure app user exists
user:
name: app
shell: /usr/sbin/nologin

- name: Download binary
get_url:
url: "{{ binary_url }}"
dest: "{{ dest_path }}"
mode: '0755'
notify: Restart service

- name: Deploy systemd unit
template:
src: my-go-service.service.j2
dest: /etc/systemd/system/my-go-service.service
notify: Reload systemd and restart

handlers:
- name: Reload systemd and restart
block:
- name: Reload systemd
command: systemctl daemon-reload
- name: Restart service
service:
name: my-go-service
state: restarted

- name: Restart service
service:
name: my-go-service
state: restarted
  1. Уровень сетевых технологий

Даже без глубокого BGP/OSPF, для продакшн-инфраструктуры требуется уверенное понимание сетей на уровне, позволяющем диагностировать реальные проблемы.

Ожидания:

  • Базовая сетевая модель:
    • OSI/TCP-IP, различие L2/L3, ARP, MAC/IP, порты, TCP/UDP.
    • CIDR, подсети, маски, шлюзы, приватные/публичные сети.
  • Практика на серверах:
    • Настройка интерфейсов, статических маршрутов.
    • DNS: /etc/resolv.conf, dig/nslookup, понимание кеширования и проблем с DNS.
    • Firewall:
      • Базовая настройка iptables/nftables/firewalld или security groups (в облаках).
  • Диагностика:
    • Использование ping, traceroute, mtr, curl, nc (netcat), ss/netstat, tcpdump.
    • Определение, где проблема: DNS, маршрутизация, firewall, приложение, TLS.
  • Для Go-разработчика:
    • Понимание, как приложение работает в сети:
      • bind на 0.0.0.0 vs 127.0.0.1,
      • таймауты, пул коннекций,
      • корректная обработка ошибок сети, retry/backoff.

Пример: корректная настройка HTTP-клиента в Go:

client := &http.Client{
Timeout: 5 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
},
}
resp, err := client.Get("http://internal-api:8080/data")
if err != nil {
// логирование + retry/метрики
}
defer resp.Body.Close()
  1. Интегральное ожидание
  • Уметь:
    • Самостоятельно поднять и сопровождать Go-сервис на VM:
      • Развернуть систему.
      • Настроить сеть и доступ.
      • Настроить systemd unit и логирование.
      • Описать всё в Ansible так, чтобы деплой был повторяемым и предсказуемым.
    • Разобраться в инциденте:
      • Сервис не отвечает: проверить порт, firewall, DNS, логи сервиса, системные ресурсы.
      • Высокая латентность: проверить сеть (mtr, ping), CPU/IO, пул коннекций, базу.

Такой уровень владения позволяет не зависеть от администраторов на каждом шаге и эффективно поддерживать продакшн-окружения на виртуальных машинах без контейнеризации.

Вопрос 2. Каков практический опыт работы с базами данных, такими как PostgreSQL и ClickHouse, в части использования из кода, проектирования, настройки и оптимизации?

Таймкод: 00:03:16

Ответ собеседника: неполный. Подтверждает опыт работы с PostgreSQL и другой СУБД, упоминает Cassandra, но не раскрывает аспекты администрирования, настройки, проектирования схем и оптимизации запросов.

Правильный ответ:

Опыт, который ценится в работе с PostgreSQL, ClickHouse (и, дополнительно, распределёнными хранилищами вроде Cassandra), включает не только умение “подключиться из Go и сделать запрос”, а глубокое понимание:

  • как спроектировать схему под реальные нагрузки,
  • как пишутся эффективные запросы,
  • как анализировать и устранять проблемы производительности,
  • как учитывать особенности конкретной СУБД в архитектуре приложения.

Важно уметь объяснить решения с точки зрения консистентности, масштабирования, отказоустойчивости и профиля нагрузки.

Основные аспекты по системам:

  1. PostgreSQL

Ключевые компетенции:

  • Проектирование схемы:

    • Нормализация (3НФ и осознанные денормализации).
    • Выбор типов данных (numeric vs bigint, jsonb, enum, timestamp with time zone).
    • Ограничения: PRIMARY KEY, UNIQUE, FOREIGN KEY, CHECK, NOT NULL.
    • Индексы:
      • B-tree, partial indexes, composite indexes.
      • Когда нужны GIN/GiST (поиск по jsonb, full-text search).
      • Понимание влияния индексов на вставки/обновления.
  • Работа с транзакциями:

    • ACID.
    • Уровни изоляции (READ COMMITTED, REPEATABLE READ, SERIALIZABLE) и их влияние на гонки (lost update, phantom read).
    • Правильное использование транзакций из Go-кода:
      • не держать транзакции дольше, чем нужно;
      • избегать обращений во внешние сервисы внутри транзакции.
  • Оптимизация запросов:

    • Использование EXPLAIN (ANALYZE, BUFFERS) для анализа планов выполнения.
    • Типичные проблемы:
      • seq scan vs index scan,
      • неправильные индексы,
      • IN/JOIN на большие наборы,
      • N+1 запросы.
    • Оптимизация heavy-запросов, разбор блокировок (pg_locks, deadlocks).
  • Администрирование на прикладном уровне:

    • Настройка подключений: pgbouncer, max_connections, пул соединений.
    • Простое шардирование или partitioning (range/hash partitioning).
    • Базовое понимание репликации (streaming replication, hot standby) и чтения с реплик.

Примеры SQL для PostgreSQL:

Пример осмысленной схемы и индексации:

CREATE TABLE orders (
id bigserial PRIMARY KEY,
user_id bigint NOT NULL,
status text NOT NULL,
total_amount numeric(12, 2) NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);

-- Индекс для выборок по пользователю и дате
CREATE INDEX idx_orders_user_created_at
ON orders (user_id, created_at DESC);

-- Частичный индекс для часто запрашиваемого статуса
CREATE INDEX idx_orders_status_active
ON orders (status)
WHERE status = 'active';

Пример анализа запроса:

EXPLAIN (ANALYZE, BUFFERS)
SELECT *
FROM orders
WHERE user_id = 123
ORDER BY created_at DESC
LIMIT 50;

Из Go: корректная работа с пулом соединений и контекстами:

db, err := sql.Open("pgx", os.Getenv("PG_DSN"))
if err != nil {
log.Fatal(err)
}
db.SetMaxOpenConns(50)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(30 * time.Minute)

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

rows, err := db.QueryContext(ctx,
`SELECT id, status, total_amount FROM orders WHERE user_id = $1 LIMIT 50`, 123)
if err != nil {
// обработка ошибки, метрики
}
defer rows.Close()

Фокус: понимание, как настройки пула и структура запросов влияют на латентность и нагрузку.

  1. ClickHouse

ClickHouse — столбцовая аналитическая СУБД, с другими приоритетами, чем PostgreSQL. Ожидается понимание, когда и зачем его использовать.

Ключевые моменты:

  • Понимание профиля:

    • Заточен под:
      • большие объемы данных (сотни млн/млрд строк),
      • аналитические запросы (агрегации, отчёты),
      • append-only / event-based модели.
    • Не под частые point-update и транзакционные сценарии.
  • Проектирование таблиц:

    • MergeTree-семейство:
      • выбор ORDER BY (primary key) под частые запросы.
      • PARTITION BY для логического разбиения (например, по дате).
    • Компромисс: скорость записи vs скорость выборок.

Пример схемы логов в ClickHouse:

CREATE TABLE events (
event_date Date,
event_time DateTime,
user_id UInt64,
event_type LowCardinality(String),
meta String
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id, event_type);
  • Оптимизация:

    • Понимание, как работает primary key и skipping indexes.
    • Использование WHERE и PREWHERE.
    • Контроль объёма данных, merge-операций, TTL.
    • Анализ запросов через system.query_log, EXPLAIN.
  • Интеграция с Go:

    • Использование официального драйвера или clickhouse-go.
    • Батчевые вставки для высокой пропускной способности.
conn, err := clickhouse.Open(&clickhouse.Options{
Addr: []string{"clickhouse:9000"},
Auth: clickhouse.Auth{
Database: "default",
Username: "default",
Password: "",
},
})
if err != nil {
log.Fatal(err)
}

batch, err := conn.PrepareBatch(context.Background(),
"INSERT INTO events (event_date, event_time, user_id, event_type, meta)")
if err != nil {
log.Fatal(err)
}

for _, e := range events {
if err := batch.Append(e.Date, e.Time, e.UserID, e.Type, e.Meta); err != nil {
log.Fatal(err)
}
}

if err := batch.Send(); err != nil {
log.Fatal(err)
}
  1. Cassandra и распределённые хранилища (если фигурируют)

Если в опыте упоминается Cassandra или аналогичные NoSQL-системы, важно:

  • Понимание модели:
    • AP-система по CAP (availability и partition tolerance важнее строгой консистентности).
    • Denormalized, query-based моделирование:
      • схема строится от запросов: под каждый ключевой паттерн — своя таблица.
  • Consistency levels (ONE, QUORUM, ALL) и их влияние на задержки и гарантию чтения/записи.
  • Partition key и clustering key:
    • предотвращение hot-keys,
    • равномерное распределение по нодам.
  1. Что в сумме ожидается от опыта

Внятный опыт работы с PostgreSQL и ClickHouse включает:

  • Умение:

    • Спроектировать схему под конкретные задачи (OLTP vs OLAP).
    • Написать эффективные SQL-запросы и доказать их эффективность через EXPLAIN.
    • Использовать индексы, partitioning, настройки пулов соединений.
    • Работать с транзакциями и конкурентным доступом.
    • Встраивать БД в архитектуру Go-сервисов:
      • миграции (go-migrate, встроенные миграции),
      • retry-политики,
      • таймауты, контексты,
      • метрики (slow queries, ошибки подключения).
  • Понимать:

    • Когда применить PostgreSQL (транзакционные данные, бизнес-логика).
    • Когда применить ClickHouse (аналитика, логи, метрики).
    • Как безопасно и эффективно их комбинировать:
      • PostgreSQL как source-of-truth,
      • ClickHouse для отчётов и аналитики (ETL/CDC-пайплайны).

Такой уровень демонстрирует не просто знакомство с СУБД, а умение осознанно использовать их как фундамент инфраструктуры и приложений.

Вопрос 3. Каков практический опыт использования Zabbix для мониторинга инфраструктуры и интеграции с мониторингом приложений?

Таймкод: 00:08:14

Ответ собеседника: правильный. Работал с уже настроенным Zabbix, дорабатывал под нужды команды: создавал кастомные шаблоны, настраивал метрики CPU/памяти, триггеры, алерты и дашборды. Zabbix использовался для инфраструктуры, метрики приложений собирались через Prometheus+Grafana+Alertmanager.

Правильный ответ:

Практический опыт с Zabbix, который является ценным в продакшн-среде, включает не только умение “пользоваться UI”, но и системный подход к мониторингу инфраструктуры и его интеграцию в общую observability-стратегию.

Ключевые аспекты зрелого использования Zabbix:

  1. Архитектура и подход
  • Понимание роли Zabbix:
    • Инфраструктурный мониторинг: сервера, ВМ, сети, диски, сервисы, агентские и безагентские проверки.
    • Чёткое разделение зон ответственности: Zabbix для хоста и окружения, Prometheus/OTel для метрик приложения — нормальная, здравая стратегия.
  • Базовое понимание компонентов:
    • Zabbix Server, Zabbix Agent (active/passive checks), Proxy (для распределённых окружений), базы данных Zabbix.
    • Основы производительности: влияние частоты опроса, количества items и триггеров.
  1. Шаблоны и масштабируемость
  • Использование и доработка шаблонов:
    • Адаптация стандартных шаблонов (Linux by Zabbix agent, файловые системы, сети и т.п.).
    • Создание кастомных шаблонов под стандарты компании:
      • единые items и триггеры по CPU, RAM, диску, I/O, сетям;
      • унификация порогов для разных типов хостов (prod/stage/dev).
  • Масштабируемость:
    • Применение шаблонов к группам хостов,
    • Минимизация ручных настроек на уровне отдельного хоста.
  1. Метрики, триггеры и шумоподавление

Важно не просто “настроить алерты”, а добиться полезных сигналов без алерт-фатуга.

  • Метрики инфраструктуры:
    • CPU: load average, utilization по ядрам, iowait.
    • Память: used vs cached vs buffers, реальные признаки memory pressure.
    • Диск: свободное место, inode usage, latency, I/O ops.
    • Сеть: трафик, ошибки, дропы.
    • Доступность сервисов: ping, TCP checks, HTTP checks.
  • Триггеры:
    • Продуманные пороги (SLA-ориентированные):
      • не триггерить по разовым пикам,
      • использовать функции last(), avg(), min(), max() за интервал.
    • Примеры:
      • CPU > 90% не 1 минуту, а, например, avg(5m) > 90%.
      • Свободное место < 10% + тренд на снижение.
  • Шумоподавление:
    • Использование dependencies (например: если упал хост — не слать алерты по всем сервисам на нём).
    • Корректная приоритизация severity (Info/Warning/Average/High/Disaster).
  1. Алертинг и интеграции
  • Настройка notification channels:
    • Email, Slack/Telegram, webhook-и, интеграция с инцидент-менеджментом (PagerDuty, VictorOps и т.п.).
  • Практика:
    • Разные профили уведомлений для разных окружений.
    • Эскалации: если алерт не закрыт — эскалировать через N минут.
  • Интеграция с другим мониторингом:
    • Нормальный продакшн-подход:
      • Zabbix: здоровье инфраструктуры.
      • Prometheus + Grafana + Alertmanager: метрики и алерты приложений.
    • Возможность:
      • Отражать ключевые инфраструктурные состояния Zabbix на общих дашбордах,
      • Учитывать инфраструктурные статусы при анализе метрик приложений.
  1. Кастомные проверки и расширение под приложения

Даже если метрики приложений идут через Prometheus, полезно уметь расширять Zabbix:

  • UserParameters и скрипты:
    • Проверки бизнес- или технических метрик: “отвечает ли HTTP-эндпоинт”, “есть ли соединение с БД”, “жив ли сервис на порту”.
  • Пример (Zabbix Agent + Go healthcheck):

Go-сервис с простым health endpoint:

http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
// здесь могут быть проверки БД, очередей, кэшей
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
})
log.Fatal(http.ListenAndServe(":8080", nil))

UserParameter на стороне Zabbix Agent:

UserParameter=app.healthcheck,/usr/bin/curl -fsS http://127.0.0.1:8080/healthz >/dev/null && echo 1 || echo 0

Далее:

  • Item в Zabbix, читающий app.healthcheck.
  • Trigger: если значение 0 в течение N минут — алерт.
  1. Практический уровень, который ценится

Ожидается опыт, когда человек:

  • Понимает, как построить мониторинг инфраструктуры так, чтобы:
    • алерты были осмысленными, а не спамом;
    • метрики позволяли расследовать инцидент, а не только фиксировать факт падения.
  • Умеет:
    • добавлять новые хосты и сервисы через шаблоны;
    • создавать корректные триггеры и дашборды под реальные потребности;
    • интегрировать Zabbix в существующую экосистему (Prometheus/Grafana/Alertmanager).
  • Может:
    • по алертам Zabbix быстро локализовать проблему (CPU/IO/сеть/диск/доступность),
    • предложить улучшения мониторинга на основе инцидентов.

Такой опыт показывает зрелое понимание мониторинга как части надежной продакшн-инфраструктуры, а не только знание конкретного инструмента.

Вопрос 4. Как организовать доставку изменений в Zabbix (шаблоны, дашборды и т.п.) в контролируемом и воспроизводимом виде?

Таймкод: 00:09:38

Ответ собеседника: неполный. Изменения вносились вручную через интерфейс, конфигурации хранились в Git; знает о существовании Ansible-модулей/ролей для Zabbix, но практического применения не было.

Правильный ответ:

Зрелый подход к управлению конфигурацией Zabbix предполагает принцип "monitoring as code": все изменения шаблонов, дашбордов, триггеров, медиа-типов и т.п. должны:

  • описываться в виде кода/артефактов (XML/JSON/YAML),
  • храниться в VCS (Git),
  • накатываться в предсказуемом, повторяемом и автоматизированном виде (CI/CD, Ansible, Terraform, API).

Основные подходы:

  1. Базовые принципы
  • Не редактировать продакшн Zabbix руками "в бою" без отражения в репозитории.
  • Все изменения:
    • проходят code review,
    • проверяются в тестовом/стейджинг Zabbix,
    • затем применяются к production.
  • Цель:
    • воспроизводимость (можно поднять новый Zabbix с теми же шаблонами),
    • аудит изменений (кто, что и когда поменял),
    • отсутствие "дрейфа конфигурации".
  1. Экспорт/импорт конфигурации как артефактов

Zabbix поддерживает экспорт шаблонов, дашбордов, hosts и других объектов в XML/JSON.

Практика:

  • Шаги:
    • Создаём/изменяем шаблон или дашборд на тестовом стенде.
    • Экспортируем в XML/JSON.
    • Кладём в Git.
    • Импортируем в production через:
      • UI (на начальном уровне),
      • или автоматизированно через API/скрипты/Ansible.

Плюсы:

  • Быстрый старт к "monitoring as code". Минусы:
  • Без автоматизации легко вернуться к ручным несогласованным правкам.
  1. Использование Zabbix API для автоматизации

Zabbix API позволяет управлять:

  • шаблонами,
  • хостами,
  • триггерами,
  • дашбордами,
  • пользователями и правами.

Подход:

  • Описывать конфигурацию в коде (JSON/YAML/Go-структуры).
  • Писать скрипты/утилиты, которые:
    • читают конфиг из репозитория,
    • применяют изменения через API (создают/обновляют шаблоны, элементы, триггеры).

Пример на Go (упрощённо, концептуально):

type ZabbixConfig struct {
Templates []TemplateConfig `json:"templates"`
}

func syncTemplates(apiURL, user, password string, cfg ZabbixConfig) error {
client := NewZabbixClient(apiURL, user, password)

for _, t := range cfg.Templates {
if err := client.UpsertTemplate(t); err != nil {
return fmt.Errorf("sync template %s: %w", t.Name, err)
}
}
return nil
}

Это можно обернуть в CI/CD: при merge в main ветку — запуск sync-скрипта, который применяет изменения в Zabbix.

  1. Ansible + Zabbix (рекомендуемый путь)

Практический и промышленный подход — держать конфигурацию Zabbix в Ansible (или подобном инструменте), используя:

  • роли (например, community.zabbix),
  • модули для:
    • zabbix_template,
    • zabbix_host,
    • zabbix_hostgroup,
    • zabbix_screen/graph/dashboard,
    • медиа-типов и action.

Плюсы:

  • Декларативность: состояние описано в yaml, Ansible приводит Zabbix к нужному виду.
  • Idempotent: повторный запуск не ломает конфигурацию.
  • Встраивается в общий стек управления инфраструктурой.

Пример Ansible-плейбука (идея):

- hosts: zabbix_server
gather_facts: no
collections:
- community.zabbix

vars:
zabbix_url: "https://zabbix.example.com"
zabbix_user: "api_user"
zabbix_password: "{{ vault_zabbix_password }}"

tasks:
- name: Ensure template for Linux servers exists
zabbix_template:
server_url: "{{ zabbix_url }}"
login_user: "{{ zabbix_user }}"
login_password: "{{ zabbix_password }}"
state: present
template_name: "Template OS Linux Custom"
template_description: "Extended Linux monitoring with tuned triggers"
link_templates:
- "Template OS Linux by Zabbix agent"

Эта конфигурация:

  • хранится в Git,
  • проходит review,
  • применяется через CI/CD или ручной запуск ansible-playbook.
  1. Terraform и другие инструменты

Альтернативно:

  • Использование Terraform-провайдеров для Zabbix:
    • Описание шаблонов, хостов, дашбордов в HCL.
    • Применение через terraform apply.
  • Преимущества:
    • Единый IaC-стек с остальной инфраструктурой.
  • Важно:
    • Следить за покрытием объектов Zabbix в выбранном провайдере.
  1. Проверка на практике: зрелый процесс доставки изменений

Хороший ответ на этот вопрос подразумевает наличие процесса, а не ручных правок:

  • Все изменения конфигурации Zabbix:
    • Описаны в коде (YAML/JSON/XML/Ansible/Terraform).
    • Хранятся в Git (с историей и ревью).
    • Проверяются на тестовом/стейджинг Zabbix.
    • Автоматизированно доставляются в production:
      • через CI/CD pipeline,
      • с использованием API/Ansible/Terraform.
  • Для критичных изменений:
    • можно добавить dry-run/plan (Terraform) или отдельный review.

Таким образом, от специалиста ожидается понимание, что Zabbix-конфигурация — такая же часть инфраструктурного кода, как Ansible-ролей для серверов или манифестов для сервисов, а не набор ручных изменений через веб-интерфейс.

Вопрос 5. Какие инструменты и подходы стоит использовать для первичной диагностики и оценки состояния Linux-системы при подключении?

Таймкод: 00:10:18

Ответ собеседника: правильный. Подключается по SSH (включая jump/proxy), использует ps/top для анализа процессов, проверяет статус сервисов, изучает конфигурации и логи, применяет top для оценки нагрузки и базовых метрик.

Правильный ответ:

Осознанная первичная диагностика Linux-системы — это быстрый, системный осмотр состояния узла, позволяющий:

  • понять, что с машиной “прямо сейчас”,
  • локализовать класс проблемы (CPU, память, диск, сеть, сервисы, конфигурация),
  • не мешать работе системы ещё сильнее.

Ниже — практичный, структурированный чек-лист и инструменты.

Подключение и базовая информация

  • SSH:
    • Использование ключей, ProxyJump / bastion-хостов.
    • Проверка задержки и стабильности сесcии (может намекнуть на сетевые проблемы).
  • Кто в системе:
    • who, w, last — понять активность, нет ли параллельных вмешательств.
  • Общая информация:
    • uname -a — ядро и дистрибутив.
    • cat /etc/os-release — версия ОС.
    • uptime — время работы и load average.

CPU и нагрузка

  • load average и общая нагрузка:
    • uptime или top, htop.
    • Интерпретация:
      • load average существенно выше количества ядер (особенно если растёт): CPU-bound, IO-wait или блокировки.
  • Детализация:
    • top/htop:
      • сортировка по CPU, проверка %wa (iowait), runaway-процессы.
    • ps aux --sort=-%cpu | head — кто прямо сейчас “жжёт” CPU.

Память

  • Общая картина:
    • free -m или free -h:
      • отличать used vs cache/buffers,
      • смотреть, есть ли реальный memory pressure и swap.
  • Дополнительно:
    • top/htop с сортировкой по памяти.
    • ps aux --sort=-%mem | head — проверки утечек.

Диск и файловая система

  • Место:
    • df -h — свободное место.
    • df -i — inodes (частая причина проблем лог-серверов).
  • I/O:
    • iostat -x 1 5 (из sysstat) — высокие await/avgqu-sz → диск не справляется.
    • lsblk — структура устройств.
  • Логи:
    • Проверить, не разрослись ли отдельные файлы (лог хвостит диск):
      • du -sh /var/log/*
      • find / -xdev -size +1G -ls

Сеть

  • Доступность и маршрутизация:
    • ip a — интерфейсы и адреса.
    • ip r — маршрут по умолчанию.
  • Диагностика:
    • ping/mtr/traceroute — задержки, потери.
    • ss -tulpen — какие порты слушаются, кем, на каких адресах.
    • curl -v — проверка HTTP/HTTPS-сервисов локально (важно для Go-сервисов и внутренних API).
  • Firewall:
    • iptables -L -n -v или nft list ruleset, firewall-cmd --list-all — базовая проверка блокировок.

Сервисы и процессы

  • Состояние системных сервисов:
    • systemctl status <service>
    • systemctl --failed
  • Поиск проблемных процессов:
    • ps aux | grep <service> — убедиться, что процесс жив, версия, аргументы.
  • Для Go-сервисов:
    • Проверка:
      • слушает ли нужный порт (ss -tulpn | grep :8080),
      • актуен ли бинарь, переменные окружения, systemd unit.

Логи и события

  • Системные логи:
    • journalctl -xe — свежие ошибки.
    • journalctl -u <service> -n 200 — логи конкретного сервиса.
  • Классические:
    • /var/log/syslog, /var/log/messages, /var/log/auth.log — ошибки ядра, ssh, sudo.
  • Прицел:
    • Ищем повторы ошибок, OOM-killer, падения демонов, проблемы диска/сети.

Температура, аппаратные проблемы (по необходимости)

  • Аппаратные метрики:
    • dmesg | tail -n 100 — ошибки диска, сетевых карт, OOM.
    • smartctl -a /dev/sdX — состояние дисков (если доступно).
    • Сенсоры (например, sensors) — перегрев.

Стратегия осмотра (важно на практике)

Хорошая практика — придерживаться порядка, чтобы быстро отсечь основные причины:

  1. Проверить доступность: хост жив? ssh заходит? latency?
  2. CPU / load average: нет ли явного перегруза или iowait.
  3. Память: нет ли OOM, активного swap.
  4. Диск: есть ли место и нормальный I/O.
  5. Сервисы: живы ли ключевые демоны, Go-сервисы, БД.
  6. Сеть: правильные IP/маршруты, порты слушаются, firewall не режет.
  7. Логи: коррелировать найденное с конкретными ошибками.
  8. Если хост под мониторингом (Zabbix/Prometheus) — сравнить текущее состояние с историей.

Такой подход позволяет за минуты получить объективную картину состояния системы и сузить круг поиска, не полагаясь на угадывания и не создавая лишнего шума в продакшене.

Вопрос 6. Что означает рост Steal time в Linux (например, в top) на виртуальной машине и как системно решать эту проблему во виртуальной инфраструктуре?

Таймкод: 00:11:18

Ответ собеседника: неполный. Правильно отмечает, что Steal time отражает "украденное" гипервизором CPU-время, и предлагает проверку логов и нагрузки на гипервизоре (например, при разворачивании новых нод), но не описывает системный подход: анализ oversubscription, планирование ресурсов, миграцию ВМ и изменение лимитов.

Правильный ответ:

Steal time — один из ключевых индикаторов проблем на виртуализированной инфраструктуре. Его нужно понимать глубоко, так как он напрямую влияет на производительность приложений, latency и предсказуемость работы сервисов.

Разберём:

  • что такое steal time;
  • как его интерпретировать;
  • какие действия предпринять на уровне гостевой ОС, гипервизора и архитектуры.

Что такое Steal time

Steal time (st) в выводе top/htop/vmstat показывает долю времени, в течение которого гостевая ОС (VM):

  • могла бы выполнять свои процессы,
  • но не получила CPU от гипервизора,
  • потому что физический CPU был занят выполнением других виртуальных машин.

Иными словами:

  • гостевая ОС запрашивает CPU (есть runnable-потоки),
  • но гипервизор не даёт ей квант, потому что:
    • CPU переподписан (oversubscription),
    • приоритеты других VM выше,
    • есть лимиты/шэйринг, неблагоприятное размещение.

Высокий steal time — это почти всегда:

  • симптом проблем на уровне виртуализации/ресурсного планирования,
  • а не внутри гостевой системы.

Как это увидеть:

  • top:
    • поле st в блоке CPU: например, st: 20% — это очень плохо.
  • vmstat:
    • столбец st — проценты времени, "украденные" гипервизором.

Если st > 5–10% стабильно под нагрузкой — это повод к разбору; десятки процентов — критическая деградация.

Практический подход к диагностике

  1. Убедиться, что это действительно проблема

На самой VM:

  • Проверить:
    • load average vs количество vCPU.
    • Есть ли runnable-процессы (R) в top/htop.
    • Есть ли высокое st:
      • в top (st),
      • в vmstat 1 (столбец st).
  • Интерпретация:
    • Высокий st при наличии runnable-процессов и нормальной IO/памяти → VM реально не получает CPU.
    • Если ст высокая только на idle VM без runnable-потоков — сначала убедиться, что мы правильно интерпретируем метрики (иногда бывают артефакты/методика измерения).
  1. Проверить локальные факторы в гостевой ОС

Хотя steal — проблема "снаружи", сначала исключим очевидное в гостевой:

  • Приложение не зажало CPU самó:
    • Посмотреть top/htop:
      • нет ли 100% single-thread bottleneck при многопоточной нагрузке,
      • нет ли lock contention в приложении.
  • Нет ли проблем с энергосбережением внутри VM:
    • Неправильные governor'ы (в классической ВМ это реже релевантно, но в некоторых конфигурациях имеет значение).
  • Нет ли массивной IO-wait, которую можно спутать с проблемами CPU.

Если гостевая выглядит "здоровой", но st высокое и latency растёт — смотрим на гипервизор.

Системное решение на уровне виртуальной инфраструктуры

Ключевая идея: высокий steal time — следствие oversubscription или неправильного планирования CPU-ресурсов.

Основные шаги:

  1. Анализ oversubscription на гипервизоре
  • Проверить:
    • Соотношение:
      • vCPU всех VM к доступным физическим CPU (pCPU).
    • Примеры:
      • 2–4x oversubscription может быть нормальным для не критичных нагрузок,
      • для latency-sensitive и высоконагруженных сервисов — часто нужны гарантированные ресурсы.
  • Инструменты:
    • Для KVM: virsh list, virsh vcpuinfo, top/htop на хосте, мониторинг нагрузки.
    • Для VMware/Hyper-V/OpenStack/облаков: метрики по CPU ready/steal time/overcommit.

Если oversubscription агрессивный — это прямая причина steal time.

  1. Миграция и перераспределение VM

Практические действия:

  • Перенести "тяжёлые" VM на другие узлы:
    • live migration (vMotion, virsh migrate, OpenStack live-migrate), если поддерживается.
  • Разнести конфликтующие нагрузки:
    • не держать несколько CPU-интенсивных прод-VM на одном хосте,
    • особенно если у них пиковая нагрузка совпадает по времени.
  1. Резервирование и политика CPU

Использовать механизмы планирования CPU, которые дают предсказуемость:

  • CPU pinning (affinity):
    • привязка vCPU конкретной VM к определённым физическим CPU.
    • полезно для high-performance и real-time сценариев.
  • CPU shares/limits/reservations:
    • задать резерв (reservation) для критичных VM;
    • ограничить “шумных соседей” (limits) — чтобы одна VM не выжигала весь CPU.
  • В облаках:
    • Использовать инстансы с гарантированным CPU (dedicated/vCPU pinned), а не burstable.
    • Избегать дешёвых burstable-инстансов для latency-critical сервисов.
  1. Корректный выбор числа vCPU

Частая ошибка — “давайте дадим больше vCPU, будет быстрее”. Для виртуализации это не всегда верно.

  • Если:
    • у VM много vCPU,
    • гипервизору сложно найти слоты для их планирования одновременно,
    • это может ухудшать ситуацию (ready/steal time растут).
  • Практика:
    • Давать столько vCPU, сколько реально нужно и чем приложение умеет пользоваться.
    • Для некоторых сервисов лучше меньше vCPU, но с более предсказуемым временем.
  1. Мониторинг и SLO-ориентированный подход

Встраиваем steal time в систему мониторинга:

  • Метрики:
    • Steal time по VM (изнутри),
    • CPU ready / overcommit (на гипервизоре/облачном уровне).
  • Алёрты:
    • Стойкое превышение steal time (например, >5–10%) на критичных сервисах.
  • Реакция:
    • Автоматическая/ручная миграция VM,
    • масштабирование (горизонтальное или по ресурсам),
    • пересмотр политики размещения.

Пример: как отследить steal time быстро

Команда:

vmstat 1 5

Пример вывода (важен столбец st):

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
3 0 0 200000 10000 500000 0 0 10 20 100 200 40 10 40 0 10

Интерпретация:

  • st = 10 → 10% времени CPU эта VM не получила, хотя могла бы работать.

В Go-сервисе это может проявляться как:

  • рост latency без изменений в коде,
  • увеличение p95/p99,
  • нестабильные таймауты при внешне "нормальной" нагрузке.

Для таких кейсов:

  • нужно явно смотреть на steal time и инфраструктуру,
  • а не только оптимизировать код и SQL.

Сводка ключевых идей

  • Steal time — это показатель того, что гипервизор не даёт вашей VM достаточно CPU, хотя ей есть что исполнять.
  • Высокий steal time:
    • почти всегда проблема инфраструктуры (oversubscription, конфигурация гипервизора, тип инстанса в облаке),
    • а не "плохой Linux" или "не тот sysctl" внутри гостя.
  • Системный подход:
    • мониторить steal time,
    • анализировать oversubscription,
    • использовать миграцию VM, pinning, гарантии ресурсов, разумное количество vCPU,
    • обеспечивать предсказуемость CPU для критичных сервисов.

Такой ответ демонстрирует понимание взаимосвязи между гостевой ОС, гипервизором и SLA приложений и позволяет принимать правильные архитектурные решения, а не ограничиваться просмотром логов гипервизора.

Вопрос 7. Как корректно проверить UDP-связность между двумя новыми серверами, если на целевой стороне ещё нет боевого приложения?

Таймкод: 00:12:05

Ответ собеседника: неправильный. Предлагает использовать nc/ss для отправки пакетов и ожидать подтверждения, но не учитывает отсутствие слушающего сокета на целевой стороне. Затем упоминает возможность открыть сокет, но не формулирует чёткого, воспроизводимого решения для UDP.

Правильный ответ:

Важный момент: в отличие от TCP, протокол UDP:

  • не устанавливает соединение;
  • не даёт встроенного подтверждения доставки;
  • не гарантирует доставку и порядок;
  • не сообщает отправителю, что “кто-то слушает”, пока вы сами не реализуете этот протокол.

Поэтому для проверки UDP-связности между серверами (портов, маршрутизации, firewall’ов) нужно:

  • организовать явный listener на целевой стороне;
  • отправить тестовый UDP-пакет с исходного сервера;
  • убедиться, что пакет дошёл (по логам listener’a или с помощью tcpdump/ss).

Ниже — несколько рабочих сценариев.

Подход 1. Использование netcat (nc) для простого теста

Если доступны нужные версии nc (поддерживающие UDP):

  1. На целевом сервере (Server B) поднимаем UDP-listener:
nc -u -l -p 9999
  • Сервер слушает UDP-порт 9999 и выводит полученные данные в stdout.
  1. На исходном сервере (Server A) отправляем тестовую строку:
echo "test-udp" | nc -u <IP_B> 9999
  1. Проверяем:
  • Если на Server B в консоли с nc видно "test-udp" — UDP-связность есть.
  • Если ничего не приходит:
    • проверяем firewall, маршрутизацию, правильность IP/порта.

Ограничения:

  • Некоторые реализации nc требуют ключей:
    • например: nc -u -l 9999 или ncat --udp -l 9999.
  • Именно наличие слушателя на целевой стороне критично — без него тест невалиден.

Подход 2. Анализ трафика с tcpdump

Даже если нет nc или хотим более низкоуровневую проверку.

  1. На Server B:
sudo tcpdump -n -i any udp port 9999
  1. На Server A:
echo "ping-udp" | nc -u <IP_B> 9999
# или
printf "ping-udp" >/dev/udp/<IP_B>/9999 # в bash с поддержкой /dev/udp
  1. Если tcpdump на B показывает входящие пакеты — UDP-трафик доходит:
  • Значит:
    • маршрутизация работает,
    • firewall пропускает (по крайней мере до уровня ядра),
    • порт достижим.

Если пакетов нет — проблема в сети/маршруте/firewall/ACL.

Подход 3. Минимальный тест на Go (надёжный и близкий к боевому)

Это хорошая практика: использовать небольшой Go-инструмент, который моделирует будущую нагрузку и явно логирует приём.

Listener (Server B):

package main

import (
"fmt"
"log"
"net"
)

func main() {
addr := ":9999"
conn, err := net.ListenPacket("udp", addr)
if err != nil {
log.Fatalf("listen error: %v", err)
}
defer conn.Close()

log.Printf("Listening on UDP %s\n", addr)

buf := make([]byte, 1500)
for {
n, src, err := conn.ReadFrom(buf)
if err != nil {
log.Printf("read error: %v", err)
continue
}
log.Printf("Got %d bytes from %s: %q", n, src.String(), string(buf[:n]))
}
}

Sender (Server A):

package main

import (
"log"
"net"
)

func main() {
addr, err := net.ResolveUDPAddr("udp", "<IP_B>:9999")
if err != nil {
log.Fatalf("resolve error: %v", err)
}

conn, err := net.DialUDP("udp", nil, addr)
if err != nil {
log.Fatalf("dial error: %v", err)
}
defer conn.Close()

msg := []byte("test-udp-connectivity")
if _, err := conn.Write(msg); err != nil {
log.Fatalf("write error: %v", err)
}

log.Println("sent")
}

Если на B видим лог о получении пакета — связность подтверждена.

Плюсы такого подхода:

  • Поведение максимально похоже на реальное приложение.
  • Можно расширить:
    • измерение потерь,
    • latency,
    • пулы сокетов,
    • большие payload’ы.

Подход 4. Проверка с учётом firewall и политики

Важно не только послать пакет, но и валидировать окружение:

  • Проверяем на обоих серверах:
    • ss -ulpn — слушает ли порт (если запускаем listener),
    • iptables -L -n -v / nft list ruleset / firewall-cmd --list-all.
  • Если есть внешние ACL / security groups (AWS, GCP, OpenStack, корпоративный firewall):
    • убедиться, что разрешён нужный UDP-порт между IP A и IP B.

Подход 5. Что делать, если "боевого" приложения ещё нет

Ключевая идея: отсутствие готового приложения — не оправдание для “магических” методов проверки.

Правильный подход:

  • временно поднять лёгкий UDP-listener:
    • с помощью nc, socat, маленькой Go/ Python-утилиты;
  • выполнить явную проверку:
    • отправить тестовый пакет,
    • увидеть его на целевой стороне;
  • задокументировать используемые порты и сценарий.

Это:

  • воспроизводимо;
  • прозрачно;
  • легко автоматизируется (health-check’и в CI, smoke-тесты окружения).

Краткая суть:

  • UDP нельзя проверить “как TCP” без слушателя.
  • Корректная проверка UDP-связности:
    • поднять listener на целевом сервере,
    • отправить тестовые пакеты с исходного,
    • подтвердить доставку по логам/выводу/pcap.
  • Для продакшн-подхода:
    • использовать простые утилиты (nc/tcpdump),
    • либо написать маленький сервис на Go,
    • интегрировать этот тест в процедуру приёмки новых серверов.

Вопрос 8. Почему после удаления файлов раздел остаётся заполненным и как найти источник проблемы?

Таймкод: 00:16:33

Ответ собеседника: правильный. Объясняет, что процесс может удерживать удалённый файл открытым, из-за чего место не освобождается до закрытия дескриптора. Предлагает найти такой процесс через lsof с фильтрацией по директории.

Правильный ответ:

Ситуация: вы удалили крупные файлы (например, логи), но:

  • df -h показывает, что свободного места не прибавилось,
  • сервисы продолжают падать из-за “No space left on device”.

Чаще всего причина в том, что файлы удалены только из файловой системы (каталога), но всё ещё открыты каким-то процессом. В Linux:

  • Удаление файла с точки зрения директории:
    • убирает ссылку (directory entry) на inode;
  • Если при этом файл ещё открыт процессом:
    • данные продолжают существовать, пока не будет закрыт последний файловый дескриптор,
    • блоки диска не освобождаются,
    • файл становится “deleted”, но живёт в памяти ядра и учитывается в использовании пространства.

Это важно понимать для логирующих приложений, баз данных, долгоживущих демонов.

Как диагностировать и устранить проблему.

  1. Проверка использования диска

Сначала убеждаемся, что проблема реально есть:

df -h

Если раздел заполнен (Use% ≈ 100%), но вы ожидаете свободное место после удаления файлов — идём дальше.

  1. Поиск открытых, но удалённых файлов (через lsof)

Классический способ:

  • Найти процессы, удерживающие удалённые файлы.

Команды:

  1. Посмотреть все удалённые, но ещё открытые файлы:
sudo lsof | grep deleted

Типичный вывод:

myapp    1234   app    5w   REG  253,0  104857600  1234567 /var/log/myapp.log (deleted)

Интерпретация:

  • Процесс myapp (PID 1234) продолжает писать в файл, который уже удалён из каталога.
  • Размер учётается на диске, но вы его “не видите” через ls.
  1. Ищем по конкретному разделу/директории:
sudo lsof +L1
  • Показывает файлы с количеством ссылок < 1 (обычно удалённые).

Если такие файлы есть и они большие — это и есть причина заполнения диска.

  1. Освобождение места

Есть несколько корректных способов.

Вариант 1. Перезапустить процесс

  • Самое частое и правильное решение:
sudo systemctl restart myapp
# или
kill -HUP <PID> # если приложение перевешивает логи по сигналу

После перезапуска:

  • все файловые дескрипторы закрываются,
  • kernel освобождает блоки,
  • df -h начинает показывать освободившееся место.

Вариант 2. Точечное закрытие или “обнуление” файла

Если нельзя сразу перезапустить:

  • Иногда можно “обнулить” файл через /proc/<pid>/fd, но это опасный и редко нужный трюк.
  • Обычно лучше:
    • настроить корректный log rotation,
    • обеспечить возможность HUP/USR1-сигналом перевешивать лог-файлы без удаления из-под живого процесса.

Важно: не пытаться “удалить ещё раз” — файла уже нет в каталоге, проблема в открытом дескрипторе.

  1. Разница между df и du (частый диагностический паттерн)

Этот кейс хорошо видно по расхождению:

  • df -h показывает почти полный диск.
  • du -sh /path показывает, что суммарно файлов гораздо меньше.

Это явный маркер:

  • есть удалённые, но всё ещё открытые файлы,
  • или есть файлы, которые считаются на другом mount namespace (в контейнерах/namespace-сценариях).

Алгоритм:

  1. Если df >> du — проверяем lsof | grep deleted.
  2. Если в контейнеризированной среде — учитывать mount namespaces и проверять внутри того же контекста.
  1. Профилактика: как делать правильно

Чтобы не “убивать прод” удалением логов:

  • Использовать logrotate или аналоги:
    • стандартный подход:
      • вместо rm /var/log/app.log:
        • logrotate делает:
          • mv /var/log/app.log /var/log/app.log.1,
          • отправляет сигнал приложению,
          • или приложение само открывает новый файл.
  • Убедиться, что:
    • приложения умеют корректно обрабатывать rotation (по сигналам или reopen).
    • операции с логами не выполняются "вручную" на живых файлах без понимания последствий.

Итог:

  • Если после удаления файлов место не освободилось — почти всегда виноваты открытые дескрипторы удалённых файлов.
  • Проверяем:
    • df -h и du -sh для сравнения,
    • lsof | grep deleted или lsof +L1,
  • Находим процесс, удерживающий файл,
  • Перезапускаем/корректно перевешиваем логи — и только так реально освобождаем пространство.

Вопрос 9. Как проверить количество свободных inode и можно ли увеличить их число на уже работающем разделе?

Таймкод: 00:17:12

Ответ собеседника: неполный. Правильно указывает использование df с ключом для просмотра inode, но не уверен в возможности изменения их числа на существующем разделе и недооценивает важность проблемы.

Правильный ответ:

Понимание работы inode — критично для продакшн-систем: раздел может быть “забит” не по объёму (df -h показывает свободное место), а по количеству inode, и тогда создание новых файлов станет невозможным, что легко уронит логи, временные файлы, сокеты и приложения.

Разберёмся по шагам.

Проверка количества inode

Для просмотра использования inode по файловым системам используется:

df -i

Вывод покажет:

  • total — общее количество inode,
  • used — использованные,
  • free — свободные,
  • IUse% — процент занятых inode.

Если IUse% близок к 100%, а места по df -h ещё много — у вас inode starvation.

Типичный симптом:

  • Ошибки вида:
    • "No space left on device"
    • при этом df -h показывает, что свободные гигабайты есть.
  • Причина:
    • закончилось количество inode, новые файлы создать нельзя.

Можно ли увеличить количество inode на уже существующем разделе?

Ключевой момент: для классических файловых систем (например, ext4) количество inode задаётся при создании файловой системы и обычно:

  • не может быть изменено “на лету” на уже смонтированном и используемом разделе;
  • не пересчитывается автоматически.

То есть:

  • Для ext2/ext3/ext4:
    • количество inode и их плотность (inode ratio) выбираются при форматировании через параметры mkfs.
    • Увеличить inode “по месту” без пересоздания файловой системы нельзя.
  • Для XFS:
    • inode аллоцируются динамически, лимит фактически определяется размером файловой системы, так что проблема фиксированного числа inode проявляется реже, но при экстремальном количестве файлов на малом объёме тоже возможны ограничения.

Вывод:

  • В общем случае: увеличить число inode на уже существующем ext4-разделе нельзя без его пересоздания (reformat).
  • Если слышите “можно как-то докрутить” — это неверный/опасный подход.

Что делать, если inode закончились

Если прод уже страдает от нехватки inode, работающий сценарий такой:

  1. Найти, кто их съедает:

Чаще всего это:

  • мелкие временные файлы,
  • некорректные лог- или кеш-директории,
  • артефакты приложений, которые не чистятся.

Поиск “самых файловых” директорий:

# Топ-10 директорий по количеству файлов
sudo find /path/to/mount -xdev -printf '%h\n' | sort | uniq -c | sort -nr | head

Или точечно:

sudo du --inodes -d 3 /path/to/mount | sort -nr | head

Это показывает, где максимальная концентрация inode.

  1. Удалить мусор/почистить каталоги
  • Очистить:
    • временные файлы,
    • старые логи,
    • кеши (tmp, app cache, build-артефакты).
  • Важно:
    • делать осознанно,
    • помнить про кейс с открытыми, но удалёнными файлами (см. разбор в предыдущем вопросе):
      • если удалён файл, но процесс держит дескриптор — inode освободится только после закрытия.
  1. Среднесрочное решение: пересоздание файловой системы с нужными inode

Если система регулярно упирается в inode при нормальных сценариях (например, много мелких файлов):

  • План действий:
    • Перенести данные временно (backup/rsync/snapshot).
    • Пересоздать файловую систему с более высокой плотностью inode.
    • Вернуть данные.

Для ext4 управление inode при создании:

  • Параметр -i (bytes-per-inode):
mkfs.ext4 -i 4096 /dev/sdX

Чем меньше значение -i, тем больше inode создаётся (т.е. больше файлов можно разместить, но растут накладные расходы).

Пример:

  • По умолчанию может быть что-то вроде 1 inode на 16К или 32К байт.
  • Для каталогов с огромным количеством мелких файлов (maildir, metadata, кеши) логично снижать -i.

Важно:

  • Это делается ТОЛЬКО при создании файловой системы.
  • На уже существующем разделе — только через полное пересоздание.
  1. Архитектурные и практические рекомендации

Чтобы не попасть в проблему inode starvation в продакшене:

  • При проектировании:
    • Оценивать профиль нагрузки:
      • если ожидаются миллионы/десятки миллионов мелких файлов → сразу закладывать:
        • корректный bytes-per-inode,
        • либо использовать файловые системы, лучше работающие с множеством файлов (XFS, btrfs) или другие подходы (объектные хранилища).
  • В эксплуатации:
    • Мониторить IUse% (использование inode) так же, как и дисковое пространство:
      • Zabbix / Prometheus-экспортёр для inode.
    • Не использовать файловую систему как бесконтрольный кеш без лимитов.
    • Для временных/кеш-данных:
      • периодическая уборка,
      • TTL-стратегии,
      • tmpfiles.d, systemd-tmpfiles, встроенные механизмы приложений.

Итог:

  • Проверка свободных inode: df -i.
  • Если inode кончились:
    • это может остановить систему, даже при наличии свободных ГБ.
  • Увеличить количество inode для ext4 “на лету” нельзя:
    • только очистка мусора,
    • или пересоздание файловой системы с другими параметрами.
  • Зрелый подход:
    • мониторить inode,
    • проектировать FS с учётом паттернов нагрузки,
    • не допускать ситуации, когда инфраструктура упирается в этот невидимый, но критичный лимит.

Вопрос 10. Что такое sticky bit для директории и какова его практическая роль в управлении правами?

Таймкод: 00:18:43

Ответ собеседника: неправильный. Путает sticky bit с ACL и возможностью иметь несколько владельцев файлов, не даёт корректного объяснения назначения и поведения sticky bit.

Правильный ответ:

Sticky bit для директории — это специальный бит прав доступа, который ограничивает удаление и переименование файлов внутри этой директории. Он не связан напрямую ни с ACL, ни с "несколькими владельцами", ни с содержимым файлов.

Суть поведения:

  • Без sticky bit:

    • Если у пользователя есть права на запись в директорию, он может:
      • создавать файлы,
      • удалять любые файлы в этой директории,
      • переименовывать любые файлы,
    • даже если он не владелец файла (классическое UNIX-поведение: контроль принадлежит директорий).
  • Со sticky bit, установленным на директории:

    • Удалять и переименовывать файл может только:
      • владелец файла,
      • владелец директории,
      • или root.
    • Другие пользователи, даже имея права на запись в директорию, не могут удалять/переименовывать чужие файлы.

Практическая роль:

Sticky bit решает задачу безопасного совместного использования директории, в которую много разных пользователей могут писать, но не должны мешать друг другу, удаляя или переименовывая чужие файлы.

Классический пример — /tmp:

  • /tmp:
    • общедоступная директория для временных файлов.
    • У всех есть права на запись.
    • При этом нельзя позволить одному пользователю удалять файлы другого.
  • Поэтому на /tmp установлены:
    • права типа: rwxrwxrwt
    • буква t в конце — это и есть sticky bit.

Проверка:

ls -ld /tmp

Типичный вывод:

drwxrwxrwt 10 root root 4096 Nov 11 10:00 /tmp

Обратите внимание на t на конце: rwxrwxrw t — это sticky bit; без него была бы обычная x.

Как установить и снять sticky bit

Sticky bit задаётся как:

  • символьной формой: +t,
  • восьмеричной формой: старший разряд 1 (1xxx).

Примеры:

  1. Установить sticky bit на директорию:
chmod +t /shared
  1. Восьмеричная запись:
  • 1777 означает:
    • 1 — sticky bit,
    • 7 — rwx для владельца,
    • 7 — rwx для группы,
    • 7 — rwx для остальных.
chmod 1777 /shared
  1. Убрать sticky bit:
chmod -t /shared

Практические сценарии использования

  • Общие временные директории:
    • /tmp, /var/tmp, кастомные tmp/dirs в много-пользовательских или multi-tenant окружениях.
  • Совместные рабочие каталоги:
    • когда разные пользователи или сервисы должны:
      • иметь возможность создавать файлы,
      • но не иметь права удалять/переименовывать чужие.
  • Без sticky bit:
    • любой пользователь с правом записи в директорию может нарушить работу других:
      • удалить сокеты/lock-файлы,
      • удалить или подменить важные временные файлы.

Важно понимать отличия от других механизмов

  • Sticky bit:
    • контролирует удаление/переименование файлов в директории.
    • не влияет на чтение/запись содержимого файла — это по-прежнему регулируется обычными правами и, при необходимости, ACL.
  • ACL:
    • более тонкая настройка прав для пользователей/групп.
  • setuid/setgid:
    • другие специальные биты, не путать со sticky:
      • setuid (4xxx) — запуск бинаря с правами владельца файла,
      • setgid (2xxx) — запуск с правами группы или наследование группы/директории.

Краткое резюме:

  • Sticky bit для директории — механизм защиты от удаления/переименования чужих файлов в общей директории.
  • Используется там, где:
    • многие могут писать,
    • но каждый отвечает за свои файлы.
  • Ключевой пример: /tmp с правами 1777.
  • Это базовая вещь, которую нужно знать при администрировании Linux-систем и настройке безопасных shared-директорий.

Вопрос 11. Как корректно посмотреть список статических маршрутов в современной Linux-системе?

Таймкод: 00:20:22

Ответ собеседника: неполный. Упоминает только команду route, не акцентируя, что она устарела и в современных системах предпочтительно использовать ip route и семейство iproute2.

Правильный ответ:

В современных Linux-системах стандартным инструментом для работы с сетью является пакет iproute2. Команда route (из net-tools) считается устаревшей и во многих дистрибутивах уже не устанавливается по умолчанию. Для просмотра таблицы маршрутизации, включая статические маршруты, следует использовать утилиту ip.

Базовые команды:

  1. Просмотр текущей таблицы маршрутизации (IPv4):
ip route show

Или короче:

ip r

Вывод покажет:

  • маршрут по умолчанию (default via ...),
  • сети, доступные через конкретные интерфейсы,
  • статические маршруты, добавленные администратором или системой.

Пример:

default via 192.168.0.1 dev eth0
10.0.0.0/24 via 192.168.0.2 dev eth0
192.168.0.0/24 dev eth0 proto kernel scope link src 192.168.0.10
  • Статический маршрут в примере:
    • 10.0.0.0/24 via 192.168.0.2 dev eth0
  1. Просмотр IPv6-маршрутов:
ip -6 route show
  1. Маршрутизация по таблицам (policy routing)

В продакшн-средах часто используются дополнительные таблицы маршрутизации и policy routing. Для их просмотра:

  • Список таблиц:
cat /etc/iproute2/rt_tables
  • Просмотр конкретной таблицы:
ip route show table main
ip route show table 100

Это важно при сложных схемах, multi-homing, отдельной маршрутизации для VPN, исходящих IP и т.п.

  1. Как отличить статические маршруты от динамических

В типичной инфраструктуре:

  • Статические маршруты:
    • прописаны явно через:
      • ip route add ... (вручную),
      • сетевые конфиги (Netplan, ifcfg, systemd-networkd и т.п.),
    • в выводе ip route обычно идут без пометок dynamic/ra/bgp/ospf.
  • Динамические маршруты:
    • приходят от демонов маршрутизации (FRR, Bird, Quagga и т.п.),
    • могут иметь пометки типа proto bgp, proto ospf, proto dhcp, proto ra.

Пример:

10.10.0.0/16 via 192.168.1.1 dev eth0 proto static

или просто:

10.10.0.0/16 via 192.168.1.1 dev eth0

— это статический маршрут.

Если видим:

2001:db8::/64 dev eth0 proto ra

— это маршрут, полученный из Router Advertisement (не статический).

  1. Почему ip route предпочтительнее route

Основные причины:

  • iproute2 (ip) поддерживает:
    • IPv4 и IPv6 единообразно;
    • policy routing;
    • работу с несколькими таблицами;
    • метрики, атрибуты, сложные сценарии.
  • net-tools (route, ifconfig):
    • устаревшие,
    • не показывают полную картину в сложных конфигурациях,
    • могут вводить в заблуждение на современных системах.

Краткое резюме:

  • Для современных Linux-систем:
    • правильно использовать ip route show / ip r для просмотра таблицы маршрутов.
    • при необходимости — уточнять таблицу: ip route show table <name|id>.
  • Команда route считается легаси-инструментом и не должна использоваться как основной способ диагностики в актуальной инфраструктуре.

Вопрос 12. Какие современные утилиты предпочтительнее использовать вместо устаревающего netstat для просмотра сетевых подключений?

Таймкод: 00:20:45

Ответ собеседника: неполный. Заявляет, что продолжит использовать netstat, пока его не уберут, но не называет ss и другие актуальные инструменты как рекомендуемый стандарт.

Правильный ответ:

В современных Linux-системах утилиты из пакета net-tools (netstat, ifconfig и т.п.) считаются устаревающими и частично не отражают всей полноты информации о сети, особенно при сложных конфигурациях, IPv6 и продвинутых возможностях ядра.

Рекомендованный и предпочтительный инструмент для просмотра сетевых подключений, слушающих портов и сокетов — это:

  • ss (часть iproute2).

Основные причины:

  • Более высокая скорость и меньшая нагрузка на систему по сравнению с netstat.
  • Корректная работа с современными ядрами и протоколами.
  • Поддержка дополнительных атрибутов (TCP states, timers, queue sizes и т.п.).

Ключевые команды с ss, которые нужно уверенно знать:

  1. Все TCP-соединения:
ss -t
  1. Только установленные TCP-сессии:
ss -t -a

(или фильтровать по state)

  1. Все слушающие TCP-порты:
ss -t -l
  1. Все слушающие UDP-сокеты:
ss -u -l
  1. Подробная информация (PID, процесс, порты, состояния):
ss -tulpn

Расшифровка:

  • t — TCP
  • u — UDP
  • l — listening
  • p — показать процесс (PID/имя)
  • n — не резолвить имена (быстрее и нагляднее)
  1. Фильтрация по порту или адресу:

Например, найти, кто слушает 8080 порт:

ss -tulpn | grep :8080

Это стандартный приём для проверки Go-сервиса или любого API.

  1. Просмотр сокетов в конкретном состоянии (пример):
ss -t state established
ss -t state time-wait
ss -t state syn-recv

Полезно при отладке проблем с соединениями, "залипшим" TIME_WAIT, SYN flood и т.п.

Дополнительные полезные инструменты:

Хотя ss — основной современный инструмент вместо netstat, в продакшн-практике также часто используются:

  • ip (iproute2) — для интерфейсов, адресов и маршрутов:
    • ip a, ip link, ip route, ip -6 route.
  • lsof -i:
    • Просмотр открытых сетевых сокетов и привязка к процессам:
      • lsof -iTCP -sTCP:LISTEN
  • tcpdump:
    • Низкоуровневый анализ трафика, если нужно увидеть реальные пакеты:
      • tcpdump -ni any port 443
  • nft, iptables:
    • Для анализа firewall-правил, влияющих на соединения.

Практический итог для инженера:

  • netstat стоит воспринимать как легаси-инструмент.
  • В современных Linux-средах:
    • для подключения и прослушивания портов: ss -tulpn;
    • для интерфейсов/маршрутов: ip из iproute2;
    • для детальной диагностики: lsof, tcpdump дополнительно.
  • На собеседовании ожидается, что в ответе прозвучит ss как основной современный инструмент, а не только "я привык к netstat".

Вопрос 13. Как определить, какая операционная система и дистрибутив установлены на сервере после входа?

Таймкод: 00:21:12

Ответ собеседника: правильный. Предлагает использовать файлы с информацией о дистрибутиве, например cat /etc/os-release, что является корректной современной практикой.

Правильный ответ:

В реальной эксплуатации важно быстро и надёжно определить:

  • семейство ОС (Linux/BSD/Unix),
  • конкретный дистрибутив и его версию,
  • версию ядра.

Это влияет на пакетный менеджер, расположение конфигураций, systemd vs sysvinit, наличие iproute2, особенности security-политик и т.п.

Современный базовый и правильный подход для Linux-дистрибутивов:

  1. Основной способ: /etc/os-release

Почти все современные Linux-дистрибутивы предоставляют стандартный файл:

cat /etc/os-release

Пример вывода:

NAME="Ubuntu"
VERSION="22.04.3 LTS (Jammy Jellyfish)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 22.04.3 LTS"
VERSION_ID="22.04"

Ключевые поля:

  • NAME, PRETTY_NAME — человекочитаемое название.
  • ID — базовый идентификатор (ubuntu, debian, centos, rhel, rocky, almalinux и т.д.).
  • VERSION, VERSION_ID — версия.
  • ID_LIKE — на что дистрибутив похож (важно для выбора семейства).
  1. Дополнительные файлы (для совместимости и старых систем)

На старых или специфичных системах:

  • RedHat/CentOS/Alma/Rocky:
cat /etc/redhat-release
  • Debian:
cat /etc/debian_version
  • SUSE:
cat /etc/SuSE-release

Но в современных системах /etc/os-release — основной источник.

  1. Проверка ядра

Версия ядра важна для:

  • поддержки сетевых и системных фич,
  • поведения cgroups, eBPF, современных протоколов.

Команды:

uname -r      # версия ядра
uname -a # расширенная информация
  1. Проверка архитектуры

Для выбора бинарей (Go-сервисов, сторонних утилит):

uname -m

Примеры:

  • x86_64 — 64-битная Intel/AMD.
  • aarch64 — 64-битная ARM.
  1. Практический паттерн “зашёл на неизвестный сервер”

Минимальный набор команд:

cat /etc/os-release
uname -r
uname -m

Этого достаточно, чтобы:

  • понять, какой пакетный менеджер использовать (apt, dnf/yum, zypper),
  • как устроены сервисы (systemd vs init),
  • какие версии библиотек/ядра доступны для приложений.
  1. Почему это важно для разработки и эксплуатации

Для прикладного разработчика и инженера:

  • выбор правильных пакетов (libpq, ca-certificates, runtime-зависимости);
  • корректная установка и настройка systemd unit-файлов;
  • уверенность, что используемые фичи ядра и сетевые настройки доступны;
  • корректная сборка и выкладка Go-бинарей под нужную архитектуру и окружение.

Кратко:

  • Современный стандарт: смотреть /etc/os-release.
  • Дополнительно: uname -r / uname -m для ядра и архитектуры.
  • Это базовая, но обязательная компетенция при работе с любыми Linux-серверами.

Вопрос 14. К какому уровню модели OSI относится протокол ICMP и где он применяется?

Таймкод: 00:21:30

Ответ собеседника: неполный. Верно относит ICMP к сетевому уровню и приводит ping как пример использования, но некорректно связывает ICMP с ARP и даёт неточные формулировки.

Правильный ответ:

ICMP (Internet Control Message Protocol):

  • Относится к сетевому уровню модели OSI (Layer 3).
  • Является вспомогательным протоколом для IP (IPv4/IPv6).
  • Используется не для передачи пользовательских данных, а для:
    • диагностики,
    • сообщений об ошибках,
    • служебных уведомлений о состоянии сети.

Ключевые моменты:

  1. Позиция в модели и стек протоколов
  • ICMP логически принадлежит сетевому уровню:
    • работает поверх IP,
    • использует IP-пакеты (для ICMPv4 — протокол номер 1, для ICMPv6 — 58),
    • но не является транспортным протоколом уровня TCP/UDP.
  • Важно:
    • ICMP не конкурирует с TCP/UDP и не “заменяет” их,
    • он передаёт служебную информацию о доставке, маршрутизации и ошибках.
  1. Основные применения ICMP

На практике ICMP используется для:

  • Проверки доступности узлов:

    • утилита ping:
      • отправляет ICMP Echo Request,
      • ожидает ICMP Echo Reply.
    • Если есть ответ — хост на IP-уровне доступен (с учётом того, что ICMP мог быть ограничен firewall’ом).
  • Диагностики маршрутов:

    • классический traceroute (в разных реализациях):
      • использует TTL/HL и ICMP Time Exceeded от промежуточных маршрутизаторов;
      • в Linux по умолчанию traceroute для UDP/TCP, но ICMP-сообщения играют ключевую роль в диагностике.
    • mtr:
      • сочетает ping + traceroute, активно опирается на ICMP-ответы.
  • Сообщения об ошибках:

    • Destination Unreachable:
      • хост/сеть недоступны,
      • порт недоступен (например, для UDP),
      • административно запрещено (файрвол/ACL).
    • Time Exceeded:
      • TTL истёк по пути (защита от петель маршрутизации, используется traceroute).
    • Fragmentation needed (для Path MTU Discovery):
      • сообщает, что пакет слишком большой и требует уменьшения MTU.

Это критично для корректной работы:

  • диагностики сетевых проблем,
  • path MTU discovery,
  • понимания, почему трафик не доходит.
  1. Важно: ICMP и ARP — разные вещи

Типичная ошибка — связывать ICMP с ARP.

  • ARP (Address Resolution Protocol):
    • работает между L2 и L3,
    • сопоставляет IP-адреса и MAC-адреса в одной L2-сети (IPv4).
    • Относится к канальному уровню (или “L2.5”) в практической терминологии.
  • ICMP:
    • не занимается резолвингом MAC-адресов,
    • это отдельный протокол поверх IP для передачи служебных сообщений.

Корректная формулировка:

  • ARP нужен, чтобы доставить IP-пакет до нужного узла внутри локального сегмента.
  • ICMP нужен, чтобы сообщить о проблемах доставки/маршрутизации или выполнить диагностику (ping, traceroute, MTU и т.п.).
  1. Практическое значение для инженера

Понимание ICMP важно, потому что:

  • Если ping не работает:
    • это может быть:
      • реальная недоступность хоста,
      • блокировка ICMP (firewall, security policy),
    • нельзя автоматически делать вывод “хост умер”.
  • Многие сетевые механизмы зависят от ICMP:
    • отключение ICMP по соображениям “безопасности” может ломать:
      • Path MTU Discovery (фрагментация/черные дыры),
      • корректную диагностику.

При отладке:

  • Используем:
    • ping для проверки базовой сетевой доступности;
    • traceroute/mtr для анализа пути и мест, где возникают проблемы;
    • tcpdump для просмотра ICMP-сообщений при сложных кейсах.

Краткое резюме:

  • ICMP — протокол сетевого уровня (L3), тесно связанный с IP.
  • Используется для:
    • диагностики (ping, traceroute),
    • сообщений об ошибках доставки,
    • служебной информации (включая MTU).
  • Не связан с ARP по функционалу: ARP решает задачу сопоставления IP→MAC, ICMP — служебные сообщения для IP-сети.

Вопрос 15. Зачем Ansible-роль должна быть идемпотентной и как практично проверять идемпотентность при разработке?

Таймкод: 00:22:18

Ответ собеседника: правильный. Говорит, что идемпотентность нужна, чтобы повторный запуск не создавал уже существующие сущности и не применял лишние изменения; проверяет по выводу Ansible и отсутствию лишних changed, учитывает особенности shell-модуля.

Правильный ответ:

Идемпотентность — фундаментальный принцип для инфраструктуры как кода и автоматизации конфигураций. Для Ansible это не просто “хорошо бы”, а практический стандарт качества.

Суть:

  • Идемпотентная роль гарантирует:
    • повторный запуск playbook'а в том же окружении:
      • не ломает систему,
      • не вносит неожиданных изменений,
      • детерминированно приводит состояние к заданному описанию.

Это критично для:

  • воспроизводимости,
  • безопасных повторных деплоев,
  • предсказуемых rollback-ов,
  • корректной работы CI/CD и GitOps-подхода.

Почему идемпотентность обязательна на практике

  1. Повторяемость и безопасность

В продакшене плейбуки запускаются многократно:

  • при новых релизах,
  • при добавлении нод,
  • при восстановлении после сбоев,
  • при дрейфе конфигурации.

Если роль не идемпотентна:

  • каждый запуск может:
    • пересоздавать ресурсы,
    • перезаписывать конфиги без причины,
    • дергать рестарт сервисов без необходимости,
    • ломать уже работающие системы.
  • итог:
    • нестабильность,
    • сложность расследования инцидентов,
    • страх запускать автоматизацию “лишний раз”.

Идемпотентная роль позволяет запускать:

  • “хоть каждый час” как drift correction,
  • без риска побочных эффектов.
  1. Прозрачный diff и контроль изменений

Стандартный рабочий процесс:

  • Сначала прогон с --check и --diff:
    • видно, какие реальные изменения будут внесены,
  • Затем обычный запуск:
    • изменения вносятся ровно один раз,
    • повторный запуск показывает, что всё в состоянии “ok”, без changed.

Это возможно только при идемпотентных задачах.

  1. Масштабирование и GitOps

При управлении десятками/сотнями нод:

  • нужен детерминизм:
    • одна и та же роль должна привести ноды к одному и тому же целевому состоянию,
    • независимо от того:
      • новый это сервер,
      • или давно живущий, на котором уже были изменения.

Идемпотентность — ключ к тому, чтобы описывать состояние декларативно, а не выполнять набор “магических скриптов”.

Практические техники обеспечения идемпотентности

  1. Использовать декларативные модули Ansible, а не shell/command

Предпочтение:

  • file, copy, template, user, group, lineinfile, blockinfile, git, service, package, apt, yum, systemd, docker_* и т.д.
  • Эти модули:
    • знают текущее состояние,
    • применяют изменения только при необходимости,
    • корректно поддерживают check_mode и changed_when.

Пример (хорошо):

- name: Ensure nginx is installed
apt:
name: nginx
state: present
become: yes

Плохой вариант (неидемпотентный, fragile):

- name: Install nginx
shell: apt-get install -y nginx
become: yes
  1. Аккуратная работа с template и file
  • Если контент реально не меняется — таска не должна давать changed.

Пример:

- name: Deploy config
template:
src: app.conf.j2
dest: /etc/myapp/app.conf
owner: root
group: root
mode: '0644'
notify: Restart myapp

template сам сравнивает содержимое: если рендер не изменился — нет changed, нет лишнего рестарта.

  1. Обход подводных камней с shell/command

Если без shell/command не обойтись:

  • используем:
    • creates / removes,
    • changed_when,
    • check_mode: no при необходимости.

Примеры:

- name: Run db migration once
command: /usr/local/bin/migrate up
args:
creates: /var/lib/myapp/.migrated
become: yes

Или:

- name: Custom command but mark no change on success
command: some-idempotent-check
changed_when: false
  1. Корректная работа с сервисами

Не рестартовать сервис без причины:

  • использовать handlers:
    • таски вызывают notify только при реальных изменениях.
  • Не писать:
- name: Restart nginx always
service:
name: nginx
state: restarted
  • Вместо этого:
handlers:
- name: Restart nginx
service:
name: nginx
state: restarted

И notify из тех тасок, которые реально поменяли конфигурацию.

Как проверяется идемпотентность на практике

Реалистичный, профессиональный цикл разработки роли:

  1. Первый прогон роли:
ansible-playbook site.yml -l test-host
  • Ожидаем корректные changed на создании ресурсов.
  1. Повторный прогон той же роли на том же хосте:
ansible-playbook site.yml -l test-host
  • Ожидаем:
    • 0 задач в статусе changed (или только строго обоснованные, например, динамические timestamp'ы, если они не критичны).
  • Если задачи показывают changed без реальных изменений:
    • это баг в идемпотентности:
      • нужно исправить (правильные модули, условия, параметры).
  1. Использование --check и --diff:
ansible-playbook site.yml -l test-host --check --diff
  • Проверка:
    • в check-режиме роль должна адекватно предсказывать изменения,
    • не ломаться на shell/command без поддержки check-mode,
    • не показывать “вечный diff” для неизменяемых сущностей.
  1. Интеграция с Molecule / CI

Для зрелых ролей:

  • использовать Molecule (или аналогичные подходы) для автоматического тестирования:
    • развернули контейнер/VM,
    • применили роль — зафиксировали изменения,
    • применили второй раз — проверили, что изменений нет.

Это превращает идемпотентность из “хотелки” в проверяемый контракт.

Итог:

  • Идемпотентность Ansible-ролей:
    • обеспечивает безопасность повторных запусков,
    • делает конфигурацию декларативной и предсказуемой,
    • является необходимым условием для автоматизации, CI/CD и GitOps.
  • Проверка:
    • минимум — двойной запуск и анализ changed,
    • плюс корректное использование модулей, check-mode и, при необходимости, Molecule/CI.
  • Любая роль, которая стабильно даёт “ложные changed” или ломает состояние при повторном запуске — требует доработки.

Вопрос 16. Как должен вести себя Ansible при удалении файла модулем file при повторном запуске роли?

Таймкод: 00:23:20

Ответ собеседника: правильный. Описывает идемпотентное поведение: при первом запуске файл удаляется; при повторном, если файла уже нет, модуль file успешно отрабатывает без изменений.

Правильный ответ:

Для модуля file с параметром state=absent ожидается строго идемпотентное поведение, соответствующее декларативной модели "инфраструктура как код":

  • Целевое состояние: указанный файл (или директория, или symlink) должен отсутствовать.
  • Поведение по шагам:
    • Первый запуск:
      • если файл существует:
        • файл удаляется,
        • задача возвращает changed: true;
      • если файла нет:
        • ничего не делается,
        • задача возвращает changed: false.
    • Повторные запуски:
      • при отсутствии файла:
        • модуль не пытается "удалить ещё раз",
        • возвращает успешный результат без изменений (changed: false).

Это и есть корректная идемпотентность: после приведения к целевому состоянию повторные запуски не вносят дополнительных изменений и не генерируют ложных changed.

Пример корректного использования:

- name: Ensure legacy config file is absent
ansible.builtin.file:
path: /etc/myapp/old.conf
state: absent

Как это интерпретируется:

  • Мы декларативно описываем желаемое состояние: "этого файла быть не должно".
  • Ansible сам определяет:
    • нужно ли удалить файл (если он есть),
    • или ничего не делать (если уже отсутствует).

Практическая значимость:

  • Такое поведение:
    • безопасно для многократных запусков ролей,
    • не вызывает ложных перезапусков сервисов, завязанных на notify, если нет реальных изменений,
    • упрощает отладку и контроль дрейфа конфигурации.
  • Если бы модуль при каждом запуске показывал changed, даже когда файла нет:
    • это ломало бы идемпотентность,
    • загрязняло бы вывод,
    • могло бы вызывать ненужные handler'ы и перезапуски.

Коротко:

  • Для file: state=absent:
    • первый запуск может быть с changed: true (если файл был удалён),
    • все последующие — changed: false, пока файл не появится снова.
  • Такое поведение является эталонным и ожидаемым при разработке качественных Ansible-ролей.

Вопрос 17. Каков приоритет переменных Ansible между defaults роли и переменными, заданными в плейбуке?

Таймкод: 00:24:03

Ответ собеседника: неправильный. Сначала верно говорит, что переменные плейбука приоритетнее, но затем путается и утверждает обратное, переворачивая порядок приоритета.

Правильный ответ:

В Ansible иерархия переменных строго определена и критична для предсказуемого поведения ролей. В контексте вопроса важно чётко понимать:

  • переменные из defaults роли — это самые низкоприоритетные значения;
  • переменные, определённые в плейбуке (vars), имеют более высокий приоритет и должны переопределять defaults роли.

Ключевой принцип:

  • defaults/main.yml внутри роли — это безопасные значения “по умолчанию”,
  • любые более конкретные источники (vars в плейбуке, group_vars, host_vars и т.д.) могут и должны их переопределять.

Упрощённо, применительно к сравнению “defaults роли vs плейбук”:

  • приоритет (от низкого к высокому):
    • defaults роли
    • vars, определённые в плейбуке

То есть:

  • Если переменная определена в defaults/main.yml роли, а та же переменная определена в секции vars плейбука, то будет использовано значение из плейбука.

Простой пример:

Роль my_role:

defaults/main.yml:

my_app_port: 8080

Плейбук:

- hosts: app
vars:
my_app_port: 9090

roles:
- role: my_role

Результат:

  • Внутри роли my_role значение my_app_port будет 9090, а не 8080.
  • Именно так и должно быть: роль задаёт "reasonable defaults", плейбук (и окружающая конфигурация) определяют реальные значения под конкретное окружение.

Практический смысл такой модели:

  1. Роли должны быть:
    • переиспользуемыми,
    • не навязывающими жёсткие значения,
    • легко конфигурируемыми снаружи.
  2. defaults:
    • задают значения, которые позволят роли “завестись из коробки”,
    • но не блокируют настройку через:
      • vars в плейбуке,
      • group_vars,
      • host_vars,
      • inventory vars,
      • extra_vars (самый верхний приоритет).
  3. Это критично для:
    • развертывания одного и того же набора ролей в разных окружениях (dev/stage/prod),
    • использования ролей в сторонних проектах.

Распространённая ошибка (как в ответе кандидата):

  • считать, что defaults роли "главнее", потому что находятся “внутри роли”.
  • На самом деле всё наоборот:
    • defaults — нижний слой,
    • конкретизация — снаружи.

Если подходить зрелым образом:

  • при разработке роли:
    • все настраиваемые параметры объявляем в defaults/main.yml,
    • используем их внутри роли,
    • документируем,
    • а в плейбуках и group_vars/host_vars переопределяем нужные под окружение.
  • при отладке конфликтов:
    • помним, что значение могло прийти из более приоритетного источника, чем defaults.

Краткое резюме по вопросу:

  • Между defaults роли и переменными плейбука:
    • переменные плейбука имеют более высокий приоритет,
    • defaults роли всегда могут быть переопределены из плейбука и других источников.

Вопрос 18. Как запустить роль Ansible последовательно на группе серверов, а не на всех одновременно?

Таймкод: 00:25:25

Ответ собеседника: правильный. Предлагает использовать стратегии выполнения и параметр serial для ограничения числа хостов, обрабатываемых одновременно, что соответствует практике пошагового/rolling деплоя.

Правильный ответ:

При работе с группой серверов в продакшене часто важно не обновлять все узлы сразу, а выполнять деплой поэтапно:

  • по одному хосту,
  • по нескольку хостов за шаг,
  • с возможностью остановиться при ошибках.

Ansible предоставляет для этого встроенные механизмы управления параллелизмом и стратегией выполнения. Ключевые инструменты: директива serial и стратегии (strategy), иногда в сочетании с max_fail_percentage и any_errors_fatal.

Базовый механизм: директива serial

serial определяет, сколько хостов из inventory-группы обрабатываются одновременно в рамках одного play.

Примеры:

  1. Полностью последовательный деплой (по одному хосту):
- hosts: app_servers
serial: 1

roles:
- my_app

Поведение:

  • Ansible:
    • берёт первый хост → выполняет все задачи play,
    • затем второй,
    • и так далее.
  • Это безопасный вариант для:
    • stateful-сервисов,
    • критичных систем, где нельзя “уронить” сразу все инстансы.
  1. Rolling-деплой батчами

Например, по два сервера за раз:

- hosts: app_servers
serial: 2

roles:
- my_app

Или гибко, в процентах:

- hosts: app_servers
serial: 30%

roles:
- my_app

Поведение:

  • Обновляет 30% хостов, проверяет результат,
  • затем следующие 30% и так далее.

Это подходит для:

  • горизонтально масштабируемых сервисов за балансировщиком,
  • микросервисов, где часть инстансов может быть временно недоступна.

Дополнительные настройки для надёжности

  1. Ограничение по ошибкам:
  • max_fail_percentage:
    • если процент упавших хостов превышает порог — play останавливается.

Пример:

- hosts: app_servers
serial: 2
max_fail_percentage: 10

roles:
- my_app
  1. Жёсткая остановка при ошибке:
  • any_errors_fatal: true:
    • если падает один хост — считаем деплой провальным, прекращаем выполнение на остальных.
- hosts: app_servers
serial: 1
any_errors_fatal: true

roles:
- my_app

Стратегии выполнения (strategy)

По умолчанию используется стратегия linear, которая нормально работает с serial и обрабатывает хосты батчами.

Опции:

  • strategy: linear:
    • стандарт, предсказуемое поведение, рекомендуется для контролируемого деплоя.
  • strategy: free:
    • хосты бегут независимо,
    • может быть удобно для ускорения, но хуже для строгого поэтапного контроля.

Для последовательного/rolling-деплоя критичных сервисов предпочтительно:

- hosts: app_servers
strategy: linear
serial: 1

roles:
- my_app

Практические рекомендации

  • Для продакшн-деплоя:
    • используйте serial обязательно, а не обновляйте все хосты разом:
      • особенно для БД, брокеров, stateful-сервисов, критичных API.
  • Комбинируйте:
    • serial + health-check таски внутри роли:
      • после деплоя на батч:
        • проверить статус сервиса,
        • проверить HTTP health endpoint,
        • только потом переходить к следующему батчу.
  • Используйте балансировщик:
    • перед обновлением батча:
      • временно вынимать хосты из LB,
      • деплой,
      • проверка,
      • возвращать в LB.

Такой подход делает деплой предсказуемым, уменьшает blast radius и соответствует зрелой практике эксплуатации распределённых систем.

Вопрос 19. Как безопасно работать с чувствительными данными (кредами) в шаблонах Ansible?

Таймкод: 00:25:58

Ответ собеседника: правильный. Описывает использование Ansible Vault для хранения секретов в плейбуках и файлах переменных, упоминает дополнительные инструменты (например, sops), дешифрование при выполнении, отсутствие открытых секретов в репозитории и внимание к безопасности локального окружения.

Правильный ответ:

Безопасная работа с секретами в Ansible и шаблонах — обязательное требование для любой продакшн-инфраструктуры. Цель: все чувствительные данные (пароли, токены, ключи, DSN с кредами, private keys) должны быть:

  • зашифрованы или получаться из доверенного секрета-сторанжа;
  • не храниться в открытом виде в Git;
  • не "утекать" в логи, stdout, артефакты CI;
  • использоваться в шаблонах только через переменные.

Ключевые практики.

Использование Ansible Vault

Ansible Vault — встроенный механизм шифрования чувствительных данных.

Основные принципы:

  • Секреты хранятся в:
    • отдельных файлах переменных (group_vars/host_vars/vars),
    • или инлайном в плейбуках/ролях (менее предпочтительно),
    • в зашифрованном виде.
  • Доступ:
    • при выполнении playbook Ansible расшифровывает данные:
      • по паролю (--ask-vault-pass) или
      • по файлу с паролем (--vault-password-file),
      • в CI/Prod — через защищённые переменные/secret storage.

Пример: файл group_vars/prod/vault.yml:

db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
6438376335643437663263...

В шаблоне:

DATABASE_URL=postgres://app:{{ db_password }}@db-prod:5432/app

Код в репозитории:

  • содержит только зашифрованный vault-файл,
  • шаблон не раскрывает секретов,
  • расшифровка происходит только на контроллере/CI при запуске.

Правила работы с Vault:

  • Не коммитить vault-пароль в репозиторий.
  • Раздавать доступ к паролю только ограниченному кругу людей/систем.
  • Использовать отдельные vault-файлы или ключи для разных окружений (dev/stage/prod).

Интеграция с внешними хранилищами секретов

Для зрелых инфраструктур часто предпочтительнее хранить секреты вне Ansible:

  • HashiCorp Vault,
  • AWS Systems Manager Parameter Store / Secrets Manager,
  • GCP Secret Manager,
  • Azure Key Vault,
  • Kubernetes Secrets (с осторожностью),
  • sops + KMS/GPG.

Подход:

  • Ansible получает секреты динамически:
    • через lookup-плагины (например, hashicorp_vault, aws_ssm, aws_secret, community.general.hashi_vault),
    • через sops-интеграцию.
  • В Git:
    • либо хранятся только ссылки/ключи запросов,
    • либо зашифрованные данные (sops), дешифрование — только в рантайме.

Пример использования sops (идеи):

  • Файл vars.prod.yaml зашифрован sops (AES/GPG/KMS).
  • Ansible через ansible-vault или отдельный плагин дешифрует при запуске.
  • Секреты остаются зашифрованными в репозитории.

Безопасное использование в шаблонах

Ключевые моменты:

  • Никогда не хардкодить секреты в шаблонах, ролях, задачах.
  • Всегда работать через переменные:
    • значения которых приходят из Vault или секрета-сторанжа.
  • Исключать секреты из логов:
    • использовать no_log: true для задач, где возможна утечка:
- name: Configure application with secret env
template:
src: app.env.j2
dest: /etc/myapp/app.env
owner: app
group: app
mode: '0640'
no_log: true
  • Следить, чтобы:
    • handler'ы, debug, register не печатали секреты.

Практика для CI/CD и рабочих мест

  • В CI:
    • доступ к vault-паролю или токену внешнего секрет-хранилища даётся только через зашифрованные переменные пайплайна.
    • логи сборки не содержат секретов.
  • На локальных машинах:
    • хранить vault-пароль в защищённом виде (ssh-agent, gpg-agent, pass, 1Password, Bitwarden и т.п.).
    • не писать пароль в history (использовать --ask-vault-pass или файловый дескриптор).

Что важно с точки зрения зрелости

Хороший, практический подход к секретам в Ansible и шаблонах включает:

  • Все чувствительные данные:
    • либо в Ansible Vault,
    • либо в внешнем secrets storage,
    • либо в sops (GitOps-friendly).
  • В репозитории:
    • нет открытых паролей, токенов, private keys, DSN с кредами.
  • В шаблонах:
    • используются только переменные, без хардкода,
    • секреты не протекают в debug и логи.
  • В процессе:
    • есть ревью и аудит изменений vault/sops-файлов,
    • доступ к ключам ограничен и управляется.

Такой подход позволяет безопасно автоматизировать деплой, не жертвуя безопасностью ради удобства.

Вопрос 20. Использовалось ли автоматизированное тестирование Ansible-ролей (например, Molecule), и как его правильно выстраивать?

Таймкод: 00:28:52

Ответ собеседника: неполный. Знает о существовании Molecule, но практически не использовал и не выстроил процесс автоматизированного тестирования ролей.

Правильный ответ:

Автоматизированное тестирование Ansible-ролей — это не “опция для перфекционистов”, а нормальная практика для любой инфраструктуры как кода, где важны предсказуемость, повторяемость и отсутствие регрессий при изменениях.

Ключевая цель:

  • гарантировать, что роль:
    • выполняется без ошибок на целевых платформах,
    • идемпотентна,
    • действительно приводит систему в ожидаемое состояние,
    • не ломает совместимость при изменениях.

Базовый и наиболее распространённый инструмент для этого — Molecule.

Основные принципы тестирования ролей с Molecule

  1. Изоляция и быстрый feedback

Molecule позволяет:

  • запускать роль в изолированном окружении:
    • Docker-контейнеры,
    • Podman,
    • Vagrant (VM),
    • cloud-драйверы (EC2 и др.);
  • прогонять:
    • полный цикл: create → converge → idempotence → verify → destroy;
  • получать быстрый фидбек на каждый коммит.

Это существенно снижает риск того, что изменения роли “сломают” прод или стейдж.

  1. Структура сценария Molecule

Типичный workflow Molecule-сценария:

  • molecule create:
    • поднять тестовое окружение (контейнер/VM).
  • molecule converge:
    • применить тестируемую роль (как в реальном плейбуке).
  • molecule idempotence:
    • повторно применить роль и убедиться, что:
      • нет лишних изменений (changed: 0),
      • роли действительно идемпотентны.
  • molecule verify:
    • выполнить тесты верификации:
      • через testinfra, pytest, shell-check-и,
      • проверить файлы, сервисы, порты, конфиги.
  • molecule destroy:
    • снести окружение, чтобы не копить мусор.
  1. Примеры проверок (verify)

Для реальной роли важно проверять не только “play прошёл без ошибок”, но и состояние системы.

Например, роль деплоя Go-сервиса:

  • Проверяем, что:
    • бинарь на месте,
    • systemd unit создан,
    • сервис запущен и работает,
    • слушает нужный порт,
    • конфиг соответствует ожиданиям.

Пример verify через Testinfra (Python):

def test_binary_exists(host):
f = host.file("/usr/local/bin/my-service")
assert f.exists
assert f.mode & 0o111 # исполняемый

def test_service_running(host):
s = host.service("my-service")
assert s.is_enabled
assert s.is_running

def test_port_listening(host):
sock = host.socket("tcp://0.0.0.0:8080")
assert sock.is_listening

Такой набор тестов превращает роль в проверяемый артефакт, а не набор “магических yaml-файлов”.

  1. Интеграция с CI/CD

Зрелая практика:

  • Для каждой роли (или монорепозитория с ролями):
    • на каждый PR/commit:
      • запускается Molecule-сценарий:
        • создаётся окружение,
        • применяется роль,
        • проверяется идемпотентность,
        • выполняются проверки состояния.
  • При падении:
    • изменения не мержатся, пока не починены тесты.

Это:

  • защищает от регрессий,
  • гарантирует, что роль рабочая на заявленных платформах (Ubuntu, Debian, CentOS, etc.), если настроены матрицы окружений.
  1. Альтернативы и дополнения

Помимо Molecule можно использовать:

  • Самодельные сценарии:
    • ansible-playbook в Docker/VM + testinfra/pytest/bash-проверки.
  • Интеграцию с:
    • Kitchen (less common),
    • Terratest (если Ansible интегрирован в более широкий IaC-стек),
    • линтеры:
      • ansible-lint для стайлгайдов и типичных ошибок.

Но Molecule:

  • даёт стандартный, удобный и хорошо поддерживаемый подход,
  • интегрируется с ansible-lint и CI.
  1. Что важно уметь и понимать

Качественный ответ и реальная практика включают:

  • Использовать Molecule или аналог для:
    • локального тестирования ролей перед пушем,
    • автоматических проверок в CI.
  • Проверять:
    • успешное выполнение play,
    • идемпотентность (второй прогон без changed),
    • фактическое состояние (через testinfra/pytest или другие проверки).
  • Учитывать разные платформы:
    • матрица тестов: ubuntu-lts, debian, rhel-based и т.п., если роль должна их поддерживать.

Кратко:

  • “Знаю о Molecule, но не использовал” — это базовый уровень.
  • Ожидаемый профессиональный подход:
    • роли тестируются автоматически,
    • идемпотентность и корректность не проверяются вручную “на глаз”, а формализованы в тестах и CI-пайплайне.

Вопрос 21. Что означает конструкция в GitLab CI, когда имя job начинается с точки, и как она используется?

Таймкод: 00:29:27

Ответ собеседника: правильный. Корректно описывает скрытые jobs/шаблоны: такие определения используются как базовые конфигурации, которые затем расширяются через extends, позволяя не дублировать однотипные стадии.

Правильный ответ:

В GitLab CI конструкции, где имя job или конфигурационного блока начинается с точки (например, .base, .test_template), используются как шаблоны (hidden jobs / hidden config). Они:

  • не запускаются как отдельные pipeline-джобы;
  • служат для переиспользования общих настроек;
  • подключаются в реальные джобы через extends или как anchors/aliases в YAML.

Это ключевой приём для построения масштабируемых, поддерживаемых CI-конфигураций.

Основные свойства и поведение

  1. Hidden job / шаблон

Если job в .gitlab-ci.yml начинается с точки:

.base_job:
image: golang:1.22
before_script:
- go version
tags:
- docker
  • GitLab:
    • не воспринимает .base_job как реальную задачу pipeline;
    • не показывает её в UI;
    • не запускает её напрямую.
  • Это просто именованный блок настроек, который можно расширять.
  1. Использование через extends

Реальные джобы объявляются без точки и могут “наследовать” конфигурацию скрытого шаблона:

unit_tests:
extends: .base_job
script:
- go test ./...

lint:
extends: .base_job
script:
- go vet ./...
- golangci-lint run

Результат:

  • unit_tests и lint:
    • используют image, before_script, tags из .base_job,
    • добавляют свои script.
  • Избегаем дублирования:
    • если поменять образ или общие шаги, достаточно изменить .base_job.
  1. Комбинирование нескольких шаблонов

GitLab CI позволяет использовать массив в extends, чтобы собирать конфигурацию из нескольких шаблонов:

.default_go:
image: golang:1.22
before_script:
- go env
tags: [docker]

.with_cache:
cache:
key: "go-mod-cache"
paths:
- go/pkg/mod

tests:
extends:
- .default_go
- .with_cache
script:
- go test ./...

Так можно:

  • разделить ответственность:
    • один шаблон — базовый образ и окружение,
    • другой — кеширование,
    • третий — правила запуска;
  • собирать итоговую джобу как композицию, а не через копипаст.
  1. Отличие от YAML anchors/aliases

Параллельно со скрытыми job'ами используются YAML-якоря:

.go-base: &go-base
image: golang:1.22
before_script:
- go version

unit_tests:
<<: *go-base
script:
- go test ./...

Разница:

  • anchors — фича YAML:
    • работают на этапе парсинга,
    • GitLab о них не “знает” как о семантическом объекте;
  • скрытые jobs (.job_name) + extends — семантика GitLab CI:
    • учитывает merge правил, override поведений и т.п.

На практике разумно:

  • использовать скрытые job'ы как основные шаблоны;
  • anchors — для низкоуровневого DRY, если нужно.
  1. Практическое значение для реальных пайплайнов

Подход с "точечными" job'ами особенно важен, когда:

  • есть много однотипных стадий:
    • build разных сервисов,
    • тесты, линтеры, security-scans;
  • требуется:
    • единое окружение (образ, кеш),
    • единые правила (only/except, rules, retry, artifacts);
  • нужно:
    • быстро и централизованно менять требования (например, обновить Go-версию со всех job'ов с 1.20 до 1.22).

Пример для микросервисов на Go:

.default_go_job:
image: golang:1.22
tags: [docker]
before_script:
- go mod download

build_service_a:
extends: .default_go_job
script:
- cd services/a
- go build -o app

build_service_b:
extends: .default_go_job
script:
- cd services/b
- go build -o app

Изменения в .default_go_job автоматически применятся ко всем build_* джобам.

Кратко:

  • Имя job с точкой в GitLab CI — это скрытый шаблон / базовая конфигурация.
  • Он:
    • не исполняется как отдельный job,
    • используется через extends (и/или YAML anchors) для DRY и единообразия.
  • Это стандартный и ожидаемый приём при построении сложных, поддерживаемых CI-конфигураций.

Вопрос 22. Каков минимальный набор git-команд, чтобы отправить изменения в удалённый репозиторий с учётом веток и merge request?

Таймкод: 00:30:42

Ответ собеседника: правильный. Описывает базовый рабочий цикл: clone → изменения → add → commit → push, упоминает создание отдельной ветки при защищённых ветках и использование merge request, что соответствует хорошей практике.

Правильный ответ:

Минимальный, но правильный рабочий процесс с Git в командной разработке (особенно с защищённой main/master веткой и обязательными MR/PR) должен включать:

  • работу в отдельной ветке,
  • чистую историю коммитов,
  • создание merge request вместо прямого пуша в защищённые ветки.

Базовый сценарий выглядит так.

  1. Клонирование репозитория
git clone git@gitlab.example.com:group/project.git
cd project
  1. Создание рабочей ветки от актуальной основной

Всегда начинаем с обновлённой основной ветки (main/master):

git checkout main
git pull origin main
git checkout -b feature/my-change

Комментарии:

  • git checkout -b создаёт новую ветку и сразу переключает на неё.
  • Имена веток должны быть осмысленными:
    • feature/..., bugfix/..., hotfix/..., chore/... и т.п.
  • В защищённые main/master обычно запрещён прямой push — и это правильно.
  1. Внесение изменений и индексирование

После изменения файлов:

  • Проверяем статус:
git status
  • Добавляем изменённые файлы:
git add path/to/file1 path/to/file2
# или все отслеживаемые изменения:
git add -u
# или и новые файлы тоже:
git add .
  1. Создание коммита

Формируем осмысленное сообщение:

git commit -m "Short, meaningful description of the change"

Рекомендации:

  • Сообщение должно объяснять суть изменения:
    • не "fix", а, например:
      • "Fix race in HTTP handler shutdown",
      • "Add healthcheck endpoint for UDP gateway",
      • "Refactor Ansible role for idempotent Nginx config".
  • В серьёзных проектах:
    • часто используются соглашения (Conventional Commits и т.п.).
  1. Отправка ветки в удалённый репозиторий

Первый push новой ветки:

git push -u origin feature/my-change

Флаги:

  • -u (или --set-upstream):
    • связывает локальную ветку с удалённой,
    • далее можно делать просто git push / git pull без указания origin/ветки.

Следующие итерации:

  • после новых изменений:
    • git add ...
    • git commit -m "..."
    • git push
  1. Создание Merge Request (Pull Request)

Далее:

  • Через интерфейс GitLab/GitHub/Gitea:
    • создаём MR/PR из feature/my-change в main (или нужную целевую ветку),
    • проходим code review, CI-пайплайны, проверки.
  • В некоторых CLI/инструментах:
    • можно создавать MR/PR командами (glab, gh), но это уже надстройка.
  1. Обновление ветки при изменениях в main

Если пока идёт ревью, основная ветка продвинулась:

  • Подтянуть изменения и обновить свою ветку:
git checkout main
git pull origin main
git checkout feature/my-change
git rebase main
# или merge, если в команде принята такая стратегия:
# git merge main

После ребейза/мержа:

git push --force-with-lease
  • --force-with-lease:
    • безопаснее обычного --force,
    • не перезаписывает чужие изменения.
  1. Краткий минимальный набор для базового случая

Если свести к самому необходимому (без ребейзов, конфликтов и т.д.):

  • Клонировать:
git clone <repo>
cd <repo>
  • Создать ветку:
git checkout -b feature/my-change
  • Зафиксировать изменения:
git add .
git commit -m "Describe change"
  • Отправить ветку:
git push -u origin feature/my-change
  • Создать MR в UI.

Этот сценарий:

  • соответствует ожиданиям в современных командах,
  • учитывает защищённые ветки,
  • интегрируется с code review и CI/CD,
  • минимален по количеству команд, но правильный по практике.

Вопрос 23. Чем отличается Continuous Deployment от Continuous Delivery?

Таймкод: 00:31:44

Ответ собеседника: правильный. Разграничивает: в одном случае деплой до продакшена происходит автоматически, в другом — требуется дополнительное ручное действие/подтверждение перед выкладкой, что соответствует сути терминов.

Правильный ответ:

Термины тесно связаны, но различие между ними принципиально важно для проектирования CI/CD-процессов, уровней автоматизации и политики рисков.

Кратко:

  • Continuous Delivery:

    • Каждое изменение, прошедшее pipeline (сборка, тесты, проверки), находится в состоянии "готово к продакшену".
    • Выкатка в продакшен не обязана быть автоматической:
      • обычно требуется явное действие:
        • нажатие кнопки,
        • подтверждение change-менеджмента,
        • согласование окна деплоя.
    • Ключевая идея:
      • вы всегда можете быстро и безопасно деплоиться,
      • артефакт всегда деплойбелен,
      • но сам акт деплоя контролируем (по времени/руками/процессу).
  • Continuous Deployment:

    • Полная автоматизация пути до продакшена.
    • Каждое изменение в main (или другой "release"-ветке), успешно прошедшее все проверки в CI/CD:
      • автоматически выкатывается в прод,
      • без ручного шага "approve to deploy".
    • Ключевая идея:
      • максимально короткий feedback loop,
      • высокая частота релизов (десятки/сотни в день),
      • все проверки качества и стабильности зашиты в pipeline.

Структурно:

  1. Continuous Integration (CI)

Общая база:

  • каждое изменение:
    • интегрируется часто,
    • автоматически собирается,
    • прогоняются тесты (unit, интеграционные, линтеры, security-checkи),
    • предотвращается “integration hell”.

И CI, и CD-подходы подразумевают зрелый CI как фундамент.

  1. Continuous Delivery

Типичный pipeline:

  • Коммиты → CI (сборка, тесты, линтеры).
  • Сборка артефакта (docker image, бинарь, helm chart и т.п.).
  • Автоматический деплой на:
    • dev / test / stage окружения.
  • Автоматические проверки:
    • smoke-тесты,
    • интеграционные тесты,
    • e2e,
    • проверка миграций БД.
  • Результат:
    • “green build” → помечен как готовый к выкладке в prod.

Но шаг в прод:

  • требует явного триггера:
    • manual job в GitLab CI,
    • ручной запуск CD-пайплайна,
    • change request в ITSM системе.

Сильные стороны:

  • Контролируемый выпуск фич:
    • удобно для регуляторных требований,
    • для сложных бизнес-процессов, где нужна координация.
  • При этом:
    • скорость остаётся высокой — деплой не превращается в "ритуал боли".
  1. Continuous Deployment

Требует более высокого уровня зрелости:

  • Все проверки должны быть настолько надёжными, чтобы:
    • можно было доверить им решение “можно ли выкатывать в прод”.
  • Pipeline обычно включает:
    • многоуровневые тесты,
    • статический анализ,
    • security scans,
    • проверку миграций,
    • иногда:
      • canary-релизы,
      • blue-green деплой,
      • автоматический rollback по сигналам мониторинга.

Поток:

  • Merge в main → CI → CD → автоматический деплой в прод.
  • Без человека между:
    • “прошёл pipeline” и “в продакшене”.

Сильные стороны:

  • Минимальный lead time от изменения до продакшена.
  • Очень быстрый feedback от реальных пользователей.
  • Снижение риска крупных релизов:
    • выкатываются маленькие, атомарные изменения.

Требования:

  • Высокое качество автотестов.
  • Нормальная observability:
    • метрики, логи, алерты, SLO.
  • Стратегии безопасного деплоя:
    • canary, progressive delivery,
    • feature flags,
    • авто-rollback.
  1. Как это отразить на практике (пример с GitLab CI)

Continuous Delivery (ручное переключение на прод):

stages:
- build
- test
- deploy_staging
- deploy_production

deploy_staging:
stage: deploy_staging
script: ./deploy.sh staging
environment: staging
when: on_success

deploy_production:
stage: deploy_production
script: ./deploy.sh production
environment: production
when: manual # ручной триггер
only:
- main

Continuous Deployment (авто в прод):

deploy_production:
stage: deploy_production
script: ./deploy.sh production
environment: production
when: on_success # без manual
only:
- main

При условии, что:

  • до этого были успешные build/test/stage,
  • пайплайн покрывает достаточно рисков.

Краткое резюме:

  • Continuous Delivery:
    • всё автоматизировано до состояния “готов к прод”,
    • реальный деплой в прод — контролируемый (manual/запланированный).
  • Continuous Deployment:
    • всё, что прошло проверки, автоматически уходит в продакшен,
    • без ручного подтверждения.
  • Выбор зависит от:
    • зрелости тестов и мониторинга,
    • требований бизнеса и регуляторов,
    • готовности команды жить с высокой частотой релизов.

Вопрос 24. Есть ли опыт работы с GitHub Actions или в основном используется GitLab, и насколько легко переносится опыт?

Таймкод: 00:32:47

Ответ собеседника: неполный. Указывает, что в основном работал с GitLab, GitHub Actions почти не использовал, кратко упоминает другие инструменты без деталей.

Правильный ответ:

Даже если основной практический опыт связан с GitLab CI, переход к GitHub Actions концептуально несложен: это тот же класс инструментов (CI/CD), с очень похожими принципами:

  • репозиторий-центричный подход,
  • декларативные pipeline'ы в YAML,
  • события-триггеры (push, PR, tag, schedule),
  • шаги-джобы в контейнеризированном окружении,
  • кэширование, артефакты, секреты.

Ключевое в зрелом ответе — показать, что:

  • понимание концепций CI/CD независимo от конкретного движка;
  • есть способность быстро перенести практику из GitLab CI в GitHub Actions.

Сравним и выделим важные моменты.

Базовые концепции GitHub Actions

  1. Хранение конфигурации:
  • Workflow-файлы:
    • размещаются в репозитории: .github/workflows/*.yml.
  • Аналог:
    • .gitlab-ci.yml в корне проекта.

Пример простого workflow для Go-сервиса:

name: CI

on:
push:
branches: [ main ]
pull_request:

jobs:
build-and-test:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.22'

- name: Test
run: go test ./...

Это по сути эквивалент базового GitLab CI-пайплайна со сборкой и тестами.

  1. Триггеры (on)

Вместо only/except или rules в GitLab используются события:

  • on: push, on: pull_request, on: workflow_dispatch, on: schedule, on: release, on: tag и т.д.
  • Можно гибко фильтровать по веткам/путям.
  1. Jobs и матрицы

Jobs в GitHub Actions:

  • выполняются параллельно по умолчанию (как в GitLab stages/jobs),
  • могут зависеть друг от друга через needs.

Пример матричных тестов под разные версии Go и ОС:

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [ '1.21', '1.22' ]

steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}

- run: go test ./...

Аналогично GitLab matrix jobs или динамическим конфигурациям.

  1. Повторное использование логики

Вместо extends и скрытых job'ов как в GitLab:

  • используются:
    • composite actions (переиспользуемые наборы шагов),
    • reusable workflows (workflow_call),
    • общедоступные actions из GitHub Marketplace.

Подход аналогичен:

  • вынос общих шагов (сборка, линт, деплой) в отдельные reusable workflows/actions;
  • в конкретных репозиториях подключение через короткую декларацию.
  1. Работа с секретами

Аналогично GitLab CI:

  • Секреты хранятся в:
    • Settings → Secrets and variables → Actions,
    • уровня репозитория / организации / окружения.
  • Доступ:
    • через secrets.MY_SECRET:
- name: Deploy
run: ./deploy.sh
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

Ключевые принципы безопасности:

  • не хардкодить,
  • не логировать секреты,
  • разделять секреты по окружениям.
  1. Деплой, окружения, approvals

GitHub Actions поддерживает:

  • environments (dev/stage/prod),
  • protection rules:
    • approvals перед деплоем в прод,
    • ожидание manual confirmation — аналог GitLab manual jobs.

Пример:

jobs:
deploy-prod:
runs-on: ubuntu-latest
environment:
name: production
url: https://app.example.com
needs: build
steps:
- uses: actions/checkout@v4
- run: ./deploy-prod.sh

Можно навесить требование ручного approve перед запуском job для production environment — это будет Continuous Delivery-подход.

Перенос опыта с GitLab CI в GitHub Actions

Главное, что важно показать:

  • Понимание не “как нажимать кнопки в GitLab”, а как строить процессы:
    • многостадийные pipeline'ы (build → test → lint → security → deploy),
    • стратегии деплоя (serial/rolling, blue-green, canary),
    • работа с секретами, артефактами, кэшем,
    • требования к качеству (quality gates, unit/integration tests, линтеры).

В этом контексте:

  • разработка пайплайна для Go/Ansible/Helm в GitHub Actions:
    • по сути упражнение по маппингу уже известных паттернов:
      • GitLab: stages, jobs, only/rules, artifacts, environments.
      • GitHub: jobs, needs, on, artifacts, environments.

Пример: CD для Go-сервиса в Kubernetes через GitHub Actions:

  • build + test,
  • build & push docker image,
  • deploy через kubectl/helm/ArgoCD trigger:
on:
push:
branches: [ main ]

jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- run: go test ./...
- run: docker build -t ghcr.io/org/app:${{ github.sha }} .
- run: echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin
- run: docker push ghcr.io/org/app:${{ github.sha }}

deploy:
needs: build-and-push
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- run: ./deploy.sh ${{ github.sha }}
env:
K8S_TOKEN: ${{ secrets.K8S_TOKEN }}

Такой пример показывает:

  • умение строить end-to-end цепочку,
  • а не зависимость от конкретного UI GitLab.

Итог:

  • Даже при основном опыте с GitLab CI важно демонстрировать:
    • понимание общих принципов CI/CD,
    • готовность и способность быстро применить их в GitHub Actions.
  • Отличия в синтаксисе и терминах минимальны:
    • концепции (pipelines, jobs, triggers, secrets, environments, reuse) сохраняются,
    • смена инструмента — вопрос нескольких дней практики, а не смены мышления.

Вопрос 25. Как выстроить общую схему мониторинга: какие уровни системы отслеживать и как системно работать с метриками?

Таймкод: 00:33:05

Ответ собеседника: неполный. Перечисляет уровни (виртуализация, инфраструктура, сервисы, JVM/Runtime, бизнес-метрики), говорит про базовые метрики и стандартные шаблоны Zabbix для Linux, но не даёт целостной архитектуры: как устроены сбор, хранение, агрегация, визуализация, алертинг и связь между уровнями.

Правильный ответ:

Зрелый мониторинг — это не набор разрозненных дашбордов, а цельная система наблюдаемости (observability), которая:

  • позволяет быстро ответить: “что ломается и где именно?”,
  • покрывает все уровни стека: от железа и сети до бизнес-метрик,
  • предоставляет метрики, логи и трассировки как единый инструмент расследования.

Удобно мыслить слоями и потоками данных.

Общие принципы

  • Многоуровневость:
    • каждый слой (виртуализация, ОС, сеть, сервисы, БД, бизнес-логика) имеет свои метрики и алерты.
  • Декларативность и стандартизация:
    • единые требования к метрикам, тегам/labels, дашбордам, SLO.
  • Отделение:
    • сбор,
    • хранение,
    • визуализация,
    • алертинг.
  • Цель:
    • минимизировать время от инцидента до понимания корня (MTTR),
    • обнаруживать проблемы раньше пользователей.

Типичная архитектура контура мониторинга

Можно разложить на 4 ключевых компонента:

  1. Агенты/экспортёры (сбор данных):

    • node exporter, blackbox exporter, процесс-экспортёры,
    • агенты Zabbix, telegraf, custom-пробки,
    • метрики приложений (Prometheus client в Go и др.).
  2. Хранилища метрик:

    • Prometheus (time-series),
    • VictoriaMetrics, Thanos, Cortex/Mimir (масштабирование и долговременное хранение),
    • Zabbix (более традиционный подход, часто для инфраструктуры).
  3. Визуализация:

    • Grafana как де-факто стандарт,
    • встроенные графы Zabbix (дополнительно, но не как единственный инструмент).
  4. Алертинг:

    • Prometheus Alertmanager,
    • Zabbix actions,
    • интеграции с Slack/Telegram/PagerDuty,
    • единые политики эскалаций.

Уровни мониторинга (сверху вниз и снизу вверх)

  1. Уровень виртуализации и облака

Что смотрим:

  • Хост-гипервизор / cloud:
    • загрузка CPU, steal time, overcommit,
    • память, диск, сеть,
    • состояние нод/зон/regions,
    • ошибки сториджа и сети.
  • Для managed-облаков:
    • CloudWatch/Stackdriver/и т.п.,
    • квоты, ошибки API, статус managed-сервисов.

Зачем:

  • Понять, проблема в платформе или в нашей VM/приложении.
  • Например:
    • высокий steal time → проблема oversubscription, а не в коде Go-сервиса.

Инструменты:

  • Метрики гипервизора, cloud monitor,
  • экспорт в Prometheus/Grafana,
  • алерты на доступность и ресурсы.
  1. Уровень ОС и инфраструктуры (host-level)

Каждая VM / bare-metal:

  • CPU:
    • загрузка, iowait, load average,
  • Память:
    • used/available, swap, OOM,
  • Диски:
    • свободное место, inode, latency, IOPS,
  • Сеть:
    • трафик, ошибки, дропы, established/failed соединения,
  • Сервисы:
    • systemd units, ssh, ntp, агенты.

Инструменты:

  • node_exporter + Prometheus,
  • Zabbix (Linux templates),
  • алерты:
    • диск < X%, inode < Y%,
    • OOM, постоянный высокий load, сетевые ошибки.

Цель:

  • Быстро отсеять “железо/ОС” как источник проблемы.
  • Корреляция с приложением:
    • рост latency совпал с iowait → смотреть диск,
    • таймауты HTTP совпали с ошибками сети.
  1. Уровень сетевой доступности и периметра
  • Blackbox-мониторинг:
    • внешние и внутренние HTTP/TCP/ICMP-проверки:
      • доступность API, DNS, TLS-сертификаты,
      • DNS-резолв,
      • цепочки до конкретных эндпоинтов.
  • Важные проверки:
    • /health, /ready у сервисов,
    • не только “порт открыт”, но и “сервис отвечает корректно”.

Инструменты:

  • blackbox_exporter,
  • curl/http-checks в Zabbix,
  • алерты:
    • “service not reachable from X”,
    • “TLS expires soon”.
  1. Уровень приложений (Go-сервисы и др.)

Здесь начинается главное для разработки.

Метрики приложения должны быть:

  • стандартизированы,
  • метко именованы,
  • с лейблами (service, instance, endpoint, version).

Типы метрик:

  • Технические:
    • RPS/throughput,
    • latency (p50/p90/p95/p99),
    • error rate (по типам ошибок),
    • очереди (job queue length),
    • пул коннекций к БД/кэшу,
    • goroutines, GC, heap (для Go):
      • берём из runtime/expvar/prometheus client.

Пример Go с Prometheus:

var (
reqDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Request latency",
Buckets: prometheus.DefBuckets,
},
[]string{"handler", "method", "code"},
)
)

func handler(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// ... handle ...
duration := time.Since(start).Seconds()
reqDuration.WithLabelValues("/api/v1/items", r.Method, "200").Observe(duration)
}
  • Бизнес-метрики:
    • количество заказов,
    • конверсия,
    • успешные/ошибочные транзакции,
    • баланс ключевых сущностей,
    • SLA-критичные события.

Они позволяют:

  • понять, "больно ли" пользователю,
  • отличить техпроблему без влияния на бизнес от реального инцидента.
  1. Уровень баз данных и storage

Мониторим для PostgreSQL/ClickHouse/Redis и т.п.:

  • доступность и время ответа,
  • количество подключений,
  • lock-и, deadlock-и,
  • репликацию (lag),
  • slow queries,
  • размеры таблиц и индексов,
  • для ClickHouse:
    • merges, ошибки, задержки вставок.

Инструменты:

  • postgres_exporter, clickhouse_exporter и аналоги,
  • Zabbix-шаблоны,
  • алерты:
    • рост slow queries,
    • рост лагов репликации,
    • исчерпание коннектов,
    • деградация QPS/latency.
  1. Уровень логов и трассировок

Метрики без логов и трейсинга — неполная картина.

  • Логи:
    • централизованный сбор (ELK / Loki / OpenSearch),
    • корреляция с метриками:
      • есть алерт → проваливаемся в логи конкретного сервиса/инстанса.
  • Distributed tracing:
    • OpenTelemetry, Jaeger, Tempo и т.п.,
    • позволяет увидеть путь запроса через микросервисы,
    • найти “узкое место”.

В связке:

  • метрика p99_latency выросла → смотрим трейс → видим, что 80% времени уходит на конкретный запрос к БД.

Системная работа с метриками и алертами

  1. SLI/SLO и алерты по симптомам, а не только по ресурсам

Важно не только "CPU>80%", но и:

  • SLI:
    • доля успешных запросов,
    • латентность,
    • ошибки по типам.
  • SLO:
    • например:
      • 99% запросов за 300 мс,
      • error rate < 0.5%.

Алерты:

  • на нарушение SLO:
    • рост p95 latency,
    • рост 5xx,
    • падение успешных бизнес-операций.
  • инфраструктурные алерты:
    • как контекст, а не единственный источник тревог.
  1. Многоуровневые алерты и эскалации
  • Warning:
    • ранний сигнал, не будит ночью, но требует внимания.
  • Critical:
    • влияющий на пользователей/бизнес → инцидент.

Зрелый стек:

  • Prometheus Alertmanager / Zabbix actions:
    • маршрутизация алертов,
    • подавление дубликатов,
    • зависимостей (упал хост → не спамить по каждому сервису на нём),
    • эскалации, расписания.
  1. Корреляция и drill-down

Хорошо выстроенная система позволяет по цепочке:

  • “Просел бизнес-показатель → метрики приложения → инфраструктура → логи → трейс → корень проблемы”.

Для этого:

  • метрики метятся:
    • service, instance, version, region, environment;
  • есть понятные дашборды:
    • общий overview,
    • по каждому сервису,
    • по БД,
    • по кластерам.
  1. Автоматизация и "monitoring as code"

Чтобы всё это было управляемо:

  • Дашборды, правила алертов и конфиги мониторинга:
    • хранятся в Git (JSON/YAML),
    • доставляются через CI/CD (Ansible/Terraform/API),
    • не настраиваются руками в проде без отражения в коде.

Итоговая картина

Зрелая схема мониторинга:

  • Покрывает:
    • платформу (виртуализация/облако),
    • ОС/хосты,
    • сеть и доступность,
    • приложения (технические метрики),
    • БД/очереди/кэши,
    • бизнес-метрики,
    • логи и трейсинг.
  • Использует:
    • специализированные инструменты (Prometheus, Grafana, Alertmanager, Zabbix, OpenTelemetry),
    • единые стандарты по метрикам, лейблам, дашбордам.
  • Обеспечивает:
    • раннее обнаружение проблем,
    • понятный путь расследования,
    • минимизацию ручных действий (monitoring as code).

Такой подход делает мониторинг не набором картинок, а полноценным инженерным инструментом управления надёжностью систем.

Вопрос 26. Как организовать алертинг по уровням критичности в системе мониторинга и привести примеры критичных и некритичных событий?

Таймкод: 00:37:59

Ответ собеседника: неполный. Корректно разделяет каналы доставки (mission-critical — звонки/мессенджеры, менее критичное — почта), приводит примеры (падение хоста без кластера — критично, краткосрочный рост CPU — некритично), отмечает зависимость от контекста, но не формализует подход через SLO/SLA, шумоподавление, корреляцию и группировку алертов.

Правильный ответ:

Грамотно выстроенный алертинг — это управляемая система сигналов, а не поток спама. Цель:

  • поднимать тревогу только тогда, когда есть реальный риск для пользователей или бизнеса;
  • давать инженерам контекст, а не отдельные “пищалки” по каждому симптому;
  • минимизировать alert fatigue и пропуски важных событий.

Ключевые идеи:

  • опираться на SLO/SLA и бизнес-значимость сервисов;
  • разделять уровни критичности;
  • использовать разные каналы и политики эскалации;
  • группировать и коррелировать алерты;
  • устранять шум.

Структурированный подход

  1. Базис: SLI/SLO, приоритизация и карты сервисов

Перед настройкой алертов нужно формализовать:

  • SLI (Service Level Indicator):
    • что измеряем: доступность, латентность, error rate, успешные бизнес-операции.
  • SLO (Service Level Objective):
    • целевой уровень:
      • доступность 99.9%,
      • p95 < 300 ms,
      • error rate < 0.5% и т.п.
  • Карта сервисов и приоритизация:
    • какие сервисы:
      • жизненно важны (mission-critical),
      • важны, но допускают деградацию,
      • вспомогательные.

Алерты строятся не только по “CPU>80”, а по влиянию на SLO и бизнес.

  1. Уровни критичности (severity)

Практически полезная градация (может варьироваться, но идея такая):

  • Info:
    • информационные события:
      • деплой выполнен,
      • нода добавлена,
      • плановое обслуживание.
    • Не требуют реакции on-call.
  • Warning:
    • потенциальная проблема, но SLO ещё не нарушен:
      • диск 80%,
      • latency немного растёт,
      • единичные ошибки 5xx,
      • нестабильный пинг, но без влияния на пользователей.
    • Канал: технические каналы (Slack/чат), без ночных звонков.
  • Major/High:
    • заметная деградация, есть риск или локальное нарушение SLO:
      • latency p95 выше порога X в течение N минут,
      • error rate выше порога,
      • реплика БД отстаёт,
      • недоступен один из нескольких инстансов за балансировщиком.
    • Требует внимательного разбора, но не обязательно будить всю команду ночью.
  • Critical:
    • прямое нарушение SLO/SLA, значимое влияние на пользователей или деньги:
      • недоступен критичный API,
      • 5xx/ошибки аутентификации/платежей,
      • падение единственного инстанса БД,
      • потеря кворума в кластере,
      • недоступность целого региона.
    • Требует немедленной реакции on-call, автоэскалации.
  1. Каналы доставки и эскалации

Для разных уровней критичности — разные каналы:

  • Info:
    • логирование в систему мониторинга,
    • низкоприоритетные сообщения в Slack/Teams.
  • Warning:
    • Slack/чат-канал, дашборды,
    • без SMS/звонков.
  • High:
    • уведомления в on-call канал (Slack с высоким приоритетом, email),
    • при накоплении/увеличении — возможна эскалация.
  • Critical:
    • интеграция с on-call платформой:
      • PagerDuty, VictorOps, Opsgenie,
      • звонки, push, SMS по дежурному графику.
    • Эскалации:
      • если за N минут нет ack от первого дежурного — звонок второму и т.д.

Важно:

  • одно и то же событие не должно “долбить” во все каналы сразу;
  • должна быть централизованная точка управления (Alertmanager, Zabbix actions, on-call система).
  1. Примеры критичных и некритичных событий

Критичные (примерно severity: Critical):

  • Падение единственного экземпляра критичного сервиса без резервирования:
    • единственная БД продакшена недоступна;
    • единственный API-gateway недоступен;
    • отказ некластеризованного message broker’а.
  • Error rate:
    • 5xx для ключевого API > X% в течение Y минут.
  • Нарушение бизнес-потока:
    • нельзя оформить заказ,
    • нельзя оплатить,
    • массовые ошибки логина.
  • Потеря кворума:
    • etcd/Consul/clustered storage.
  • Потенциальная потеря данных:
    • отказ дисковой подсистемы без актуального бэкапа/реплики.

Некритичные / предупреждения (Warning/High):

  • Кратковременный рост CPU до 90–100% на одной ноде:
    • без роста latency и ошибок.
  • Диск 80–85%:
    • алерт предупреждения,
    • критичный алерт — ближе к 90–95% и с трендом.
  • Высокое количество TIME_WAIT при отсутствии влияния на бизнес-метрики.
  • Одна реплика сервиса в кластере “NotReady”, но есть достаточный запас.

Это не значит “игнорировать”, а:

  • фиксировать,
  • планировать действия,
  • не будить ночами без реальной боли.
  1. Шумоподавление, группировка и корреляция

Зрелая система алертинга:

  • Группирует события:
    • если упал хост:
      • не слать 20 алертов “nginx down”, “app down”, “node exporter down” отдельно;
      • один инцидент “host X down” + связанный контекст.
  • Учитывает зависимости:
    • если недоступен upstream/БД:
      • не спамить по каждому зависимому сервису отдельно,
      • показывать корневую причину.
  • Использует устойчивые условия:
    • алерт не по одному пику, а по стабильному нарушению за интервал (avg/max over time).
  • Управляет флаппингом:
    • предотвращает постоянное “up/down” при пограничных состояниях.

В инструментах:

  • Prometheus Alertmanager:
    • routing, grouping (по service/cluster/instance), inhibit rules.
  • Zabbix:
    • dependencies, escalation rules, event correlation.
  1. Привязка к SLO/SLA и метрикам, а не только к "ресурсам"

Плохой алертинг:

  • CPU>80% → Critical.
  • Это создаёт шум и не отражает влияния.

Хороший алертинг:

  • симптомы + влияние:
    • рост p95 latency,
    • рост 5xx,
    • снижение успешных бизнес-операций,
    • в сочетании с ресурсами (CPU, диск, сеть) для диагностики.

Пример: алерт в стиле "по SLO":

  • Critical:
    • “95-й перцентиль latency /api/checkout > 500ms более 5 минут И error_rate > 2% И затронуто > N запросов”
  • Warning:
    • “latency близка к порогу, error_rate растёт, но SLO ещё не нарушен”.
  1. Практические рекомендации по реализации
  • Явно задокументировать:
    • уровни severity,
    • примеры,
    • реакцию,
    • каналы уведомлений.
  • Centralized alerting:
    • одна точка входа для алертов, где:
      • настраивается routing,
      • осуществляется эскалация,
      • ведётся история инцидентов.
  • Регулярный review:
    • разбор инцидентов (postmortem),
    • чистка шумных алертов,
    • корректировка порогов,
    • добавление “умных” симптомов вместо тупых метрик.

Краткий вывод:

  • Алертинг должен быть:
    • многоуровневым по критичности,
    • завязанным на SLO и влияние на пользователей,
    • с разными каналами и правилами эскалаций,
    • с шумоподавлением, группировкой и учётом зависимостей.
  • Критичные алерты — о нарушении доступности/бизнес-функций.
  • Некритичные — о ресурсах и трендах, требующих внимания, но не немедленного ночного реагирования.

Вопрос 27. Какой опыт работы с контейнерами и Docker, и как передать собранный Docker-образ другому человеку?

Таймкод: 00:40:26

Ответ собеседника: правильный. Подтверждает опыт работы с Docker, docker-compose и Kubernetes. Для передачи образа предлагает два корректных способа: выгрузить образ в файл через docker save и передать его, либо запушить образ в контейнерный registry, откуда другой человек сможет его забрать.

Правильный ответ:

Опыт работы с контейнерами должен включать не только умение “собрать и запустить образ локально”, но и:

  • грамотную сборку минимальных и безопасных образов;
  • структуру multi-stage сборок (особенно для Go);
  • работу с docker-compose как средством локальной оркестрации;
  • интеграцию с реестрами (Container Registry) и CI/CD;
  • понимание основ Kubernetes (или другой оркестрации) и того, как образы живут в продакшене.

Передача образа — частный случай работы с реестром/артефактами и должна быть встроена в общий процесс.

Разберём по пунктам.

Опыт работы с Docker и контейнерами

Ключевые аспекты, которые ожидаются:

  1. Сборка production-ready образов для Go-приложений
  • Использование multi-stage build для:
    • минимизации размера final-образа;
    • отделения среды сборки от среды выполнения;
    • снижения поверхности атак.

Пример:

# Stage 1: build
FROM golang:1.22-alpine AS builder
WORKDIR /app

RUN apk add --no-cache git ca-certificates

COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app ./cmd/app

# Stage 2: runtime
FROM alpine:3.20
RUN adduser -D -g '' appuser
WORKDIR /app

COPY --from=builder /app/app /app/app

USER appuser
EXPOSE 8080

ENTRYPOINT ["/app/app"]

Особенности:

  • статически собранный бинарник,
  • отсутствие dev-зависимостей в runtime-образе,
  • запуск не от root.
  1. Умение работать с docker-compose
  • Описание локального окружения:
    • приложение,
    • БД (PostgreSQL),
    • кэш (Redis),
    • вспомогательные сервисы.
  • Типичный пример:
version: "3.9"

services:
app:
build: .
ports:
- "8080:8080"
environment:
- DB_DSN=postgres://app:pass@db:5432/app?sslmode=disable
depends_on:
- db

db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: pass
POSTGRES_DB: app
ports:
- "5432:5432"
  1. Понимание базовых концепций контейнеризации
  • Изоляция через namespaces и cgroups;
  • Обращение к ресурсам хоста:
    • volume'ы, сети;
  • Отличия контейнера от ВМ:
    • общий kernel, lightweight runtime.
  1. Интеграция с CI/CD и Kubernetes
  • Автоматическая сборка образов в CI (GitLab CI, GitHub Actions);
  • Тегирование (по коммиту, версии, ветке, окружению);
  • Push в приватные/публичные registry;
  • Использование образов в:
    • Deployment/StatefulSet манифестах,
    • Helm-чартах,
    • GitOps-подходах.

Как передать собранный Docker-образ другому человеку

Два основных промышленных способа (оба были упомянуты кандидатом, и это верно):

  1. Через контейнерный registry (рекомендуемый путь)

Это стандартный и предпочтительный вариант.

Шаги:

  1. Протегировать образ:
docker build -t registry.example.com/myteam/myapp:1.0.0 .
  1. Залогиниться и запушить:
docker login registry.example.com
docker push registry.example.com/myteam/myapp:1.0.0
  1. Другой человек на своей машине:
docker login registry.example.com
docker pull registry.example.com/myteam/myapp:1.0.0

Преимущества:

  • Централизованное хранение образов;
  • Управление доступом;
  • Интеграция с CI/CD;
  • Версионирование и воспроизводимость.

Используемые registry:

  • Docker Hub (публичный или приватный);
  • GitLab Container Registry;
  • GitHub Container Registry;
  • Harbor, ECR, GCR, ACR и др.
  1. Через файл (docker save / docker load)

Подходит для оффлайн-сред, air-gapped-инфраструктуры или одноразовой передачи.

На стороне отправителя:

docker build -t myapp:1.0.0 .
docker save myapp:1.0.0 -o myapp_1.0.0.tar

Передаём файл (scp, rsync, флешка).

На стороне получателя:

docker load -i myapp_1.0.0.tar
docker images | grep myapp

После этого образ доступен локально под тем же именем/тегом.

Особенности:

  • Удобно для изолированных сред без доступа к внешнему registry;
  • Меньше контроля версий и управления доступами, чем через registry;
  • Подходит для дистрибуции заранее собранных образов заказчикам/в отдельные контуры.

Дополнительно: best practices при передаче и использовании образов

  • Использовать осмысленные теги:
    • не только latest, а:
      • app:v1.2.3,
      • app:git-<sha>,
      • app:<env>-<build-id>.
  • Закреплять инфраструктуру на конкретных версиях образов:
    • чтобы деплой был детерминированным.
  • В CI:
    • собирать образ один раз,
    • пушить в registry,
    • использовать тот же тег во всех окружениях.

Краткий вывод:

  • Опыт работы с Docker должен включать:
    • multi-stage build,
    • оптимизацию, безопасность,
    • docker-compose для локальных стендов,
    • интеграцию с registry и оркестраторами.
  • Для передачи образа:
    • промышленный способ: push/pull через registry;
    • оффлайн/разовый: docker save/docker load с передачей tar-файла.

Вопрос 28. Что делает компонент kubelet в Kubernetes?

Таймкод: 00:42:02

Ответ собеседника: правильный. Описывает kubelet как агент на узле, который взаимодействует с API-сервером и контейнерным рантаймом: следит за состоянием Pod'ов, создаёт и удаляет контейнеры. Это соответствует сути.

Правильный ответ:

kubelet — это агент Kubernetes, работающий на каждом узле (worker/иногда master), который отвечает за то, чтобы желаемое состояние Pod'ов, описанное в кластере, было реально обеспечено на конкретном узле.

Ключевая идея:

  • Kubernetes — декларативная система:
    • API-сервер хранит desired state (манифесты Deployment/DaemonSet/Pod и т.д.).
    • kubelet на узле отвечает за reconciliation:
      • “что должно быть запущено на этом узле” vs “что реально запущено”.

Основные функции kubelet:

  1. Регистрация и управление узлом
  • kubelet:
    • регистрирует ноду в API-сервере (Node object),
    • периодически отправляет:
      • статус ноды,
      • ресурсы (CPU, память, диски),
      • условия (Ready/NotReady, DiskPressure, MemoryPressure и т.п.).
  • Если kubelet не репортит состояние:
    • кластер может считать ноду недоступной,
    • запустить перемещение Pod'ов на другие ноды (при наличии контроллеров).
  1. Управление Pod'ами на узле

kubelet:

  • следит за списком Pod'ов, назначенных на эту ноду (через API-сервер);
  • для каждого такого Pod'а:
    • обеспечивает, чтобы:
      • нужные контейнеры были запущены,
      • в нужных версиях образов,
      • с нужными параметрами (env, volume, probes, ресурсы);
    • при падениях/рестартах:
      • перезапускает контейнеры согласно policy (Always, OnFailure, Never).

Если Pod не должен больше работать на узле (удалён/переназначен):

  • kubelet останавливает его контейнеры и очищает связанные ресурсы.
  1. Взаимодействие с контейнерным рантаймом

kubelet не запускает контейнеры напрямую:

  • он взаимодействует с контейнерным runtime через:
    • CRI (Container Runtime Interface),
  • поддерживаемые рантаймы:
    • containerd, CRI-O и др.

Основные действия:

  • pull образов;
  • создание container sandbox (network namespace, etc.);
  • запуск/остановка контейнеров;
  • управление логами и exit-кодами.
  1. Работа с volumes и конфигурацией

kubelet отвечает за:

  • монтирование томов:
    • PVC (PersistentVolumeClaim),
    • hostPath,
    • configMap, secret, projected volumes и т.п.;
  • корректное предоставление этих томов контейнерам;
  • обновление:
    • configMap/secret (в рамках поддерживаемой семантики),
    • если они примонтированы как volume.
  1. Пробы живости и готовности (liveness / readiness / startup)

kubelet выполняет:

  • livenessProbe:
    • решает, нужно ли перезапустить контейнер (если приложение зависло).
  • readinessProbe:
    • решает, можно ли считать Pod готовым принимать трафик:
      • влияет на включение/исключение Pod'а из Endpoints/EndpointSlice.
  • startupProbe:
    • позволяет отделить старт длительного приложения от нормальной liveness.

Это критично для:

  • корректной балансировки,
  • отказоустойчивости,
  • а не “тупого” проверки по факту наличия процесса.
  1. Отчёт статуса Pod'ов и контейнеров

kubelet:

  • собирает информацию:
    • какие контейнеры запущены,
    • их статус, рестарты, причины падений,
  • отправляет статус Pod'а в API-сервер:
    • это то, что вы видите в kubectl get pods -o wide и kubectl describe pod.

Таким образом, API-сервер знает реальное состояние, а контроллеры (ReplicaSet, Deployment и т.п.) могут принимать решения.

  1. PodSecurity, ресурсы и QoS

kubelet:

  • применяет лимиты и запросы ресурсов:
    • CPU/Memory requests/limits → cgroups,
  • поддерживает классы качества обслуживания (QoS):
    • Guaranteed / Burstable / BestEffort,
  • применяет security-контексты:
    • user/group, filesystem permissions,
    • некоторые аспекты security policy на уровне ноды.

Резюме:

  • kubelet — “исполнитель” на узле:
    • синхронизирует желаемое состояние Pod'ов с реальным,
    • через взаимодействие с API-сервером и контейнерным рантаймом.
  • Он:
    • регистрирует ноду,
    • репортит её состояние,
    • запускает/останавливает Pod'ы,
    • следит за probes,
    • монтирует volumes,
    • управляет ресурсами и статусами.
  • Без kubelet узел фактически перестаёт быть частью Kubernetes-кластера как исполнительный элемент.

Вопрос 29. Как обеспечить размещение реплик Deployment в Kubernetes на разных worker-узлах?

Таймкод: 00:42:25

Ответ собеседника: неполный. Указывает на использование affinity/anti-affinity по селекторам, направление верное, но не раскрывает конкретно podAntiAffinity и практические варианты конфигурации.

Правильный ответ:

Цель: гарантировать (или максимально обеспечить), что несколько реплик одного приложения не “сложатся” на один и тот же узел, чтобы:

  • отказ одного worker-узла не уронил весь сервис;
  • повысить реальную отказоустойчивость и соответствие SLO.

В Kubernetes это достигается через:

  1. podAntiAffinity (предпочтительный механизм для реплик)
  2. node affinity / nodeSelector / topologySpreadConstraints (для продвинутого контроля распределения)
  3. (исторически) pod (anti)affinity с topologyKey

Разберём по сути.

  1. Pod Anti-Affinity (основной инструмент)

Pod anti-affinity позволяет задать правило:

  • “не размещай Pod этого приложения на ноде, где уже есть Pod с такими же (или заданными) метками”.

Классический пример для Deployment:

  • хотим, чтобы каждая реплика находилась на отдельном узле (по возможности).

Манифест (ключевой фрагмент):

apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- my-app
topologyKey: "kubernetes.io/hostname"
containers:
- name: app
image: my-app:latest

Ключевые моменты:

  • podAntiAffinity:
    • requiredDuringSchedulingIgnoredDuringExecution:
      • жёсткое требование для scheduler:
        • не размещать Pod, если на ноде уже есть Pod с app=my-app.
      • Если свободных нод меньше, чем replicas:
        • часть Pod'ов останется Pending (и это правильно: лучше недоразместить, чем сломать гарантию).
  • topologyKey: "kubernetes.io/hostname":
    • означает, что правило действует на уровне ноды:
      • “не клади два Pod'а с этим label на один hostname”.

Это типичный способ “разнести реплики по нодам”.

Если хотим, чтобы это было “по возможности, но не строго”:

podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: my-app
topologyKey: "kubernetes.io/hostname"

Тогда:

  • scheduler постарается разнести Pod'ы,
  • но при нехватке ресурсов может всё равно положить несколько реплик на одну ноду.
  1. Node Affinity и nodeSelector (контекст)

Дополнительно к podAntiAffinity иногда:

  • ограничивают пул нод, на которых может жить приложение:
    • по зоне (zone), региону,
    • по типу ноды (ssd/hdd, prod/non-prod).

Например:

spec:
template:
spec:
nodeSelector:
node-role.kubernetes.io/worker: "true"

или более гибко:

affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-pool
operator: In
values:
- critical-apps

Связка:

  • nodeAffinity/nodeSelector определяет, НА КАКИХ нодах можно жить.
  • podAntiAffinity определяет, КАК распределить Pod'ы внутри этого пула нод.
  1. Topology Spread Constraints (современный и удобный механизм)

Начиная с новых версий Kubernetes, рекомендуется использовать topologySpreadConstraints для более декларативного и гибкого управления распределением Pod'ов по топологиям (узлы, зоны и т.п.).

Пример:

spec:
template:
metadata:
labels:
app: my-app
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: "kubernetes.io/hostname"
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: my-app

Интерпретация:

  • Распределяй Pod'ы с app=my-app равномерно по нодам.
  • whenUnsatisfiable: DoNotSchedule:
    • не размещай, если не можешь соблюдать правило (Pod остаётся Pending).
  • Это более общая и иногда более удобная альтернатива podAntiAffinity.

Можно также использовать зоны:

topologyKey: "topology.kubernetes.io/zone"

чтобы обеспечить распределение реплик по разным Availability Zones.

  1. Практические рекомендации
  • Для простого и понятного решения “каждая реплика на своей ноде”:
    • используем podAntiAffinity с requiredDuringSchedulingIgnoredDuringExecution и topologyKey: "kubernetes.io/hostname".
  • Для гибкого, балансированного распределения:
    • используем topologySpreadConstraints:
      • особенно в мультизональных кластерах.
  • Следим за консистентностью:
    • убедиться, что:
      • достаточно нод (min nodes >= replicas),
      • ресурсы (requests/limits) адекватны,
      • нет конфликтующих правил affinity/taints/tolerations.
  • Важно:
    • разнесение реплик — часть стратегии HA:
      • вместе с readiness/liveness probes,
      • корректной конфигурацией сервисов и клиентов,
      • отказоустойчивостью БД и внешних зависимостей.

Краткое резюме:

  • Для разнесения реплик одного Deployment по разным worker-нодам:
    • основной инструмент — podAntiAffinity с topologyKey: kubernetes.io/hostname,
    • либо topologySpreadConstraints для более гибкого распределения.
  • Это позволяет уменьшить blast radius при падении одной ноды и сделать HA не номинальной, а реальной.

Вопрос 30. В чём разница между stateless и stateful приложениями в Kubernetes и какова роль PersistentVolume?

Таймкод: 00:42:57

Ответ собеседника: неполный. Правильно отмечает, что stateless-приложения не хранят состояние локально, а stateful требуют сохранения состояния (WordPress, БД и т.п.). Упоминает PV как абстракцию над хранилищем через StorageClass. Однако путает понятия StatefulSet vs stateful-приложения, не даёт чёткого разграничения, когда использовать Deployment, а когда StatefulSet, и как именно связаны PVC/PV с “состоянием”.

Правильный ответ:

Тема “stateless vs stateful” в Kubernetes — фундамент для проектирования архитектуры и выбора правильных примитивов: Deployment, StatefulSet, PersistentVolume (PV), PersistentVolumeClaim (PVC), StorageClass.

Важно чётко разделять:

  • свойства приложения по работе с состоянием;
  • способы управления Pod'ами (Deployment/StatefulSet);
  • модель работы с хранилищем (PV/PVC).
  1. Stateless-приложения

Суть:

  • Приложение не зависит от локального состояния Pod'а.
  • Всё необходимое для обработки запроса:
    • либо приходит снаружи (запрос + внешние системы),
    • либо хранится во внешних сервисах:
      • БД,
      • кэш,
      • message broker,
      • object storage (S3),
      • конфигурация (ConfigMap, Secret).
  • Потеря любого Pod'а:
    • не приводит к потере данных,
    • позволяет безопасно пересоздать Pod где угодно.

С точки зрения Kubernetes:

  • Типичный контроллер: Deployment.
  • Pod'ы:
    • идентичны и взаимозаменяемы,
    • без жёсткой привязки к имени или диску,
    • могут быть пересозданы в любой момент на любой ноде.
  • Хранилище:
    • чаще всего:
      • ephemeral storage контейнера,
      • или шареные/внешние ресурсы (БД, S3, Redis).

Примеры:

  • HTTP API на Go, которое читает/пишет данные в PostgreSQL/Redis.
  • Frontend (SPA), backend gateway, stateless workers с задачами в очереди.
  1. Stateful-приложения

Суть:

  • Приложение опирается на состояние, которое важно сохранить:
    • данные БД,
    • очередь сообщений,
    • файловое хранилище,
    • состояния репликации.
  • Идентичность инстанса (Pod'а) может иметь значение:
    • фиксированное имя,
    • стабильный network identity,
    • закреплённый диск с данными.

Важно:

  • “stateful” не равен “StatefulSet”.
  • Stateful-приложения МОЖНО запускать и через Deployment, если:
    • состояние полностью вынесено во внешнее managed-хранилище (RDS, внешние кластеры БД);
    • Pod — только stateless-клиент этого хранилища.

Но если состояние хранится в самом кластере (на диске, привязанном к Pod'у), уже нужны особые механизмы.

  1. StatefulSet vs Deployment

Deployment:

  • Для stateless Pod'ов.
  • Pod'ы анонимны и взаимозаменяемы.
  • Не гарантирует стабильные имена и порядок.
  • Типовой выбор для большинства Go/HTTP-сервисов.

StatefulSet:

  • Для Pod'ов, где важна идентичность и привязка к диску/имени.
  • Гарантирует:
    • стабильные DNS-имена Pod'ов:
      • my-db-0, my-db-1, ...
    • предсказуемый порядок создания/удаления:
      • сначала -0, потом -1 и т.д.
    • связь Pod <-> PVC:
      • my-db-0 всегда использует свой PVC, даже после пересоздания/перезапуска.
  • Используется для:
    • БД (PostgreSQL в кластере, ClickHouse-узлы, Cassandra),
    • очередей (Kafka, RabbitMQ-кластеры),
    • приложений, которым нужен стабильный storage и hostname.

Ключевая идея:

  • StatefulSet сам по себе не делает приложение "stateful".
  • Он даёт инфраструктурные гарантии, необходимые stateful-приложениям:
    • фиксированные идентификаторы,
    • закреплённые volume'ы.
  1. Роль PersistentVolume (PV) и PersistentVolumeClaim (PVC)

В Kubernetes файловое состояние должно быть отделено от жизненного цикла Pod'а.

Почему:

  • Pod может быть пересоздан на другой ноде.
  • Эфемерное хранилище контейнера исчезает при падении Pod'а.
  • Для настоящего “state” нужен устойчивый volume.

Здесь вступают в игру:

  • PersistentVolume (PV):
    • абстракция реального хранилища:
      • локальный диск,
      • NFS,
      • Ceph, Gluster,
      • облачные диски (EBS, GCE Persistent Disk, Azure Disk),
      • CSI-плагины.
    • Создаётся админом или динамически через StorageClass.
  • PersistentVolumeClaim (PVC):
    • запрос приложения на хранилище:
      • “дайте мне 20Gi, класс fast-ssd”.
    • Kubernetes связывает PVC с подходящим PV.

Простейший пример PVC:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
storageClassName: fast-ssd

В Pod/Deployment/StatefulSet:

spec:
volumes:
- name: data
persistentVolumeClaim:
claimName: app-data
containers:
- name: app
image: my-app:latest
volumeMounts:
- name: data
mountPath: /var/lib/my-app

Что это даёт:

  • Данные в /var/lib/my-app сохраняются, даже если Pod упадёт и будет пересоздан.
  • При переносе Pod на другую ноду:
    • volume будет перемонтирован (в зависимости от драйвера/StorageClass).
  • Жизненный цикл:
    • PVC (и PV) дольше, чем у Pod'а.

Для StatefulSet обычно используется шаблон PVC (volumeClaimTemplates):

apiVersion: apps/v1
kind: StatefulSet
metadata:
name: my-db
spec:
serviceName: "my-db"
replicas: 3
selector:
matchLabels:
app: my-db
template:
metadata:
labels:
app: my-db
spec:
containers:
- name: db
image: postgres:16
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 50Gi
storageClassName: ssd

Результат:

  • Для my-db-0, my-db-1, my-db-2 создаются PVC:
    • data-my-db-0, data-my-db-1, ...
  • Каждый Pod жёстко привязан к своему PVC:
    • перезапуск не теряет данные.
  1. Как мыслить правильно
  • Stateless:
    • Deployment,
    • без локального состояния,
    • хранение данных во внешних системах/managed-сервисах.
  • Stateful:
    • если нужно локальное persistent-состояние → PVC + PV;
    • если нужна идентичность Pod'ов (имя, порядок, закреплённый диск) → StatefulSet + PVC/PV.
  • PersistentVolume:
    • не “делает приложение stateful” сам по себе,
    • а предоставляет устойчивое хранилище, которое можно безопасно использовать из Pod'ов.
  • Хорошая практика:
    • по возможности выносить состояние во внешние managed-сервисы (RDS, Cloud SQL, внешние кластера БД),
    • а в Kubernetes держать stateless-приложения:
      • это упрощает масштабирование, обновления и отказоустойчивость.

Краткое резюме:

  • Stateless-приложения:
    • не зависят от жизни конкретного Pod'а,
    • обычно крутятся в Deployment,
    • не требуют PV/PVC (если всё состояние во внешних системах).
  • Stateful-приложения:
    • зависят от сохранения состояния,
    • часто используют StatefulSet + PVC/PV,
    • получают стабильные идентичности и диски.
  • PersistentVolume:
    • основа для устойчивого хранилища в Kubernetes,
    • отделяет хранение данных от жизненного цикла Pod'ов,
    • критичен для всех сценариев, где потеря данных недопустима.

Вопрос 31. В чём ключевое отличие использования PVC/PV в Deployment и StatefulSet в Kubernetes?

Таймкод: 00:44:26

Ответ собеседника: правильный. После уточнения объясняет: в StatefulSet каждый Pod получает собственный PVC/PV, привязанный к его идентичности; в Deployment volume обычно общий для всех реплик или подключается одинаково, без уникальной привязки к Pod.

Правильный ответ:

Ключевое отличие в том, как Kubernetes связывает Pod'ы и тома:

  • Deployment:
    • не даёт гарантированной индивидуальной идентичности Pod'ов,
    • не создаёт автоматически отдельный PVC на Pod,
    • чаще всего использует:
      • либо:
        • общий volume (один PVC, примонтированный в несколько Pod'ов — если это поддерживает storage),
      • либо:
        • внешние сервисы (БД, S3 и т.п.), без PVC на каждый Pod.
  • StatefulSet:
    • обеспечивает стабильную идентичность Pod'ов (name-0, name-1, ...),
    • через volumeClaimTemplates автоматически создаёт отдельный PVC для КАЖДОГО Pod'a,
    • Pod и его PVC логически связаны: pod my-app-0pvc data-my-app-0, и это соответствие сохраняется при пересоздании.

Разберём подробнее.

  1. Deployment + PVC/PV

Deployment управляет набором взаимозаменяемых Pod'ов:

  • Pod'ы анонимны: сегодня my-app-7c9d4f7fdf-pxz8q, завтра другой.
  • Нет концепции “этот Pod = эти данные” на уровне API.

Использование PVC:

  • В типовом (и безопасном) случае с репликами:

    • Либо приложение stateless:

      • вообще не нуждается в PVC,
      • всё состояние — во внешних БД/кэшах/объектных хранилищах.
    • Либо нужен shared storage:

      • один PVC (например, NFS/ReadWriteMany),
      • монтируется во все Pod'ы:
        • общий контент (статические файлы),
        • shared storage для чтения/записи, если это поддерживается.

Пример (общий PVC для всех Pod'ов Deployment):

apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
volumes:
- name: shared
persistentVolumeClaim:
claimName: web-shared-pvc
containers:
- name: web
image: nginx
volumeMounts:
- name: shared
mountPath: /usr/share/nginx/html

Особенности:

  • Все Pod'ы разделяют один и тот же PVC.
  • Никакой гарантированной “персональной” привязки Pod↔PVC нет.
  • Если вы начнёте вручную генерировать отдельные PVC и пытаться по label'ам раздавать их Pod'ам Deployment — это хрупко и против шаблона.
  1. StatefulSet + volumeClaimTemplates (per-Pod PVC)

StatefulSet решает другую задачу:

  • Поддерживает Pod'ы с устойчивой идентичностью:
    • my-db-0, my-db-1, my-db-2,
  • Нужен для stateful-систем:
    • БД, кластеры брокеров, shard'ы, реплики и т.п.

volumeClaimTemplates:

  • на основе шаблона создаёт:
    • отдельный PVC для каждого Pod'а;
  • имя PVC, как правило:
    • <volume-name>-<statefulset-name>-<ordinal>,
    • например: data-my-db-0, data-my-db-1.

Пример:

apiVersion: apps/v1
kind: StatefulSet
metadata:
name: my-db
spec:
serviceName: my-db
replicas: 3
selector:
matchLabels:
app: my-db
template:
metadata:
labels:
app: my-db
spec:
containers:
- name: db
image: postgres:16
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 50Gi
storageClassName: ssd

Что происходит:

  • Для my-db-0 создаётся PVC data-my-db-0,
  • для my-db-1data-my-db-1,
  • для my-db-2data-my-db-2.
  • При перезапуске:
    • my-db-0 всегда будет использовать СВОЙ PVC data-my-db-0,
    • данные не потеряются при пересоздании Pod'а.

Это критично для:

  • реплик PostgreSQL, ClickHouse, Kafka, Cassandra и др.,
  • где каждый Pod хранит свой фрагмент/реплику данных.
  1. Практические выводы
  • Если приложение stateless:

    • Deployment,
    • без PVC или с общим read-only/read-many PVC,
    • масштабирование “как угодно” без привязки к дискам.
  • Если приложению нужен persistent state НА УРОВНЕ КАЖДОГО ИНСТАНСА:

    • StatefulSet + volumeClaimTemplates:
      • per-Pod PVC,
      • стабильные имена Pod'ов,
      • предсказуемое поведение при рестартах.
  • Не стоит:

    • пытаться эмулировать поведение StatefulSet поверх Deployment для stateful-сценариев;
    • использовать один RWO PVC на несколько реплик Deployment — это сломает storage-семантику и может привести к потере данных.

Кратко:

  • Deployment:
    • Pod'ы взаимозаменяемые,
    • PVC обычно общий или внешний сервис,
    • нет автоматической схемы “один Pod — один PVC”.
  • StatefulSet:
    • Pod'ы с устойчивой идентичностью,
    • через volumeClaimTemplates: “один Pod — один PVC”,
    • это и есть основа корректной работы stateful-приложений внутри кластера.

Вопрос 32. Что означает сервис Kubernetes с типом ClusterIP и значением None для clusterIP?

Таймкод: 00:45:04

Ответ собеседника: неполный. Правильно догадывается, что это headless-сервис, но изначально связывает None с отсутствием IP и pod'ов, не раскрывает механизм работы и назначение headless-сервисов и их роль в сервис-дискавери.

Правильный ответ:

Сервис вида:

apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
clusterIP: None
selector:
app: my-app
ports:
- port: 80
targetPort: 8080

при type: ClusterIP по умолчанию и clusterIP: None — это headless-сервис.

Ключевые особенности:

  • Не выделяется виртуальный ClusterIP.
  • kube-proxy и виртуальный IP-лоадбалансинг не используются.
  • DNS-запись указывает непосредственно на Pod'ы (их IP), а не на один виртуальный сервисный IP.
  • Используется для:
    • прямого сервис-дискавери Pod'ов,
    • stateful-систем, где важна индивидуальность инстансов (БД, кластера),
    • случаев, когда балансировка/логика находится на стороне клиента или приложения.

Разберём по слоям.

  1. Обычный ClusterIP-сервис (по умолчанию)

Если мы создаём сервис без clusterIP: None:

kind: Service
spec:
type: ClusterIP # по умолчанию
clusterIP: 10.0.0.123 # назначается автоматически
selector:
app: my-app

Получаем:

  • Один виртуальный IP (ClusterIP).
  • kube-proxy на нодах настраивает iptables/ipvs:
    • трафик на ClusterIP равномерно распределяется по Pod'ам в Endpoints.
  • DNS:
    • my-service.default.svc.cluster.local → A-запись на ClusterIP.
  • Клиент:
    • подключается к ClusterIP,
    • не знает реальных IP Pod'ов.

Это удобно для статeless-сервисов с прозрачной балансировкой.

  1. Headless Service: clusterIP: None

Когда указываем:

spec:
clusterIP: None

Kubernetes перестаёт выделять виртуальный IP:

  • Нет ClusterIP.
  • kube-proxy не настраивает VIP для этого сервиса.
  • Вместо этого:
    • DNS-сервер Kubernetes возвращает:
      • либо IP конкретных Pod'ов (A-записи),
      • либо SRV-записи для портов.

Поведение DNS зависит от типа сервиса и контроллера:

  • Для обычного headless-сервиса (Deployment):

    • my-service.default.svc.cluster.local вернёт все Pod IP, попадающие под selector.
    • Клиент, резолвящий имя, получает набор IP и сам решает, как с ними работать:
      • round-robin,
      • custom логика.
  • Для StatefulSet + headless-сервис:

    • Помимо общего имени, генерируются записи вида:
      • pod-0.my-service.default.svc.cluster.local,
      • pod-1.my-service.default.svc.cluster.local.
    • Это даёт:
      • стабильные DNS-имена для каждого Pod'а,
      • критично для stateful-кластеров (PostgreSQL primary/replica, Cassandra, Kafka, ZooKeeper и др.).
  1. Типичные сценарии использования headless-сервисов
  1. StatefulSet и stateful-приложения

Для кластера БД или брокеров нам нужны:

  • уникальные идентификаторы инстансов,
  • прямой доступ к каждому инстансу,
  • возможность различать роли (leader/follower, shard и т.п.).

Используем связку:

  • StatefulSet:
    • даёт стабильные Pod-имена: db-0, db-1, ...
  • Headless Service (clusterIP: None):
    • даёт DNS:
      • db-0.db.default.svc.cluster.local,
      • db-1.db.default.svc.cluster.local.

Пример:

apiVersion: v1
kind: Service
metadata:
name: db
spec:
clusterIP: None # headless
selector:
app: db
ports:
- port: 5432
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: db
spec:
serviceName: db
replicas: 3
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
spec:
containers:
- name: postgres
image: postgres:16
ports:
- containerPort: 5432

Результат:

  • db-0.db.svc.cluster.local → IP Pod'а db-0,
  • db-1.db.svc.cluster.local → IP db-1,
  • и т.д.

Клиент или init-скрипт кластера БД может:

  • обращаться к нужной ноде по имени,
  • строить топологию без дополнительного сервис-дискавери.
  1. Клиентский load balancing / сервис-дискавери

Для некоторых систем:

  • gRPC-клиенты,
  • сервисы с встроенным клиентским load-balancing,
  • сторонние сервис-меши,

нужен список эндпоинтов (Pod IP), а не один виртуальный адрес.

Headless-сервис:

  • отдаёт список IP всех Pod'ов:
    • клиент сам балансирует,
    • сам реализует retry/health-check.

Это:

  • даёт больше контроля,
  • иногда нужно для специализированных протоколов.
  1. Интеграция с Service Mesh / DNS-наблюдением

Некоторые mesh/системы обнаружения используют headless-сервисы как источник правды о Pod'ах.

  1. Что headless-сервис НЕ делает

Важно развеять заблуждения:

  • clusterIP: None НЕ означает:
    • “нет pod'ов”,
    • “сервис не работает”.
  • Pod'ы:
    • существуют,
    • endpoints заполняются,
    • DNS даёт прямые IP этих pod'ов.

Это управляемый режим сервис-дискавери, а не отключение сервиса.

  1. Когда использовать обычный ClusterIP vs headless
  • Обычный ClusterIP:

    • Нужен:
      • когда вам достаточно абстракции "сервис как единая точка входа".
      • балансировка делегируется Kubernetes (kube-proxy/ipvs).
    • Типично для:
      • stateless HTTP/GRPC-сервисов,
      • внутренних API.
  • Headless (clusterIP: None):

    • Нужен:
      • когда вы хотите управлять трафиком на уровне Pod'ов сами,
      • когда требуется стабильное имя и диск на Pod,
      • когда приложение “понимает” топологию (БД-кластера, шардирование).
    • Типично для:
      • StatefulSet-кластеров БД/брокеров,
      • клиентского load balancing,
      • специализированных протоколов.

Кратко:

  • Service c type: ClusterIP и clusterIP: None — это headless-сервис:
    • без виртуального IP,
    • с DNS, который возвращает реальные Pod IP (или именованные записи для StatefulSet).
  • Он используется для:
    • прямого сервис-дискавери,
    • stateful-нагрузок,
    • ситуаций, когда стандартный kube-proxy load-balancing не подходит или недостаточен.

Вопрос 33. Каким механизмом реализуется работа сервисов Kubernetes на уровне сети?

Таймкод: 00:45:43

Ответ собеседника: неполный. Упоминает iptables и eBPF как основу реализации сервисов, но не объясняет роль kube-proxy, режимы работы и схему маршрутизации трафика.

Правильный ответ:

Работа Service в Kubernetes — это, по сути, механизм абстракции и балансировки трафика между Pod'ами. На сетевом уровне она реализуется комбинацией:

  • объект Service + Endpoints/EndpointSlice (desired state),
  • компонент kube-proxy (или eBPF-эквивалент),
  • механизмов ядра: iptables, IPVS, eBPF (в зависимости от конфигурации и CNI).

Ключевая идея:

  • У сервиса есть стабильный виртуальный IP (ClusterIP, NodePort, LoadBalancer IP и т.п.).
  • kube-proxy (или eBPF dataplane) на нодах настраивает правила так, чтобы трафик на этот виртуальный IP распределялся по реальным Pod IP.

Разберём по этапам.

  1. Исходные сущности: Service и Endpoints

Для обычного ClusterIP-сервиса:

apiVersion: v1
kind: Service
metadata:
name: my-api
spec:
selector:
app: my-api
ports:
- port: 80
targetPort: 8080

Kubernetes:

  • создаёт объект Service (с clusterIP, например 10.3.0.15),
  • по selector формирует список целевых Pod'ов:
    • в виде Endpoints/EndpointSlice с IP Pod'ов и их портами.

Этот список — источник правды для kube-proxy/сетевого dataplane.

  1. kube-proxy — связующее звено

kube-proxy запускается на каждом worker-узле и:

  • подписан на изменения Service и Endpoints (через API-сервер),
  • на основе этих данных программирует сетевые правила на ноде.

Режимы работы kube-proxy:

  • iptables mode:
    • по умолчанию во многих кластерах;
  • IPVS mode:
    • более продвинутый вариант с использованием IPVS как L4-балансировщика;
  • userspace (исторический, практически не используется).

Основная задача:

  • перенаправить трафик, приходящий на:
    • ClusterIP:port,
    • NodePort,
    • внешние IP (LoadBalancer / ExternalIPs),
  • на один из Pod IP:targetPort,
  • с учётом:
    • балансировки,
    • health (убрать неработающие эндпоинты),
    • source/dest NAT, если нужно.
  1. Реализация через iptables (классика)

В iptables-режиме kube-proxy:

  • создаёт набор цепочек в таблице nat:
    • KUBE-SERVICES,
    • KUBE-NODEPORTS,
    • KUBE-SEP-... (service endpoints),
    • KUBE-SVC-... (service backends group).
  • Правила:
    • матч по ClusterIP:port → переход в цепочку KUBE-SVC-...;
    • внутри — DNAT трафика на один из Pod IP:targetPort (round-robin через iptables statistic/random/multi rules).

Схематично:

  • пакет к 10.3.0.15:80:
    • попадает в PREROUTING nat,
    • перенаправляется в цепочки kube-proxy,
    • DNAT → 10.2.1.23:8080 (Pod),
    • дальнейшая маршрутизация до Pod.

Плюсы:

  • Просто, стабильно, поддерживается “из коробки”. Минусы:

  • iptables плохо масштабируется при большом количестве сервисов/Pod'ов (длинные цепочки, линейный матч).

  1. Реализация через IPVS

В IPVS-режиме:

  • kube-proxy:
    • настраивает IPVS (L4-балансировщик в ядре),
    • создаёт virtual services (ClusterIP:port),
    • добавляет real servers (Pod IP:port).
  • балансировка:
    • rr, wrr, lc, и др., реализованные в IPVS.

Плюсы:

  • лучше масштабируется,
  • гибче по алгоритмам балансировки.
  1. eBPF-подход (Cilium, kube-proxy replacement)

Современные CNI-плагины (например, Cilium, некоторые режимы Calico и др.) могут:

  • полностью или частично заменить kube-proxy,
  • реализовать сервисную маршрутизацию через eBPF-программы в ядре.

Суть:

  • вместо длинных цепочек iptables:
    • eBPF-программы на точках hook (TC/XDP) читают таблицы сервисов/эндпоинтов,
    • выполняют:
      • быстрый lookup,
      • DNAT,
      • балансировку,
      • дополнительные функции (policy, observability),
      • всё in-kernel, без накладных расходов iptables.

Преимущества eBPF-решений:

  • высокая производительность при большом числе сервисов и Pod'ов;
  • лучшая наблюдаемость и гибкие сетевые политики;
  • уменьшение зависимости от kube-proxy (или его полная замена).
  1. Разные типы Service и как они ложатся на сетевой механизм
  • ClusterIP:
    • виртуальный IP внутри кластера,
    • DNAT на Pod IP через iptables/IPVS/eBPF.
  • NodePort:
    • открывает порт на всех нодах,
    • трафик на nodeIP:nodePort → перенаправляется на Pod'ы.
  • LoadBalancer:
    • облачный LB получает внешний IP,
    • трафик снаружи → LB → NodePort/ClusterIP → Pod.
  • Headless Service (clusterIP: None):
    • без VIP,
    • DNS сразу возвращает Pod IP,
    • балансировка и маршрутизация — на клиенте или приложении.
  1. Что важно понимать на собеседовании

Хороший ответ должен отражать:

  • Логическую модель:
    • Service — абстракция.
    • Endpoints/EndpointSlice — список backend'ов.
    • kube-proxy/eBPF — реализация на ноде, которая мапит виртуальные сервисные IP/порты на реальные Pod IP.
  • Практическое знание:
    • iptables/IPVS режимы kube-proxy,
    • роль CNI и возможных eBPF-реализаций,
    • базовое понимание, как пакет попадает от клиента до Pod'а при разных типах сервисов.

Краткое резюме:

  • Механизм работы сервисов Kubernetes на сети реализуется:
    • через kube-proxy (или eBPF-based dataplane),
    • который программирует ядро (iptables, IPVS или eBPF),
    • чтобы трафик на виртуальные IP/порты Service направлялся на реальные Pod IP согласно Endpoints/EndpointSlice.
  • Service — это не “магия”, а конкретный набор сетевых правил и DNS-записей поверх абстракции Kubernetes API.

Вопрос 34. Какой практический опыт и какие подходы важны при развёртывании PostgreSQL с высокой доступностью (HA)?

Таймкод: 00:46:06

Ответ собеседника: неполный. Упоминает ручное развертывание, master-slave репликацию, использование HAProxy с PostgreSQL-модулем и знание VRRP/виртуальных IP. Отвечает в целом уверенно, но без деталей по настройке, мониторингу, автоматическому failover и без опыта с Patroni.

Правильный ответ:

При оценке опыта по PostgreSQL с высокой доступностью важно не только “умею поднять мастер-слейв и повесить HAProxy”, а понимание:

  • моделей отказоустойчивости;
  • механизмов репликации и переключения ролей;
  • типичных паттернов (Patroni, repmgr, встроенные средства, облачные решения);
  • интеграции с приложениями (включая Go-сервисы);
  • мониторинга и предотвращения split-brain.

Ниже — системный обзор, на который стоит ориентироваться.

Основные цели HA для PostgreSQL

  • Минимизировать RTO (время восстановления) и RPO (потерю данных).
  • Обеспечить:
    • автоматическое или полуавтоматическое переключение;
    • понятную точку входа для приложений (единый endpoint);
    • защиту от split-brain;
    • предсказуемое поведение при падении master, реплики, сети.
  1. Базовые механизмы: репликация PostgreSQL

PostgreSQL предоставляет:

  • Streaming Replication:
    • основа большинства HA-сценариев.
    • Primary (ранее “master”) стримит WAL-записи на standby-реплики.
  • Типы:
    • Asynchronous:
      • Primary не ждёт подтверждения от реплики.
      • RPO > 0 возможно (потеря последних транзакций при падении primary).
    • Synchronous:
      • транзакция считается подтвержденной только после записи WAL минимум на один synchronous-standby.
      • ниже риск потери данных (RPO ~ 0),
      • выше латентность и зависимость от standby.

Важно уметь:

  • настраивать репликацию:
    • primary_conninfo,
    • primary_slot_name,
    • replication slots для предотвращения удаления нужных WAL;
  • понимать влияния sync vs async для конкретного SLA.
  1. Примитивный сценарий: ручной master + standby

Ручной сценарий, который часто “умеют”, но которого недостаточно:

  • Есть primary и один или несколько standby.
  • При падении primary:
    • вручную promote standby:
      • pg_ctl promote или SELECT pg_promote();
    • перенастроить приложения/HAProxy на новый primary.

Проблемы:

  • Долгое и не всегда предсказуемое переключение.
  • Риск split-brain, если старый primary внезапно “ожил”.
  • Нет формализованного механизма выбора лидера.

Это базовый уровень, но не полноценное HA-решение.

  1. Production-уровень: автоматизированный failover

Зрелые решения строятся вокруг:

  • consensus/coordination (etcd/Consul/ZooKeeper),
  • контроллера, который:
    • знает топологию,
    • следит за health,
    • принимает решение, кого promote,
    • обновляет routing/виртуальный IP.

Наиболее распространённый и зрелый стек:

  1. Patroni
  • Patroni:
    • управляет PostgreSQL-инстансами,
    • использует DCS (distributed configuration store):
      • etcd, Consul, ZooKeeper,
    • следит за здоровьем primary,
    • инициирует failover/promote standby при необходимости.
  • Схема:
    • Patroni на каждом узле с PostgreSQL;
    • один из узлов — лидер (primary), остальные — standby;
    • при падении Легитимный новый лидер выбирается через консенсус в DCS;
    • минимизация split-brain:
      • если узел потерял контакт с DCS — не может самовольно стать primary.
  1. Управление точкой входа

Для приложений нужен единый endpoint к “актуальному primary”.

Стандартные подходы:

  • HAProxy / pgBouncer с health-check для primary:
    • HAProxy:
      • использует PostgreSQL-check (например, option pgsql-check) или HTTP health Patroni,
      • трафик на backend, который reported как master.
  • Виртуальный IP (VIP) через keepalived/VRRP:
    • VIP всегда смотрит на node с текущим primary.
    • Patroni/скрипты управляют переключением VIP.
  • DNS-based:
    • менее надёжно из-за кешей, но допустимо.

Пример простого HAProxy backend для primary:

listen postgres_rw
bind *:5432
mode tcp
option tcp-check
tcp-check connect
tcp-check send "SELECT 1;\n"
tcp-check expect string 1
server pg1 10.0.0.1:5432 check
server pg2 10.0.0.2:5432 check backup

В более продвинутых конфигурациях используют:

  • Patroni REST API для определения роли;
  • tag-ирование primary/standby.
  1. VRRP/виртуальный IP

VRRP (keepalived) часто используется:

  • как простой способ иметь один IP для текущего primary.

Схема:

  • Несколько узлов:
    • только один держит VIP (MASTER),
    • остальные в BACKUP.
  • Здоровье primary:
    • скрипты/health-check:
      • если PostgreSQL или Patroni на узле в некорректном состоянии:
        • keepalived теряет MASTER-статус,
        • VIP уезжает на другой узел.
  • Важно:
    • связать логику VIP с реальной ролью PostgreSQL (primary), а не только “postgres слушает порт”,
    • иначе легко получить split-brain (две ноды считают себя primary и держат VIP).
  1. Предотвращение split-brain

Критический момент для HA PostgreSQL:

  • Нельзя допустить одновременного существования двух primary, оба принимающих запись.

Решается:

  • Использованием внешнего quorum (DCS: etcd/Consul),
  • Жёсткой логикой:
    • если нода потеряла связь с DCS — запрещено promote,
    • fencing/STONITH в связке с оркестраторами (Pacemaker/Corosync в старых решениях),
  • Чётким порядком:
    • manual override — только через контролируемые процедуры.
  1. Мониторинг HA-кластера PostgreSQL

Зрелый опыт должен включать мониторинг:

  • Ролей:
    • кто primary, кто standby.
  • Лага репликации:
    • pg_stat_replication,
    • задержка WAL.
  • Репликационных слотов:
    • отсутствие overflow и отставания.
  • Доступности:
    • health-check primary/standby,
    • статус Patroni/repmgr (если используется).
  • Метрик производительности:
    • TPS,
    • latency,
    • блокировки,
    • slow queries.

И алертов:

  • Лаг репликации > X.
  • Нет доступных standby.
  • Некорректное состояние: два master-кандидата.
  • Переполнение WAL-диска из-за нечитабельных реплик.
  1. Интеграция с приложениями (на примере Go)

Go-сервис должен быть:

  • готов к изменению роли узлов;
  • корректно работать с пулом соединений и retry.

Пример использования подключения к HA endpoint:

dsn := os.Getenv("PG_DSN") // указывает на VIP/HAProxy, а не на конкретный хост
db, err := sql.Open("pgx", dsn)
if err != nil {
log.Fatal(err)
}

db.SetMaxOpenConns(50)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(30 * time.Minute)

// при failover соединения могут рваться — важно иметь retry-логику на уровне запросов/репозитория

Важно:

  • не хардкодить список отдельных host:port без понимания механики,
  • использовать единый HA endpoint (VIP/HAProxy/pgBouncer/Service name),
  • уметь обрабатывать transient-ошибки (connection reset, read-only, failover in progress).
  1. Облачные и managed-решения

В реальной жизни часто используются:

  • Managed PostgreSQL:
    • AWS RDS / Aurora,
    • GCP Cloud SQL,
    • Azure Database for PostgreSQL.
  • Они:
    • уже обеспечивают репликацию и автоматический failover,
    • требуют правильного использования endpoint'ов и понимания модели.

Зрелый специалист:

  • понимает как self-managed HA, так и особенности managed-сервисов,
  • выбирает подход исходя из SLA, нагрузки и операционных затрат.

Краткий вывод:

При ответе “опыт HA PostgreSQL” важно показывать:

  • Понимание:
    • streaming replication (async/sync),
    • роли primary/standby,
    • RTO/RPO.
  • Владение:
    • паттернами автоматического failover (Patroni + etcd/Consul, repmgr, VIP/HAProxy),
    • концепциями защиты от split-brain.
  • Наличие:
    • мониторинга ключевых метрик репликации и доступности.
  • Умение:
    • давать приложениям (в т.ч. Go-сервисам) стабильный HA endpoint
    • и обрабатывать failover на уровне клиента.

Просто “мы поднимали master-slave и повесили HAProxy” — это только начальный уровень; для продакшн-HA важен системный, формализованный подход.

Вопрос 35. Как устроена связка PersistentVolumeClaim и PersistentVolume в Kubernetes?

Таймкод: 00:43:59

Ответ собеседника: правильный. Корректно описывает PVC как запрос на ресурс и связующее звено между подом (через Deployment/StatefulSet) и PV.

Правильный ответ:

PersistentVolume (PV) и PersistentVolumeClaim (PVC) — это базовые примитивы Kubernetes для работы с постоянным хранилищем, которые отделяют:

  • жизнь данных от жизни Pod,
  • разработку/деплой приложений от деталей инфраструктурного хранилища.

Ключевая идея:

  • Pod не должен знать, “какой именно диск/сторидж используется”.
  • Приложение запрашивает характеристики хранилища (PVC),
  • кластер сопоставляет этот запрос с конкретным ресурсом (PV).

Рассмотрим по шагам.

  1. PersistentVolume (PV)

PersistentVolume — это объект кластера, описывающий конкретный кусок хранилища.

Основные свойства:

  • Описывает “физический” ресурс:
    • диск в облаке (EBS/GCE/Azure),
    • NFS,
    • Ceph, Gluster, iSCSI,
    • локальный диск ноды,
    • любой сторидж через CSI-плагин.
  • Создаётся:
    • вручную (static provisioning),
    • или автоматически (dynamic provisioning) на основе StorageClass.

Пример (упрощённый PV):

apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-data-1
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath:
path: /mnt/data1
  1. PersistentVolumeClaim (PVC)

PVC — это запрос на хранилище от приложения.

Основные свойства:

  • Описывает требования:
    • размер (storage),
    • accessModes (ReadWriteOnce/ReadWriteMany/ReadOnlyMany),
    • storageClassName (класс хранилища).
  • Не привязан к конкретному PV:
    • кластер сам подбирает подходящий PV или создаёт новый (при динамическом провижининге).

Пример PVC:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
storageClassName: manual
  1. Связывание PVC и PV (binding)

Механика:

  • Контроллер кластера ищет PV, который удовлетворяет требованиям PVC:
    • размер ≥ requested,
    • подходящий accessMode,
    • совпадающий storageClassName (или пустые совместимые значения).
  • Если найден подходящий PV:
    • устанавливается связь:
      • PV.status.phase → Bound,
      • PVC.status.phase → Bound,
    • PV “закрепляется” за этим PVC и не используется другими.
  • При динамическом provisioning:
    • провайдер (через StorageClass) создаёт новый PV “под этот PVC”,
    • затем так же биндинг.

Таким образом:

  • PVC — интерфейс,
  • PV — реализация.
  1. Использование PVC в Pod/Deployment/StatefulSet

Pod не работает напрямую с PV. Он монтирует PVC как volume.

Пример в Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
volumes:
- name: data
persistentVolumeClaim:
claimName: app-data
containers:
- name: app
image: my-app:latest
volumeMounts:
- name: data
mountPath: /var/lib/my-app

Механика:

  • Pod запрашивает volume data на основе PVC app-data.
  • PVC уже Bound к конкретному PV.
  • Kubernetes монтирует реальное хранилище (PV) в контейнер по mountPath.

Важно:

  • При перезапуске Pod:
    • PVC остаётся,
    • PV остаётся привязан,
    • данные в /var/lib/my-app сохраняются.
  • Pod можно пересоздать на другой ноде (если storage-класс поддерживает это) — PVC/PV смонтируются заново.
  1. Режимы использования и политика жизни PV

persistentVolumeReclaimPolicy определяет, что делать с PV после удаления PVC:

  • Retain:
    • PV и данные остаются.
    • Требуется ручная очистка/перепривязка.
  • Delete:
    • PV и реальный storage удаляются автоматически (для динамических provisioners).
  • Recycle (deprecated):
    • устаревший режим.

Это важно для:

  • сценариев, где данные нельзя потерять автоматически (Retain),
  • или наоборот, временных окружений (Delete).
  1. StatefulSet и volumeClaimTemplates

В контексте stateful-приложений:

  • StatefulSet использует volumeClaimTemplates для автоматического создания отдельного PVC на Pod.

Кратко:

  • Под my-db-0 → PVC data-my-db-0 → PV (диск-0),
  • Под my-db-1 → PVC data-my-db-1 → PV (диск-1),
  • и эти связи сохраняются при пересоздании Pod'ов.
  1. Практический смысл для архитектуры

PVC/PV-подход даёт:

  • Чёткую границу:
    • приложения описывают “что нужно” (через PVC),
    • платформа/админы отвечают за “как предоставить”.
  • Независимость:
    • можно сменить backend (NFS → Ceph → облачный диск), не переписывая deployment-приложения.
  • Устойчивость:
    • данные не пропадают при рестарте/пересоздании Pod'ов.
  • Контроль:
    • через StorageClass задаём:
      • тип дисков,
      • политики,
      • параметры производительности.

Краткое резюме:

  • PVC — запрос на persistent-ресурс с заданными характеристиками.
  • PV — конкретный persistent-ресурс, удовлетворяющий этому запросу.
  • Kubernetes автоматически связывает PVC с подходящим PV.
  • Pod монтирует PVC, а данные живут на PV, переживая перезапуски Pod'ов и обеспечивая отделение состояния от жизненного цикла контейнеров.