DevOps Ida Project - Senior от 250+ тыс. / Реальное собеседование
Сегодня мы разберем живое техническое собеседование DevOps-инженера, в котором интервьюеры последовательно проверяют опыт кандидата в Linux, Ansible, CI/CD, мониторинге и контейнеризации, параллельно рассказывая о специфике проекта и роли. Кандидат демонстрирует уверенный практический бэкграунд, честно обозначает пробелы и задает зрелые вопросы о процессах, ответственности и архитектуре, что позволяет увидеть не только его уровень, но и реальный формат работы в команде.
Вопрос 1. Какой уровень владения Linux, Ansible и сетевыми технологиями ожидается для работы с виртуальными машинами без контейнеров?
Таймкод: 00:02:18
Ответ собеседника: неполный. Уверенно работает с Linux и Ansible, есть опыт написания playbook'ов для деплоя и конфигурации серверов. В сетях ориентируется на уровне базовой настройки и отладки, без глубокого погружения в маршрутизацию (BGP, OSPF и т.п.).
Правильный ответ:
Для работы с неконтейнерной инфраструктурой на виртуальных машинах ожидается уверенный, практический уровень владения Linux, Ansible и сетевыми технологиями, достаточный для самостоятельного проектирования, развертывания, отладки и поддержки продакшн-систем. Важна не только способность "пользоваться", но и понимание того, как все устроено под капотом.
Основные ожидания по каждому направлению:
- Уровень 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
- Уровень 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
- Уровень сетевых технологий
Даже без глубокого 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()
- Интегральное ожидание
- Уметь:
- Самостоятельно поднять и сопровождать Go-сервис на VM:
- Развернуть систему.
- Настроить сеть и доступ.
- Настроить systemd unit и логирование.
- Описать всё в Ansible так, чтобы деплой был повторяемым и предсказуемым.
- Разобраться в инциденте:
- Сервис не отвечает: проверить порт, firewall, DNS, логи сервиса, системные ресурсы.
- Высокая латентность: проверить сеть (mtr, ping), CPU/IO, пул коннекций, базу.
- Самостоятельно поднять и сопровождать Go-сервис на VM:
Такой уровень владения позволяет не зависеть от администраторов на каждом шаге и эффективно поддерживать продакшн-окружения на виртуальных машинах без контейнеризации.
Вопрос 2. Каков практический опыт работы с базами данных, такими как PostgreSQL и ClickHouse, в части использования из кода, проектирования, настройки и оптимизации?
Таймкод: 00:03:16
Ответ собеседника: неполный. Подтверждает опыт работы с PostgreSQL и другой СУБД, упоминает Cassandra, но не раскрывает аспекты администрирования, настройки, проектирования схем и оптимизации запросов.
Правильный ответ:
Опыт, который ценится в работе с PostgreSQL, ClickHouse (и, дополнительно, распределёнными хранилищами вроде Cassandra), включает не только умение “подключиться из Go и сделать запрос”, а глубокое понимание:
- как спроектировать схему под реальные нагрузки,
- как пишутся эффективные запросы,
- как анализировать и устранять проблемы производительности,
- как учитывать особенности конкретной СУБД в архитектуре приложения.
Важно уметь объяснить решения с точки зрения консистентности, масштабирования, отказоустойчивости и профиля нагрузки.
Основные аспекты по системам:
- 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()
Фокус: понимание, как настройки пула и структура запросов влияют на латентность и нагрузку.
- ClickHouse
ClickHouse — столбцовая аналитическая СУБД, с другими приоритетами, чем PostgreSQL. Ожидается понимание, когда и зачем его использовать.
Ключевые моменты:
-
Понимание профиля:
- Заточен под:
- большие объемы данных (сотни млн/млрд строк),
- аналитические запросы (агрегации, отчёты),
- append-only / event-based модели.
- Не под частые point-update и транзакционные сценарии.
- Заточен под:
-
Проектирование таблиц:
- MergeTree-семейство:
- выбор
ORDER BY(primary key) под частые запросы. PARTITION BYдля логического разбиения (например, по дате).
- выбор
- Компромисс: скорость записи vs скорость выборок.
- MergeTree-семейство:
Пример схемы логов в 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)
}
- Cassandra и распределённые хранилища (если фигурируют)
Если в опыте упоминается Cassandra или аналогичные NoSQL-системы, важно:
- Понимание модели:
- AP-система по CAP (availability и partition tolerance важнее строгой консистентности).
- Denormalized, query-based моделирование:
- схема строится от запросов: под каждый ключевой паттерн — своя таблица.
- Consistency levels (ONE, QUORUM, ALL) и их влияние на задержки и гарантию чтения/записи.
- Partition key и clustering key:
- предотвращение hot-keys,
- равномерное распределение по нодам.
- Что в сумме ожидается от опыта
Внятный опыт работы с 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:
- Архитектура и подход
- Понимание роли Zabbix:
- Инфраструктурный мониторинг: сервера, ВМ, сети, диски, сервисы, агентские и безагентские проверки.
- Чёткое разделение зон ответственности: Zabbix для хоста и окружения, Prometheus/OTel для метрик приложения — нормальная, здравая стратегия.
- Базовое понимание компонентов:
- Zabbix Server, Zabbix Agent (active/passive checks), Proxy (для распределённых окружений), базы данных Zabbix.
- Основы производительности: влияние частоты опроса, количества items и триггеров.
- Шаблоны и масштабируемость
- Использование и доработка шаблонов:
- Адаптация стандартных шаблонов (Linux by Zabbix agent, файловые системы, сети и т.п.).
- Создание кастомных шаблонов под стандарты компании:
- единые items и триггеры по CPU, RAM, диску, I/O, сетям;
- унификация порогов для разных типов хостов (prod/stage/dev).
- Масштабируемость:
- Применение шаблонов к группам хостов,
- Минимизация ручных настроек на уровне отдельного хоста.
- Метрики, триггеры и шумоподавление
Важно не просто “настроить алерты”, а добиться полезных сигналов без алерт-фатуга.
- Метрики инфраструктуры:
- 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% + тренд на снижение.
- Продуманные пороги (SLA-ориентированные):
- Шумоподавление:
- Использование dependencies (например: если упал хост — не слать алерты по всем сервисам на нём).
- Корректная приоритизация severity (Info/Warning/Average/High/Disaster).
- Алертинг и интеграции
- Настройка notification channels:
- Email, Slack/Telegram, webhook-и, интеграция с инцидент-менеджментом (PagerDuty, VictorOps и т.п.).
- Практика:
- Разные профили уведомлений для разных окружений.
- Эскалации: если алерт не закрыт — эскалировать через N минут.
- Интеграция с другим мониторингом:
- Нормальный продакшн-подход:
- Zabbix: здоровье инфраструктуры.
- Prometheus + Grafana + Alertmanager: метрики и алерты приложений.
- Возможность:
- Отражать ключевые инфраструктурные состояния Zabbix на общих дашбордах,
- Учитывать инфраструктурные статусы при анализе метрик приложений.
- Нормальный продакшн-подход:
- Кастомные проверки и расширение под приложения
Даже если метрики приложений идут через 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 минут — алерт.
- Практический уровень, который ценится
Ожидается опыт, когда человек:
- Понимает, как построить мониторинг инфраструктуры так, чтобы:
- алерты были осмысленными, а не спамом;
- метрики позволяли расследовать инцидент, а не только фиксировать факт падения.
- Умеет:
- добавлять новые хосты и сервисы через шаблоны;
- создавать корректные триггеры и дашборды под реальные потребности;
- интегрировать 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).
Основные подходы:
- Базовые принципы
- Не редактировать продакшн Zabbix руками "в бою" без отражения в репозитории.
- Все изменения:
- проходят code review,
- проверяются в тестовом/стейджинг Zabbix,
- затем применяются к production.
- Цель:
- воспроизводимость (можно поднять новый Zabbix с теми же шаблонами),
- аудит изменений (кто, что и когда поменял),
- отсутствие "дрейфа конфигурации".
- Экспорт/импорт конфигурации как артефактов
Zabbix поддерживает экспорт шаблонов, дашбордов, hosts и других объектов в XML/JSON.
Практика:
- Шаги:
- Создаём/изменяем шаблон или дашборд на тестовом стенде.
- Экспортируем в XML/JSON.
- Кладём в Git.
- Импортируем в production через:
- UI (на начальном уровне),
- или автоматизированно через API/скрипты/Ansible.
Плюсы:
- Быстрый старт к "monitoring as code". Минусы:
- Без автоматизации легко вернуться к ручным несогласованным правкам.
- Использование 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.
- 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.
- Terraform и другие инструменты
Альтернативно:
- Использование Terraform-провайдеров для Zabbix:
- Описание шаблонов, хостов, дашбордов в HCL.
- Применение через
terraform apply.
- Преимущества:
- Единый IaC-стек с остальной инфраструктурой.
- Важно:
- Следить за покрытием объектов Zabbix в выбранном провайдере.
- Проверка на практике: зрелый процесс доставки изменений
Хороший ответ на этот вопрос подразумевает наличие процесса, а не ручных правок:
- Все изменения конфигурации 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-процессы.
- сортировка по CPU, проверка
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) — перегрев.
Стратегия осмотра (важно на практике)
Хорошая практика — придерживаться порядка, чтобы быстро отсечь основные причины:
- Проверить доступность: хост жив? ssh заходит? latency?
- CPU / load average: нет ли явного перегруза или iowait.
- Память: нет ли OOM, активного swap.
- Диск: есть ли место и нормальный I/O.
- Сервисы: живы ли ключевые демоны, Go-сервисы, БД.
- Сеть: правильные IP/маршруты, порты слушаются, firewall не режет.
- Логи: коррелировать найденное с конкретными ошибками.
- Если хост под мониторингом (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%— это очень плохо.
- поле st в блоке CPU: например,
- vmstat:
- столбец st — проценты времени, "украденные" гипервизором.
Если st > 5–10% стабильно под нагрузкой — это повод к разбору; десятки процентов — критическая деградация.
Практический подход к диагностике
- Убедиться, что это действительно проблема
На самой VM:
- Проверить:
- load average vs количество vCPU.
- Есть ли runnable-процессы (R) в
top/htop. - Есть ли высокое
st:- в
top(st), - в
vmstat 1(столбец st).
- в
- Интерпретация:
- Высокий st при наличии runnable-процессов и нормальной IO/памяти → VM реально не получает CPU.
- Если ст высокая только на idle VM без runnable-потоков — сначала убедиться, что мы правильно интерпретируем метрики (иногда бывают артефакты/методика измерения).
- Проверить локальные факторы в гостевой ОС
Хотя steal — проблема "снаружи", сначала исключим очевидное в гостевой:
- Приложение не зажало CPU самó:
- Посмотреть top/htop:
- нет ли 100% single-thread bottleneck при многопоточной нагрузке,
- нет ли lock contention в приложении.
- Посмотреть top/htop:
- Нет ли проблем с энергосбережением внутри VM:
- Неправильные governor'ы (в классической ВМ это реже релевантно, но в некоторых конфигурациях имеет значение).
- Нет ли массивной IO-wait, которую можно спутать с проблемами CPU.
Если гостевая выглядит "здоровой", но st высокое и latency растёт — смотрим на гипервизор.
Системное решение на уровне виртуальной инфраструктуры
Ключевая идея: высокий steal time — следствие oversubscription или неправильного планирования CPU-ресурсов.
Основные шаги:
- Анализ 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.
- Для KVM:
Если oversubscription агрессивный — это прямая причина steal time.
- Миграция и перераспределение VM
Практические действия:
- Перенести "тяжёлые" VM на другие узлы:
- live migration (vMotion, virsh migrate, OpenStack live-migrate), если поддерживается.
- Разнести конфликтующие нагрузки:
- не держать несколько CPU-интенсивных прод-VM на одном хосте,
- особенно если у них пиковая нагрузка совпадает по времени.
- Резервирование и политика 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 сервисов.
- Корректный выбор числа vCPU
Частая ошибка — “давайте дадим больше vCPU, будет быстрее”. Для виртуализации это не всегда верно.
- Если:
- у VM много vCPU,
- гипервизору сложно найти слоты для их планирования одновременно,
- это может ухудшать ситуацию (ready/steal time растут).
- Практика:
- Давать столько vCPU, сколько реально нужно и чем приложение умеет пользоваться.
- Для некоторых сервисов лучше меньше vCPU, но с более предсказуемым временем.
- Мониторинг и 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):
- На целевом сервере (Server B) поднимаем UDP-listener:
nc -u -l -p 9999
- Сервер слушает UDP-порт 9999 и выводит полученные данные в stdout.
- На исходном сервере (Server A) отправляем тестовую строку:
echo "test-udp" | nc -u <IP_B> 9999
- Проверяем:
- Если на Server B в консоли с nc видно "test-udp" — UDP-связность есть.
- Если ничего не приходит:
- проверяем firewall, маршрутизацию, правильность IP/порта.
Ограничения:
- Некоторые реализации nc требуют ключей:
- например:
nc -u -l 9999илиncat --udp -l 9999.
- например:
- Именно наличие слушателя на целевой стороне критично — без него тест невалиден.
Подход 2. Анализ трафика с tcpdump
Даже если нет nc или хотим более низкоуровневую проверку.
- На Server B:
sudo tcpdump -n -i any udp port 9999
- На Server A:
echo "ping-udp" | nc -u <IP_B> 9999
# или
printf "ping-udp" >/dev/udp/<IP_B>/9999 # в bash с поддержкой /dev/udp
- Если 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”, но живёт в памяти ядра и учитывается в использовании пространства.
Это важно понимать для логирующих приложений, баз данных, долгоживущих демонов.
Как диагностировать и устранить проблему.
- Проверка использования диска
Сначала убеждаемся, что проблема реально есть:
df -h
Если раздел заполнен (Use% ≈ 100%), но вы ожидаете свободное место после удаления файлов — идём дальше.
- Поиск открытых, но удалённых файлов (через lsof)
Классический способ:
- Найти процессы, удерживающие удалённые файлы.
Команды:
- Посмотреть все удалённые, но ещё открытые файлы:
sudo lsof | grep deleted
Типичный вывод:
myapp 1234 app 5w REG 253,0 104857600 1234567 /var/log/myapp.log (deleted)
Интерпретация:
- Процесс myapp (PID 1234) продолжает писать в файл, который уже удалён из каталога.
- Размер учётается на диске, но вы его “не видите” через ls.
- Ищем по конкретному разделу/директории:
sudo lsof +L1
- Показывает файлы с количеством ссылок < 1 (обычно удалённые).
Если такие файлы есть и они большие — это и есть причина заполнения диска.
- Освобождение места
Есть несколько корректных способов.
Вариант 1. Перезапустить процесс
- Самое частое и правильное решение:
sudo systemctl restart myapp
# или
kill -HUP <PID> # если приложение перевешивает логи по сигналу
После перезапуска:
- все файловые дескрипторы закрываются,
- kernel освобождает блоки,
df -hначинает показывать освободившееся место.
Вариант 2. Точечное закрытие или “обнуление” файла
Если нельзя сразу перезапустить:
- Иногда можно “обнулить” файл через
/proc/<pid>/fd, но это опасный и редко нужный трюк. - Обычно лучше:
- настроить корректный log rotation,
- обеспечить возможность HUP/USR1-сигналом перевешивать лог-файлы без удаления из-под живого процесса.
Важно: не пытаться “удалить ещё раз” — файла уже нет в каталоге, проблема в открытом дескрипторе.
- Разница между df и du (частый диагностический паттерн)
Этот кейс хорошо видно по расхождению:
df -hпоказывает почти полный диск.du -sh /pathпоказывает, что суммарно файлов гораздо меньше.
Это явный маркер:
- есть удалённые, но всё ещё открытые файлы,
- или есть файлы, которые считаются на другом mount namespace (в контейнерах/namespace-сценариях).
Алгоритм:
- Если
df>>du— проверяемlsof | grep deleted. - Если в контейнеризированной среде — учитывать mount namespaces и проверять внутри того же контекста.
- Профилактика: как делать правильно
Чтобы не “убивать прод” удалением логов:
- Использовать 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, работающий сценарий такой:
- Найти, кто их съедает:
Чаще всего это:
- мелкие временные файлы,
- некорректные лог- или кеш-директории,
- артефакты приложений, которые не чистятся.
Поиск “самых файловых” директорий:
# Топ-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.
- Удалить мусор/почистить каталоги
- Очистить:
- временные файлы,
- старые логи,
- кеши (tmp, app cache, build-артефакты).
- Важно:
- делать осознанно,
- помнить про кейс с открытыми, но удалёнными файлами (см. разбор в предыдущем вопросе):
- если удалён файл, но процесс держит дескриптор — inode освободится только после закрытия.
- Среднесрочное решение: пересоздание файловой системы с нужными 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.
Важно:
- Это делается ТОЛЬКО при создании файловой системы.
- На уже существующем разделе — только через полное пересоздание.
- Архитектурные и практические рекомендации
Чтобы не попасть в проблему inode starvation в продакшене:
- При проектировании:
- Оценивать профиль нагрузки:
- если ожидаются миллионы/десятки миллионов мелких файлов → сразу закладывать:
- корректный bytes-per-inode,
- либо использовать файловые системы, лучше работающие с множеством файлов (XFS, btrfs) или другие подходы (объектные хранилища).
- если ожидаются миллионы/десятки миллионов мелких файлов → сразу закладывать:
- Оценивать профиль нагрузки:
- В эксплуатации:
- Мониторить IUse% (использование inode) так же, как и дисковое пространство:
- Zabbix / Prometheus-экспортёр для inode.
- Не использовать файловую систему как бесконтрольный кеш без лимитов.
- Для временных/кеш-данных:
- периодическая уборка,
- TTL-стратегии,
- tmpfiles.d, systemd-tmpfiles, встроенные механизмы приложений.
- Мониторить IUse% (использование inode) так же, как и дисковое пространство:
Итог:
- Проверка свободных 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).
Примеры:
- Установить sticky bit на директорию:
chmod +t /shared
- Восьмеричная запись:
- 1777 означает:
- 1 — sticky bit,
- 7 — rwx для владельца,
- 7 — rwx для группы,
- 7 — rwx для остальных.
chmod 1777 /shared
- Убрать 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:
Краткое резюме:
- Sticky bit для директории — механизм защиты от удаления/переименования чужих файлов в общей директории.
- Используется там, где:
- многие могут писать,
- но каждый отвечает за свои файлы.
- Ключевой пример: /tmp с правами 1777.
- Это базовая вещь, которую нужно знать при администрировании Linux-систем и настройке безопасных shared-директорий.
Вопрос 11. Как корректно посмотреть список статических маршрутов в современной Linux-системе?
Таймкод: 00:20:22
Ответ собеседника: неполный. Упоминает только команду route, не акцентируя, что она устарела и в современных системах предпочтительно использовать ip route и семейство iproute2.
Правильный ответ:
В современных Linux-системах стандартным инструментом для работы с сетью является пакет iproute2. Команда route (из net-tools) считается устаревшей и во многих дистрибутивах уже не устанавливается по умолчанию. Для просмотра таблицы маршрутизации, включая статические маршруты, следует использовать утилиту ip.
Базовые команды:
- Просмотр текущей таблицы маршрутизации (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
- Просмотр IPv6-маршрутов:
ip -6 route show
- Маршрутизация по таблицам (policy routing)
В продакшн-средах часто используются дополнительные таблицы маршрутизации и policy routing. Для их просмотра:
- Список таблиц:
cat /etc/iproute2/rt_tables
- Просмотр конкретной таблицы:
ip route show table main
ip route show table 100
Это важно при сложных схемах, multi-homing, отдельной маршрутизации для VPN, исходящих IP и т.п.
- Как отличить статические маршруты от динамических
В типичной инфраструктуре:
- Статические маршруты:
- прописаны явно через:
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 (не статический).
- Почему 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, которые нужно уверенно знать:
- Все TCP-соединения:
ss -t
- Только установленные TCP-сессии:
ss -t -a
(или фильтровать по state)
- Все слушающие TCP-порты:
ss -t -l
- Все слушающие UDP-сокеты:
ss -u -l
- Подробная информация (PID, процесс, порты, состояния):
ss -tulpn
Расшифровка:
- t — TCP
- u — UDP
- l — listening
- p — показать процесс (PID/имя)
- n — не резолвить имена (быстрее и нагляднее)
- Фильтрация по порту или адресу:
Например, найти, кто слушает 8080 порт:
ss -tulpn | grep :8080
Это стандартный приём для проверки Go-сервиса или любого API.
- Просмотр сокетов в конкретном состоянии (пример):
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-дистрибутивов:
- Основной способ: /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 — на что дистрибутив похож (важно для выбора семейства).
- Дополнительные файлы (для совместимости и старых систем)
На старых или специфичных системах:
- RedHat/CentOS/Alma/Rocky:
cat /etc/redhat-release
- Debian:
cat /etc/debian_version
- SUSE:
cat /etc/SuSE-release
Но в современных системах /etc/os-release — основной источник.
- Проверка ядра
Версия ядра важна для:
- поддержки сетевых и системных фич,
- поведения cgroups, eBPF, современных протоколов.
Команды:
uname -r # версия ядра
uname -a # расширенная информация
- Проверка архитектуры
Для выбора бинарей (Go-сервисов, сторонних утилит):
uname -m
Примеры:
- x86_64 — 64-битная Intel/AMD.
- aarch64 — 64-битная ARM.
- Практический паттерн “зашёл на неизвестный сервер”
Минимальный набор команд:
cat /etc/os-release
uname -r
uname -m
Этого достаточно, чтобы:
- понять, какой пакетный менеджер использовать (apt, dnf/yum, zypper),
- как устроены сервисы (systemd vs init),
- какие версии библиотек/ядра доступны для приложений.
- Почему это важно для разработки и эксплуатации
Для прикладного разработчика и инженера:
- выбор правильных пакетов (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).
- Используется не для передачи пользовательских данных, а для:
- диагностики,
- сообщений об ошибках,
- служебных уведомлений о состоянии сети.
Ключевые моменты:
- Позиция в модели и стек протоколов
- ICMP логически принадлежит сетевому уровню:
- работает поверх IP,
- использует IP-пакеты (для ICMPv4 — протокол номер 1, для ICMPv6 — 58),
- но не является транспортным протоколом уровня TCP/UDP.
- Важно:
- ICMP не конкурирует с TCP/UDP и не “заменяет” их,
- он передаёт служебную информацию о доставке, маршрутизации и ошибках.
- Основные применения ICMP
На практике ICMP используется для:
-
Проверки доступности узлов:
- утилита ping:
- отправляет ICMP Echo Request,
- ожидает ICMP Echo Reply.
- Если есть ответ — хост на IP-уровне доступен (с учётом того, что ICMP мог быть ограничен firewall’ом).
- утилита ping:
-
Диагностики маршрутов:
- классический traceroute (в разных реализациях):
- использует TTL/HL и ICMP Time Exceeded от промежуточных маршрутизаторов;
- в Linux по умолчанию traceroute для UDP/TCP, но ICMP-сообщения играют ключевую роль в диагностике.
- mtr:
- сочетает ping + traceroute, активно опирается на ICMP-ответы.
- классический traceroute (в разных реализациях):
-
Сообщения об ошибках:
- Destination Unreachable:
- хост/сеть недоступны,
- порт недоступен (например, для UDP),
- административно запрещено (файрвол/ACL).
- Time Exceeded:
- TTL истёк по пути (защита от петель маршрутизации, используется traceroute).
- Fragmentation needed (для Path MTU Discovery):
- сообщает, что пакет слишком большой и требует уменьшения MTU.
- Destination Unreachable:
Это критично для корректной работы:
- диагностики сетевых проблем,
- path MTU discovery,
- понимания, почему трафик не доходит.
- Важно: 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 и т.п.).
- Практическое значение для инженера
Понимание ICMP важно, потому что:
- Если ping не работает:
- это может быть:
- реальная недоступность хоста,
- блокировка ICMP (firewall, security policy),
- нельзя автоматически делать вывод “хост умер”.
- это может быть:
- Многие сетевые механизмы зависят от ICMP:
- отключение ICMP по соображениям “безопасности” может ломать:
- Path MTU Discovery (фрагментация/черные дыры),
- корректную диагностику.
- отключение ICMP по соображениям “безопасности” может ломать:
При отладке:
- Используем:
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'а в том же окружении:
- не ломает систему,
- не вносит неожиданных изменений,
- детерминированно приводит состояние к заданному описанию.
- повторный запуск playbook'а в том же окружении:
Это критично для:
- воспроизводимости,
- безопасных повторных деплоев,
- предсказуемых rollback-ов,
- корректной работы CI/CD и GitOps-подхода.
Почему идемпотентность обязательна на практике
- Повторяемость и безопасность
В продакшене плейбуки запускаются многократно:
- при новых релизах,
- при добавлении нод,
- при восстановлении после сбоев,
- при дрейфе конфигурации.
Если роль не идемпотентна:
- каждый запуск может:
- пересоздавать ресурсы,
- перезаписывать конфиги без причины,
- дергать рестарт сервисов без необходимости,
- ломать уже работающие системы.
- итог:
- нестабильность,
- сложность расследования инцидентов,
- страх запускать автоматизацию “лишний раз”.
Идемпотентная роль позволяет запускать:
- “хоть каждый час” как drift correction,
- без риска побочных эффектов.
- Прозрачный diff и контроль изменений
Стандартный рабочий процесс:
- Сначала прогон с
--checkи--diff:- видно, какие реальные изменения будут внесены,
- Затем обычный запуск:
- изменения вносятся ровно один раз,
- повторный запуск показывает, что всё в состоянии “ok”, без
changed.
Это возможно только при идемпотентных задачах.
- Масштабирование и GitOps
При управлении десятками/сотнями нод:
- нужен детерминизм:
- одна и та же роль должна привести ноды к одному и тому же целевому состоянию,
- независимо от того:
- новый это сервер,
- или давно живущий, на котором уже были изменения.
Идемпотентность — ключ к тому, чтобы описывать состояние декларативно, а не выполнять набор “магических скриптов”.
Практические техники обеспечения идемпотентности
- Использовать декларативные модули 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
- Аккуратная работа с 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, нет лишнего рестарта.
- Обход подводных камней с 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
- Корректная работа с сервисами
Не рестартовать сервис без причины:
- использовать handlers:
- таски вызывают
notifyтолько при реальных изменениях.
- таски вызывают
- Не писать:
- name: Restart nginx always
service:
name: nginx
state: restarted
- Вместо этого:
handlers:
- name: Restart nginx
service:
name: nginx
state: restarted
И notify из тех тасок, которые реально поменяли конфигурацию.
Как проверяется идемпотентность на практике
Реалистичный, профессиональный цикл разработки роли:
- Первый прогон роли:
ansible-playbook site.yml -l test-host
- Ожидаем корректные
changedна создании ресурсов.
- Повторный прогон той же роли на том же хосте:
ansible-playbook site.yml -l test-host
- Ожидаем:
- 0 задач в статусе
changed(или только строго обоснованные, например, динамические timestamp'ы, если они не критичны).
- 0 задач в статусе
- Если задачи показывают
changedбез реальных изменений:- это баг в идемпотентности:
- нужно исправить (правильные модули, условия, параметры).
- это баг в идемпотентности:
- Использование
--checkи--diff:
ansible-playbook site.yml -l test-host --check --diff
- Проверка:
- в check-режиме роль должна адекватно предсказывать изменения,
- не ломаться на shell/command без поддержки check-mode,
- не показывать “вечный diff” для неизменяемых сущностей.
- Интеграция с 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", плейбук (и окружающая конфигурация) определяют реальные значения под конкретное окружение.
Практический смысл такой модели:
- Роли должны быть:
- переиспользуемыми,
- не навязывающими жёсткие значения,
- легко конфигурируемыми снаружи.
- defaults:
- задают значения, которые позволят роли “завестись из коробки”,
- но не блокируют настройку через:
- vars в плейбуке,
- group_vars,
- host_vars,
- inventory vars,
- extra_vars (самый верхний приоритет).
- Это критично для:
- развертывания одного и того же набора ролей в разных окружениях (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.
Примеры:
- Полностью последовательный деплой (по одному хосту):
- hosts: app_servers
serial: 1
roles:
- my_app
Поведение:
- Ansible:
- берёт первый хост → выполняет все задачи play,
- затем второй,
- и так далее.
- Это безопасный вариант для:
- stateful-сервисов,
- критичных систем, где нельзя “уронить” сразу все инстансы.
- Rolling-деплой батчами
Например, по два сервера за раз:
- hosts: app_servers
serial: 2
roles:
- my_app
Или гибко, в процентах:
- hosts: app_servers
serial: 30%
roles:
- my_app
Поведение:
- Обновляет 30% хостов, проверяет результат,
- затем следующие 30% и так далее.
Это подходит для:
- горизонтально масштабируемых сервисов за балансировщиком,
- микросервисов, где часть инстансов может быть временно недоступна.
Дополнительные настройки для надёжности
- Ограничение по ошибкам:
max_fail_percentage:- если процент упавших хостов превышает порог — play останавливается.
Пример:
- hosts: app_servers
serial: 2
max_fail_percentage: 10
roles:
- my_app
- Жёсткая остановка при ошибке:
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.
- по паролю (
- при выполнении playbook Ansible расшифровывает данные:
Пример: файл 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
- Изоляция и быстрый feedback
Molecule позволяет:
- запускать роль в изолированном окружении:
- Docker-контейнеры,
- Podman,
- Vagrant (VM),
- cloud-драйверы (EC2 и др.);
- прогонять:
- полный цикл: create → converge → idempotence → verify → destroy;
- получать быстрый фидбек на каждый коммит.
Это существенно снижает риск того, что изменения роли “сломают” прод или стейдж.
- Структура сценария Molecule
Типичный workflow Molecule-сценария:
molecule create:- поднять тестовое окружение (контейнер/VM).
molecule converge:- применить тестируемую роль (как в реальном плейбуке).
molecule idempotence:- повторно применить роль и убедиться, что:
- нет лишних изменений (
changed: 0), - роли действительно идемпотентны.
- нет лишних изменений (
- повторно применить роль и убедиться, что:
molecule verify:- выполнить тесты верификации:
- через
testinfra,pytest, shell-check-и, - проверить файлы, сервисы, порты, конфиги.
- через
- выполнить тесты верификации:
molecule destroy:- снести окружение, чтобы не копить мусор.
- Примеры проверок (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-файлов”.
- Интеграция с CI/CD
Зрелая практика:
- Для каждой роли (или монорепозитория с ролями):
- на каждый PR/commit:
- запускается Molecule-сценарий:
- создаётся окружение,
- применяется роль,
- проверяется идемпотентность,
- выполняются проверки состояния.
- запускается Molecule-сценарий:
- на каждый PR/commit:
- При падении:
- изменения не мержатся, пока не починены тесты.
Это:
- защищает от регрессий,
- гарантирует, что роль рабочая на заявленных платформах (Ubuntu, Debian, CentOS, etc.), если настроены матрицы окружений.
- Альтернативы и дополнения
Помимо Molecule можно использовать:
- Самодельные сценарии:
- ansible-playbook в Docker/VM + testinfra/pytest/bash-проверки.
- Интеграцию с:
- Kitchen (less common),
- Terratest (если Ansible интегрирован в более широкий IaC-стек),
- линтеры:
- ansible-lint для стайлгайдов и типичных ошибок.
Но Molecule:
- даёт стандартный, удобный и хорошо поддерживаемый подход,
- интегрируется с ansible-lint и CI.
- Что важно уметь и понимать
Качественный ответ и реальная практика включают:
- Использовать 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-конфигураций.
Основные свойства и поведение
- Hidden job / шаблон
Если job в .gitlab-ci.yml начинается с точки:
.base_job:
image: golang:1.22
before_script:
- go version
tags:
- docker
- GitLab:
- не воспринимает
.base_jobкак реальную задачу pipeline; - не показывает её в UI;
- не запускает её напрямую.
- не воспринимает
- Это просто именованный блок настроек, который можно расширять.
- Использование через 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.
- если поменять образ или общие шаги, достаточно изменить
- Комбинирование нескольких шаблонов
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 ./...
Так можно:
- разделить ответственность:
- один шаблон — базовый образ и окружение,
- другой — кеширование,
- третий — правила запуска;
- собирать итоговую джобу как композицию, а не через копипаст.
- Отличие от 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, если нужно.
- Практическое значение для реальных пайплайнов
Подход с "точечными" 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 вместо прямого пуша в защищённые ветки.
Базовый сценарий выглядит так.
- Клонирование репозитория
git clone git@gitlab.example.com:group/project.git
cd project
- Создание рабочей ветки от актуальной основной
Всегда начинаем с обновлённой основной ветки (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 — и это правильно.
- Внесение изменений и индексирование
После изменения файлов:
- Проверяем статус:
git status
- Добавляем изменённые файлы:
git add path/to/file1 path/to/file2
# или все отслеживаемые изменения:
git add -u
# или и новые файлы тоже:
git add .
- Создание коммита
Формируем осмысленное сообщение:
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".
- не "fix", а, например:
- В серьёзных проектах:
- часто используются соглашения (Conventional Commits и т.п.).
- Отправка ветки в удалённый репозиторий
Первый push новой ветки:
git push -u origin feature/my-change
Флаги:
-u(или--set-upstream):- связывает локальную ветку с удалённой,
- далее можно делать просто
git push/git pullбез указания origin/ветки.
Следующие итерации:
- после новых изменений:
git add ...git commit -m "..."git push
- Создание Merge Request (Pull Request)
Далее:
- Через интерфейс GitLab/GitHub/Gitea:
- создаём MR/PR из
feature/my-changeвmain(или нужную целевую ветку), - проходим code review, CI-пайплайны, проверки.
- создаём MR/PR из
- В некоторых CLI/инструментах:
- можно создавать MR/PR командами (
glab,gh), но это уже надстройка.
- можно создавать MR/PR командами (
- Обновление ветки при изменениях в 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, - не перезаписывает чужие изменения.
- безопаснее обычного
- Краткий минимальный набор для базового случая
Если свести к самому необходимому (без ребейзов, конфликтов и т.д.):
- Клонировать:
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.
Структурно:
- Continuous Integration (CI)
Общая база:
- каждое изменение:
- интегрируется часто,
- автоматически собирается,
- прогоняются тесты (unit, интеграционные, линтеры, security-checkи),
- предотвращается “integration hell”.
И CI, и CD-подходы подразумевают зрелый CI как фундамент.
- Continuous Delivery
Типичный pipeline:
- Коммиты → CI (сборка, тесты, линтеры).
- Сборка артефакта (docker image, бинарь, helm chart и т.п.).
- Автоматический деплой на:
- dev / test / stage окружения.
- Автоматические проверки:
- smoke-тесты,
- интеграционные тесты,
- e2e,
- проверка миграций БД.
- Результат:
- “green build” → помечен как готовый к выкладке в prod.
Но шаг в прод:
- требует явного триггера:
- manual job в GitLab CI,
- ручной запуск CD-пайплайна,
- change request в ITSM системе.
Сильные стороны:
- Контролируемый выпуск фич:
- удобно для регуляторных требований,
- для сложных бизнес-процессов, где нужна координация.
- При этом:
- скорость остаётся высокой — деплой не превращается в "ритуал боли".
- 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.
- Как это отразить на практике (пример с 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
- Хранение конфигурации:
- 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-пайплайна со сборкой и тестами.
- Триггеры (on)
Вместо only/except или rules в GitLab используются события:
on: push,on: pull_request,on: workflow_dispatch,on: schedule,on: release,on: tagи т.д.- Можно гибко фильтровать по веткам/путям.
- 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 или динамическим конфигурациям.
- Повторное использование логики
Вместо extends и скрытых job'ов как в GitLab:
- используются:
- composite actions (переиспользуемые наборы шагов),
- reusable workflows (
workflow_call), - общедоступные actions из GitHub Marketplace.
Подход аналогичен:
- вынос общих шагов (сборка, линт, деплой) в отдельные reusable workflows/actions;
- в конкретных репозиториях подключение через короткую декларацию.
- Работа с секретами
Аналогично GitLab CI:
- Секреты хранятся в:
Settings → Secrets and variables → Actions,- уровня репозитория / организации / окружения.
- Доступ:
- через
secrets.MY_SECRET:
- через
- name: Deploy
run: ./deploy.sh
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
Ключевые принципы безопасности:
- не хардкодить,
- не логировать секреты,
- разделять секреты по окружениям.
- Деплой, окружения, 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 ключевых компонента:
-
Агенты/экспортёры (сбор данных):
- node exporter, blackbox exporter, процесс-экспортёры,
- агенты Zabbix, telegraf, custom-пробки,
- метрики приложений (Prometheus client в Go и др.).
-
Хранилища метрик:
- Prometheus (time-series),
- VictoriaMetrics, Thanos, Cortex/Mimir (масштабирование и долговременное хранение),
- Zabbix (более традиционный подход, часто для инфраструктуры).
-
Визуализация:
- Grafana как де-факто стандарт,
- встроенные графы Zabbix (дополнительно, но не как единственный инструмент).
-
Алертинг:
- Prometheus Alertmanager,
- Zabbix actions,
- интеграции с Slack/Telegram/PagerDuty,
- единые политики эскалаций.
Уровни мониторинга (сверху вниз и снизу вверх)
- Уровень виртуализации и облака
Что смотрим:
- Хост-гипервизор / cloud:
- загрузка CPU, steal time, overcommit,
- память, диск, сеть,
- состояние нод/зон/regions,
- ошибки сториджа и сети.
- Для managed-облаков:
- CloudWatch/Stackdriver/и т.п.,
- квоты, ошибки API, статус managed-сервисов.
Зачем:
- Понять, проблема в платформе или в нашей VM/приложении.
- Например:
- высокий steal time → проблема oversubscription, а не в коде Go-сервиса.
Инструменты:
- Метрики гипервизора, cloud monitor,
- экспорт в Prometheus/Grafana,
- алерты на доступность и ресурсы.
- Уровень ОС и инфраструктуры (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 совпали с ошибками сети.
- Уровень сетевой доступности и периметра
- Blackbox-мониторинг:
- внешние и внутренние HTTP/TCP/ICMP-проверки:
- доступность API, DNS, TLS-сертификаты,
- DNS-резолв,
- цепочки до конкретных эндпоинтов.
- внешние и внутренние HTTP/TCP/ICMP-проверки:
- Важные проверки:
- /health, /ready у сервисов,
- не только “порт открыт”, но и “сервис отвечает корректно”.
Инструменты:
- blackbox_exporter,
- curl/http-checks в Zabbix,
- алерты:
- “service not reachable from X”,
- “TLS expires soon”.
- Уровень приложений (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-критичные события.
Они позволяют:
- понять, "больно ли" пользователю,
- отличить техпроблему без влияния на бизнес от реального инцидента.
- Уровень баз данных и storage
Мониторим для PostgreSQL/ClickHouse/Redis и т.п.:
- доступность и время ответа,
- количество подключений,
- lock-и, deadlock-и,
- репликацию (lag),
- slow queries,
- размеры таблиц и индексов,
- для ClickHouse:
- merges, ошибки, задержки вставок.
Инструменты:
- postgres_exporter, clickhouse_exporter и аналоги,
- Zabbix-шаблоны,
- алерты:
- рост slow queries,
- рост лагов репликации,
- исчерпание коннектов,
- деградация QPS/latency.
- Уровень логов и трассировок
Метрики без логов и трейсинга — неполная картина.
- Логи:
- централизованный сбор (ELK / Loki / OpenSearch),
- корреляция с метриками:
- есть алерт → проваливаемся в логи конкретного сервиса/инстанса.
- Distributed tracing:
- OpenTelemetry, Jaeger, Tempo и т.п.,
- позволяет увидеть путь запроса через микросервисы,
- найти “узкое место”.
В связке:
- метрика p99_latency выросла → смотрим трейс → видим, что 80% времени уходит на конкретный запрос к БД.
Системная работа с метриками и алертами
- SLI/SLO и алерты по симптомам, а не только по ресурсам
Важно не только "CPU>80%", но и:
- SLI:
- доля успешных запросов,
- латентность,
- ошибки по типам.
- SLO:
- например:
- 99% запросов за 300 мс,
- error rate < 0.5%.
- например:
Алерты:
- на нарушение SLO:
- рост p95 latency,
- рост 5xx,
- падение успешных бизнес-операций.
- инфраструктурные алерты:
- как контекст, а не единственный источник тревог.
- Многоуровневые алерты и эскалации
- Warning:
- ранний сигнал, не будит ночью, но требует внимания.
- Critical:
- влияющий на пользователей/бизнес → инцидент.
Зрелый стек:
- Prometheus Alertmanager / Zabbix actions:
- маршрутизация алертов,
- подавление дубликатов,
- зависимостей (упал хост → не спамить по каждому сервису на нём),
- эскалации, расписания.
- Корреляция и drill-down
Хорошо выстроенная система позволяет по цепочке:
- “Просел бизнес-показатель → метрики приложения → инфраструктура → логи → трейс → корень проблемы”.
Для этого:
- метрики метятся:
- service, instance, version, region, environment;
- есть понятные дашборды:
- общий overview,
- по каждому сервису,
- по БД,
- по кластерам.
- Автоматизация и "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 и бизнес-значимость сервисов;
- разделять уровни критичности;
- использовать разные каналы и политики эскалации;
- группировать и коррелировать алерты;
- устранять шум.
Структурированный подход
- Базис: 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 и бизнес.
- Уровни критичности (severity)
Практически полезная градация (может варьироваться, но идея такая):
- Info:
- информационные события:
- деплой выполнен,
- нода добавлена,
- плановое обслуживание.
- Не требуют реакции on-call.
- информационные события:
- Warning:
- потенциальная проблема, но SLO ещё не нарушен:
- диск 80%,
- latency немного растёт,
- единичные ошибки 5xx,
- нестабильный пинг, но без влияния на пользователей.
- Канал: технические каналы (Slack/чат), без ночных звонков.
- потенциальная проблема, но SLO ещё не нарушен:
- Major/High:
- заметная деградация, есть риск или локальное нарушение SLO:
- latency p95 выше порога X в течение N минут,
- error rate выше порога,
- реплика БД отстаёт,
- недоступен один из нескольких инстансов за балансировщиком.
- Требует внимательного разбора, но не обязательно будить всю команду ночью.
- заметная деградация, есть риск или локальное нарушение SLO:
- Critical:
- прямое нарушение SLO/SLA, значимое влияние на пользователей или деньги:
- недоступен критичный API,
- 5xx/ошибки аутентификации/платежей,
- падение единственного инстанса БД,
- потеря кворума в кластере,
- недоступность целого региона.
- Требует немедленной реакции on-call, автоэскалации.
- прямое нарушение SLO/SLA, значимое влияние на пользователей или деньги:
- Каналы доставки и эскалации
Для разных уровней критичности — разные каналы:
- Info:
- логирование в систему мониторинга,
- низкоприоритетные сообщения в Slack/Teams.
- Warning:
- Slack/чат-канал, дашборды,
- без SMS/звонков.
- High:
- уведомления в on-call канал (Slack с высоким приоритетом, email),
- при накоплении/увеличении — возможна эскалация.
- Critical:
- интеграция с on-call платформой:
- PagerDuty, VictorOps, Opsgenie,
- звонки, push, SMS по дежурному графику.
- Эскалации:
- если за N минут нет ack от первого дежурного — звонок второму и т.д.
- интеграция с on-call платформой:
Важно:
- одно и то же событие не должно “долбить” во все каналы сразу;
- должна быть централизованная точка управления (Alertmanager, Zabbix actions, on-call система).
- Примеры критичных и некритичных событий
Критичные (примерно 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”, но есть достаточный запас.
Это не значит “игнорировать”, а:
- фиксировать,
- планировать действия,
- не будить ночами без реальной боли.
- Шумоподавление, группировка и корреляция
Зрелая система алертинга:
- Группирует события:
- если упал хост:
- не слать 20 алертов “nginx down”, “app down”, “node exporter down” отдельно;
- один инцидент “host X down” + связанный контекст.
- если упал хост:
- Учитывает зависимости:
- если недоступен upstream/БД:
- не спамить по каждому зависимому сервису отдельно,
- показывать корневую причину.
- если недоступен upstream/БД:
- Использует устойчивые условия:
- алерт не по одному пику, а по стабильному нарушению за интервал (avg/max over time).
- Управляет флаппингом:
- предотвращает постоянное “up/down” при пограничных состояниях.
В инструментах:
- Prometheus Alertmanager:
- routing, grouping (по service/cluster/instance), inhibit rules.
- Zabbix:
- dependencies, escalation rules, event correlation.
- Привязка к 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 ещё не нарушен”.
- Практические рекомендации по реализации
- Явно задокументировать:
- уровни 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 и контейнерами
Ключевые аспекты, которые ожидаются:
- Сборка 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.
- Умение работать с 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"
- Понимание базовых концепций контейнеризации
- Изоляция через namespaces и cgroups;
- Обращение к ресурсам хоста:
- volume'ы, сети;
- Отличия контейнера от ВМ:
- общий kernel, lightweight runtime.
- Интеграция с CI/CD и Kubernetes
- Автоматическая сборка образов в CI (GitLab CI, GitHub Actions);
- Тегирование (по коммиту, версии, ветке, окружению);
- Push в приватные/публичные registry;
- Использование образов в:
- Deployment/StatefulSet манифестах,
- Helm-чартах,
- GitOps-подходах.
Как передать собранный Docker-образ другому человеку
Два основных промышленных способа (оба были упомянуты кандидатом, и это верно):
- Через контейнерный registry (рекомендуемый путь)
Это стандартный и предпочтительный вариант.
Шаги:
- Протегировать образ:
docker build -t registry.example.com/myteam/myapp:1.0.0 .
- Залогиниться и запушить:
docker login registry.example.com
docker push registry.example.com/myteam/myapp:1.0.0
- Другой человек на своей машине:
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 и др.
- Через файл (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:
- Регистрация и управление узлом
- kubelet:
- регистрирует ноду в API-сервере (Node object),
- периодически отправляет:
- статус ноды,
- ресурсы (CPU, память, диски),
- условия (Ready/NotReady, DiskPressure, MemoryPressure и т.п.).
- Если kubelet не репортит состояние:
- кластер может считать ноду недоступной,
- запустить перемещение Pod'ов на другие ноды (при наличии контроллеров).
- Управление Pod'ами на узле
kubelet:
- следит за списком Pod'ов, назначенных на эту ноду (через API-сервер);
- для каждого такого Pod'а:
- обеспечивает, чтобы:
- нужные контейнеры были запущены,
- в нужных версиях образов,
- с нужными параметрами (env, volume, probes, ресурсы);
- при падениях/рестартах:
- перезапускает контейнеры согласно policy (
Always,OnFailure,Never).
- перезапускает контейнеры согласно policy (
- обеспечивает, чтобы:
Если Pod не должен больше работать на узле (удалён/переназначен):
- kubelet останавливает его контейнеры и очищает связанные ресурсы.
- Взаимодействие с контейнерным рантаймом
kubelet не запускает контейнеры напрямую:
- он взаимодействует с контейнерным runtime через:
- CRI (Container Runtime Interface),
- поддерживаемые рантаймы:
- containerd, CRI-O и др.
Основные действия:
- pull образов;
- создание container sandbox (network namespace, etc.);
- запуск/остановка контейнеров;
- управление логами и exit-кодами.
- Работа с volumes и конфигурацией
kubelet отвечает за:
- монтирование томов:
- PVC (PersistentVolumeClaim),
- hostPath,
- configMap, secret, projected volumes и т.п.;
- корректное предоставление этих томов контейнерам;
- обновление:
- configMap/secret (в рамках поддерживаемой семантики),
- если они примонтированы как volume.
- Пробы живости и готовности (liveness / readiness / startup)
kubelet выполняет:
- livenessProbe:
- решает, нужно ли перезапустить контейнер (если приложение зависло).
- readinessProbe:
- решает, можно ли считать Pod готовым принимать трафик:
- влияет на включение/исключение Pod'а из Endpoints/EndpointSlice.
- решает, можно ли считать Pod готовым принимать трафик:
- startupProbe:
- позволяет отделить старт длительного приложения от нормальной liveness.
Это критично для:
- корректной балансировки,
- отказоустойчивости,
- а не “тупого” проверки по факту наличия процесса.
- Отчёт статуса Pod'ов и контейнеров
kubelet:
- собирает информацию:
- какие контейнеры запущены,
- их статус, рестарты, причины падений,
- отправляет статус Pod'а в API-сервер:
- это то, что вы видите в
kubectl get pods -o wideиkubectl describe pod.
- это то, что вы видите в
Таким образом, API-сервер знает реальное состояние, а контроллеры (ReplicaSet, Deployment и т.п.) могут принимать решения.
- 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 это достигается через:
- podAntiAffinity (предпочтительный механизм для реплик)
- node affinity / nodeSelector / topologySpreadConstraints (для продвинутого контроля распределения)
- (исторически) pod (anti)affinity с topologyKey
Разберём по сути.
- 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.
- не размещать Pod, если на ноде уже есть Pod с
- Если свободных нод меньше, чем replicas:
- часть Pod'ов останется Pending (и это правильно: лучше недоразместить, чем сломать гарантию).
- жёсткое требование для scheduler:
topologyKey: "kubernetes.io/hostname":- означает, что правило действует на уровне ноды:
- “не клади два Pod'а с этим label на один hostname”.
- означает, что правило действует на уровне ноды:
Это типичный способ “разнести реплики по нодам”.
Если хотим, чтобы это было “по возможности, но не строго”:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: my-app
topologyKey: "kubernetes.io/hostname"
Тогда:
- scheduler постарается разнести Pod'ы,
- но при нехватке ресурсов может всё равно положить несколько реплик на одну ноду.
- 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'ы внутри этого пула нод.
- 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.
- Практические рекомендации
- Для простого и понятного решения “каждая реплика на своей ноде”:
- используем
podAntiAffinityсrequiredDuringSchedulingIgnoredDuringExecutionиtopologyKey: "kubernetes.io/hostname".
- используем
- Для гибкого, балансированного распределения:
- используем
topologySpreadConstraints:- особенно в мультизональных кластерах.
- используем
- Следим за консистентностью:
- убедиться, что:
- достаточно нод (min nodes >= replicas),
- ресурсы (requests/limits) адекватны,
- нет конфликтующих правил affinity/taints/tolerations.
- убедиться, что:
- Важно:
- разнесение реплик — часть стратегии HA:
- вместе с readiness/liveness probes,
- корректной конфигурацией сервисов и клиентов,
- отказоустойчивостью БД и внешних зависимостей.
- разнесение реплик — часть стратегии HA:
Краткое резюме:
- Для разнесения реплик одного 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).
- 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 с задачами в очереди.
- Stateful-приложения
Суть:
- Приложение опирается на состояние, которое важно сохранить:
- данные БД,
- очередь сообщений,
- файловое хранилище,
- состояния репликации.
- Идентичность инстанса (Pod'а) может иметь значение:
- фиксированное имя,
- стабильный network identity,
- закреплённый диск с данными.
Важно:
- “stateful” не равен “StatefulSet”.
- Stateful-приложения МОЖНО запускать и через Deployment, если:
- состояние полностью вынесено во внешнее managed-хранилище (RDS, внешние кластеры БД);
- Pod — только stateless-клиент этого хранилища.
Но если состояние хранится в самом кластере (на диске, привязанном к Pod'у), уже нужны особые механизмы.
- 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, даже после пересоздания/перезапуска.
- стабильные DNS-имена Pod'ов:
- Используется для:
- БД (PostgreSQL в кластере, ClickHouse-узлы, Cassandra),
- очередей (Kafka, RabbitMQ-кластеры),
- приложений, которым нужен стабильный storage и hostname.
Ключевая идея:
- StatefulSet сам по себе не делает приложение "stateful".
- Он даёт инфраструктурные гарантии, необходимые stateful-приложениям:
- фиксированные идентификаторы,
- закреплённые volume'ы.
- Роль 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:
- перезапуск не теряет данные.
- Как мыслить правильно
- 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-0→pvc data-my-app-0, и это соответствие сохраняется при пересоздании.
- обеспечивает стабильную идентичность Pod'ов (
Разберём подробнее.
- 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 — это хрупко и против шаблона.
- 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создаётся PVCdata-my-db-0, - для
my-db-1—data-my-db-1, - для
my-db-2—data-my-db-2. - При перезапуске:
my-db-0всегда будет использовать СВОЙ PVCdata-my-db-0,- данные не потеряются при пересоздании Pod'а.
Это критично для:
- реплик PostgreSQL, ClickHouse, Kafka, Cassandra и др.,
- где каждый Pod хранит свой фрагмент/реплику данных.
- Практические выводы
-
Если приложение stateless:
- Deployment,
- без PVC или с общим read-only/read-many PVC,
- масштабирование “как угодно” без привязки к дискам.
-
Если приложению нужен persistent state НА УРОВНЕ КАЖДОГО ИНСТАНСА:
- StatefulSet + volumeClaimTemplates:
- per-Pod PVC,
- стабильные имена Pod'ов,
- предсказуемое поведение при рестартах.
- StatefulSet + volumeClaimTemplates:
-
Не стоит:
- пытаться эмулировать поведение 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-систем, где важна индивидуальность инстансов (БД, кластера),
- случаев, когда балансировка/логика находится на стороне клиента или приложения.
Разберём по слоям.
- Обычный 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-сервисов с прозрачной балансировкой.
- Headless Service:
clusterIP: None
Когда указываем:
spec:
clusterIP: None
Kubernetes перестаёт выделять виртуальный IP:
- Нет ClusterIP.
- kube-proxy не настраивает VIP для этого сервиса.
- Вместо этого:
- DNS-сервер Kubernetes возвращает:
- либо IP конкретных Pod'ов (A-записи),
- либо SRV-записи для портов.
- DNS-сервер Kubernetes возвращает:
Поведение 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 и др.).
- Помимо общего имени, генерируются записи вида:
- Типичные сценарии использования headless-сервисов
- StatefulSet и stateful-приложения
Для кластера БД или брокеров нам нужны:
- уникальные идентификаторы инстансов,
- прямой доступ к каждому инстансу,
- возможность различать роли (leader/follower, shard и т.п.).
Используем связку:
- StatefulSet:
- даёт стабильные Pod-имена:
db-0,db-1, ...
- даёт стабильные Pod-имена:
- Headless Service (
clusterIP: None):- даёт DNS:
db-0.db.default.svc.cluster.local,db-1.db.default.svc.cluster.local.
- даёт DNS:
Пример:
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-скрипт кластера БД может:
- обращаться к нужной ноде по имени,
- строить топологию без дополнительного сервис-дискавери.
- Клиентский load balancing / сервис-дискавери
Для некоторых систем:
- gRPC-клиенты,
- сервисы с встроенным клиентским load-balancing,
- сторонние сервис-меши,
нужен список эндпоинтов (Pod IP), а не один виртуальный адрес.
Headless-сервис:
- отдаёт список IP всех Pod'ов:
- клиент сам балансирует,
- сам реализует retry/health-check.
Это:
- даёт больше контроля,
- иногда нужно для специализированных протоколов.
- Интеграция с Service Mesh / DNS-наблюдением
Некоторые mesh/системы обнаружения используют headless-сервисы как источник правды о Pod'ах.
- Что headless-сервис НЕ делает
Важно развеять заблуждения:
clusterIP: NoneНЕ означает:- “нет pod'ов”,
- “сервис не работает”.
- Pod'ы:
- существуют,
- endpoints заполняются,
- DNS даёт прямые IP этих pod'ов.
Это управляемый режим сервис-дискавери, а не отключение сервиса.
- Когда использовать обычный 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.
Разберём по этапам.
- Исходные сущности: 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.
- 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, если нужно.
- Реализация через 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'ов (длинные цепочки, линейный матч).
- Реализация через IPVS
В IPVS-режиме:
- kube-proxy:
- настраивает IPVS (L4-балансировщик в ядре),
- создаёт virtual services (ClusterIP:port),
- добавляет real servers (Pod IP:port).
- балансировка:
- rr, wrr, lc, и др., реализованные в IPVS.
Плюсы:
- лучше масштабируется,
- гибче по алгоритмам балансировки.
- 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 (или его полная замена).
- Разные типы 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,
- балансировка и маршрутизация — на клиенте или приложении.
- Что важно понимать на собеседовании
Хороший ответ должен отражать:
- Логическую модель:
- 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, реплики, сети.
- Базовые механизмы: репликация PostgreSQL
PostgreSQL предоставляет:
- Streaming Replication:
- основа большинства HA-сценариев.
- Primary (ранее “master”) стримит WAL-записи на standby-реплики.
- Типы:
- Asynchronous:
- Primary не ждёт подтверждения от реплики.
- RPO > 0 возможно (потеря последних транзакций при падении primary).
- Synchronous:
- транзакция считается подтвержденной только после записи WAL минимум на один synchronous-standby.
- ниже риск потери данных (RPO ~ 0),
- выше латентность и зависимость от standby.
- Asynchronous:
Важно уметь:
- настраивать репликацию:
primary_conninfo,primary_slot_name,- replication slots для предотвращения удаления нужных WAL;
- понимать влияния sync vs async для конкретного SLA.
- Примитивный сценарий: ручной master + standby
Ручной сценарий, который часто “умеют”, но которого недостаточно:
- Есть primary и один или несколько standby.
- При падении primary:
- вручную promote standby:
pg_ctl promoteилиSELECT pg_promote();
- перенастроить приложения/HAProxy на новый primary.
- вручную promote standby:
Проблемы:
- Долгое и не всегда предсказуемое переключение.
- Риск split-brain, если старый primary внезапно “ожил”.
- Нет формализованного механизма выбора лидера.
Это базовый уровень, но не полноценное HA-решение.
- Production-уровень: автоматизированный failover
Зрелые решения строятся вокруг:
- consensus/coordination (etcd/Consul/ZooKeeper),
- контроллера, который:
- знает топологию,
- следит за health,
- принимает решение, кого promote,
- обновляет routing/виртуальный IP.
Наиболее распространённый и зрелый стек:
- Patroni
- Patroni:
- управляет PostgreSQL-инстансами,
- использует DCS (distributed configuration store):
- etcd, Consul, ZooKeeper,
- следит за здоровьем primary,
- инициирует failover/promote standby при необходимости.
- Схема:
- Patroni на каждом узле с PostgreSQL;
- один из узлов — лидер (primary), остальные — standby;
- при падении Легитимный новый лидер выбирается через консенсус в DCS;
- минимизация split-brain:
- если узел потерял контакт с DCS — не может самовольно стать primary.
- Управление точкой входа
Для приложений нужен единый endpoint к “актуальному primary”.
Стандартные подходы:
- HAProxy / pgBouncer с health-check для primary:
- HAProxy:
- использует PostgreSQL-check (например,
option pgsql-check) или HTTP health Patroni, - трафик на backend, который reported как master.
- использует PostgreSQL-check (например,
- HAProxy:
- Виртуальный 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.
- VRRP/виртуальный IP
VRRP (keepalived) часто используется:
- как простой способ иметь один IP для текущего primary.
Схема:
- Несколько узлов:
- только один держит VIP (MASTER),
- остальные в BACKUP.
- Здоровье primary:
- скрипты/health-check:
- если PostgreSQL или Patroni на узле в некорректном состоянии:
- keepalived теряет MASTER-статус,
- VIP уезжает на другой узел.
- если PostgreSQL или Patroni на узле в некорректном состоянии:
- скрипты/health-check:
- Важно:
- связать логику VIP с реальной ролью PostgreSQL (primary), а не только “postgres слушает порт”,
- иначе легко получить split-brain (две ноды считают себя primary и держат VIP).
- Предотвращение split-brain
Критический момент для HA PostgreSQL:
- Нельзя допустить одновременного существования двух primary, оба принимающих запись.
Решается:
- Использованием внешнего quorum (DCS: etcd/Consul),
- Жёсткой логикой:
- если нода потеряла связь с DCS — запрещено promote,
- fencing/STONITH в связке с оркестраторами (Pacemaker/Corosync в старых решениях),
- Чётким порядком:
- manual override — только через контролируемые процедуры.
- Мониторинг HA-кластера PostgreSQL
Зрелый опыт должен включать мониторинг:
- Ролей:
- кто primary, кто standby.
- Лага репликации:
pg_stat_replication,- задержка WAL.
- Репликационных слотов:
- отсутствие overflow и отставания.
- Доступности:
- health-check primary/standby,
- статус Patroni/repmgr (если используется).
- Метрик производительности:
- TPS,
- latency,
- блокировки,
- slow queries.
И алертов:
- Лаг репликации > X.
- Нет доступных standby.
- Некорректное состояние: два master-кандидата.
- Переполнение WAL-диска из-за нечитабельных реплик.
- Интеграция с приложениями (на примере 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).
- Облачные и 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).
Рассмотрим по шагам.
- 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
- 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
- Связывание 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 — реализация.
- Использование 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на основе PVCapp-data. - PVC уже Bound к конкретному PV.
- Kubernetes монтирует реальное хранилище (PV) в контейнер по
mountPath.
Важно:
- При перезапуске Pod:
- PVC остаётся,
- PV остаётся привязан,
- данные в
/var/lib/my-appсохраняются.
- Pod можно пересоздать на другой ноде (если storage-класс поддерживает это) — PVC/PV смонтируются заново.
- Режимы использования и политика жизни PV
persistentVolumeReclaimPolicy определяет, что делать с PV после удаления PVC:
- Retain:
- PV и данные остаются.
- Требуется ручная очистка/перепривязка.
- Delete:
- PV и реальный storage удаляются автоматически (для динамических provisioners).
- Recycle (deprecated):
- устаревший режим.
Это важно для:
- сценариев, где данные нельзя потерять автоматически (Retain),
- или наоборот, временных окружений (Delete).
- StatefulSet и volumeClaimTemplates
В контексте stateful-приложений:
- StatefulSet использует
volumeClaimTemplatesдля автоматического создания отдельного PVC на Pod.
Кратко:
- Под
my-db-0→ PVCdata-my-db-0→ PV (диск-0), - Под
my-db-1→ PVCdata-my-db-1→ PV (диск-1), - и эти связи сохраняются при пересоздании Pod'ов.
- Практический смысл для архитектуры
PVC/PV-подход даёт:
- Чёткую границу:
- приложения описывают “что нужно” (через PVC),
- платформа/админы отвечают за “как предоставить”.
- Независимость:
- можно сменить backend (NFS → Ceph → облачный диск), не переписывая deployment-приложения.
- Устойчивость:
- данные не пропадают при рестарте/пересоздании Pod'ов.
- Контроль:
- через StorageClass задаём:
- тип дисков,
- политики,
- параметры производительности.
- через StorageClass задаём:
Краткое резюме:
- PVC — запрос на persistent-ресурс с заданными характеристиками.
- PV — конкретный persistent-ресурс, удовлетворяющий этому запросу.
- Kubernetes автоматически связывает PVC с подходящим PV.
- Pod монтирует PVC, а данные живут на PV, переживая перезапуски Pod'ов и обеспечивая отделение состояния от жизненного цикла контейнеров.
