DevOps инженер Solar - 200+ тыс. руб. / Реальное собеседование.
Сегодня мы разберем интервью с кандидатом на роль DevOps/SRE-инженера, в котором хорошо виден контраст между его практическим опытом с Kubernetes, CI/CD и автоматизацией и существенными пробелами в области Linux-администрирования, сетей и базовых инфраструктурных компетенций. Беседа показывает, как интервьюеры аккуратно “прозванивают” глубину знаний, переводя разговор от общих деклараций к конкретике, и одновременно оценивают мотивацию кандидата к развитию в более широком инженерном профиле.
Вопрос 1. Кратко опишите ваш текущий опыт, основные обязанности и используемый стек.
Таймкод: 00:00:27
Ответ собеседника: правильный. Кандидат работает DevOps-инженером над ПО для автодорог: проектирует и поддерживает CI/CD пайплайны, администрирует Kubernetes, контейнеризирует приложения, использует Ansible для деплоя.
Правильный ответ:
В текущей роли я отвечаю за полный цикл доставки и эксплуатации приложений, с фокусом на надежность, воспроизводимость и автоматизацию.
Основные зоны ответственности:
- Проектирование и поддержка CI/CD:
- Автоматизация сборки, тестирования и деплоя.
- Валидация кода (lint, unit/integration тесты, security checks).
- Разделение пайплайнов по окружениям (dev/stage/prod), управление артефактами и версиями.
- Kubernetes:
- Проектирование манифестов: Deployments, StatefulSets, Services, Ingress, ConfigMap, Secret.
- Настройка стратегий деплоя (RollingUpdate, Blue-Green/Canary при необходимости).
- Мониторинг и логирование (метрики, алерты, tracing).
- Работа с сетевой политикой, ресурсами (requests/limits), autoscaling (HPA/VPA).
- Контейнеризация:
- Создание оптимизированных Docker-образов (multi-stage build, минимизация attack surface).
- Управление зависимостями и версиями, работа с private registry.
- Автоматизация инфраструктуры и деплоя:
- Использование Ansible для конфигурации серверов, deployment-плейбуков, идемпотентных ролей.
- Описательная конфигурация и повторяемость окружений.
Стек может включать:
- GitLab CI / GitHub Actions / Jenkins (или аналог для CI/CD),
- Docker,
- Kubernetes,
- Ansible,
- Helm / Kustomize,
- Prometheus / Grafana / Loki / ELK,
- облачные провайдеры (например, AWS/GCP/Azure) или on-prem.
Если в контексте вакансии на Go разработку, корректно дополнительно подчеркнуть:
- опыт работы с Go-сервисами с точки зрения сборки, деплоя и observability;
- понимание того, как писать код и структурировать сервисы так, чтобы они были удобны для контейнеризации, масштабирования и эксплуатации (health-checks, конфигурация через env, graceful shutdown, метрики и логи).
Вопрос 2. Какой у вас опыт администрирования Linux и с какими дистрибутивами вы работали?
Таймкод: 00:01:32
Ответ собеседника: неполный. Регулярно работает с Ubuntu и Debian, выполняет стандартные задачи администрирования, но без глубокого низкоуровневого администрирования и сложного траблшутинга ядра.
Правильный ответ:
Мой опыт с Linux включает как повседневное администрирование, так и задачи, связанные с эксплуатацией продакшн-систем и сред для высоконагруженных сервисов.
Основные дистрибутивы:
- Ubuntu (серверные LTS-версии) — основная платформа для сервисов и CI/CD.
- Debian — базис для production-сред, контейнеров и образов.
- Также при необходимости: CentOS/AlmaLinux/Rocky и др. для корпоративных окружений.
Ключевые компетенции:
-
Управление пользователями и безопасностью:
- Настройка пользователей, групп, sudo-политик.
- SSH-доступ, ключевая аутентификация, базовая hardening-конфигурация.
- Работа с файловыми правами (chmod, chown, setuid/setgid, sticky bit), понимание их влияния на безопасность сервисов.
-
Работа с пакетами и репозиториями:
- apt/yum/dnf: установка, обновление, пинning версий, управление сторонними репозиториями.
- Оптимизация минимальных базовых образов для контейнеров (alpine/distroless, slim-образы).
-
Сетевое администрирование:
- Настройка сетевых интерфейсов, маршрутизации, DNS.
- Диагностика проблем: ping, traceroute, dig, ss, netstat, tcpdump, mtr.
- Базовая настройка firewall (iptables/nftables, ufw), работа с ingress/egress-трафиком в контексте сервисов и Kubernetes-нод.
-
Работа с сервисами и демонами:
- systemd: юниты, перезапуски, зависимости, логирование через journalctl.
- Настройка и отладка системных сервисов (nginx, postgres, docker, kubelet и др.).
-
Файловые системы и диски:
- Работа с разделами и файловыми системами (ext4, xfs), монтирование, fstab.
- Понимание влияния настроек диска и FS на производительность приложений и баз данных.
- Использование LVM, базовый RAID в контексте продакшн-инфраструктуры.
-
Мониторинг и диагностика производительности:
- Анализ загрузки: top/htop, iostat, vmstat, mpstat, free, df/du.
- Поиск узких мест: CPU, память (swap, OOM), диск, сеть.
- Диагностика проблем с long-running сервисами.
-
Логи и траблшутинг:
- Работа с системными логами: /var/log, journalctl.
- Анализ падений процессов, core dumps (при необходимости) и взаимодействие с разработчиками для поиска причин.
- Локализация проблем окружения, влияющих на приложения (permissions, env variables, DNS, TLS, лимиты ulimit и др.).
Связь с Go и контейнерами:
Для разработки и эксплуатации Go-сервисов важно:
-
Понимать, как Go-приложения взаимодействуют с ОС: файловая система, сеть, дескрипторы, лимиты (ulimit, nofile), сигналы (SIGTERM, SIGINT) и корректный graceful shutdown.
-
Уметь готовить минимальные, безопасные образы:
-
пример Dockerfile:
FROM golang:1.22-bookworm AS build
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o app
FROM gcr.io/distroless/base-debian12
WORKDIR /app
COPY --from=build /app/app .
USER nonroot:nonroot
ENTRYPOINT ["./app"]
-
-
Учитывать особенности Linux при написании сетевых и высоконагруженных сервисов:
- настройка таймаутов, keep-alive, connection pooling;
- правильная работа с файлами и сокетами, обработка ошибок уровня ОС.
Такой уровень администрирования позволяет уверенно запускать, диагностировать и оптимизировать Go-сервисы в реальных продакшн-средах.
Вопрос 3. Какие практические задачи вы решали в Linux?
Таймкод: 00:02:04
Ответ собеседника: неполный. В основном поднимал стенды на Debian/Ubuntu, устанавливал Kubernetes и настраивал сеть для кластера, без глубокого системного дебага.
Правильный ответ:
На практике в Linux важно уметь не только разворачивать окружения, но и уверенно диагностировать и решать проблемы, влияющие на доступность и производительность сервисов. Примеры задач, которые демонстрируют достаточную глубину компетенций:
-
Подготовка окружений для сервисов и кластеров:
- Установка и базовая настройка дистрибутивов (Ubuntu/Debian/CentOS/AlmaLinux):
- разметка дисков, настройка файловых систем;
- базовый hardening (SSH, sudo, firewall, отключение неиспользуемых сервисов).
- Настройка репозиториев, установка системных зависимостей для приложений и CI/CD агентов.
- Подготовка нод под Kubernetes или Docker:
- swap, cgroup, sysctl-настройки (net.ipv4.ip_forward, conntrack и т.п.);
- настройки для kubelet/containerd/docker.
- Установка и базовая настройка дистрибутивов (Ubuntu/Debian/CentOS/AlmaLinux):
-
Сетевые задачи и диагностика:
- Настройка сети для Kubernetes-кластера и сервисов:
- корректная маршрутизация между подами и нодами;
- проверка MTU, проблем с overlay-сетями, CNI-плагинами (Calico/Flannel/Cilium);
- настройка Ingress-контроллеров (nginx, traefik) и балансировщиков.
- Диагностика сетевых проблем:
- использование
ping,traceroute,mtr,dig,nslookup,ss,tcpdumpдля поиска причин:- недоступности сервисов;
- проблем с DNS/SSL;
- неожиданных обрывов соединений или timeouts.
- использование
- Настройка firewall:
- iptables/nftables/ufw для ограничения доступа к сервисам;
- открытие портов под API, базу данных, ingress.
- Настройка сети для Kubernetes-кластера и сервисов:
-
Управление сервисами и troubleshooting приложений:
- Работа с
systemd:- создание unit-файлов для Go-сервисов:
[Unit]
Description=My Go Service
After=network-online.target
[Service]
User=app
Group=app
WorkingDirectory=/opt/my-service
ExecStart=/opt/my-service/my-service
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
Environment=ENV=prod
EnvironmentFile=-/etc/my-service/env
[Install]
WantedBy=multi-user.target - анализ падений через
journalctl, настройка рестартов, ограничений ресурсов.
- создание unit-файлов для Go-сервисов:
- Решение проблем уровня окружения:
- неверные права на конфиги/директории;
- некорректные переменные окружения;
- конфликты портов, нехватка дескрипторов (
too many open files); - проблемы с TLS-сертификатами (пути, права, цепочка).
- Работа с
-
Производительность и ресурсы:
- Поиск причин деградации:
- CPU: бесконечные циклы, профилирование, runaway-процессы.
- RAM: утечки памяти, OOM killer, анализ
dmesg,journalctl,free,top/htop. - IO: медленные диски,
iostat,iotop, конкуренция за диск. - Сеть: высокая латентность, дропы пакетов, перегруженные соединения.
- Настройка лимитов:
ulimit,LimitNOFILE,vm.max_map_count,fs.inotify.max_user_watchesи т.п.- Тюнинг под базы данных, брокеры сообщений и высоконагруженные Go-сервисы.
- Поиск причин деградации:
-
Логи и наблюдаемость:
- Централизация логов (rsyslog/journalbeat/filebeat/Fluent Bit + ELK/Loki).
- Структурированные логи для Go-сервисов (JSON, correlation-id, trace-id).
- Настройка метрик (Prometheus node_exporter, cadvisor) и базовая аналитика.
-
Интеграция с Go-сервисами:
- Обеспечение корректной работы:
- graceful shutdown по SIGTERM/SIGINT (Kubernetes, systemd);
- health-check endpoints (
/healthz,/readyz) для liveness/readiness.
- Пример корректной обработки сигналов в Go:
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
srv := &http.Server{
Addr: ":8080",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
}),
}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %v", err)
}
}()
stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
<-stop
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Printf("graceful shutdown failed: %v", err)
}
}
- Обеспечение корректной работы:
Такие примеры показывают не просто умение "устанавливать Kubernetes", а способность уверенно управлять Linux-средой, находить и устранять системные проблемы, влияющие на работу Go-приложений и инфраструктуры в целом.
Вопрос 4. С какими балансировщиками нагрузки и прокси-серверами вы работали, и в чем их различия?
Таймкод: 00:02:48
Ответ собеседника: неправильный. Упоминает только Nginx, ошибочно утверждает, что HAProxy в основном для TCP, а Nginx — только для HTTP и не подходит для TCP-прокси, дает путаное объяснение различий.
Правильный ответ:
При работе с высоконагруженными распределенными системами важно понимать не только конкретный инструмент (например, Nginx), но и модель работы балансировщиков/прокси, их сильные/слабые стороны, уровни OSI, на которых они работают, и сценарии использования.
Основные типы и роли:
- L4-балансировщик:
- Работает на уровне TCP/UDP.
- Принимает решение на основе IP/порта без разбора HTTP-протокола.
- Быстрее, проще, меньше логики контента, часто используется как entrypoint или для TCP/UDP-сервисов (БД, gRPC over TCP и т.п.).
- L7-балансировщик/HTTP reverse proxy:
- Понимает HTTP/HTTPS, может анализировать заголовки, URI, cookies.
- Умеет делать маршрутизацию по домену, пути, версии API, аутентификации и т.д.
- Поддерживает более сложные сценарии: A/B, canary, content-based routing.
Разберем ключевые решения.
- Nginx
- Основное назначение:
- HTTP(S) reverse proxy, L7-балансировщик, статика, TLS-терминация.
- Может работать и как L4 TCP/UDP-прокси (stream), это важный момент.
- Возможности:
- Балансировка HTTP/HTTPS с поддержкой различных алгоритмов (round-robin, least_conn, ip_hash).
- Маршрутизация по:
- host (виртуальные хосты),
- URI (location),
- заголовкам.
- TLS-терминация и offload.
- Rate limiting, cache, gzip, WebSocket, gRPC, rewrite/redirect.
- TCP/UDP-прокси через блок
stream:- проксирование баз данных, gRPC, произвольных TCP/UDP-сервисов.
- Пример HTTP reverse-proxy для Go-сервиса:
http {
upstream go_app {
server 10.0.0.2:8080;
server 10.0.0.3:8080;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://go_app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
} - Пример TCP-прокси (важно, что это возможно):
stream {
upstream postgres_upstream {
server 10.0.0.10:5432;
server 10.0.0.11:5432;
}
server {
listen 5432;
proxy_pass postgres_upstream;
}
}
- HAProxy
- Основное назначение:
- Высокопроизводительный L4/L7-балансировщик.
- Исторически силен в TCP и HTTP, глубоко оптимизирован под высокие нагрузки.
- Возможности:
- L4: TCP/UDP балансировка (базы данных, внутренних сервисов).
- L7: HTTP/HTTPS с продвинутым роутингом по заголовкам, cookies, пути, методам.
- Тонкий контроль над:
- health checks,
- timeouts,
- retry-политикой,
- stickiness (session persistence),
- circuit breaking-подобными сценариями.
- Очень детальная статистика и fine-grained конфигурация.
- Пример конфигурации:
frontend http_front
bind *:80
mode http
default_backend go_app
backend go_app
mode http
balance roundrobin
server s1 10.0.0.2:8080 check
server s2 10.0.0.3:8080 check
- Nginx vs HAProxy — ключевые различия
- Оба умеют:
- работать как reverse proxy,
- выполнять L7 HTTP-балансировку,
- работать с TCP (и HAProxy, и Nginx через stream).
- Типичные отличия:
- HAProxy:
- традиционно выбирают там, где важна максимальная производительность, сложные сценарии балансировки и наблюдаемости.
- более детальный контроль и богатый функционал для health-checks, failover, тонких таймаутов.
- Nginx:
- часто используется как универсальный фронт (HTTP, статика, TLS, простая балансировка).
- удобен как edge-прокси + терминатор TLS + reverse proxy.
- HAProxy:
- Оба решения активно применяются перед Go-сервисами:
- TLS offload;
- проброс X-Forwarded-*;
- маршрутизация по доменам/путям на разные микросервисы.
- Envoy
Кратко о современном варианте, популярном в микросервисной архитектуре.
- Основное назначение:
- L7-прокси и сервис-меш data plane (Istio и др.).
- Возможности:
- gRPC-native, HTTP/2, HTTP/3.
- Расширяемость через xDS API.
- Наблюдаемость: метрики, tracing, детализированные логи.
- Поддержка сложных политик маршрутизации, retries, circuit breaking, rate limiting.
- Используется как sidecar рядом с Go-сервисами в Kubernetes.
- Балансировка в Kubernetes: kube-proxy, Ingress, Service
- Service (ClusterIP, NodePort, LoadBalancer):
- L4-балансировка трафика на поды.
- Ingress + Ingress Controller:
- Nginx Ingress, HAProxy Ingress, Envoy, Traefik:
- L7-маршрутизация HTTP/HTTPS по доменам/пути.
- Nginx Ingress, HAProxy Ingress, Envoy, Traefik:
- Важно понимать:
- Внешний балансировщик (например, AWS ELB/NLB/GCLB) + Ingress Controller (Nginx/HAProxy/Envoy) + Service (k8s) работают совместно.
- Связь с Go-сервисами
При проектировании Go-приложений под балансировщики нужно:
- Корректно обрабатывать:
- X-Forwarded-For / X-Real-IP / X-Forwarded-Proto.
- Не завязываться на локальное состояние:
- избегать sticky-сессий на уровне памяти;
- использовать внешние хранилища (Redis, БД) или JWT.
- Поддерживать health-check endpoints:
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
}) - Учитывать таймауты, keep-alive и размер ответов, особенно при работе через прокси.
Таким образом, корректный ответ должен:
- Упоминать несколько инструментов (Nginx, HAProxy, возможно Envoy/Traefik/cloud LB).
- Показывать понимание уровней (L4/L7) и сценариев использования.
- Не допускать ошибки о том, что Nginx не умеет TCP: он умеет, через stream, и оба (Nginx и HAProxy) могут работать и на L4, и на L7.
Вопрос 5. С какими базами данных вы работали и есть ли у вас опыт настройки кластерных инсталляций (например, PostgreSQL)?
Таймкод: 00:03:49
Ответ собеседника: неполный. Устанавливал PostgreSQL и MongoDB, базово понимает модель leader/replica, но полноценной настройки и сопровождения кластеров не делал.
Правильный ответ:
При ответе на такой вопрос важно показать:
- понимание моделей данных и сценариев использования разных БД;
- знание механизмов репликации, отказоустойчивости и масштабирования;
- связь с тем, как приложения (в том числе на Go) работают с этими БД.
Ниже структурированный, содержательный ответ.
Работа с реляционными СУБД:
-
PostgreSQL:
- Использование:
- проектирование схем: нормализация, индексы, внешние ключи, уникальные ограничения;
- сложные запросы, JOIN, оконные функции, CTE, транзакции;
- понимание уровней изоляции транзакций и их влияния на конкурентный доступ.
- Репликация и кластеры:
- Physical replication (streaming replication): primary/standby.
- Настройка WAL, параметры
wal_level,max_wal_senders,hot_standby. - Асинхронная и синхронная репликация, trade-offs между latency и durability.
- Настройка WAL, параметры
- Логическая репликация:
- публикация/подписчики, частичная репликация таблиц.
- сценарии миграций, blue-green, разделение нагрузки по чтению.
- High Availability:
- Инструменты: Patroni, repmgr, Pacemaker/Corosync.
- Автоматический failover, виртуальный IP/endpoint, интеграция с сервис-дискавери.
- Масштабирование:
- Чтение с реплик (read replicas).
- Шардинг/partitioning (native partitioning, pg_partman).
- Производительность и эксплуатация:
- Индексация (B-Tree, GIN, BRIN), анализ планов запросов (EXPLAIN/ANALYZE).
- Тюнинг
work_mem,shared_buffers,effective_cache_size,max_connections. - Контроль автovacuum, борьба с bloat, dead tuples.
- Physical replication (streaming replication): primary/standby.
- Пример взаимодействия Go + PostgreSQL:
import (
"context"
"database/sql"
_ "github.com/lib/pq"
"log"
"time"
)
func main() {
dsn := "postgres://user:pass@db-master:5432/app?sslmode=disable"
db, err := sql.Open("postgres", dsn)
if err != nil {
log.Fatal(err)
}
db.SetMaxOpenConns(50)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(30 * time.Minute)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
if err := db.PingContext(ctx); err != nil {
log.Fatalf("db not available: %v", err)
}
}
- Использование:
-
MySQL/MariaDB:
- Понимание репликации (asynchronous/semi-sync), read replicas.
- Использование в проектах с высоким числом чтений.
- Аналогичные подходы к индексации, анализу запросов, тюнингу.
Работа с NoSQL и документо-ориентированными БД:
- MongoDB:
- Модель данных: документы, коллекции, денормализация, embedded структуры.
- Replica Set:
- автоматический выбор primary,
- failover,
- чтение с secondary (при правильной конфигурации read preference).
- Sharding:
- горизонтальное масштабирование по shard key,
- router (mongos), config servers.
- Важные аспекты:
-Write concern, read concern;
- понимание последствий eventual consistency.
- Пример подключения из Go:
import (
"context"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"log"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://user:pass@mongo-1,mongo-2,mongo-3/?replicaSet=rs0"))
if err != nil {
log.Fatal(err)
}
if err := client.Ping(ctx, nil); err != nil {
log.Fatal(err)
}
}
Кластерные инсталляции и отказоустойчивость — ключевые моменты:
-
Общие принципы:
- leader/replica (primary/standby, primary/secondary);
- автоматический или ручной failover;
- split-brain и необходимость consensus-механизмов (etcd/consul в Patroni, кворум).
-
PostgreSQL в кластере (пример логики правильной конфигурации):
- Primary:
- принимает записи, отдает WAL.
- Standby(реплики):
- получают WAL через streaming, могут обслуживать только чтение.
- HA-обвязка:
- Patroni управляет ролью узлов;
- etcd/Consul хранит кластерное состояние;
- HAProxy или PgBouncer как точка входа (роутинг на актуальный primary).
- Типичная архитектура:
- 2–3 PostgreSQL-ноды;
- etcd/Consul кластер;
- 1–2 HAProxy/балансировщика;
- Приложения всегда ходят на единый endpoint, не зная о смене роли.
- Primary:
-
Важные практические аспекты:
- Backup и PITR:
- base backups + WAL архивирование (pg_basebackup, wal-g).
- Тестирование восстановления, а не только наличие бэкапов.
- Наблюдаемость:
- Метрики (pg_stat_activity, pg_stat_statements, экспортеры для Prometheus).
- Алерты по replication lag, deadlocks, росту размера, медленным запросам.
- Безопасность:
- Настройки
pg_hba.conf, шифрование каналов, доступ по ролям.
- Настройки
- Backup и PITR:
Типичные ошибки, которых важно избегать и уметь объяснить:
- Подмена понятий: "репликация есть кластер". Нужны:
- управляющий слой,
- мониторинг,
- механизмы failover,
- единая точка подключения.
- Игнорирование latency между нодами при синхронной репликации.
- Отсутствие стратегии бэкапа и регулярных проверок восстановления.
- Клиентские коннекты без retry/timeout-логики при failover.
Если нет большого практического опыта:
Корректный взрослый ответ:
- честно обозначить пределы hands-on опыта;
- при этом показать:
- уверенное понимание архитектурных подходов;
- знание инструментов (Patroni, repmgr, PgBouncer, HAProxy, Mongo replica set/sharding);
- понимание, как адаптировать приложение на Go под отказоустойчивую БД:
- connection pooling,
- idempotent операции,
- retry при transient errors,
- корректная обработка ошибок подключения.
Вопрос 6. Какой у вас опыт работы с Kubernetes: вы разворачивали кластеры с нуля или работали только с готовой инфраструктурой?
Таймкод: 00:05:03
Ответ собеседника: правильный. Работал с on-prem Kubernetes на виртуальных машинах, без managed-решений, несколько раз самостоятельно разворачивал кластер с нуля.
Правильный ответ:
Опыт с Kubernetes стоит описывать в разрезе:
- разворачивание кластера;
- эксплуатация и отладка;
- дизайн манифестов и интеграция с приложениями (в т.ч. на Go);
- безопасность, observability и обновления.
Разворачивание кластеров (on-prem и self-managed):
- Проектирование архитектуры:
- Планирование control plane и worker-нод:
- HA-control plane (3+ master-ноды);
- выделение etcd (встроенный или отдельный кластер).
- Планирование сети:
- выбор CNI-плагина (Calico, Cilium, Flannel);
- диапазоны Pod CIDR и Service CIDR;
- интеграция с существующей сетью, маршрутизация, MTU.
- Планирование control plane и worker-нод:
- Практические варианты установки:
- kubeadm:
- Инициализация control plane (
kubeadm init), конфигурацияclusterConfiguration; - присоединение worker-нод (
kubeadm join); - настройка certificates, kubeconfig, загрузка образов, интеграция с CRI (containerd/docker).
- Инициализация control plane (
- Ручная настройка базовых компонентов:
- kube-apiserver, kube-controller-manager, kube-scheduler;
- kubelet, kube-proxy;
- CNI-плагин.
- Понимание managed vs self-managed:
- в on-prem всё: апгрейды, etcd, certificates, network — зона ответственности команды.
- kubeadm:
Эксплуатация и управление:
- Обновления:
- Control plane поэтапно, затем worker-ноды;
- совместимость версий Kubernetes / kubelet / CNI / CSI.
- Диагностика:
kubectl describe,kubectl logs,kubectl get events;- анализ проблем с Pending/CrashLoopBackOff/Init контейнерами;
- проверка readiness/liveness, DNS внутри кластера (CoreDNS), CNI-проблем.
- Ресурсы и планирование:
- Requests/limits для CPU и памяти;
- QoS классы (Guaranteed/Burstable/BestEffort);
- HPA (по CPU/пользовательским метрикам), PodDisruptionBudget.
- Работа с Storage:
- Настройка StorageClass, CSI-драйверов;
- PersistentVolume / PersistentVolumeClaim;
- подходы к stateful-сервисам (PostgreSQL, Kafka, Redis) в Kubernetes.
Конфигурация и манифесты (важно для Go-сервисов):
- Базовые объекты:
- Deployment, StatefulSet, DaemonSet;
- Service (ClusterIP, NodePort, LoadBalancer), Ingress;
- ConfigMap, Secret.
- Пример манифеста Deployment для Go-сервиса:
apiVersion: apps/v1
kind: Deployment
metadata:
name: go-api
labels:
app: go-api
spec:
replicas: 3
selector:
matchLabels:
app: go-api
template:
metadata:
labels:
app: go-api
spec:
containers:
- name: go-api
image: registry.example.com/go-api:1.0.0
ports:
- containerPort: 8080
env:
- name: ENV
value: "prod"
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 3
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi" - Service + Ingress:
apiVersion: v1
kind: Service
metadata:
name: go-api-svc
spec:
selector:
app: go-api
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: go-api-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: go-api-svc
port:
number: 80
Безопасность и политика:
- RBAC:
- Настройка ServiceAccount, Role/ClusterRole, RoleBinding/ClusterRoleBinding.
- Минимально необходимые права для CI/CD, операторов, приложений.
- NetworkPolicy:
- Ограничение трафика между namespace и сервисами;
- принцип "deny by default" для чувствительных окружений.
- Secrets:
- Хранение конфиденциальных данных в Secret (Base64 не шифрование, часто + KMS/Sealed Secrets/HashiCorp Vault).
Observability:
- Логи:
- sidecar/агенты (Fluent Bit, Vector, Filebeat) + централизованное хранилище.
- Метрики:
- Prometheus + Alertmanager;
- kube-state-metrics, node-exporter, метрики приложений на Go (client_golang).
- Tracing:
- OpenTelemetry/Jaeger/Tempo для распределенного трейсинга.
Интеграция с Go-приложениями:
- Корректная обработка:
- SIGTERM/SIGINT для graceful shutdown;
- readiness/liveness endpoints;
- конфигурация через env и ConfigMap/Secret;
- отсутствие привязки к локальному диску, использование PVC/S3/внешних сервисов.
- Учет особенностей:
- короткие timeouts при остановке pod (terminationGracePeriodSeconds);
- устойчивость к рестартам и перераспределению нагрузки.
Сильный ответ на вопрос о Kubernetes показывает:
- опыт именно self-managed/on-prem (понимание, что без managed-сервиса вся ответственность на команде);
- умение не только "поднять кластер", но и эксплуатировать его: обновлять, мониторить, отлаживать сеть и storage;
- понимание практик, которые делают запуск Go-сервисов в Kubernetes предсказуемым, отказоустойчивым и управляемым.
Вопрос 7. Как обеспечивается отказоустойчивость Kubernetes-кластера и его контрольного плана?
Таймкод: 00:05:29
Ответ собеседника: неполный. Указывает на использование трёх master-нод и etcd-кластера, но не объясняет механизмы высокой доступности control plane и не отвечает, как достигается отказоустойчивость при использовании одного адреса для доступа к API и worker-нодам.
Правильный ответ:
Отказоустойчивость Kubernetes — это не только “три master-ноды”, а продуманная архитектура control plane, etcd, балансировки API и worker-нод, плюс процессы обновлений и мониторинга. Важно показать понимание:
- как устроен HA control plane;
- как клиенты и kubelet используют единый endpoint;
- как обеспечивается устойчивость worker-нод и прикладных нагрузок.
Разберём по слоям.
Control Plane: архитектура высокой доступности
Компоненты control plane:
- kube-apiserver
- etcd (может быть встроен или выделен)
- kube-controller-manager
- kube-scheduler
- дополнительные контроллеры/операторы
Ключевые принципы:
-
Несколько нод control plane:
- Обычно минимум 3 ноды.
- kube-apiserver запускается на каждой ноде (каждый экземпляр stateless).
- kube-controller-manager и kube-scheduler запускаются в режиме leader election:
- на всех master-нодах, но активен один лидер, остальные standby;
- при падении лидера — автоматический перехват роли.
-
HA для etcd:
- etcd — критичный компонент, источник правды о состоянии кластера.
- Рекомендуемая конфигурация: 3 или 5 узлов etcd, каждый на отдельной master-ноде.
- Используется механизм кворума Raft:
- кластер продолжает работать при потере меньшинства нод;
- например, из 3 нод можно потерять 1, из 5 — до 2.
- Важные моменты:
- размещение etcd на разных физических/виртуальных хостах/зонах;
- стабильная сеть и низкая латентность между нодами etcd;
- регулярные бэкапы и тест восстановления.
-
Доступ к kube-apiserver: единый отказоустойчивый endpoint
Клиенты (kubectl, контроллеры, kubelet на worker-нодах) должны ходить на один стабильный адрес, не зная о том, сколько master-нод и какая сейчас жива.
Варианты реализации:
-
Внешний L4-балансировщик (apiserver endpoint):
- Например: HAProxy, Nginx (stream), cloud LB (AWS NLB/ALB, GCP LB, F5 и т.п.).
- Конфигурация:
- балансировка по нескольким kube-apiserver-инстансам на master-нодах;
- health-check по
/healthzили/readyz.
- Пример (HAProxy, упрощённо):
frontend k8s_api
bind *:6443
mode tcp
default_backend k8s_api_backend
backend k8s_api_backend
mode tcp
balance roundrobin
server master1 10.0.0.11:6443 check
server master2 10.0.0.12:6443 check
server master3 10.0.0.13:6443 check
-
Виртуальный IP (VIP) + keepalived:
- Несколько master-нод, один виртуальный IP.
- keepalived/VRRP следит за доступностью и переносит VIP на живую ноду.
- kube-apiserver слушает этот VIP; клиенты подключаются к нему.
-
В managed Kubernetes:
- endpoint control plane обеспечивает облачный провайдер (мы лишь понимаем модель).
Идея:
- kubelet/клиенты всегда используют один URL/адрес (например,
https://k8s-api.example.com:6443). - Если один master падает, LB/VRRP перенаправит трафик на живые ноды.
- Это и есть ответ на вопрос, "как достигается отказоустойчивость при одном адресе".
Отказоустойчивость worker-нод и приложений
Для воркеров важны:
- несколько worker-нод;
- корректные механизмы планирования и перезапуска подов.
Ключевые элементы:
-
Много worker-нод:
- Нагрузку и поды распределяет kube-scheduler.
- При падении одной ноды:
- kubelet перестает отчитываться;
- node помечается NotReady;
- контроллеры пересоздают поды на других нодах (при корректно настроенных Deployment/ReplicaSet).
-
Использование контроллеров:
- Deployment:
- гарантирует нужное количество реплик;
- при сбое ноды — реплики поднимаются на других нодах.
- StatefulSet:
- для stateful-сервисов (БД, очереди) с учётом PVC, identity.
- DaemonSet:
- системные агенты (логирование, мониторинг) на всех нодах.
- Deployment:
-
Node draining и обновления:
- Перед техническими работами:
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data- поды мигрируют на другие ноды.
- Это позволяет обновлять кластер без даунтайма приложений (при достаточном количестве реплик).
- Перед техническими работами:
-
Анти-афинити и распределение:
- PodAntiAffinity:
- предотвращает размещение всех реплик на одной ноде или AZ.
- TopologySpreadConstraints:
- равномерное распределение подов по нодам/зонам.
- PodAntiAffinity:
-
Health-checks и self-healing:
- livenessProbe / readinessProbe:
- Kubernetes автоматически перезапускает "умершие" контейнеры;
- не шлёт трафик на под, пока тот не готов.
- Приложения на Go должны корректно:
- обрабатывать сигналы SIGTERM;
- завершать соединения и фоновые задачи;
- экспонировать health-check endpoints.
- livenessProbe / readinessProbe:
Безопасность и операционные аспекты HA
- Бэкапы etcd:
- регулярные, с проверкой восстановления;
- хранятся в отдельном надёжном хранилище.
- Мониторинг:
- Метрики control plane (apiserver, etcd, scheduler, controller-manager).
- Алерты по:
- недоступности API;
- времени ответа;
- состоянию etcd кластера;
- числу NotReady-нod и CrashLoopBackOff-подов.
- План обновлений:
- поэтапные апгрейды control plane;
- соблюдение матрицы совместимости версий.
Кратко:
Отказоустойчивость Kubernetes-кластера обеспечивается комбинацией:
- нескольких master-нод и HA etcd;
- отказоустойчивого endpoint’а для kube-apiserver через LB/VIP;
- нескольких worker-нод и контроллеров (Deployment/StatefulSet) для автоматического перераспределения подов;
- health-checks, мониторинга, бэкапов и грамотного процесса обновлений.
Упоминание только “три master-а” без объяснения роли балансировщика/единого endpoint и поведения подов при падении нод — это неполный ответ.
Вопрос 8. Как обеспечить, чтобы worker-ноды продолжали работать при отказе одного из управляющих узлов Kubernetes, если в конфигурации указан один адрес?
Таймкод: 00:06:54
Ответ собеседника: неправильный. Признает, что отказоустойчивость была реализована некорректно, предполагает указание всех master-нод, но не знает про VIP/балансировщик и не описывает корректное решение.
Правильный ответ:
Ключевая идея: worker-ноды (kubelet) и все клиенты кластера должны обращаться к API Kubernetes через единый стабильный endpoint, за которым скрывается отказоустойчивый control plane. Этот endpoint не должен быть "одним физическим сервером", он должен быть виртуальным или балансируемым.
Если в kubelet/kubeconfig прописан один адрес, то:
- этот адрес должен указывать не на конкретную master-ноду,
- а на отказоустойчивый frontend: виртуальный IP (VIP) или L4-балансировщик, который маршрутизирует трафик на живые kube-apiserver-инстансы.
Основные подходы:
- L4-балансировщик перед kube-apiserver
- Разворачиваем несколько master-нод, на каждой работает kube-apiserver.
- Спереди ставим TCP-балансировщик (например, HAProxy, Nginx stream, Envoy, F5, AWS NLB и т.п.).
- Все worker-ноды и kubectl используют один адрес балансировщика, например:
https://k8s-api.example.com:6443
- Балансировщик:
- проверяет здоровье каждого apiserver (TCP-check или
/healthz//readyz); - исключает упавшие master-ноды из пулла.
- проверяет здоровье каждого apiserver (TCP-check или
- Пример HAProxy:
frontend k8s_api
bind *:6443
mode tcp
default_backend k8s_api_backend
backend k8s_api_backend
mode tcp
balance roundrobin
server master1 10.0.0.11:6443 check
server master2 10.0.0.12:6443 check
server master3 10.0.0.13:6443 check - В kubeconfig worker-ноды указываем:
server: https://k8s-api.example.com:6443
- При падении одной master-ноды:
- kubelet продолжает ходить на тот же адрес;
- балансировщик сам перенаправляет трафик на живые master-ноды;
- воркеры и поды продолжают функционировать.
- VIP (Virtual IP) + keepalived/VRRP
- На нескольких master-нодах поднимаем keepalived (или аналог) с VRRP.
- Конфигурируем один виртуальный IP, например
10.0.0.100, который "плавает" между нодами. - kube-apiserver слушает этот VIP (или маршруты настроены соответствующе).
- kubelet и клиенты обращаются к
https://10.0.0.100:6443. - При отказе активной master-ноды:
- VIP автоматически переезжает на другой master;
- трафик к API продолжается по тому же адресу.
- Почему "прописать все master-ноды" — плохое решение
Часто встречающееся, но некорректное представление: "давайте добавим несколько server в kubeconfig".
Проблемы:
- стандартный kubeconfig не поддерживает автоматический failover списком серверов для kubelet "из коробки" так, как ожидают;
- даже если реализовать собственную логику, это:
- усложняет конфигурацию,
- дублирует функциональность балансировщика,
- требует ручного управления и повышает риск ошибок.
Правильная практика:
- один стабильный endpoint (DNS-имя или VIP),
- под ним — отказоустойчивый слой балансировки или VRRP.
- Что происходит при отказе master-ноды при правильной схеме
- etcd-кластер продолжает работать за счет кворума.
- kube-controller-manager и kube-scheduler переизбирают лидера (если отказ был у лидера).
- Балансировщик/VRRP перестает слать трафик на недоступную ноду.
- kubelet на worker-нодах по-прежнему общается с API по тому же адресу.
- Уже запущенные поды на воркерах продолжают работать, даже если control plane временно частично недоступен.
- Как только control plane стабилен, контроллеры продолжают reconciliation loop:
- пересоздание подов,
- реакция на изменения и т.п.
Краткая формулировка корректного ответа:
Чтобы worker-ноды продолжали работать при отказе одного из master-ов при наличии одного адреса в конфигурации, этот адрес должен вести не на конкретный master, а на:
- L4-балансировщик, который распределяет запросы между несколькими kube-apiserver и исключает упавшие,
- или виртуальный IP (VIP) с VRRP (keepalived), который автоматически переезжает на живой master.
Это и есть ключ к отказоустойчивому control plane при использовании одного стабильного endpoint.
Вопрос 9. Что такое StatefulSet в Kubernetes и для каких задач он используется?
Таймкод: 00:07:55
Ответ собеседника: правильный. Описывает StatefulSet как аналог Deployment для stateful-приложений: с устойчивыми именами подов, порядком запуска и отдельными персистентными томами на под, что подходит для кластерных сервисов, где важны идентичность и порядок.
Правильный ответ:
StatefulSet — это контроллер в Kubernetes, предназначенный для управления приложениями с состоянием и требованиями к устойчивой идентичности подов. В отличие от Deployment, который подходит для stateless-сервисов и рассматривает поды как взаимозаменяемые, StatefulSet обеспечивает предсказуемость в идентификации и хранении данных каждой реплики.
Ключевые особенности StatefulSet:
-
Стабильная идентичность подов:
- Каждая реплика получает фиксированное DNS-имя и индекс:
name-0,name-1,name-2и т.д.
- Имя пода привязано к логической "идентичности", а не к конкретному запуску.
- Это важно для систем, где:
- реплики выполняют разные роли,
- требуется согласованная конфигурация или кластерная топология.
- Каждая реплика получает фиксированное DNS-имя и индекс:
-
Устойчивые тома (PersistentVolumeClaim на под):
- Для каждого pod создаётся отдельный PVC на основе
volumeClaimTemplates. - PVC сохраняется даже при удалении pod:
- при пересоздании
name-0получит свой же том с данными.
- при пересоздании
- Это критично для:
- баз данных,
- брокеров сообщений,
- других сервисов, где данные должны переживать рестарты.
- Для каждого pod создаётся отдельный PVC на основе
-
Управляемый порядок операций:
- Порядок запуска:
- по умолчанию pod’ы запускаются последовательно: сначала
-0, затем-1,-2и т.д.
- по умолчанию pod’ы запускаются последовательно: сначала
- Порядок остановки и обновления:
- при обновлении/удалении идёт обратный порядок: сначала
-N, затем ниже.
- при обновлении/удалении идёт обратный порядок: сначала
- Это полезно для кластеров, где:
- есть первичный узел и вторичные;
- необходим корректный bootstrap или graceful shutdown;
- важна согласованность кворума.
- Порядок запуска:
-
Headless Service и DNS:
- Обычно StatefulSet используется вместе с headless Service (
clusterIP: None). - Это даёт стабильные DNS-имена:
pod-0.service-name.namespace.svc.cluster.local
- Кластерные приложения могут обнаруживать друг друга по этим именам.
- Обычно StatefulSet используется вместе с headless Service (
Когда использовать StatefulSet (типичные сценарии):
- Базы данных:
- PostgreSQL, MySQL, MongoDB, Cassandra.
- Где каждая нода хранит уникальное состояние и может иметь свою роль (primary/replica, shard).
- Кластерные хранилища и распределённые системы:
- Kafka, RabbitMQ (в кластерном режиме), ZooKeeper, Etcd.
- Любые приложения, где:
- важна фиксированная идентичность ноды;
- требуется консистентная привязка "инстанс → данные";
- нужно детерминированное поведение при рестартах.
Когда StatefulSet НЕ нужен:
- Если приложение stateless:
- все реплики взаимозаменяемы;
- хранят состояние вне pod (S3, внешняя БД, кеш).
- Если нет требований к порядку запуска и уникальности идентичности:
- используется Deployment (проще, гибче для большинства сервисов).
Пример упрощенного StatefulSet для кластерного сервиса:
apiVersion: v1
kind: Service
metadata:
name: my-db
spec:
clusterIP: None
selector:
app: my-db
---
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: my-db-image:latest
ports:
- containerPort: 5432
volumeMounts:
- name: data
mountPath: /var/lib/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
Связь с Go-приложениями:
- При разработке на Go сервисов, работающих в кластере (например, свой распределённый сервис или координатор), StatefulSet полезен когда:
- каждой реплике нужно стабильное имя/ID;
- конфигурация или роль (leader/follower) зависит от индекса;
- требуется хранить локальное состояние между рестартами.
- Приложение может:
- определять свою роль по hostname (
os.Hostname(), напримерapp-0,app-1); - использовать стабильные DNS других реплик для формирования кворума.
- определять свою роль по hostname (
Краткая формулировка:
StatefulSet используется для stateful-приложений, которым необходимы:
- стабильная идентичность подов,
- привязанные персистентные хранилища,
- гарантированный порядок запуска/обновления,
- предсказуемые DNS-имена для построения кластерной логики.
Вопрос 10. С каким сетевым плагином Kubernetes вы работали и почему был выбран именно он?
Таймкод: 00:09:16
Ответ собеседника: неполный. Указывает использование Flannel как простого варианта, упоминает Calico как более надёжный, ссылается на проблему во Flannel, но не даёт структурированного объяснения причин выбора и различий между плагинами.
Правильный ответ:
Сетевой плагин (CNI) в Kubernetes определяет модель сети для Pod’ов: как им выдаются адреса, как они общаются между собой и с внешним миром, как реализуются политики безопасности. Важно понимать не только "что использовали", но и архитектурные отличия основных решений и критерии выбора под реальные нагрузки.
Чаще всего рассматривают:
- Flannel — простой overlay-плагин.
- Calico — более продвинутый, с поддержкой NetworkPolicy и разных backend’ов.
- Другие (Cilium, Weave Net, Canal) — в зависимости от требований к безопасности, observability и производительности.
Нужно уметь объяснить выбор.
Flannel
- Основная идея:
- Реализует плоскую сеть между Pod’ами разных нод.
- Использует overlay-сети: VXLAN, host-gw и другие backend’ы.
- Плюсы:
- Простая установка и конфигурация.
- Подходит для небольших и средних кластеров.
- Минусы:
- Базовый функционал, без полноценной реализации продвинутых сетевых политик на уровне L3/L4.
- Overlay через VXLAN добавляет overhead:
- дополнительная инкапсуляция;
- потенциальное влияние на latency и CPU.
- Когда выбор оправдан:
- небольшие on-prem кластеры;
- простые сценарии, где достаточно "Pod-to-Pod работает" без сложной сегментации;
- быстрое поднятие кластера для dev/stage.
Calico
- Основная идея:
- L3-сеть без обязательного overlay (IP-in-IP/VXLAN опциональны).
- Каждому Pod назначается routable IP, маршрутизация реализуется через BGP или статические маршруты.
- Ключевые преимущества:
- Полноценная поддержка Kubernetes NetworkPolicy:
- ограничение трафика по namespace, labels, направлениям, портам.
- Гибкая архитектура:
- режим без overlay (native routing) — выше производительность;
- поддержка VXLAN/IP-in-IP при необходимости.
- Хорошо подходит для production-нагрузок, multi-tenant, zero-trust подхода.
- Полноценная поддержка Kubernetes NetworkPolicy:
- Когда выбор оправдан:
- продакшн-кластеры, особенно multi-tenant;
- требования к изоляции трафика и соблюдению security-политик;
- большие кластеры и/или чувствительные к latency сервисы.
- Типичный мотив формулировки:
- "Выбрали Calico, так как он:
- обеспечивает NetworkPolicy,
- даёт гибкость в выборе архитектуры сети,
- лучше подходит под требования безопасности и управляемости."
- "Выбрали Calico, так как он:
Cilium (как современный ориентир)
Кратко, без дублирования:
- Использует eBPF вместо iptables, даёт:
- высокую производительность;
- богатую наблюдаемость;
- L7-aware политики.
- Уместно упомянуть как вариант для продвинутых сценариев:
- микросервисы, сервис-меш, deep observability, строгие security-требования.
Критерии выбора сетевого плагина, которые важно уметь аргументировать:
- Простота vs функциональность:
- Flannel:
- минимум фич, быстро стартануть.
- Calico/Cilium:
- сетевые политики, лучшее управление безопасностью и маршрутизацией.
- Flannel:
- Производительность:
- overlay (VXLAN) = overhead;
- native routing (Calico) или eBPF (Cilium) обычно эффективнее.
- Безопасность:
- Наличие и зрелость NetworkPolicy:
- для продакшн-систем рекомендуется поддержка сетевых политик.
- Наличие и зрелость NetworkPolicy:
- Экосистема и опыт команды:
- Наличие практик, документации, успешных кейсов.
Пример практического, зрелого ответа:
- "В боевых кластерах мы предпочитаем Calico:
- он даёт полную поддержку NetworkPolicy, что критично для сегментации микросервисов;
- позволяет использовать как overlay, так и чистый routing, что даёт гибкость под инфраструктуру;
- хорошо масштабируется и имеет зрелую документацию.
- Flannel использовали/рассматривали как простой вариант для dev/stage-сред:
- минимальный порог входа,
- но недостаточно возможностей по безопасности и контролю трафика для продакшн-нагрузок."
Связь с Go-сервисами:
При проектировании Go-приложений под Kubernetes важно:
- Не полагаться на "магическую" сетевую топологию:
- использовать сервисы (Service) и DNS, а не IP-адреса Pod’ов.
- Учитывать сетевые политики:
- если включены NetworkPolicy (Calico/Cilium), явно открывать нужные порты и направления.
- Уметь дебажить сетевые проблемы:
- проверка связи между Pod’ами;
- диагностика дропа пакетов политиками;
- работа с readiness/liveness в условиях ограниченного трафика.
Такой ответ показывает понимание:
- что именно делает выбранный плагин;
- чем он отличается от альтернатив;
- почему его выбор рационален под конкретные требования кластера, а не случайен.
Вопрос 11. Как предоставить внешний доступ к Kafka, запущенной внутри Kubernetes-кластера?
Таймкод: 00:09:57
Ответ собеседника: неполный. Перечисляет NodePort, Ingress, external IP, но не учитывает особенности Kafka (listener’ы, broker-идентичность, адреса в metadata) и не выделяет корректные схемы экспонирования именно для Kafka.
Правильный ответ:
Экспонирование Kafka из Kubernetes — это нетривиальная задача, потому что Kafka передает клиентам в метаданных конкретные адреса брокеров. Недостаточно просто "открыть порт"; нужно гарантировать, что:
- внешний клиент получит корректные адреса брокеров;
- эти адреса будут доступны извне (DNS/порт);
- схема соответствует топологии (количество брокеров, их идентичность).
Ключевая особенность Kafka:
- Клиент сначала подключается к bootstrap-серверу.
- В ответ получает список брокеров с их
advertised.listeners. - Далее клиент ходит напрямую к конкретным брокерам.
- Если
advertised.listenersуказывают внутренние адреса (ClusterIP, pod IP), внешний клиент подключиться не сможет.
Поэтому основной вопрос: как сделать так, чтобы advertised.listeners Kafka-сервисов указывали на реальные внешние (достижимые) адреса.
Основные подходы (production-релевантные):
- NodePort для каждого брокера
Схема:
- Каждый Kafka-брокер — отдельный Pod (обычно StatefulSet).
- Для каждого брокера создается Service типа NodePort.
advertised.listenersнастроены так, чтобы указывать:- внешний адрес/имя ноды + NodePort для каждого конкретного брокера.
Минусы:
- Много ручной работы и сложностей с конфигурацией:
- фиксированные порты,
- необходимость стабильного соответствия broker_id → NodePort/Node/DNS.
- Требует:
- стабильных нод,
- корректной настройки DNS (например,
broker-0.kafka.example.com→ выбранная нода/порт).
Используется в основном:
- когда нет балансировщиков уровня облака,
- или при простых стендах с небольшим числом брокеров.
- LoadBalancer на брокер (облачный сценарий)
В облаке (AWS/GCP/Azure) для каждого брокера можно:
- создать Service типа LoadBalancer;
- получить отдельный внешний IP/DNS для каждого брокера;
- прописать эти значения в
advertised.listeners.
Плюсы:
- Прозрачная маршрутизация, минимальная ручная логика.
Минусы:
- Стоимость: по LB на брокер.
- Не всегда доступно on-prem.
- HostPort / NodePort + внешний L4-балансировщик
Схема:
- Используется StatefulSet, каждый Pod соответствует брокеру:
kafka-0,kafka-1, … - Через HostPort или NodePort/DaemonSet/LB привязываем каждый брокер к конкретному внешнему адресу/порту.
- Настраиваем внешний L4-балансировщик или DNS, чтобы:
broker-0.kafka.example.com→ конкретный node:port;broker-1.kafka.example.com→ другой node:port.
Идея:
- Каждый брокер получает свой стабильный внешний endpoint.
advertised.listenersуказывают именно на него.
- Использование специализированных операторов: Strimzi / Confluent Operator
Самый зрелый и рекомендуемый путь для production в Kubernetes.
Например, Strimzi Kafka Operator:
- Автоматизирует:
- создание StatefulSet для брокеров;
- конфигурацию listeners (internal/external);
- создание Service-ов нужного типа (NodePort, LoadBalancer, Route/Ingress в OpenShift).
- Позволяет выбрать режимы экспонирования:
type: nodeporttype: loadbalancertype: ingress(через TCP Ingress/рout’ы)
- Сам правильно прописывает
advertised.listenersдля брокеров. - Пример (упрощённая идея, не полный манифест):
apiVersion: kafka.strimzi.io/v1beta2
kind: Kafka
metadata:
name: my-kafka
spec:
kafka:
replicas: 3
listeners:
- name: internal
port: 9092
type: internal
tls: false
- name: external
port: 9094
type: nodeport
tls: false
storage:
type: persistent-claim
size: 100Gi
class: fast-ssd
Operator:
- создаст NodePort/LoadBalancer/Ingress;
- настроит правильные
advertised.listenersдля внешних клиентов.
Почему обычный HTTP Ingress обычно не подходит
- Стандартный Ingress в Kubernetes ориентирован на HTTP/HTTPS (L7).
- Kafka использует свой бинарный протокол поверх TCP, не HTTP.
- Для Kafka нужен L4-прокси или Ingress-контроллер, который умеет TCP:
- Nginx stream,
- HAProxy,
- Envoy,
- или специальные конфигурации Ingress-контроллеров, поддерживающих TCP маршрутизацию.
- Даже при использовании TCP Ingress важно:
- маппинг "конкретный брокер → конкретный внешний адрес/порт".
Критические моменты, которые нужно явно учитывать:
- Для Kafka недостаточно "одного NodePort/Ingress":
- bootstrap endpoint + индивидуальные адреса для брокеров (из metadata).
- Нужно обеспечить:
- стабильную идентичность брокеров (обычно через StatefulSet);
- согласованность
advertised.listenersс реальными внешними endpoint’ами; - соответствующий DNS (назначение имён брокерам).
Хороший ответ в интервью должен звучать примерно так:
- "Kafka требует корректной настройки advertised.listeners, так как клиенты подключаются к конкретным брокерам. В Kubernetes для внешнего доступа мы:
- поднимаем Kafka как StatefulSet;
- для каждого брокера используем отдельный Service (NodePort/LoadBalancer) или HostPort;
- настраиваем DNS так, чтобы у каждого брокера был стабильный внешний адрес;
- или используем Strimzi/Confluent Operator, который автоматизирует создание external listeners и правильно прописывает advertised.listeners.
- Обычный HTTP Ingress для Kafka не подходит; нужен L4-балансировщик или TCP-роутинг."
Если привязать к Go:
- Go-сервис, работающий с Kafka извне кластера:
- использует bootstrap servers вида:
broker-0.kafka.example.com:9094,broker-1.kafka.example.com:9094,...
- важно:
- корректно настроенные адреса брокеров,
- таймауты, retry-политику,
- поддержку TLS/SASL при необходимости.
- использует bootstrap servers вида:
Вопрос 12. Для чего в Kubernetes используется компонент kube-proxy?
Таймкод: 00:10:44
Ответ собеседника: неполный. Отмечает, что kube-proxy отвечает за сетевое взаимодействие и настройку правил, но не объясняет, как он реализует механизмы Service IP, iptables/ipvs и балансировку трафика между Pod’ами.
Правильный ответ:
kube-proxy — это ключевой компонент плоскости данных Kubernetes, отвечающий за реализацию абстракции Service и маршрутизацию трафика к Pod’ам, которые стоят за этим Service. Его задача — обеспечить прозрачный, отказоустойчивый доступ к backend-подам через стабильный виртуальный IP/порт.
Важно: kube-proxy не "просто что-то проксирует", а программирует сетевой стек ноды (iptables/ipvs), чтобы:
- каждый ClusterIP Service был доступен по своему виртуальному IP;
- трафик к этому IP корректно распределялся между живыми Pod’ами;
- изменения состава Pod’ов (scale, рестарты) автоматически учитывались в правилах маршрутизации.
Основные функции kube-proxy
- Реализация Service (ClusterIP, NodePort, частично LoadBalancer):
- Service в Kubernetes:
- абстракция "виртуального" IP (ClusterIP) + порт;
- за ним — динамический набор Pod’ов (endpoints).
- kube-proxy следит за объектами:
- Service,
- Endpoints/EndpointSlice (список Pod IP + port),
- и на основе их состояния обновляет правила маршрутизации на каждой ноде.
Результат:
- Любой Pod в кластере, обратившийся к ClusterIP:Port сервиса, прозрачно попадает на один из backend-Pod’ов.
- Балансировка трафика между Pod’ами
kube-proxy не делает L7-балансировку и не парсит HTTP. Он работает на уровне L3/L4:
- равномерно распределяет соединения между доступными Pod’ами сервиса (round-robin/хеширование в зависимости от backend-механизма);
- учитывает только те Pod’ы, которые присутствуют в Endpoint/EndpointSlice (т.е. прошли readiness и не помечены как NotReady).
- Механизмы реализации: iptables и ipvs
Режимы работы (зависят от конфигурации):
-
iptables mode:
- kube-proxy генерирует цепочки iptables:
- для ClusterIP;
- для NodePort;
- DNAT трафика с Service IP на Pod IP.
- Преимущества:
- простота, повсеместная доступность.
- Недостатки:
- большое число правил → потенциальные проблемы с производительностью на очень больших кластерах.
- kube-proxy генерирует цепочки iptables:
-
IPVS mode:
- Использует Linux IPVS — ядровой модуль для высокопроизводительной L4-балансировки.
- kube-proxy настраивает виртуальные сервисы IPVS:
- более эффективен по производительности;
- лучше масштабируется на тысячи сервисов и эндпоинтов.
- Рекомендуется для крупных продакшн-кластеров.
Во всех режимах идея одна:
- kube-proxy НЕ является data-plane-прокси в виде user-space процесса, через который бегут все пакеты;
- он конфигурирует ядро, чтобы маршрутизация происходила на уровне ОС.
- NodePort и LoadBalancer
- NodePort:
- kube-proxy открывает указанный порт на каждой ноде;
- настраивает правила, чтобы трафик с nodeIP:nodePort попадал на backend-Pod’ы сервиса.
- LoadBalancer:
- внешний LB (cloud) указывает на NodePort/ClusterIP;
- kube-proxy обеспечивает, чтобы входящий трафик корректно разошелся по Pod’ам.
- Ошибочные представления, которые важно уметь исправить
- kube-proxy — не про Pod-to-Pod networking в целом:
- За базовую связность Pod’ов (overlay/маршрутизация) отвечает CNI-плагин (Calico, Flannel, Cilium и т.д.).
- kube-proxy — про Service-ы: виртуальные IP и балансировку к endpoints.
- kube-proxy не выполняет L7-логики:
- не читает HTTP-заголовки,
- не делает path-based routing (это задача Ingress Controller’ов / L7-прокси).
- Практическое влияние на Go-сервисы
При разработке сервисов на Go важно понимать поведение kube-proxy:
- Подключение к другим сервисам:
- используем DNS-имя Service (
my-service.namespace.svc.cluster.local) илиmy-service:порт; - обращения идут через ClusterIP, который kube-proxy мапит на Pod’ы.
- используем DNS-имя Service (
- Особенности балансировки:
- балансировка — по соединениям, а не по отдельным HTTP-запросам;
- при долгоживущих соединениях (gRPC, WebSocket) нужно учитывать, что один Pod может получить несоразмерную нагрузку.
- Health-check:
- только Pod’ы с успешным readiness включаются в списки Endpoints;
- при падении/NotReady kube-proxy перестает маршрутизировать к ним трафик.
Минимальная, но точная формулировка:
- kube-proxy:
- наблюдает за Service и Endpoints;
- программирует iptables/ipvs на нодах;
- обеспечивает работу ClusterIP/NodePort и L4-балансировку трафика между Pod’ами сервиса.
Такой ответ показывает глубокое понимание роли kube-proxy в сетевой модели Kubernetes, а не только общее "он за сеть отвечает".
Вопрос 13. Для чего используется kube-proxy и как он связан с iptables/ipvs?
Таймкод: 00:10:44
Ответ собеседника: неполный. Говорит, что kube-proxy отвечает за сетевую часть Kubernetes и настройку правил, упоминает iptables/ipvs, но не раскрывает, что kube-proxy реализует абстракцию Service и балансировку трафика к Pod’ам на каждой ноде.
Правильный ответ:
kube-proxy — это компонент, который реализует сетевую модель Kubernetes для Service-ов на уровне L3/L4. Его главная задача — обеспечить:
- доступ к сервисам по стабильным виртуальным IP (ClusterIP, NodePort),
- распределение трафика между актуальными Pod’ами (endpoints),
- прозрачную маршрутизацию без необходимости для приложений знать реальные Pod IP.
Связь с iptables/ipvs заключается в том, что kube-proxy не проксирует трафик напрямую в user-space (в продакшене), а программирует сетевой стек Linux (iptables или IPVS), чтобы маршрутизация и балансировка выполнялись ядром.
Ключевые моменты:
- Реализация абстракции Service
- Kubernetes Service:
- задает виртуальный IP (ClusterIP) и порт;
- описывает селектор, указывающий на Pod’ы (backend’ы).
- kube-proxy:
- отслеживает объекты Service и Endpoints/EndpointSlice через API-сервер;
- на основе этого:
- создает/обновляет правила в iptables или таблицах IPVS на каждой ноде.
Результат:
- Любой Pod в кластере, обращающийся к ClusterIP:Port, попадает на один из Pod’ов, входящих в Service.
- Балансировка трафика между Pod’ами
kube-proxy обеспечивает L4-балансировку:
- распределяет новые соединения между backend-Pod’ами;
- учитывает только живые endpoints (связано с readiness);
- не анализирует HTTP/протоколы L7 (это не Ingress, не L7-прокси).
- Режимы работы: iptables и IPVS
-
iptables mode:
- kube-proxy генерирует цепочки и правила DNAT:
- трафик на ClusterIP:Port перенаправляется (DNAT) на Pod IP:Port одного из endpoints.
- Преимущество:
- простота, нет зависимости от IPVS-модулей.
- Недостаток:
- большое количество правил → потенциальные проблемы масштабирования.
- kube-proxy генерирует цепочки и правила DNAT:
-
ipvs mode:
- kube-proxy создает виртуальные сервисы в IPVS:
- каждый Service становится виртуальным сервисом;
- Pod’ы — real servers.
- Преимущества:
- более высокая производительность,
- лучшая масштабируемость для больших кластеров.
- IPVS работает в ядре и оптимизирован под L4-балансировку.
- kube-proxy создает виртуальные сервисы в IPVS:
- NodePort и LoadBalancer
- Для Service типа NodePort:
- kube-proxy настраивает правила, чтобы трафик с nodeIP:nodePort попадал на backend-Pod’ы.
- Для Service типа LoadBalancer:
- внешний облачный LB указывает на NodePort/ClusterIP;
- kube-proxy обеспечивает дальнейшее распределение по Pod’ам.
- Взаимодействие с CNI
Важно различать:
- CNI-плагин (Calico, Flannel, Cilium):
- обеспечивает связность Pod↔Pod, маршрутизацию или overlay.
- kube-proxy:
- строит поведение Service (ClusterIP/NodePort) поверх уже существующей сетевой связности.
- Вместе:
- CNI отвечает за "кто до кого может достучаться" на уровне IP-сетей;
- kube-proxy — за то, чтобы обращение к Service вело на правильные Pod’ы.
Практическое влияние на Go-сервисы:
- Сервисы на Go в кластере обычно ходят к другим сервисам по:
http://my-service.namespace.svc.cluster.local:port- или просто
my-service:portвнутри одного namespace.
- kube-proxy гарантирует, что этот DNS-нейм/ClusterIP:
- всегда будет маршрутизирован на набор актуальных Pod’ов,
- без необходимости хардкодить Pod IP или реализовывать свой discovery.
Кратко:
- kube-proxy:
- наблюдает за Service и Endpoints;
- конфигурирует iptables или IPVS на каждой ноде;
- реализует виртуальные Service IP и L4-балансировку трафика между Pod’ами.
- Это ключевой элемент реализации Service-абстракции, а не просто "что-то про сеть".
Вопрос 14. Что такое Custom Resource Definition (CRD) и для чего он нужен?
Таймкод: 00:11:34
Ответ собеседника: правильный. Описывает CRD как механизм создания пользовательских сущностей в Kubernetes, специфичных для нужд системы, которые работают аналогично стандартным объектам и интегрируются с RBAC.
Правильный ответ:
Custom Resource Definition (CRD) — это стандартный механизм расширения Kubernetes API без форка кластера и без изменения исходного кода Kubernetes. С его помощью можно добавлять новые типы ресурсов (Custom Resources), которые:
- ведут себя как "первоклассные" объекты Kubernetes (как Pod, Deployment, Service),
- хранятся в etcd,
- управляются через kubectl, API, RBAC, admission- и validation-webhook’и,
- становятся частью декларативной модели инфраструктуры.
CRD — фундамент для операторов и любой "platform engineering" логики поверх Kubernetes.
Ключевые идеи:
- Расширение API Kubernetes
CRD позволяет зарегистрировать новый тип ресурса:
- Например:
KafkaCluster,PostgresCluster,Redis,Backup,CanaryRelease,App,Team,AlertPolicyи т.д.
- После создания CRD:
- в кластере появляется новый endpoint API:
apis/<group>/<version>/...
- можно создавать объекты нового типа с помощью
kubectl apply,kubectl get,kubectl describe.
- в кластере появляется новый endpoint API:
Условно:
- Был только
Deployment,Service. - Стало:
KafkaCluster(описывает кластер Kafka),PostgresCluster(описывает отказоустойчивый PostgreSQL).
- Связка CRD + Controller = Operator-паттерн
Сам CRD — только схема данных. "Магия" начинается, когда к нему добавляется контроллер (Operator):
- Контроллер отслеживает объекты кастомного ресурса и приводит кластер к желаемому состоянию.
- Пример:
- CRD
PostgresClusterописывает:- количество реплик,
- параметры ресурсов,
- backup-политику.
- Оператор:
- создает StatefulSet, Service, ConfigMap, Secrets,
- настраивает репликацию,
- следит за health,
- выполняет failover,
- управляет обновлениями.
- CRD
Это ровно тот же reconciliation loop, что есть у нативных контроллеров Kubernetes, но под вашу предметную область.
- Интеграция с механизмами Kubernetes
CRD полностью вписывается в экосистему:
- RBAC:
- Можно ограничивать доступ к кастомным ресурсам:
- кто может
get/list/watch, - кто может
create/update/delete.
- кто может
- Можно ограничивать доступ к кастомным ресурсам:
- Validation:
- Поддержка OpenAPI-схемы (structural schema) и валидации на уровне API-сервера.
- Admission Webhooks:
- Дополнительная валидация, мутинги, enforce-политики.
- Finalizers:
- Управление жизненным циклом (graceful удаление, cleanup ресурсов).
- Events, kubectl, audit:
- Всё работает так же, как для стандартных ресурсов.
Простой пример CRD (упрощённый):
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: apps.mycompany.io
spec:
group: mycompany.io
scope: Namespaced
names:
plural: apps
singular: app
kind: App
shortNames:
- app
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
image:
type: string
replicas:
type: integer
minimum: 1
После применения CRD можно создать ресурс:
apiVersion: mycompany.io/v1alpha1
kind: App
metadata:
name: my-api
spec:
image: registry.example.com/my-api:1.0.0
replicas: 3
Контроллер (ваш оператор) на Go может:
- считать этот объект,
- создать под него Deployment, Service, Ingress,
- следить за изменениями
specи обновлять инфраструктуру.
- Пример контроллера на Go (очень упрощённо)
Используя controller-runtime / client-go, можно реализовать reconciliation loop:
func (r *AppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var app myv1alpha1.App
if err := r.Get(ctx, req.NamespacedName, &app); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// На основе app.spec создаём/обновляем Deployment
desired := buildDeployment(app)
var existing appsv1.Deployment
err := r.Get(ctx, types.NamespacedName{
Name: desired.Name,
Namespace: desired.Namespace,
}, &existing)
if apierrors.IsNotFound(err) {
if err := r.Create(ctx, &desired); err != nil {
return ctrl.Result{}, err
}
} else if err == nil {
// сравнить и при необходимости обновить spec
// ...
} else {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
Так строятся реальные операторы (Strimzi, Cert-Manager, Prometheus Operator и т.д.).
- Когда использовать CRD
CRD особенно полезны, когда:
- Нужна декларативная модель управления сложным сервисом:
- БД-кластеры, очереди, Kafka, VPN, GitOps-конфигурации, policy-as-code.
- Вы хотите дать разработчикам простой API:
- вместо десятков YAML’ов (Deployment, Service, PVC, Secrets, ConfigMap…) —
один объект
AppилиServiceStack, который оператор разворачивает.
- вместо десятков YAML’ов (Deployment, Service, PVC, Secrets, ConfigMap…) —
один объект
- Требуется инкапсулировать инфраструктурную экспертизу:
- логика "как правильно поднять PostgreSQL с HA" живет в операторе,
- разработчик описывает только желаемое состояние.
При этом не стоит делать CRD ради "просто нового имени". Он оправдан, когда:
- есть нетривиальная логика жизненного цикла,
- вы реально используете reconciliation и automation,
- есть ценность в унификации и декларативности.
Краткая формулировка:
- CRD — это способ добавить в Kubernetes собственные типы ресурсов, чтобы управлять доменной логикой и внешними системами в том же стиле, что и встроенными объектами.
- В связке с контроллерами CRD позволяет строить операторов, превращая Kubernetes в универсальную платформу оркестрации чего угодно, от БД до внутренних бизнес-примитивов.
Вопрос 15. В чем отличие ConfigMap от Secret?
Таймкод: 00:12:29
Ответ собеседника: неправильный. Утверждает, что данные в Secret шифруются и маскируются, упоминает base64, но не поясняет, что это только кодирование, а не шифрование, и не раскрывает корректно разницу с ConfigMap и реальный уровень безопасности Secret.
Правильный ответ:
ConfigMap и Secret — это оба механизма хранения конфигурационных данных в Kubernetes, но с разным назначением, моделью безопасности и типом данных.
Ключевые отличия по сути:
- Семантика и назначение
-
ConfigMap:
- Для неконфиденциальных данных конфигурации.
- Примеры:
- URL внешних сервисов,
- имена топиков Kafka,
- feature-флаги,
- форматы логов, параметры таймаутов.
- Хранит строки/ключи в открытом виде.
-
Secret:
- Для конфиденциальных данных.
- Примеры:
- пароли к БД,
- токены API,
- ключи доступа к S3,
- TLS-сертификаты и приватные ключи.
- Семантически помечает данные как секретные: это триггер для более строгого обращения.
- Хранение и base64: важное заблуждение
- По умолчанию:
- И ConfigMap, и Secret хранятся в etcd.
- Secret:
- Данные в манифесте и в etcd — в base64-кодировке.
- base64 — это НЕ шифрование, а просто кодирование (легко обратно декодируется).
- То есть:
- "Secret зашифрован" по умолчанию — это неверно.
- Правильная формулировка:
- Secret помечает данные как чувствительные,
- позволяет включить к ним дополнительные меры защиты,
- но безопасность зависит от настроек кластера и инфраструктуры.
Рекомендуемая практика для реальной безопасности:
- Включить Encryption at Rest для Secret в etcd:
- через EncryptionConfiguration на уровне kube-apiserver,
- данные Secret в etcd будут шифроваться (например, AES).
- Ограничить доступ к Secret через RBAC:
- только нужные ServiceAccount/роли могут читать.
- Ограничить доступ к бэкапам etcd и снапшотам:
- они содержат контент Secret.
- Рассматривать Secret как конфиденциальные данные, не класть их в git в открытом виде.
- Доступ в Pod’ах
И ConfigMap, и Secret могут быть:
- смонтированы как файлы:
- ConfigMap — обычно текстовые конфиги;
- Secret — ключи/сертификаты/пароли.
- переданы как переменные окружения.
Главная разница — как к ним относятся с точки зрения безопасности:
- Secret:
- обычно монтируется с более строгими правами (например, 0400/0440);
- может обрабатываться отдельной логикой (скрытие в интерфейсах, не логировать значения).
- ConfigMap:
- используется как обычные конфиги, без строгих ограничений.
- Управление доступом (RBAC)
- Чтение ConfigMap и Secret контролируется отдельно:
- Разные ресурсы в RBAC:
configmapsvssecrets.
- Разные ресурсы в RBAC:
- Best practice:
- Сильно ограничивать чтение
secrets:- по namespace,
- по service account,
- по ролям.
- Доступ к ConfigMap может быть более либеральным.
- Сильно ограничивать чтение
- Типы Secret
Secret дополнительно предоставляет семантику типов:
kubernetes.io/basic-authkubernetes.io/dockerconfigjsonkubernetes.io/tls- и т.д.
Это:
- помогает валидировать структуру,
- упрощает использование в других компонентах (Ingress, Docker registry auth).
- Как это выглядит в манифестах (наглядно)
ConfigMap (неконфиденциальный):
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "info"
API_URL: "https://api.example.com"
Secret (конфиденциальный):
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
username: YXBwX3VzZXI= # "app_user"
password: c2VjcmV0X3Bhc3M= # "secret_pass"
Пример использования в поде:
env:
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
- Связь с приложениями на Go
Для сервисов на Go важно:
- Никогда не хардкодить секреты в коде/образе.
- Читать чувствительные данные из:
- переменных окружения,
- файлов в смонтированных Secret volume.
- Учитывать ротацию:
- при изменении Secret Kubernetes может перезапустить Pod или обновить том;
- код должен уметь переподключаться (особенно к БД/внешним сервисам).
Пример чтения секрета из env в Go:
user := os.Getenv("DB_USER")
pass := os.Getenv("DB_PASSWORD")
// использовать для подключения к БД
Краткая корректная формулировка:
- ConfigMap — для некритичных конфигураций, хранит данные в открытом виде.
- Secret — для чувствительных данных. По умолчанию только base64-кодирование (не шифрование), но:
- поддерживает более строгий контроль доступа (RBAC),
- может и должен использоваться вместе с шифрованием at rest и безопасными практиками.
- Главное отличие — семантика и модель безопасности, а не "волшебное шифрование".
Вопрос 16. Как плавно вывести несколько рабочих нод из Kubernetes-кластера для обслуживания, чтобы нагрузки и поды корректно перенеслись на другие ноды?
Таймкод: 00:13:05
Ответ собеседника: неправильный. Рассчитывает на автоматическое перераспределение после выключения нод, не использует cordon/drain и не описывает управляемую миграцию подов.
Правильный ответ:
Плавное выведение нод из кластера — это управляемый процесс, который должен:
- не привести к даунтайму;
- корректно мигрировать поды на другие ноды;
- не нарушить Stateful/critical workloads;
- сохранить инварианты по ReplicaSet/Deployment/StatefulSet/DaemonSet и PodDisruptionBudget.
Ключевые инструменты: cordon, drain, PodDisruptionBudget, мониторинг.
Правильная последовательность действий:
- Cordon: запретить новые поды на ноде
Команда:
kubectl cordon <node-name>
Что происходит:
- Нода помечается как Unschedulable.
- На неё больше не будут планироваться новые поды.
- Уже запущенные поды продолжают работать.
Для вывода нескольких нод:
kubectl cordon node-1 node-2 node-3
Зачем это нужно:
- предотвращаем появление новых подов перед drain;
- не ломаем распределение при длительных операциях.
- Drain: контролируемая эвакуация подов
Команда (базовый вариант):
kubectl drain <node-name> \
--ignore-daemonsets \
--delete-emptydir-data
Основные моменты:
- Для pod’ов, управляемых контроллерами (Deployment, ReplicaSet, StatefulSet):
- Kubernetes попытается пересоздать их на других нодах.
- Поды DaemonSet:
- по умолчанию не удаляются (поэтому нужен
--ignore-daemonsets).
- по умолчанию не удаляются (поэтому нужен
- Поды с emptyDir:
- при drain будут удалены; данные потеряются (поэтому
--delete-emptydir-dataтребует осознанности).
- при drain будут удалены; данные потеряются (поэтому
- Поды без контроллера (standalone pod):
- по умолчанию блокируют drain;
- их нужно удалить или явно разрешить (не рекомендовано держать такие pod’ы).
Важное отличие от "просто выключить ноду":
- drain:
- корректно удаляет поды,
- даёт контроллерам возможность пересоздать их на других нодах,
- учитывает PodDisruptionBudget.
- PodDisruptionBudget (PDB): защита от "убили все реплики"
Для критичных сервисов необходимо заранее настроить PDB:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-api-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: my-api
Или:
spec:
maxUnavailable: 1
Поведение:
- drain не сможет удалить слишком много pod’ов сервиса одновременно, если это нарушит PDB.
- Это предотвращает ситуацию, когда обслуживаем несколько нод и случайно "выносим" все реплики сервиса.
Чтобы плавно вывести несколько нод:
- cordon всех планируемых к обслуживанию нод;
- drain по одной (или батчами), следя за:
- выполнением PDB;
- состоянием подов/сервисов;
- убедиться, что достаточно capacity на оставшихся нодах.
- Проверка состояния и емкости кластера
До и во время операций:
- Проверить, что есть достаточно ресурсов:
- CPU/memory на оставшихся нодах для размещения подов.
- Мониторить:
kubectl get pods -o wide;kubectl get nodes;- метрики (Prometheus, Grafana);
- ошибки в Events.
Если capacity не хватает:
- сначала увеличить число нод / ресурсов;
- затем выполнять cordon/drain.
- Возврат ноды в кластер после обслуживания
После обслуживания:
- Включаем ноду;
- Убираем cordon:
kubectl uncordon <node-name>
- Нода снова становится доступной для планирования подов.
- Что будет, если просто выключить ноду
Ошибочный подход (как в ответе кандидата):
- Если просто "грохнуть" ноду:
- kubelet перестаёт отвечать;
- через некоторое время нода помечается NotReady;
- контроллеры могут пересоздать поды на других нодах.
- Проблемы:
- пауза до перераспределения (зависит от grace periods, timeouts);
- риск одновременного "отвала" нескольких нод и нарушения PDB;
- более грубое поведение, возможные временные ошибки для клиентов;
- stateful/critical workloads могут пострадать.
Этот путь не считается корректной практикой для управляемого обслуживания.
- Учет особенностей разных типов workloads
- Deployment/ReplicaSet:
- хорошо переносятся drain при наличии реплик и PDB.
- StatefulSet:
- важен порядок и хранение данных (PVC).
- drain корректен, поды поднимутся на других нодах, если storage позволяет.
- DaemonSet:
- не удаляются при drain по умолчанию;
- после возвращения ноды DaemonSet-поды будут созданы заново.
- Standalone Pods:
- лучше не использовать; если есть — мешают drain.
Краткая формулировка корректного ответа:
- Для плавного вывода ноды:
- сначала cordon (запретить новые поды),
- затем drain (эвакуировать поды на другие ноды с учетом PDB),
- выполнить обслуживание,
- uncordon для возврата ноды.
- Для нескольких нод:
- последовательно (или батчами) cordon+drain, контролируя PDB и ресурсы,
- а не "выключать в лоб" и надеяться, что кластер сам разрулит.
Вопрос 17. Какие механизмы Kubernetes позволяют предоставить персистентное хранилище для подов (например, для брокеров очередей)?
Таймкод: 00:16:01
Ответ собеседника: неполный. Упоминает PersistentVolume, PersistentVolumeClaim и провижионеры, но путает, что является физическим томом, а что "заявкой", сумбурно описывает биндинг и не раскрывает типы хранилищ и динамическое выделение.
Правильный ответ:
Для stateful-сервисов (Kafka, RabbitMQ, PostgreSQL, Elastic, другие брокеры и БД) ключевая задача — обеспечить надежное, предсказуемое и персистентное хранилище, независимое от жизненного цикла Pod. В Kubernetes это решается связкой:
- PersistentVolume (PV)
- PersistentVolumeClaim (PVC)
- StorageClass
- динамические провижионеры (CSI-драйверы)
Плюс понимание того, как все это используется в StatefulSet и других контроллерах.
Разберем по слоям.
Основные сущности
- PersistentVolume (PV)
- Это объект кластера, описывающий реальное хранилище:
- диск в облаке (EBS, GCE Persistent Disk, Azure Disk),
- сетевое хранилище (NFS, Ceph RBD, CephFS, GlusterFS, iSCSI),
- локальный диск ноды (Local Persistent Volume),
- любой storage, подключенный через CSI-драйвер.
- PV — это ресурс кластера:
- имеет емкость, тип доступа, параметры.
- Создается:
- вручную (static provisioning);
- автоматически через StorageClass (dynamic provisioning).
- PersistentVolumeClaim (PVC)
- Это "заявка" Pod/приложения на хранилище.
- Приложение говорит:
- "мне нужен volume размером 20Gi, с таким-то режимом доступа, из такого-то StorageClass".
- Kubernetes:
- находит подходящий PV (static),
- либо создаёт новый PV через провижионер (dynamic),
- связывает PVC с конкретным PV (binding 1:1).
Ключевой момент:
- PV — сам том (ресурс).
- PVC — запрос на том.
- Pod использует PVC, не зная деталей реализации.
- StorageClass
- Описывает "класс" хранилища:
- тип диска (ssd/hdd),
- параметры производительности,
- зону доступности,
- политику reclaim,
- провижионер (какой драйвер создаёт тома).
- Важен для динамического выделения:
- когда создаётся PVC с
storageClassName, Kubernetes вызывает соответствующий провижионер, который создаёт PV.
- когда создаётся PVC с
Пример StorageClass (облако):
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp3
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
- Dynamic provisioning (динамическое выделение)
- Вместо ручного создания PV под каждый PVC:
- При создании PVC с указанным StorageClass:
- CSI/провижионер автоматически создаёт PV нужного размера/типа.
- При создании PVC с указанным StorageClass:
- Это стандарт в продакшене:
- масштабируемо,
- меньше ручных операций,
- удобно для операторов (Kafka, PostgreSQL и др.).
Пример PVC:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: kafka-broker-0-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd
resources:
requests:
storage: 100Gi
Kubernetes:
- вызовет провижионер из fast-ssd;
- создаст PV (например, EBS-диск) и привяжет его к этому PVC.
Типы доступа (Access Modes)
Важно понимать для брокеров и БД:
- ReadWriteOnce (RWO):
- том может быть смонтирован для чтения/записи только на одной ноде;
- типично для блочных устройств (EBS, GCE PD, Azure Disk).
- Стандартный выбор для Kafka, RabbitMQ, PostgreSQL и большинства брокеров.
- ReadOnlyMany (ROX):
- том доступен для чтения на многих нодах.
- ReadWriteMany (RWX):
- том доступен для чтения/записи с многих нод (NFS, CephFS, CSI-решения).
- Нужен для некоторых специфичных сценариев (shared storage), но для брокеров обычно не используется.
Использование с StatefulSet (для брокеров очередей)
Для брокеров (Kafka, RabbitMQ, NATS JetStream, etc.) важно, чтобы у каждого инстанса были:
- стабильная идентичность,
- привязанный к нему персистентный том, который переживает рестарты pod’а.
StatefulSet + volumeClaimTemplates:
- StatefulSet сам создаёт отдельный PVC для каждой реплики.
Пример для Kafka-брокеров:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: kafka
spec:
serviceName: "kafka"
replicas: 3
selector:
matchLabels:
app: kafka
template:
metadata:
labels:
app: kafka
spec:
containers:
- name: broker
image: confluentinc/cp-kafka:latest
volumeMounts:
- name: data
mountPath: /var/lib/kafka/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd
resources:
requests:
storage: 200Gi
Результат:
- Для
kafka-0→ PVCdata-kafka-0→ свой PV. - Для
kafka-1→ PVCdata-kafka-1→ свой PV. - Если pod
kafka-1перезапускается или переезжает на другую ноду:- он монтирует тот же PVC (тот же PV),
- данные брокера сохраняются.
Провижионеры и CSI
Современный подход — использовать CSI-драйверы:
- AWS EBS CSI Driver, GCP PD CSI, Azure Disk/File CSI;
- Ceph RBD/FS, Longhorn, OpenEBS, NetApp и т.д.
Их задачи:
- создавать/удалять реальные тома;
- подключать/отключать тома к нодам;
- поддерживать снапшоты, расширение томов и другие фичи.
Частые ошибки и что важно уметь объяснить
- Не путать:
- PV — описание/ресурс хранилища;
- PVC — заявка на использование хранилища;
- StorageClass — "шаблон" для динамического создания PV.
- Не думать, что:
- локальные emptyDir — персистентные (они живут только пока жив pod/нодa).
- Понимать:
- для stateful-систем нужна связка StatefulSet + PVC;
- брокеры и БД используют RWO-тома;
- dynamic provisioning — стандарт для автоматизации.
Краткая формулировка корректного ответа:
- В Kubernetes персистентное хранилище для подов обеспечивается через:
- PersistentVolume (PV) — реальный том,
- PersistentVolumeClaim (PVC) — запрос на этот том,
- StorageClass + CSI-провижионеры — для динамического создания PV.
- Для брокеров очередей и БД обычно:
- используют StatefulSet с volumeClaimTemplates,
- каждый pod получает свой PVC/PV,
- данные переживают рестарты и миграции подов.
Вопрос 18. Зачем нужны StorageClass в Kubernetes?
Таймкод: 00:17:49
Ответ собеседника: правильный. Говорит, что StorageClass используется для классификации и выбора томов, PV и PVC сопоставляются по storageClassName, что позволяет управлять типами и параметрами хранения. Объяснение верное, но упрощённое.
Правильный ответ:
StorageClass — это механизм абстракции и автоматизации работы с хранилищем в Kubernetes. Он позволяет:
- описать классы хранения (SSD/HDD, быстрый/дешевый, локальный/сетевой),
- управлять политиками создания и удаления томов,
- включить динамическое выделение PersistentVolume под запросы приложений,
- инкапсулировать детали конкретного storage-бэкенда (через CSI/провижионеры),
- отделить заботы инфраструктуры от приложений.
По сути, StorageClass — это "профиль" или "политика" хранилища, к которой приложения привязываются через PVC.
Ключевые задачи StorageClass:
- Динамическое выделение томов (Dynamic Provisioning)
Без StorageClass:
- Админ вручную создаёт PersistentVolume (PV).
- Приложения создают PersistentVolumeClaim (PVC).
- Kubernetes пытается сопоставить PVC с подходящим PV.
- Это не масштабируется и неудобно в динамичных средах.
Со StorageClass:
- Приложение создаёт только PVC с
storageClassNameи нужным размером. - Kubernetes:
- на основании StorageClass вызывает провижионер (CSI-драйвер),
- автоматически создаёт PV нужного типа и размера,
- биндинг PVC → PV происходит прозрачно.
Пример PVC с динамическим выделением:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd
resources:
requests:
storage: 20Gi
Если существует StorageClass fast-ssd с настроенным провижионером — PV создастся автоматически.
- Абстракция типов и параметров хранилища
StorageClass позволяет скрыть детали реализации:
- типы дисков:
- SSD vs HDD;
- gp3/io2 (AWS), pd-ssd/pd-standard (GCP), Premium/LRS (Azure).
- параметры:
- IOPS,
- throughput,
- зона доступности,
- политика шифрования,
- специфичные опции для NFS, Ceph, NetApp и др.
Пример:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: ebs.csi.aws.com
parameters:
type: gp3
iops: "6000"
throughput: "250"
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
Приложение просто указывает storageClassName: fast-ssd, не зная, что за ним AWS EBS gp3 с нужными параметрами.
- Управление жизненным циклом томов
StorageClass задает:
reclaimPolicy:Delete— удалять PV вместе с PVC (подходит для временных окружений).Retain— сохранять данные после удаления PVC (для ручного восстановления/анализа).
volumeBindingMode:Immediate:- том создаётся сразу при создании PVC;
WaitForFirstConsumer:- том создаётся только при планировании pod’а;
- важно для zonal storage:
- позволяет выбрать зону в соответствии с нодой, где будет pod,
- предотвращает конфликт "PV в одной зоне, pod в другой".
Это критично для продакшна: управляем, когда и где создаётся физический том, и что с ним происходит после удаления.
- Интеграция с StatefulSet и брокерами/БД
Для stateful-систем (Kafka, RabbitMQ, PostgreSQL и т.д.):
- StatefulSet использует
volumeClaimTemplates, в которых указываетсяstorageClassName. - StorageClass определяет:
- какой тип диска получит каждый брокер/реплика;
- как создаются и живут эти PV.
Это позволяет:
- задать, что БД и брокеры должны работать только на быстрых SSD,
- автоматически обеспечивать персистентность и производительность,
- не заставлять разработчиков и операторов руками управлять PV.
- Связь с CSI и внешними хранилищами
StorageClass — точка интеграции с:
- CSI-драйверами облаков (AWS, GCP, Azure),
- on-prem хранилищами (Ceph, NetApp, Pure, Longhorn, OpenEBS и др.).
Параметры provisioner и parameters определяют:
- как именно драйвер создаст том,
- какие настройки применит.
- Практические best practices
- Иметь несколько StorageClass:
fast-ssdдля критичных БД и брокеров,standardдля обычных приложений,archiveдля редко используемых данных.
- Явно указывать
storageClassNameв PVC:- не полагаться слепо на default, особенно в продакшене.
- Использовать
WaitForFirstConsumerв zonal окружениях:- для избежания проблем с размещением томов.
Краткая формулировка:
- StorageClass в Kubernetes:
- определяет "классы" хранилища и политику работы с ними;
- позволяет автоматически (динамически) создавать PV под PVC;
- инкапсулирует детали провайдера, производительности, зон, reclaim-политик;
- критичен для корректной работы StatefulSet, БД и брокеров в продакшене.
Вопрос 19. Какой PersistentVolume будет выбран, если есть несколько заранее созданных PV разных размеров, а приходит запрос на объем 15 ГБ?
Таймкод: 00:18:23
Ответ собеседника: правильный. Говорит, что будет выбран наименьший том, размер которого не меньше запрошенного (в примере — 20 ГБ), что соответствует реальному поведению биндинга PV.
Правильный ответ:
Поведение биндинга PVC → PV при статическом провижининге базируется на принципе:
- PVC получает первый подходящий по требованиям PV.
- "Подходящий" означает:
- размер PV ≥ запрошенного размера PVC;
- совпадают
accessModes(RWO/RWX/ROX); - совпадает
storageClassName(или оба без класса); - PV не забинден к другому PVC.
При наличии нескольких PV:
- Kubernetes выбирает один из удовлетворяющих условий.
- Для размеров действует принцип "подходящий минимально достаточный":
- из тех PV, которые удовлетворяют требования по размеру и параметрам,
- выбирается том с минимальным размером, который не меньше запрошенного.
- В примере:
- есть, допустим, PV на 10 ГБ, 20 ГБ, 50 ГБ;
- PVC запрашивает 15 ГБ;
- 10 ГБ не подходит (меньше запроса),
- 20 ГБ и 50 ГБ подходят,
- выбирается 20 ГБ как минимально возможный удовлетворяющий.
Важно учитывать:
- Если
storageClassNameзадан в PVC:- будут рассматриваться только PV с тем же
storageClassName(или соответствующие правилам).
- будут рассматриваться только PV с тем же
- Если используется dynamic provisioning (через StorageClass и CSI):
- PV создается "под запрос", и вопрос выбора из заранее созданных PV не возникает.
- Выбор PV — это одноразовое связывание 1:1:
- после биндинга PVC "привязан" к конкретному PV,
- этот PV не будет использоваться другими PVC до освобождения (и с учётом
reclaimPolicy).
Кратко:
- В описанном сценарии корректный выбор — наименьший PV, чей размер не меньше, чем запрошенный PVC, при условии совпадения остальных параметров (access mode, storage class).
Вопрос 20. Что произойдет с привязкой PersistentVolume при перезапуске Deployment с несколькими репликами (например, Kafka) без использования StatefulSet?
Таймкод: 00:19:49
Ответ собеседника: неправильный. Утверждает, что после масштабирования до нуля и обратно под займет тот же том, игнорируя отсутствие стабильной идентичности подов в Deployment и невозможность корректно привязать отдельные PV к конкретным репликам при нескольких экземплярах.
Правильный ответ:
Ключевая идея: Deployment не обеспечивает стабильную идентичность подов и не предназначен для сценариев, где каждая реплика должна иметь свой устойчивый том. Это фундаментально важно для брокеров (Kafka, RabbitMQ, NATS JetStream), БД и других stateful-сервисов.
Разберём по сути.
- Как работают Deployment и PVC/PV
- Deployment:
- управляет ReplicaSet;
- поды считаются взаимозаменяемыми;
- имена подов не гарантированы:
- при пересоздании — будут другие имена;
- при scale-to-zero → все поды удаляются;
- при scale-up → создаются "новые" анонимные реплики.
- PVC/PV:
- PVC — запрос на том;
- PV — конкретный том;
- между ними устанавливается 1:1-привязка;
- Pod ссылается не на PV напрямую, а на PVC:
- Pod → PVC → PV.
Если у нас один PVC, один PV и несколько реплик Deployment:
- Один PVC не может одновременно быть ReadWriteOnce на нескольких нодах/подах.
- Для stateful-систем обычно требуется:
- отдельный том на каждую реплику.
- Что ломается при использовании Deployment для stateful-сервисов
Сценарий: Kafka через Deployment с replicas > 1, каждый pod использует один и тот же PVC или "каким-то образом" ожидается привязка томов.
Проблемы:
- Отсутствие стабильной идентичности:
- Нет гарантии, что "реплика 0" при перезапуске — это тот же pod, что ранее.
- Нельзя детерминированно сказать: "этот том всегда принадлежит именно этому брокеру".
- При scale-to-zero:
- все поды удалены;
- если PVC остаются:
- при новом scale-up поды могут:
- смонтировать существующие PVC "в другом порядке" (если реализовано вручную),
- или вообще не смонтировать ожидаемо без явной логики.
- при новом scale-up поды могут:
- если PVC удаляются вместе с Deployment (через ownerReferences или Helm-чарт):
- данные утрачиваются.
- При неправильной конфигурации:
- возможен конфликт доступа:
- несколько pod’ов пытаются использовать один и тот же PV (RWO);
- это либо не смонтируется, либо приведет к некорректной работе.
- возможен конфликт доступа:
Важно:
- Kubernetes не "догадается", что конкретный том "принадлежит" конкретному брокеру Kafka, если это не описано явно через StatefulSet/индексацию.
- Как должно быть: StatefulSet + volumeClaimTemplates
Для stateful-систем используется StatefulSet, а не Deployment, потому что он:
- Обеспечивает стабильные имена подов:
kafka-0,kafka-1,kafka-2и т.д.
- Автоматически создает отдельный PVC для каждой реплики:
data-kafka-0,data-kafka-1, ...
- Гарантирует:
- при рестарте
kafka-0смонтирует именноdata-kafka-0; - привязка pod↔PVC/PV стабильна во времени.
- при рестарте
Пример для Kafka (схематично):
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: kafka
spec:
serviceName: "kafka"
replicas: 3
selector:
matchLabels:
app: kafka
template:
metadata:
labels:
app: kafka
spec:
containers:
- name: broker
image: confluentinc/cp-kafka:latest
volumeMounts:
- name: data
mountPath: /var/lib/kafka/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd
resources:
requests:
storage: 200Gi
Поведение:
kafka-0всегда ↔data-kafka-0.kafka-1всегда ↔data-kafka-1.- При рестарте или миграции на другую ноду:
- под монтирует тот же PVC/PV,
- данные и идентичность брокера сохраняются.
- Что конкретно неправильно в утверждении "под возьмет тот же том" с Deployment
Если использовать Deployment:
- Нет нативного механизма:
- привязать "replica 1" к "volume 1",
- "replica 2" к "volume 2".
- При scale-to-zero:
- Pod’ы удалены,
- при новом запуске:
- биндинг к конкретным PVC не гарантирован;
- возможна неконсистентность и поломка кластера Kafka/БД.
- Единственный "стабильный" сценарий:
- Deployment с 1 репликой + один PVC:
- тогда при перезапуске pod (без удаления PVC) действительно смонтирует тот же том.
- Но это не решает задачу кластера (несколько брокеров), о которой был вопрос.
- Deployment с 1 репликой + один PVC:
Именно в задаче "Deployment с несколькими репликами (Kafka)" корректный ответ:
- При использовании Deployment нет гарантированного и детерминированного соответствия между конкретной репликой и конкретным томом.
- После масштабирования до нуля и обратно, или при перезапуске:
- поды могут получить тома не так, как ожидалось,
- архитектура stateful-кластера ломается.
- Правильный вывод
Для брокеров очередей, БД и любых stateful-сервисов с несколькими репликами:
- Нельзя полагаться на Deployment + "как-нибудь PVC прикрутится".
- Нужно использовать:
- StatefulSet + volumeClaimTemplates,
- или операторы (Strimzi, Zalando Postgres Operator и т.п.), которые поверх используют StatefulSet и CRD.
- Только так обеспечивается:
- стабильная идентичность,
- предсказуемая привязка PV к конкретной реплике,
- корректное поведение при рестартах и масштабировании.
Краткая формулировка:
- В случае Deployment с несколькими репликами нет гарантии, что после перезапуска каждая реплика получит "свой прежний" том.
- Для stateful-нагрузок (Kafka и др.) требуется StatefulSet, который обеспечивает стабильное соответствие pod ↔ PVC ↔ PV.
Вопрос 21. Сколько контейнеров можно запускать внутри одного пода и что у них общего?
Таймкод: 00:20:44
Ответ собеседника: неполный. Указывает, что обычно используют один контейнер на под (best practice), но можно и больше; отмечает общую сеть, однако не упоминает общие volume и другие namespace, ответ неполный.
Правильный ответ:
Под — это минимальная единица деплоя в Kubernetes, которая инкапсулирует один или несколько контейнеров, тесно связанных логически и технически. Количество контейнеров в поде не ограничено жестко самим Kubernetes, но ограничивается только ресурсами ноды и здравым смыслом.
Ключевой принцип:
- В один под имеет смысл объединять контейнеры, которые:
- всегда должны запускаться и масштабироваться вместе;
- разделяют контекст выполнения (сеть, тома);
- реализуют паттерн "sidecar", "adapter", "init" или "collocated helper".
Практически: в большинстве случаев — 1 контейнер на под, в продуманных случаях — несколько.
Что общего у контейнеров внутри одного пода:
- Общий сетевой namespace
- Контейнеры в поде:
- используют один IP-адрес (IP пода);
- видят друг друга как localhost.
- Порты:
- нет конфликтов портов между подами, но внутри пода порты должны быть уникальны:
- если один контейнер слушает :8080, второй не может занять тот же порт.
- нет конфликтов портов между подами, но внутри пода порты должны быть уникальны:
- Взаимодействие:
- контейнеры внутри пода могут общаться через 127.0.0.1, что удобно для sidecar-архитектуры:
- например, Go-сервис на :8080 и sidecar-прокси/agent рядом.
- контейнеры внутри пода могут общаться через 127.0.0.1, что удобно для sidecar-архитектуры:
- Общие volume (диски и файлы)
- Под определяет volumes, которые могут быть смонтированы в несколько контейнеров внутри пода.
- Это позволяет:
- шарить конфиги;
- лог-файлы;
- unix-сокеты;
- кеш;
- данные, необходимые нескольким контейнерам.
- Примеры:
- Go-приложение пишет логи в файловую систему, sidecar (Fluent Bit/vector) читает их и отправляет в централизованное хранилище.
- Sidecar с TLS-сертификатами монтирует общий volume, основной контейнер использует те же файлы.
- Общие (или частично общие) namespace процесса/UTS IPC (в зависимости от настроек)
По умолчанию:
- Network namespace — общий.
- IPC и PID namespace:
- могут быть общими или раздельными, но часто используются по умолчанию общие сетевой и IPC в рамках пода.
- Практическое следствие:
- можно взаимодействовать через локальные механизмы (сокеты, shared memory),
- но основное, что важно в ответе: под — это единый "хост" с точки зрения сети.
- Общая жизненная модель
- Все контейнеры в поде:
- запускаются на одной ноде;
- планируются вместе;
- перезапускаются вместе (контроллером, если под удален/упал);
- масштабируются как единое целое (нельзя масштабировать один контейнер внутри пода отдельно от других).
- Если вам нужно независимое масштабирование — это должны быть разные поды.
- Init-контейнеры и sidecar-контейнеры
-
Init-контейнеры:
- выполняются до основных контейнеров,
- используют ту же сетевую и volume-модель,
- подготавливают окружение (миграции, генерация конфигов и т.п.).
-
Sidecar-контейнеры:
- типичный паттерн:
- лог-агент,
- прокси (Envoy/Nginx),
- сервис-меш sidecar,
- агент метрик/трассировки.
- Работают рядом с основным приложением, разделяя сеть и volumes.
- типичный паттерн:
Пример пода с несколькими контейнерами:
apiVersion: v1
kind: Pod
metadata:
name: go-app-with-sidecar
spec:
volumes:
- name: logs
emptyDir: {}
containers:
- name: app
image: my-registry/go-api:1.0.0
ports:
- containerPort: 8080
volumeMounts:
- name: logs
mountPath: /var/log/app
- name: log-forwarder
image: fluent/fluent-bit:latest
volumeMounts:
- name: logs
mountPath: /var/log/app
# лог-агент читает те же файлы и пересылает их наружу
В этом примере:
- Оба контейнера:
- в одном поде,
- с общим IP,
- с общим volume для логов.
Когда использовать несколько контейнеров в поде (осознанно):
- Sidecar:
- логирование,
- метрики,
- прокси,
- TLS-termination (редко сейчас, но возможно).
- Helper-контейнеры:
- обработка файлов,
- локальные агенты, tightly coupled с основным сервисом.
- Инкапсуляция инфраструктурной логики рядом с приложением.
Когда не надо:
- Если контейнеры не жестко связаны и должны масштабироваться независимо:
- использовать отдельные поды + Service, а не "напихивать всё в один под".
Краткая правильная формулировка:
- Количество контейнеров в поде не ограничено строго, но обычно 1–3 по смыслу.
- Контейнеры внутри одного пода:
- разделяют один IP и сетевой namespace,
- могут разделять volumes (общую файловую систему),
- всегда планируются и живут вместе как единое целое.
- Паттерн "один контейнер на под" — рекомендуемый по умолчанию, но несколько контейнеров оправданы для sidecar и других tightly coupled сценариев.
Вопрос 22. Что такое sidecar-контейнеры и для чего они используются?
Таймкод: 00:21:21
Ответ собеседника: неправильный. Признает, что слышал термин, но не знает концепцию, не работал с ними и не может объяснить назначение sidecar-подхода.
Правильный ответ:
Sidecar-контейнер — это дополнительный контейнер, запускаемый в одном Pod c основным приложением и разделяющий с ним:
- сетевой namespace (общий IP, localhost),
- общие volume’ы (общие файлы/сокеты),
- жизненный цикл (планируются и перезапускаются вместе).
Цель sidecar-паттерна — вынести вспомогательные, кросс-срезные или инфраструктурные задачи из основного контейнера, сохранив при этом тесную связность и минимальные накладные расходы на взаимодействие.
Ключевые свойства:
- Совместное размещение и жизнь с основным контейнером
- Sidecar не существует сам по себе: он смыслен только в контексте основного приложения.
- Масштабирование:
- под масштабируется целиком: основное приложение + все его sidecar’ы.
- Перезапуск:
- при перезапуске pod перезапускаются все контейнеры.
- Общая сеть
- Оба контейнера используют один IP-адрес pod’a:
- общение по localhost (127.0.0.1).
- Удобно для:
- локальных прокси,
- шифрования трафика,
- сервис-меша,
- инспекции запросов.
- Общие тома
- Через общие volume’ы:
- основной контейнер и sidecar могут обмениваться файлами, логами, сокетами.
- Это ключевой механизм для:
- лог-агентов;
- агентов метрик;
- обновления конфигов и сертификатов.
Типичные сценарии применения sidecar-контейнеров:
- Логирование
- Основное приложение пишет логи в файл в общий volume.
- Sidecar (Fluent Bit / Vector / Filebeat):
- читает эти файлы;
- отправляет логи в Elasticsearch, Loki, Kafka и т.п.
Пример:
apiVersion: v1
kind: Pod
metadata:
name: go-app-with-logs
spec:
volumes:
- name: logs
emptyDir: {}
containers:
- name: app
image: my-registry/go-api:1.0.0
volumeMounts:
- name: logs
mountPath: /var/log/app
- name: log-agent
image: fluent/fluent-bit:latest
volumeMounts:
- name: logs
mountPath: /var/log/app
- Метрики и наблюдаемость
- Sidecar может:
- собирать метрики приложения (через HTTP, unix-сокет, файлы),
- конвертировать формат,
- пушить в Prometheus Pushgateway или другие системы.
- Либо экспортировать метрики нестандартного компонента в формате, понятном Prometheus.
- Конфигурация, TLS, секреты
- Sidecar периодически:
- подтягивает конфиги или сертификаты из внешней системы (Vault, S3, Git),
- кладет их в общий том.
- Основное приложение:
- читает конфиги/ключи из этого тома,
- может отслеживать изменения (reloading без рестарта pod).
- Прокси и сервис-меш
- Sidecar-прокси (Envoy, Istio, Linkerd):
- перехватывает весь входящий/исходящий трафик приложения;
- реализует:
- mTLS,
- ретраи,
- circuit breaking,
- роутинг, A/B, canary;
- трассировку.
- Приложение на Go:
- работает как обычно,
- о сложной сетевой логике "заботится" sidecar.
- Доменные адаптеры
- Sidecar преобразует протоколы/данные:
- например, основной сервис пишет в локальный unix-сокет,
- sidecar читает и отправляет в удаленный брокер/шину.
Почему sidecar-подход важен:
- Разделение ответственности:
- основной контейнер: бизнес-логика.
- sidecar: инфраструктура, интеграции, кросс-функциональные задачи.
- Повторное использование:
- одинаковые sidecar-образы для разных приложений (логирование, TLS, метрики).
- Упрощение приложений:
- Go-сервис не обязан уметь всё: достаточно HTTP/логов/простых интерфейсов,
- сложные задачи берёт на себя sidecar.
Когда НЕ стоит использовать sidecar:
- Если задача не требует тесного совместного размещения:
- можно вынести в отдельный сервис и ходить по Service/DNS.
- Если sidecar ломает независимое масштабирование:
- возможно, это уже не sidecar, а самостоятельный компонент.
Связь с приложениями на Go:
Примеры реальных практик:
-
Go-сервис + Envoy sidecar:
- Go-сервис слушает на :8080;
- Envoy слушает на :80, терминирует TLS, делает retries, маршрутизацию;
- весь трафик к приложению/от приложения проходит через sidecar.
-
Go-сервис + лог-агент:
- Go пишет структурированные JSON-логи в файл;
- sidecar отправляет их в централизованное хранилище.
-
Go-сервис + cert-renewal sidecar:
- sidecar обновляет TLS-сертификат;
- Go-сервис периодически перечитывает файл без рестарта pod.
Краткая формулировка:
- Sidecar-контейнер — это вспомогательный контейнер в том же pod, который:
- разделяет сеть и volume с основным приложением,
- живет и масштабируется вместе с ним,
- реализует инфраструктурные функции (логи, метрики, прокси, TLS, конфиги и т.п.).
- Это ключевой архитектурный паттерн в Kubernetes, позволяющий держать бизнес-логику простой, а сложное окружение — рядом, но изолированным по ответственности.
Вопрос 23. Есть ли у вас опыт работы с сервис-меш решениями (например, Istio)?
Таймкод: 00:21:34
Ответ собеседника: правильный. Честно сообщает, что сервис-меш решения в продакшене не использовал.
Правильный ответ:
Сама по себе честная фиксация отсутствия опыта — нормальный ответ. Однако в контексте собеседования важно продемонстрировать понимание концепции сервис-меша и того, как он влияет на архитектуру и разработку сервисов (включая Go-приложения).
Кратко о сути service mesh:
Service mesh — это инфраструктурный слой для управления сетевым взаимодействием между сервисами в распределенной системе. Он выносит кросс-срезные сетевые задачи из кода приложений в отдельный слой, реализуемый через:
- data plane: sidecar-прокси (часто Envoy) рядом с каждым pod;
- control plane: центральный компонент (например, Istio Pilot/Galley/istiod), который управляет конфигурацией прокси.
Ключевые возможности service mesh (на примере Istio, Linkerd, Consul Connect):
-
Трафик-менеджмент:
- умный роутинг (по версиям, заголовкам, пользователям);
- canary releases, A/B-тесты;
- постепенные выкаты, blue-green;
- ретраи, timeouts, circuit breaking на уровне сетевой политики, а не приложения.
-
Безопасность:
- mTLS между сервисами "по умолчанию";
- автоматическая выдача и ротация сертификатов;
- авторизация и политика доступа на уровне сервисов и namespace.
-
Наблюдаемость:
- единообразные метрики (latency, error rate, RPS) для всех сервисов;
- распределенный трейсинг (trace-id проходит через sidecar);
- детализированные логи запросов.
-
Политики:
- rate limiting,
- access policies,
- контроль, кто с кем может говорить на уровне L7.
Как это выглядит технически:
- В каждый pod автоматически добавляется sidecar-прокси (часто Envoy).
- Приложение (например, Go-сервис):
- слушает на localhost:порт;
- весь входящий/исходящий трафик проходит через sidecar.
- Control plane:
- раздает конфиг прокси через xDS/gRPC API;
- оператор управляет поведением трафика декларативно (YAML-манифестами), не меняя код.
Влияние на разработку Go-сервисов:
При использовании service mesh:
- В коде Go можно упростить:
- не реализовывать вручную:
- mTLS между сервисами;
- сложные ретраи и backoff’ы;
- canary-логики;
- фокус на корректных HTTP/gRPC endpoint’ах, статусах, idempotency.
- не реализовывать вручную:
- Важно:
- уважать таймауты и контексты (context.Context);
- быть готовым к ретраям (idempotent операции);
- корректно обрабатывать коды ошибок, не завязываться на "магическое" поведение сети;
- прокидывать trace-id и correlation-id (mesh часто добавляет заголовки типа x-request-id, b3, traceparent).
Пример (Go, корректная работа с контекстом и таймаутами):
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://orders/api/v1/orders", nil)
if err != nil {
// handle
}
resp, err := http.DefaultClient.Do(req)
// mesh может ретраить/балансировать, но приложение должно корректно реагировать на timeout/cancel
Если отвечать на интервью:
Даже без практического опыта можно дать зрелый ответ:
- "В продакшене Istio/Linkerd/Consul Connect не использовал, но понимаю модель:
- sidecar-прокси + control plane,
- mTLS, наблюдаемость, политики, умный роутинг.
- Понимаю, как это влияет на разработку сервисов:
- работа с контекстами и таймаутами,
- idempotent операции,
- корректное логирование и трейсинг.
- При необходимости могу быстро на практике поднять mesh в test-окружении и интегрировать с существующими Go-сервисами."
Такой ответ показывает и честность, и достаточное архитектурное понимание темы.
Вопрос 24. В чем разница между Job и CronJob в Kubernetes и можно ли запускать Job без расписания?
Таймкод: 00:21:58
Ответ собеседника: правильный. Корректно объясняет, что Job описывает одноразовую задачу (ограниченную по выполнению), а CronJob по расписанию создает Job; подтверждает, что Job можно запускать без расписания.
Правильный ответ:
Job и CronJob — это механизмы для управления batch- и одноразовыми задачами в Kubernetes, но с разной семантикой жизненного цикла.
Ключевые различия:
- Job
- Назначение:
- Запуск одноразовой или ограниченной по числу попыток задачи до успешного завершения.
- Гарантия: "выполнить N успешных запусков" (обычно 1).
- Поведение:
- Создает один или несколько Pod’ов для выполнения работы.
- Если Pod завершился с ошибкой — Job может перезапустить его (в рамках
backoffLimit). - Когда требуемое количество успешных выполнений достигнуто (
completions), Job помечается как "Completed".
- Использование:
- Миграции БД.
- Разовые batch-задачи.
- Генерация отчетов, подготовка данных.
- Инициализационные задачи, которые не должны висеть постоянно.
- Можно запускать Job без расписания?
- Да, именно так Job и используется по умолчанию.
- Job — это самостоятельный объект, не требующий CronJob.
- Вы создаете Job манифестом или через API, он отрабатывает и завершается.
Пример Job:
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration
spec:
backoffLimit: 3
template:
spec:
restartPolicy: OnFailure
containers:
- name: migrate
image: my-registry/migrator:1.0.0
args: ["up"]
- CronJob
- Назначение:
- Регулярный запуск Job по расписанию, задаваемому в cron-формате.
- Поведение:
- Сам CronJob не выполняет работу.
- По заданному расписанию создает новые Job-объекты.
- Каждый созданный Job:
- запускает свои Pod’ы,
- отрабатывает до завершения,
- помечается как Completed или Failed.
- Использование:
- Периодические задачи:
- очистка старых данных/logs,
- регулярные бэкапы,
- периодическая синхронизация данных.
- Периодические задачи:
- Критически важно:
- Контролировать
successfulJobsHistoryLimitиfailedJobsHistoryLimit, чтобы не захламлять кластер старыми Job. - Настроить
concurrencyPolicy:Allow— разрешать параллельные запуски,Forbid— не запускать новый, пока не завершился предыдущий,Replace— отменять предыдущий и запускать новый.
- Контролировать
Пример CronJob:
apiVersion: batch/v1
kind: CronJob
metadata:
name: cleanup-temp-files
spec:
schedule: "0 3 * * *" # каждый день в 03:00
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 2
template:
spec:
restartPolicy: OnFailure
containers:
- name: cleanup
image: my-registry/cleanup:1.0.0
args: ["--path=/tmp/data", "--max-age=7d"]
- Резюме различий
- Job:
- одноразовая или конечная задача;
- создается и запускается напрямую;
- не требует cron-расписания;
- управляет перезапусками до успешного выполнения.
- CronJob:
- планировщик;
- по расписанию создает Job-ы;
- применяется для периодических задач.
Связь с Go:
- Часто логику разовой миграции/обработки удобно выносить в отдельный Go-бинарник и запускать через Job.
- Для периодических задач:
- вместо "вечного" сервиса с таймером внутри Go-приложения:
- использовать CronJob + маленький Go-скрипт,
- это даёт лучшую наблюдаемость, управление ретраями и изоляцию выполнений.
- вместо "вечного" сервиса с таймером внутри Go-приложения:
Вопрос 25. Что такое сетевые политики в Kubernetes и для чего они используются?
Таймкод: 00:22:44
Ответ собеседника: неполный. Отмечает, что сетевые политики были в проектах, сам почти не писал; понимает их как механизм контроля, кто с кем может взаимодействовать по сети, без раскрытия деталей.
Правильный ответ:
Сетевые политики (NetworkPolicy) в Kubernetes — это декларативный механизм управления сетевым трафиком на уровне L3/L4 между подами и внешними сущностями. Они позволяют задать, какой трафик:
- разрешен или запрещен между подами внутри кластера;
- разрешен из/в namespace;
- разрешен из внешних источников (IP-блоков, ingress-контроллеров и т.п.).
Ключевая идея:
По умолчанию (при включенной поддержке в CNI):
- Если в namespace нет NetworkPolicy:
- все поды могут общаться со всеми (allow all).
- Как только для pod’а появляется хоть одна NetworkPolicy:
- включается модель "deny by default" для тех направлений, которые эта политика покрывает;
- явно нужно описывать, какой ingress/egress трафик разрешен.
Важно: NetworkPolicy — это "разрешающие" правила. Явного "deny" нет: запрет реализуется через отсутствие разрешающего правила при активной политике.
Зависимость от CNI:
- NetworkPolicy — это стандарт Kubernetes API.
- Но их реальная работа зависит от сетевого плагина:
- Calico, Cilium, Weave Net, Kube-router и другие mature-плагины поддерживают NetworkPolicy.
- Flannel "из коробки" NetworkPolicy не реализует (нужна дополнительная обвязка).
- В продакшене выбор CNI часто делают с учетом поддержки политик.
Основные понятия и возможности:
- Селекторы
Политики применяются к подам через:
podSelector:- какие поды защищаем (к кому применяется политика).
namespaceSelector:- какие namespace разрешены/участвуют.
- Комбинации:
- фильтрация по labels подов и namespace.
- Направления трафика
ingress:- определяет, кто может подключаться к выбранным подам.
egress:- определяет, к кому могут подключаться выбранные поды.
- Условия в правилах
Можно указывать:
- поды-источники/получатели по label;
- namespace по label;
- IP-блоки (CIDR, с исключениями);
- порты (TCP/UDP), протоколы.
Базовый пример: запрет всего, кроме трафика от подов с нужным label внутри namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-frontend
namespace: prod
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
Поведение:
- Политика применяется ко всем подам с
app=backendв namespaceprod. - Для них:
- входящий трафик (Ingress) разрешен только:
- от подов с
app=frontendв том же namespace; - на порт 8080/TCP.
- от подов с
- входящий трафик (Ingress) разрешен только:
- Все прочие входящие соединения к backend-подам блокируются (при условии поддержки NetworkPolicy CNI-плагином).
Пример с egress: ограничить исходящий трафик (например, только к БД и внутрь кластера)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: restrict-egress
namespace: prod
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: db
ports:
- protocol: TCP
port: 5432
- to:
- ipBlock:
cidr: 10.0.0.0/16
Это позволяет:
- запретить неконтролируемые внешние вызовы;
- избежать утечек данных;
- сегментировать внутреннюю сеть.
Практические цели использования:
- Сегментация среды:
- разделение dev/stage/prod;
- ограничение общения между сервисами разных доменов/команд.
- Zero trust внутри кластера:
- не считать, что "если внутри кластера — значит доверенный".
- Защита критичных сервисов:
- базы данных принимают трафик только от определенных приложений;
- административные endpoints доступны только с utility/ops-подов.
- Минимизация blast radius:
- скомпрометированный pod не может "ходить ко всем".
Типичные best practices:
- Включать политики для продакшн namespace:
- стартовать с "default deny" и постепенно открывать нужные маршруты.
- Использовать метки:
team,tier(frontend/backend/db),env(dev/prod);- строить политики вокруг понятных доменных групп.
- Документировать и тестировать:
- использовать
kubectl exec,curl,netcatдля проверки; - иметь staging для обкатки политик.
- использовать
Связь с Go-сервисами:
При включенных NetworkPolicy разработчик и DevOps должны:
- Ясно понимать, к каким сервисам/БД Go-приложение должно иметь доступ.
- Настроить политики так, чтобы:
- разрешить только нужный трафик;
- не ломать health-check и вспомогательные зависимости.
- В коде:
- корректно обрабатывать ошибки сетевого уровня:
- timeouts,
- connection refused,
- no route to host.
- иметь явные конфиги для целевых endpoint’ов (DNS/Service).
- корректно обрабатывать ошибки сетевого уровня:
Если отвечать на интервью:
Сильный ответ, даже при ограниченном hands-on, звучит так:
- "NetworkPolicy — это L3/L4 firewall для pod’ов. Они позволяют декларативно описать, кто с кем и по каким портам может общаться. Работают на базе CNI-плагина (Calico/Cilium и т.д.). Используются для сегментации, повышения безопасности и реализации принципа минимально необходимых прав. В продакшене разумный подход — default deny для критичных namespace и явные allow-политики для нужных направлений (ingress/egress)."
Такое объяснение показывает глубокое понимание роли сетевых политик, даже если пишет их чаще платформа-команда.
Вопрос 26. Разрешено ли по умолчанию сетевое взаимодействие между подами из разных namespace при использовании CNI-плагина, если сетевые политики не заданы?
Таймкод: 00:23:08
Ответ собеседника: неправильный. Колеблется, не даёт чёткого ответа; рассуждает про уникальность IP, предполагает, что доступ может быть, но не подтверждает, что без сетевых политик трафик между namespace обычно не ограничен.
Правильный ответ:
Кратко и по сути:
- В типичной конфигурации Kubernetes + CNI (Calico, Flannel, Cilium и др.), при отсутствии NetworkPolicy:
- сетевое взаимодействие между всеми Pod’ами во всех namespace разрешено по умолчанию.
- Namespace сам по себе не является сетевым изолятором:
- это логическая граница ресурсов и политики, но не firewall.
- Ограничение трафика между Pod’ами/namespace появляется только при:
- наличии корректно настроенных NetworkPolicy,
- и поддержке их со стороны CNI-плагина.
Подробности:
- Модель сети Kubernetes по умолчанию
Базовые принципы (официальная модель):
- У каждого Pod свой уникальный IP.
- Любой Pod должен иметь возможность:
- напрямую (без NAT) обратиться к любому другому Pod в кластере по его IP.
- Это касается:
- подов в том же namespace,
- подов в других namespace — ограничений нет.
Именно так и работают большинство CNI по умолчанию:
- Calico (без политик),
- Flannel,
- Cilium (без политик),
- Weave Net и др.
- Роль NetworkPolicy
NetworkPolicy вводит правила L3/L4:
- определяет, какой ingress/egress-трафик разрешен для подов с определенными метками;
- как только для пода появляется хотя бы одна политика:
- всё, что не разрешено явно — блокируется (в рамках покрываемых направлений и условий);
- можно реализовать:
- запрет трафика между namespace;
- ограничение "frontend может ходить к backend, но не к db напрямую";
- default deny для продакшн namespace и точечные allow.
Без NetworkPolicy:
- действует модель "allow all" внутри кластера.
- Как ответить уверенно на интервью
Корректная, зрелая формулировка:
- "По умолчанию, без сетевых политик, Kubernetes считает сеть плоской и доверенной: pod’ы из разных namespace могут свободно общаться друг с другом по IP/Service. Namespace не даёт сетевой изоляции сам по себе. Для ограничения трафика используются NetworkPolicy при условии, что CNI-плагин их поддерживает."
- Практический вывод для production
- Если нужны границы между командами, средами или сервисами:
- необходимо явно включать и настраивать NetworkPolicy.
- Иначе:
- любой pod из dev-namespace потенциально может ходить к pod’ам в prod-namespace (если нет других внешних ограничений).
Это один из ключевых "подводных камней" безопасности Kubernetes, и его важно понимать уверенно.
Вопрос 27. С какими дистрибутивами Linux вы работали на практике?
Таймкод: 00:24:48
Ответ собеседника: правильный. В основном работал с Debian/Ubuntu, дистрибутивы семейства RHEL использовал мало.
Правильный ответ:
Полноценный ответ на этот вопрос должен не только перечислять дистрибутивы, но и показывать переносимость навыков и понимание различий между семействами систем.
Пример развёрнутого ответа:
На практике работал преимущественно со следующими дистрибутивами:
-
Debian / Ubuntu:
- Основная рабочая среда для:
- разворачивания приложений,
- Kubernetes-нод,
- CI/CD раннеров,
- сервисов мониторинга и логирования.
- Хорошее знание:
- apt/ecosystem (apt, dpkg),
- структуры конфигураций,
- systemd-юнитов,
- сетевой настройки (netplan/ifupdown, resolvconf/systemd-resolved),
- базового hardening (SSH, sudo, firewall, audit).
- Основная рабочая среда для:
-
Дистрибутивы семейства RHEL (CentOS, Rocky, AlmaLinux) — при необходимости:
- Установка и базовая эксплуатация:
- yum/dnf,
- firewalld/iptables,
- SELinux (понимание контекстов, умение диагностировать типичные проблемы: доступ к портам, файлам, HTTP-сервисам).
- Работа в окружениях, где стандартизированы именно RHEL-совместимые образы.
- Установка и базовая эксплуатация:
Ключевой момент: опыт с конкретным дистрибутивом важен, но ещё важнее:
- глубокое понимание Linux как платформы:
- процессы, systemd, cgroups;
- сеть (TCP/IP, маршрутизация, DNS, firewall);
- storage (LVM, RAID, файловые системы, монтирование, производительность дисков);
- безопасность (права, capabilities, chroot/namespace, audit);
- умение адаптироваться под любой дистрибутив:
- отличия в пакетных менеджерах,
- в layout конфигов,
- политиках безопасности (SELinux vs AppArmor),
- дефолтных настройках ядра и сетевых параметрах.
В контексте Go и Kubernetes:
- Умение:
- собирать и запускать Go-сервисы в минимальных образах (distroless/alpine/debian-slim);
- интегрировать их с системными service manager’ами и контейнерными рантаймами;
- настраивать ноды Kubernetes (Debian/Ubuntu/RHEL-based) под требования kubelet, CNI, CSI;
- диагностировать проблемы на уровне ОС (network, DNS, MTU, ulimit, OOM, дисковая подсистема), влияющие на поведение контейнеризованных Go-приложений.
Даже если основной практический опыт — Debian/Ubuntu, важно показать:
- понимание, что переход на RHEL- и другие семейства — это вопрос адаптации к окружению, а не смены парадигмы;
- готовность работать с тем стеком, который принят в инфраструктуре заказчика.
Вопрос 28. Где и как на Linux-сервере можно настраивать параметры ядра, например полностью отключить IPv6?
Таймкод: 00:25:11
Ответ собеседника: неправильный. Говорит об общей директории /etc и не называет sysctl и реальные механизмы настройки параметров ядра, фактически демонстрируя отсутствие практического опыта.
Правильный ответ:
Для настройки параметров ядра в Linux используется механизм sysctl и связанные с ним конфигурационные файлы, а также (при необходимости) параметры загрузки ядра и конфиги конкретных сервисов/дистрибутива. Важно понимать различие между:
- временными изменениями (до перезагрузки),
- постоянными (переживают reboot),
- и тем, какие параметры можно безопасно менять на живой системе.
Базовый инструмент: sysctl
- Просмотр текущих параметров ядра
- Все runtime-параметры находятся в:
- /proc/sys
- Примеры:
- /proc/sys/net/ipv4/ip_forward
- /proc/sys/net/ipv6/conf/all/disable_ipv6
Посмотреть значение:
sysctl net.ipv4.ip_forward
# или
cat /proc/sys/net/ipv4/ip_forward
- Временное изменение параметров (до перезагрузки)
Используется утилита sysctl или запись в /proc/sys:
-
Пример: включить IP forward:
sysctl -w net.ipv4.ip_forward=1
# или
echo 1 > /proc/sys/net/ipv4/ip_forward -
Эти изменения действуют до перезагрузки системы или переконфигурации.
- Постоянные изменения (переживают reboot)
Основной файл:
- /etc/sysctl.conf
Дополнительная директория:
- /etc/sysctl.d/*.conf
Рекомендуемая практика — использовать /etc/sysctl.d:
- Например, создать файл:
- /etc/sysctl.d/99-custom.conf
Пример содержимого:
net.ipv4.ip_forward = 1
net.core.somaxconn = 1024
fs.file-max = 1048576
Применить без reboot:
sysctl -p /etc/sysctl.d/99-custom.conf
Если вызвать просто:
sysctl -p
— будет перечитан /etc/sysctl.conf по умолчанию.
Отключение IPv6: корректный подход
Важно различать:
- логическое отключение IPv6 на уровне интерфейсов,
- полное отключение поддержки IPv6 в стеке ядра.
- Отключение IPv6 через sysctl (на большинстве дистрибутивов)
Добавляем в /etc/sysctl.d/disable-ipv6.conf, например:
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
Иногда добавляют ещё:
net.ipv6.conf.lo.disable_ipv6 = 1
Применяем:
sysctl -p /etc/sysctl.d/disable-ipv6.conf
Это:
- запрещает использование IPv6 на интерфейсах для уровня IP;
- для большинства задач этого достаточно.
- Полное отключение IPv6 через параметры ядра (grub)
В некоторых системах или для жесткого требования политики применяют параметр ядра:
- В конфиг загрузчика (например, GRUB):
- редактируем /etc/default/grub:
GRUB_CMDLINE_LINUX="ipv6.disable=1 ..."
- редактируем /etc/default/grub:
- Затем:
update-grub
reboot
Параметр:
- ipv6.disable=1
- полностью отключает стек IPv6 в ядре.
Использовать этот подход имеет смысл только при чётком понимании последствий:
- возможны зависимости сервисов, systemd units, DNS и т.п.
- Особенности в разных дистрибутивах
- Debian/Ubuntu:
- sysctl: /etc/sysctl.conf, /etc/sysctl.d/*.conf
- grub: /etc/default/grub → update-grub
- RHEL/CentOS/Rocky/Alma:
- sysctl: те же пути, плюс можно использовать /etc/sysctl.d
- grub2:
grub2-mkconfig -o /boot/grub2/grub.cfg
- Некоторые дистрибутивы имеют дополнительные network-конфиги (например, netplan, NetworkManager), но для параметров ядра базовый механизм один: sysctl + параметры ядра.
Практические примеры полезных sysctl-настроек (в контексте highload/Go/Kubernetes):
-
Увеличить лимит открытых сокетов:
net.core.somaxconn = 1024
net.ipv4.ip_local_port_range = 1024 65000 -
Тюнинг для большого числа соединений (reverse proxy, Kafka, Go API-шлюз):
net.core.netdev_max_backlog = 4096
net.ipv4.tcp_max_syn_backlog = 4096 -
Для Kubernetes-нод (пример):
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
Важно:
- Любые изменения sysctl должны быть осознанными:
- проверять влияние на безопасность,
- тестировать под нагрузкой,
- документировать.
Краткая формулировка для интервью:
- "Параметры ядра в Linux настраиваются через sysctl:
- временно: sysctl -w или запись в /proc/sys/...;
- постоянно: /etc/sysctl.conf или файлы в /etc/sysctl.d с последующим sysctl -p.
- Для полного отключения IPv6 можно использовать sysctl-параметры disable_ipv6 или параметр ядра ipv6.disable=1 через grub.
- В продакшене изменения делаются через /etc/sysctl.d/* с версионированием и документированием."
Вопрос 29. Как можно восстановить или изменить пароль root, если это единственная учётная запись и доступ утерян?
Таймкод: 00:26:15
Ответ собеседника: неправильный. Говорит общими фразами про системные файлы с пользователями, не называет конкретные файлы и методы (single-user/recovery mode, initramfs, изменение через chroot и т.п.), затем признаёт, что не знает и обычно ищет в интернете.
Правильный ответ:
Важный принцип: смена пароля root без знания текущего возможна только при наличии физического или административного доступа к системе (консоль, управление загрузчиком, доступ к диску). Если злоумышленник может это сделать — машина считается скомпрометированной на уровне физической безопасности.
Ниже — типовой, практический и безопасный подход (на примере систем с GRUB и Linux семейства Debian/Ubuntu/RHEL). Конкретные шаги могут немного отличаться по дистрибутиву, но общая идея одинакова.
Ключевые методы:
- Загрузка в single-user / rescue / emergency режим.
- Монтирование корневой файловой системы с правами записи.
- Смена пароля через
passwdили правка файлов/etc/shadow(менее предпочтительно). - Учет безопасности: ограничение такой возможности через пароль на GRUB, шифрование диска и т.п.
Разбор по шагам.
- Вход в режим восстановления через GRUB (single-user / init=/bin/bash)
Типичный сценарий (виртуалка/сервер с доступом к консоли):
- Перезагрузить сервер.
- На экране GRUB:
- выбрать нужный пункт,
- нажать
e(edit) для редактирования параметров загрузки.
- Найти строку, начинающуюся с:
linux,linux16илиlinuxefi— в ней параметры ядра.
- В конец строки добавить один из вариантов:
- вариант 1:
single - вариант 2:
init=/bin/bash
- вариант 1:
- Загрузиться с изменёнными параметрами:
- обычно
Ctrl+XилиF10.
- обычно
Дальше возможны два основных потока.
Вариант с init=/bin/bash:
-
Вы попадаете в shell root без запроса пароля, но:
- корень часто смонтирован только для чтения.
-
Перемонтировать корень в режим RW:
mount -o remount,rw / -
Сменить пароль root:
passwdВвести новый пароль дважды.
-
Синхронизировать на диск:
sync -
Перезагрузиться:
exec /sbin/reboot -f
После этого можно входить под root с новым паролем.
Вариант с single или systemd.unit=rescue.target:
- На многих системах single-user/rescue режим по умолчанию:
- либо входит как root без пароля,
- либо запрашивает пароль root (зависит от настроек).
- Если попали в shell root:
- аналогично, при необходимости ремонтируем
/в rw; - выполняем
passwd.
- аналогично, при необходимости ремонтируем
- Использование live-cd / rescue-среды и chroot
Если GRUB защищён или нужно работать очень аккуратно:
-
Загрузиться с live-образа (например, Ubuntu live, rescue ISO).
-
Определить корневой раздел основой системы:
lsblk
blkid -
Монтировать корень:
mount /dev/sdXn /mnt -
При необходимости смонтировать /boot, /boot/efi, /dev, /proc, /sys:
mount --bind /dev /mnt/dev
mount --bind /proc /mnt/proc
mount --bind /sys /mnt/sys -
Войти в chroot:
chroot /mnt -
Сменить пароль root:
passwd -
Выйти, размонтировать, перезагрузить.
- Прямая правка /etc/shadow (крайний случай)
Теоретически можно:
- отредактировать
/etc/shadow, обнулив хэш пароля root:- заменить хэш на пустое поле или
*/!в контролируемых сценариях, - либо подставить заранее сгенерированный хэш (
openssl passwd -6,mkpasswdи т.п.).
- заменить хэш на пустое поле или
- Но:
- это более рискованно,
- легко испортить формат;
- предпочтительнее использовать
passwd.
- Важные замечания по безопасности
- Все описанные методы требуют:
- физического доступа или доступа к консоли (IPMI/iLO/DRAC/virt-консоль),
- или доступа к среде виртуализации/облака (для управления загрузкой или подмонтирования диска).
- Если злоумышленник имеет такой доступ:
- он может сбросить root-пароль;
- поэтому:
- защищают GRUB паролем;
- шифруют корневой диск (LUKS, dm-crypt);
- ограничивают доступ к гипервизору и out-of-band management.
- В корпоративных окружениях вместо ручного сброса root:
- часто используют:
- централизованные механизмы аутентификации (LDAP/AD, sudo),
- break-glass аккаунты с регламентированным доступом,
- аудит подобных действий.
- часто используют:
Краткая корректная формулировка:
- "Если есть физический/консольный доступ, пароль root можно сбросить:
- загрузившись в single-user/rescue режим (через редактирование параметров GRUB),
- или через init=/bin/bash с перемонтированием / в rw,
- либо через live-образ и chroot в установленную систему,
- затем используя
passwdдля установки нового пароля. - В продакшене такие операции регламентируются и защищаются (GRUB-пароль, шифрование диска, контроль доступа к консоли), поскольку возможность сброса root напрямую связана с моделью угроз."
Вопрос 30. С какими файловыми системами Linux вы знакомы и что с ними делали?
Таймкод: 00:27:55
Ответ собеседника: неправильный. Признает, что осознанно с файловыми системами не работал, практического понимания выбора и особенностей FS не демонстрирует.
Правильный ответ:
От разработчика и инженера, работающего с продакшн-инфраструктурой, ожидается не просто знание названий файловых систем, а понимание:
- где какую FS уместно применять,
- как её смонтировать и проверить,
- как ее особенности влияют на базы данных, брокеры, логи и Go-сервисы под нагрузкой.
Ниже структурированный ответ с акцентом на практику.
Основные файловые системы и опыт работы с ними:
- ext4
- Наиболее распространенная и зрелая FS в Linux.
- Использование:
- корневые файловые системы,
- каталоги с логами,
- стандартные persistent-тома под приложения,
- часто как дефолт для облачных дисков (EBS, GCE PD, etc.).
- Практика:
- разметка и создание:
mkfs.ext4 /dev/sdX - Монтирование:
mkdir -p /data
mount /dev/sdX /data - Настройка в /etc/fstab:
/dev/sdX /data ext4 defaults,noatime 0 2 - Использование опций:
- noatime — уменьшение нагрузки на запись для highload-систем.
- разметка и создание:
- Особенности:
- баланс стабильности и производительности;
- подходит для большинства сценариев, включая БД и брокеров, при правильных настройках и железе.
- XFS
- Высокопроизводительная журналируемая FS, хорошо подходит для больших объемов и параллельных операций.
- Часто используется:
- в RHEL/Alma/Rocky по умолчанию,
- под базы данных, хранилища логов, брокеры.
- Практика:
- создание:
mkfs.xfs /dev/sdX - монтирование с нужными опциями (пример):
/dev/sdX /var/lib/postgresql xfs defaults,noatime 0 2
- создание:
- Особенности:
- отлично масштабируется для больших файловых систем;
- рекомендуется к использованию для многих продакшн-нагрузок (DB, Kafka), особенно в RHEL-экосистеме;
- не поддерживает уменьшение размера онлайн, это нужно учитывать при планировании.
- Btrfs
- Copy-on-write файловая система с поддержкой:
- снапшотов,
- subvolume,
- checksumming данных и метаданных,
- встроенного RAID.
- Практическое использование:
- выборочно: для окружений, где важны лёгкие снапшоты и тестовые среды;
- в продакшн под критичные БД — с осторожностью (вопрос зрелости и профиля нагрузки).
- Уместно упомянуть:
- понимание концепций CoW, снапшотов, компрессии;
- но для "тяжелых" БД и брокеров чаще используют ext4/XFS.
- ZFS (на Linux через ZFS on Linux)
- Богатая функционально FS и volume manager:
- снапшоты, репликация,
- checksumming, self-healing,
- пулы устройств, RAID-Z.
- Использование:
- для систем, где важны:
- надёжность данных,
- защита от битых блоков,
- удобные снапшоты и репликация (backup, staging).
- для систем, где важны:
- Особенности:
- требует понимания модели (ARC, ZIL, log/ cache устройства);
- не всегда подходит в контейнерных средах без аккуратной настройки;
- лицензирование делает её не дефолтным выбором во многих дистрибутивах.
- NFS и сетевые FS
- Использование:
- общие директории,
- shared storage между нодами,
- зачастую как backend для Kubernetes RWX-томов.
- Практика:
- монтирование:
mount -t nfs nfs-server:/export/data /mnt/data - Учет задержек, отказоустойчивости сети, настроек кеширования.
- монтирование:
- Важно:
- понимать, что NFS — сетевой уровень + локальная FS,
- для stateful-систем с высокими требованиями к latency может быть проблематичен.
- Local Persistent Volumes, tmpfs
- Local PV:
- привязка к локальному диску ноды;
- применяются для высокопроизводительных, но "зонально привязанных" сервисов (Kafka, ClickHouse и т.п.).
- tmpfs:
- в памяти, для быстрых, но не персистентных данных:
mount -t tmpfs -o size=1G tmpfs /mnt/tmp - для кешей, временных файлов, тестов.
- в памяти, для быстрых, но не персистентных данных:
Практические действия, которые важно уметь:
- Разметка и создание файловых систем
- Использование fdisk/parted для создания разделов.
- Создание FS:
- mkfs.ext4, mkfs.xfs, mkfs.btrfs, mkfs.xfs -f /dev/... и т.п.
- Монтирование и автоматизация
- Ручное монтирование:
mount -t ext4 /dev/sdX /data - /etc/fstab:
- использование UUID:
UUID=<uuid> /data ext4 defaults,noatime 0 2
- использование UUID:
- Диагностика и контроль
- df -h — использование диска.
- du -sh — размер каталогов.
- fsck — проверка и восстановление для ext4/xfs (с учётом особенностей: для XFS — xfs_repair).
- iostat, sar, atop — анализ IO под нагрузкой.
- Влияние файловой системы на приложения (Go, БД, брокеры)
- Для баз данных и брокеров (PostgreSQL, Kafka, RabbitMQ, MongoDB):
- выбирать зрелые FS (ext4, XFS),
- уделять внимание:
- отключению лишних atime-записей,
- корректной настройке write cache/барьеров,
- надёжности underlying storage (RAID, replication).
- Для Go-сервисов:
- логирование, временные файлы:
- понимать, что медленный диск = рост latency;
- при интенсивном логировании — вынос логов на отдельный том, настройка ротации.
- логирование, временные файлы:
- В Kubernetes:
- PV/PVC поверх ext4/XFS/NFS/CSI;
- осознавать, что тип файловой системы и storage backend влияет на:
- производительность,
- надежность,
- поведение stateful-приложений.
Краткая формулировка для интервью:
- "Работал с ext4 и XFS как основными FS для продакшн: создавал тома, настраивал монтирование через /etc/fstab, тюнил опции (noatime и др.), использовал для БД и брокеров. Понимаю особенности NFS как сетевого хранилища, его ограничения по latency и надежности. Знаком с концепциями Btrfs и ZFS (снапшоты, CoW, self-healing) и выбираю их осознанно под конкретные задачи. При проектировании инфраструктуры и Kubernetes-хранилища учитываю влияние выборa FS на производительность и надежность приложений, включая базы данных и высоконагруженные Go-сервисы."
Вопрос 31. Приходилось ли вам восстанавливать повреждённые файловые системы после сбоев (краша гипервизора, падения виртуалки)?
Таймкод: 00:28:51
Ответ собеседника: правильный. Честно отмечает, что с аварийным восстановлением повреждённых файловых систем не сталкивался.
Правильный ответ:
Честный ответ об отсутствии практики — это нормально. Но на сильном уровне важно понимать, как такие ситуации решаются, какие инструменты используются и как минимизировать риск потери данных для продакшн-систем, включая БД и брокеры.
Ниже — сжатое, но содержательное описание подхода.
Общие принципы:
- Не начинать с "чинить", а:
- зафиксировать состояние,
- не усугубить повреждение,
- максимально сохранить возможность восстановления.
- Любое вмешательство в поврежденную FS может:
- как восстановить,
- так и безвозвратно уничтожить часть данных.
- В продакшене:
- всегда опираться на бэкапы и репликацию как основной механизм восстановления;
- fsck и ручное восстановление — это "best effort", а не стратегия.
Типовой подход к восстановлению (ext4/XFS как наиболее частые):
- Изоляция и диагностика
- Не монтировать подозрительный том в rw без проверки.
- Проверить системные логи:
- dmesg,
- /var/log/syslog, /var/log/messages.
- Определить:
- тип файловой системы (blkid, lsblk);
- есть ли аппаратные ошибки (SMART через smartctl, лог гипервизора/СХД).
- Создание образа (по возможности)
Для критичных данных:
- Снять поблочный образ диска или раздела:
dd if=/dev/sdX of=/backup/sdX.img bs=4M conv=noerror,sync - Либо использовать:
ddrescueдля работы с проблемными носителями.
- Работать сначала с копией:
- это позволяет повторять попытки восстановления разными методами.
- Проверка и восстановление ext4 (fsck.ext4)
-
Если файловая система не монтируется или монтируется только ro, выполняем проверку:
- Убедиться, что том размонтирован:
umount /dev/sdX1- Запустить fsck:
fsck.ext4 -f /dev/sdX1 -
Варианты:
- автоматически исправить ошибки (на тестовых/менее критичных системах),
- на продакшене — более осторожно, с логированием решений.
-
После успешного fsck:
- повторное монтирование,
- проверка целостности приложений, БД (внутренние проверки, consistency check).
- Проверка и восстановление XFS
-
XFS нельзя чинить онлайн через fsck.ext4 — используется свой инструментарий:
-
Проверка (только на размонтированной FS):
umount /dev/sdX1
xfs_repair -n /dev/sdX1 # -n = no modify, посмотреть, что будет делать -
Если вывод приемлем:
xfs_repair /dev/sdX1
-
-
При серьёзных повреждениях:
- возможна потеря части дерева каталогов (перемещение в lost+found),
- требуется ручной разбор.
- Работа с повреждёнными данными приложений (БД, брокеры)
Даже если FS восстановлена успешно, критичные сервисы (PostgreSQL, Kafka, etc.) могут иметь:
- битые WAL/commit-логи,
- неконсистентные сегменты.
Правильная стратегия:
- Для БД:
- использовать встроенные механизмы восстановления:
- PostgreSQL: replay WAL, PITR;
- MySQL: InnoDB recovery.
- При тяжелых повреждениях:
- поднять из бэкапа + применить журналы, если возможно.
- использовать встроенные механизмы восстановления:
- Для Kafka и брокеров:
- проверка consistency,
- при необходимости — пересоздание реплик из живых нод,
- опора на репликацию, а не на "починку бинарников".
- Профилактика: что важно делать заранее
Сильный ответ обязательно упоминает профилактику:
- Регулярные бэкапы:
- snapshot’ы на уровне СХД / LVM / ZFS / Ceph / cloud-volume;
- логические бэкапы БД (pg_dump, logical replication).
- Репликация:
- leader/replica для БД и брокеров, чтобы можно было "выкинуть" убитый инстанс.
- Использование зрелых файловых систем:
- ext4/XFS с корректной конфигурацией,
- отключение опасных флагов mount (например, data=writeback без понимания).
- Корректное завершение работы ВМ/нод:
- избегать "жёсткого убийства" гипервизором без ACPI shutdown;
- иметь защиту от внезапного отключения питания (UPS).
- Мониторинг:
- слежение за SMART,
- ошибками IO,
- latency диска,
- early warning для деградирующих дисков/СХД.
Краткая формулировка для интервью:
- "Даже если лично не чинил сильно побитые FS, я понимаю стандартный подход:
- не монтировать в rw,
- по возможности снять образ,
- для ext4 использовать fsck.ext4, для XFS — xfs_repair (сначала в no-op режиме),
- после восстановления проверять консистентность данных приложений.
- В продакшене ключевой упор — на бэкапы, репликацию и корректную работу хранилища, чтобы не доводить до сложного forensics-восстановления."
Вопрос 32. Как посмотреть текущее потребление ресурсов (CPU, RAM) в Linux?
Таймкод: 00:29:24
Ответ собеседника: правильный. Упоминает использование команды top для мониторинга ресурсов.
Правильный ответ:
Для оценки текущего потребления CPU и памяти в Linux есть несколько уровней инструментов: базовые встроенные утилиты, более удобные интерактивные интерфейсы и метрики для систем мониторинга. Важно не только знать top, но и понимать, как читать вывод и чем дополнять.
Базовые интерактивные утилиты:
- top
- Быстрый и доступный инструмент "из коробки".
- Показывает:
- общую загрузку CPU (us, sy, id, wa и т.п.),
- использование памяти (total, used, free, buff/cache),
- список процессов с их:
- %CPU,
- %MEM,
- RES (фактическое использование RAM),
- командой.
- Полезные моменты:
- сортировка по CPU/MEM (
Shift+P,Shift+M); - фильтрация по имени процесса;
- понимание, что buff/cache — не "потерянная" память.
- сортировка по CPU/MEM (
- htop
- Удобная улучшенная версия top (часто ставится отдельным пакетом).
- Возможности:
- цветной вывод,
- наглядные графики CPU по ядрам и RAM,
- дескрипторы, команды, дерево процессов,
- интерактивное завершение/renice процессов.
- Рекомендован как ежедневный инструмент для ручной диагностики.
- atop
- Более подробный инструмент:
- CPU, память, диск, сеть;
- может работать в режиме записи истории.
- Удобен для пост-анализа: "что происходило час назад".
Специализированные утилиты:
- free
-
Краткая сводка по памяти:
free -h -
Показывает:
- total, used, free;
- отдельные столбцы для buff/cache;
-
Важно понимать:
- buff/cache — это используемая, но освобождаемая память;
- "доступная" память — это не только "free", но и часть cache.
- vmstat
-
Дает обзор:
- процессы (r — runnable, b — blocked),
- память,
- swap,
- IO,
- системные прерывания, context switch,
- нагрузка на CPU.
vmstat 1 -
Полезно для диагностики общей загруженности системы, а не только одного процесса.
- ps
-
Для точечного анализа конкретных процессов:
ps aux --sort=-%cpu | head
ps aux --sort=-%mem | head -
Показывает:
- %CPU, %MEM,
- RSS (реальная память),
- команду процесса.
- mpstat, iostat, sar (из пакета sysstat)
- mpstat:
- загрузка CPU по ядрам.
- iostat:
- нагрузка на диски.
- sar:
- исторические метрики (если включен сбор).
В контексте контейнеров и Kubernetes:
- На хосте:
- top/htop покажут процессы containerd/docker/kubelet и сами workload-процессы.
- Внутри пода:
- можно использовать те же утилиты (если они установлены) или:
kubectl top pod
kubectl top node - при установленном metrics-server.
- можно использовать те же утилиты (если они установлены) или:
- Для production:
- используются системы мониторинга:
- Prometheus + node_exporter + cAdvisor,
- Grafana для визуализации.
- используются системы мониторинга:
Связь с Go-приложениями:
- При отладке нагрузки Go-сервиса:
- смотреть %CPU, рост RSS,
- использовать pprof для анализа CPU/heap,
- связывать системные метрики (top/htop/vmstat) с профилями приложения.
Краткая формулировка:
- "Минимальный набор:
- top или htop — интерактивный просмотр процессов и их CPU/RAM,
- free -h — обзор использования памяти,
- ps aux --sort=-%cpu/-%mem — найти прожорливые процессы.
- Для глубокой диагностики: vmstat, iostat, mpstat, atop/sar и, в контейнерных средах, kubectl top + Prometheus/Grafana."
Вопрос 33. Что показывает параметр load average (LA) и как интерпретировать ситуацию с очень большим значением при невысокой загрузке CPU?
Таймкод: 00:29:42
Ответ собеседника: неполный. Говорит, что LA — это средняя нагрузка за периоды времени и что большое значение при низкой текущей загрузке может означать сильную нагрузку в прошлом; не объясняет, что LA — это среднее количество процессов в run queue и в состоянии ожидания I/O, не связывает с количеством ядер и не раскрывает причины высокого LA при низком CPU.
Правильный ответ:
Load average в Linux показывает не "процент загрузки CPU", а среднее число процессов (и потоков), находящихся:
- в состоянии выполнения (running),
- в очереди на выполнение (runnable),
- в состоянии непрерываемого ожидания (D-state), как правило, блокирующего I/O (диск, NFS и т.п.).
Это усреднённое значение за три периода:
- 1 минута,
- 5 минут,
- 15 минут.
Ключевые моменты:
- Как правильно интерпретировать load average
- Если очень грубо:
- LA ≈ "сколько задач в среднем одновременно хотели/пытались выполняться".
- Важнейший фактор — количество CPU-ядер:
- Для 1 CPU:
- LA ≈ 1 → система загружена, но не перегружена.
- LA значительно > 1 → есть очередь, задачи ждут CPU или I/O.
- Для 4 CPU:
- LA ≈ 4 → нормальная полная загрузка.
- LA ≈ 8 → в среднем вдвое больше runnable/blocked задач, чем ядер.
- Для 1 CPU:
- Правило:
- Сравнивайте load average с количеством логических CPU:
- LA / (кол-во CPU) ≈ коэффициент загрузки/очереди.
- Сравнивайте load average с количеством логических CPU:
Пример:
- 8 ядер, LA = 8 → система загружена, но не обязательно перегружена.
- 8 ядер, LA = 40 → много задач ждут (CPU или I/O), система в стрессе.
- Почему load average может быть большим при низкой загрузке CPU
Это важный диагностический сценарий.
Высокий LA при низком %CPU обычно означает:
- процессы "застряли" в непрерываемых состояниях (D-state),
- они ждут:
- медленный диск (IO wait),
- NFS/сетевое хранилище,
- залоченный ресурс ядра или драйвер,
- проблемы с SAN/RAID/бэкендом;
- эти процессы учитываются в load average, хотя не жрут CPU:
- они "в очереди" или "висят", но не вычисляют.
Типичные причины:
- Медленный или деградировавший диск:
- большое количество процессов в D-state;
- высокая IO latency, видно через:
- iostat,
- pidstat -d,
- dmesg (ошибки контроллеров, timeouts).
- Проблемы с NFS/сетевым хранилищем:
- зависшие I/O-операции,
- процессы ждут ответа от удаленного сервера.
- Лок, contention:
- массовый доступ к одному ресурсу (например, log-файлу на медленном диске).
- Некорректные лимиты или проблемы со scheduler’ом в специфичных случаях.
Как проверить:
- top / htop:
- смотреть:
- load average,
- %wa (iowait),
- процессы в D.
- смотреть:
- iostat -x:
- загрузка и latency дисков.
- ps:
ps aux | awk '$8 ~ /D/ { print }'- список процессов в непрерываемом ожидании.
- dmesg / syslog:
- искать ошибки I/O, timeouts.
- Высокий load average при высоком CPU
Классический сценарий:
- Много CPU-bound задач:
- LA высокий,
- CPU загружен (us+sy близко к 100% по ядрам).
- Это нормальная ситуация при вычислительных нагрузках:
- вопрос только в том, соответствует ли это ожиданиям.
- Практическая диагностика (пошагово)
Если видим:
- LA >> кол-ва CPU,
- CPU при этом частично свободен или основная часть времени в iowait:
Действуем:
- Проверяем диски:
- iostat -x, наличие больших await/svctm, %util=100%.
- Проверяем D-state процессы:
- ps, top с отображением статуса.
- Проверяем NFS/сетевые FS:
- монтирование, доступность серверов.
- Проверяем блокировки в приложении:
- возможно, много процессов ждут один и тот же ресурс.
- Связь с Go-сервисами и продакшн-системами
Для Go и высоконагруженных сервисов:
- Высокий LA + низкий CPU:
- часто указывает на:
- блокирующий доступ к диску (логирование в sync-режиме на медленный диск),
- проблемы с внешним хранилищем (NFS/S3-fuse),
- contention при доступе к локальным ресурсам.
- часто указывает на:
- Важно:
- использовать буферизованное логирование,
- следить за тем, куда и как пишутся логи,
- не зависеть от медленных сетевых FS в горячих путях.
- Краткая, корректная формулировка для интервью
- Load average — это среднее количество процессов, которые:
- выполняются,
- ждут CPU,
- либо находятся в непрерываемом ожидании I/O.
- Его нужно интерпретировать относительно числа CPU-ядер.
- Большой LA при низком CPU обычно означает не "была нагрузка в прошлом", а:
- текущие проблемы с I/O или блокировками:
- много процессов ждут диска или сетевого хранилища,
- поэтому учитываются в LA, но не расходуют CPU.
- текущие проблемы с I/O или блокировками:
Вопрос 34. Что обозначает поле "Shared" (shmem) в выводе free -m/-h?
Таймкод: 00:31:03
Ответ собеседника: неправильный. Пытается трактовать как "расшаренную память между процессами", но без привязки к shared memory segments и tmpfs, без точности в определении.
Правильный ответ:
Поле "Shared" (или "shmem") в выводе команды free показывает объем памяти, используемой для разделяемых (shared) страниц, в первую очередь:
- разделяемой памяти (System V/Posix shared memory),
- tmpfs (включая /dev/shm и другие файловые системы на базе tmpfs),
- некоторых внутренних механизмов ядра, основанных на shared memory.
Важно: это НЕ "вся память, которая логически разделяется процессами" в смысле общих библиотек, и НЕ главный ориентир для оценки потребления памяти системой.
Типичный вывод:
$ free -m
total used free shared buff/cache available
Mem: 7857 1200 2000 150 4657 6200
Swap: 2047 0 2047
Здесь:
- shared (150 МБ) — это объем памяти, задействованный под:
- tmpfs (например, /dev/shm),
- сегменты shared memory, созданные приложениями,
- другие объекты, реализованные через shmem.
Ключевые моменты:
- Shared != "сколько памяти процессы совместно используют"
- Библиотеки, загруженные несколькими процессами, или page cache, используемый многими процессами, не напрямую отражаются в этом поле как "shared".
- Поле "Shared" — это часть "used" памяти, которая относится именно к shmem/tmpfs.
- Поэтому:
- по нему нельзя судить о "настоящем уровне шаринга" памяти приложений.
- Основные источники shared/shmem
Наиболее частые причины роста:
- /dev/shm:
- POSIX shared memory,
- некоторые приложения (базы данных, брокеры, очереди, high-performance IPC) используют её.
- tmpfs:
- любые tmpfs-монты (например, для временных файлов, Kubernetes emptyDir с medium: Memory).
- Графические/десктопные окружения, некоторые драйверы и IPC-механизмы:
- тоже могут использовать shmem.
- В контексте контейнеров и Kubernetes
- Многие поды используют tmpfs:
- для /dev/shm,
- для временных файлов в памяти.
- Эти tmpfs потребляют память, которая попадает в "shared/shmem".
- При расследовании memory-issues:
- высокий shared/shmem может означать:
- интенсивное использование tmpfs,
- активно используемую shared memory.
- Для точной диагностики смотрят:
- mount’ы tmpfs;
- использование /dev/shm;
- smaps для процессов;
- инструменты вроде
smem.
- высокий shared/shmem может означать:
- Как правильно объяснить на собеседовании
Хорошая, точная формулировка:
- Поле "Shared" (shmem) в free показывает объем памяти, используемой под shared memory (shmem), то есть:
- tmpfs (включая /dev/shm),
- разделяемые сегменты памяти, используемые для IPC и других механизмов.
- Это не просто "любая разделяемая память между процессами", и не ключевой показатель общей загруженности.
- Для анализа памяти системы важнее смотреть:
- used/free/available,
- buff/cache,
- и разбирать конкретные процессы через top/htop/smaps, а shared/shmem использовать как дополнительный индикатор активного использования tmpfs/shared memory.
Вопрос 35. Как интерпретировать разницу между значениями total, free и buff/cache в выводе free, если почти вся память используется, но система работает нормально?
Таймкод: 00:32:01
Ответ собеседника: неправильный. Не даёт чёткого объяснения, путается и не поясняет, что значительная часть "занятой" памяти может быть кэшем и фактически доступна для приложений.
Правильный ответ:
Ключевая идея: то, что Linux использует почти всю память, обычно нормально и ожидаемо. Важно понимать, какая часть памяти реально занята приложениями, а какая используется как кэш и может быть быстро освобождена.
Посмотрим на вывод:
free -h
total used free shared buff/cache available
Mem: 16Gi 14Gi 200Mi 300Mi 1.8Gi 8Gi
Swap: 2Gi 0Gi 2Gi
Расшифровка:
- total:
- общий объём RAM.
- used:
- "используемая" память, но сюда входят:
- память под приложения,
- cache,
- buffers,
- slab и прочее.
- Это сырое значение, и его часто неправильно трактуют как "всё съели".
- "используемая" память, но сюда входят:
- free:
- реально сейчас неиспользуемая, пустая память.
- Обычно небольшая — и это нормально.
- buff/cache:
- память, используемая под:
- page cache (кэш файловой системы),
- буферы ввода-вывода,
- metadata (dentry/inode cache),
- эта память:
- УСКОРЯЕТ работу системы (меньше обращений к диску),
- МОЖЕТ быть освобождена под нужды приложений при росте нагрузки.
- память, используемая под:
- available:
- оценка ядром, сколько памяти можно отдать новым процессам и под рост существующих, не начиная активно свопить.
- Это главное поле для оценки того, "есть ли память".
Почему "почти вся память занята", но всё хорошо:
- Linux придерживается принципа:
- "Свободная RAM — потерянная RAM".
- Если память не используется приложениями, ядро:
- кэширует файловые данные, каталоги, блоки диска;
- это делает повторное чтение быстрым.
- При необходимости:
- кэш можно быстро вытеснить,
- освободив место под новые запросы приложений.
Правильная интерпретация:
- Не смотреть только на "used" и "free".
- Оценивать:
- available — ключевой индикатор.
- buff/cache — сколько можно при необходимости вернуть приложениям.
- Если:
- available большой (например, несколько гигабайт),
- swap почти не используется,
- нет OOM-killer-событий,
- система работает без тормозов, — значит, ситуация нормальная, несмотря на "почти всё занято".
Пример разбора:
Сценарий:
- total: 16Gi
- used: 15Gi
- free: 100Mi
- buff/cache: 6Gi
- available: 7Gi
Вывод:
- Наивный взгляд: "15 из 16 занято, ужас!"
- Реальность:
- Из 15Gi:
- часть — реально приложения,
- 6Gi — кэш, который можно отдать;
- available 7Gi → системе есть что предоставить новым процессам.
- Паниковать не надо.
- Из 15Gi:
Когда бить тревогу:
- available падает к сотням мегабайт или ниже.
- активно используется swap (особенно при наличии ещё разумного buff/cache).
- часто срабатывает OOM-killer.
- наблюдаются реальные тормоза (high iowait, долгие GC/alloc, системные лаги).
Связь с Go-приложениями и продакшн:
- При анализе "утечек памяти" или high RSS:
- смотреть не только free, но и:
- RSS конкретного процесса (ps/top/htop),
- динамику available,
- использование swap.
- смотреть не только free, но и:
- Go-сервис может работать нормально при низком free, если:
- available и поведение GC/latency в норме.
- Ошибочная реакция:
- "освободить кэш" вручную через
echo 3 > /proc/sys/vm/drop_cachesв продакшене:- приводит к просадке производительности (теряется полезный cache);
- делать это стоит крайне редко и осознанно.
- "освободить кэш" вручную через
Краткая формулировка для интервью:
- "В выводе free важно смотреть не на used/free, а на available:
- buff/cache — это в основном файловый кэш, который ядро использует для ускорения работы и может освободить при необходимости.
- Поэтому ситуация, когда free почти 0, но available большой и swap не используется — нормальна и не говорит о нехватке памяти."
Вопрос 36. Для чего используется команда df -h и в чем отличие df от du?
Таймкод: 00:32:54
Ответ собеседника: неполный. Корректно говорит, что df -h показывает использование дисковых разделов/файловых систем, но не объясняет, что делает du и в чем принципиальное отличие этих утилит.
Правильный ответ:
Команды df и du обе связаны с дисковым пространством, но отвечают на разные вопросы и работают на разных уровнях.
Кратко:
- df — "Сколько занято/свободно на файловой системе/разделе?"
- du — "Сколько места занимают конкретные файлы и каталоги?"
Разберем детальнее.
Команда df -h
-
Назначение:
- Показать состояние файловых систем (FS) целиком.
-
Что показывает:
- размер каждого смонтированного тома/раздела;
- сколько занято;
- сколько свободно;
- точку монтирования.
-
Ключевые моменты:
- Работает на уровне файловой системы:
- данные берутся из метаданных FS;
- учитывает всё, включая:
- файлы,
- зарезервированное пространство (например, для root),
- возможные "утечки" (удалённые, но ещё открытые файлы).
- Работает на уровне файловой системы:
-
Пример:
df -hПокажет, например:
- /dev/sda1 монтирован на /
- /dev/sdb1 монтирован на /data
- их общий размер и свободное место.
Используем df, когда нужно ответить на вопросы:
- "Почему у нас диск на / заполнен на 95%?"
- "Сколько свободного места на томе с базой данных /var/lib/postgresql?"
- "Хватает ли места под логи, бэкапы, persistent volumes?"
Команда du
-
Назначение:
- Посчитать, сколько места занимают файлы и каталоги.
-
Работает на уровне файлов:
- обходит файловую систему,
- суммирует размер содержимого.
-
Примеры:
-
размер конкретного каталога:
du -sh /var/log -
топ "тяжёлых" директорий:
du -sh /var/* | sort -h
-
Используем du, когда нужно ответить на вопросы:
- "Кто именно съел место на диске?"
- "Какой каталог/приложение разрослось до десятков гигабайт?"
- "Почему df показывает 90% использования — где эти данные?"
Ключевые отличия df vs du
- Уровень:
- df:
- уровень файловой системы / раздела;
- показывает агрегированное состояние.
- du:
- уровень файлов и каталогов;
- показывает распределение использования.
- Источники данных:
- df:
- читает метаданные ФС;
- учитывает зарезервированное пространство;
- видит занятое место даже для удалённых, но всё ещё открытых файлов (когда процесс держит дескриптор).
- du:
- следует по видимому дереву каталогов;
- не видит:
- уже "удалённые", но удерживаемые открытыми файловые дескрипторы;
- данные вне namespace (например, скрытые mount’ы).
Из-за этого возможны ситуации:
- df показывает: диск почти заполнен,
- du по всем каталогам показывает меньше. Типичные причины:
- лог-файл удалили (rm), но процесс продолжает в него писать:
- место занято, пока не перезапустить или не закрыть файл;
- зарезервированное пространство ФС (например, 5% для root на ext4);
- снапшоты или скрытые mount’ы (LVM, ZFS, overlayfs, контейнеры).
- Практика диагностики
Если:
- df -h показывает, что / или /data почти заполнен,
- нужно выяснить, кто виноват:
Шаги:
- Смотрим общую картину:
df -h
- Ищем крупные каталоги:
du -xhd1 / | sort -h
du -xhd1 /var | sort -h
du -xhd1 /data | sort -h
- флаг -x исключает переход на другие FS;
- -h — человекочитаемо;
- -d1 — глубина 1 для верхнеуровневого обзора.
- Если суммы по du сильно меньше, чем занято по df:
-
проверяем открытые, но удалённые файлы:
lsof | grep '(deleted)' -
ищем процессы, которые держат удалённые большие файлы:
- особенно демоны логирования, приложения, базы.
Связь с Kubernetes и Go-сервисами:
- На нодах Kubernetes:
- df -h:
- контролируем заполнение /var, /var/lib/docker, /var/lib/containerd, томов с PV.
- du:
- ищем:
- разросшиеся логи контейнеров,
- кеши,
- временные файлы.
- ищем:
- df -h:
- Для Go-приложений:
- важно не плодить бесконтрольные логи и tmp-файлы;
- при отладке утечек диска:
- du помогает найти директории приложения, которые разрослись;
- df показывает, насколько критична ситуация.
Краткая формулировка для интервью:
- "df -h показывает использование дискового пространства на уровне файловых систем и разделов: общий размер, занято, свободно. du показывает, сколько места занимают конкретные каталоги и файлы. Обычно:
- df — чтобы понять, заполнен ли диск,
- du — чтобы найти, что именно его заполнило.
- Разница между df и суммой du бывает из-за удалённых, но открытых файлов, резервов ФС и особенностей mount/overlay."
Вопрос 37. Есть ли у вас практический опыт настройки iptables?
Таймкод: 00:33:21
Ответ собеседника: правильный. В отрывке прямой ответ не представлен, но по контексту можно предположить ограниченный опыт; оценка как формально корректная невозможна, однако вопрос предполагает проверку наличия hands-on практики.
Правильный ответ:
Да, и при таком вопросе важно показать не только знакомство с синтаксисом iptables, но и понимание его роли в сетевой архитектуре Linux и Kubernetes, а также умение применять его осознанно.
Краткий концептуальный обзор:
iptables — это фронтенд к подсистеме фильтрации и трансляции пакетов в Linux (netfilter), который позволяет:
- фильтровать трафик (firewall),
- выполнять NAT (SNAT/DNAT/masquerade),
- реализовывать port forwarding,
- ограничивать доступ по IP/портам/протоколам,
- участвовать в сетевой логике Kubernetes (kube-proxy в режиме iptables, CNI-плагины).
Основные таблицы:
- filter — для разрешения/блокировки трафика.
- CHAIN’ы: INPUT, OUTPUT, FORWARD.
- nat — для трансляции адресов (NAT).
- PREROUTING, POSTROUTING, OUTPUT.
- mangle — для модификации пакетов (TTL, маркировка).
- raw — для управления conntrack.
- security — для интеграции с LSM (SELinux и др.).
Типичные практические задачи, которые стоит уметь:
- Базовый firewall для сервера
Пример: разрешить SSH и HTTP/HTTPS, остальное запретить.
# Политика по умолчанию — DROP
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Разрешить loopback
iptables -A INPUT -i lo -j ACCEPT
# Разрешить уже установленные соединения
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Разрешить SSH
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Разрешить HTTP/HTTPS
iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
Здесь важно:
- сначала установить корректные policy,
- разрешать loopback,
- разрешать ESTABLISHED,RELATED,
- только затем открывать нужные порты.
- NAT и masquerade (шлюз/роутер)
Пример: сервер как шлюз из внутренней сети в интернет.
# Включаем форвардинг
sysctl -w net.ipv4.ip_forward=1
# NAT для исходящего трафика
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# Разрешаем форвардинг из внутреннего интерфейса
iptables -A FORWARD -i eth1 -o eth0 -m conntrack --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -i eth0 -o eth1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
- Взаимодействие с Kubernetes
Важно понимать:
- kube-proxy в режиме iptables:
- сам программирует правила в таблицах nat и filter для реализации Service/ClusterIP/NodePort.
- CNI-плагины:
- могут добавлять свои правила (маршрутизация, политики).
- Следствия:
- на Kubernetes-нодах:
- ручное управление iptables требует аккуратности,
- нельзя бездумно чистить (
iptables -F,iptables -t nat -F), чтобы не сломать kube-proxy/CNI.
- правильная практика:
- если нужен хост firewall, интегрировать свои правила в существующую схему,
- фиксировать порядок и цепочки (свои chains, jump из INPUT/FORWARD).
- на Kubernetes-нодах:
Пример осторожного добавления правила на ноде:
- Создать свою цепочку и отправлять туда трафик:
iptables -N MY_RULES
iptables -A INPUT -j MY_RULES
# В MY_RULES уже добавлять/изменять правила, не ломая системные/ kube-proxy’ные цепочки
iptables -A MY_RULES -p tcp --dport 22 -j ACCEPT
- Диагностика и отладка
Практический опыт должен включать:
- Просмотр правил:
iptables -L -v -n
iptables -t nat -L -v -n - Понимание counters (pkts/bytes) для проверки, "стреляют ли" правила.
- Поиск причин:
- почему сервис недоступен,
- где пакет дропается:
- INPUT / FORWARD,
- nat PREROUTING/POSTROUTING.
- Связь с безопасностью и эксплуатацией
Зрелое использование iptables включает:
- Явное ограничение входящего трафика.
- Не мешать внутреннему трафику кластера/сервисов без необходимости.
- Использование более высокоуровневых обёрток:
- ufw, firewalld, nftables (новый стек) — при этом понимать, что они генерируют правила iptables/nftables.
Краткая формулировка хорошего ответа:
- "Да, у меня есть практический опыт:
- настройки базового firewall (INPUT/FORWARD policy, allowed ports),
- настройки NAT/masquerade для шлюзов,
- диагностики сетевых проблем через анализ iptables-правил.
- При работе с Kubernetes понимаю, что kube-proxy и CNI активно используют iptables, поэтому любые кастомные правила добавляю аккуратно, в свои цепочки, не ломая системные.
- Осознаю, что в новых окружениях постепенно переходят на nftables, но большая часть продакшн-инфраструктур все еще полагается на iptables, и я умею с ним работать осознанно."
Вопрос 37. Есть ли у вас опыт настройки iptables и как вы его использовали?
Таймкод: 00:33:21
Ответ собеседника: неправильный. Говорит, что реальным администрированием iptables не занимался, только просматривал сгенерированные правила Kubernetes, не демонстрирует понимания практического применения и принципов.
Правильный ответ:
Для уверенной работы с инфраструктурой и Kubernetes важно понимать iptables как фундаментальный механизм Linux-файрвола и базу для kube-proxy (в режиме iptables) и многих CNI-плагинов. Ожидается не просто "видел правила", а умение:
- читать и интерпретировать цепочки;
- безопасно добавлять свои правила;
- понимать влияние iptables на сетевое взаимодействие приложений и кластера.
Ключевые моменты, которые стоит уметь объяснить и демонстрировать на практике.
Базовые концепции iptables:
- iptables — интерфейс к подсистеме netfilter в ядре Linux.
- Позволяет:
- фильтровать трафик (firewall),
- выполнять NAT (SNAT/DNAT, MASQUERADE),
- маркировать пакеты,
- управлять маршрутизацией трафика через пользовательские цепочки.
Основные таблицы:
- filter:
- для разрешения/блокировки трафика.
- цепочки:
- INPUT — входящий локально на машину;
- OUTPUT — исходящий с машины;
- FORWARD — транзитный трафик через машину.
- nat:
- для трансляции адресов/портов.
- цепочки:
- PREROUTING — до маршрутизации (обычно DNAT);
- POSTROUTING — после маршрутизации (SNAT/MASQUERADE);
- OUTPUT — для локально исходящего.
- mangle, raw, security:
- для продвинутых сценариев (QoS, conntrack, LSM-интеграция).
Практические сценарии использования:
- Базовый firewall для сервера
Пример: оставить вход только для SSH и HTTP/HTTPS:
# Политики по умолчанию
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Разрешить loopback
iptables -A INPUT -i lo -j ACCEPT
# Разрешить уже установленные соединения
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Разрешить SSH
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Разрешить HTTP/HTTPS
iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
Что важно уметь:
- правильно задать policy,
- не отрезать себе доступ (loopback, ESTABLISHED),
- проверять счётчики правил (
iptables -L -v -n).
- NAT и маршрутизация
Сервер как шлюз для внутренней сети:
# Включить форвардинг
sysctl -w net.ipv4.ip_forward=1
# NAT для выхода в интернет
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# Разрешить форвардинг
iptables -A FORWARD -i eth1 -o eth0 -m conntrack --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -i eth0 -o eth1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
- Взаимодействие с Kubernetes
В кластере Kubernetes iptables используется:
- kube-proxy (в режиме iptables):
- создает правила в таблице nat для реализации:
- ClusterIP,
- NodePort,
- балансировки на pod’ы-эндпоинты.
- создает правила в таблице nat для реализации:
- CNI-плагины (Calico, Cilium с iptables backend, и др.):
- создают правила для маршрутизации и политик (NetworkPolicy).
Что нужно понимать и уметь:
- Нельзя бездумно делать:
iptables -F,iptables -t nat -F,- так можно убить сервисную сетку кластера.
- При необходимости добавить свои правила:
- создавать отдельные цепочки и подключать их аккуратно:
iptables -N CUSTOM_INPUT
iptables -A INPUT -j CUSTOM_INPUT
iptables -A CUSTOM_INPUT -p tcp --dport 22 -j ACCEPT
# другие правила...
- Уметь читать существующие правила:
и понимать, как трафик "течет" через цепочки.
iptables -L -v -n
iptables -t nat -L -v -n
- Диагностика проблем
Опытный специалист при сетевых проблемах:
- Проверяет iptables как один из первых уровней:
- не блокируется ли нужный порт (API, база, сервис),
- корректно ли работает DNAT/SNAT.
- Использует:
iptables -L -v -n— смотреть срабатывание правил (счётчики pkts/bytes),tcpdump— сверять, доходят ли пакеты.
- Контекст современных систем
- Понимать, что:
- iptables постепенно заменяется nftables, но:
- многие окружения и Kubernetes-решения по-прежнему завязаны на iptables;
- iptables постепенно заменяется nftables, но:
- Уметь работать с iptables — обязательный навык для:
- анализа сетевых проблем,
- настройки безопасности legacy и текущих кластеров.
Краткая формулировка сильного ответа:
- "Да, я использовал iptables для:
- настройки базового firewall (ограничение входящих портов),
- конфигурации NAT/masquerade для шлюзов,
- диагностики сетевых проблем.
- В Kubernetes-кластерах понимаю, что kube-proxy и CNI строят на iptables сервисную сеть:
- читаю и интерпретирую их правила,
- любые свои изменения вношу через отдельные цепочки, чтобы не ломать системную конфигурацию.
- Знаю базовые паттерны безопасной конфигурации и типичные ловушки (политики по умолчанию, ESTABLISHED, loopback, DNAT/SNAT)."
Вопрос 38. С какими системами CI/CD вы работали и какие технологии приходилось собирать?
Таймкод: 00:33:54
Ответ собеседника: правильный. Использовал GitLab CI, собирал проекты на Java, Go, Python и C++, подстраиваясь под стек компании.
Правильный ответ:
При ответе на этот вопрос важно показать не только список инструментов, но и глубину понимания процессов доставки, качества, безопасности и reproducibility сборок для разных стеков.
Ключевые моменты сильного ответа:
- Используемые CI/CD-системы
- GitLab CI:
- как основная платформа:
- pipeline-as-code через
.gitlab-ci.yml, - GitLab Runners (shell, docker, Kubernetes),
- кэш, артефакты, environments, manual/approval-steps.
- pipeline-as-code через
- как основная платформа:
- Дополнительно (если было):
- GitHub Actions, Jenkins, TeamCity и т.п.:
- важно уметь перенести концепции (stages, jobs, artifacts, triggers) между системами.
- GitHub Actions, Jenkins, TeamCity и т.п.:
- Общая архитектура пайплайнов
Подход должен демонстрировать зрелость:
- Стандартные стадии:
- lint/format,
- unit tests,
- integration tests,
- build,
- security checks (SAST, dependency scan, container scan),
- packaging (docker image, бинарник, helm chart),
- deploy (dev → stage → prod).
- Обязательные принципы:
- repeatable builds:
- фиксированные версии зависимостей,
- lock-файлы (go.sum, requirements.txt, pom.xml, etc.),
- детерминированные Docker-образы.
- "Shift-left":
- максимум проверки на ранних этапах (PR/MR),
- быстрый фидбек разработчику.
- Разделение окружений:
- разные конфигурации и секреты,
- ручные подтверждения на production,
- аудит изменений.
- repeatable builds:
- Сборка и доставка Go-проектов
Go — ключевой стек в контексте собеседования, здесь важна конкретика.
- Типичный пайплайн для Go:
stages:
- lint
- test
- build
- docker
- deploy
lint:
stage: lint
image: golang:1.22
script:
- go vet ./...
- golangci-lint run ./...
only:
- merge_requests
- main
test:
stage: test
image: golang:1.22
script:
- go test ./... -race -coverprofile=coverage.out
artifacts:
paths:
- coverage.out
only:
- merge_requests
- main
build:
stage: build
image: golang:1.22
script:
- CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o app ./cmd/app
artifacts:
paths:
- app
only:
- main
docker:
stage: docker
image: docker:24
services:
- docker:24-dind
script:
- docker build -t registry.example.com/app:${CI_COMMIT_SHA} .
- docker push registry.example.com/app:${CI_COMMIT_SHA}
only:
- main
- Best practices:
- multi-stage Docker build;
- минимальные образы (distroless, alpine с осторожностью, debian-slim);
- встраивание метаданных (version, commit, build time) через -ldflags;
- health-check endpoints для оркестраторов (Kubernetes).
- Сборка Java-проектов
- Maven/Gradle:
- кэширование зависимостей между job’ами;
- профили для разных окружений.
- Артефакты:
- JAR/WAR,
- контейнеры с JDK/JRE или distroless/openjdk-bазами.
- Важное:
- тесты (unit + integration),
- проверка качества (spotbugs, checkstyle),
- SAST/Dependency Check.
- Сборка Python-проектов
- Использование:
- virtualenv / venv,
- requirements.txt / poetry.lock.
- Пайплайн:
- lint (flake8, black, isort, mypy),
- тесты (pytest),
- сборка wheel/образа.
- В контейнерах:
- фиксированные версии,
- multi-stage: установка зависимостей → копия только нужного в финальный образ.
- Сборка C/C++ проектов
- Использование:
- CMake, Makefile, Ninja;
- компиляторы gcc/clang.
- Пайплайн:
- конфигурация (cmake ..),
- сборка (cmake --build),
- unit/integration tests,
- статический анализ (clang-tidy, cppcheck),
- упаковка бинарников/библиотек,
- при необходимости сборка docker-образов с нативными зависимостями.
- Работа с артефактами, версиями и безопасностью
- Версионирование:
- семантические версии,
- теги git,
- автогенерация образов вида:
- app:${CI_COMMIT_SHA},
- app:${CI_COMMIT_TAG},
- app:latest для удобства.
- Registry:
- приватные Docker registry,
- GitLab Container Registry / Harbor / ECR / GCR.
- Секреты:
- хранение в GitLab CI variables / Vault;
- никогда не коммитить секреты в репозиторий.
- Checks:
- образ сканируется на уязвимости;
- зависимости проверяются (go list -m -u, osv-scanner, trivy и т.п.).
- Интеграция с Kubernetes
- Автоматический деплой:
- через kubectl/helm/kustomize в отдельном job’е;
- GitOps (Argo CD/Flux) как предпочтительный подход:
- CI собирает и публикует артефакты,
- CD (GitOps) выкатвает через декларативные манифесты.
- Типичный шаг деплоя:
deploy:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl set image deployment/app app=registry.example.com/app:${CI_COMMIT_SHA} -n prod
when: manual
only:
- main
Итоговая хорошая формулировка:
- "Работал преимущественно с GitLab CI:
- проектировал многостадийные пайплайны для Go, Java, Python, C++,
- использовал кэширование, артефакты, разделение окружений,
- собирал оптимизированные Docker-образы, проводил тесты и security-сканы,
- интегрировал пайплайны с Kubernetes-деплоем.
- Фокус — на воспроизводимости сборок, быстром фидбеке, качестве и безопасной поставке артефактов."
Такой ответ демонстрирует не только знакомство с инструментом, но и зрелый подход к построению CI/CD для разнородного стека.
Вопрос 39. Чем в Maven-сборке Java-проектов является файл pom.xml и что в нем описывается?
Таймкод: 00:34:33
Ответ собеседника: неполный. Говорит, что pom.xml в основном содержит зависимости и правила сборки; общее понимание верное, но без деталей структуры, типов артефактов, плагинов, наследования и управления профилями.
Правильный ответ:
В Maven файл pom.xml (Project Object Model) — центральный декларативный контракт проекта. Это не просто список зависимостей, а формальное описание:
- что это за артефакт,
- как его собирать, тестировать, публиковать,
- какие зависимости, плагины, профили и репозитории используются,
- как проект связан с родительскими POM’ами и мульти-модульной структурой.
Ключевые элементы и их назначение:
- Идентификация артефакта
Базовые координаты Maven:
- groupId — логическая группа/организация:
- пример: com.example, org.springframework.
- artifactId — имя артефакта (проекта/модуля):
- пример: payment-service, core-lib.
- version — версия:
- пример: 1.0.0, 1.0.0-SNAPSHOT.
- packaging — тип артефакта:
- jar (по умолчанию), war, pom, ear и др.
Пример:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>payment-service</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
</project>
Эта тройка (groupId, artifactId, version) однозначно идентифицирует артефакт в Maven-экосистеме и репозиториях.
- Зависимости (dependencies)
В pom.xml определяются зависимости с указанием:
- groupId, artifactId, version,
- scope (compile, provided, runtime, test, system, import),
- опционально — exclusions для конфликтующих транзитивных зависимостей.
Пример:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>
Важно:
- Maven автоматом подтягивает транзитивные зависимости;
- версии и конфликты управляются через dependencyManagement.
- Плагины и фаза сборки (build)
Раздел build определяет:
- какие плагины используются;
- как именно выполняются:
- компиляция,
- тесты,
- упаковка,
- генерация доки,
- проверка качества кода,
- сборка docker-образов (через специализированные плагины).
Пример:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
</plugins>
</build>
Через плагины и lifecycle определяем, что именно делает mvn clean, mvn package, mvn test и т.д.
- Управление версиями и транзитивными зависимостями: dependencyManagement
В родительском или общем POM:
- фиксируются версии зависимостей без прямого подключения;
- дочерние модули могут ссылаться без указания version.
Пример:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.1</version>
</dependency>
</dependencies>
</dependencyManagement>
Это позволяет:
- централизованно управлять версиями,
- избегать "зоопарка" версий по модулям,
- улучшать воспроизводимость сборок — критично для CI/CD.
- Наследование и мульти-модульные проекты
pom.xml поддерживает:
- parent:
- наследование общих настроек (dependencies, plugins, properties).
- packaging = pom:
- для "root"-модулей, объединяющих подпроекты.
Пример мульти-модульного POM:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>platform-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>api</module>
<module>service</module>
<module>common</module>
</modules>
</project>
Так строятся большие системы с общими правилами сборки и зависимостями.
- Профили (profiles)
Раздел profiles позволяет:
- изменять конфигурацию сборки под разные окружения/сценарии:
- dev/test/prod,
- включение/отключение некоторых плагинов,
- разные настройки.
<profiles>
<profile>
<id>prod</id>
<properties>
<env>prod</env>
</properties>
<activation>
<property>
<name>env</name>
<value>prod</value>
</property>
</activation>
</profile>
</profiles>
Запуск:
mvn clean package -Pprod
- Репозитории и публикация
В pom.xml также могут быть определены:
- дополнительные репозитории для зависимостей;
- distributionManagement:
- куда публиковать собранные артефакты (Nexus, Artifactory, внутренние репо).
Это критично для корпоративных CI/CD-процессов.
- Связь с CI/CD и Go/Polyglot-окружением
Для инженера, работающего не только с Java:
- Важно понимать, что pom.xml:
- является контрактом между разработчиком, CI/CD и артефакт-репозиторием;
- обеспечивает воспроизводимость сборки:
- фиксированные версии зависимостей;
- формальный lifecycle;
- интеграцию с quality- и security-инструментами.
При построении пайплайнов:
- CI дергает mvn clean verify/package, опираясь на pom.xml;
- на основе pom.xml:
- кэшируются зависимости,
- запускаются тесты,
- формируются JAR/WAR,
- далее оборачиваются в контейнеры или выкладываются в артефакт-репозиторий.
Краткая формулировка:
- "pom.xml — это декларативная модель Maven-проекта:
- описывает координаты артефакта (groupId/artifactId/version/packaging),
- зависимости и управление версиями,
- плагины и жизненный цикл сборки,
- профили, репозитории, наследование и мульти-модульную структуру.
- Это основной источник правды для reproducible и стандартизованной сборки Java-проектов в CI/CD."
Вопрос 40. Как перенастроить Maven на использование Nexus в изолированном контуре вместо обращения к внешним репозиториям?
Таймкод: 00:35:13
Ответ собеседника: неправильный. Признаёт, что не знает, как указать Maven использовать Nexus, и обычно ищет решение в интернете; не демонстрирует понимания settings.xml, зеркал (mirror), репозиториев и аутентификации.
Правильный ответ:
В изолированном контуре (air-gapped, restricted network) Maven должен использовать внутренний репозиторий (Nexus/Artifactory/Harbor и т.п.) как единственный источник артефактов, вместо прямого доступа к Maven Central и прочим внешним репозиториям.
Корректная перенастройка включает:
- конфигурацию Maven-клиентов через settings.xml;
- настройку mirror, чтобы все запросы шли в Nexus;
- (опционально) настройку репозиториев в pom.xml под внутренний прокси/группу;
- аутентификацию при необходимости.
Ключевой файл: settings.xml
- Глобальный:
${M2_HOME}/conf/settings.xml
- Пользовательский:
${HOME}/.m2/settings.xml
- В CI/CD обычно подменяется/монтируется свой settings.xml.
Основные элементы настройки:
- Зеркала (mirrors): принудительно отправить весь трафик в Nexus
Задача:
- Любая попытка Maven обратиться к репозиторию (например, central) должна идти через Nexus.
Пример настроек в settings.xml:
<settings>
<mirrors>
<mirror>
<id>nexus-all</id>
<name>Nexus mirror for all repositories</name>
<url>http://nexus.internal.local/repository/maven-public/</url>
<mirrorOf>*</mirrorOf>
</mirror>
</mirrors>
</settings>
Что это делает:
<mirrorOf>*</mirrorOf>:- подменяет все репозитории (включая central) на указанный Nexus-URL;
maven-public— это group repository в Nexus, обычно объединяющий:- прокси для Maven Central,
- внутренние хостед-репозитории.
- В изолированном контуре:
- Nexus сам заполняется артефактами (импорт/репликация/seed из внешнего мира),
- Maven внутри контура ходит только в Nexus.
- Репозитории в pom.xml: использовать внутренний Nexus, а не интернет
Часто репозитории в pom.xml вообще не указываются, полагаясь на central. При использовании mirror в settings.xml этого достаточно.
Если нужно задать явно:
<project>
...
<repositories>
<repository>
<id>internal-nexus</id>
<url>http://nexus.internal.local/repository/maven-public/</url>
</repository>
</repositories>
</project>
Но правильнее:
- использовать mirrors в settings.xml, а pom.xml не размножать специфическими урлами;
- так pom.xml остаётся универсальным, а окружение управляется через настройки.
- Аутентификация к Nexus
Если доступ к Nexus защищён:
- В settings.xml добавляем server:
<settings>
...
<servers>
<server>
<id>nexus-all</id>
<username>maven-ci</username>
<password>STRONG_PASSWORD_OR_TOKEN</password>
</server>
</servers>
</settings>
И связываем:
<id>в<mirror>или<repository>должен совпадать с<id>в<server>, если требуется аутентификация.
Логику:
- credentials хранятся в settings.xml (а не в pom.xml),
- в CI/CD:
- секреты подставляются через переменные/secret storage,
- settings.xml шаблонизируется при старте job.
- Публикация артефактов в Nexus (deploy)
Если изолированный контур предполагает:
- выкладку артефактов (библиотек, BOM, parent-pom и т.п.) в Nexus:
В pom.xml (или родительском pom) настраиваем distributionManagement:
<distributionManagement>
<repository>
<id>nexus-releases</id>
<url>http://nexus.internal.local/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>nexus-snapshots</id>
<url>http://nexus.internal.local/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
В settings.xml:
<servers>
<server>
<id>nexus-releases</id>
<username>ci-user</username>
<password>RELEASE_TOKEN</password>
</server>
<server>
<id>nexus-snapshots</id>
<username>ci-user</username>
<password>SNAPSHOT_TOKEN</password>
</server>
</servers>
Далее:
mvn deployпубликует артефакты в Nexus, не выходя в интернет.
- Особенности для изолированного контура
В air-gapped/secure окружениях:
- Внешний интернет либо полностью недоступен, либо жестко ограничен.
- Nexus наполняется:
- через предварительное скачивание артефактов (на внешнем стенде),
- импорт с помощью репликации, backup/restore,
- ручной upload критичных артефактов.
- В settings.xml:
- обязательно использовать mirrorOf=*,
- запретить прямой доступ к Maven Central:
- либо на уровне сети (FW),
- либо не прописывать его.
Контроль:
- Проверить, что Maven реально не ходит во внешний интернет:
- запуск сборки при отключённой маршрутизации наружу;
- мониторинг логов Nexus:
- все dependency-запросы должны идти через него.
- Интеграция с CI/CD
В CI (GitLab CI, Jenkins, GitHub Actions on-prem):
- settings.xml:
- хранится как секрет/конфиг (CI variables, Kubernetes Secret),
- монтируется/копируется в контейнер বিলда:
~/.m2/settings.xml.
- Пример для GitLab CI (фрагмент):
image: maven:3.9-eclipse-temurin-17
variables:
MAVEN_OPTS: "-Dmaven.repo.local=/cache/.m2/repository"
cache:
paths:
- .m2/repository
before_script:
- mkdir -p ~/.m2
- echo "$MAVEN_SETTINGS_XML" > ~/.m2/settings.xml
build:
script:
- mvn -B clean package
Где:
- MAVEN_SETTINGS_XML — защищённая переменная с содержимым настроенного settings.xml.
Краткая, сильная формулировка для интервью:
- "Чтобы Maven в изолированном контуре использовал Nexus вместо внешних репозиториев, настраиваем settings.xml:
- определяем mirror с mirrorOf="*" и URL на Nexus group repo (например, maven-public),
- при необходимости добавляем аутентификацию через <servers>,
- избегаем прямых ссылок на external repo в pom.xml.
- В CI/CD подставляем этот settings.xml через секреты.
- Таким образом, все зависимости и публикации проходят только через Nexus, без выхода в интернет."
Вопрос 41. В чем разница между инструкциями ENTRYPOINT и CMD в Dockerfile?
Таймкод: 00:36:22
Ответ собеседника: правильный. Говорит, что ENTRYPOINT задает основную команду, а CMD — аргументы к ней; упоминает корректный паттерн совместного использования.
Правильный ответ:
ENTRYPOINT и CMD определяют, что именно будет запущено в контейнере по умолчанию, но играют разные роли и по-разному переопределяются при запуске.
Ключевая идея:
- ENTRYPOINT — фиксирует "что это за контейнер" и какую программу он должен запускать.
- CMD — задает значения по умолчанию:
- либо аргументы для ENTRYPOINT,
- либо команду, если ENTRYPOINT не задан.
Важно понимать различия в режимах (shell vs exec) и в том, как они переопределяются docker run, Kubernetes и CI/CD.
- ENTRYPOINT
- Определяет основной исполняемый процесс контейнера.
- Обычно трактуется как "контейнер делает именно это".
- Часто задается в exec-форме (рекомендуется):
ENTRYPOINT ["./app"]
- Если используется shell-форма:
ENTRYPOINT ./app
то команда выполняется через /bin/sh -c, что влияет на обработку сигналов, переменных и т.д.
- CMD
- Задает:
- либо команду по умолчанию (если ENTRYPOINT не установлен),
- либо аргументы по умолчанию для ENTRYPOINT (если ENTRYPOINT есть).
- Может быть:
- exec-форма:
CMD ["--port=8080", "--env=prod"] - shell-форма:
CMD ./app --port=8080
- exec-форма:
- Совместное использование ENTRYPOINT + CMD (рекомендуемый паттерн)
Самый полезный и часто применяемый паттерн:
- ENTRYPOINT — бинарник/основная команда.
- CMD — аргументы по умолчанию, которые можно переопределять.
Пример для Go-приложения:
FROM golang:1.22 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o app ./cmd/app
FROM gcr.io/distroless/base-debian12
WORKDIR /app
COPY --from=build /src/app .
ENTRYPOINT ["/app/app"]
CMD ["--config=/etc/app/config.yaml"]
Поведение:
-
По умолчанию при
docker run imageзапустится:- /app/app --config=/etc/app/config.yaml
-
Если при запуске указать свои аргументы:
docker run image --config=/custom/config.yamlони заменят CMD, но не ENTRYPOINT:
- /app/app --config=/custom/config.yaml
То есть:
- ENTRYPOINT остается неизменным (если явно не переопределить
--entrypoint), - CMD легко переопределяется при запуске и управляет поведением приложения.
- Переопределение при docker run
- Если в образе:
- только CMD:
docker run image <command>полностью заменяет CMD.
- только CMD:
- Если в образе:
- ENTRYPOINT + CMD:
docker run image <args>заменяет CMD на новые args, но ENTRYPOINT остается.
- ENTRYPOINT + CMD:
Пример:
ENTRYPOINT ["ping"]
CMD ["8.8.8.8"]
docker run image→ ping 8.8.8.8docker run image 1.1.1.1→ ping 1.1.1.1
- Использование в Kubernetes
В PodSpec:
- command:
- переопределяет ENTRYPOINT.
- args:
- переопределяет CMD.
Пример:
containers:
- name: app
image: my/app:latest
command: ["/app/app"] # заменит ENTRYPOINT
args: ["--config=/tmp/cfg"] # заменит CMD
Важно:
- Правильная связка ENTRYPOINT+CMD делает образ гибким:
- дефолтное поведение для продакшена,
- возможность менять аргументы в разных окружениях (Kubernetes, docker run, CI).
- Типичные ошибки и best practices
- Не использовать shell-форму без необходимости:
- хуже обработка сигналов,
- сложнее graceful shutdown;
- для Go-сервисов важно корректно ловить SIGTERM/SIGINT.
- Не "хардкодить" всё в ENTRYPOINT:
- тогда сложно менять параметры без пересборки образа.
- Не дублировать логику:
- один явный ENTRYPOINT, один CMD — чистая и предсказуемая семантика.
Краткая формулировка:
- ENTRYPOINT определяет основную команду контейнера и почти всегда остаётся неизменным.
- CMD задает параметры по умолчанию (или команду), которые легко переопределить.
- Рекомендуемый паттерн:
- ENTRYPOINT — бинарник,
- CMD — дефолтные аргументы,
- что даёт удобство для эксплуатации (docker run, Kubernetes, CI/CD) без пересборки образа.
Вопрос 42. Для чего используется директива EXPOSE в Dockerfile?
Таймкод: 00:36:51
Ответ собеседника: правильный. Объясняет, что EXPOSE декларативно объявляет используемый контейнером порт и сам по себе порт не публикует; реальный проброс происходит при запуске контейнера или через docker-compose.
Правильный ответ:
Директива EXPOSE в Dockerfile служит декларативным описанием того, какие порты внутри контейнера предназначены для приёма входящих соединений приложением. Это:
- документация для людей и инструментов,
- подсказка для оркестраторов и tooling,
- но НЕ механизм реальной публикации порта наружу.
Ключевые моменты:
- Что делает EXPOSE
-
Объявляет, что контейнер внутри слушает указанный порт (или порты).
-
Пример:
EXPOSE 8080Семантика:
- "Приложение в этом образе ожидает входящие соединения на порту 8080".
-
Можно указывать протокол:
EXPOSE 8080/tcp
EXPOSE 8125/udp
- Чего EXPOSE не делает
- Не открывает порт на хосте.
- Не настраивает проброс портов автоматически.
- Не является firewall-правилом.
Чтобы порт был доступен снаружи:
- при запуске контейнера нужно явно указать:
- docker run -p 8080:8080 image
- или настроить:
- docker-compose (ports),
- Kubernetes Service (targetPort/port),
- другой оркестратор.
- Практическая польза EXPOSE
Хотя EXPOSE не публикует порт сам по себе, он полезен:
- Как самодокументация образа:
- глядя в Dockerfile или docker inspect, понятно, какие порты использовать.
- Для tooling:
- некоторые инструменты (docker run с опциями по умолчанию, docker-compose, UI) могут использовать EXPOSE как подсказку.
- Для командной работы:
- разработчик, DevOps, платформа видят контракт образа:
- "этот сервис ожидает трафик на 8080" и т.п.
- разработчик, DevOps, платформа видят контракт образа:
- Связка с Go-приложениями
Для Go-сервисов стандартный паттерн:
log.Fatal(http.ListenAndServe(":8080", handler))
Dockerfile:
FROM gcr.io/distroless/base-debian12
WORKDIR /app
COPY app .
EXPOSE 8080
ENTRYPOINT ["/app/app"]
Запуск локально:
docker run -p 8080:8080 my-app
В Kubernetes:
- EXPOSE не обязателен для работы, но служит документацией.
- Реальная маршрутизация делается через Service/Ingress:
apiVersion: v1
kind: Service
metadata:
name: my-app
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
- Краткая формулировка
- EXPOSE:
- декларативно указывает порты, которые контейнер "слушает";
- не публикует и не открывает их на хосте;
- используется как контракт/подсказка для людей и инструментов.
- Для реального доступа:
- docker run -p / docker-compose ports / Kubernetes Service и пр.
Вопрос 43. Что делает блок workflow: rules: в файле .gitlab-ci.yml?
Таймкод: 00:37:48
Ответ собеседника: правильный. Правильно указывает, что workflow: rules: задаёт условия, при которых запускается пайплайн (например, только для определённых веток, тегов или типов событий).
Правильный ответ:
В GitLab CI блок workflow: с секцией rules: управляет тем, будет ли вообще создан и запущен pipeline для конкретного события (push, merge request, tag, webhook и т.п.). Это "фильтр верхнего уровня", который срабатывает до оценки правил для отдельных jobs.
Ключевые моменты:
- Роль workflow: rules:
- Определяет, создавать ли pipeline в принципе.
- Позволяет:
- ограничить запуск pipeline только для:
- определённых веток (например, main, release/*),
- merge request’ов,
- тегов (релизов),
- конкретных авторов/paths/trigger-источников;
- отменять/пропускать pipeline для:
- черновых веток,
- служебных коммитов (например, только изменения документации),
- auto-merge/ботов,
- deprecated веток.
- ограничить запуск pipeline только для:
Без workflow: rules:
- GitLab по умолчанию создаёт pipeline для большинства push-событий, если есть .gitlab-ci.yml.
С workflow: rules:
- можно централизованно сказать:
- "Создавать pipeline только когда это действительно нужно".
- Пример базовой конфигурации
Запускать pipeline только для:
- ветки main,
- веток feature/*,
- merge request’ов.
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: always
- if: '$CI_COMMIT_BRANCH == "main"'
when: always
- if: '$CI_COMMIT_BRANCH =~ /^feature\/.+$/'
when: always
- when: never
Логика:
- Последовательная проверка правил:
- если событие — MR → создаём pipeline;
- если ветка main → создаём;
- если ветка feature/* → создаём;
- иначе —
when: never→ pipeline не создаётся.
- Отличие от rules: у job’ов
Важно не путать:
- workflow: rules:
- решает, создавать pipeline или нет.
- применяется один раз на уровне всего pipeline.
- rules: внутри job:
- управляют запуском конкретной job внутри уже созданного pipeline.
- пример:
- одни job’ы только для main,
- другие только для MR,
- третьи только для тегов.
Комбинация:
- Сначала workflow: rules: решает:
- будет pipeline или нет.
- Потом для каждого job:
- его собственные rules/only/except решают, запускаться или быть skipped.
- Типичные production-паттерны
- Не создавать pipeline для draft/черновых веток:
workflow:
rules:
- if: '$CI_COMMIT_BRANCH =~ /^wip\//'
when: never
- when: always
- Запускать pipeline только для MR и main:
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: always
- if: '$CI_COMMIT_BRANCH == "main"'
when: always
- when: never
- Разделять behavior для тегов (релизные pipeline):
workflow:
rules:
- if: $CI_COMMIT_TAG
when: always
- if: '$CI_COMMIT_BRANCH == "main"'
when: always
- when: never
- Практическая ценность
Грамотное использование workflow: rules::
- снижает шум и нагрузку на CI:
- не запускаем pipeline на каждое незначительное изменение, где он не нужен;
- повышает предсказуемость:
- явные правила, когда создаются:
- build/test-пайплайны,
- release-пайплайны,
- проверки для MR;
- явные правила, когда создаются:
- упрощает сопровождение:
- логика создания pipeline сосредоточена в одном месте,
- а не размазана по rules каждого job.
Краткая формулировка:
workflow: rules:— это верхнеуровневая логика, определяющая, создавать ли pipeline для данного события. Он позволяет централизованно ограничивать запуск CI по веткам, тегам, типам событий и условиям, после чего обычныеrules/only/exceptв job’ах управляют уже отдельными шагами внутри созданного pipeline.
Вопрос 44. Что делает блок include: в .gitlab-ci.yml и зачем нужен параметр ref?
Таймкод: 00:40:04
Ответ собеседника: правильный. Объясняет, что include подключает внешний файл с конфигурацией из указанного проекта и пути, а ref позволяет выбрать конкретную ветку/тег этого проекта; проводит корректную аналогию с include в языках программирования.
Правильный ответ:
Блок include: в .gitlab-ci.yml используется для композиции и повторного использования конфигураций GitLab CI. Он позволяет:
- подключать общие пайплайны, шаблоны job’ов и настройки из:
- текущего репозитория,
- других проектов,
- удалённых файлов,
- предопределённых шаблонов GitLab;
- унифицировать CI/CD в масштабах организации;
- сократить дублирование и риски расхождения настроек.
Параметр ref определяет, из какого именно ref (ветки, тега, коммита) подключать файл при include из другого проекта/репозитория.
Ключевые моменты:
- Основные варианты include
GitLab поддерживает несколько форматов include:
-
Локальный файл в том же репозитории:
include:
- local: .gitlab/ci/base.yml -
Файл из другого проекта:
include:
- project: my-group/ci-templates
file: /java/pipeline.yml
ref: v1.2.3 -
Удалённый URL (обычно не для изолированных контуров):
include:
- remote: https://example.com/ci-templates/base.yml -
Встроенные шаблоны GitLab:
include:
- template: Security/SAST.gitlab-ci.yml
Все подключенные файлы мержатся в единую итоговую конфигурацию pipeline.
- Зачем нужен ref
При include из другого проекта:
refоднозначно фиксирует версию конфигурации, которую вы подключаете:- ветка (например, main),
- тег (например, v1.0.0),
- конкретный SHA.
Пример:
include:
- project: my-group/ci-templates
file: /go/base-pipeline.yml
ref: v2.3.1
Почему это важно:
- Репозиторий с шаблонами CI — такой же код, он развивается.
- Без фиксированного ref:
- вы можете внезапно "подцепить" несовместимые изменения в шаблоне,
- это ломает стабильность пайплайнов в зависимых проектах.
- С фиксированным тегом/sha:
- получаете reproducible конфигурацию:
- так же, как фиксируете версии зависимостей в Go (go.mod) или Maven (pom.xml).
- получаете reproducible конфигурацию:
Практический паттерн:
- Для production-проектов:
- использовать
refна тег/релиз шаблона (например, v1.5.0), - обновлять осознанно после тестирования.
- использовать
- Для ранних стадий:
- можно временно ссылаться на ветку (main/dev), осознавая риск.
- Типичные сценарии использования include
-
Общие стандартизованные пайплайны:
-
Один репозиторий
ci-templatesсодержит:- базовые стейджи,
- правила запуска (workflow: rules),
- security-сканы,
- публикацию артефактов,
- деплой-джобы.
-
В сервисах (Go, Java, Python, etc.):
include:
- project: platform/ci-templates
file: /pipelines/go.yml
ref: v1.0.0 -
Локально в сервисе:
- только минимальные overrides (имя образа, специфические шаги).
-
-
DRY (Don’t Repeat Yourself):
- Вынос общих определений:
- anchors, шаблонные job’ы, before_script, образа, policies,
- чтобы не копировать во все
.gitlab-ci.yml.
- Вынос общих определений:
-
Централизованный контроль:
- Платформа-команда обновляет шаблоны,
- команды сервисов просто обновляют
refпосле проверки.
- Как это помогает в реальных проектах
Для зрелых CI/CD-процессов:
-
include + ref дают:
-
повторное использование:
- один раз описать "как собирать Go-сервис":
- линт, тесты, race, build, docker, scan;
- переиспользовать десятками сервисов;
- один раз описать "как собирать Go-сервис":
-
управляемые обновления:
- можно протестировать новый шаблон,
- выпустить тег v2.0.0,
- постепенно перевести сервисы, обновив
ref.
-
Это особенно важно в больших компаниях и платформах, где:
- Go, Java, Python, фронтенд — десятки/сотни проектов,
- требования к безопасности, логированию, тестированию должны быть единообразны.
Краткая формулировка:
include:позволяет подключать и переиспользовать внешние CI-конфигурации (локальные, из других проектов, из шаблонов).refпри include из другого проекта фиксирует ветку/тег/коммит, откуда брать конфигурацию:- обеспечивает предсказуемость и стабильность пайплайна,
- защищает от неожиданных изменений в репозитории с шаблонами.
Вопрос 45. Как вы деплоили приложения в Kubernetes: использовали ли Helm-чарты?
Таймкод: 00:41:24
Ответ собеседника: правильный. Говорит, что использовал Helm-чарты для деплоя и воспринимает это как стандартный подход.
Правильный ответ:
Helm является де-факто стандартным инструментом пакетирования и деплоя приложений в Kubernetes. В сильном ответе важно показать:
- понимание роли Helm,
- умение структурировать чарты,
- работу с values, шаблонами, зависимостями,
- подходы к разным окружениям,
- интеграцию с CI/CD и безопасностью.
Краткий концептуальный обзор:
Helm решает задачу:
- как описать Kubernetes-манифесты приложения единообразно, параметризуемо и переиспользуемо;
- как устанавливать/обновлять/откатывать релизы приложения в кластере.
Основные сущности:
- Chart:
- пакет, описывающий приложение:
- шаблоны манифестов (templates),
- значения по умолчанию (values.yaml),
- метаданные (Chart.yaml),
- зависимости (charts/ или requirements в старых версиях).
- пакет, описывающий приложение:
- Release:
- установленный экземпляр чарта в конкретном namespace с конкретным набором values.
Ключевые аспекты корректного использования Helm:
- Структура чарта
Типичная структура:
my-app/
Chart.yaml
values.yaml
templates/
deployment.yaml
service.yaml
ingress.yaml
configmap.yaml
secret.yaml
hpa.yaml
-
Chart.yaml:
- имя чарта, версия, описание, совместимость с apiVersion.
-
values.yaml:
- значения по умолчанию:
- образ,
- ресурсы,
- порты,
- env,
- настройки ingress, liveness/readiness, и т.п.
- значения по умолчанию:
- Шаблонизация манифестов
В templates используются Go-шаблоны:
Пример deployment.yaml (фрагмент):
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-app.fullname" . }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "my-app.name" . }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "my-app.name" . }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.service.port }}
env:
- name: ENV
value: {{ .Values.env | quote }}
Это позволяет:
- не дублировать манифесты для разных окружений;
- переиспользовать один чарт с разными values.
- Разные окружения через values-файлы
Подход:
- базовый values.yaml — дефолт.
- override-файлы для окружений:
values-dev.yaml
values-stage.yaml
values-prod.yaml
Примеры:
- Для dev:
replicaCount: 1
image:
tag: "dev-{{ .CI_COMMIT_SHORT_SHA }}"
resources:
limits:
cpu: 200m
memory: 256Mi
- Для prod:
replicaCount: 4
image:
tag: "1.3.0"
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 1
memory: 1Gi
ingress:
enabled: true
hosts:
- my-app.example.com
Деплой:
helm upgrade --install my-app ./my-app -f values-prod.yaml -n prod
Это даёт:
- один чарт,
- разные конфигурации под окружения без копипасты.
- Интеграция с CI/CD
Типичный пайплайн (GitLab CI, пример):
- Stage build:
- собрать Go-бинарь,
- собрать и запушить Docker-образ.
- Stage deploy:
- helm upgrade --install с указанием образа и окружения.
Пример job деплоя:
deploy_prod:
stage: deploy
image: alpine/helm:3.14.0
script:
- helm upgrade --install my-app ./helm/my-app \
--namespace=prod \
--create-namespace \
--set image.repository=registry.example.com/my-app \
--set image.tag=${CI_COMMIT_SHA}
when: manual
only:
- main
Лучшие практики:
- использовать --atomic для безопасного отката при неуспехе;
- использовать --wait для ожидания readiness;
- использовать зафиксированные версии чартов (и своих, и внешних).
- Работа с секретами и конфигурацией
Подходы:
- Секреты:
- НЕ хранить прямо в values.yaml в git в открытом виде;
- варианты:
- sealed-secrets,
- SOPS,
- ExternalSecrets + Vault/Secret Manager;
- подстановка на уровне CI (helm --set или отдельные зашифрованные values).
- Конфиги:
- выносить в ConfigMap/Secret через values;
- шаблонизировать URL-ы, feature-flags, лимиты.
- Зависимости и общие чарты
- Использование зависимостей:
- БД, Kafka, Redis как subchart (для dev/stage) или внешние managed-сервисы.
- Выделение базовых чартов:
- "library chart" для типового сервиса:
- Deployment + Service + Ingress + HPA,
- сервисы подключают его и задают только специфичные параметры.
- "library chart" для типового сервиса:
- Связь с Go-сервисами
Для Go-сервисов Helm позволяет формализовать контракт деплоя:
- значения:
- image.repository/tag,
- ресурсы,
- env,
- probes,
- сервисные порты;
- гарантировать:
- единый стандарт health-check,
- логическую структуру labels/annotations (observability, tracing),
- понятный rollout (rollingUpdate, maxSurge/maxUnavailable).
Краткая формулировка:
- "Да, использую Helm как основной инструмент деплоя в Kubernetes:
- описываю приложение через чарт (Deployment, Service, Ingress, ConfigMap/Secret, HPA),
- разделяю конфигурацию по окружениям через values-файлы,
- интегрирую helm upgrade --install в CI/CD,
- обеспечиваю повторяемость, откаты и стандартизованный подход к деплою Go- и других сервисов."
Вопрос 46. Что происходит в фрагменте Helm-чарта с env и Values, и зачем используется nindent?
Таймкод: 00:41:54
Ответ собеседника: правильный. Объясняет, что Helm подставляет значения из values.yaml в секцию env Deployment'а, корректно описывает использование шаблонов, вложенных переменных и nindent для формирования правильных отступов в YAML.
Правильный ответ:
В Helm-шаблонах блоки с env и обращениями к .Values используются для динамической генерации секции окружения контейнера на основе конфигурации, описанной во values-файлах. nindent применяется для корректного форматирования итогового YAML (отступы), чтобы результат был валидным и читаемым.
Разберём по сути.
Типичный пример фрагмента шаблона:
env:
{{- range $key, $value := .Values.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
или более сложный вариант с nindent:
env:
{{- with .Values.env }}
{{- toYaml . | nindent 2 }}
{{- end }}
Что здесь происходит:
- Использование
.Values
.Values— это объект, содержащий значения из:- values.yaml (дефолт),
- values-override файлов,
- параметров
--setи--set-fileпри helm install/upgrade.
.Values.env:- обычно словарь (map), где:
- ключ — имя переменной окружения,
- значение — её значение.
- обычно словарь (map), где:
- Это позволяет:
- вынести конфигурацию приложения (URLs, feature-flags, креды из SecretRef, режимы) за пределы шаблонов,
- менять поведение без изменения шаблона.
Пример values.yaml:
env:
APP_ENV: "prod"
LOG_LEVEL: "info"
API_URL: "https://api.example.com"
- Генерация блока env в Deployment
С шаблоном:
env:
{{- with .Values.env }}
{{- toYaml . | nindent 2 }}
{{- end }}
Helm делает следующее:
-
with .Values.env:- меняет контекст
.на.Values.env, если он не пустой.
- меняет контекст
-
toYaml .:-
конвертирует map в YAML-структуру.
-
Для примера выше получится:
APP_ENV: prod
LOG_LEVEL: info
API_URL: https://api.example.com
-
-
nindent 2:- добавляет перенос строки и нужные отступы (2 пробела) ко всем строкам результата.
- Это критично, чтобы сгенерированный YAML корректно вложился под ключом
env:.
В итоге итоговый YAML может выглядеть так:
env:
APP_ENV: prod
LOG_LEVEL: info
API_URL: https://api.example.com
или, если шаблон ожидает массив:
env:
- name: APP_ENV
value: "prod"
- name: LOG_LEVEL
value: "info"
(конкретная структура зависит от шаблона; часто используют цикл range для массива env-пар).
- Зачем нужен nindent
Проблема без nindent:
- YAML чувствителен к отступам.
- Если просто вставить
toYamlбез правильных отступов:- структура может оказаться на неверном уровне;
- Kubernetes-манифест станет невалидным.
nindent N делает две вещи:
- добавляет перенос строки перед выводом (в отличие от
indent, который просто сдвигает без "reset"); - добавляет N пробелов в начале каждой строки результата.
Пример:
toYaml .:APP_ENV: prod
LOG_LEVEL: infotoYaml . | nindent 2:APP_ENV: prod
LOG_LEVEL: info
И при наличии:
env:
{{ toYaml . | nindent 2 }}
это становится валидным дочерним блоком под env:.
- Практические выводы и best practices
- Использование
.Values+ шаблонов:- делает чарт гибким:
- разные окружения, разные конфиги → без правки шаблонов;
- Code/Config separation:
- логика деплоя в шаблонах,
- конкретные значения в values.
- делает чарт гибким:
- Использование
nindentи аккуратная работа с отступами:- необходимы для корректности YAML;
- ошибки с отступами — один из частых источников багов в Helm-чартах.
- Для сложных env-конфигураций:
- часто комбинируют:
- статические env,
- динамические через
.Values.env, - ссылку на Secrets/ConfigMaps:
envFrom:
- configMapRef:
name: {{ include "my-app.fullname" . }}-config
- secretRef:
name: {{ include "my-app.fullname" . }}-secret
- часто комбинируют:
Краткая формулировка:
- В данном фрагменте Helm-чарт берёт значения из
.Valuesи генерирует секциюenvдля контейнера. toYamlпревращает map/структуру во вложенный YAML.nindentобеспечивает корректные отступы, чтобы получился валидный Kubernetes-манифест.- Это позволяет централизованно управлять переменными окружения через values-файлы и переиспользовать один шаблон для разных окружений.
Вопрос 47. Что делает второй фрагмент шаблона Helm-чарта с перебором env-переменных?
Таймкод: 00:44:44
Ответ собеседника: правильный. Описывает, что через range по values.env генерируются переменные окружения: ключи нормализуются (в верхний регистр, точки заменяются), значения берутся из value и кавычкуются; корректно интерпретирует логику Go template.
Правильный ответ:
Во втором фрагменте Helm-шаблона используется цикл range по .Values.env (или аналогичной структуре), чтобы динамически сгенерировать список переменных окружения для контейнера. Этот подход позволяет:
- декларативно задать env-переменные в values.yaml;
- автоматически преобразовать ключи к требуемому формату имени переменной;
- корректно экранировать и форматировать значения;
- избежать ручного дублирования env-блоков в шаблонах.
Рассмотрим типичный пример логики (обобщённо):
env:
{{- range $key, $value := .Values.env }}
- name: {{ $key | upper | replace "." "_" | replace "-" "_" }}
value: {{ $value | quote }}
{{- end }}
Что здесь происходит пошагово:
-
range $key, $value := .Values.env:-
Итерируемся по map, определённому в values.yaml.
-
Пример values.yaml:
env:
app.env: "prod"
log-level: "info"
external.api.url: "https://api.example.com"
-
-
Генерация имени переменной (
name):- Применяются функции Go template:
upper— приводит строку к верхнему регистру;replace "." "_"— заменяет точки на подчёркивания;replace "-" "_"— заменяет дефисы на подчёркивания.
- Это нормализует ключ к валидному имени переменной окружения.
Примеры преобразований:
app.env→APP_ENVlog-level→LOG_LEVELexternal.api.url→EXTERNAL_API_URL
Это важный практический приём:
- в values удобно использовать "человеческие" ключи (с точками/дефисами),
- в env внутри контейнера — стандартные UPPER_SNAKE_CASE.
- Применяются функции Go template:
-
Генерация значения (
value):{{ $value | quote }}:- оборачивает значение в кавычки;
- гарантирует корректный YAML (например, для строк с символами
:,true/false, чисел, URL); - избегает неожиданных интерпретаций YAML (например,
on,yes,no).
Примеры:
"prod","info","https://api.example.com".
-
Итоговый сгенерированный YAML
Из приведенного примера values.yaml и шаблона мы получим:
env:
- name: APP_ENV
value: "prod"
- name: LOG_LEVEL
value: "info"
- name: EXTERNAL_API_URL
value: "https://api.example.com"
- Практические плюсы такого подхода:
- Гибкость:
- можно легко добавлять/менять переменные окружения без правки шаблона;
- достаточно обновить values-файл.
- Стандартизация:
- имена env-переменных нормализуются по единым правилам;
- удобно для команд и инструментов.
- Безопасность и корректность:
quoteпредотвращает YAML-ошибки;- можно дополнить логикой для секретов (через
valueFromили отдельные секции).
Расширенный пример с учётом Go-сервисов:
-
В Go-коде:
os.Getenv("APP_ENV")
os.Getenv("LOG_LEVEL")
os.Getenv("EXTERNAL_API_URL") -
В Helm values.yaml:
env:
app.env: "prod"
log-level: "debug"
external.api.url: "https://api.internal.local" -
Шаблон автоматически приведёт всё к корректным именам, и Go-сервис сможет их читать без изменений в коде при добавлении новых переменных.
Краткая формулировка:
Второй фрагмент шаблона:
- итерирует
.Values.env, - нормализует ключи к валидным env-именам (UPPER_SNAKE_CASE),
- подставляет значения с безопасным квотированием,
- формирует секцию
envDeployment’а динамически.
Это правильный и гибкий способ управлять переменными окружения через Helm.
Вопрос 48. Как выстроить процесс CI/CD с нуля в компании, где сейчас всё деплоится вручную?
Таймкод: 00:45:42
Ответ собеседника: правильный. Предлагает конвейер с этапами: линтеры и базовые проверки, сборка и пуш образов в Registry, unit-тесты и нагрузочные тесты, деплой на тест/стейджинг, затем ручной триггер деплоя на прод; логика выстроена последовательно и соответствует подходу continuous delivery.
Правильный ответ:
Правильный подход — не просто "запустить pipeline", а построить управляемую, воспроизводимую и безопасную цепочку доставки, которая:
- опирается на Git как единственный источник правды,
- гарантирует качество (тесты, проверки, security),
- обеспечивает быстрый фидбек разработчикам,
- делает деплой на прод контролируемым и предсказуемым,
- минимизирует ручные, нефиксируемые действия.
Ниже — практическая, зрелая схема, ориентированная на микросервисы, Kubernetes, Go и контейнеры, которую можно внедрять поэтапно.
- Базовые принципы
С самого начала фиксируем фундамент:
- Git-flow/Trunk-based:
- main/master — только через Merge Request (MR),
- feature-ветки,
- ревью обязательны.
- Infrastructure as Code:
- Dockerfile,
- Helm-чарты/Kustomize,
- манифесты Kubernetes,
- CI-конфигурации (.gitlab-ci.yml и аналогичные) — всё хранится в репозитории.
- Один артефакт — множество окружений:
- один и тот же образ/бинарь идёт на dev → stage → prod;
- меняются только конфигурации (values, secrets, URLs).
- Структура CI-пайплайна
Минимально рекомендуемые стадии:
- lint
- test (unit)
- build
- security / quality
- integration / e2e
- package (docker/helm)
- deploy-dev
- deploy-stage
- deploy-prod
Пример для GitLab CI (упрощённая схема):
stages:
- lint
- test
- build
- docker
- deploy_dev
- deploy_stage
- deploy_prod
- Линт и быстрые проверки (lint stage)
Цель: максимально ранний и быстрый фидбек.
Для Go-сервисов:
lint:
stage: lint
image: golang:1.22
script:
- go vet ./...
- golangci-lint run ./...
only:
- merge_requests
- main
Дополнительно:
- форматирование (go fmt),
- проверка Dockerfile,
- проверка Helm-чартов (helm lint),
- базовые SAST-инструменты.
- Unit-тесты (test stage)
Без прохождения тестов — никаких сборок образов и деплоев.
test:
stage: test
image: golang:1.22
script:
- go test ./... -race -coverprofile=coverage.out
artifacts:
paths:
- coverage.out
only:
- merge_requests
- main
Важно:
- тесты должны быть быстрыми,
- запускаться на каждый MR,
- стандарт качества: код без тестов в main не допускается.
- Сборка артефакта и Docker-образа (build + docker)
Сборка бинаря (детерминированная):
build:
stage: build
image: golang:1.22
script:
- CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags "-s -w -X main.commit=${CI_COMMIT_SHORT_SHA}" -o app ./cmd/app
artifacts:
paths:
- app
only:
- main
Сборка Docker-образа (multi-stage, минимальный рантайм):
FROM golang:1.22 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o app ./cmd/app
FROM gcr.io/distroless/base-debian12
WORKDIR /app
COPY --from=build /src/app .
USER nonroot:nonroot
ENTRYPOINT ["/app/app"]
В CI:
docker:
stage: docker
image: docker:24
services:
- docker:24-dind
script:
- docker build -t registry.example.com/my-app:${CI_COMMIT_SHA} .
- docker push registry.example.com/my-app:${CI_COMMIT_SHA}
only:
- main
Практика:
- использовать commit SHA/теги релиза в качестве image tag;
- не использовать плавающие latest как единственный тег.
- Security и качество
На зрелом уровне обязательно:
- Сканирование зависимостей:
- Go: govulncheck, trivy fs/repo.
- Сканирование Docker-образов:
- trivy, grype.
- Политики:
- фейлить пайплайн при критичных уязвимостях (или хотя бы алертить).
Это можно оформить отдельной стадией или шагом после сборки образа.
- Автоматический деплой на dev/test (deploy_dev)
После успешных тестов и сборки:
- автоматический деплой в dev-окружение.
Если используется Helm:
deploy_dev:
stage: deploy_dev
image: alpine/helm:3.14.0
script:
- helm upgrade --install my-app ./helm/my-app \
--namespace=dev --create-namespace \
--set image.repository=registry.example.com/my-app \
--set image.tag=${CI_COMMIT_SHA}
only:
- main
Цели:
- быстрый feedback loop;
- все изменения в main автоматически доступны в dev.
- Деплой на stage (deploy_stage)
Здесь добавляются:
- дополнительные проверки,
- smoke/e2e-тесты,
- требование, чтобы stage был максимально близок к prod.
Деплой:
- либо автоматический при условии успеха предыдущих стадий,
- либо с ручным подтверждением (when: manual).
deploy_stage:
stage: deploy_stage
image: alpine/helm:3.14.0
script:
- helm upgrade --install my-app ./helm/my-app \
--namespace=stage \
--set image.repository=registry.example.com/my-app \
--set image.tag=${CI_COMMIT_SHA}
when: manual
only:
- main
Плюс:
- возможен автоматический запуск e2e/нагрузочных тестов против stage.
- Деплой на prod (deploy_prod) — controlled Continuous Delivery
Прод — всегда:
- прозрачно,
- воспроизводимо,
- управляемо.
Минимум:
- ручной триггер (manual job / protected environment),
- ограниченный круг людей,
- логирование факта деплоя.
deploy_prod:
stage: deploy_prod
image: alpine/helm:3.14.0
script:
- helm upgrade --install my-app ./helm/my-app \
--namespace=prod \
--set image.repository=registry.example.com/my-app \
--set image.tag=${CI_COMMIT_SHA} \
--atomic --wait
when: manual
environment:
name: production
url: https://my-app.example.com
only:
- main
Рекомендуется:
- использовать
--atomicи--wait:- при неуспешном релизе — автоматический rollback;
- включить мониторинг и алерты:
- деплой без наблюдаемости — риск.
- Эволюция: GitOps и продвинутая стратегия
После базового CI/CD можно перейти к:
- GitOps:
- Argo CD / Flux:
- CI только билдит и пушит образы,
- состояние кластеров управляется декларативно через git-репозитории манифестов/Helm-чартов,
- изменения в git → автоматический sync в кластере.
- Argo CD / Flux:
- Blue-Green / Canary:
- через Istio/Ingress/Service Mesh/Argo Rollouts:
- плавный перевод трафика,
- автоматический rollback при деградации метрик.
- через Istio/Ingress/Service Mesh/Argo Rollouts:
- Политики безопасности:
- OPA/Gatekeeper, Kyverno:
- запрет небезопасных образов, прав, capabilities;
- Image Signing (Cosign, Notary).
- OPA/Gatekeeper, Kyverno:
- Поэтапное внедрение в компании "с ручным деплоем"
Реалистичный план:
- Этап 1:
- Ввести Git как обязательный workflow.
- Добавить линтеры и unit-тесты на MR.
- Этап 2:
- Ввести детерминированную сборку артефактов и Docker-образов.
- Подключить Registry.
- Этап 3:
- Автоматический деплой на dev.
- Простые Helm-чарты для сервисов.
- Этап 4:
- Stage-окружение, e2e, нагрузочные тесты.
- Manual деплой на prod через CI.
- Этап 5:
- Наблюдаемость, алерты, GitOps, canary/blue-green.
Краткая итоговая формулировка:
- "Я бы выстроил CI/CD вокруг:
- Git как единственного источника правды,
- автоматических линтов и тестов на каждый MR,
- детерминированной сборки контейнеров,
- Helm-деплоя в Kubernetes на dev/stage,
- контролируемого деплоя на prod через manual approval и
helm upgrade --atomic --wait, - с последующей эволюцией в сторону GitOps и прогрессивных выкатов.
- Фокус — на воспроизводимости, прозрачности и постепенном уменьшении ручных, неаудируемых операций."
Вопрос 49. Какой у вас опыт работы с Ansible и писали ли вы собственные роли?
Таймкод: 00:48:14
Ответ собеседника: правильный. Использовал Ansible для деплоя в проектах без Kubernetes и в закрытом контуре, писал собственные роли и плейбуки, а не только пользовался готовыми.
Правильный ответ:
Сильный ответ про опыт с Ansible должен показывать умение:
- проектировать инфраструктуру как код,
- писать переиспользуемые роли,
- работать в закрытых контурах,
- аккуратно управлять конфигурацией, секретами и идемпотентностью,
- интегрировать Ansible в общий CI/CD-процесс.
Ключевые аспекты.
Общие принципы работы с Ansible:
- Agentless:
- не требует агентов, использует SSH/WinRM.
- Идемпотентность:
- повторный прогон playbook не должен ломать состояние.
- Declarative behavior поверх императивных задач:
- описываем требуемое состояние, а не последовательность "ручных" операций.
Практические зоны применения:
- Provisioning и конфигурирование серверов
Типичные задачи:
- установка и настройка:
- Docker/containerd,
- Nginx/HAProxy,
- PostgreSQL/Redis/RabbitMQ,
- Java/Go runtime, Python, системных библиотек.
- базовый hardening:
- SSH-настройки,
- sudo-политики,
- firewall (ufw/iptables/firewalld),
- пользователи и группы.
- настройка сервисов как systemd-юнитов.
Пример фрагмента playbook:
- hosts: app_servers
become: true
roles:
- common
- docker
- go-app
- Работа в закрытом контуре
Особенности:
- запрет прямого доступа в интернет:
- используем локальные репозитории (apt/yum mirror, Nexus/Artifactory),
- собственные артефакт-репозитории и registry.
- роли:
- настраивают систему на использование внутренних зеркал;
- не тянут пакеты "снаружи".
- управление секретами:
- Ansible Vault для шифрования:
- паролей БД,
- ключей к приватным репозиториям,
- сертификатов.
- Ansible Vault для шифрования:
Пример использования Vault (упрощённо):
db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256...
- Собственные роли: структура, переиспользуемость
Хорошая роль:
- структурирована и изолирует ответственность:
roles/
go-app/
tasks/
main.yml
templates/
app.service.j2
files/
vars/
defaults/
handlers/
main.yml
Пример роли для деплоя Go-сервиса (без Kubernetes):
tasks/main.yml:
- name: Create app directory
file:
path: /opt/my-app
state: directory
owner: app
group: app
mode: '0755'
- name: Copy binary
copy:
src: files/my-app
dest: /opt/my-app/my-app
owner: app
group: app
mode: '0755'
- name: Install systemd unit
template:
src: app.service.j2
dest: /etc/systemd/system/my-app.service
notify: Restart my-app
handlers/main.yml:
- name: Restart my-app
systemd:
name: my-app
state: restarted
enabled: true
daemon_reload: true
Такая роль:
- идемпотентна,
- легко переиспользуется для разных окружений,
- хорошо ложится в CI/CD (ansible-playbook как шаг деплоя).
- Интеграция Ansible в CI/CD
Сильный подход:
- playbook’и и роли — в git,
- проверки:
- ansible-lint,
- yamllint,
- запуск из CI:
- для provisioning окружений,
- для деплоя приложений в non-k8s инфраструктуре,
- использование inventory:
- статического,
- динамического (cloud, CMDB),
- шаблонов под окружения (dev/stage/prod).
- Работа с шаблонами и конфигами
Через Jinja2:
- параметризация конфигов:
- environment-specific значения,
- endpoints, credentials (через Vault),
- feature-флаги.
- пример шаблона systemd юнита для Go-сервиса:
[Unit]
Description=My Go Service
After=network-online.target
[Service]
User=app
Group=app
WorkingDirectory=/opt/my-app
ExecStart=/opt/my-app/my-app --env={{ env }} --config={{ config_path }}
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
- Idempotency и качество ролей
Критично:
- не использовать "shell/command" там, где есть модули:
apt,yum,service,user,lineinfile,template,unarchiveи т.д.;
- явно указывать состояние:
state: present/absent/latest,
- избегать неопределенного поведения (случайных изменений при каждом запуске).
Это делает:
- роли безопасными для многократного запуска,
- инфраструктуру предсказуемой.
Краткая формулировка сильного ответа:
- "Использовал Ansible как основной инструмент автоматизации в средах без Kubernetes и в изолированных контурах:
- писал собственные роли и playbook’и для установки middleware, настройки сервисов, деплоя Go/Java-приложений, управления systemd, настройки внутренних репозиториев и зеркал,
- применял Ansible Vault для секретов,
- следил за идемпотентностью задач и структурой ролей,
- интегрировал Ansible-запуски в CI/CD, чтобы получить воспроизводимые и документированные изменения инфраструктуры."
Вопрос 50. Как с помощью Ansible проверить доступность хостов без написания плейбука?
Таймкод: 00:49:19
Ответ собеседника: правильный. После уточнения говорит, что можно использовать модуль ping Ansible для проверки доступности узлов.
Правильный ответ:
Ansible позволяет проверить доступность хостов без отдельного плейбука с помощью adhoc-команд. Это удобный способ быстро убедиться:
- что инвентори настроен корректно,
- что SSH-доступ работает,
- что sudo/remote_user/ключи сконфигурированы.
Ключевой инструмент — модуль ping, вызываемый через ansible в one-liner.
Основная команда:
ansible all -m ping
Расшифровка:
ansible— CLI для ad-hoc выполнения модулей (в отличие отansible-playbook).all— группа/шаблон хостов из inventory (можно указать конкретную группу или хост):- например:
web,db,k8s_nodes,app-01.
- например:
-m ping— использовать модуль ping.
Важно понимать:
- Модуль ping:
- не ICMP ping;
- Ansible-пинг:
- проверяет возможность подключиться по SSH,
- выполнить простой Python-код (или raw-режим для
pingбез Python), - возвращает "pong" при успешном выполнении.
- Если ответ успешен:
- значит хост доступен,
- учётные данные корректны,
- базовые требования Ansible соблюдены.
Примеры:
- Проверить только группу web:
ansible web -m ping
- Использовать конкретного пользователя:
ansible all -m ping -u ansible
- С sudo (become):
ansible all -m ping -u ansible --become
Если хосты недоступны:
- увидите ошибки:
- SSH timeout,
- Permission denied,
- отсутствие Python (для стандартного ping),
- это помогает быстро локализовать проблемные машины или некорректные настройки.
Краткая формулировка:
- Чтобы без плейбука проверить доступность хостов в Ansible, используют ad-hoc команду:
ansible <hosts> -m ping
- Это быстрый health-check SSH-доступа и базовой работоспособности Ansible-инфраструктуры.
Вопрос 51. Можно ли с помощью Ansible установить Python на удалённый сервер, если Python там ещё не установлен?
Таймкод: 00:49:55
Ответ собеседника: неполный. Путается в требованиях Ansible, упоминает отсутствие агентов, интуитивно говорит про удалённое выполнение команд через SSH, но не поясняет, что большинство модулей требуют Python на целевой машине и как правильно обойти это ограничение.
Правильный ответ:
Кратко: да, можно. Ansible действительно требует Python на целевой машине для большинства модулей, но:
- подключение осуществляется по SSH без агентов;
- есть специальный режим/подход для хостов без Python:
- использование
raw(и похожих низкоуровневых механизмов), чтобы сначала установить Python; - затем уже использовать обычные Ansible-модули.
- использование
Нужно чётко понимать механизм.
- Требования Ansible к целевому хосту
По умолчанию:
- Ansible:
- подключается по SSH,
- копирует и запускает на целевой ноде маленькие Python-скрипты (модули),
- значит, ожидает наличие Python (обычно python или python3).
Если на хосте нет Python:
- большинство модулей (apt, yum, user, template, service и т.д.) работать не смогут;
- но есть исключения —
raw,script, некоторые Windows/сетевые модули и специальная логика "bootstrap".
- Как установить Python на чистый сервер с помощью Ansible
Подход:
- Использовать модуль
raw:- он отправляет команду как есть по SSH, без зависимости от Python;
- подходит для первичной установки Python.
Пример adhoc-команды:
ansible all -i hosts -m raw -a "apt-get update && apt-get install -y python3"
или для RHEL/CentOS:
ansible all -i hosts -m raw -a "yum install -y python3"
Дальше:
- после установки Python:
- можно использовать обычные модули Ansible (apt, yum, package, service, и т.д.).
- Использование в playbook’ах (bootstrap-паттерн)
Часто делают bootstrap-задачу:
- hosts: new_servers
gather_facts: no
tasks:
- name: Install python3 on Debian/Ubuntu
raw: |
if command -v apt-get >/dev/null 2>&1; then
apt-get update -y && apt-get install -y python3
fi
- name: Install python3 on RHEL/CentOS
raw: |
if command -v yum >/dev/null 2>&1; then
yum install -y python3
fi
- name: Gather facts after python installation
setup:
Комментарии:
gather_facts: no:- отключаем автоматический сбор фактов, который требует Python.
- Используем
raw:- команды выполняются напрямую через shell удалённого хоста.
- После установки Python:
- запускаем
setup(модуль для сбора фактов), - дальше playbook может использовать обычные модули.
- запускаем
- Важные нюансы и best practices
- Дистрибутив-зависимость:
- команды установки Python отличаются:
- apt (Debian/Ubuntu):
apt-get install -y python3 - yum/dnf (RHEL/Alma/Rocky/CentOS):
yum install -y python3илиdnf install -y python3 - zypper, apk и т.д. — свои команды.
- apt (Debian/Ubuntu):
- В bootstrap-логике обычно:
- проверяют, какой пакетный менеджер доступен,
- выполняют нужную команду через
raw.
- команды установки Python отличаются:
- Ошибки и идемпотентность:
raw— низкоуровневый инструмент, сам по себе не идемпотентен;- стоит писать команды так, чтобы повторный запуск не ломал систему.
- Безопасность:
- убедиться, что вы точно управляете нужными хостами;
- не выполнять "агрессивные" raw-команды без проверки.
- Чего ответ не должен говорить (и типичные заблуждения)
Неверно:
- "Ansible не может работать без Python вообще" — частично неверно:
- управляющая машина требует Python,
- целевой хост для большинства модулей — да,
- но bootstrap через
rawвозможен.
- "Нужно руками зайти и поставить Python" — это как раз то, что Ansible помогает автоматизировать.
Корректная формулировка для интервью:
- "Да, Ansible обычно требует Python на целевом хосте, так как модули запускаются как Python-скрипты. Но для начального bootstrap на 'голых' серверах можно использовать модуль raw или ad-hoc команды:
- через raw выполнить установку python3 (через apt/yum/dnf),
- после этого уже использовать стандартные Ansible-модули.
- В playbook’ах это оформляется отдельной bootstrap-задачей с gather_facts: no.
- Такой подход позволяет автоматически разворачивать даже полностью чистые сервера."
Вопрос 52. Какой у вас опыт работы с системами логирования и мониторинга (например, ELK/Elastic Stack)?
Таймкод: 00:52:09
Ответ собеседника: правильный. Говорит, что ELK в инфраструктуре был, но сам его почти не администрировал; признаёт, что боевого опыта в логировании, мониторинге и алертинге немного.
Правильный ответ:
В хорошей практике от разработчика и инженера, работающего с продакшн-системами и Kubernetes/микросервисами, ожидается:
- понимание архитектуры систем логирования и мониторинга;
- умение встраивать приложения (особенно на Go) в существующий стек observability;
- базовая насмотренность по ELK/EFK, Prometheus, Alertmanager, Tempo/Jaeger и т.п.;
- осознанный подход к метрикам, логам, трассировкам и алертам.
Ниже — структурированный обзор, который показывает зрелое понимание, даже если разворачивать кластеры Elastic лично приходилось не всегда.
Компоненты логирования (на примере ELK/EFK)
Типичный стек централизованного логирования:
- Источники логов:
- приложения (Go, Java, Python),
- nginx/ingress, системные сервисы, Kubernetes события.
- Агент/коллектор на ноде/в поде:
- Filebeat, Fluent Bit, Vector, Promtail, Logstash.
- Хранилище и поиск:
- Elasticsearch / OpenSearch.
- Визуализация:
- Kibana / OpenSearch Dashboards.
- Иногда:
- брокер (Kafka, Redis) как буфер для логов.
Архитектура для Kubernetes (EFK-подход):
- Fluent Bit / Filebeat как DaemonSet:
- читает логи контейнеров из /var/log/containers или stdout;
- парсит JSON, добавляет метаданные (namespace, pod, labels, container);
- отправляет в:
- Elasticsearch,
- Loki,
- Kafka, S3 и др.
- Elasticsearch:
- хранит логи с индексами по времени и типам;
- позволяет быстрый поиск и агрегации.
- Kibana:
- дешборды, фильтры, лог-аналитика, saved searches.
Ключевые практики для приложений на Go:
-
Логировать структурировано (JSON):
log := zerolog.New(os.Stdout).With().Timestamp().Logger()
log.Info().
Str("service", "payment-api").
Str("request_id", reqID).
Msg("payment processed") -
Обязательно включать:
- correlation-id / trace-id,
- service name, environment, версию,
- ключевые бизнес-поля.
-
Не логировать секреты.
-
Использовать stdout/stderr в контейнерах:
- пусть агент соберёт и отправит дальше.
Мониторинг метрик (Prometheus-экосистема)
Стандартный продакшн-стек мониторинга:
- Prometheus:
- собирает метрики pull-моделью по HTTP /metrics.
- Exporters:
- node_exporter (ноды),
- cAdvisor/kube-state-metrics (Kubernetes),
- DB-exporters (PostgreSQL, Redis, Kafka и т.п.).
- Alertmanager:
- маршрутизация алертов (Slack, email, PagerDuty).
- Grafana:
- дешборды по метрикам.
Практики для Go-приложений:
-
Использовать prometheus client_golang:
var (
reqTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"path", "method", "status"},
)
)
func init() {
prometheus.MustRegister(reqTotal)
} -
Экспортировать /metrics:
http.Handle("/metrics", promhttp.Handler()) -
В Kubernetes:
- добавить аннотации для автоматического scrape:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
- добавить аннотации для автоматического scrape:
-
Следить за:
- latency (histogram/summary),
- error rate,
- RPS,
- ресурсами (CPU/RAM, gc паузы),
- бизнес-метриками (успешные платежи, очередь задач, и т.п.).
Distributed tracing (Jaeger, Tempo, OpenTelemetry)
Для сложных микросервисов:
- Используется трассировка:
- Jaeger, Tempo, Zipkin, OTel Collector;
- Go-приложения:
- интеграция через OpenTelemetry SDK;
- проброс trace-id в логи и метрики;
- возможность отследить запрос от API Gateway до БД.
Зрелый подход к алертингу
Ключевой принцип:
-
Алерты не по "CPU > 80%", а по симптомам и SLO:
- error rate > X%,
- p95/p99 latency выше порога,
- нет успешных запросов определенного типа,
- не обновляется бизнес-счётчик.
Инструменты:
- Prometheus + Alertmanager:
- правила alerting,
- маршруты по severity, сервисам, командам.
Примеры production-алертов:
- "5xx rate > 5% в течение 5 минут"
- "DB connection errors > N"
- "Нет ни одного живого pod у критичного сервиса"
- "Kafka consumer lag превышает порог"
Связь с Kubernetes и инфраструктурой
В продакшн-кластерах:
- Логи:
- централизованы (EFK/ELK/Loki);
- доступны разработчикам;
- размечены по namespace, сервисам, релизам.
- Метрики:
- по приложениям, нодам, кластерам;
- дешборды per service + platform overview.
- Трейсы:
- для сложных запросов и дебага деградаций.
- Всё это:
- интегрировано в CI/CD (канареечные выкатки, auto-rollback при ухудшении метрик).
Краткая формулировка сильного ответа:
- "Даже если сам не поднимал Elastic-кластер с нуля, я понимаю, как должен выглядеть продакшн-стек observability:
- централизованные логи (ELK/EFK, структурированный JSON, контекстные поля),
- метрики (Prometheus, экспорт /metrics в Go-сервисах, дешборды в Grafana),
- алерты по SLO и ключевым техническим/бизнесовым метрикам,
- при необходимости — распределённый трейсинг (OpenTelemetry + Jaeger/Tempo).
- В своих сервисах закладываю корректное логирование, экспонирование метрик и поддержку trace-id, чтобы их можно было органично встроить в существующий стек мониторинга и логирования."
Такой уровень ответа показывает понимание не только инструментов, но и культуры эксплуатации продакшн-систем.
Вопрос 53. Какое направление развития вам наиболее интересно: продолжать фокус на DevOps/CI/CD или углубляться в администрирование Linux и инфраструктуры?
Таймкод: 00:52:53
Ответ собеседника: правильный. Говорит, что занимался развёртыванием инфраструктуры и DevOps-практиками, ему интересно углубляться и в системное администрирование/Linux, демонстрирует готовность совмещать DevOps и инфраструктурные задачи.
Правильный ответ:
Сильный, зрелый ответ на этот вопрос не про выбор "DevOps vs Linux администрирование", а про понимание, что:
- эффективная работа с CI/CD, Kubernetes, облаками, сервис-мешами, безопасностью и наблюдаемостью невозможна без уверенной базы в Linux, сети и инфраструктуре;
- и, наоборот, современный "классический" админ/инфраструктурный инженер должен мыслить как разработчик: инфраструктура как код, автоматизация, воспроизводимость, тестируемость.
Оптимальная позиция звучит так:
- Инфраструктура как код и DevOps-подход — основной вектор
Приоритет:
- строить воспроизводимую инфраструктуру:
- Terraform/CloudFormation/Pulumi для облаков,
- Ansible/Salt для конфигурации,
- Helm/Kustomize/Operators для Kubernetes,
- GitOps (Argo CD/Flux) для управления манифестами.
- автоматизировать всё, что можно:
- CI/CD-пайплайны,
- деплой,
- миграции,
- ротацию секретов,
- обновление кластеров и сервисов.
Фокус на:
- качественных пайплайнах (build/test/scan/deploy),
- стандартизованных шаблонах для сервисов (Go/Java/Python),
- безопасной поставке (SAST, DAST, image scanning, policy-as-code),
- интеграции с Kubernetes и сервис-мешами.
- Углубление в Linux и инфраструктуру — обязательное условие, а не "альтернатива"
Для уверенной работы нужны глубокие знания:
- Linux:
- namespaces, cgroups, systemd,
- сеть (iptables/nftables, routing, MTU, TCP tune),
- storage (файловые системы, RAID, LVM, I/O, latency),
- безопасность (users, capabilities, SELinux/AppArmor).
- Сети:
- L3/L4, DNS, Load Balancing,
- VPN, BGP (для продвинутых CNI и multi-cluster),
- TLS, mTLS, PKI.
- Kubernetes internals:
- как действительно работают kubelet, kube-proxy, API server, контроллеры,
- CNI/CSI, сетевые политики, ingress, сервис-меш,
- продакшн-паттерны для stateful-сервисов.
- Наблюдаемость:
- Prometheus/Grafana, логирование (ELK/Loki), трассировка (OTel, Jaeger),
- SLO/SLA, error budget, алертинг по симптомам, а не по CPU.
Это не отдельный трек, а фундамент, который позволяет:
- не "магически настраивать Helm-чарты", а понимать, почему запросы падают по timeout;
- не "просто писать пайплайн", а видеть, как под капотом работает окружение, куда он деплоит.
- Совмещение DevOps, SRE и инфраструктурного подхода
Зрелый ответ демонстрирует стремление:
- проектировать системы целиком:
- от кода (Go/сервисы),
- через CI/CD и инфраструктуру,
- до эксплуатации (мониторинг, алерты, инцидент-менеджмент, постмортемы).
- брать ответственность за:
- надёжность (reliability),
- отказоустойчивость,
- наблюдаемость,
- производительность,
- безопасность.
Пример формулировки:
- "Интересно продолжать развиваться в направлении автоматизации, CI/CD, Kubernetes и облачной инфраструктуры, при этом целенаправленно углубляясь в системное администрирование Linux, сеть и storage. Хочу не просто собирать пайплайны и чарты, а понимать, как работает платформа на всех уровнях: от ядра и сети до сервис-меша и GitOps. Тогда можно строить решения, которые действительно надёжны и предсказуемы в продакшене."
Такой ответ показывает:
- технологическую зрелость,
- ориентацию на end-to-end ответственность за платформу,
- понимание, что современный инженер должен уверенно держать и DevOps-практики, и инфраструктурный фундамент.
Вопрос 54. Почему вы рассматриваете смену работы и как оцениваете развитие на текущем месте?
Таймкод: 00:55:27
Ответ собеседника: правильный. Отмечает, что фраза "нет развития" некорректна и он её больше использовать не будет; говорит, что развитие возможно, но ему не близка текущая предметная область (автодороги), и он хочет перейти в финтех или более продуктовую разработку.
Правильный ответ:
На такой вопрос важен зрелый, спокойный и прозрачный ответ, который:
- не критикует в лоб текущего работодателя,
- показывает осознанность выбора,
- подчёркивает мотивацию к росту и смене домена, а не бегство от проблем,
- привязывает смену работы к целям развития компетенций и ответственности.
Сильная позиция может выглядеть так:
- Признание ценности текущего опыта
- Текущее место дало:
- опыт развёртывания и автоматизации инфраструктуры,
- понимание CI/CD, контейнеризации, Kubernetes, Ansible,
- работу с промышленными требованиями к надёжности.
- Нет смысла говорить "нет развития":
- развитие почти всегда есть — техническое, процессное, доменное;
- важно показать уважение к команде и проекту.
- Чёткая формулировка причин смены
Ключевой акцент — не на "плохом", а на несоответствии долгосрочным целям:
- Не совпадает домен:
- инфраструктура/проекты связаны, например, с автодорогами, гос-системами, интеграциями, где:
- цикл принятия решений долгий,
- меньше возможностей влиять на продукт,
- меньше фокуса на высоконагруженных распределённых сервисах.
- инфраструктура/проекты связаны, например, с автодорогами, гос-системами, интеграциями, где:
- Хочется уйти в область:
- финтех,
- платёжные системы,
- highload B2C-продукты,
- платформенные решения,
- где:
- высокие требования к отказоустойчивости, транзакционной целостности, безопасности;
- понятные метрики ценности (uptime, latency, конверсия, SLA/SLO);
- сложные технические задачи: производительность, масштабирование, распределённые системы, event-driven архитектуры.
Такая мотивация показывает:
- ориентацию на более сложные и ответственные задачи,
- интерес к предметным областям, где инфраструктура и разработка критичны для бизнеса.
- Связь с профессиональным развитием
Хорошо подчеркнуть, что смена работы — это инструмент для:
- углубления компетенций в:
- Go и backend-разработке под продакшн-нагрузки,
- Kubernetes, сервис-мешах, observability,
- построении надёжных CI/CD конвейеров,
- безопасности (secret management, policy-as-code, compliance),
- проектировании архитектуры (микросервисы, очереди, кэширование, resiliency-паттерны).
- работы в среде, где:
- есть культура code review, архитектурных обсуждений, incident-постмортемов,
- ценится инженерное качество и прозрачная ответственность.
- Формулировка, которую хорошо воспринимают на собеседовании
Пример корректного ответа:
- "На текущем месте я получил хороший опыт в развёртывании инфраструктуры, CI/CD и автоматизации, за это я команде благодарен. Сейчас хочу двигаться дальше в сторону более сложных продуктовых и высоконагруженных систем, где выше требования к надёжности, безопасности и качеству архитектуры. Текущий домен мне концептуально менее близок, чем, например, финтех или продуктовые сервисы, поэтому ищу среду, где мой фокус на инженерных практиках, Go/Kubernetes и платформенных решениях будет максимально востребован и даст больший вклад."
Такой ответ:
- честный,
- уважительный к текущему работодателю,
- демонстрирует зрелую мотивацию и фокус на профессиональном росте, а не эмоциональную реакцию.
Вопрос 55. Какой у вас опыт разработки на Python и какие задачи вы решали?
Таймкод: 00:56:27
Ответ собеседника: правильный. Говорит, что регулярно пишет скрипты на работе, делал ботов на Python, учебные проекты на Django, подчёркивает уверенное владение языком и алгоритмами.
Правильный ответ:
На этот вопрос важно отвечать так, чтобы было видно:
- уверенное владение базой языка;
- опыт прикладного использования в реальных задачах (автоматизация, сервисы, интеграции);
- понимание качества кода, тестирования и производительности;
- умение сочетать Python с остальным стеком (Go, Docker, CI/CD, Kubernetes).
Сильное содержание ответа может включать:
- Скрипты и автоматизация инфраструктуры
- Регулярное использование Python для:
- служебных утилит вокруг CI/CD:
- генерация конфигов,
- валидация YAML/JSON/Helm values,
- интеграция с GitLab/Jenkins API;
- работы с облаками и API:
- управление ресурсами через boto3 (AWS), google-api-python-client и т.п.;
- миграций и обслуживания:
- подготовка данных,
- массовые проверки/изменения, аудит конфигураций.
- служебных утилит вокруг CI/CD:
Пример небольшого утилитарного скрипта (запрос в REST API и агрегация):
import requests
def fetch_all_pages(base_url, token):
page = 1
items = []
headers = {"Authorization": f"Bearer {token}"}
while True:
r = requests.get(base_url, headers=headers, params={"page": page})
r.raise_for_status()
data = r.json()
if not data:
break
items.extend(data)
page += 1
return items
if __name__ == "__main__":
services = fetch_all_pages("https://api.example.com/services", "TOKEN")
critical = [s for s in services if s["status"] != "OK"]
print(f"Found {len(critical)} problematic services")
Зрелый подход:
- использовать virtualenv/poetry/pip-tools;
- фиксировать зависимости;
- писать понятный, поддерживаемый код.
- Веб-разработка и сервисы
- Опыт с Django / Flask / FastAPI:
- разработка REST API;
- интеграция с БД (PostgreSQL, MySQL);
- авторизация, простые CRUD-сервисы;
- понимание middleware, ORM, миграций и структуры проекта.
Пример минимального API на FastAPI (хорош к микросервисам и интеграциям):
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
id: int
name: str
DB = {}
@app.post("/items")
def create_item(item: Item):
DB[item.id] = item
return item
@app.get("/items/{item_id}")
def get_item(item_id: int):
return DB.get(item_id)
Важно:
- умение упаковать Python-сервис в Docker;
- добавить health-check, логирование, метрики, конфигурацию через env.
- Боты, интеграции, вспомогательные сервисы
- Телеграм-/Slack-боты для:
- уведомлений из CI/CD;
- алертов о статусе деплоев, мониторинга;
- внутренних инструментов (запрос логов/метрик, управление окружением).
- Интеграция с внешними API:
- платежи, биллинг, CRM, GitLab/GitHub, Jira.
Такой опыт подчёркивает:
- уверенную работу с HTTP, JSON, OAuth/токенами;
- умение проектировать небольшие, но полезные сервисы.
- Качество кода, тестирование, структура
Ожидается понимание:
- модульного тестирования:
- pytest, unittest;
- логирования:
- logging c понятным форматированием;
- обработки ошибок:
- честные исключения, явные ошибки при работе с сетью/БД;
- стиля:
- PEP8, type hints (mypy), black/isort/flake8.
Пример:
import logging
log = logging.getLogger(__name__)
def divide(a: float, b: float) -> float:
if b == 0:
log.error("Division by zero: a=%s", a)
raise ValueError("b must not be zero")
return a / b
- Взаимодействие с остальным стеком
В сильном ответе полезно показать, что Python — часть общей картины:
- как инструмент для:
- glue-кода между системами,
- прототипирования,
- миграций и утилит;
- при этом для высоконагруженных backend-сервисов в продакшене используется Go:
- Python можно применять для оркестрации, админ-сервисов, ETL, внутренних тулов.
- Краткая формулировка
Хороший, уверенный ответ может звучать так:
- "Регулярно использую Python для автоматизации и вспомогательных сервисов:
- пишу утилиты для CI/CD, работы с API, миграции данных;
- делал ботов и небольшие web-сервисы (Flask/FastAPI/Django),
- уверенно владею базой языка, структурой проектов, тестированием и типизацией.
- Встраиваю Python-скрипты в общий процесс: Docker, GitLab CI, Kubernetes.
- Для основных высоконагруженных сервисов использую Go, а Python — как гибкий инструмент для интеграций и инфраструктурных задач."
Такой ответ демонстрирует не просто "писал скрипты", а зрелое использование Python в продуманном инженерном контексте.
Вопрос 56. Как в Python-приложении получить значение переменной окружения и какую библиотеку для этого нужно импортировать?
Таймкод: 00:57:27
Ответ собеседника: неполный. Помнит про getenv и связь с env, но не называет модуль os, ссылается на документацию; базовое направление верное, но знание не сформулировано чётко.
Правильный ответ:
Для работы с переменными окружения в Python используется стандартный модуль os.
Основные способы:
- Через
os.getenv:
- Возвращает значение переменной окружения или
None(или заданное значение по умолчанию), если переменная не установлена.
Пример:
import os
db_host = os.getenv("DB_HOST")
db_port = os.getenv("DB_PORT", "5432") # значение по умолчанию
if not db_host:
raise RuntimeError("DB_HOST is not set")
print(f"Connecting to DB at {db_host}:{db_port}")
- Через
os.environ:
os.environ— отображение (dict-like) всех переменных окружения процесса.- Бросает KeyError, если переменной нет.
Пример:
import os
db_host = os.environ["DB_HOST"] # упадёт, если переменной нет
db_port = os.environ.get("DB_PORT", "5432") # безопаснее
print(db_host, db_port)
Практические рекомендации (особенно для продакшн-сервисов, включая контейнеры и Kubernetes):
- Использовать переменные окружения для конфигурации:
- адреса БД, очередей, feature flags, режим окружения (ENV, LOG_LEVEL и т.п.).
- Явно обрабатывать отсутствие критичных переменных:
- либо задавать дефолты,
- либо падать с понятной ошибкой при старте.
- Не логировать секреты (пароли, токены), даже если получаете их через env.
- В контейнерных окружениях:
- значения env задаются через:
- Docker (
-e), - Kubernetes (
env,envFrom, Secret/ConfigMap), - Helm values → env.
- Docker (
- значения env задаются через:
Кратко:
- Импортируем
os. - Используем
os.getenv("NAME")илиos.environ["NAME"]/.get("NAME")для получения значения переменной окружения.
Вопрос 57. Есть ли у вас опыт работы с PHP и готовы ли вы с ним работать?
Таймкод: 00:58:58
Ответ собеседника: правильный. Говорит, что практического опыта с PHP нет, но как инженер по инфраструктуре/DevOps нейтрально относится к языку и готов работать с любым стеком, не выделяя PHP отдельно.
Правильный ответ:
Для такого вопроса важен не столько конкретный опыт с PHP, сколько отношение к стеку и понимание, что:
- язык — это деталь реализации,
- ответственность за платформу и CI/CD предполагает работу с разными технологиями,
- многие практики, используемые для Go/Java/Python, аналогично применимы к PHP-приложениям.
Сильный ответ может выглядеть так:
- Честно фиксируем отсутствие глубокого коммерческого опыта в PHP-разработке (если это так).
- Подчеркиваем, что:
- комфортно работаем с любым языком как с "приложением над платформой";
- умеем выстраивать для него:
- окружение выполнения,
- билд/деплой,
- логирование, мониторинг,
- observability и безопасность.
Ключевые моменты, которые стоит уметь и проговорить применительно к PHP-стеку:
- Запуск и окружение
- Понимание типичных вариантов:
- php-fpm + nginx;
- Apache + mod_php (реже в современных продакшн-конфигурациях).
- В контейнерном окружении:
- Docker-образы на базе официального php-fpm,
- отдельный контейнер nginx как reverse proxy,
- проброс сокета/порта между nginx и php-fpm.
- Конфигурация через переменные окружения, ConfigMap/Secret в Kubernetes.
- CI/CD для PHP-приложений
Подход аналогичен Go/Java/Python:
- Статика: composer для зависимостей:
composer install --no-dev --optimize-autoloader
- Проверки:
- линтеры (phpcs, phpstan, psalm),
- unit-тесты (PHPUnit),
- code style.
- Сборка артефакта:
- создание Docker-образа с:
- nginx + php-fpm,
- кодом приложения,
- конфигами.
- создание Docker-образа с:
- Деплой:
- через Helm/Kustomize в Kubernetes,
- или через Ansible/Capistrano-аналоги на bare metal/VM.
- Observability и эксплуатация
- Логирование:
- доступы/ошибки nginx,
- php-fpm error log,
- централизованный сбор в ELK/Loki.
- Метрики:
- nginx (exporter),
- php-fpm (метрики воркеров, очередь),
- бизнес-метрики через sidecar/экспортеры или интеграции.
- Настройки производительности:
- opcache,
- лимиты php-fpm workers,
- корректные timeouts на уровне nginx и приложения.
- Безопасность и best practices
- Работа с секретами через env/Secret, а не через хардкод.
- Обновления PHP и расширений:
- следование LTS-веткам,
- своевременные security-патчи.
- Типичные уязвимости:
- RCE, SQLi, XSS, CSRF, file upload — контроль через конфиги и защитные практики.
Краткая формулировка:
- "Глубокого продакшн-опыта разработки на PHP у меня нет, но проблем работать с этим стеком не вижу. На уровне инфраструктуры и DevOps я одинаково подхожу к PHP, Go, Java или Python:
- поднимаю и настраиваю окружение (php-fpm + nginx, Docker, Kubernetes),
- строю CI/CD с тестами, линтами и сборкой образов,
- интегрирую логирование, метрики и алертинг,
- соблюдаю требования по безопасности и производительности.
- Если потребуется доработать само приложение или разобраться в коде, смогу быстро войти, опираясь на опыт в других языках и общие инженерные принципы."
Такой ответ показывает технологическую гибкость, зрелость и отсутствие предвзятости к стеку.
Вопрос 58. Как вы относитесь к ненормированному графику и внеплановым задачам (вечерние релизы, аварии ночью)?
Таймкод: 00:59:29
Ответ собеседника: правильный. Говорит, что нормально относится к редким внеплановым работам и авариям, но постоянные переработки должны компенсироваться; приводит пример редких ночных работ на текущем месте.
Правильный ответ:
На этот вопрос важен зрелый, взвешенный ответ, отражающий понимание реальностей продакшн-систем и здорового отношения к work-life balance.
Сильная позиция включает следующие элементы:
- Признание реальности продакшн-эксплуатации
- К критичным системам (финтех, биллинг, B2C-продукты, highload-сервисы) предъявляются требования 24/7.
- Редкие:
- ночные аварии,
- вечерние релизы,
- срочные хотфиксы — являются нормальной частью ответственности за продакшн, если:
- это исключения, а не ежедневная рутина;
- команда и процессы организованы грамотно.
Корректно показать готовность:
- участвовать в on-call ротации,
- разбираться в инцидентах,
- выходить за рамки рабочего дня при реальной необходимости.
- Требование системности, а не хаоса
Важно подчеркнуть, что:
-
постоянный хаос, вечные ночные релизы и "перегорание" — это не норма, а симптом плохих процессов.
-
При зрелых практиках:
- используются:
- планирование релизов,
- feature_flags,
- canary/blue-green,
- автоматические тесты,
- rollback-стратегии;
- большая часть релизов проходит:
- безболезненно,
- без необходимости будить людей ночью.
- используются:
Ожидание:
- если переработки становятся системными:
- это или временный этап (миграция, крупный релиз),
- или должно быть:
- компенсировано (отгулами, оплатой),
- и/или решено на уровне процессов.
- Готовность к on-call и аварийным работам, но в рамках разумного
Сбалансированная формулировка:
- "Готов участвовать в дежурствах и внеплановых работах:
- при действительно критичных инцидентах,
- при важных релизах, которые нельзя делать в рабочий день.
- Это часть ответственности за продакшн."
- "При этом считаю важным:
- чтобы такие случаи были исключением, а не повседневностью;
- чтобы существовала прозрачная ротация on-call;
- чтобы переработки компенсировались и команда имела возможность восстанавливаться."
- Ориентация на снижение частоты ночных аварий
Зрелый инженер не только "готов чинить ночью", но и:
-
заинтересован в снижении вероятности повторения:
- через postmortem без поиска виноватых,
- улучшение мониторинга и алертов,
- автоматизацию rollback,
- повышение покрытия тестами,
- улучшения в архитектуре.
Это показывает фокус не на героизме, а на инженерной зрелости.
Краткая формулировка:
- "Нормально отношусь к редким внеплановым задачам, вечерним релизам и участию в on-call, особенно в критичных продуктах — это часть ответственности за продакшн. Ожидаю, что:
- это организовано через прозрачную ротацию,
- не превращается в постоянные переработки,
- компенсируется и сопровождается работой над качеством процессов и архитектуры, чтобы аварий и 'ночных подвигов' становилось меньше, а не больше."
Вопрос 59. Что вы вкладываете в понятие комфортных условий работы?
Таймкод: 01:01:13
Ответ собеседника: правильный. Подчеркивает важность адекватной команды, уважения личных границ, открытой коммуникации и отсутствия токсичного давления со стороны руководства.
Правильный ответ:
На этот вопрос важно ответить так, чтобы показать зрелое понимание рабочих процессов, ответственности и взаимодействия, без акцента на бытовые мелочи. Сильный ответ фокусируется на условиях, которые:
- позволяют эффективно решать сложные инженерные задачи,
- поддерживают здоровье команды,
- минимизируют хаос и бессмысленные конфликты,
- способствуют росту и надёжности продукта.
Ключевые компоненты комфортных условий работы:
- Профессиональная среда и адекватная коммуникация
- Уважительное общение:
- без токсичности, крика, обесценивания, пассивной агрессии.
- Прозрачная обратная связь:
- конструктивный code review,
- обсуждение архитектурных решений по существу, а не "по званию".
- Доступность информации:
- документация,
- понятные процессы,
- открытые обсуждения технических решений.
- Четкие ожидания и ответственность
- Понятные зоны ответственности:
- кто отвечает за код, инфраструктуру, релизы, инциденты;
- нет постоянного "перекидывания мяча".
- Реалистичные сроки:
- оценка задач совместно,
- отсутствие практики "вчера надо было",
- планирование, учитывающее риски и сложность.
- Осознанное отношение к инцидентам:
- postmortem без охоты на ведьм,
- поиск причин в процессах/архитектуре, а не в "кривых руках".
- Зрелые инженерные практики
Комфорт для сильного инженера — это не "меньше задач", а:
- наличие:
- CI/CD,
- автоматических тестов,
- инфраструктуры как код,
- мониторинга, логирования, алертинга,
- нормальных инструментов (GitLab/GitHub, Kubernetes, observability-стек).
- минимизация ручной, рутинной, нестандартизированной работы:
- меньше "залить по ssh на прод",
- больше воспроизводимых пайплайнов и декларативных конфигов.
- Поддержка времени на:
- рефакторинг,
- улучшение инфраструктуры,
- снижение технического долга.
- Баланс нагрузки и уважение к личному времени
- Готовность к редким ночным авариям и релизам:
- как к части ответственности;
- Но:
- отсутствие культуры постоянных переработок "по умолчанию";
- компенсация и ротация при on-call;
- уважение к личным границам и времени вне работы.
- Возможность роста и влияния
Комфорт включает:
- возможность:
- принимать участие в архитектурных решениях,
- предлагать улучшения и видеть, что их рассматривают;
- поддержка профессионального развития:
- задачи, которые технически развивают;
- обмен знаниями внутри команды (митапы, ревью, обсуждения).
Краткая формулировка:
- "Комфортные условия — это среда, где:
- есть уважительное и честное взаимодействие,
- понятны ожидания и зона ответственности,
- используются зрелые инженерные практики (CI/CD, IaC, мониторинг),
- нет токсичности и хаотичных 'подвигов' каждый день,
- редкие внеплановые работы воспринимаются как часть ответственности и компенсируются,
- у инженера есть возможность влиять на технические решения и развиваться.
- В таких условиях можно спокойно тащить серьёзные продакшн-системы и делать результат."
Вопрос 1. Кратко опишите текущий опыт, основные обязанности и используемый стек.
Таймкод: 00:00:27
Ответ собеседника: правильный. Говорит, что работает DevOps-инженером в компании, разрабатывающей ПО для автодорог: отвечает за CI/CD пайплайны, администрирование Kubernetes, контейнеризацию сервисов, использует Ansible для деплоя.
Правильный ответ:
Сильное краткое описание текущего опыта должно одновременно:
- показать зону ответственности end-to-end (от кода до продакшена),
- подчеркнуть владение ключевыми технологиями,
- продемонстрировать опыт работы с продакшн-системами и автоматизацией.
Пример содержательного ответа:
- Работаю в продуктовой/проектной команде, отвечающей за полный цикл доставки и эксплуатации сервисов.
- Основные направления ответственности:
- Проектирование и поддержка CI/CD:
- GitLab CI (или аналог):
- многостадийные pipeline’ы (lint → test → build → security scan → deploy),
- работа с артефактами, кэшем, environments, ручными шагами для prod.
- Сборка и публикация Docker-образов:
- multi-stage build,
- минимальные образы,
- встраивание метаданных (commit, версия).
- GitLab CI (или аналог):
- Контейнеризация и Kubernetes:
- упаковка сервисов в контейнеры;
- разработка и поддержка Helm-чартов или Kustomize:
- Deployment, Service, Ingress, HPA, ConfigMap/Secret;
- настройка rollout/rollback стратегий;
- работа с namespaces, ресурсными лимитами, probes.
- Управление инфраструктурой как кодом:
- Ansible:
- роли и playbook’и для настройки серверов,
- деплой сервисов в средах без Kubernetes,
- bootstrap, конфигурация middleware (nginx, базы, очереди).
- (При наличии) Terraform / аналог — для описания облачной инфраструктуры.
- Ansible:
- Эксплуатация и поддержка:
- базовая диагностика Linux:
- top/htop/vmstat/iostat, анализ load average, памяти, диска;
- сетевые проверки (ss, netstat, tcpdump).
- участие в расследовании инцидентов, оптимизации конфигураций.
- базовая диагностика Linux:
- Проектирование и поддержка CI/CD:
Технологический стек (примерно):
- Языки:
- Go (основные сервисы и тулзы),
- Python (скрипты, интеграции, утилиты),
- Bash.
- CI/CD:
- GitLab CI (pipelines-as-code, include, workflow: rules, окружения),
- Docker Registry.
- Контейнеры и оркестрация:
- Docker,
- Kubernetes:
- Helm для деплоя;
- использование ConfigMap/Secret, Ingress, HPA.
- Конфигурация и инфраструктура:
- Ansible (роли, плейбуки, деплой, конфигурация хостов),
- системное администрирование Linux на уровне достаточном для продакшн-поддержки.
- Логирование и мониторинг:
- интеграция сервисов в существующий стек (ELK/Prometheus/Grafana и др.);
- настройка метрик и логов на уровне приложений и инфраструктуры.
- Сетевые и сопутствующие технологии:
- базовое понимание iptables, работы L4/L7,
- HTTP, TLS, reverse proxy (nginx/ingress-контроллеры).
Краткая формулировка:
- "Сейчас отвечаю за построение и поддержку CI/CD, контейнеризацию и деплой сервисов в Kubernetes, инфраструктуру как код на Ansible, а также базовую эксплуатацию Linux и диагностику продакшн-систем. Активно работаю с Go-сервисами, Docker, Kubernetes, GitLab CI и Ansible, интегрируя всё это в стабильный и воспроизводимый процесс доставки."
Вопрос 2. Какой у вас опыт администрирования Linux и с какими дистрибутивами вы работали?
Таймкод: 00:01:32
Ответ собеседника: неполный. Упоминает постоянную работу с Ubuntu и Debian и выполнение стандартных задач, но не раскрывает глубину: сервисы, сеть, безопасность, траблшутинг, отличия дистрибутивов.
Правильный ответ:
При ответе на такой вопрос важно показать не просто знакомство с Ubuntu/Debian, а уверенное владение базовыми и продвинутыми аспектами администрирования Linux, которые критичны для продакшн-сервисов и Go-приложений.
Основные акценты сильного ответа:
- Дистрибутивы
- Практический опыт с:
- Debian/Ubuntu (частый выбор для приложений и контейнерных хостов),
- при необходимости: CentOS/RHEL/Alma/Rocky, возможно контейнерные образы на базе Alpine/Debian-slim.
- Понимание отличий:
- менеджеры пакетов:
- apt/dpkg vs yum/dnf/rpm;
- расположение конфигов;
- политика обновлений и безопасности.
- менеджеры пакетов:
- Базовые задачи администрирования (обязательный минимум)
- Управление пакетами:
- установка/обновление/удаление:
- apt, apt-get, dpkg;
- работа с репозиториями (sources.list, зеркала, прокси, локальные репо).
- установка/обновление/удаление:
- Управление пользователями и правами:
- useradd/usermod/userdel,
- группы, sudoers (visudo),
- базовые практики безопасности (запуск сервисов не от root).
- Работа с файлами и файловыми системами:
- mount/umount,
- базовая диагностика дисков:
- df -h, du -sh, iostat, inodes.
- systemd и управление сервисами
Уверенное владение systemd — ключ к эксплуатации:
-
Запуск/остановка/рестарт сервисов:
systemctl start|stop|restart|status <service> -
Логи:
journalctl -u <service> -f -
Создание и отладка unit-файлов для собственных сервисов (например, Go):
[Unit]
Description=My Go Service
After=network-online.target
[Service]
User=app
Group=app
WorkingDirectory=/opt/my-app
ExecStart=/opt/my-app/my-app --config=/etc/my-app/config.yaml
Restart=on-failure
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target -
Понимание зависимостей, рестартов, ограничений ресурсов.
- Сеть и базовый сетевой траблшутинг
Для работы с микросервисами и Kubernetes без этого никуда:
- Проверка доступности и маршрутизации:
- ping, traceroute, ip route, ip addr, ip link.
- Порты и соединения:
- ss -tulpen, netstat (если есть),
- кто слушает порт, что держит соединения.
- DNS:
- dig, nslookup, настройка resolv.conf/systemd-resolved.
- Базовое понимание firewall:
- iptables/nftables/ufw/firewalld:
- просмотр правил;
- влияние на сервисы и Kubernetes (особенно важно не ломать iptables-правила кластера).
- iptables/nftables/ufw/firewalld:
- Безопасность и доступ
- SSH:
- ключи, авторизация, отключение root login по паролю,
- sshd_config базовые параметры.
- Обновления безопасности:
- регулярное применение security updates;
- аккуратность с kernel/critical updates.
- Использование sudo, разделение прав.
- Диагностика производительности и инцидентов
Даже без "низкоуровневой магии" нужно уверенно владеть:
-
Нагрузка и CPU:
top, htop, uptime, vmstat -
Память:
free -h
cat /proc/meminfoПонимание:
- различий между used/free/available;
- роли buff/cache;
- того, что "почти вся память занята" — нормально при высоком usage page cache.
-
Диск и I/O:
iostat, iotop, df -h, du -sh -
Логи:
- /var/log,
- journalctl,
- интеграция с централизованным логированием (ELK, Loki).
- Инфраструктура как код и Linux
Связь с Ansible/Terraform/Kubernetes:
- Понимание, как ручные команды транслируются в Ansible-roles:
- установка пакетов,
- деплой конфигов,
- перезапуск сервисов.
- Умение описать конфигурацию Linux-хостов декларативно:
- чтобы то, что настроено руками, можно было воспроизвести пайплайном.
- Формат сильного краткого ответа
Пример:
- "Практически постоянно работаю с Linux, в первую очередь Ubuntu/Debian:
- настраиваю и сопровождаю приложенческие сервера и Kubernetes-ноды;
- управляю пакетами, пользователями, правами, SSH-доступом;
- создаю и отлаживаю systemd unit-файлы для сервисов (Go/инфраструктурные тулзы);
- занимаюсь базовой сетевой диагностикой (ss, ip, dig, tcpdump при необходимости);
- разбираю проблемы с диском, памятью, нагрузкой через top/htop/vmstat/df/du/journalctl;
- автоматизирую эти действия через Ansible и CI/CD. Сложные кейсы по kernel-level тюнингу и файловым системам возникают реже, но базу понимаю и умею в неё зайти при необходимости."
Такой ответ показывает уверенное продакшн-администрирование, достаточное для построения и поддержки инфраструктуры под современные сервисы и кластеры.
Вопрос 3. Приведите примеры задач в Linux, которые вам приходилось решать.
Таймкод: 00:02:04
Ответ собеседника: неполный. Говорит о разворачивании стендов на Debian/Ubuntu, установке Kubernetes и настройке сети для кластера, но не раскрывает спектр практических задач и глубину владения инструментами диагностики и администрирования.
Правильный ответ:
Хороший ответ должен показать, что работа в Linux для вас — не только "ставил пакеты", но и:
- реальная эксплуатация сервисов,
- понимание сети, прав, ресурсов,
- умение диагностировать проблемы,
- интеграция с контейнерами и Kubernetes,
- автоматизация этих действий.
Ниже примеры задач, которые демонстрируют зрелый уровень.
- Развёртывание и базовая конфигурация серверов
Типичные действия:
-
Настройка пользователей и доступа:
useradd -m -s /bin/bash appuser
mkdir -p /home/appuser/.ssh
chmod 700 /home/appuser/.ssh
# деплой ключа
echo "ssh-ed25519 AAAA..." > /home/appuser/.ssh/authorized_keys
chmod 600 /home/appuser/.ssh/authorized_keys
chown -R appuser:appuser /home/appuser/.ssh -
Настройка sudo для ограниченной команды/группы.
-
Отключение паролей для root, настройка SSH (PermitRootLogin no, AllowUsers).
- Сервисы и systemd
- Оформление приложений (в том числе Go-сервисов) как systemd-сервисов:
- автозапуск, рестарт, логирование через journalctl.
Пример unit-файла:
[Unit]
Description=My Go API
After=network-online.target
[Service]
User=appuser
Group=appuser
WorkingDirectory=/opt/my-api
ExecStart=/opt/my-api/my-api --config=/etc/my-api/config.yaml
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
-
Диагностика падений:
systemctl status my-api
journalctl -u my-api -n 100 --no-pager
- Сетевые задачи и диагностика
Примеры:
-
Настройка интерфейсов, маршрутов, DNS:
- через netplan/systemd-networkd/NetworkManager (в зависимости от дистрибутива),
- проверка ip addr, ip route.
-
Диагностика "сервис не доступен":
-
проверить, слушает ли порт:
ss -tulpen | grep 8080 -
проверить доступность с других хостов (curl, nc);
-
убедиться, что firewall не блокирует (iptables/ufw/nftables);
-
сверить bind-адрес (0.0.0.0 vs 127.0.0.1).
-
-
Настройка простого reverse proxy (nginx) перед приложением:
- TLS-терминация,
- проброс на внутренний порт Go/Python/PHP сервиса.
- Интеграция с Kubernetes и подготовка нод
Из практических задач, которые хорошо демонстрируют Linux-скилл:
-
Подготовка нод под k8s:
-
настройка cgroup-драйвера, модулей ядра, sysctl:
cat <<EOF > /etc/sysctl.d/99-kubernetes.conf
net.bridge.bridge-nf-call-iptables=1
net.ipv4.ip_forward=1
EOF
sysctl --system -
установка контейнер-рантайма (containerd/docker),
-
настройка kubelet unit’ов.
-
-
Отладка проблем:
- pod не выходит в интернет:
- проверка iptables-правил, маршрутизации, MASQUERADE;
- CoreDNS не работает:
- проверка /etc/resolv.conf, kube-dns svc, iptables rules.
- pod не выходит в интернет:
- Ресурсы и производительность
Зрелый инженер умеет быстро понять "что болит":
-
CPU / load:
top, htop, uptimeПонимание:
- разницы между load average и количеством ядер,
- когда load из-за I/O, а не CPU.
-
Память:
free -h
cat /proc/meminfo -
Диск:
df -h
du -sh /var/log/* /var/lib/*
iostat -x 1 10 -
Поиск тяжёлых процессов:
ps aux --sort=-%cpu | head
ps aux --sort=-%mem | head
- Логирование и управление логами
- Работа с journalctl:
- фильтрация по сервису, по времени;
- Ротация логов:
- logrotate, настройка ротации для приложений,
- Подготовка хостов к централизованному логированию:
- корректное размещение логов,
- формат, удобный для парсинга (JSON).
- Безопасность и обновления
-
Регулярные security-updates:
apt-get update && apt-get upgrade -
Настройка fail2ban / ограничений для brute-force на SSH (при необходимости).
-
Базовые проверки:
- нет ли world-writable там, где не нужно,
- права на конфиги с секретами.
- Автоматизация этих задач
Каждое из действий выше:
- выносится в Ansible-роль/плейбук:
- подготовка хоста,
- деплой приложения,
- настройка nginx/systemd,
- интегрируется в CI/CD:
- минимизация ручного доступа по SSH.
Краткая формулировка ответа на интервью:
- "Из практических задач в Linux:
- поднимал и настраивал серверы на Ubuntu/Debian,
- оформлял приложения как systemd-сервисы, настраивал автозапуск и логирование,
- настраивал nginx как reverse proxy с TLS,
- готовил ноды под Kubernetes: containerd/docker, sysctl, сети, проверка iptables,
- решал типичные проблемы с доступностью сервисов: порты, firewall, DNS, маршруты,
- диагностировал нагрузку по CPU/памяти/диску и утечки логов,
- автоматизировал всё это через Ansible. В более глубокий kernel-tuning и сложные файловые системы захожу по мере необходимости, но фундамент для продакшн-эксплуатации уверен."
Вопрос 4. С какими балансировщиками нагрузки и прокси-серверами вы работали, и чем они отличаются?
Таймкод: 00:02:48
Ответ собеседника: неправильный. Упоминает только Nginx, говорит, что HAProxy видел на обучении; ошибочно утверждает, что HAProxy больше про TCP, а Nginx нельзя использовать для TCP, даёт путаное объяснение различий.
Правильный ответ:
Хороший ответ должен:
- показать понимание основных классов балансировщиков,
- корректно описать возможности Nginx и HAProxy,
- понимать, как это встраивается в современную инфраструктуру (Kubernetes, сервисы на Go, gRPC, WebSocket),
- без мифов вроде "Nginx не умеет TCP".
Ниже структурированный разбор.
Основные игроки (наиболее часто ожидаемые):
- Nginx / Nginx Plus
- HAProxy
- Envoy
- В контексте Kubernetes:
- ingress-контроллеры (nginx-ingress, HAProxy Ingress, Envoy-based),
- сервисы облачных провайдеров (L4/L7 LB)
- Nginx
Ключевые особенности:
- Исторически:
- высокопроизводительный HTTP-сервер и reverse proxy.
- Поддерживает:
- HTTP/1.1, HTTP/2, WebSocket,
- gRPC (через
grpc_pass), - TCP/UDP (через
stream {}блоки) — важно, Nginx МОЖЕТ работать как L4-балансировщик.
- Типовые сценарии:
- reverse proxy перед backend-сервисами (Go, PHP-FPM, Python, Java);
- terminação TLS (offload SSL),
- статический контент,
- L7-балансировка по URI, заголовкам, cookies;
- rate limiting, basic auth, простые WAF-функции.
Пример HTTP-балансировки:
http {
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
}
Пример TCP-балансировки:
stream {
upstream pg {
server 10.0.0.10:5432;
server 10.0.0.11:5432;
}
server {
listen 5432;
proxy_pass pg;
}
}
Когда выбирать Nginx:
- нужен мощный HTTP reverse proxy,
- нужен TLS-терминатор,
- нужно что-то универсальное и знакомое команде,
- нужна простая TCP/UDP-балансировка в дополнение.
- HAProxy
Ключевые особенности:
- Заточен под:
- высокопроизводительную балансировку L4 и L7;
- огромную нагрузку и тонкую настройку.
- Из коробки:
- очень богатый набор health-check’ов,
- гибкие ACL (условия на основе IP, заголовков, путей и т.п.),
- продвинутые алгоритмы балансировки,
- детальный контроль таймаутов, ретраев,
- очень детальная статистика.
- Умеет:
- TCP и HTTP(S),
- TLS-терминацию,
- HTTP/2, WebSocket,
- сложный роутинг по множеству условий.
Пример (упрощённый):
frontend http_in
bind *:80
acl is_api path_beg /api
use_backend api_back if is_api
default_backend web_back
backend api_back
balance roundrobin
server api1 10.0.0.1:8080 check
server api2 10.0.0.2:8080 check
backend web_back
balance leastconn
server web1 10.0.0.3:80 check
server web2 10.0.0.4:80 check
Когда выбирать HAProxy:
- нужен максимально гибкий L4/L7 балансировщик,
- критичны сложные health-check’и,
- много тонких правил маршрутизации и таймаутов,
- highload, где важна предсказуемость и прозрачная статистика.
Миф из ответа кандидата:
- "HAProxy — только TCP, Nginx — только HTTP" — неверно.
- Оба умеют HTTP(S),
- Оба умеют TCP,
- выбор — в деталях, экосистеме и удобстве настройки.
- Envoy
Современный L7 proxy, часто ожидаемый в контексте микросервисов:
- Разработан Lyft, сейчас CNCF-проект.
- Особенности:
- natively поддерживает HTTP/2, gRPC,
- богатая телеметрия,
- динамическая конфигурация (xDS API),
- основа для многих сервис-мешей (Istio, Consul Connect).
- Сценарии:
- sidecar-прокси,
- ingress/egress-шлюзы,
- сложная маршрутизация, ретраи, circuit breaking.
Если вы упоминаете современную платформенную инфраструктуру, Envoy — сильный плюс.
- Балансировка в Kubernetes (как контекст)
Важно показать понимание, как всё это живёт в Kubernetes:
- Service type=ClusterIP:
- kube-proxy, iptables/ipvs на L4.
- Service type=LoadBalancer:
- облачный L4/L7 балансировщик.
- Ingress + ingress-controller:
- чаще всего Nginx Ingress Controller, HAProxy Ingress, Traefik, Envoy.
- Внутри:
- тот же Nginx/HAProxy/Envoy, но управляемый через CRD и конфигурацию Kubernetes.
Связь с Go-сервисами:
- Go-сервисы:
- слушают HTTP/gRPC,
- за ними стоит ingress (Nginx/Envoy/HAProxy),
- используются readiness/liveness для health-check;
- балансировщик:
- делает TLS-терминацию,
- маршрутизирует по host/path,
- пробрасывает заголовки (X-Request-ID, X-Forwarded-For).
- Как корректно сформулировать отличия (кратко)
- Nginx:
- сильный HTTP reverse proxy + web-сервер,
- умеет TCP/UDP через stream,
- удобен для статического контента, простого роутинга, TLS.
- HAProxy:
- специализированный, очень мощный L4/L7-балансировщик,
- более гибкие ACL, health-check’и, метрики,
- часто выбор для тяжёлых нагруженных систем.
- Envoy:
- современный L7-прокси,
- ориентирован на микросервисы, gRPC, сервис-меш, динамическую конфигурацию.
- Все:
- умеют быть reverse proxy и балансировать нагрузку;
- выбираем по требованиям к функционалу, удобству и экосистеме.
Краткая формулировка для интервью:
- "Работал с Nginx как HTTP reverse proxy и TLS-терминатором, использовал и http{}, и stream{} для L7 и L4. Понимаю архитектуру и возможности HAProxy: он силён в высоконагруженной L4/L7-балансировке, гибких ACL и health-check’ах. В современных кластерах также важно знать Envoy и ingress-контроллеры Kubernetes. Nginx и HAProxy оба умеют HTTP и TCP; выбор делается по требованиям к гибкости, observability и производительности."
Вопрос 5. С какими базами данных вы работали и есть ли опыт настройки кластерных инсталляций (например, PostgreSQL)?
Таймкод: 00:03:49
Ответ собеседника: неполный. Упоминает установку PostgreSQL и MongoDB, базовое понимание схемы ведущий/реплики, но без реального опыта проектирования и сопровождения отказоустойчивых кластеров.
Правильный ответ:
Зрелый ответ должен показать:
- понимание основных типов БД и их сценариев,
- практический опыт эксплуатации (пусть даже частичный),
- базовое знание концепций отказоустойчивости и репликации,
- умение встроить БД в инфраструктуру (Docker, Kubernetes, CI/CD, мониторинг).
- Базы данных и типичные сценарии
Обычно ожидается опыт хотя бы с:
- реляционными:
- PostgreSQL (предпочтительно),
- MySQL/MariaDB;
- нереляционными:
- Redis (кэш, очереди),
- MongoDB (документное хранилище),
- возможно Kafka (как лог/стриминговая платформа, хотя формально не БД).
Сильный ответ:
- "Работал с PostgreSQL как основной транзакционной БД, с Redis как кэшем/локером, с MongoDB в проектах, где требовалась гибкая документная модель."
- Базовые компетенции по PostgreSQL
Минимум, который важен:
-
Установка и конфигурация:
- параметры подключения (listen_addresses, port),
- pg_hba.conf (сетевой доступ и методы аутентификации),
- базовые настройки производительности (shared_buffers, work_mem, max_connections и т.д. — хотя бы на уровне понимания).
-
Управление пользователями и правами:
CREATE ROLE app_user LOGIN PASSWORD 'strongpass';
CREATE DATABASE app_db OWNER app_user;
GRANT ALL PRIVILEGES ON DATABASE app_db TO app_user; -
Резервное копирование:
- logical backups: pg_dump/pg_dumpall,
- понимание назначения WAL, base backup для point-in-time recovery.
- Кластерные установки PostgreSQL: ключевые концепции
Даже если вы не поднимали сложный кластер сами, важно понимать, как это делается.
Основные подходы:
-
Стриминговая репликация:
- primary (master) + one/many standby;
- реплика читает WAL-записи и применяет их.
-
Отказоустойчивость:
- использование Patroni, repmgr, Pacemaker/Corosync или облачных managed-сервисов;
- наличие виртуального IP/endpoint, который "переезжает" на новый primary.
-
Типичная схема:
- 1 primary:
- принимает запись,
- 1–N реплик:
- для чтения и/или резервирования,
- мониторинг репликации (lag, состояние),
- автоматический или полуавтоматический failover.
- 1 primary:
Пример: high-level конфигурация реплики:
- на primary:
- включить wal_level = replica,
- настроить max_wal_senders, archive_mode, hot_standby.
- на реплике:
- сделать base backup (pg_basebackup),
- создать standby.signal,
- настроить primary_conninfo.
В production-среде часто используют операторы и готовые решения:
- В Kubernetes:
- Zalando Patroni,
- CrunchyData,
- CloudNativePG.
- В bare-metal/VM:
- Patroni + etcd/Consul + HAProxy/Keepalived.
Сильный ответ может звучать так (даже если ваш опыт частичный):
- "Понимаю, что корректная кластеризация PostgreSQL — это не просто 'включить репликацию', а:
- обеспечить стриминговую репликацию,
- следить за WAL lag,
- иметь механизм failover и единый endpoint для приложений,
- обеспечить консистентный бэкап и восстановление."
- MongoDB и другие NoSQL
Кратко:
- MongoDB:
- replica set: primary + secondaries + arbiter;
- автоматический failover;
- шардирование для масштабирования по данным.
- Redis:
- single instance — SPOF,
- Redis Sentinel для failover,
- Redis Cluster для шардинга.
Важный момент:
- показываем понимание, что просто поднять два контейнера — не значит сделать HA:
- нужна координация, health-check, failover, тесты отказоустойчивости.
- Интеграция с приложениями (Go/микросервисы)
Хорошо подчеркнуть, что:
- приложения (на Go) должны быть готовы к кластерной БД:
- использовать строку подключения к endpoint’у, а не конкретному хосту;
- уметь переподключаться при failover;
- не кэшировать DNS "навсегда".
- в Kubernetes:
- доступ к БД часто через Service (ClusterIP) или внешний endpoint,
- secret для кредов,
- readiness/liveness у приложений, завязанные на успешные запросы к БД.
Пример (Go + PostgreSQL):
connStr := os.Getenv("DB_DSN") // например: postgres://user:pass@pg-primary:5432/app?sslmode=disable
db, err := sql.Open("pgx", connStr)
if err != nil {
log.Fatal().Err(err).Msg("failed to open db")
}
db.SetMaxOpenConns(50)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(time.Hour)
Важно:
- не полагаться на "один IP", а использовать механизм, который переведёт трафик на новый primary при failover (HAProxy, VIP, DNS с малым TTL, оператор).
- Мониторинг и бэкапы (ключ к production-ready)
Сильный ответ обязательно упоминает:
- мониторинг БД:
- метрики (через exporters в Prometheus),
- alerты по:
- replication lag,
- отсутствию реплик,
- росту диска,
- медленным запросам.
- бэкапы:
- регулярные, проверенные восстановлением,
- знание инструментов:
- pg_dump/pg_basebackup/pgBackRest/Barman для PostgreSQL.
- Краткая формулировка ответа
Пример зрелого ответа:
- "Работал с PostgreSQL как основной реляционной БД и Redis как кэшем, с MongoDB сталкивался для документных данных. Устанавливал и настраивал PostgreSQL на Ubuntu/Debian: пользователи, права, базовые параметры, pg_hba.conf, бэкапы через pg_dump. Понимаю принципы стриминговой репликации и схем primary/standby, необходимость отдельного решения для failover (Patroni, HAProxy/VIP, операторы в Kubernetes), мониторинга replication lag и регулярных бэкапов. Самостоятельно production-кластер с автоматическим failover с нуля не проектировал, но разбираюсь в концепциях и готов это делать: понимаю, что отказоустойчивость БД — это связка репликации, health-check, маршрутизации и процедур восстановления."
Такой ответ:
- честен относительно текущего опыта,
- демонстрирует понимание продакшн-требований,
- показывает готовность проектировать более зрелые решения.
Вопрос 6. Какой у вас опыт работы с Kubernetes: вы разворачивали кластеры с нуля или работали с готовой инфраструктурой?
Таймкод: 00:05:03
Ответ собеседника: правильный. Говорит, что работал с on-prem Kubernetes на виртуальных машинах, без managed-решений, и несколько раз разворачивал кластер с нуля.
Правильный ответ:
Сильный ответ по Kubernetes должен показать:
- понимание устройства кластера,
- опыт как минимум базового bootstrap’а,
- уверенную работу с объектами и эксплуатацией,
- связь с CI/CD, observability и сетевой инфраструктурой.
Если есть реальный опыт on-prem — это большой плюс, важно его грамотно описать.
Ключевые аспекты, которые стоит покрыть.
- Типы опыта: managed vs on-prem
- Managed (EKS, GKE, AKS, Yandex, etc.):
- провайдер берёт на себя control plane;
- вы отвечаете за:
- манифесты,
- ingress,
- сетевые политики,
- observability,
- безопасную поставку.
- On-prem/self-hosted:
- вы отвечаете за всё:
- master-компоненты (kube-apiserver, controller-manager, scheduler, etcd),
- kubelet и kube-proxy на нодах,
- CNI, CSI,
- certificates, high availability control plane,
- апдейты, backup’ы, восстановление.
- вы отвечаете за всё:
Корректно подчеркнуть:
- "Работал именно с on-prem Kubernetes: сам поднимал и обслуживал кластеры, а не только 'деплоил в уже готовый'."
- Разворачивание кластера с нуля (high-level)
Ожидается понимание, какие компоненты и шаги задействованы:
-
Базовая подготовка нод (Linux):
- установка containerd/Docker;
- sysctl-настройки (bridge-nf-call-iptables, ip_forward);
- отключение swap (для kubelet);
- базовый firewall.
-
Использование инструментов:
- kubeadm:
- init control-plane,
- join worker-нод,
- настройка certificates, kubeconfig;
- или kOps, Kubespray, RKE, Ansible-ролей — как альтернатива ручному поднятию.
- kubeadm:
-
CNI-плагин:
- Calico, Flannel, Cilium, Weave;
- понимание, что CNI отвечает за pod-to-pod сеть, policy и т.д.
-
Ingress и балансировка:
- установка ingress-контроллера (Nginx Ingress, HAProxy, Traefik, Envoy);
- интеграция с внешним L4/L7-балансировщиком или on-prem LB (Nginx/HAProxy/MetalLB).
-
etcd:
- осознание, что это критичная часть control plane;
- необходимость бэкапов и HA.
Сильный ответ не обязан расписывать все команды kubeadm, но должен показывать понимание архитектуры и последовательности.
- Работа с Kubernetes как с платформой для приложений
Важно показать не только "я поднимал кластер", но и:
-
Описывание приложений декларативно:
- Deployment, StatefulSet, DaemonSet;
- Service (ClusterIP, NodePort, LoadBalancer);
- Ingress;
- ConfigMap, Secret;
- Jobs/CronJobs.
-
Ресурсы и надёжность:
- requests/limits,
- liveness/readiness/startup probes,
- стратегии деплоя (RollingUpdate, Recreate),
- PodDisruptionBudget.
-
Пример (усечённый) деплоя Go-сервиса:
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-api
spec:
replicas: 3
selector:
matchLabels:
app: payment-api
template:
metadata:
labels:
app: payment-api
spec:
containers:
- name: payment-api
image: registry.example.com/payment-api:v1.2.3
ports:
- containerPort: 8080
env:
- name: DB_DSN
valueFrom:
secretKeyRef:
name: payment-api-db
key: dsn
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /live
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
- Интеграция Kubernetes с CI/CD
Сильный кандидат обязательно связывает k8s с пайплайнами:
- GitLab CI/GitHub Actions + kubectl/helm:
- build → test → security scan → build image → push → helm upgrade/rollout.
- Использование:
- Helm charts или Kustomize для шаблонизации;
- GitOps-подхода (Argo CD, Flux):
- кластер сам подтягивает изменения из git-репозитория манифестов.
- Blue-green/canary:
- через отдельные Deployment/Service,
- через сервис-меш или ingress-аннотации.
- Observability, безопасность, эксплуатация
Для полноты стоит упомянуть:
-
Мониторинг:
- Prometheus + node_exporter + kube-state-metrics;
- Grafana-дэшборды для cluster health, namespaces, приложений.
-
Логирование:
- EFK/ELK/Loki:
- DaemonSet-агенты (Fluent Bit/Filebeat) собирают логи контейнеров.
- EFK/ELK/Loki:
-
Безопасность:
- RBAC:
- service accounts, role/rolebinding;
- ограничение прав:
- не запускать приложения от root,
- PodSecurityContext / SecurityContext;
- NetworkPolicy:
- ограничение трафика между неймспейсами и сервисами;
- Secret management (Kubernetes Secrets, внешние vault-ы).
- RBAC:
- Краткая формулировка сильного ответа
Пример:
- "Работал с on-prem Kubernetes-кластерами на виртуальных машинах. Несколько раз поднимал кластеры с нуля с использованием kubeadm:
- готовил ноды (containerd, sysctl, отключение swap),
- настраивал CNI (Calico/Flannel),
- устанавливал ingress-контроллер,
- интегрировал кластер с CI/CD (GitLab CI + Helm),
- организовывал деплой приложений через Deployment/Service/Ingress, ConfigMap/Secret, probes, requests/limits. Отвечал за эксплуатацию: мониторинг (Prometheus/Grafana), сбор логов, базовые политики безопасности и обновления. Managed-решения концептуально близки: ключевое отличие в том, кто управляет control plane; сами принципы работы и объекты те же."
Такой ответ:
- подтверждает реальный опыт,
- демонстрирует понимание архитектуры,
- увязывает Kubernetes с пайплайнами, мониторингом и эксплуатацией.
Вопрос 7. Как вы обеспечивали отказоустойчивость Kubernetes-кластера и его управляющих узлов?
Таймкод: 00:05:29
Ответ собеседника: неполный. Упоминает схему с тремя управляющими нодами и отказоустойчивым etcd-кластером, но не раскрывает, как именно организован HA control plane, доступ воркер-нод и клиентов к нескольким мастерам, и какие механизмы используются на практике.
Правильный ответ:
Для отказоустойчивого Kubernetes важно понимать:
- control plane — единая точка правды и управления;
- etcd — критичное хранилище состояния;
- доступ к API-серверу должен быть устойчивым и единообразным для воркеров и клиентов;
- HA — это не только "3 мастера", но и:
- балансировка,
- корректная конфигурация,
- бэкапы и процедуры восстановления.
Ниже — структурированный, production-ориентированный ответ.
- Базовая архитектура HA control plane
Классическая схема on-prem/high-availability:
- Несколько control plane нод (обычно 3):
- на каждой:
- kube-apiserver,
- kube-controller-manager,
- kube-scheduler,
- локальный или внешний etcd (чаще выносится отдельно или в те же ноды, но как кластер).
- на каждой:
- etcd-кластер:
- минимум 3 ноды (1,3,5 для кворума),
- расположен:
- либо на тех же control plane нодах,
- либо на выделенных хостах.
- Worker-ноды:
- kubelet и kube-proxy настроены на единый endpoint API-сервера (а не на конкретный мастер).
Ключевая идея:
- отказ одной control plane ноды не ломает кластер,
- кворум etcd сохраняется,
- kube-apiserver остаётся доступен через общий виртуальный endpoint.
- Доступ к нескольким мастерам: балансировщик/виртуальный IP
Чего не хватило в ответе кандидата — конкретики по доступу:
-
Воркеры не должны быть привязаны к одному IP мастера.
-
Используются:
- внешний L4-балансировщик;
- или Keepalived + HAProxy/Nginx для виртуального IP (VIP);
- или облачный Load Balancer (в managed-средах).
Типичный подход on-prem:
- Разворачивается HAProxy/keepalived на отдельных нодах или тех же мастерах:
- есть VIP (например, 10.0.0.100:6443),
- HAProxy проксирует на kube-apiserver всех control plane нод,
- health-check по /healthz или /livez API-сервера.
Пример упрощённой конфигурации HAProxy для API-сервера:
frontend k8s_api
bind 0.0.0.0:6443
mode tcp
default_backend k8s_api_back
backend k8s_api_back
mode tcp
option tcp-check
balance roundrobin
server cp1 10.0.0.11:6443 check
server cp2 10.0.0.12:6443 check
server cp3 10.0.0.13:6443 check
Все:
- kubelet’ы,
- kubectl (через kubeconfig),
- компоненты кластера
подключаются к https://10.0.0.100:6443 — отказ одного мастера не влияет на доступность API.
Важно:
- health-check на API-сервер,
- таймауты и retry,
- корректная работа TLS (сертификат на VIP/имя).
- HA для etcd
Критичные моменты:
-
etcd — единственный источник правды для Kubernetes.
-
Требования:
- нечётное количество нод: 3 или 5;
- хранение данных на надёжном диске (SSD, без "шаринга" с шумными соседями);
- регулярные бэкапы и тест восстановления.
Минимум:
-
etcd cluster с 3 нодами:
- каждый мастер = член etcd-кластера или
- отдельные etcd-ноды.
-
мониторинг:
- задержка (leader, raft index),
- alarms,
- состояние кворума.
-
регулярные snapshot’ы:
ETCDCTL_API=3 etcdctl snapshot save /backup/etcd.snap \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/etcd/pki/ca.crt \
--cert=/etc/etcd/pki/etcd.crt \
--key=/etc/etcd/pki/etcd.key
- Компоненты control plane и их отказоустойчивость
- kube-apiserver:
- статeless (по сути);
- масштабируется горизонтально;
- достаточно запускать на всех control plane нодах.
- kube-controller-manager, kube-scheduler:
- обычно по одному активному экземпляру;
- запускаются с leader election:
- несколько инстансов, один лидер, при падении — переизбрание.
Важный момент:
- используем флаги leader-elect=true,
- это обеспечивает автоматический failover управляющих компонентов.
- Обеспечение отказоустойчивости на уровне приложений и сети
HA control plane — это только часть картины.
Сильный ответ упомянет, что также:
-
Используются:
- несколько worker-нод,
- PodAntiAffinity/TopologySpreadConstraints для развода pod’ов по нодам/зонам,
- реплики Deployment/StatefulSet’ов,
- корректные readiness/liveness probes.
-
Для ingress/экспонирования:
- несколько реплик ingress-контроллера,
- DaemonSet/Deployment с hostNetwork/Service LoadBalancer/NodePort,
- внешний L4/L7-балансировщик перед ingress-контроллерами.
- Мониторинг, алерты, практические аспекты
Зрелая реализация HA включает:
-
Мониторинг control plane:
- доступность /healthz, /livez, /readyz;
- метрики:
- apiserver_request_total,
- apiserver_current_inflight_requests,
- etcd_disk_wal_fsync_duration_seconds и др.;
-
Алерты:
- недоступен API-сервер,
- потеря кворума etcd,
- деградация ingress-контроллера,
- высокий latency.
-
Процедуры:
- инструкция по выводу ноды из кластера (cordon/drain/delete),
- шаги по восстановлению control plane или etcd из snapshot,
- тестирование failover (выключение одной ноды и проверка работоспособности).
- Краткая формулировка для интервью
Пример сильного ответа, без лишней воды:
- "Для отказоустойчивости Kubernetes-кластера используем multi-master архитектуру:
- три control plane ноды с запущенными kube-apiserver, controller-manager, scheduler и etcd-кластером (3 ноды для кворума).
- Доступ к API-серверу организован через внешний TCP-балансировщик (HAProxy/Keepalived с VIP), все worker’ы и kubectl ходят на единый виртуальный адрес.
- etcd работает как кластер, регулярно снимаем snapshot’ы и мониторим его состояние.
- Компоненты control plane запускаются с leader election, так что при падении одного мастера управление автоматически переезжает.
- На уровне приложений обеспечиваем HA через несколько worker-нод, реплики Pod’ов, anti-affinity, отказоустойчивый ingress и мониторинг.
- В результате потеря одной ноды control plane или worker не приводит к простоям, а восстановление выполняется по отработанным процедурам."
Такой ответ показывает понимание реальной, а не "учебной" отказоустойчивости Kubernetes.
Вопрос 8. Как сделать так, чтобы ноды-воркеры продолжали работать при отказе указанного в конфиге мастера Kubernetes?
Таймкод: 00:06:54
Ответ собеседника: неправильный. Говорит, что кластеры были недостаточно отказоустойчивыми, предполагает просто указать несколько мастеров, но не показывает понимание использования виртуального IP или внешнего балансировщика для HA API-сервера.
Правильный ответ:
Ключевая идея: воркеры и клиенты не должны ходить на "конкретный мастер". Они должны ходить на устойчивый endpoint, за которым уже скрываются несколько control plane нод. Указывать список мастеров в kubeconfig без правильного механизма — не решение.
Базовые принципы:
- Используем единый стабильный endpoint для kube-apiserver
Все:
- kubelet на воркерах,
- kubectl,
- системные компоненты
должны быть настроены на один URL вида:
https://k8s-api.example.com:6443- или
https://10.0.0.100:6443(VIP).
Этот endpoint:
- не меняется при падении одной master-ноды,
- за ним стоит балансировщик или виртуальный IP.
- Вариант 1: Внешний L4-балансировщик (рекомендуется)
Частый продакшн-подход:
- поднимаем HAProxy/NGINX/Envoy как TCP-балансировщик:
- на выделенных хостах или на тех же мастерах;
- он слушает, например,
:6443и проксирует на все живые kube-apiserver’ы.
Упрощённый пример для HAProxy:
frontend k8s_api
bind 0.0.0.0:6443
mode tcp
default_backend k8s_api_back
backend k8s_api_back
mode tcp
option tcp-check
balance roundrobin
server cp1 10.0.0.11:6443 check
server cp2 10.0.0.12:6443 check
server cp3 10.0.0.13:6443 check
Дальше:
- kubeadm init делаем с
--control-plane-endpoint=k8s-api.example.com:6443, - kubelet на воркерах при
kubeadm joinполучает именно этот endpoint.
Если один мастер упал:
- балансировщик убирает его из пула по health-check,
- воркеры продолжают работать через оставшиеся мастера,
- никакие kubeconfig менять не надо.
- Вариант 2: VIP (Keepalived) + локальный прокси
Если нет внешнего балансировщика:
-
поднимаем на мастерах:
- Keepalived:
- держит виртуальный IP (VIP), который "переезжает" между мастерами;
- локальный HAProxy/NGINX:
- слушает на VIP:6443,
- проксирует на локальный kube-apiserver или на все мастера.
- Keepalived:
С точки зрения воркеров:
- всегда один адрес (VIP),
- кто именно сейчас мастер — не важно.
- Почему "просто указать все мастера в конфиге" — не решение
Ошибочный путь:
- вручную прописывать несколько server-строк в kubeconfig/kubelet-конфиге без нормального failover,
- или использовать DNS round-robin без health-check.
Проблемы:
- kubelet/kubectl могут уткнуться в мёртвый IP,
- отсутствует корректный health-check и балансировка,
- часть воркеров может "залипнуть" на недоступном мастере.
Правильный подход:
- один стабильный endpoint,
- за ним — рабочий механизм балансировки или VIP + failover.
- Связь с отказоустойчивостью из предыдущего вопроса
В контексте HA control plane (multi-master, HA etcd):
- мы делаем:
- несколько control plane нод,
- etcd-кластер,
- балансировщик/VIP перед kube-apiserver;
- воркеры всегда стучатся в этот балансировщик,
- при потере master-ноды:
- etcd кворум сохраняется,
- kube-apiserver доступен через другие ноды,
- балансировщик/Keepalived обеспечивает прозрачный failover.
- Краткая формулировка для интервью
Сильный ответ может звучать так:
- "Чтобы воркеры продолжали работать при отказе одного из мастеров, их нельзя привязывать к конкретному хосту. Мы поднимаем несколько control plane нод и выносим kube-apiserver за стабильный endpoint: либо L4-балансировщик (HAProxy/NGINX/Envoy), либо VIP с Keepalived. Все kubelet’ы и kubeconfig указывают на этот виртуальный адрес. Балансировщик делает health-check API-серверов и убирает недоступные ноды из пула. В результате падение отдельного мастера прозрачно для воркеров и клиентов."
Этот ответ демонстрирует понимание реальных механизмов отказоустойчивости, а не только "нарисовал три мастера на схеме".
Вопрос 9. Что такое StatefulSet в Kubernetes и для каких задач он используется?
Таймкод: 00:07:55
Ответ собеседника: правильный. Говорит, что StatefulSet предназначен для stateful-приложений, обеспечивает стабильные имена подов, порядок запуска, отдельные persistent-тома на под и подходит для кластерных сервисов, где важна идентичность и порядок.
Правильный ответ:
StatefulSet — это контроллер в Kubernetes для управления stateful-приложениями, где важны:
- стабильная сетёвая идентичность каждого pod’а,
- привязка данных к конкретному pod’у,
- управляемый порядок запуска/остановки/обновления.
В отличие от Deployment (который хорош для stateless-сервисов), StatefulSet решает задачи, когда:
- каждый экземпляр — не взаимозаменяемый,
- между репликами есть роли (leader/follower, master/replica, node-0/node-1),
- нужен предсказуемый DNS-имя и том, привязанный к конкретному pod’у.
Ключевые особенности StatefulSet:
- Стабильная идентичность pod’ов
Каждый pod имеет предсказуемое имя:
name-0,name-1,name-2, …
Например, для StatefulSet zk с 3 репликами:
zk-0zk-1zk-2
И для каждого создаётся стабильная DNS-запись через headless Service:
zk-0.zk-headless.default.svc.cluster.localzk-1.zk-headless.default.svc.cluster.local
Зачем это нужно:
- приложения типа ZooKeeper, Kafka, Elasticsearch, Redis Cluster, PostgreSQL Patroni и т.п. ждут фиксированные имена/роли нод;
- конфиги, cluster membership, quorum, репликация завязаны на стабильную идентичность.
- Привязка PersistentVolume к pod’у
StatefulSet обычно описывает volumeClaimTemplates:
- для каждого pod создаётся свой PVC (например,
data-zk-0,data-zk-1), - при перезапуске
zk-0он снова используетdata-zk-0, - данные не "переезжают" к другому pod’у при рестарте или перемещении на другую ноду.
Это критично для:
- баз данных,
- брокеров сообщений,
- storage-узлов,
- любых сервисов, где локальное состояние важно для конкретного экземпляра.
- Управляемый порядок запуска и остановки
StatefulSet обеспечивает:
- порядок создания по возрастанию индекса:
- сначала
pod-0, потомpod-1, потомpod-2;
- сначала
- порядок удаления и обновления по убыванию:
- сначала
pod-2, потомpod-1, потомpod-0.
- сначала
Это важно когда:
- кластерные приложения требуют, чтобы первая нода поднялась как primary/seed,
- новые ноды подключаются к уже работающим,
- корректный shutdown требует сначала выключить "младшие" ноды.
- Обновления (RollingUpdate) и контроль
StatefulSet поддерживает стратегии обновления:
RollingUpdate(по одному pod’у, в порядке индексов),OnDelete(обновление только при ручном удалении pod).
Плюсы:
- можно безопасно и поэтапно катить версии на кластер баз данных или брокеров;
- при проблемах — легко увидеть, какой конкретно pod/индекс сломался.
- Когда использовать StatefulSet (практические кейсы)
Подходит для:
- распределённых баз данных:
- PostgreSQL with replication (Patroni, Stolon),
- MySQL/Galera, Percona XtraDB Cluster,
- Cassandra, ScyllaDB,
- MongoDB ReplicaSet;
- брокеры и очереди:
- Kafka, RabbitMQ (кластер), NATS JetStream;
- координационные сервисы:
- ZooKeeper, Etcd (если по каким-то причинам запускается внутри кластера);
- любой кластерный сервис, завязанный на:
- уникальный ID ноды,
- предсказуемые имена,
- локальное состояние.
Не нужен StatefulSet, если:
- сервис полностью stateless;
- реплики полностью взаимозаменяемы;
- данные хранятся во внешнем сервисе (RDS, Cloud SQL, Kafka as a Service, S3 и т.п.). В этих случаях Deployment проще и надёжнее.
- Минимальный пример StatefulSet
Пример простого StatefulSet для условного кластера хранения:
apiVersion: v1
kind: Service
metadata:
name: storage-headless
spec:
clusterIP: None
selector:
app: storage
ports:
- port: 8080
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: storage
spec:
serviceName: storage-headless
replicas: 3
selector:
matchLabels:
app: storage
template:
metadata:
labels:
app: storage
spec:
containers:
- name: storage
image: my-registry.local/storage-node:v1.0.0
ports:
- containerPort: 8080
volumeMounts:
- name: data
mountPath: /var/lib/storage
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 10Gi
Здесь:
- каждый pod получит:
- своё имя:
storage-0,storage-1,storage-2, - свой PVC:
data-storage-0,data-storage-1,data-storage-2, - свой DNS:
storage-0.storage-headless, и т.д.
- своё имя:
- Типичный ответ на собеседовании
Хорошая формулировка:
- "StatefulSet используется для stateful-приложений, где важны стабильная идентичность pod’ов, привязка к постоянным томам и контролируемый порядок операций. Он даёт:
- фиксированные имена и DNS-адреса (
pod-0,pod-1, ...), - отдельный persistent volume на каждый pod,
- последовательный запуск/удаление и поэтапное обновление. Это делает его подходящим для баз данных, брокеров сообщений, координационных сервисов и любых кластерных систем, где экземпляры не взаимозаменяемы. Для stateless-сервисов обычно достаточно Deployment."
- фиксированные имена и DNS-адреса (
Такой ответ показывает не только знание определения, но и практическое понимание, зачем и когда использовать StatefulSet.
Вопрос 10. С каким сетевым плагином Kubernetes вы работали и почему был выбран именно он?
Таймкод: 00:09:16
Ответ собеседника: неполный. Указывает, что использовали Flannel как более простой вариант, упоминает Calico как более сложный и надёжный, но не даёт внятного сравнения, обоснования выбора под требования продакшена, сетевые политики и особенности инфраструктуры.
Правильный ответ:
Выбор CNI-плагина — это не "взяли что попроще", а инженерное решение, завязанное на:
- требования безопасности (network policies),
- производительность и тип трафика,
- тип инфраструктуры (on-prem, bare metal, облако),
- простоту эксплуатации и диагностики.
Ниже — разбор ключевых плагинов и аргументированного выбора (без повторения общеизвестных маркетинговых описаний).
- Базовые требования к CNI в продакшене
Хороший сетевой плагин должен:
- обеспечивать модель Kubernetes:
- pod-to-pod connectivity (Cluster CIDR),
- pod-to-service,
- непрозрачность для приложений — без NAT внутри кластера (по возможности);
- поддерживать:
- NetworkPolicy (L3/L4 минимум),
- интеграцию с существующей инфраструктурной сетью;
- быть:
- наблюдаемым (метрики, логи, debuginfo),
- предсказуемым под нагрузкой,
- понятным по failure modes.
- Flannel: когда он уместен
Flannel:
- простой overlay (vxlan/host-gw),
- легко поднимается,
- минимальное количество фич:
- не поддерживает NetworkPolicy из коробки,
- в основном только L3 connectivity.
Когда выбор Flannel можно считать оправданным:
- dev/stage кластера,
- минимальные требования по безопасности:
- нет сложных сегментаций,
- доверенная внутренняя сеть;
- нужен максимально быстрый старт, а ограничения понятны.
Но:
- для продакшен-систем с жёсткими требованиями к безопасности и микросегментации Flannel быстро становится ограничением, потому что сетевые политики нужны почти всегда.
- Calico: типичный продакшен-стандарт
Calico — один из наиболее зрелых вариантов.
Ключевые преимущества:
- Полноценная поддержка NetworkPolicy:
- L3/L4-политики,
- возможность задавать политики между namespace, labels, CIDR, service-аккаунтами.
- Гибкие backend’ы:
- BGP (интеграция с физической сетью, без overlay),
- VXLAN/IPIP (overlay),
- гибридные схемы.
- Хорошая наблюдаемость:
- Prometheus-метрики,
- calicoctl для диагностики,
- понятные таблицы маршрутизации.
Причины выбрать Calico в продакшене:
- Нужно жёстко ограничить трафик между неймспейсами и сервисами.
- Интеграция с существующим маршрутизируемым underlay через BGP.
- Требуется масштабируемая, предсказуемая сеть без лишнего overhead.
Пример сетевой политики под Calico/стандартный CNI:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-api
namespace: prod
spec:
podSelector:
matchLabels:
app: api
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
policyTypes:
- Ingress
Такой уровень контроля недостижим с "голым" Flannel.
- Cilium: когда важен eBPF и deep observability
Отдельно стоит упомянуть Cilium:
- использует eBPF вместо iptables:
- выше производительность,
- меньше проблем на больших кластерах,
- гибкие политики до L7 (HTTP-aware).
- Отличная наблюдаемость:
- Hubble для просмотра потоков трафика.
Выбор Cilium оправдан, когда:
- много микросервисов и сложные сетевые политики;
- нужно видеть и контролировать трафик вплоть до HTTP-запросов;
- важна производительность и масштабируемость сетевого стека.
- Как звучит зрелый ответ на вопрос "почему выбрали X?"
Пример сильного ответа (если вы, например, выбрали Calico):
- "Мы рассматривали несколько CNI: Flannel как простой baseline, Calico, Cilium. В итоге выбрали Calico, потому что:
- нам критичны сетевые политики между микросервисами и неймспейсами;
- требовалась интеграция с нашей L3-сетью через BGP без избыточных NAT’ов;
- Calico даёт предсказуемую модель маршрутизации, хорошие инструменты диагностики и стабильно работает на кластерах 100+ нод. Flannel оставили для небольших dev-стендов, где network policy и сложная сегментация не нужны."
Если используете Flannel осознанно:
- "На конкретном проекте выбрали Flannel (vxlan), потому что:
- это закрытый изолированный контур без жёстких требований к межсервисной сегментации;
- упор был на простоту эксплуатации и минимальное количество движущихся частей;
- сетевые политики реализовывались на уровне внешних firewall’ов/SDN. Если бы стояла задача более детализированного контроля трафика внутри кластера, выбрали бы Calico или Cilium."
- Кратко: как отвечать на интервью
Чтобы ответ выглядел профессионально, важно:
- назвать конкретный CNI (или несколько),
- описать:
- какие требования были,
- как выбранный плагин этим требованиям соответствует,
- чем он лучше альтернатив в вашем контексте.
Избегать формулировок уровня:
- "Flannel, потому что он проще." Лучше:
- "Flannel, потому что для этого кластера не требовались NetworkPolicy, а приоритетом была простота и минимальный operational overhead; для продакшен-кластеров с жёсткой сегментацией мы используем Calico/Cilium."
Вопрос 11. Как можно предоставить внешний доступ к Kafka, запущенной внутри Kubernetes-кластера?
Таймкод: 00:09:57
Ответ собеседника: неполный. Перечисляет общие варианты экспонирования сервисов (NodePort, Ingress, external IP), но не учитывает специфику Kafka: работу с брокерскими ID и advertised адресами, необходимость стабильных endpoint’ов для каждого брокера и выбор корректного типа Service.
Правильный ответ:
Для Kafka недостаточно "просто открыть сервис наружу". В отличие от обычного HTTP-сервиса:
- клиент сначала подключается к bootstrap-брокеру,
- затем получает из кластера метаданные с advertised listener’ами всех брокеров,
- и дальше ходит напрямую к конкретным брокерам по тем адресам/портам, которые те отдают.
Поэтому ключевая задача — сделать так, чтобы:
- внутренние адреса работали внутри кластера,
- внешние клиенты видели корректные внешние адреса и порты для каждого брокера,
- advertised.listeners были согласованы с реальной сетевой схемой.
Ниже основные подходы.
- Базовый принцип: отдельный внешний endpoint на брокер
Для продакшен-конфигурации чаще всего:
- каждому Kafka-брокеру нужен свой внешний адрес (DNS или IP) и порт,
- эти адреса прописываются в
advertised.listeners, - типичные схемы:
- NodePort + external LB/DNS;
- LoadBalancer на брокер;
- hostPort/HostNetwork (реже, аккуратно);
- использования операторов (Strimzi, Confluent), которые автоматизируют это.
- StatefulSet + Headless Service для внутренних клиентов
Внутри кластера Kafka обычно разворачивается через StatefulSet:
- брокеры:
kafka-0,kafka-1,kafka-2, - headless service, например
kafka-internal:
Внутренний доступ:
kafka-0.kafka-internal:9092kafka-1.kafka-internal:9092kafka-2.kafka-internal:9092
Конфигурация listeners/advertised.listeners:
listeners=PLAINTEXT://:9092
advertised.listeners=PLAINTEXT://kafka-0.kafka-internal:9092
broker.id=0
Это достаточно для pod-to-pod внутри кластера, но не для внешних клиентов.
- Внешний доступ: NodePort + DNS
Типичный и понятный вариант без "магии" cloud LoadBalancer’ов.
Схема:
- для каждого брокера создаём отдельный Service типа NodePort, например:
kafka-0-external-> nodePort31090kafka-1-external-> nodePort31091kafka-2-external-> nodePort31092
- публикуем DNS-имена вида:
kafka-0.example.com-> указывает на нужную ноду,- или используем один внешний LB, который по SNI/портам маршрутизирует трафик.
Конфиг брокера (упрощённо):
listeners=PLAINTEXT://:9092,EXTERNAL://:9094
advertised.listeners=PLAINTEXT://kafka-0.kafka-internal:9092,EXTERNAL://kafka-0.example.com:9094
listener.security.protocol.map=PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT
inter.broker.listener.name=PLAINTEXT
Плюсы:
- Полный контроль.
- Работает на bare-metal и в любых окружениях.
Минусы:
- Нужен management DNS/балансировщиков;
- Нужно следить за соответствием StatefulSet ordinal ↔ Service ↔ DNS.
- Внешний доступ: LoadBalancer на брокер (облако)
Если кластер в облаке (GKE/EKS/AKS):
- для каждого брокера можно поднять Service типа LoadBalancer:
apiVersion: v1
kind: Service
metadata:
name: kafka-0-external
spec:
type: LoadBalancer
ports:
- name: external
port: 9094
targetPort: 9094
selector:
statefulset.kubernetes.io/pod-name: kafka-0
Дальше:
- используем выданный external IP/DNS в
advertised.listeners:EXTERNAL://a1b2c3d4e5f6.cloudprovider.net:9094.
Плюсы:
- минимум ручной работы — cloud сам даёт адреса.
Минусы:
- дорого при большом количестве брокеров;
- жёсткая связка с конкретным cloud-провайдером.
- Ingress для Kafka: осторожно
Классический HTTP Ingress Controller (Nginx/Ingress-Nginx/Traefik):
- не подходит для Kafka "из коробки", потому что Kafka — не HTTP, а бинарный протокол с особой моделью коннектов;
- возможны решения:
- TCP/SSL passthrough,
- отдельные Ingress-контроллеры с поддержкой TCP/UDP (Nginx, HAProxy) и маппингом портов,
- но это уже ближе к "L4 ingress / TCP LB", а не обычному HTTP ingress.
Итого:
- Ingress можно использовать как L4 TCP proxy, но только если он умеет прозрачно прокидывать TCP, без ломания advertised адресов и с корректной маршрутизацией на конкретные брокеры.
- Почему "просто NodePort/Ingress" — ложка дёгтя
Типичная ошибка ответа:
-
"Откроем NodePort/Ingress и всё". Проблема:
-
если advertised.listeners указывают внутренние DNS/адреса, внешний клиент до них не достучится;
-
если один внешний endpoint, а внутри несколько брокеров, нужно гарантировать, что маршрутизация не ломает affinity и протокол.
Поэтому правильная конфигурация всегда включает:
- корректные listeners/advertised.listeners,
- соответствие внешних адресов каждому брокеру,
- разделение internal/external listeners.
- Краткий сильный ответ для интервью
Пример:
- "Kafka не достаточно просто 'повесить на Ingress': клиенты используют advertised.listeners, и каждый брокер должен отдавать клиенту такой адрес, по которому до него реально можно достучаться извне. В Kubernetes я обычно поднимаю Kafka в StatefulSet + headless service для внутреннего трафика и добавляю отдельные external listeners. Для внешних клиентов:
- в облаке — Service типа LoadBalancer на каждый брокер, эти адреса попадают в advertised.listeners;
- on-prem — NodePort + внешний TCP-балансировщик/DNS, снова с привязкой к конкретным брокерам. Важно чётко развести internal и external listeners и убедиться, что метаданные Kafka всегда отдают клиенту валидные внешние endpoint’ы."
Такой ответ демонстрирует понимание специфики Kafka, а не просто знание типов сервисов Kubernetes.
Вопрос 12. Для чего в Kubernetes используется компонент kube-proxy и как он связан с iptables/ipvs?
Таймкод: 00:10:44
Ответ собеседника: неполный. В общих чертах говорит про "сеть и правила", упоминает iptables/ipvs, но не раскрывает ключевую роль kube-proxy: реализацию механизма Service, виртуальных IP и балансировки трафика между подами.
Правильный ответ:
kube-proxy — это компонент, который реализует семантику Kubernetes Service на уровне сетевой плоскости узла.
Кратко:
- следит за объектами Service и Endpoints/EndpointSlice в API,
- на основе их состояния настраивает правила маршрутизации/балансировки на каждой ноде,
- обеспечивает доступ к сервису по виртуальному IP (ClusterIP), NodePort и, частично, для LoadBalancer,
- использует для этого iptables или IPVS (а также есть режим userspace, который в реальном продакшене почти не используется).
То есть kube-proxy — не "про сеть вообще", а конкретно про "как трафик, пришедший на Service, попадает в нужный Pod".
- Как работает kube-proxy логически
Когда вы создаёте Service:
- Kubernetes выделяет ему виртуальный IP (ClusterIP),
- kube-proxy на каждой ноде:
- получает обновления через watch API,
- строит локальные правила:
- "если трафик пришёл на ClusterIP:port — отправить его на один из backend pod’ов (Endpoints)".
Важно:
- все ноды знают, как добраться до любого Service (ClusterIP),
- нет отдельного "центрального" балансировщика: логика размазана по нодам.
- Режим iptables
Один из распространённых режимов (по умолчанию в классических установках):
- kube-proxy генерирует набор правил iptables (chain’ы
KUBE-SERVICES,KUBE-NODEPORTS,KUBE-SEP-*,KUBE-SVC-*).
Механика:
- на ноде:
- пакеты к ClusterIP:port матчутся в
KUBE-SERVICES, - далее DNAT на конкретный pod IP:port,
- выбор backend’а происходит через iptables
statistic/random/nth(примитивный балансинг).
- пакеты к ClusterIP:port матчутся в
Особенности:
- Преимущества:
- просто,
- надёжно,
- не требует дополнительного демона в data plane: после применения правил ядро всё само маршрутизирует.
- Недостатки:
- при большом количестве сервисов и эндпоинтов таблицы iptables разрастаются,
- обновление большого набора правил дорого,
- масштабируемость хуже по сравнению с IPVS.
- Режим IPVS
Более современный и предпочтительный для больших кластеров режим.
IPVS (IP Virtual Server):
- реализует L4 балансировку в ядре Linux,
- kube-proxy:
- создаёт виртуальные сервисы IPVS для каждого Service,
- добавляет реальные серверы (pod IP) как backend’ы,
- всё это по-прежнему управляется на основе Service/EndpointSlice.
Плюсы IPVS:
- лучше масштабируемость:
- тысячи/десятки тысяч сервисов и эндпоинтов обрабатываются эффективнее, чем в iptables;
- более богатые алгоритмы балансировки:
- rr, wrr, lc, wlc, sh, dh и др.;
- более быстрые обновления конфигурации.
Минусы/нюансы:
- чуть сложнее отладка, нужно смотреть
ipvsadm -Ln, - требуется поддержка модуля IPVS в ядре.
- Userspace-режим (исторический)
Старый режим, сейчас использовать в серьёзных окружениях не нужно:
- kube-proxy сам слушает порт service’а и проксирует трафик в pod,
- медленно, лишние hop’ы, неэффективно.
- Связь с типами Service
kube-proxy обслуживает:
- ClusterIP:
- виртуальный IP внутри кластера,
- весь трафик идёт через iptables/IPVS rules.
- NodePort:
- настраивает правила:
- порт на ноде -> соответствующий ClusterIP -> pod’ы;
- настраивает правила:
- LoadBalancer:
- сам внешний LB создаёт cloud-controller-manager,
- но backend-логика на нодах (NodePort/ClusterIP -> pod) всё равно реализуется kube-proxy.
- Как это правильно сформулировать на интервью
Хороший ответ:
- "kube-proxy — это компонент на каждой ноде, который реализует механику Kubernetes Service. Он смотрит на Service и EndpointSlice в API и программирует сетевой стек ноды (в режиме iptables или IPVS), чтобы трафик, приходящий на ClusterIP/NodePort/LoadBalancer, прозрачно балансировался между живыми подами. В iptables-режиме он генерирует DNAT-правила; в IPVS-режиме использует встроенный в ядро L4-балансировщик для более эффективной и масштабируемой обработки. Таким образом kube-proxy — это glue между абстракцией Service в Kubernetes и реальными сетевыми правилами в Linux."
Такой ответ показывает понимание роли kube-proxy как реализационного механизма сервисов и балансировки, а не абстрактного "сетевого компонента".
Вопрос 13. Что такое Custom Resource Definition (CRD) и для чего он нужен?
Таймкод: 00:11:34
Ответ собеседника: правильный. Описывает CRD как механизм создания пользовательских сущностей в Kubernetes, аналогичных стандартным объектам, с возможностью настройки прав через RBAC.
Правильный ответ:
Custom Resource Definition (CRD) — это механизм расширения Kubernetes API без форка и без модификации исходного кода Kubernetes. С его помощью вы добавляете собственные типы ресурсов (Custom Resources), которые:
- живут в том же API, что и Pod, Service, Deployment;
- поддерживаются теми же инструментами (kubectl, клиенты, RBAC, audit, admission webhooks);
- становятся частью декларативной модели управления инфраструктурой и приложениями.
По сути, CRD позволяет превратить Kubernetes в платформу для построения своих операторов и абстракций.
Ключевые идеи:
- Расширение модели Kubernetes
CRD используется, когда стандартных примитивов (Deployment, StatefulSet, Ingress и т.д.) недостаточно для описания доменной логики.
Примеры:
KafkaCluster,PostgresCluster,Redis,Elasticsearchкак абстракции управляемых сервисов;CanaryRelease,BackupPolicy,AlertRule,FeatureFlagдля описания инфраструктурных и продуктовых сущностей;- внутренние бизнес-объекты:
PaymentGatewayConfig,Tenant,MLJob.
После создания CRD вы можете:
- создавать объекты нового типа (Custom Resource, CR),
- работать с ними как с любыми ресурсами:
kubectl get kafkaclusters
kubectl describe kafkacluster my-cluster
kubectl apply -f my-cluster.yaml
- Связка CRD + Controller = Operator-паттерн
Сам по себе CRD — только схема и API. "Мозги" живут в контроллере.
Типичный паттерн:
- Вы определяете CRD: описываете желаемое состояние.
- Пишете контроллер/оператор (на Go это делается через Kubebuilder/operator-sdk), который:
- подписывается на события CR,
- сравнивает desired state (spec) и actual state (cluster),
- приводит систему к желаемому состоянию (создаёт/обновляет Pod’ы, Service’ы, Secrets, ConfigMap’ы, PVC и т.д.).
Пример логики:
- CRD
KafkaCluster:spec.replicas: 3,spec.storage,spec.zookeeper,spec.listenersи т.д.
- Оператор:
- создаёт StatefulSet, Services, конфиги, следит за обновлениями, перезапусками, версиями.
Так появляются "нативные" для Kubernetes управляемые сервисы.
- Структура CRD (вкратце)
CRD — это объект типа CustomResourceDefinition в группе apiextensions.k8s.io/v1.
Упрощённый пример:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: kafkaclusters.mycompany.io
spec:
group: mycompany.io
names:
kind: KafkaCluster
plural: kafkaclusters
singular: kafkacluster
shortNames:
- kfk
scope: Namespaced
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
replicas:
type: integer
minimum: 1
version:
type: string
status:
type: object
После применения этого CRD вы можете создать ресурс:
apiVersion: mycompany.io/v1alpha1
kind: KafkaCluster
metadata:
name: demo
spec:
replicas: 3
version: "3.7.0"
- Валидация, RBAC, экосистема
CRD полноценно интегрируется в экосистему Kubernetes:
- Валидация:
- через OpenAPI v3 schema в CRD,
- через admission webhooks для сложной/динамической логики.
- RBAC:
- доступ к CR регулируется как к любому ресурсу:
apiGroups: ["mycompany.io"],resources: ["kafkaclusters"],verbs: ["get","list","watch","create","update","delete"].
- доступ к CR регулируется как к любому ресурсу:
- Observability:
- CR-ы видны в audit-логах, можно навесить policies (OPA/Gatekeeper/Kyverno).
- Версионирование:
- разные версии (v1alpha1, v1beta1, v1) с conversion webhook для плавной миграции.
- Когда CRD — правильный выбор, а когда нет
CRD уместен, когда:
- есть повторяющаяся доменная задача, которую хочется описать декларативно:
- "кластер Kafka", "pipeline деплоя", "бэкап", "правило алерта";
- нужно спрятать сложность behind high-level API:
- вместо десятка YAML’ов с StatefulSet, Services, ConfigMap, PVC дать один CR
KafkaCluster.
- вместо десятка YAML’ов с StatefulSet, Services, ConfigMap, PVC дать один CR
CRD не нужен, если:
- вы просто описываете конфиг одного приложения — достаточно ConfigMap/Secret/Deployment;
- нет контроллера, который будет интерпретировать CR — голый CRD без логики превращается в "словарь YAML’ов", а не в работающую абстракцию.
- Краткий сильный ответ для интервью
Вариант:
- "CRD — это способ расширить Kubernetes собственными типами ресурсов. Он регистрирует новый API-объект (Custom Resource), который выглядит и ведёт себя как нативный: доступен через kubectl, защищается RBAC, валидируется схемой, логируется. В связке с контроллером CRD позволяет реализовать operator-паттерн — автоматизировать управление сложными системами (например, БД, очередями, доменными сущностями) декларативно, через Kubernetes API."
Такой ответ показывает не только знание определения, но и понимание, как CRD используется в реальной архитектуре.
Вопрос 14. В чем отличие ConfigMap от Secret?
Таймкод: 00:12:29
Ответ собеседника: неправильный. Утверждает, что данные в Secret "шифруются" и маскируются, опираясь на base64, не отличая кодирование от шифрования, и не даёт корректного сравнения с ConfigMap.
Правильный ответ:
ConfigMap и Secret — оба механизма для передачи конфигурации в поды, но они решают разные задачи и имеют разный уровень требований к безопасности.
Ключевое отличие:
- ConfigMap — для неконфиденциальных данных.
- Secret — для чувствительных данных (пароли, ключи, токены).
Важно: стандартный Secret по умолчанию НЕ шифруется, а только кодируется в base64 (это не защита, а формат хранения/транспортировки). Реальную безопасность обеспечивают:
- шифрование данных Secret в etcd (encryption at rest),
- ограничение доступа через RBAC,
- использование внешних секрет-хранилищ/integраций (Vault, KMS и т.п.).
Разберём по пунктам.
- Назначение
- ConfigMap:
- хранит конфигурацию приложения:
- URL сервисов,
- feature-флаги,
- настройки логирования,
- произвольные конфиги (YAML/JSON/properties).
- не предназначен для секретов.
- хранит конфигурацию приложения:
- Secret:
- хранит:
- пароли к БД,
- API keys,
- OAuth/JWT токены,
- TLS-сертификаты и приватные ключи,
- SSH-ключи и т.п.
- подразумевает более строгую модель доступа.
- хранит:
- Хранение и base64
И ConfigMap, и Secret в etcd по умолчанию хранятся в открытом виде, с одним отличием:
- ConfigMap:
- данные лежат как есть (строки/бинарь),
- Secret:
- значения в манифесте указываются в base64:
- это кодирование, а не шифрование,
- оно защищает только от случайного "подглядывания" в YAML, но не от того, у кого есть доступ к API/etcd.
- значения в манифесте указываются в base64:
Пример Secret:
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
data:
username: YXBwX3VzZXI= # "app_user"
password: c3VwZXJfc2VjcmV0 # "super_secret"
Правильный подход к безопасности Secret:
- включить EncryptionConfiguration на уровне API-сервера:
- шифрование Secret в etcd (KMS/AES и т.д.);
- жёсткий RBAC:
- ограничить
get/list/watchдля Secret только нужными сервис-аккаунтами;
- ограничить
- не логировать значения Secret.
- Доступ из Pod’ов
И ConfigMap, и Secret могут прокидываться:
- как переменные окружения,
- как файлы в volume (Projected/ConfigMap/Secret volumes).
Примеры:
- ConfigMap как конфиг:
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
- Secret как пароль:
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
Их также можно монтировать как файлы; для Secret (особенно TLS-ключей) это распространённый паттерн.
- RBAC и безопасность
- ConfigMap:
- часто доступен шире,
- иногда читается сервисами мониторинга, дебаг-утилитами и т.п.
- Secret:
- должен иметь строго ограниченный доступ:
- отдельные ServiceAccount’ы,
- namespace isolation,
- запрет list/watch для широких ролей (особенно cluster-admin-like tooling в проде).
- должен иметь строго ограниченный доступ:
Ошибкой является:
- хранить пароли/токены в ConfigMap вместо Secret,
- раздавать
list secretsшироким ролям.
- Дополнительные особенности Secret
- Типы Secret:
Opaque— произвольные ключ/значение;kubernetes.io/dockerconfigjson— креды для registry;kubernetes.io/tls— сертификат + приватный ключ;- и другие — с семантикой, которую понимают компоненты Kubernetes.
- Работа с CSI и внешними секрет-хранилищами:
- через Secret Store CSI Driver можно монтировать секреты из Vault/AWS Secrets Manager/GCP SM и т.д.
- Краткий корректный ответ для интервью
Вариант:
- "ConfigMap хранит некритичные конфигурационные данные, Secret — чувствительные данные: пароли, ключи, токены. Главное отличие не в том, что Secret 'зашифрован base64' — base64 это просто кодирование. За безопасность отвечают: RBAC, опциональное шифрование Secret в etcd (EncryptionConfiguration), ограничения доступа к namespace и интеграции с безопасными хранилищами. С точки зрения использования оба ресурса монтируются в поды как env или файлы, но секреты всегда должны храниться и раздаваться через Secret, а не через ConfigMap."
Такой ответ подчёркивает понимание безопасности, а не только синтаксиса YAML.
Вопрос 15. Как плавно вывести несколько рабочих нод из Kubernetes-кластера для обслуживания, чтобы поды перенеслись без даунтайма?
Таймкод: 00:13:05
Ответ собеседника: неправильный. Надеется на автоматическое перераспределение после "падения" нод, не использует cordon/drain, не описывает управляемую миграцию подов и не учитывает риски даунтайма.
Правильный ответ:
Плавный вывод нод из кластера — это управляемый процесс, при котором:
- на ноды перестают попадать новые поды,
- существующие поды корректно выселяются (evict) и перезапускаются на других нодах,
- трафик постепенно уходит от старых подов,
- состояние кластера остаётся "зелёным" без даунтайма (при условии, что есть достаточная емкость и правильно настроены приложения).
Ключевые инструменты:
kubectl cordonkubectl drain- корректные
PodDisruptionBudgetиreadinessProbeу приложений.
Базовая стратегия:
- Подготовка: убедиться, что кластер выдержит
Перед началом:
- Проверь:
- хватает ли свободных ресурсов на оставшихся нодах для всех подов, которые будут перемещены;
- нет ли single-point-of-failure:
- только один реплика Set/Deployment с
replicas: 1на выводимой ноде; - stateful-сервисы без репликации.
- только один реплика Set/Deployment с
- Если нужно — предварительно:
- добавить новые ноды (scale out),
- увеличить реплики критичных сервисов.
- Cordon: остановить расписание новых подов на ноду
Команда:
kubectl cordon <node-name>
Эффект:
node.spec.unschedulable = true;- новые поды на ноду не планируются;
- существующие поды продолжают работать.
Для нескольких нод:
kubectl cordon node1 node2 node3
Это безопасный первый шаг: вы замораживаете ноды для новых workload’ов.
- Drain: контролируемо выселить поды
Команда (для каждой ноды):
kubectl drain <node-name> \
--ignore-daemonsets \
--delete-emptydir-data
Что делает drain:
- Выполняет eviction подов (через API) с учётом:
PodDisruptionBudget(PDB): не нарушает заданные ограничения доступности;readinessProbe: новые поды должны стать ready, прежде чем трафик уйдёт со старых.
- Не трогает:
- Pod’ы DaemonSet (если указан
--ignore-daemonsets), - системные поды в
kube-systemбез соответствующей конфигурации.
- Pod’ы DaemonSet (если указан
- Для управляемых ресурсов (Deployment, ReplicaSet, StatefulSet) поды будут пересозданы на других нодах.
Важно:
--delete-emptydir-dataнужен, т.к. pod’ы с emptyDir локальны ноде; при drain они будут удалены и пересозданы.- Если есть поды, не управляемые контроллерами (standalone Pod), или без replicaSet/StatefulSet:
- drain по умолчанию откажется их удалять;
- нужно либо указать
--force, либо сначала поправить манифесты (так делать правильно).
- Роль PodDisruptionBudget (PDB)
Чтобы не словить даунтайм при drain, для критичных сервисов нужно определить PDB:
Пример:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: my-api
Это гарантирует:
- при drain Kubernetes не выселит одновременно слишком много подов одного сервиса;
- всегда останется минимум 2 живых пода.
Без PDB:
- Kubernetes может эвиктнуть все поды сервиса с нескольких нод, теоретически создав окно недоступности (особенно при агрессивном drain нескольких нод).
- ReadinessProbe и бездаунтаймовость
Для реального no-downtime важно:
- Настроить
readinessProbeтак, чтобы:- новый pod начинает принимать трафик только когда действительно готов (прогрет кэш, миграции выполнены и т.д.);
- при drain:
- старый pod сначала помечается not-ready,
- kube-proxy / сервис-меш убирает его из балансировки,
- только после этого pod удаляется.
Если readiness-проб нет или они формальны:
- трафик может прилетать в еще не готовые поды,
- клиент увидит ошибки — это уже не "плавный" вывод ноды.
- Очерёдность и массовый вывод нескольких нод
Лучший подход:
- Выводить ноды по одной или небольшими партиями:
- провёл
cordon + drainnode1, - убедился, что все поды переселились, сервисы зелёные,
- затем node2, node3 и т.д.
- провёл
- После обслуживания:
- вернуть ноды в пул:
kubectl uncordon <node-name>
Если нужно вывести ноду навсегда:
- после drain:
- удалить ноду из кластера (kubeadm, cloud provider API, Terraform и т.п.).
- Типичные ошибки, которые стоит не допускать (и которые были в ответе)
- Просто "выключить" ноду и надеяться, что "Kubernetes сам поднимет поды":
- да, контроллеры пересоздадут поды, но:
- это происходит с таймаутами (node not ready, grace period),
- можно получить даунтайм, всплеск ошибок, потерю соединений.
- да, контроллеры пересоздадут поды, но:
- Не использовать
cordon:- новые поды могут продолжать планироваться на ноду, которую вы собираетесь выключить.
- Отсутствие PDB:
- легко убить все реплики сервиса при drain нескольких нод.
- Неучитывание stateful workloads:
- базы, кластеры, storage могут вести себя сложнее, требуется дополнительная логика.
- Краткий сильный ответ для интервью
Вариант:
- "Для плавного вывода ноды мы сначала делаем cordon, чтобы остановить расписание новых подов на неё, затем drain — это инициирует контролируемый eviction подов с учётом PodDisruptionBudget и readiness-проб. Поды пересоздаются на других нодах, трафик уходит от уходящих подов до их удаления. Ноды выводим по одной (или малыми партиями), предварительно проверив, что оставшихся ресурсов достаточно. После обслуживания используем uncordon, чтобы вернуть ноду в пул. Просто 'выключить' ноду без cordon/drain — плохая практика, она может привести к даунтайму."
Такой ответ показывает понимание механизмов Kubernetes, доступности сервисов и production-подхода к эксплуатационным операциям.
Вопрос 16. Что такое сетевые политики в Kubernetes и каков опыт их использования?
Таймкод: 00:22:44
Ответ собеседника: неполный. Верно указывает, что сетевые политики регулируют, кто с кем может взаимодействовать, но не раскрывает модель, детали реализации и практику применения; опыт написания минимальный.
Правильный ответ:
Сетевые политики (NetworkPolicy) в Kubernetes — это декларативный механизм уровня L3/L4 для управления сетевым трафиком между подами и между подами и внешними сущностями. Они определяют, какой ingress/egress трафик разрешён, на основе:
- namespace,
- label’ов подов,
- IP-блоков,
- портов и протоколов.
Ключевые моменты:
- Базовая модель безопасности
По умолчанию (без NetworkPolicy):
- все поды в кластере могут общаться со всеми (full mesh),
- это плохо для безопасности: любая уязвимая или скомпрометированная служба может сканировать и атаковать весь кластер.
NetworkPolicy позволяет перейти от модели "allow all" к "zero trust внутри кластера":
- явным образом описать, кто с кем и по каким портам может говорить;
- сегментировать трафик:
- frontend → backend,
- backend → database,
- запретить "все со всеми".
Важно: NetworkPolicy — это whitelist-модель:
- если для pod’а не существует ни одной политики, его трафик не ограничен (по умолчанию allow);
- если для pod’а есть хотя бы одна NetworkPolicy:
- для указанных направлений (ingress/egress) всё неописанное считается запрещённым;
- нужно явно описать, что разрешено.
- Кто реализует NetworkPolicy
NetworkPolicy — это спецификация. Её применимость зависит от CNI-плагина:
- Поддерживают: Calico, Cilium, Weave Net, Kube-router и др.
- Не поддерживают или частично: стандартный kubenet, некоторые облачные CNI без расширений.
Если CNI не поддерживает NetworkPolicy:
- объект в Kubernetes создастся, ошибок не будет,
- но трафик фактически не будет фильтроваться.
В проде всегда нужно:
- проверять поддержку сетевых политик выбранным CNI;
- иногда использовать расширения (Calico GlobalNetworkPolicy, CiliumNetworkPolicy) для более тонкого контроля (L7, DNS, HTTP).
- Структура NetworkPolicy
Простой пример: разрешаем входящий трафик к app=backend только от pod’ов с app=frontend на порт 8080.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
Семантика:
podSelector— к каким pod’ам применяется политика (target поды).policyTypes:- Ingress — контролируем входящий трафик;
- Egress — исходящий трафик;
- Внутри
ingress:from— кто может подключаться:podSelector,namespaceSelector,ipBlockили их комбинации;
ports— какие порты разрешены.
Пример egress-политики для backend, разрешающей только доступ к БД и DNS, остальное запрещено:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-egress
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: db
ports:
- protocol: TCP
port: 5432
- to:
- namespaceSelector:
matchLabels:
kube-system: "true"
ports:
- protocol: UDP
port: 53
Теперь backend не может "гулять" по всему миру и сканировать сеть.
- Практические паттерны использования
На практике сетевые политики используют для:
- Сегментации по уровням:
- frontend может ходить только к backend;
- backend — только к БД и нескольким внешним сервисам;
- admin/ops-инструменты — имеют отдельные права.
- Изоляции namespace:
- запрет кросс-namespace трафика по умолчанию;
- отдельные политики для shared-сервисов (auth, logging, metrics).
- Ограничения egress:
- запрет прямых исходящих соединений в интернет;
- разрешение только через proxy/NAT-шлюз;
- защита от data exfiltration при компрометации.
Хороший production-подход:
- Ввести default-deny:
- политика, которая запрещает весь ingress/egress по умолчанию для namespace или набора подов.
- Поверх неё — точечные "allow":
- тем самым получается явная модель доступа.
Пример default-deny для ingress в namespace:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Важные нюансы
- NetworkPolicy не управляет:
- NodePort/LoadBalancer уровнем за пределами CNI (там нужны firewall’ы/cloud security groups),
- L7-правилами (HTTP-пути, JWT и т.д.) — это зона Istio/Ingress Controller/WAF.
- Порядка применения нет:
- все политики для pod’а объединяются (logical OR по правилам разрешения, но итоговая модель — пересечение allow/deny: разрешено только то, что кем-то явно разрешено).
- Тестирование критично:
- легко отрезать сервисы (особенно DNS и метрики);
- полезно писать тесты или использовать инструменты визуализации (Calico, Cilium UI, Policies audit).
- Как можно кратко описать свой опыт (идеальный ответ)
Даже если реальный опыт ограничен, зрелый ответ должен звучать так:
- "NetworkPolicy — это механизм L3/L4-фильтрации трафика между подами и наружу на основе label’ов, namespace и IP-блоков. В проде их использую для реализации zero-trust внутри кластера: default-deny и набор явных правил для frontend → backend → db, плюс ограничения egress. Важно понимать, что политики работают только при поддержке соответствующим CNI (Calico/Cilium и т.д.). Обычно начинаю с изоляции namespace, затем добавляю политики per-service, обязательно не забывая про DNS и сервисы мониторинга."
Такой ответ показывает:
- понимание концепции,
- знание механики и ограничений,
- понимание того, как это применяется в реальной инфраструктуре.
Вопрос 17. Разрешено ли по умолчанию сетевое взаимодействие между подами из разных namespace при использовании CNI-плагина, если сетевые политики не заданы?
Таймкод: 00:23:08
Ответ собеседника: неправильный. Не даёт чёткого ответа и уходит в рассуждения про IP, не демонстрируя понимания стандартного поведения: при отсутствии сетевых политик трафик обычно не ограничен.
Правильный ответ:
По умолчанию, в типичной Kubernetes-инсталляции с корректным CNI-плагином:
- сетевое взаимодействие между подами разрешено:
- внутри одного namespace,
- между разными namespace,
- если НЕ заданы NetworkPolicy (сетевые политики), то ограничений на L3/L4-уровне нет.
То есть дефолтная модель — "allow all внутри кластера", пока вы явно не включили политику ограничения.
Ключевые моменты:
- Роль CNI и базовое поведение
- CNI-плагин (Calico, Cilium, Weave Net, flannel+cali, cloud CNI и т.п.) отвечает за:
- адресацию подов,
- маршрутизацию трафика между нодами/подами,
- иногда за реализацию NetworkPolicy.
- Базовый контракт Kubernetes:
- каждый pod получает IP,
- любой pod должен иметь возможность достучаться до любого другого pod по этому IP (pod-to-pod connectivity), если политиками это не ограничено.
Если сетевых политик нет:
- CNI настраивает "плоскую" сеть:
- все pod IP достижимы друг для друга;
- namespace не является сетевым boundary сам по себе, это логическая изоляция API-объектов, не сети.
- Когда трафик начнёт блокироваться
Ограничения появляются, когда:
- вы создаёте NetworkPolicy, у которой:
podSelectorсовпадает с подами,- указаны
policyTypes: Ingress/Egress.
С этого момента для выбранных подов:
- всё, что не разрешено политикой, становится запрещено (whitelist-модель).
Важно:
- Политики применяются пер-pod:
- если для пода не существует ни одной NetworkPolicy, он продолжает жить в режиме "allow all", даже если в namespace есть другие политики, но с другим
podSelector.
- если для пода не существует ни одной NetworkPolicy, он продолжает жить в режиме "allow all", даже если в namespace есть другие политики, но с другим
- Namespace сам по себе не включает "default deny", пока вы явно не создадите политику вроде:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: some-namespace
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
- Особенности и исключения
- Некоторые enterprise/CNI-решения могут предлагать "global default deny" или свои глобальные политики:
- это уже специфика конкретного решения (Calico GlobalNetworkPolicy, CiliumClusterwidePolicy и т.п.);
- но это не стандартное поведение vanilla Kubernetes.
- В облаках безопасность "снаружи" (NodePort, LoadBalancer, ingress) дополнительно зависит от:
- security groups / firewall,
- но внутри кластера между подами без NetworkPolicy всё обычно открыто.
- Краткий правильный ответ для интервью
Можно ответить так:
- "По умолчанию, если NetworkPolicy не заданы, весь pod-to-pod трафик, включая между namespace, разрешён. Namespace не даёт сетевой изоляции. Ограничения появляются только при наличии политик и поддерживающего их CNI. В проде обычно вводят default-deny и явные allow-правила, чтобы уйти от этой 'allow all' модели."
Такой ответ показывает понимание дефолтного поведения Kubernetes и того, что безопасность — это всегда эксплицитный выбор через сетевые политики или внешние механизмы.
Вопрос 18. С какими дистрибутивами Linux вы работали на практике?
Таймкод: 00:24:48
Ответ собеседника: правильный. Уточняет, что основной опыт связан с Debian/Ubuntu; дистрибутивы семейства RHEL практически не использовал.
Правильный ответ:
Для этого вопроса не требуется "правильный" в смысле теории — важен честный и конкретный практический опыт. Однако сильный технический ответ должен показать:
- понимание различий между семействами дистрибутивов,
- влияние этих отличий на разработку, деплой, CI/CD и эксплуатацию сервисов (включая Go-сервисы),
- умение быстро адаптироваться к другим дистрибутивам.
Полезно упомянуть:
- Типовые дистрибутивы и их особенности
-
Debian/Ubuntu:
- Пакетный менеджер:
apt, формат.deb. - Часто используется в контейнерах и в облаке:
- базовые образы:
debian,ubuntu,ubuntu-minimal.
- базовые образы:
- Структура и пути:
- конфиги обычно в
/etc, - логи — в
/var/log.
- конфиги обычно в
- Хороший выбор для Go-сервисов:
- часто используют минимальные базовые образы (
ubuntu,debian:stable-slim) или вообще переходят наscratch/distrolessпосле сборки бинаря.
- часто используют минимальные базовые образы (
- Пакетный менеджер:
-
RHEL/CentOS/Rocky/AlmaLinux:
- Пакетный менеджер:
yum/dnf, формат.rpm. - Часто встречаются в корпоративных средах.
- Отличия:
- другие названия/версии пакетов,
- другая политика обновлений и поддержки,
- SELinux по умолчанию:
- важно понимать контексты, разрешения, логи (
/var/log/audit/audit.log), - при деплое Go-сервисов и nginx/postgres часто приходится разруливать SELinux.
- важно понимать контексты, разрешения, логи (
- Пакетный менеджер:
-
Alpine Linux:
- Пакетный менеджер:
apk. - Очень лёгкий, часто используется в Docker-образах.
- Особенности:
muslвместоglibc, что может ломать некоторые бинарники/библиотеки.- Для Go обычно всё ок при статической линковке, но важно это понимать:
- при сборке:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app.
- при сборке:
Пример Dockerfile для Go-сервиса (multi-stage, Debian + distroless):
FROM golang:1.22-bookworm AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o service ./cmd/service
FROM gcr.io/distroless/base-debian12
COPY --from=builder /app/service /service
USER nonroot:nonroot
ENTRYPOINT ["/service"] - Пакетный менеджер:
- На что обратить внимание в ответе на интервью
Хороший ответ не просто перечисляет дистрибутивы, а показывает:
-
Умение:
-
работать с пакетными менеджерами (
apt,yum/dnf,apk), -
настраивать systemd-юниты для сервисов (Go/API/worker):
[Unit]
Description=My Go Service
After=network.target
[Service]
ExecStart=/usr/local/bin/my-service
User=app
Group=app
Restart=on-failure
Environment=ENV=prod
[Install]
WantedBy=multi-user.target -
разбираться в логах, ротации (
logrotate), -
настраивать базовые вещи: hostname, time sync, ssh, firewall (ufw, firewalld, iptables/nftables).
-
-
Понимание различий:
- Debian/Ubuntu vs RHEL-based:
- разные команды установки:
apt installvsyum install/dnf install; - разные пути и имена модулей, пакетов (например,
build-essentialvs@developmentgroup); - SELinux vs AppArmor/нет мандатного контроля.
- разные команды установки:
- Для Kubernetes/контейнеров:
- в проде чаще важна предсказуемость образов, чем конкретный "фулл" дистрибутив.
- Debian/Ubuntu vs RHEL-based:
- Как звучит сильный краткий ответ
Например:
- "Практически работал в основном с Debian/Ubuntu: настройка серверов, systemd-сервисов, деплой Go-приложений, базовая безопасность, интеграция в CI/CD. В контейнерах часто использовал Debian-slim или distroless. С RHEL-семейством сталкивался реже, но знаком с
yum/dnf, структурой, особенностями SELinux и могу быстро адаптироваться. Alpine использовал как базу для минимальных Docker-образов, учитывая нюансы с musl."
Такой ответ демонстрирует и честность, и готовность уверенно работать в различных Linux-средах.
Вопрос 19. Где и как можно настраивать параметры ядра Linux, например полностью отключить IPv6?
Таймкод: 00:25:11
Ответ собеседника: неправильный. Упоминает директорию /etc и некие сетевые файлы, но не называет ключевые механизмы (sysctl, /proc/sys, модульные параметры, boot-параметры ядра), фактически демонстрируя отсутствие практики.
Правильный ответ:
Параметры ядра Linux настраиваются через несколько основных механизмов:
- интерфейс
/proc/sys(виртуическая файловая система), - конфигурация
sysctl(persistent-настройки), - параметры модулей ядра (
modprobe.d), - параметры загрузки ядра (grub, cmdline),
- дистрибутив-специфичные сетевые конфиги (NetworkManager, netplan и т.п. — для сетевых опций, но не для всех kernel tunables).
Для примера возьмём задачу: "полностью отключить IPv6".
- Базовая модель:
/proc/sysиsysctl
Любой tunable из sysctl соответствует файлу в /proc/sys.
Пример (runtime-установка):
# Отключить IPv6 глобально (до перезагрузки)
echo 1 > /proc/sys/net/ipv6/conf/all/disable_ipv6
echo 1 > /proc/sys/net/ipv6/conf/default/disable_ipv6
Эквивалент через sysctl:
sysctl -w net.ipv6.conf.all.disable_ipv6=1
sysctl -w net.ipv6.conf.default.disable_ipv6=1
Но так настройки пропадут после перезагрузки. Для постоянных изменений используют файлы конфигурации sysctl.
- Постоянные настройки через
sysctl.d
Современный и корректный способ:
- не править напрямую
/etc/sysctl.conf(хотя можно), - добавлять отдельные файлы в
/etc/sysctl.d/*.conf.
Например, создаём файл:
/etc/sysctl.d/99-disable-ipv6.conf:
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
Применение без перезагрузки:
sysctl --system
# или
sysctl -p /etc/sysctl.d/99-disable-ipv6.conf
Важно:
allиdefaultвместе:defaultвлияет на интерфейсы, создаваемые после изменения,allпытается применить к существующим.
- На некоторых системах одного
sysctlнедостаточно для "жёсткого" отключения IPv6 — нужен уровень загрузчика.
- Полное отключение IPv6 через параметры ядра (жёсткий вариант)
Если нужно гарантированно вырубить IPv6 на уровне ядра:
- добавляют параметр ядра в загрузчик (
grub/grub2):
Например, для систем с GRUB:
- редактируем
/etc/default/grub:
GRUB_CMDLINE_LINUX="ipv6.disable=1"
- затем:
update-grub # Debian/Ubuntu
grub2-mkconfig -o /boot/grub2/grub.cfg # RHEL/CentOS/Rocky/AlmaLinux (может отличаться)
После перезагрузки IPv6 будет отключён на уровне ядра.
Разница:
sysctl disable_ipv6— логическое отключение стеков на интерфейсах, но ядро всё ещё собрано с IPv6;ipv6.disable=1— IPv6-стек вообще не поднимается.
- Параметры модулей ядра
Некоторые настройки делаются через modprobe.d, особенно если функциональность реализована как модуль.
Например, если IPv6 как модуль (исторически):
echo "options ipv6 disable=1" > /etc/modprobe.d/ipv6.conf
Но актуальный, более универсальный способ — ipv6.disable=1 через cmdline ядра, как выше.
- Общий подход к kernel tuning
Ключевые практики:
-
Смотреть текущие значения:
sysctl -a | grep ipv6
# или
cat /proc/sys/net/ipv6/conf/all/disable_ipv6 -
Для любых параметров ядра:
- найти соответствующий ключ:
- документация:
/usr/share/doc/*/sysctl.txtили/usr/src/linux/Documentation/sysctl, sysctl -a | grep <pattern>.
- документация:
- временно применить через
sysctl -w, - сделать постоянным через файл в
/etc/sysctl.d/.
- найти соответствующий ключ:
Типичные важные параметры (которые полезно знать, особенно под высоконагруженные Go-сервисы):
- сетевые:
net.core.somaxconnnet.ipv4.ip_local_port_rangenet.ipv4.tcp_tw_reuse(осторожно, зависит от ядра)net.ipv4.tcp_fin_timeoutnet.core.rmem_max,net.core.wmem_max
- файловые дескрипторы:
fs.file-max+ ulimit для процессов.
Пример sysctl для сетевого сервиса:
# /etc/sysctl.d/60-tuning.conf
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 16384
net.ipv4.ip_local_port_range = 1024 65000
fs.file-max = 1048576
- Как звучит зрелый ответ на интервью (кратко):
- "Параметры ядра настраиваются через sysctl (runtime и persistent), то есть
/proc/sysи файлы/etc/sysctl.confили/etc/sysctl.d/*.conf. Для полного отключения IPv6 можно задатьnet.ipv6.conf.all.disable_ipv6=1иnet.ipv6.conf.default.disable_ipv6=1в sysctl, а для жёсткого варианта — добавитьipv6.disable=1в cmdline ядра через GRUB. Также часть настроек может задаваться через параметры модулей в/etc/modprobe.d. Всегда различаю временные изменения и те, что переживают перезагрузку."
Такой ответ показывает:
- знание реальных механизмов,
- понимание различий между уровнями (runtime/sysctl/grub/modprobe),
- практический опыт администрирования Linux в контексте прод-окружения.
Вопрос 20. Как можно восстановить или изменить пароль root, если это единственная учётная запись и доступ утерян?
Таймкод: 00:26:15
Ответ собеседника: неправильный. Говорит в общих чертах про системные файлы с пользователями, но не описывает реальные процедуры восстановления через режимы загрузки или chroot, признаёт, что не помнит и ищет в интернете.
Правильный ответ:
Восстановление/смена пароля root при утере доступа — типовая административная задача. Общая идея:
- получить локальный физический или консольный доступ к машине;
- загрузиться в окружение, где можно редактировать систему без текущего пароля;
- изменить пароль стандартными средствами (
passwd), либо отредактировать файлы/etc/shadow//etc/passwdпри необходимости.
Важно: это допустимо только при легитимном доступе к серверу. Если можно сделать это физически — система по определению вам принадлежит (или плохо защищена).
Ниже — канонические подходы (Debian/Ubuntu/RHEL-подобные).
- Через режим single-user/emergency (classic способ)
Часто:
-
В GRUB выбираем нужное ядро,
-
Нажимаем
eдля редактирования, -
В строке, начинающейся с
linuxилиlinux16, добавляем в конец:Вариант 1 (single user):
systemd.unit=rescue.targetили
singleВариант 2 (полный root shell):
rw init=/bin/bash -
Жмём
Ctrl+XилиF10для загрузки.
Дальше:
Если загружаемся с init=/bin/bash, корень часто уже в rw (или его нужно перемонтировать):
mount -o remount,rw /
passwd
# вводим новый пароль root
sync
exec /sbin/reboot -f
Если rescue/однопользовательский режим:
- Обычно даёт shell от root;
- Выполняем:
passwd
sync
reboot
Нюансы:
- На некоторых системах включена защита: для перехода в single-user может требоваться пароль root (RHEL, hardened конфиги).
- На зашифрованных дисках потребуется пароль для расшифровки LUKS, иначе доступ к файловой системе невозможен.
- Через LiveCD/ISO + chroot (универсальный способ)
Подходит, когда:
- нет возможности легко править grub/режим,
- система не даёт root shell без пароля.
Шаги:
-
Загрузиться с rescue/live-образа (например, Ubuntu Live, SystemRescueCD).
-
Определить root-раздел основной системы:
lsblk
fdisk -l
Допустим, это /dev/sda2.
- Смонтировать и подготовить окружение:
mount /dev/sda2 /mnt
# если есть отдельные /boot, /boot/efi, /var и т.п., тоже монтируем:
# mount /dev/sda1 /mnt/boot
# mount /dev/sdaX /mnt/boot/efi
# подготовить chroot
mount --bind /dev /mnt/dev
mount --bind /proc /mnt/proc
mount --bind /sys /mnt/sys
chroot /mnt /bin/bash
- Внутри
chroot:
passwd
# или явно:
# passwd root
sync
exit
- Отмонтировать и перезагрузить:
umount /mnt/dev /mnt/proc /mnt/sys || true
umount /mnt
reboot
- Ручное редактирование
/etc/shadow(экстренный вариант)
Если по какой-то причине passwd недоступен, можно:
- В режиме
chrootили с root shell отредактировать/etc/shadow:- Удалить хэш пароля для root (между первым и вторым двоеточием), оставив пустое поле — тогда root войдёт без пароля (временная мера).
- Затем зайти как root и сразу задать новый пароль
passwd.
Пример до:
root:$6$hash...:19328:0:99999:7:::
Пример временно (без пароля):
root::19328:0:99999:7:::
После входа:
passwd
И хэш будет восстановлен корректно.
- Особенности безопасности и защиты
С точки зрения зрелого инженера важно отметить:
- Если злоумышленник имеет:
- физический доступ к серверу,
- доступ к IPMI/ILO/DRAC/виртуальной консоли и к управлению загрузкой, он почти всегда может:
- загрузиться с LiveCD,
- изменить grub-опции,
- получить root-доступ.
- От этого защищаются:
- паролем на GRUB,
- отключением загрузки с внешних носителей,
- шифрованием диска (LUKS) — без ключа/пароля даже LiveCD не поможет.
- В прод-средах с Kubernetes/VM/облаками:
- такие операции делаются через cloud-init, reset-паролей, замену ключей SSH, но базовый принцип тот же: имея контроль над загрузкой и дисками, можно восстановить root.
- Краткий ответ для интервью
Компактно, как правильно отвечать:
- "Если потерян пароль root и это единственная учётка, стандартный путь — получить локальный доступ и зайти в систему в однопользовательском или emergency-режиме через GRUB, смонтировать root в rw и выполнить
passwdдля root. Либо загрузиться с LiveCD, смонтировать разделы, сделать chroot в установленную систему и там сменить пароль черезpasswd. В крайних случаях можно временно отредактировать/etc/shadow. Важно понимать, что наличие физического или out-of-band доступа почти всегда даёт возможность восстановления, поэтому для реальной защиты используют шифрование и пароль на GRUB."
Такой ответ показывает и знание практических техник, и понимание модели безопасности Linux.
Вопрос 21. С какими файловыми системами Linux вы знакомы и что с ними делали?
Таймкод: 00:27:55
Ответ собеседника: неправильный. После подсказок фактически подтверждает отсутствие осознанного опыта работы с распространёнными ФС (ext4, XFS, Btrfs, ZFS и др.).
Правильный ответ:
Осмысленный ответ на этот вопрос должен показать понимание:
- основных файловых систем, реально используемых в Linux в продакшене,
- их особенностей,
- типовых операций: разметка, форматирование, монтирование, проверка, рост/уменьшение, снапшоты, RAID/LVM-интеграция,
- влияния выбора ФС на поведение баз данных и сервисов (включая высоконагруженные Go-сервисы).
Краткий обзор ключевых файловых систем и практических сценариев.
- Семейство ext (ext3/ext4)
ext4 — классический дефолт во многих дистрибутивах.
Основные особенности:
- журналирование (metadata + data/journal режимы),
- стабильность, предсказуемость, отличная поддержка инструментов (
fsck.ext4), - поддержка больших файлов и разделов, extents, delayed allocation,
- хорошо подходит для:
- корневых файловых систем,
- типичных серверных нагрузок,
- баз данных (PostgreSQL/MySQL) при правильной настройке mount-опций.
Типичные операции:
- Форматирование:
mkfs.ext4 /dev/sdb1
- Монтирование:
mount /dev/sdb1 /data
echo '/dev/sdb1 /data ext4 defaults,noatime 0 2' >> /etc/fstab
- Проверка:
fsck.ext4 /dev/sdb1
Практический комментарий:
- Для интенсивных I/O-сервисов (включая Go-сервисы с высокой конкуренцией записи в логи/кеши) часто используют опции:
noatime— уменьшает лишние записи,- корректная настройка
barrier,data=orderedпо умолчанию достаточно.
- XFS
Частый выбор в enterprise и по умолчанию в RHEL/CentOS/Rocky/AlmaLinux.
Особенности:
- высокопроизводительная журналируемая ФС,
- хорошо масштабируется по размеру и параллелизму,
- отличная работа с большими файлами и большими файловыми системами,
- активно используется под базы данных, большие хранилища, логи.
Типичные операции:
mkfs.xfs /dev/sdb1
mount /dev/sdb1 /data
echo '/dev/sdb1 /data xfs defaults,noatime 0 0' >> /etc/fstab
Особенности:
- XFS не поддерживает "уменьшение" файловой системы, только рост.
- Имеет свои инструменты:
xfs_repair,xfs_info,xfs_growfs.
Практический комментарий:
- Для прод-PostgreSQL или больших Go-сервисов (лог-хранилища, blob-хранилища) XFS — очень частый выбор.
- Важно корректно настраивать RAID/LVM под ним.
- Btrfs
Современная copy-on-write ФС с богатым функционалом:
- встроенные:
- снапшоты,
- субволюмы,
- онлайн-сжатие,
- RAID (разных уровней),
- check-summing данных и метаданных.
- Удобна для:
- development-сред,
- контейнерных хостов,
- сценариев, где нужны быстрые снапшоты, rollback, тестовые окружения.
Пример:
mkfs.btrfs /dev/sdb1
mount /dev/sdb1 /btrfs
# создание subvolume
btrfs subvolume create /btrfs/data
# снапшот
btrfs subvolume snapshot /btrfs/data /btrfs/data-snap-2025-01-01
Практический комментарий:
- Хороша для гибких окружений, но требует понимания, особенно:
- COW-особенностей для баз данных (часто включают
nodatacowдля db-директории), - мониторинга места (из-за снапшотов можно "потерять" понимание реального использования).
- COW-особенностей для баз данных (часто включают
- ZFS
Мощная файловая система и менеджер томов:
- copy-on-write,
- пулы (zpool), dataset'ы,
- встроенный RAID, снапшоты, репликации,
- checksums, самовосстановление (self-healing),
- квоты, сжатие, dedup.
Типичный сценарий:
- хранилища, бэкапы, базы данных, где важна целостность.
Пример (упрощённо):
zpool create tank /dev/sdb
zfs create tank/data
mountpoint: /tank/data
Практический комментарий:
- Очень силён, но:
- не входит по лицензии в стандартное ядро Linux, требует отдельной установки,
- требует памяти (ARC), нужно понимать тюнинг.
- Что важно уметь на практике
Хороший ответ должен демонстрировать не только "знаю названия", но и умение:
- Разметить диск:
fdisk /dev/sdb
# или parted / lsblk
- Создать ФС и примонтировать её:
mkfs.ext4 /dev/sdb1
mkdir -p /data
mount /dev/sdb1 /data
- Настроить автоматическое монтирование в
/etc/fstab:
UUID=<uuid> /data ext4 defaults,noatime 0 2
-
Проверить и починить ФС:
fsck.ext4,xfs_repair,btrfs check.
-
Понимать влияние mount-опций на производительность и надёжность:
noatime,nodiratime,async,data=ordered,nobarrier,compress=zstdи т.д.- в контексте: API на Go, база данных, лог-файлы, временные каталоги.
- Как звучит хороший краткий ответ на интервью
Например:
- "Работал с ext4 и XFS как с основными ФС под Linux-сервера. Для новых серверов обычно: разметка через parted/fdisk, LVM поверх дисков, затем ext4 или XFS, настройка
/etc/fstab,noatime, проверка fsck/xfs_repair при проблемах. Btrfs и ZFS рассматриваю как более функциональные решения: снапшоты, встроенный RAID, COW — особенно актуально для бэкапов и storage-узлов, но требуют аккуратного тюнинга (COW/сжатие/ресурсы). Выбор ФС всегда делаю исходя из нагрузки: для классических сервисов и баз чаще ext4/XFS, для storage и сложных сценариев — ZFS или Btrfs."
Такой ответ показывает:
- знание реальных ФС,
- практические навыки,
- понимание связи между ФС и эксплуатацией сервисов.
Вопрос 22. Приходилось ли вам восстанавливать повреждённые файловые системы после сбоев?
Таймкод: 00:28:51
Ответ собеседника: правильный. Честно говорит, что с такими аварийными случаями и восстановлением не сталкивался.
Правильный ответ:
Так как это уточняющий вопрос (предыдущий был про знание ФС), здесь достаточно сформулировать, как это делается на практике и что важно знать. Ожидается не «да/нет», а понимание подхода.
Ключевые моменты:
- различать тип файловой системы (ext4, XFS, Btrfs, ZFS и др.),
- никогда не гонять ремонт по примонтированной в rw ФС,
- сначала сохранять данные (образы, dd, снапшоты), потом чинить.
- Общий подход к восстановлению
Типичный порядок действий:
- зафиксировать симптомы:
- ошибки ввода-вывода (I/O error),
- "read-only filesystem",
- сообщения в
dmesg/journalctl(I/O, corruption, XFS/Btrfs/ZFS errors), - падения сервисов/БД.
- проверить железо:
smartctlдля дисков,- логи RAID-контроллера,
- состояние массива (mdadm/hw-raid).
- минимизировать запись:
-
перемонтировать ФС в
ro:mount -o remount,ro /mountpoint -
либо выключить сервер и работать из rescue/live-окружения.
-
- сделать копию:
- по возможности
dd/ddrescueпроблемного диска или раздела, - на копии экспериментировать с восстановлением.
- по возможности
- ext3/ext4: fsck
Для ext3/ext4 используется fsck.ext4 (или e2fsck).
Типичный сценарий (в rescue-режиме):
umount /dev/sda1
fsck.ext4 -f -y /dev/sda1
Комментарии:
-f— принудительно проверить, даже если ФС "чистая";-y— автоматически соглашаться с предложенными исправлениями (в бою часто использовать с осторожностью; лучше начать без-y, посмотреть масштаб).- После исправлений:
- примонтировать и проверить логи, целостность данных, критичные директории (БД, конфиги).
- XFS: xfs_repair
XFS не чинится fsck; используется xfs_repair.
Шаги:
- Всегда работать на размонтированном разделе:
umount /dev/sdb1
xfs_repair /dev/sdb1
- Если раздел — root, делается через live/rescue.
- Иногда предварительно:
xfs_repair -n /dev/sdb1
(анализ без внесения изменений).
Особенности:
- XFS очень не любит падения по питанию + плохие контроллеры/RAID без write-back с батареей.
- Если повреждён журнал —
xfs_repairего реконструирует, часть последних операций может потеряться.
- Btrfs: btrfs check / scrub / restore
Btrfs имеет свои механизмы:
btrfs scrub— онлайн-проверка и восстановление с использованием редундантности (RAID1/10 и т.д.);btrfs check— оффлайн-проверка (опасен при неквалифицированном использовании, официально рекомендуют осторожность);btrfs restore— попытка вытащить файлы без монтирования.
Пример (оффлайн):
umount /dev/sdb1
btrfs check /dev/sdb1
# при критических проблемах:
btrfs restore /dev/sdb1 /mnt/recovery
Практический фокус:
- сначала стараться считать данные (restore),
- только потом — потенциально разрушающие операции.
- ZFS: scrub, import, rollback
ZFS изначально ориентирован на целостность:
zpool scrub— фоновая проверка и исправление по checksum'ам;- при сбоях:
zpool status— анализ ошибок;- замена битых дисков,
zpool scrubдля восстановления;- при тяжёлой порче — использование снапшотов:
zfs rollback pool/dataset@healthy_snapshot
Если пул не монтируется:
zpool import -f pool
При серьёзных проблемах могут понадобиться опции -F, -X, но их использование требует понимания, так как можно потерять новые транзакции.
- Что ожидается на интервью (краткий зрелый ответ)
Кратко, как стоило бы ответить:
- "Да, базовый подход знаю. Для ext4 работаю через
fsck.ext4по размонтированному разделу, обычно из rescue-окружения, предварительно проверяю состояние дисков (SMART, RAID). Для XFS используюxfs_repair(никогда не по примонтированной ФС). Для Btrfs/ZFS — вначале смотрю статус пула/тома, использую scrub, снапшоты и инструменты типаbtrfs restoreилиzfs rollback, при необходимости — импорт с флагами. Важно сначала минимизировать запись, по возможности сделать образ, и только потом чинить. При серьёзной порче приоритет — вытащить данные, а не "во что бы то ни стало оживить ФС"."
Такой ответ показывает:
- понимание различий между ФС,
- осознанное отношение к рискам,
- практический, а не учебниковый подход к аварийному восстановлению.
Вопрос 23. Как посмотреть текущее потребление ресурсов (CPU, RAM) в Linux?
Таймкод: 00:29:24
Ответ собеседника: правильный. Упоминает команду top для мониторинга ресурсов.
Правильный ответ:
Для оценки текущего потребления CPU, RAM и общей загрузки системы в Linux важно знать не только top, но и уметь пользоваться рядом специализированных инструментов, а также понимать, какие метрики смотреть и как их интерпретировать в контексте работы серверных приложений (например, Go-сервисов и баз данных).
Основные инструменты:
- top
Базовый инструмент реального времени.
Ключевые моменты:
- Вверху:
- load average (нагрузка),
- общее использование CPU (user/system/idle/iowait),
- суммарная память и swap.
- В списке процессов:
%CPU— процент использования CPU процессом,%MEM— доля потребляемой RAM,RES— реальное (resident) использование памяти,COMMAND— имя процесса.
Полезно уметь:
- сортировать по CPU:
Shift + P, - по памяти:
Shift + M, - фильтровать по имени (
o,Oв некоторых реализациях).
- htop
Улучшенная версия top (аналогично top, но более удобна).
Особенности:
- цветной вывод, удобная навигация,
- дерево процессов,
- быстрое убийство/приостановка процесса,
- интерактивный выбор сортировки.
Команда:
htop
- ps + сортировки
Для скриптов, one-shot-замеров и логирования состояния.
Примеры:
- Топ-10 процессов по CPU:
ps aux --sort=-%cpu | head -n 11
- Топ-10 по памяти:
ps aux --sort=-%mem | head -n 11
- free
Для быстрой оценки использования RAM и swap.
free -h
Ключевое — смотреть:
Mem: usedиavailable(а не толькоfree),Swap: used— признак нехватки памяти.
- vmstat, iostat, mpstat
Для более глубокой диагностики (особенно под нагрузкой):
vmstat 1— сводка по:- процессам (r — runnable),
- памяти,
- swap,
- cpu (us/sys/wa/id),
iostat -x 1— использование дисков (важно при I/O-bound),mpstat -P ALL 1— загрузка по каждому CPU.
- pidstat
Точечный мониторинг конкретных процессов:
pidstat -p <PID> 1
Показывает динамику CPU/IO/мемори по процессу — удобно для анализа Go-сервиса или базы.
- /proc напрямую
Для автоматизации и инструментов:
/proc/meminfo— детальная инфа по памяти,/proc/stat— CPU-статистика,/proc/<pid>/status,/proc/<pid>/smaps— память процесса.
Пример чтения нагрузки CPU из Go-кода:
data, _ := os.ReadFile("/proc/loadavg")
fmt.Println(string(data)) // load average и др.
- Как звучит хороший ответ на интервью
Краткий, но содержательный вариант:
- "Обычно начинаю с
topилиhtopдля общей картины: загрузка CPU, использование памяти, кто в топе по ресурсам. Для одноразовых срезов используюps aux --sort=-%cpuили--sort=-%mem. Память быстро смотрю черезfree -h. Для более детальной диагностики нагруженных систем —vmstat,iostat,mpstat,pidstat. При необходимости данные читаю из/procдля автоматического мониторинга или интеграции с нашим сервисом."
Такой ответ показывает:
- знание основных CLI-инструментов,
- понимание, когда какой использовать,
- ориентацию на практику и наблюдаемость продакшн-систем.
Вопрос 24. Что показывает load average и как интерпретировать ситуацию с очень большим значением при невысокой загрузке CPU?
Таймкод: 00:29:42
Ответ собеседника: неполный. Правильно указывает, что это средняя нагрузка за интервалы времени, но не раскрывает, что именно измеряется, как это связано с количеством CPU, очередью задач и I/O, и как интерпретировать высокие значения при низком %CPU.
Правильный ответ:
Load average — это не «процент загрузки CPU». Это усреднённое число процессов, которые:
- либо реально выполняются на CPU (running),
- либо готовы к выполнению и стоят в очереди на CPU (runnable),
- либо находятся в непрерывном ожидании ресурсов ядра/устройства (в "uninterruptible sleep", обычно I/O: диск, сеть, NFS и т.п.).
Обычно в Linux мы видим три значения:
- среднее за 1 минуту,
- за 5 минут,
- за 15 минут.
Например:
$ uptime
16:20:01 up 10 days, 3:12, 2 users, load average: 2.35, 1.80, 1.20
Эти значения показывают, какова была средняя длина очереди runnable/blocked процессов за соответствующие интервалы.
Ключ к интерпретации — сравнивать load average с количеством CPU (ядер/аппаратных потоков):
- Если у вас 1 CPU:
- load ~1 — система примерно сбалансирована,
- load ~2 — в среднем один процесс работает, один ждёт,
- load >>1 — ощущается очередь, возможны задержки.
- Если у вас 4 CPU:
- load ~4 — все ядра заняты, но без очереди,
- load ~8 — в среднем 4 процесса работают, 4 ждут; это уже перегрузка.
Поэтому корректный вопрос всегда: "load average относительно скольких CPU?"
Теперь важная часть: почему load average может быть очень большим при невысокой загрузке CPU?
Типичная ситуация:
load averageвысокое (например, 20+),- при этом
%CPU idleвысокий, CPU почти не занято.
Это означает:
- много процессов находятся не в активном вычислении, а в ожидании:
- медленного диска,
- NFS,
- сетевого I/O,
- блокировок ядра/FS,
- иногда — проблем с storage/RAID/SAN.
Причина: процессы в состоянии "D" (uninterruptible sleep) тоже учитываются в load average. Это создаёт "большой load" без высокой CPU-нагрузки.
Как это диагностировать на практике:
- Посмотреть load:
uptime
# или
cat /proc/loadavg
- Открыть top/htop и посмотреть:
- много ли процессов в
D-state:
top
# столбец S: D = uninterruptible sleep
- Проверить I/O:
iostat -x 1— высокая загрузка дисков (util %, await), медленные ответы;vmstat 1— большие значения вb(процессы, ожидающие I/O);dmesg,journalctl— ошибки дисков/NFS/RAID.
Интерпретация примеров:
- 8 vCPU, load average: 7.5, 7.0, 6.8, CPU загружен 80–90%:
- это нормально, система близка к полной загрузке, но без жёсткого оверкоммита.
- 8 vCPU, load average: 40, CPU idle 70%:
- это ненормально, много процессов ждут I/O или блокировок;
- симптом проблем с диском, файловой системой, NFS, базой данных или глобальными блокировками.
Краткий ответ, который хорошо звучит на интервью:
- "Load average — это среднее количество процессов, которые либо выполняются, либо ждут выполнения/ресурсов (CPU или непрерываемый I/O), за 1, 5 и 15 минут. Интерпретировать его нужно относительно числа CPU: если load сильно больше числа ядер, есть очередь. Высокий load при низком
%CPUобычно говорит не о 'прошлой нагрузке', а о том, что много процессов зависли в I/O (D-state), ждут диска, NFS, блокировок и т.п. Для диагностики смотрюtop/htop(состояние процессов),iostat,vmstat, логи и сравниваю load с количеством доступных CPU."
Такой ответ показывает понимание:
- что именно измеряет load average,
- связь с архитектурой системы,
- умение отличать CPU-bound от I/O-bound проблем.
Вопрос 25. Что обозначает поле Shared (shmem) в выводе free?
Таймкод: 00:31:03
Ответ собеседника: неправильный. Говорит о «расшаренной» памяти между процессами, но без точного объяснения и без связи с тем, как ядро учитывает сегменты разделяемой памяти.
Правильный ответ:
Поле Shared (или shmem в новых версиях free) показывает объем памяти, используемой разделяемыми (shared) сегментами, которые учитываются как:
- анонимная разделяемая память (POSIX / System V shared memory),
- память, используемая tmpfs (включая
/dev/shm), - часть памяти, которая реально шарится между процессами, а не принадлежит только одному.
Важно понимать несколько моментов.
Основные тезисы:
-
Это не "просто память, используемая несколькими процессами" на уровне логики «если у нас два одинаковых процесса, код шарится — значит это shared». Поле
Shared/shmemвfreeотражает в основном:- объем памяти в
tmpfs, - объем System V shared memory (
shmget/shmat), - часть POSIX shared memory и других механизмов, которые учитываются как shmem в ядре.
- объем памяти в
-
Это не "лишняя" память и не отдельный плюс к used:
shmemвходит в общееused(и часто вbuffers/cache),- это просто разрез (breakdown) уже занятой памяти по типам.
-
В современных Linux:
Команда:
free -m
может показывать, например:
total used free shared buff/cache available
Mem: 16024 10240 1024 2048 4759 3200
Swap: 2047 128 1919
Где:
shared(илиshmem) — примерно соответствует полюShmemиз/proc/meminfo.
Можно проверить:
grep -E 'Shmem|SwapCached|Mapped' /proc/meminfo
- Типичные источники высокой shared/shmem:
- активно используемый
/dev/shm(межпроцессное взаимодействие, временные буферы), - tmpfs (например, для k8s ConfigMap/Secret, логов, overlayfs нижних слоёв),
- SHM-сегменты для баз данных (PostgreSQL, Oracle, некоторые кеши),
- различные IPC-механизмы, использующие shared memory.
- Как это интерпретировать на практике:
- Небольшое значение
shared— нормально и обычно неинтересно. - Большое значение
shared:- смотрим, кто использует:
df -h | grep tmpfsls -lh /dev/shm- анализ служб (PostgreSQL, Redis с hugepages, приложения, юзающие shm).
- смотрим, кто использует:
- Не нужно пугаться, что
shared«отъедает память навсегда»:- это такая же RAM, как и остальная,
- освобождается, когда процессы освобождают соответствующие сегменты/shared-ресурсы.
- Чем это НЕ является:
- Это не просто "общее между всеми процессами code segment". Код бинарей и разделяемых библиотек действительно мапится нескольким процессам, но учитывается по-другому и не напрямую как
shmemвfree. - Это не индикатор утечки памяти сам по себе. Но резкий рост shmem без очевидной причины может говорить о:
- некорректной работе с shared memory (сегменты не освобождаются),
- особенностях конфигурации контейнеров/оркестраторов,
- утечках в компонентах, использующих tmpfs или /dev/shm.
Краткий хороший ответ на интервью:
- "Поле
shared(илиshmem) вfreeпоказывает объем разделяемой памяти: в первую очередь сегменты shared memory и tmpfs (/dev/shmи подобное), то есть ту часть памяти, которая потенциально используется несколькими процессами. Это не отдельная 'расходуемая' категория, она входит вused. При больших значениях имеет смысл проверить/dev/shm, tmpfs и приложения, использующие shared memory (БД, кеши и т.д.)."
Такое объяснение демонстрирует понимание того, как ядро классифицирует память, а не поверхностное "это что-то общее между процессами".
Вопрос 26. Как интерпретировать вывод free, где почти вся память используется, но система работает нормально?
Таймкод: 00:32:01
Ответ собеседника: неправильный. Не объясняет роль кэшей, page cache и buff/cache, не раскрывает модель использования памяти Linux.
Правильный ответ:
Ключевой момент: в Linux "почти вся память занята" — это чаще норма, а не проблема. Ядро специально старается использовать свободную RAM максимально эффективно под кэш, чтобы ускорять работу системы.
Важно понять различие:
- "Память занята навсегда процессами" vs "память временно используется под кеш и может быть освобождена при необходимости".
Рассмотрим типичный вывод:
free -h
Например:
total used free shared buff/cache available
Mem: 16Gi 14Gi 200Mi 500Mi 1.8Gi 3.0Gi
Swap: 2Gi 100Mi 1.9Gi
На что реально смотреть:
- Поле free — почти бесполезно само по себе
freeпоказывает "ничем не занятую" память, к которой ядро пока не притронулось.- В хорошо работающей системе это значение часто маленькое, потому что:
- ядро использует свободную память под:
- page cache (кэш файловой системы),
- dentry/inode cache,
- различные буферы.
- ядро использует свободную память под:
Это хорошо: свободная RAM, которая не используется, — потерянный перформанс.
- Поле buff/cache — критически важное
buff/cache— память, используемая под:- буферы блочных устройств,
- кэш файлов (page cache),
- структуры файловой системы.
- Эта память:
- используется для ускорения работы: уменьшения обращений к диску;
- МОЖЕТ БЫТЬ ОСВОБОЖДЕНА ядром при необходимости для приложений.
- Поэтому большое значение buff/cache + маленький free — норма.
- Поле available — главное для быстрой оценки
Современные версии free показывают available:
- это оценка объёма памяти, который можно выделить под новые приложения БЕЗ активного свопинга;
- учитывает:
- реально free,
- часть кэшей, которую можно безопасно сбросить,
- некоторые особенности использования памяти.
- Если
available:- много (например, несколько ГБ) — система чувствует себя нормально;
- мало (сотни МБ и падает, при этом swap растет) — есть риск проблем.
- Когда "почти вся память занята" — нормально:
Ситуация:
usedпочти равноtotal;freeочень мало;buff/cacheбольшой;availableадекватный (есть запас);- swap либо не используется, либо чуть-чуть.
Интерпретация:
- ядро кэширует файлы и данные,
- при приходе новых аллокаций:
- будет сбрасывать часть кэша,
- отдавать память под процессы.
- Это оптимальное состояние: максимум RAM работает на ускорение приложения/ФС.
- Когда это уже проблема:
Признаки реальной нехватки памяти:
availableмаленький и продолжает падать;- активно используется swap (особенно если растет и
%wa/latency); - частые major page faults;
- OOM-killer в логах (
dmesg,journalctl -k | grep -i oom).
Тогда нужно:
- искать процессы с большим RSS (
ps aux --sort=-%mem | head); - профилировать приложение (в т.ч. Go heap/cpu профили);
- проверять утечки (Go: pprof, GODEBUG=gctrace).
- Практический короткий ответ для интервью:
- "В Linux мало 'free' памяти — это нормально. Ядро использует свободную RAM под page cache и buff/cache, чтобы ускорять доступ к файлам и снизить нагрузку на диск. Эти кэши считаются 'used', но они вытесняемы: при потребности приложений ядро освобождает их. Для реальной оценки состояния смотрят на столбец
available, использование swap и метрики нагрузки. Еслиavailableдостаточно и swap почти не трогается — то даже при 'почти вся память занята' система работает в штатном режиме."
Такое объяснение показывает понимание модели памяти Linux и умение отличать здоровую картину от реальной проблемы.
Вопрос 27. Для чего используется команда df -h и в чем отличие df от du?
Таймкод: 00:32:54
Ответ собеседника: неполный. Указывает, что df -h показывает использование файловых систем, но не знает du и не объясняет ключевые отличия уровней измерения.
Правильный ответ:
Команды df и du обе связаны с дисковым пространством, но отвечают на разные вопросы и работают на разных уровнях.
df— про файловые системы (какой объем доступен и занят на уровне устройства/раздела/FS).du— про файлы и директории (сколько места фактически занимают данные в конкретном поддереве).
Развернуто:
- Назначение df -h
Команда:
df -h
Показывает сводку по смонтированным файловым системам в "human-readable" формате (KB/MB/GB):
- размер файловой системы целиком,
- сколько занято,
- сколько свободно,
- процент использования,
- точка монтирования.
Типичный вывод:
df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 50G 30G 18G 63% /
tmpfs 8.0G 100M 7.9G 2% /run
/dev/sdb1 200G 150G 50G 75% /data
Это ответ на вопросы:
- "Сколько места осталось на разделе / или /data?"
- "Где у меня кончается дисковое пространство?"
- "Какие FS почти заполнены (Use% → алерты)?"
df читает метаданные файловой системы (суперблоки, битмапы блоков/инодов), а не обходит все файлы.
- Назначение du
Команда:
du -sh /var/log
показывает, сколько места на диске занимает конкретный каталог (и его содержимое) с точки зрения выделенных блоков.
Ключевые опции:
-s— summary, только итог по каталогу, без рекурсии по всем подпапкам в выводе;-h— human-readable;- частый паттерн поиска топ-пожирателей:
du -sh * | sort -h
внутри проблемного каталога.
Это ответ на вопросы:
- "Кто съел место на диске?"
- "Какой каталог/сервис генерирует больше всего данных?"
- Главное отличие df vs du
-
df:- измеряет свободное/занятое пространство на уровне файловой системы;
- не знает деталей по отдельным каталогам;
- быстрый, опирается на метаданные FS.
-
du:- измеряет суммарный размер файлов/каталогов;
- обходит дерево каталогов, может быть тяжелым на больших структурах;
- показывает "видимый" размер, зависящий от прав и монтирований.
Поэтому:
dfотвечает: "из 100G раздела / занято 95G".duпомогает ответить: "из них 70G в /var/log, 20G в /var/lib/postgresql".
- Почему df и du могут не совпадать
Это важный момент, который часто проверяют.
Типичные причины расхождений:
- Удаленные, но ещё открытые файлы:
- если лог-файл удалён, но процесс продолжает писать в уже открытый дескриптор,
duего не увидит, аdfпокажет занятые блоки. - диагностируется через:
lsof | grep deleted
- если лог-файл удалён, но процесс продолжает писать в уже открытый дескриптор,
- Hard links:
duможет считать размер файла один раз (зависит от опций), а фактически блоки учтены в FS иначе.
- Разные точки монтирования:
- если внутри директории примонтирован другой FS,
duпо умолчанию может не переходить в другую файловую систему (или наоборот, в зависимости от опций), аdfпоказывает их отдельно.
- если внутри директории примонтирован другой FS,
- Sparse-файлы:
duс разными ключами может показывать логический размер vs реально занятое на диске (--apparent-size).
- Практика для продакшена
-
Если алерт: "Disk 90% full" на
/data:- сначала:
чтобы понять, какой раздел в проблеме.
df -h - затем:
чтобы найти крупные каталоги; далее рекурсивно углубляемся в самые большие.
cd /data
du -sh * | sort -h
- сначала:
-
Если
dfпоказывает мало свободного, аduсуммарно меньше:- проверяем удалённые, но открытые файлы (
lsof), - overlayfs/containers bind-mount, отдельные FS.
- проверяем удалённые, но открытые файлы (
Краткий ответ, уместный на интервью:
- "
df -hпоказывает использование места на уровне файловых систем: общий размер, занято, доступно, процент.duпоказывает, сколько места занимают конкретные директории и файлы, обходя дерево.df— про состояние раздела,du— инструмент, чтобы найти, кто именно это место съел. Несовпадения между ними часто связаны с удалёнными, но открытыми файлами, sparse-файлами или особенностями монтирования."
Вопрос 28. Есть ли у вас опыт настройки iptables и как вы его использовали?
Таймкод: 00:33:21
Ответ собеседника: неправильный. Фактически опыта настройки нет, только чтение сгенерированных правил в Kubernetes.
Правильный ответ:
Для технического собеседования на разработчика важно не только "да/нет", а понимание, как iptables работает, как им управлять и как он вписывается в сетевой стек Linux и современные решения (Kubernetes, kube-proxy, CNI, сервис-меши).
Кратко о сути:
iptables— интерфейс управления подсистемой фильтрации пакетов в ядре Linux (netfilter).- Используется для:
- фильтрации трафика (firewall),
- NAT (SNAT, DNAT, Masquerade),
- маркировки пакетов (mangle),
- реализации политик доступа между сервисами/подсетями,
- интеграции с приложениями (load balancer, Kubernetes, докер-сетевые правила и т.п.).
- Базовая модель iptables
Понимание таблиц и цепочек — обязательный минимум.
Основные таблицы:
filter— фильтрация пакетов (по умолчанию).- Цепочки:
INPUT,FORWARD,OUTPUT.
- Цепочки:
nat— трансляция адресов.- Цепочки:
PREROUTING,POSTROUTING,OUTPUT.
- Цепочки:
mangle— изменение полей пакетов (TTL, TOS, маркировка).raw— до логики connection tracking (оптимизации, исключения).
Примеры ключевых цепочек:
INPUT— пакеты, адресованные самому хосту.OUTPUT— исходящие с хоста.FORWARD— транзит через хост (маршрутизатор, нода k8s, NAT-шлюз).
- Типичные сценарии использования
Примеры, которые хорошо показать на собеседовании.
- Запрет всего, кроме нужного SSH:
# Политика по умолчанию - дроп
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Разрешить loopback
iptables -A INPUT -i lo -j ACCEPT
# Разрешить установленные соединения
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Разрешить SSH
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
- Простейший NAT-шлюз (masquerade):
# Разрешаем форвардинг
echo 1 > /proc/sys/net/ipv4/ip_forward
# NAT для выходящего трафика через внешний интерфейс eth0
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
- Проброс порта (DNAT):
# Входящий трафик на 80 порт перенаправляем на внутренний хост 10.0.0.10:8080
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 10.0.0.10:8080
# Разрешаем форвардинг до внутреннего сервера
iptables -A FORWARD -p tcp -d 10.0.0.10 --dport 8080 -j ACCEPT
Такие примеры показывают понимание практических задач: firewall, NAT, роутинг трафика.
- Связь с Kubernetes и контейнерами
Даже если правила генерирует Kubernetes или Docker, полезно понимать, что там происходит:
- Docker:
- создает цепочки (
DOCKER,DOCKER-USER) и добавляет DNAT/SNAT для публикации портов.
- создает цепочки (
- Kubernetes (iptables mode в kube-proxy):
- использует цепочки (
KUBE-SERVICES,KUBE-NODEPORT,KUBE-POSTROUTINGи т.п.) для реализации:- ClusterIP,
- NodePort,
- балансировки по backend-подам.
- использует цепочки (
- Понимание iptables:
- помогает отлаживать проблемы с сетевой связностью,
- объяснить, почему сервис доступен/недоступен,
- дебажить конфликт правил от разных компонентов.
Команда для анализа:
iptables-save | less
iptables -L -n -v
iptables -t nat -L -n -v
- Практические подходы и хорошие практики
- Никогда не менять правила "вслепую":
- сначала
iptables-saveв файл как бэкап; - применять изменения через скрипт или конфиг-менеджмент.
- сначала
- Использовать conntrack:
-m conntrack --ctstate NEW,ESTABLISHED,RELATED— базовый паттерн для адекватного фаервола.
- Учитывать порядок правил:
- первый матч выигрывает, порядок в цепочке критичен.
- Быть в курсе перехода на
nftables:- многие современные дистрибутивы проксируют iptables к nftables, это важно при глубоком дебаге.
- Ответ, который звучит уверенно на интервью
- "Да, я работал с iptables как с инструментом для настройки фаервола и NAT. Использовал таблицу filter для ограничения входящих подключений (INPUT/FORWARD), таблицу nat для SNAT/MASQUERADE и DNAT/порт-форвардинга, работал с conntrack-состояниями. В контексте контейнеров и Kubernetes разбирался в сгенерированных правилах kube-proxy и Docker: как реализуется ClusterIP, NodePort, проброс портов и SNAT. При изменениях всегда делаю backup через iptables-save и аккуратно работаю с порядком правил. Если нужно, могу настроить базовый фаервол, NAT-шлюз или отдебажить сетевую проблему по цепочкам и счетчикам iptables."
Такой ответ демонстрирует не только знакомство с командой, но и понимание сетевой модели Linux, что критично для работы с продакшеном, k8s и сложными сервисами.
Вопрос 29. С какими системами CI/CD вы работали и какие технологии приходилось собирать?
Таймкод: 00:33:54
Ответ собеседника: правильный. Упоминает GitLab CI и сборку проектов на Java, Go, Python и C++.
Правильный ответ:
Хороший ответ на такой вопрос должен показать:
- понимание концепций CI/CD, а не только перечисление инструментов;
- конкретный опыт: какие системы, какие типы проектов, как устроен pipeline;
- умение адаптировать пайплайны под разные языки, окружения и архитектуру репозитория.
Ниже структурированный развернутый ответ.
- Ключевые системы CI/CD
Типичный продвинутый профиль:
-
GitLab CI:
- основная система, глубоко интегрированная с репозиторием;
- используется для build/test/lint, сборки артефактов, Docker-образов, деплоя;
- поддержка динамических окружений, ручных job'ов, pipelines for merge requests, rules/only/except, multi-stage pipelines.
-
GitHub Actions:
- удобно для open-source, мультиплатформенных билдов;
- matrix jobs (linux/windows/macos), интеграция с GitHub Packages, security checks.
-
Jenkins:
- кастомные сценарии, сложная оркестрация, интеграция с on-prem инфраструктурой;
- declarative pipelines (Jenkinsfile), shared libraries.
-
Дополнительно (по ситуации):
- TeamCity, Drone, Argo Workflows/Argo CD (GitOps), CircleCI и др.
Важно не просто назвать, а показать умение переносить те же практики между системами.
- Типы технологий и артефактов
Чего ожидают от сильного кандидата:
-
Go:
- сборка бинарников под разные платформы:
# .gitlab-ci.yml фрагмент
build_go:
image: golang:1.22
script:
- go mod download
- CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app ./cmd/app
artifacts:
paths:
- app - запуск unit-тестов (
go test ./...), race detector, покрытие; - линтеры (golangci-lint), генерация Swagger, protobuf, миграций.
- сборка бинарников под разные платформы:
-
Java:
- Maven/Gradle, сборка jar/war;
- кэширование зависимостей;
- прогон unit/integration тестов.
-
Python:
- venv/poetry/pipenv;
- pytest, mypy, flake8/ruff;
- сборка wheel, публикация в internal PyPI.
-
C/C++:
- сборка через CMake/Make;
- разные toolchain'ы, cross-compilation;
- статический анализ (clang-tidy, cppcheck), sanitizer'ы.
-
Docker и Kubernetes:
- сборка минимальных образов (multi-stage Dockerfile, distroless/ubi/alpine);
- пуш в приватный registry (GitLab Registry, ECR, GCR);
- деплой через:
- helm/helmfile,
- kustomize,
- kubectl apply,
- GitOps (Argo CD, Flux).
- Архитектура пайплайнов и практики
Продвинутый ответ должен показать понимание:
- Многостадийные pipeline:
- stages:
lint,test,build,security,package,deploy;
- stages:
- Оптимизации:
- кэширование зависимостей (Go build cache, Maven repo, pip cache);
- parallel jobs, matrix builds;
- Контроль качества:
- обязательные проверки для merge request (quality gates);
- stat analysis (SonarQube, linters);
- security сканирование:
- SAST/DAST,
- сканирование Docker-образов (Trivy, Grype),
- проверка зависимостей (Dependabot, Renovate, GitLab Dependency Scanning).
- Стратегии деплоя:
- blue-green, canary, rolling updates;
- feature flags;
- миграции БД как часть пайплайна:
- пример: перед rollout выполняем миграции (goose, migrate, Flyway), при фейле откатываем деплой.
- Краткий ответ для собеседования
Вариант, который звучит убедительно:
- "Основной опыт — с GitLab CI: настраивал многостадийные пайплайны для Go, Java, Python и C++ проектов. В пайплайнах — линтеры, unit/integration тесты, сборка бинарников и Docker-образов, публикация артефактов и деплой в Kubernetes через helm. Работал также с GitHub Actions и Jenkins, переносил практики: кэширование зависимостей, матричные билды, environment-specific конфигурации. Для продакшена использую многостадийные Dockerfile, security-сканирование образов и GitOps/helm подходы для деплоя."
Такой ответ демонстрирует не только знакомство с инструментами, но и зрелое понимание CI/CD как части инженерной культуры и жизненного цикла сервиса.
Вопрос 30. Какую роль играет файл pom.xml в Maven-проекте и что в нем описывается?
Таймкод: 00:34:33
Ответ собеседника: неполный. Верно указывает про зависимости и правила сборки, но не раскрывает структуру и ключевые элементы POM.
Правильный ответ:
Файл pom.xml — это центральный конфигурационный документ Maven-проекта. Он описывает:
- кто мы (идентификация артефакта),
- что мы собираем,
- от чего зависит проект,
- как именно он должен быть собран, протестирован и задеплоен,
- как проект связан с другими модулями и репозиториями.
Грамотное понимание структуры pom.xml важно для интеграции с CI/CD, управления зависимостями и воспроизводимой сборки.
Основные элементы и их роль.
- Идентификация артефакта
Базовые координаты Maven:
- groupId — логическое пространство имен организации/проекта:
- пример:
com.example.payment
- пример:
- artifactId — имя конкретного артефакта:
- пример:
payment-service
- пример:
- version — версия артефакта:
- пример:
1.2.3,1.2.3-SNAPSHOT
- пример:
- packaging — тип артефакта:
jar,war,pom,earи др.
Пример:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example.payment</groupId>
<artifactId>payment-service</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
</project>
Эти координаты используются:
- для сборки (
mvn package→payment-service-1.0.0.jar), - для публикации в репозитории (Nexus/Artifactory),
- в зависимостях других модулей.
- Управление зависимостями
Секция <dependencies>:
- объявление внешних библиотек;
- указание scope (где доступна зависимость);
- управление версиями.
Пример:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>github.com/lib/pq</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>
Важные моменты:
- Maven сам тянет транзитивные зависимости;
- через
<dependencyManagement>в родительском POM можно централизованно задавать версии для всех модулей; - правильные scope (compile, provided, runtime, test) влияют на размер артефакта и поведение в runtime.
- Плагины и жизненный цикл сборки
Секция <build> и <plugins> описывает, как именно проект собирается:
- компиляция (
maven-compiler-plugin), - упаковка (
maven-jar-plugin,maven-war-plugin), - запуск тестов (
maven-surefire-plugin,maven-failsafe-plugin), - генерация документации, отчётов, javadoc,
- интеграция с Docker/CI (через специальные плагины).
Пример:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
Maven управляет фазами жизненного цикла (validate, compile, test, package, verify, install, deploy), а плагины привязываются к этим фазам. Это критично для CI/CD: на сервере сборки достаточно запустить mvn test или mvn deploy, а всё поведение определяется pom.xml.
- Родительский POM, наследование и multi-module
В крупных системах pom.xml описывает архитектуру проекта:
<parent>— наследование общих настроек:- общие версии плагинов,
- dependencyManagement,
- репозитории, properties.
<packaging>pom</packaging>+<modules>— multi-module проекты:- общие правила в корневом POM,
- каждый модуль — свой pom.xml, свои зависимости.
Пример родителя:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>platform-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>service-a</module>
<module>service-b</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
Пример модуля:
<project>
<parent>
<groupId>com.example</groupId>
<artifactId>platform-parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>service-a</artifactId>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
</project>
Так обеспечивается единообразие версий и поведения по всей кодовой базе — ключевой момент для больших команд и CI/CD.
- Репозитории, профили и конфигурация окружений
-
<repositories>/<pluginRepositories>:- где искать зависимости/плагины (Nexus, Artifactory, внутренние mirror'ы).
-
<profiles>:- разные настройки для dev/stage/prod или специфичных окружений:
- можно менять параметры сборки, активировать плагины, зависимости.
Пример профиля:
<profiles>
<profile>
<id>prod</id>
<properties>
<env>prod</env>
</properties>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
</profile>
</profiles>
В CI:
mvn clean package -Pprod
- Как это выглядит с точки зрения CI/CD
Для пайплайна достаточно:
mvn -B clean verify
и вся логика — зависимости, плагины, тесты, упаковка — описана в pom.xml. Это делает сборку:
- воспроизводимой,
- декларативной,
- независимой от ручной настройки окружения.
Краткий, сильный ответ для собеседования:
- "pom.xml — это декларативное описание Maven-проекта. В нем задаются координаты артефакта (groupId, artifactId, version, packaging), зависимости и их scope, плагины и привязка к фазам жизненного цикла, настройки сборки, профили для разных окружений, а в больших системах — наследование и multi-module структура. За счет pom.xml мы получаем воспроизводимую, стандартизированную сборку, легко интегрируемую в CI/CD."
Вопрос 31. Как перенастроить Maven на использование Nexus в изолированном контуре вместо внешних репозиториев?
Таймкод: 00:35:13
Ответ собеседника: неправильный. Не знает, где указывать URL Nexus, опирается на поиск.
Правильный ответ:
В изолированном контуре Maven должен работать только с внутренним Nexus/Artifactory и не ходить во внешние репозитории (Central, сторонние). Корректная настройка делается на уровне:
- глобальной конфигурации Maven (
settings.xml), - и/или pom.xml,
- конфигурации Nexus (proxy/hosted/group репозитории).
Ключевая идея: настроить mirror и репозитории так, чтобы Maven все запросы зависимостей и плагинов отправлял в Nexus.
- Базовая точка: settings.xml
Обычно используется:
- глобальный:
${MAVEN_HOME}/conf/settings.xml - пользовательский:
${USER_HOME}/.m2/settings.xml
Для изолированного контура чаще кладут корпоративный settings.xml на билд-агенты (CI) и разработчикам.
Основные шаги:
- объявить сервер (credentials для Nexus),
- задать mirror для Maven Central и остальных,
- при необходимости — профили с нужными репозиториями.
Пример settings.xml для Nexus:
<settings>
<servers>
<server>
<id>nexus</id>
<username>ci-user</username>
<password>ci-password</password>
</server>
</servers>
<mirrors>
<!-- Зеркалим все репозитории через Nexus -->
<mirror>
<id>nexus-all</id>
<mirrorOf>*</mirrorOf>
<url>https://nexus.internal.company/repository/maven-group/</url>
</mirror>
</mirrors>
<profiles>
<profile>
<id>use-nexus</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<repositories>
<repository>
<id>central</id>
<url>https://nexus.internal.company/repository/maven-group/</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<url>https://nexus.internal.company/repository/maven-group/</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
</settings>
Важно:
mirrorOf="*"гарантирует, что все обращения Maven к репозиториям будут идти только через указанный Nexus.- В изолированном контуре внешний Central вообще недоступен, поэтому такая настройка обязательна.
- Настройка на стороне Nexus
Типичная конфигурация Nexus (для полноты картины):
- Hosted репозитории:
maven-releases— для внутренних релиз-артефактов,maven-snapshots— для SNAPSHOT-сборок.
- (Если когда-то был онлайн-доступ) Proxy репозитории:
maven-centralи др. (но в полностью изолированном контуре обычно убираются или предварительно наполняются).
- Group репозиторий:
maven-group, включающий hosted (и при наличии — proxy).- Именно его URL указываем в settings.xml как единый вход.
В изоляции вы либо:
- используете только hosted и предварительно загруженные артефакты,
- либо обеспечиваете импорт артефактов через offline-канал (tar-архивы, staging, и т.п.).
- Использование pom.xml в связке с Nexus
В большинстве случаев pom.xml можно не править, достаточно settings.xml + mirror.
Но допустимо (и иногда полезно):
- Явно указывать внутренние репозитории:
<repositories>
<repository>
<id>internal-nexus</id>
<url>https://nexus.internal.company/repository/maven-group/</url>
</repository>
</repositories>
- Не указывать в pom.xml прямых ссылок на внешний Central:
- это упрощает перенос в изолированный контур,
- уменьшает риск "утечек" во внешку.
- Привязка credentials
Если Nexus требует аутентификацию:
- логин/пароль указываются только в
settings.xmlв<servers>, - в pom.xml используется
<distributionManagement>или<repository>с<id>, совпадающим с<server><id>.
Пример для деплоя:
<distributionManagement>
<repository>
<id>nexus</id>
<url>https://nexus.internal.company/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>nexus</id>
<url>https://nexus.internal.company/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
И в settings.xml:
<servers>
<server>
<id>nexus</id>
<username>ci-user</username>
<password>ci-password</password>
</server>
</servers>
Так CI (или разработчик) сможет выполнять mvn deploy в Nexus.
- Практический ответ для собеседования
Кратко и по делу:
- "В изолированном контуре Maven настраивается через settings.xml. Мы поднимаем в Nexus hosted/group репозитории и в settings.xml прописываем mirror, обычно с mirrorOf="*", чтобы все запросы Maven шли только через внутренний Nexus. Там же настраиваем credentials через
<servers>и профили с<repositories>и<pluginRepositories>. В pom.xml оставляем стандартные координаты и, при необходимости, distributionManagement под Nexus. В итоге CI/CD и локальные сборки работают детерминированно и не зависят от внешнего Maven Central."
Такой ответ показывает понимание не только "куда URL вписать", а всей схемы работы Maven в enterprise/secure окружении.
Вопрос 32. В чем разница между инструкциями ENTRYPOINT и CMD в Dockerfile?
Таймкод: 00:36:22
Ответ собеседника: правильный. Говорит, что ENTRYPOINT задаёт основную команду, а CMD — аргументы к ней; упоминает рекомендуемый паттерн использования.
Правильный ответ:
Инструкции ENTRYPOINT и CMD определяют, что именно будет запущено при старте контейнера, но играют разные роли и по-разному переопределяются.
Ключевое:
- ENTRYPOINT — задает «что за процесс этот контейнер по своей природе» (primary executable).
- CMD — задает аргументы по умолчанию (или fallback-команду), которые можно легко переопределить при запуске.
- Формы записи и поведение
Обе инструкции поддерживают две формы:
- exec-форма (рекомендуется):
ENTRYPOINT ["executable", "arg1", "arg2"]CMD ["arg1", "arg2"]
- shell-форма (оборачивается в
/bin/sh -c):ENTRYPOINT command arg1 arg2CMD command arg1 arg2
Exec-форма предпочтительна:
- корректная обработка сигналов (SIGTERM, SIGINT),
- корректный PID 1 для основного процесса,
- предсказуемое поведение без лишней оболочки.
- Типичные паттерны использования
Базовый паттерн:
- ENTRYPOINT задаёт бинарник/скрипт контейнера;
- CMD задаёт параметры по умолчанию, которые можно переопределить.
Пример:
FROM alpine:3.18
ENTRYPOINT ["ping"]
CMD ["127.0.0.1"]
docker run image→ запуститping 127.0.0.1docker run image 8.8.8.8→ запуститping 8.8.8.8(CMD переопределён аргументами).
- Переопределение при запуске
Важно понимать различия:
- Если ENTRYPOINT задан (exec-форма), аргументы при
docker run image ...дописываются к ENTRYPOINT. - CMD без ENTRYPOINT:
CMD ["sleep", "10"]docker run image→sleep 10docker run image ls→ls(команда полностью заменяет CMD).
- CMD с ENTRYPOINT:
- CMD работает как набор аргументов по умолчанию к ENTRYPOINT.
- ENTRYPOINT можно переопределить только через:
docker run --entrypoint /bin/sh image- или в docker-compose / Kubernetes манифестах.
- Комбинации и подводные камни
- Только CMD:
- удобно для простых образов, но легко сломать при
docker run image bash.
- удобно для простых образов, но легко сломать при
- Только ENTRYPOINT:
- контейнер жестко привязан к одной команде;
- неудобно для отладки (приходится всегда переопределять
--entrypoint).
- ENTRYPOINT + CMD (рекомендуемый паттерн):
- ENTRYPOINT — ваш бинарник или wrapper-скрипт;
- CMD — дефолтные аргументы, переопределяемые пользователем.
Пример рекомендованного паттерна для приложения:
FROM golang:1.22-alpine AS build
WORKDIR /app
COPY . .
RUN go build -o app ./cmd/app
FROM alpine:3.18
WORKDIR /app
COPY --from=build /app/app .
ENTRYPOINT ["/app/app"]
CMD ["--config=/etc/app/config.yaml"]
docker run image→/app/app --config=/etc/app/config.yamldocker run image --config=/tmp/dev.yaml→/app/app --config=/tmp/dev.yaml
Это даёт:
- стабильный entrypoint (приложение всегда запускается),
- гибкие параметры.
- Для собеседования, кратко и сильно
- ENTRYPOINT определяет неизменяемый основной процесс контейнера.
- CMD задаёт умолчания (обычно аргументы к ENTRYPOINT или fallback-команду), легко переопределяется в
docker run. - Рекомендуется: использовать exec-форму и паттерн
ENTRYPOINT ["binary"]+CMD ["args..."], чтобы контейнер был и предсказуемым, и настраиваемым.
Вопрос 33. Для чего используется директива EXPOSE в Dockerfile?
Таймкод: 00:36:51
Ответ собеседника: правильный. Говорит, что EXPOSE декларативно описывает порт, но не публикует его; реальный проброс делается при запуске контейнера или в docker-compose.
Правильный ответ:
Директива EXPOSE в Dockerfile:
- не открывает и не пробрасывает порт наружу,
- служит декларативной меткой: какие порты этот образ предполагает использовать для входящих соединений,
- облегчает понимание, автоматизацию и интеграцию с оркестраторами и tooling.
Это часть контракта образа: «приложение внутри слушает на этих портах».
- Как это работает по сути
Когда вы пишете, например:
FROM alpine:3.18
RUN apk add --no-cache nginx
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]
Вы тем самым:
- сообщаете людям, читающим Dockerfile/образ: сервис доступен на 80/443,
- даете подсказку инструментам (docker inspect, некоторые UI, генераторы конфигов).
Но:
- контейнер по-прежнему слушает эти порты только внутри своей сети,
- чтобы порт стал доступен с хоста, нужно при запуске указать
-p/--publishили настроить проброс в docker-compose/Kubernetes.
- Публикация портов: как связано с EXPOSE
Без EXPOSE:
docker run -p 8080:80 your-image
всё равно будет работать, даже если в Dockerfile нет EXPOSE 80. Docker не требует EXPOSE для -p.
С EXPOSE:
docker run -P your-image(заглавная P) автоматически пробросит все порты, объявленные в EXPOSE, на случайные порты хоста.docker inspectпокажет список ExposedPorts, что удобно для tooling/скриптов.
- Хорошие практики
- Всегда явно указывать EXPOSE для основных сервисных портов образа.
- Это улучшает читаемость и делает образ самодокументированным.
- Не добавлять EXPOSE для внутренних/служебных портов, которые не должны использоваться снаружи контейнера.
- Не полагаться на EXPOSE как на механизм безопасности:
- EXPOSE не открывает порт и не закрывает — он не firewall.
- Без
-pили сетевых настроек оркестратора порт будет доступен только внутри сетей Docker.
- Пример для Go-сервиса
Классический HTTP-сервис на Go:
// main.go
package main
import (
"log"
"net/http"
)
func main() {
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})
log.Println("listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Dockerfile:
FROM golang:1.22-alpine AS build
WORKDIR /app
COPY . .
RUN go build -o server ./...
FROM alpine:3.18
WORKDIR /app
COPY --from=build /app/server .
EXPOSE 8080
ENTRYPOINT ["./server"]
- Тут
EXPOSE 8080говорит: сервис слушает 8080 внутри контейнера. - Запуск:
docker run --rm -p 8080:8080 app-image- или
docker run --rm -P app-image(в этом случае внешний порт будет случайным, но основан на EXPOSE).
- Кратко для собеседования
EXPOSE— декларация, а не реальный проброс.- Показывает, какие порты сервис внутри контейнера ожидает использовать.
- Для доступа с хоста нужны
-p/--publish, docker-compose ports, или настройки в Kubernetes/оркестраторах. - Полезен для читаемости, автоконфигурации и использования
-P.
Вопрос 34. Что делает блок workflow: rules: в конфигурации .gitlab-ci.yml?
Таймкод: 00:37:48
Ответ собеседника: правильный. Объясняет, что workflow rules определяют условия запуска пайплайна (например, для определённых веток или источников).
Правильный ответ:
Блок workflow: rules: управляет тем, будет ли вообще создан и запущен пайплайн для конкретного события (push, MR, tag, webhook и т.п.). Это фильтр верхнего уровня над всем .gitlab-ci.yml, который срабатывает ДО оценки rules/only/except в отдельных джобах.
Ключевые моменты:
workflow: rules:решает: создать пайплайн или полностью его не запускать.- Логика похожа на
rulesв джобах, но применяется ко всему пайплайну. - Позволяет централизованно описать политики: для каких веток, MR, тегов, schedule и т.п. пайплайны разрешены или отключены.
- Базовый принцип работы
GitLab последовательно проходит по правилам:
workflow:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: always
- if: '$CI_COMMIT_BRANCH == "main"'
when: always
- when: never
Семантика:
- Первое подходящее правило определяет судьбу пайплайна:
when: always— создать пайплайн,when: never— не создавать,when: on_success/manualи др. допустимы, но для workflow критичны в первую очередьalways/never.
- В примере выше:
- пайплайн запускается для MR и для ветки
main, - для всех остальных веток/событий — не создается вообще.
- пайплайн запускается для MR и для ветки
- Отличие от rules / only / except на уровне jobs
Важно различать уровни:
workflow: rules:— решает, существует ли пайплайн как факт.rules:в джобах (илиonly/except) — решают, войдет ли конкретная job в уже созданный пайплайн.
Типичный паттерн:
workflow:
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: always
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: always
- when: never
build:
stage: build
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
workflowне даст создать пайплайн для случайных веток.rulesв job'ах дополнительно уточняют, какие job'ы запускать для main/MR.
- Типичные сценарии использования
- Ограничить запуск CI:
- только для
main,develop, релизных веток, MR:workflow:
rules:
- if: '$CI_COMMIT_BRANCH =~ /^release\//'
when: always
- if: '$CI_COMMIT_BRANCH == "main"'
when: always
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: always
- when: never
- только для
- Разделить поведение по типу события:
- разные пайплайны для tag vs branch vs schedule.
- Отключить «мусорные» пайплайны:
- для временных веток, автогенерируемых коммитов, ручных пушей в старые релизы.
- Важные нюансы
- Если ни одно правило не сработало и нет
when: never, GitLab применяет поведение по умолчанию:- если есть
workflow: rules:и ни одинwhenне сработал явно, пайплайн не создается (поэтому часто в конце добавляют- when: neverкак явное правило).
- если есть
- В
if:можно использовать любые CI-переменные:$CI_PIPELINE_SOURCE,$CI_COMMIT_BRANCH,$CI_COMMIT_TAG,$CI_MERGE_REQUEST_TARGET_BRANCH_NAME,$CI_PROJECT_PATH, и т.д.
workflow: rules:не заменяет логику включения/исключения job'ов:- обычно используется в связке с
rulesна job'ах для более тонкой настройки.
- обычно используется в связке с
- Краткий ответ для собеседования
workflow: rules:— это глобальные правила, которые решают, создавать ли пайплайн для конкретного события.- Он применяется до job-уровня и позволяет централизованно фильтровать: для каких веток, тегов, merge request'ов, расписаний и т.п. вообще запускать CI.
- Таким образом снижаем шум, экономим ресурсы и делаем поведение CI детерминированным.
Вопрос 35. Что делает блок include в .gitlab-ci.yml и зачем нужен параметр ref?
Таймкод: 00:40:04
Ответ собеседника: правильный. Говорит, что include подключает внешний файл конфигурации из указанного проекта и пути, а ref задаёт ветку или тег, откуда брать файл.
Правильный ответ:
Блок include в .gitlab-ci.yml позволяет разбивать и переиспользовать конфигурацию пайплайнов, а также подключать общие шаблоны из других репозиториев или удалённых файлов. Это ключевой инструмент для построения масштабируемых и стандартизованных CI/CD-конвейеров.
Параметр ref в блоке include используется для указания конкретной ревизии (ветки, тега или коммита), из которой должен быть взят подключаемый файл. Это критично для стабильности: ваша CI-конфигурация не должна «ломаться» из-за изменений в другом репозитории.
- Основные варианты использования include
GitLab поддерживает несколько источников для include (можно комбинировать):
local— подключение локальных файлов из того же репозитория.file+project— файлы из другого проекта GitLab.remote— внешние URL.template— предопределённые шаблоны GitLab (например, для Security, Code Quality и т.п.).
Примеры:
- Локальный include:
include:
- local: .gitlab/ci/jobs/build.yml
- local: .gitlab/ci/jobs/deploy.yml
Используем для:
- логического разбиения монолитного
.gitlab-ci.yml, - повторного использования job'ов и anchors внутри одного репо.
- Include из другого проекта:
include:
- project: devops/ci-templates
file: /go/app-ci.yml
ref: main
Здесь:
project— namespace/project в GitLab.file— путь к файлу внутри того проекта.ref— обязательный для production-подхода параметр:- указывает, какую ветку/тег/коммит использовать;
- без него файл будет браться из дефолтной ветки — это риск ломких изменений.
- Remote include:
include:
- remote: https://example.com/gitlab-ci/common.yml
Используем осторожно:
- удобно для общих корпоративных шаблонов,
- но критично контролировать доступность и целостность.
- Template include:
include:
- template: Security/SAST.gitlab-ci.yml
Подключает встроенные шаблоны GitLab без необходимости хранить их у себя.
- Зачем нужен ref
ref решает задачу версионирования подключаемой конфигурации:
- Гарантирует, что пайплайны в вашем репозитории используют конкретную версию CI-шаблонов.
- Позволяет постепенно обновлять шаблоны, меняя
refна новый тег:- сначала протестировать в отдельных проектах,
- затем массово обновить.
Пример хорошей практики:
include:
- project: devops/ci-templates
file: /go/app-ci.yml
ref: v1.4.2
Плюсы:
- при изменениях в
devops/ci-templatesваши пайплайны не «поедут» неожиданно; - вы явно контролируете момент обновления (меняете
refнаv1.4.3, когда готовы).
Можно указывать:
- ветку:
ref: mainилиref: ci-templates, - тег:
ref: v1.4.2(лучший вариант для стабильности), - конкретный SHA-коммита (максимально жёсткая фиксация).
- Типичный паттерн для команд и монорепо
Для стандартизации CI по всем Go-сервисам:
В проекте devops/ci-templates:
# /go/app-ci.yml
stages:
- build
- test
build:
stage: build
image: golang:1.22
script:
- go build ./...
test:
stage: test
image: golang:1.22
script:
- go test ./...
В сервисе:
# .gitlab-ci.yml
include:
- project: devops/ci-templates
file: /go/app-ci.yml
ref: v1.0.0
variables:
GOFLAGS: "-mod=vendor"
Результат:
- все сервисы используют единый стандарт CI,
- изменения стандартов вносятся централизованно через новый тег шаблонов,
- каждый сервис контролирует, когда перейти на новую версию.
- Нюансы, о которых стоит знать
- Порядок include важен: позже подключенные файлы могут переопределять ранее объявленные элементы (jobs, variables, anchors).
- Можно комбинировать несколько include; итоговая конфигурация — это merge всех подключенных файлов.
- Ошибка в include (невалидный путь/file/ref) ломает пайплайн на этапе загрузки конфигурации.
- Для чувствительных изменений в общих шаблонах:
- всегда используйте теги/коммиты в
ref, - избегайте «плавающего»
ref: mainв прод-проектах.
- всегда используйте теги/коммиты в
- Кратко для собеседования
includeподключает внешние/локальные CI-конфиги и позволяет переиспользовать общие шаблоны.refуказывает, из какой ветки/тега/коммита другого проекта брать конфигурацию, и нужен для версионирования и стабильности пайплайнов.- Правильное использование
include + ref— базис масштабируемого и безопасного GitLab CI во многих репозиториях.
Вопрос 36. Использовали ли вы Helm-чарты для деплоя в Kubernetes и как понимаете работу шаблонов env/values?
Таймкод: 00:41:24
Ответ собеседника: правильный. Говорит, что использовал Helm-чарты для деплоя; корректно описывает, как из values.yaml подставляются переменные окружения и зачем нужен nindent для отступов.
Правильный ответ:
Helm-чарты — это способ описать Kubernetes-манифесты как параметризуемые шаблоны. Вместо того чтобы держать десятки/сотни YAML-файлов для разных окружений, мы имеем:
- шаблоны
templates/*.yamlс Go-templating, - конфигурацию по умолчанию
values.yaml, - environment-специфичные values-файлы (
values.prod.yaml,values.staging.yamlи т.п.), - логику рендеринга (
helm install/upgrade) → готовые Kubernetes-манифесты.
Работа env/values — это, по сути, механизм проброса конфигурации (включая переменные окружения контейнера) через values в шаблоны.
- Базовая схема: values → templates → манифесты
- В
values.yamlописываем параметры:- ресурсы,
- репозиторий образа,
- env-переменные,
- конфиги для разных окружений.
Пример values.yaml:
image:
repository: my-registry/my-go-app
tag: "1.2.3"
env:
- name: APP_ENV
value: "production"
- name: LOG_LEVEL
value: "info"
config:
db:
host: "db.example"
port: 5432
- В
templates/deployment.yamlиспользуем Go-шаблоны для подстановки:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "app.fullname" . }}
spec:
replicas: 2
template:
spec:
containers:
- name: app
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
env:
{{- range .Values.env }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
Helm при установке:
- берет
.Values(слитые изvalues.yaml+-f values.<env>.yaml+--set), - рендерит шаблоны,
- отдает чистый YAML в Kubernetes.
- env как способ прокинуть конфигурацию приложения
В продакшене env-переменные обычно формируются из:
- values-файлов для окружения,
- секретов (Secret),
- ConfigMap.
Пример (выражаем env на базе values):
values.prod.yaml:
env:
- name: APP_ENV
value: "prod"
- name: LOG_LEVEL
value: "warn"
- name: DB_HOST
value: "prod-db.internal"
Шаблон (фрагмент containers.env):
env:
{{- range .Values.env }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
Или более сложный вариант, комбинирующий secretKeyRef:
env:
- name: APP_ENV
value: {{ .Values.app.env | default "dev" | quote }}
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.secrets.db.name }}
key: password
- Как работает nindent и почему это важно
Helm-шаблоны — это обычный YAML + Go template. Ошибка в отступах ломает манифест. nindent решает задачу:
indent— добавляет пробелы, но не обрезает начальные,nindent— сначала делает\n, потом indent, удобно для многострочных блоков.
Пример:
env:
{{- range .Values.env }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
При сложных условиях и вложенных объектах код быстро становится нечитаемым. Лучше вынести блок в helper и использовать nindent:
_helpers.tpl:
{{- define "app.env" -}}
{{- range .Values.env }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
{{- end }}
deployment.yaml:
env:
{{- include "app.env" . | nindent 10 }}
Преимущества:
app.envрендерит список без начальных отступов,nindent 10корректно смещает весь блок так, чтобы он совпал с уровнемenv:(10 пробелов в данном примере),- уменьшается количество YAML-ошибок и улучшается читаемость.
- Пример для Go-приложения
Go-код, читающий env:
package main
import (
"log"
"net/http"
"os"
)
func main() {
addr := os.Getenv("APP_ADDR")
if addr == "" {
addr = ":8080"
}
dbHost := os.Getenv("DB_HOST")
if dbHost == "" {
log.Fatal("DB_HOST is required")
}
log.Printf("Starting server at %s, DB: %s", addr, dbHost)
http.ListenAndServe(addr, nil)
}
Helm-values для разных окружений:
values.dev.yaml:
env:
- name: APP_ADDR
value: ":8080"
- name: DB_HOST
value: "dev-db"
values.prod.yaml:
env:
- name: APP_ADDR
value: ":8080"
- name: DB_HOST
value: "prod-db"
Шаблон один и тот же; разное поведение достигается подстановкой разных values:
helm upgrade --install app ./chart -f values.prod.yaml
- Важные практики
- Всегда четко разделяйте:
- шаблоны (логика, структура манифестов),
- values (данные для окружений).
- Используйте
ref-подобную семантику на уровне Git (теги/ветки для чартов), чтобы деплой был воспроизводим. - Следите за отступами и используйте
nindent/indentи helpers — это повышает надёжность и читаемость. - Не хардкодьте чувствительные данные в values; используйте Secret'ы и ссылки на них в шаблонах.
Кратко: да, Helm-чарты — стандартный инструмент деплоя в Kubernetes; values управляют параметрами, шаблоны env подставляют их в манифесты, а nindent помогает корректно формировать YAML-структуру.
Вопрос 37. Что делает фрагмент Helm-шаблона с перебором env-переменных через range и функциями upper/replace?
Таймкод: 00:44:44
Ответ собеседника: правильный. Говорит, что шаблон итерируется по values.env, преобразует ключи к верхнему регистру с заменой точек на дефисы и формирует пары name/value для переменных окружения.
Правильный ответ:
Такой фрагмент Helm-шаблона обычно используется для автоматического формирования списка переменных окружения контейнера на основе значений из values.yaml с нормализацией ключей под принятый формат для env-переменных.
Типичный пример подобного шаблона:
env:
{{- range $key, $value := .Values.env }}
- name: {{ $key | upper | replace "." "_" | replace "-" "_" }}
value: {{ $value | quote }}
{{- end }}
Или вариант, который вы могли видеть:
env:
{{- range $k, $v := .Values.env }}
- name: {{ $k | upper | replace "." "-" }}
value: "{{ $v }}"
{{- end }}
Суть работы по шагам:
-
range .Values.env-
Проходим по карте/списку, определённому в
values.yaml, например:env:
app.mode: "prod"
log-level: "info"
db.host: "db.internal" -
Для каждого элемента получаем ключ (
$k/$key) и значение ($v/$value).
-
-
Генерация имени переменной окружения
- Функции
upper,replaceи подобные используются для приведения ключа к корректному и единообразному формату env-переменных:upper— делает имя "константным" (APP_MODE, LOG_LEVEL и т.д.).replace "." "-"илиreplace "." "_"и т.п. — убирает недопустимые или нежелательные символы.
- Это позволяет:
- хранить конфигурацию в
values.yamlв удобном "dot notation" стиле, - но получать на выходе валидные, читаемые env-переменные в контейнере.
- хранить конфигурацию в
Пример трансформаций:
app.mode→APP_MODElog-level→LOG_LEVELdb.host→DB_HOST
- Функции
-
Формирование блока env
-
На каждый элемент формируется:
- name: <ПРЕОБРАЗОВАННЫЙ_KEY>
value: "<значение из values>" -
В итоге Deployment/Pod получает набор env-переменных, которые приложение может читать через стандартный механизм
os.Getenv(в Go) или аналоги в других языках.
-
-
Зачем так делать и почему это хороший приём
- Централизация конфигурации:
- В
values.yamlописываются бизнес-настройки в удобном виде. - Шаблон автоматически конвертирует их в env-переменные.
- В
- Избежание дублирования:
- Вам не нужно руками прописывать каждый
- name: ... value: ...в шаблоне. - Добавление нового параметра = добавить запись в
values.yaml.
- Вам не нужно руками прописывать каждый
- Единый нейминг:
- upper/replace обеспечивают консистентный формат env-переменных.
- Гибкость:
- Можно менять стратегию именования (например, точки → нижнее подчёркивание, дефисы → подчёркивание), не трогая манифесты по всему проекту.
- Централизация конфигурации:
-
Практический пример (Go-приложение)
values.yaml:
env:
app.mode: prod
log-level: info
db.host: db.internal
Шаблон:
env:
{{- range $k, $v := .Values.env }}
- name: {{ $k | upper | replace "." "_" | replace "-" "_" }}
value: {{ $v | quote }}
{{- end }}
Рендер в итоговый Deployment:
env:
- name: APP_MODE
value: "prod"
- name: LOG_LEVEL
value: "info"
- name: DB_HOST
value: "db.internal"
Фрагмент Go-кода:
mode := os.Getenv("APP_MODE")
logLevel := os.Getenv("LOG_LEVEL")
dbHost := os.Getenv("DB_HOST")
- Важные моменты
- Нужно осознанно выбирать правила преобразования:
- чтобы разные ключи не схлопывались в одно имя (
a-bиa.bпри агрессивной замене).
- чтобы разные ключи не схлопывались в одно имя (
- Для сложных структур (nested map) часто делают отдельные helper-шаблоны или явно перечисляют важные ключи.
- В реальных чартах это часто выносят в
helpers.tplи подключают черезinclude ... | nindent, чтобы не ломать отступы и держать шаблон читабельным.
Итого: такой фрагмент — это мощный и удобный способ автоматически маппить декларативную конфигурацию из Helm values в корректные переменные окружения контейнера с нормализованными именами.
Вопрос 38. Как вы с нуля выстроите процесс CI/CD в компании, где сейчас всё деплоится вручную?
Таймкод: 00:45:42
Ответ собеседника: правильный. Предлагает пайплайн: линтеры и базовые проверки, сборка и push образов в GitLab Registry с тегированием, юнит- и нагрузочные тесты, деплой на тест/стейджинг, затем ручной триггер деплоя на прод (continuous delivery).
Правильный ответ:
Подход к построению CI/CD с нуля должен быть поэтапным, с упором на:
- предсказуемость сборки,
- воспроизводимость артефактов,
- безопасность,
- управляемость деплоев (особенно продакшена),
- минимизацию ручных и "магических" действий.
Ниже — практичная схема, которую можно использовать как референс.
- Базовые принципы
- Один источник правды: все манифесты, Dockerfile, Helm-чарты, скрипты деплоя — в Git.
- Каждый commit/merge request должен быть:
- собран,
- проверен (линтеры, тесты),
- при необходимости — задеплоен в подходящее окружение.
- Артефакты (бинарники, Docker-образы, Helm-чарты) — версионируются и хранятся в репозитории (Docker Registry, Chart Registry).
- Прод — только через контролируемую процедуру:
- либо ручной апрув,
- либо auto-deploy по release-тегу.
- Структура окружений
Рекомендуемый минимум:
- dev (optional, для командной разработки/feature review),
- stage / preprod (максимально близко к prod),
- prod.
Основной принцип: один и тот же артефакт (образ, чарт) проходит через окружения, меняются только values/конфигурация.
- CI-пайплайн: шаги и логика
Рассмотрим типичный стек: GitLab CI / GitHub Actions, Go + Docker + Kubernetes + Helm.
Этапы:
-
Stage:
lint/static-check- gofmt/goimports
- golangci-lint
- проверка Dockerfile, Helm-чартов (
hadolint,helm lint).
-
Stage:
test- юнит-тесты:
go test ./... -race -coverprofile=coverage.out - при необходимости — component/integration tests (с docker-compose/kind/testcontainers).
- юнит-тесты:
-
Stage:
build-
сборка Go-бинарника:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o app ./cmd/app -
сборка Docker-образа (multi-stage, минимальный runtime image):
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o app ./cmd/app
FROM gcr.io/distroless/base-debian12
WORKDIR /app
COPY --from=builder /src/app .
USER nonroot:nonroot
ENTRYPOINT ["/app/app"] -
тегирование образов:
- по SHA коммита (
app:<commit-sha>), - по ветке (
app:branch-name), - по релизу (
app:v1.2.3при теге).
- по SHA коммита (
-
-
Stage:
push-image- пуш в приватный registry (GitLab, ECR, GCR, Harbor).
- это ключевой артефакт: один и тот же образ идёт на stage и prod.
-
Stage:
deploy-stage- авто-деплой на stage:
- через Helm:
helm upgrade --install app ./deploy/chart \
--namespace stage \
--set image.repository=registry/app \
--set image.tag=${CI_COMMIT_SHA} \
-f values.stage.yaml - smoke-тесты: healthcheck, базовые API-пробеги.
- через Helm:
- падение smoke-тестов должно фейлить пайплайн.
- авто-деплой на stage:
-
Stage:
deploy-prod- строго управляемый:
- либо manual job (GitLab
when: manual), - либо автоматический при git-теге вида
v*(release-based).
- либо manual job (GitLab
- тот же Helm-чарт, другой values-файл:
helm upgrade --install app ./deploy/chart \
--namespace prod \
--set image.repository=registry/app \
--set image.tag=${RELEASE_TAG} \
-f values.prod.yaml
--history-max=10 - обязательные требования:
- rollbacks через Helm (
helm rollback), - аудит через Git/CI-лог.
- rollbacks через Helm (
- строго управляемый:
- Пошаговое внедрение (как сделать безболезненно)
Нельзя прыгнуть сразу в "идеальный" CI/CD, если команда всё делала руками. Правильная стратегия:
Шаг 1: Формализовать ручной процесс
- Описать в README/доках:
- как собрать,
- как запустить тесты,
- как собрать образ,
- как задеплоить.
- Это станет основой для автоматизации.
Шаг 2: Ввести базовый CI без авто-деплоя
- На каждый commit/MR:
- линтеры,
- go test,
- build (без пуша образа или с пушем только для main).
- Цель: сделать зелёный pipeline обязательным условием merge.
Шаг 3: Автоматизировать сборку образов и registry
- Для main/master:
- build + push image.
- Для feature-веток:
- build (optional: push с отдельными тегами для review environment).
Шаг 4: Ввести stage-окружение и auto-deploy на него
- Описать инфраструктуру:
- Kubernetes/Helm или docker-compose для stage.
- Настроить:
- auto-deploy на stage при merge в main.
- Добиться:
- каждая актуальная версия в main живёт на stage.
Шаг 5: Controlled prod deploy
- Продакшен-деплой:
- только с зафиксированного артефакта (образ по SHA или релиз-тег).
- через CI job:
- с ручным подтверждением,
- с ограничением по ролям.
- На этом этапе компания переходит от "ssh и руками" к воспроизводимому, логируемому деплою.
Шаг 6: Улучшения
В зависимости от зрелости команды и требований:
- Blue-Green / Canary:
- через сервис-меш или ingress-правила,
- прогрев новой версии на части трафика.
- Quality gates:
- покрытие тестами,
- статический анализ (gosec, trivy для образов),
- SAST/DAST.
- GitOps:
- ArgoCD/Flux:
- CI только билдит и пушит артефакты,
- CD занимается арго/флюкс, синхронизируя кластер с git-репозиторием манифестов.
- ArgoCD/Flux:
- Observability-by-default:
- метрики, логи, трассировки подключаются в шаблонах деплоя сразу.
- Моменты, о которых важно не забыть
- Секреты:
- никогда не хранить в репо.
- использовать Vault/SealedSecrets/External Secrets, CI variables.
- Migration workflow:
- для Go-сервисов с БД:
- миграции как отдельный шаг/джоб,
- безопасный порядок: миграции → деплой.
- для Go-сервисов с БД:
- Backward compatibility:
- деплои должны быть безопасными при поэтапном раскатывании (schema-first, feature-flags).
- Rollback:
- формально описанный и быстрый:
helm rollback+ известный образ,- либо GitOps revert commit.
- формально описанный и быстрый:
Итого: правильный ответ — это не просто "запускаем линтер и деплоим". Это поэтапное внедрение конвейера, где:
- билд и тесты → обязательны и автоматизированы,
- артефакты → версионируемы и переиспользуемы,
- деплой → декларативен, воспроизводим и управляем,
- прод → с ручным контролем (или через формализованные release-процессы), с возможностью быстрого отката.
Вопрос 39. Каков ваш опыт работы с Ansible и писали ли вы собственные роли?
Таймкод: 00:48:14
Ответ собеседника: правильный. Использовал Ansible для деплоя на серверах без Kubernetes и обновления закрытых контуров; упоминает, что писал собственные роли и плейбуки.
Правильный ответ:
Ansible — один из ключевых инструментов для инфраструктурной автоматизации, особенно в средах, где:
- нет Kubernetes или он внедряется постепенно,
- есть изолированные контуры и ограничения по доступу,
- нужен воспроизводимый и проверяемый конфиг вместо "ssh + bash".
Грамотный опыт с Ansible включает:
- Архитектура и организация кода
- Использование best practices структуры проекта:
- разделение на:
inventories/(prod, stage, dev),group_vars/,host_vars/,roles/,playbooks/.
- разделение на:
- Явное управление окружениями:
- разные наборы переменных для stage/prod,
- общие роли, переиспользуемые между окружениями.
Пример структуры:
ansible/
inventories/
prod/
hosts.ini
group_vars/
app.yml
stage/
hosts.ini
roles/
app/
tasks/
templates/
files/
handlers/
defaults/
playbooks/
deploy_app.yml
setup_base.yml
- Собственные роли
Опыт работы с Ansible на хорошем уровне подразумевает не только использование готовых ролей с Galaxy, но и написание своих, заточенных под инфраструктуру компании:
- Примеры типичных собственных ролей:
- установка и конфигурация:
- Go runtime / нужных тулов,
- nginx/haproxy,
- systemd unit для Go-сервиса,
- логирования и мониторинга (promtail/node_exporter),
- деплой приложения:
- доставка бинарника или Docker-образа,
- обновление конфигов из шаблонов,
- перезапуск сервиса с
serial/rollingстратегией.
- установка и конфигурация:
Пример: роль деплоя Go-сервиса через systemd.
playbooks/deploy_app.yml:
- name: Deploy app
hosts: app_servers
become: yes
roles:
- role: app
roles/app/tasks/main.yml:
- name: Create app directory
file:
path: /opt/myapp
state: directory
owner: appuser
group: appuser
mode: '0755'
- name: Copy binary
copy:
src: files/myapp
dest: /opt/myapp/myapp
owner: appuser
group: appuser
mode: '0755'
- name: Render config
template:
src: templates/config.yaml.j2
dest: /opt/myapp/config.yaml
owner: appuser
group: appuser
mode: '0644'
notify: Restart app
- name: Ensure systemd unit present
template:
src: templates/myapp.service.j2
dest: /etc/systemd/system/myapp.service
notify:
- Daemon reload
- Restart app
roles/app/handlers/main.yml:
- name: Daemon reload
systemd:
daemon_reload: yes
- name: Restart app
systemd:
name: myapp
state: restarted
enabled: yes
- Идемпотентность и качество
Ключевой показатель опыта — умение писать идемпотентные роли:
- повторный запуск playbook не должен "ломать" состояние,
- изменения происходят только при реальной разнице (Темплейты, пакеты, файлы),
- нет "shell: echo >> /etc/..." без проверок.
Также важно:
- использовать
check_modeи--diff, - разделять
defaultsиvars, - не хардкодить чувствительные данные.
- Работа с секретами и закрытыми контурами
В реальных компаниях:
- используется
ansible-vaultдля хранения секретов, - есть отдельные инвентори и ключи под закрытые контуры,
- часто есть ограничение по доступу в интернет:
- роли должны уметь работать с локальными зеркалами репозиториев и артефактов,
- бинарники/архивы/образы ставятся из internal storage.
Опытный подход:
- все, что используется для деплоя, заранее кладется во внутренний артефактори или файловый сервер,
- роли параметризуются, чтобы переключаться между "онлайн" и "офлайн" режимом.
- Интеграция с CI/CD
Ansible органично встраивается в конвейер:
- CI собирает артефакты (Go-бинарник, Docker-образ),
- CD запускает Ansible playbook для:
- выкладки на bare-metal/VM,
- обновления конфигов,
- миграций.
Пример джоба в GitLab CI:
deploy_prod:
stage: deploy
image: alpine/ansible
script:
- ansible-playbook -i inventories/prod/hosts.ini playbooks/deploy_app.yml
when: manual
only:
- tags
- Практики для production-уровня
- Использование
serial,max_fail_percentageдля rolling-deploy:- не обновлять все ноды сразу,
- иметь возможность быстрого отката.
- Проверки после деплоя:
- таски, которые дергают healthcheck endpoint сервиса,
- break/failed_when, если сервис не поднялся.
- Разделение ролей:
- базовая ОС,
- платформа (Docker, логирование, мониторинг),
- приложения.
- Повторное использование:
- один набор ролей для разных проектов, конфигурируемый через переменные.
Итого: зрелый ответ на вопрос про Ansible должен показывать:
- умение структурировать инфраструктурный код,
- опыт написания собственных ролей под конкретные требования,
- идемпотентность, безопасность и гибкость,
- интеграцию с CI/CD и поддержку ограниченных/закрытых сред.
Вопрос 40. Как с помощью Ansible проверить доступность хостов без написания плейбука?
Таймкод: 00:49:19
Ответ собеседника: правильный. Уточняет вопрос и предлагает использовать модуль ping Ansible для проверки доступности.
Правильный ответ:
Для быстрой проверки доступности хостов без написания отдельного playbook используется команда ad-hoc с модулем ping:
-
Базовый пример:
ansible all -m pingall— группа из inventory.-m ping— вызов модуляping.- Проверяется не ICMP-пинг, а:
- возможность подключиться по SSH,
- запуск Python (или
raw-режим для некоторыхansible -c), - выполнение простого кода на целевой машине.
-
Проверка конкретной группы:
ansible app_servers -m ping -
С явным указанием inventory:
ansible -i inventories/prod/hosts.ini app_servers -m ping -
С конкретным пользователем:
ansible -i inventories/prod/hosts.ini all -m ping -u deploy -
Если используются ключи/SSH-конфиг:
ansible -i inventories/prod/hosts.ini all -m ping --ask-pass
# или
ansible -i inventories/prod/hosts.ini all -m ping --private-key ~/.ssh/id_rsa
Ключевые моменты:
ansible <pattern> -m ping— стандартный, быстрый, правильный способ проверить:- корректность inventory,
- доступность по SSH,
- базовую готовность хостов к работе с Ansible.
- Это лучше, чем обычный
ping, потому что тестирует именно тот путь, по которому Ansible реально работает.
Вопрос 41. Можно ли с помощью Ansible установить Python на удалённый сервер, если Python там ещё не установлен?
Таймкод: 00:49:55
Ответ собеседника: неполный. Говорит про отсутствие агентов и предлагает использовать shell/command для установки Python по SSH, но без понимания специальных no-python механизмов.
Правильный ответ:
Да, можно. Важно понимать, как Ansible работает "под капотом" и какие есть специальные режимы для хостов без Python.
Базовый принцип:
- Обычные модули Ansible (apt, yum, copy, template и т.п.) требуют Python на целевой машине.
- Но Ansible умеет:
- выполнять "сырые" команды без Python (
raw), - использовать специальные "bootstrap"-паттерны,
- применять connection-параметры и no-python-модули, чтобы как раз установить Python.
- выполнять "сырые" команды без Python (
Практический подход:
- Использование
rawдля установки Python
raw — это модуль, который отправляет команду напрямую по SSH без каких-либо обёрток на Python. Его можно использовать в ад-хок команде или в первом таске плейбука.
Примеры ад-хок:
# Debian/Ubuntu
ansible all -i hosts -m raw -a "apt-get update && apt-get install -y python3" --become
# RHEL/CentOS/Alma/Rocky
ansible all -i hosts -m raw -a "yum install -y python3" --become
После этого на хосте есть Python, и можно работать обычными модулями.
Пример плейбука для bootstrap:
- name: Bootstrap Python on hosts without it
hosts: all
become: yes
gather_facts: no
tasks:
- name: Install Python on Debian-based
raw: |
test -e /usr/bin/python3 || (apt-get update && apt-get install -y python3)
args:
warn: false
register: deb_python
changed_when: deb_python.stdout != ""
- name: Install Python on RHEL-based
raw: |
test -e /usr/bin/python3 || yum install -y python3
args:
warn: false
when: deb_python is failed
И только после такого bootstrap запускают обычные роли и плейбуки.
- Управление поведением через
ansible_python_interpreter
Если Python установлен не по стандартному пути или вы используете другой runtime:
[all]
server1 ansible_host=1.2.3.4 ansible_python_interpreter=/usr/local/bin/python3
Это важно при bootstrap-е, когда надо явно указать, какой интерпретатор использовать после установки.
- Особенности и best practices
- Нельзя рассчитывать на
ping,setup,apt,yumи т.п. до установки Python — они не сработают без него. - Для хостов без Python:
- используем
gather_facts: noв bootstrap-плейбуке, - используем только
raw(или некоторые специализированные модули, которые не требуют Python, зависят от версии Ansible), - после установки Python можно включить
setup(факты) и все остальные модули.
- используем
Пример последовательности:
# 1. Устанавливаем Python на все новые хосты
ansible new_hosts -m raw -a "apt-get update && apt-get install -y python3" --become
# 2. Проверяем готовность
ansible new_hosts -m ping
# 3. Запускаем обычный плейбук
ansible-playbook -i hosts site.yml
- Почему это важно понимать на глубоком уровне
- Ansible — безагентный, но не "без требований": Python — ключевой runtime для модулей.
- Для production-инфраструктуры:
- готовят стандартные образы (golden image) уже с установленным Python,
- или делают единый bootstrap-плейбук/роль для первых шагов.
- Корректное использование
raw:- минимизировать количество команд,
- делать проверки (
test -e ...) для идемпотентности, - не зашивать дистрибутив-специфичные команды без условий.
Итого:
Да, Ansible может установить Python на хост, где его нет. Делается это через raw (или аналогичные no-python механизмы) в bootstrap-шаге, без использования обычных модулей, а затем уже используется стандартный стек модулей поверх установленного Python. Это типичный продакшн-паттерн для подготовки "голых" серверов.
Вопрос 42. Какой у вас опыт работы с системами логирования и мониторинга (например, ELK/Elastic Stack)?
Таймкод: 00:52:09
Ответ собеседника: правильный. Отмечает наличие ELK в инфраструктуре, но минимальный практический опыт администрирования и настройки логирования/алертинга.
Правильный ответ:
Опыт работы с логированием и мониторингом в контексте распределённых сервисов на Go должен демонстрировать понимание:
- как лог собирается,
- как он структурируется,
- как индексируется и хранится,
- как по нему ищут, отлаживают и строят алерты,
- как всё это встраивается в CI/CD и эксплуатацию.
Ниже — обзор ключевых аспектов на уровне практики.
Основные компоненты Elastic Stack (ELK):
- Elasticsearch — хранилище и поисковый движок.
- Logstash — конвейер обработки логов (парсинг, фильтрация, обогащение).
- Kibana — визуализация, дашборды, поиск, алерты.
- Beats (Filebeat, Metricbeat и др.) — лёгкие агенты для доставки данных.
Типичный продакшн-пайплайн логов:
- Приложение логирует в stdout/файл в структурированном формате (JSON).
- Filebeat (или аналог) читает логи и отправляет:
- напрямую в Elasticsearch,
- или в Logstash для парсинга и обогащения.
- В Elasticsearch:
- настроены индексы, ILM (lifecycle: hot/warm/cold/delete),
- маппинг полей, ключевые поля проиндексированы.
- В Kibana:
- настроены дашборды, фильтры,
- алерты на основе поисковых запросов или метрик.
Практики для Go-сервисов (логирование):
- Структурированные логи
Для production-сервисов важно писать логи в структурированном виде, обычно JSON.
Пример с использованием стандартного logger + своей обёртки или популярной библиотеки:
package main
import (
"os"
"time"
"github.com/rs/zerolog"
)
func main() {
logger := zerolog.New(os.Stdout).With().
Str("service", "payments-api").
Timestamp().
Logger()
logger.Info().
Str("event", "startup").
Msg("service started")
logger.Error().
Err(errSome()).
Str("operation", "charge").
Str("user_id", "123").
Msg("failed to process payment")
}
func errSome() error {
return nil
}
Ключевые моменты:
- обязательные поля:
service,env,version,trace_id,span_id,request_id,user_id(если уместно),
- чтобы в Kibana можно было фильтровать:
- по сервису,
- по окружению,
- по trace/request id.
- Интеграция с Filebeat / Logstash
Пример Filebeat-конфига для Go-сервиса:
filebeat.inputs:
- type: log
paths:
- /var/log/myapp/*.log
json.keys_under_root: true
json.add_error_key: true
fields:
service: myapp
env: prod
fields_under_root: true
output.elasticsearch:
hosts: ["https://es-internal:9200"]
index: "logs-myapp-%{+yyyy.MM.dd}"
Идея:
- сразу парсим JSON на стороне Filebeat,
- добавляем поля
service,env, - отправляем в целевой индекс.
- Индексы, retention, производительность
Хороший опыт включает:
- проектирование index pattern-ов:
logs-<service>-yyyy.MM.ddили по env:logs-prod-*,logs-stage-*,
- использование:
- ILM (lifecycle management):
- hot (часто читаем/пишем),
- warm (редко читаем),
- cold (архив),
- delete (через N дней),
- ILM (lifecycle management):
- ограничения:
- size-based rollover (например, 30-50GB на индекс),
- количество шардов и реплик под нагрузку.
Это критично, чтобы:
- не убить Elasticsearch,
- иметь прогнозируемое время запросов в Kibana.
- Поиск и отладка в Kibana
Опытный разработчик:
- умеет быстро находить:
- все логи по trace_id,
- все ошибки конкретного сервиса за период,
- события вокруг инцидента (до и после).
- использует KQL/Lucene:
service: "payments-api" and level: "error" and trace_id: "abc123"
- Алертинг на базе логов
Подходы:
- Kibana Alerting:
- создаём rule, который срабатывает, если:
- количество записей с
level: errorпревышает порог, - встречается конкретный текст/тип ошибки,
- пропали heartbeat-сообщения.
- количество записей с
- создаём rule, который срабатывает, если:
- Интеграция с:
- Slack, Email, PagerDuty, Opsgenie.
Пример идеи алерта:
- "Если за 5 минут более 50 ошибок
timeoutвpayments-apiв проде" — вероятно, проблема с зависимым сервисом/DB.
- Мониторинг (метрики) рядом с логированием
Хорошая практика — дополнять логи метриками:
- использовать Prometheus + Grafana или Metricbeat + Elasticsearch/Kibana,
- ключевые метрики:
- RPS,
- latency (p95, p99),
- error rate,
- использование CPU/RAM,
- состояние пулов (DB, HTTP),
- длина очередей.
Пример для Go (Prometheus экспортер):
import "github.com/prometheus/client_golang/prometheus"
var (
httpRequests = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"path", "method", "code"},
)
)
func init() {
prometheus.MustRegister(httpRequests)
}
Логи + метрики дают:
- быстрый детект: "что-то не так",
- детализацию: "где именно и с чем".
- Зрелые практики:
- Корреляция логов и трассировок:
- использование OpenTelemetry: trace_id в логах, в APM, в Kibana.
- Единые стандарты логов по всем сервисам:
- уровни, формат, обязательные поля.
- Защита:
- маскирование чувствительных данных (PII, токены, пароли),
- контроль доступа к индексам.
Итого:
Зрелый опыт с ELK/логированием и мониторингом — это не только "видел Kibana", а понимание:
- как сервисы логируют (структурно),
- как эти логи попадают в централизованное хранилище,
- как проектировать индексы и retention,
- как по логам быстро расследовать инциденты и строить алерты,
- как связать логи, метрики и трассировки в единую наблюдаемость.
Вопрос 43. Почему вы рассматриваете смену работы и какие домены вам интересны?
Таймкод: 00:55:27
Ответ собеседника: правильный. Корректирует формулировку про "отсутствие развития"; говорит, что развитие возможно, но хочет сменить домен (с инфраструктуры автодорог) на более интересные ему области, такие как финтех или продуктовые компании.
Правильный ответ:
На такой вопрос важно отвечать зрело, без негатива в адрес текущего работодателя, показывая:
- осознанную мотивацию,
- долгосрочное мышление,
- интерес к домену и сложным задачам,
- отсутствие "токсичных" формулировок.
Хороший развернутый ответ может выглядеть так.
- Фокус на развитии, а не на жалобах
- Мне важно работать там, где:
- есть сложные технические задачи,
- высокие требования к надежности и производительности,
- практикуются инженерные практики: код-ревью, дизайн, тестирование, CI/CD, observability.
- На текущем месте я получил ценный опыт:
- в построении и поддержке сервисов,
- в работе с конкретной предметной областью,
- во взаимодействии с командой и продакшн-процессами.
- Сейчас основной мотив смены не в том, что "развития нет", а в том, что:
- стек задач и бизнес-домен перестали соответствовать моим долгосрочным интересам.
- Интерес к домену как к фактору качества работы
Важно показать, что домен для вас — не просто "модно / не модно", а:
- влияет на то, насколько глубоко вы хотите вовлекаться,
- определяет класс задач, сложность и требования.
Примеры доменов, которые логично упомянуть:
- Финтех:
- высокие требования к консистентности данных, отказоустойчивости, безопасным интеграциям;
- много задач по транзакционности, idempotency, reconciliation, работе с внешними API.
- Продуктовые компании:
- долгий жизненный цикл систем,
- ответственность за архитектуру и её эволюцию,
- возможность видеть влияние решений на метрики продукта.
- Highload / B2B платформы / API-first сервисы:
- большие объемы данных, пиковые нагрузки,
- строгие SLO, latency, доступность,
- сложные интеграции.
Формулировка может быть такой:
- "Я ищу среду, где техническая сложность и бизнес-требования стимулируют постоянный рост, и где можно погружаться в архитектуру, надежность и производительность систем."
- Связь с опытом и ценностью для нового работодателя
Ответ должен показывать, что:
- вы приходите не "от проблемы", а "к задачам":
- "Я хочу применить свой опыт в построении сервисов, автоматизации, работе с инфраструктурой и наблюдаемостью в более требовательном домене."
- вы понимаете специфику интересующего домена:
- финтех — про деньги, риски, регуляцию, аудит, безопасность, SLA;
- продукт — про метрики, эксперименты, качество UX и стабильность платформы.
- Чего лучше избегать
- Обвинений:
- "меня не развивают", "слабая команда", "старый стек" — без контекста звучит плохо.
- Неопределенности:
- "хочу что-то новое", "просто устал" — не даёт сигналов о зрелости.
- Агрессивной критики текущего работодателя.
Грамотный итоговый месседж:
- "На текущем месте я получил хороший опыт, но домен и профиль задач перестали совпадать с моими долгосрочными целями. Мне интересны области, где высокие требования к надежности, данным и архитектуре — например, финтех или продуктовые / высоконагруженные платформы. Я хочу работать там, где мой опыт и инженерный подход дают максимальную пользу и при этом позволяют мне дальше расти."
Вопрос 44. Какой у вас опыт разработки на Python и какие задачи вы решали?
Таймкод: 00:56:27
Ответ собеседника: правильный. Упоминает рабочие скрипты, ботов, проекты на Django; заявляет уверенное владение Python и алгоритмами.
Правильный ответ:
На этот вопрос важно отвечать так, чтобы показать не только факт "писал на Python", а понимание экосистемы, типичных задач и инженерных практик. Хороший ответ структурируется по нескольким направлениям:
- Типы задач, которые имеет смысл выделить
- Инфраструктурные и служебные скрипты:
- автоматизация рутины (деплой, миграции, обслуживание сервисов),
- интеграция с внешними API,
- генерация отчетов, парсинг данных (logs, csv, json, html).
- Web-разработка:
- Django / Flask / FastAPI:
- REST/GraphQL API,
- классические веб-приложения (админки, панели мониторинга),
- авторизация/аутентификация, работа с сессиями и токенами,
- работа с ORM, транзакциями, миграциями.
- Django / Flask / FastAPI:
- Интеграции и бэкенд-сервисы:
- сервисы, которые связывают несколько систем (CRM, платежные шлюзы, биллинг, внутренние API),
- очереди (Celery, RQ, Kafka consumers).
- Скрипты для DevOps/Data:
- упаковка/деплой приложений,
- утилиты для CI/CD,
- начальный ETL (выгрузка из БД, очистка, агрегация).
- Практики и подходы, которые стоит показать
Ключевой сигнал — Python используется инженерно, а не только "написал пару скриптов":
- Стиль и качество кода:
- использование
venv/Poetry/pip-tools для управления зависимостями, - структура проекта (packages, модули, entrypoints),
- использование
logging, а неprint, - typing (
typing,mypy) для критичных частей.
- использование
- Тестирование:
pytest, фикстуры, мокирование внешних сервисов,- unit-тесты для бизнес-логики, интеграционные для API.
- Производительность и надежность:
- понимание GIL и библиотек, использующих C-расширения,
- использование
asyncio/FastAPI/aioclient там, где нужно много IO, - профилирование и оптимизация "узких мест" при необходимости.
- Примеры задач (конкретика повышает доверие)
Пример инфраструктурного скрипта (автоматизация):
import subprocess
import sys
def deploy(service: str, version: str) -> None:
cmd = ["ansible-playbook", "deploy.yml", "-e", f"service={service}", "-e", f"version={version}"]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print("Deploy failed:", result.stderr, file=sys.stderr)
sys.exit(1)
print("Deploy ok:", result.stdout)
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: deploy.py <service> <version>", file=sys.stderr)
sys.exit(1)
deploy(sys.argv[1], sys.argv[2])
Пример простого API на FastAPI:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class PaymentRequest(BaseModel):
user_id: int
amount: float
@app.post("/payments")
async def create_payment(req: PaymentRequest):
if req.amount <= 0:
raise HTTPException(status_code=400, detail="Invalid amount")
# здесь могла бы быть логика записи в БД и вызова внешнего платежного шлюза
return {"status": "ok", "user_id": req.user_id, "amount": req.amount}
Это показывает:
- владение синтаксисом,
- базовое понимание валидации, ошибок, асинхронности.
- Связка Python-опыта с основным стеком
Если основная роль — Go/бэкенд:
- Python уместно позиционировать как:
- язык для:
- быстрого прототипирования,
- сервисных задач,
- glue-кода между системами,
- внутренних тулов и миграций.
- язык для:
- Важно подчеркнуть:
- вы не пишете "одноразовый скрипт в помойку",
- а применяете те же инженерные стандарты, что и к основным сервисам:
- читаемость, тесты, логирование, развертывание.
Хороший итоговый месседж:
- "Python использую как рабочий инструмент: писал боевые скрипты для автоматизации, интеграционные сервисы и небольшие веб-приложения на Django/FastAPI, есть опыт с ботами и внешними API. Уверенно владею языком, стандартной библиотекой и типичными библиотеками, применяю практики тестирования, логирования и типизации. Это позволяет быстро закрывать вспомогательные задачи и прототипировать решения рядом с основным стеком."
Вопрос 45. Как в Python-приложении получить значение переменной окружения (какую библиотеку нужно импортировать)?
Таймкод: 00:57:27
Ответ собеседника: неполный. Вспоминает про функцию getenv, но не называет модуль os, отмечает, что обычно такие вещи смотрит в документации.
Правильный ответ:
Для получения значения переменной окружения в Python стандартный и базовый способ — использовать модуль os из стандартной библиотеки.
Основные варианты:
- Через
os.getenv:
import os
db_url = os.getenv("DB_URL") # вернёт None, если переменной нет
Можно указать значение по умолчанию:
db_url = os.getenv("DB_URL", "postgres://user:pass@localhost:5432/db")
- Через
os.environ:
import os
db_url = os.environ["DB_URL"] # KeyError, если переменной нет
Этот способ полезен там, где отсутствие переменной — ошибка конфигурации, и вы хотите "упасть громко".
- Хорошая практика для продакшн-кода:
- Явно валидировать критичные переменные окружения при старте:
- чтобы не получить "тихое" некорректное поведение.
- Оборачивать чтение в небольшую функцию/модуль конфигурации.
Пример:
import os
def must_getenv(key: str) -> str:
value = os.getenv(key)
if not value:
raise RuntimeError(f"Env var {key} is required")
return value
DB_URL = must_getenv("DB_URL")
В контексте backend-разработки (и особенно при работе с Docker/Kubernetes, 12-factor apps) умение работать с переменными окружения через os — базовый минимальный стандарт.
Вопрос 46. Есть ли у вас опыт с PHP и готовы ли вы с ним работать?
Таймкод: 00:58:58
Ответ собеседника: правильный. Говорит, что прямого опыта с PHP нет, но как инженер готов работать с любым языком и стеком.
Правильный ответ:
На такой вопрос важно ответить честно, без попыток приписать себе несуществующий опыт, и при этом показать:
- способность быстро осваивать новые языки,
- понимание общих принципов backend-разработки,
- готовность поддерживать и развивать существующую систему, даже если стек не идеален.
Оптимальный развернутый ответ может выглядеть так.
- Честное описание текущего опыта
- "Коммерческого опыта с PHP у меня нет / минимальный."
- "Основной продакшн-опыт — с Go, Python, инфраструктурой и окружением вокруг сервисов."
- "При этом я хорошо понимаю общие backend-паттерны: HTTP, REST/gRPC, очереди, базы данных, кеши, безопасность, CI/CD, контейнеризация и т.д."
Это прозрачно и не вызывает недоверия.
- Готовность работать с PHP и существующим кодом
Важно явно показать, что стек вас не отпугивает:
- "Я нормально отношусь к PHP как к инструменту. Если продукт и задачи интересные — язык не является для меня блокирующим фактором."
- "Готов разбираться в существующем PHP-коде, фиксить баги, оптимизировать, покрывать тестами, постепенно улучшать архитектуру."
Зрелая позиция: язык — инструмент, важнее инженерные практики и качество решений.
- Как быстро закрыть пробел по PHP
Полезно описать подход к ускоренному онбордингу:
- "Для входа в проект на PHP я сделал бы следующее:
- изучил бы актуальную версию PHP (с акцентом на типизацию, ООП-модель, error handling),
- разобрался бы с используемым фреймворком (Laravel/Symfony/др.),
- посмотрел бы существующую кодовую базу: архитектуру, слои, подход к DI, валидации, работе с БД,
- настроил бы локальное окружение (Docker/Makefile), прогнал тесты, изучил pipeline."
- "Благодаря опыту с другими языками и системами я достаточно быстро переношу навыки: паттерны, тестирование, рефакторинг, observability."
Так вы показываете не абстрактное "быстро учусь", а конкретный план.
- Связь с DevOps/инфраструктурным опытом
Если в команде важен человек, который понимает и рантайм, и инфраструктуру:
- "Даже без глубокого PHP-опыта я могу:
- настроить окружение (PHP-FPM, Nginx, Docker, Kubernetes),
- оптимизировать конфигурации, логирование, метрики, трейсинг,
- обеспечить корректный деплой, zero-downtime релизы, катастрофоустойчивость."
- Это показывает ценность сверх самого языка.
- Чего избегать
- Не говорить: "PHP — плохой язык, не буду с ним работать" — это минус к гибкости.
- Не переоценивать опыт (например, "чуть-чуть покликал в туториале" выдавать за продакшн).
Хороший краткий месседж:
- "Коммерческого опыта с PHP у меня немного/нет, но я спокойно отношусь к этому стеку и готов в него войти. У меня сильный опыт в backend-разработке и инфраструктуре, я понимаю типичные паттерны веб-приложений и умею быстро адаптироваться к новому языку. Если задачи интересные и есть здравый инженерный процесс, PHP для меня не проблема, а вопрос нескольких недель на эффективное погружение."
Вопрос 47. Как вы относитесь к ненормированному графику (вечерние релизы, аварии ночью)?
Таймкод: 00:59:29
Ответ собеседника: правильный. Спокойно относится к редким аварийным ситуациям и вечерним задачам, считает, что постоянные переработки должны компенсироваться; приводит пример эпизодических ночных работ.
Правильный ответ:
Зрелый ответ на этот вопрос должен показать:
- понимание ответственности за продакшн,
- готовность участвовать в инцидентах,
- неприятие культуры бесконечных переработок,
- ориентацию на процессы, снижающие количество ночных аварий.
Ключевые моменты, которые стоит отразить.
- Нормальное отношение к редким инцидентам
- "Я нормально отношусь к редким вечерним релизам и ночным выездам по реальным авариям."
- "Поддержка продакшена — часть ответственности команды разработки/инженерной команды."
- "Готов участвовать в on-call ротации при прозрачных правилах."
Это показывает, что кандидат понимает реальность эксплуатации, а не живет в вакууме "только с 10 до 18".
- Неприятие постоянных необоснованных переработок
Важно четко очертить границу:
- "Регулярные переработки и постоянные ночные релизы считаю признаком организационных или технических проблем."
- "Если переработки становятся системой, это должно:
- а) быть осознанным решением,
- б) компенсироваться (деньгами или отгулами),
- в) сопровождаться планом, как выйти на устойчивый режим."
Такой ответ демонстрирует зрелый подход: уважение к себе, к команде и к процессам.
- Ожидания к процессам и культуре
Хорошо дополнительно показать инженерное мышление:
- "Я за процессы, которые минимизируют необходимость ночных вмешательств:
- автоматизированные тесты и CI/CD,
- blue-green / canary релизы,
- feature-флаги,
- четкие playbook'и на инциденты,
- мониторинг, алертинг с вменяемыми порогами."
- "Вечерние или ночные работы должны быть исключением:
- крупные миграции,
- критические security-fix,
- инциденты, влияющие на деньги/клиентов."
Это связывает отношение к графику с качеством инженерной практики.
- Формулировка для интервью
Собранный, сильный ответ может звучать так:
- "К редким вечерним релизам и аварийным подъемам отношусь спокойно — это часть ответственности за продакшн. Готов участвовать в on-call ротации при понятных правилах и компенсации. Но считаю нормой, когда команда стремится к устойчивому процессу без постоянных переработок, через автоматизацию, качественное тестирование и продуманное планирование. Если переработки становятся регулярными, это сигнал к тому, чтобы пересмотреть процессы, а не жить в режиме вечного пожаротушения."
Вопрос 48. Что вы вкладываете в понятие комфортных условий работы?
Таймкод: 01:01:13
Ответ собеседника: правильный. Под комфортом понимает адекватную команду, уважение личных границ, возможность договариваться и отсутствие токсичного давления.
Правильный ответ:
Сильный ответ показывает, что под "комфортом" понимаются не только плюшки, а зрелые рабочие процессы, понятные правила и здоровая культура. Важно связать это с эффективностью команды и качеством продукта.
Ключевые компоненты комфортных условий:
- Профессиональная и уважительная среда
- Взаимное уважение, отсутствие токсичности, личных наездов и пассивной агрессии.
- Обсуждение технических решений через аргументы, а не "авторитеты" и давление.
- Прозрачная и конструктивная обратная связь: можно спорить, можно ошибаться, можно признавать ошибки.
- Вменяемые ожидания и границы
- Понятный рабочий график, уважение личного времени.
- Ненормированный/он-call — по договоренности, с понятными правилами и компенсацией.
- Нет культуры "героизма 24/7" как нормы; если такое возникает, это повод менять процессы.
- Зрелые процессы разработки
Комфорт напрямую связан с тем, насколько хаотична работа:
- Наличие базового инженерного фундамента:
- Code review как инструмент качества и обмена знаниями, а не бюрократия.
- CI/CD: авто-тесты, линтеры, проверка стиля, быстрые фидбеки.
- Документация по ключевым сервисам и инфраструктуре.
- Планирование:
- адекватные оценки и дедлайны,
- отсутствие постоянных "горящих" задач из-за чьей-то хаотичности.
- Технические решения:
- возможность влиять на архитектуру и качество кода,
- разумный баланс между "сделать быстро" и "сделать правильно".
- Открытая коммуникация и возможность договариваться
- Возможность обсуждать нагрузку, приоритеты, конфликты и рабочие процессы без страха.
- Гибкость:
- договориться о формате работы (офис/remote/hybrid),
- смещать график под жизнь, если это не вредит команде и SLA.
- Прозрачность решений менеджмента:
- почему мы делаем приоритет X,
- какие бизнес-ограничения и риски.
- Условия для профессионального роста
- Доступ к сложным задачам, а не только "чинить мелочь".
- Поддержка инициатив:
- улучшить мониторинг,
- оптимизировать запросы в БД,
- зарефакторить критичный модуль.
- Обучение и обмен опытом:
- внутренние митапы, ревью, парное программирование,
- адекватное отношение к времени на улучшения и технический долг.
Хорошая формулировка на интервью:
- "Комфортные условия для меня — это адекватная команда и культура: уважение, отсутствие токсичности, возможность открыто обсуждать решения и проблемы. Плюс зрелые процессы разработки: нормальный график, прогнозируемые релизы, разумный on-call, CI/CD, code review. Важно, чтобы можно было договариваться и влиять на технические решения, а не жить в вечном пожаротушении. В такой среде я могу брать на себя ответственность за результат и стабильно выдавать качественный продукт."
