Initial commit: interview prep repo (resume, legend, stack cases, Kubernetes course)
This commit is contained in:
169
stack/kubernetes/CASE.md
Normal file
169
stack/kubernetes/CASE.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# Кейс: Kubernetes (домашний pet-кластер)
|
||||
|
||||
Если K8s незнаком — сначала пройти учебный курс с нуля [LEARNING.md](LEARNING.md), этот файл — компактный конспект результата для повторения перед собеседованием, без пошаговых объяснений «зачем».
|
||||
|
||||
Связь с легендой: [../../legend/LEGEND.md](../../legend/LEGEND.md) — раздел «Домашняя лаборатория / pet-проект». **Важно: K8s не входит в рабочий стек ни АО ТНИИС, ни ОКБ СУХОЙ** — это честный pet-проект поверх рабочего опыта с Docker/Ansible/мониторингом, а не часть легенды о работе в конкретной компании. На собеседовании так и позиционируется: «в проде на текущей работе K8s не используем, но поднимал себе дома кластер, чтобы разобраться» (этот же приём в разборе [ВТБ](../../interview/Bank%20VTB.md) сработал в плюс — см. [../../interview/real-interviews.md](../../interview/real-interviews.md)).
|
||||
|
||||
## Что нужно реально сделать (домашний стенд)
|
||||
|
||||
Локальный кластер через `kind` (Kubernetes IN Docker) — не требует отдельной виртуалки, поднимается поверх уже установленного Docker. Цель — своими руками пройти путь «манифест → под → сервис → доступ снаружи», задеплоить кусок стека мониторинга через Helm и прогнать диагностические команды, которые реально спрашивали на собеседованиях.
|
||||
|
||||
### 1. Установка и создание кластера
|
||||
|
||||
```bash
|
||||
# kind и kubectl (Windows, через choco; либо скачать бинарники с GitHub releases)
|
||||
choco install kind kubernetes-cli kubernetes-helm
|
||||
|
||||
# кластер из 1 control-plane + 2 worker-нод — чтобы было что рисовать при вопросе про архитектуру
|
||||
```
|
||||
|
||||
```yaml
|
||||
# kind-config.yaml
|
||||
kind: Cluster
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
nodes:
|
||||
- role: control-plane
|
||||
- role: worker
|
||||
- role: worker
|
||||
```
|
||||
|
||||
```bash
|
||||
kind create cluster --name lab --config kind-config.yaml
|
||||
kubectl cluster-info
|
||||
kubectl get nodes -o wide
|
||||
```
|
||||
|
||||
### 2. Деплой простого приложения: Deployment + Service + ConfigMap
|
||||
|
||||
```yaml
|
||||
# app.yaml
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: demo
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: app-config
|
||||
namespace: demo
|
||||
data:
|
||||
GREETING: "Привет из ConfigMap"
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: app-secret
|
||||
namespace: demo
|
||||
type: Opaque
|
||||
stringData:
|
||||
API_KEY: "lab-secret-value"
|
||||
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: demo-app
|
||||
namespace: demo
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: demo-app
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: demo-app
|
||||
spec:
|
||||
containers:
|
||||
- name: demo-app
|
||||
image: nginxdemos/hello:latest
|
||||
ports:
|
||||
- containerPort: 80
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: app-config
|
||||
- secretRef:
|
||||
name: app-secret
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: demo-app-svc
|
||||
namespace: demo
|
||||
spec:
|
||||
selector:
|
||||
app: demo-app
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
type: ClusterIP
|
||||
```
|
||||
|
||||
```bash
|
||||
kubectl apply -f app.yaml
|
||||
kubectl -n demo get pods -o wide
|
||||
kubectl -n demo rollout status deployment/demo-app
|
||||
```
|
||||
|
||||
### 3. Доступ снаружи: port-forward и Ingress
|
||||
|
||||
```bash
|
||||
# быстрый способ проверить сервис
|
||||
kubectl -n demo port-forward svc/demo-app-svc 8080:80
|
||||
curl http://localhost:8080
|
||||
```
|
||||
|
||||
Для полноценного Ingress — установить NGINX Ingress Controller (kind предоставляет готовый манифест под себя) и завести `Ingress`-ресурс с host-based роутингом — так на собеседовании про Реалист спрашивали про «два ЦОД под K8s с балансировщиком»: балансировщик снаружи (в лабе — сам kind/NGINX Ingress) → Service (ClusterIP, виртуальный балансировщик по подам через kube-proxy) → поды на worker-нодах.
|
||||
|
||||
### 4. Helm: разворачиваем kube-prometheus-stack
|
||||
|
||||
Практическая связка с уже готовым кейсом [../monitoring/CASE.md](../monitoring/CASE.md) — тот же принцип «Prometheus + Grafana», но теперь через Helm-чарт в кластере, а не через голый docker-compose.
|
||||
|
||||
```bash
|
||||
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
|
||||
helm repo update
|
||||
|
||||
helm install monitoring prometheus-community/kube-prometheus-stack \
|
||||
--namespace monitoring --create-namespace
|
||||
|
||||
kubectl -n monitoring get pods
|
||||
kubectl -n monitoring port-forward svc/monitoring-grafana 3000:80
|
||||
```
|
||||
|
||||
Это даёт прямой ответ на вопрос Bi.Zone «как задеплоишь мониторинг на 300 000 серверов»: не поднимать вручную, а Helm-чартом с overrides под масштаб (`values.yaml`: количество реплик, remote_write в Mimir/Thanos, ресурсы). См. цифры и архитектуру масштаба в [../../legend/CAPACITY.md](../../legend/CAPACITY.md).
|
||||
|
||||
### 5. Диагностика руками (то, что реально спрашивали «показать в терминале»)
|
||||
|
||||
```bash
|
||||
kubectl -n demo get pods
|
||||
kubectl -n demo describe pod <pod-name>
|
||||
kubectl -n demo logs <pod-name>
|
||||
kubectl -n demo logs <pod-name> --previous # логи упавшего предыдущего инстанса контейнера
|
||||
kubectl -n demo exec -it <pod-name> -- sh
|
||||
kubectl -n demo get events --sort-by=.lastTimestamp
|
||||
kubectl get daemonset -A # например, kube-proxy — daemonset на каждой ноде
|
||||
```
|
||||
|
||||
Специально сломать под (например, указать несуществующий образ в `image:`) и посмотреть на `ImagePullBackOff`/`CrashLoopBackOff` через `describe` — реальная практика диагностики, а не просто описание.
|
||||
|
||||
### 6. Разбор компонентов кластера (для вопроса «из чего состоит K8s»)
|
||||
|
||||
- **control-plane**: `kube-apiserver` (единая точка входа для всех запросов), `etcd` (хранилище состояния кластера, key-value), `kube-scheduler` (назначает поды на ноды), `kube-controller-manager` (следит, что желаемое состояние совпадает с реальным).
|
||||
- **worker-нода**: `kubelet` (агент, который управляет подами на ноде), `kube-proxy` (сетевые правила для Service), container runtime (`containerd`).
|
||||
- **etcd отдельно от control-plane или нет** — в проде для отказоустойчивости etcd часто выносят на отдельные ноды (нечётное количество, 3/5, из-за кворума), в маленьких/pet-инсталляциях (как kind) он живёт на той же control-plane ноде. В `kind` это видно напрямую: `kubectl get pods -n kube-system` покажет `etcd-lab-control-plane`.
|
||||
|
||||
## Что это даёт в разговоре с интервьюером
|
||||
|
||||
- Понимание базовых объектов: Pod, Deployment, Service, Namespace, ConfigMap, Secret, Ingress — и что каждый из них реально содержит (проверено `kubectl get -o yaml`).
|
||||
- Понимание разницы ConfigMap (несекретные конфиги) и Secret (base64-кодированные чувствительные данные — не шифрование, а кодирование; для реальной защиты нужны внешние решения типа Sealed Secrets/Vault, см. [../vault/CASE.md](../vault/CASE.md)).
|
||||
- Практика с Helm — установка чарта, `values.yaml`, `helm upgrade`.
|
||||
- Живой опыт диагностики через `kubectl describe`/`logs`/`events`.
|
||||
- Честная граница: это pet-кластер из 3 нод на своей машине, а не эксплуатация продового HA-кластера — расширение до multi-datacenter/etcd-кворума описывается на уровне понимания архитектуры, не личного опыта эксплуатации на этом масштабе.
|
||||
|
||||
## Как это ложится в легенду
|
||||
|
||||
K8s **не** приписывается к опыту в АО ТНИИС или ОКБ СУХОЙ — там его не было и в резюме он не должен появляться в блоках компаний. В «Ключевые навыки» резюме и в легенду он входит как отдельный, честно обозначенный pet-опыт: практическое знакомство с базовыми объектами и Helm поверх домашнего кластера, дополняющее рабочий опыт с Docker и Ansible.
|
||||
833
stack/kubernetes/LEARNING.md
Normal file
833
stack/kubernetes/LEARNING.md
Normal file
@@ -0,0 +1,833 @@
|
||||
# Учебный курс: Kubernetes с нуля
|
||||
|
||||
Это учебный документ, а не легенда — здесь нет утверждений «делал на работе». Цель: пройти путь от нуля до уверенного pet-уровня, объясняя себе на каждом шаге «что это и зачем», а не просто копируя команды. Итоговая позиция на собеседовании не меняется: K8s — честная домашняя практика поверх рабочего опыта с Docker/Ansible/мониторингом (см. [../../legend/LEGEND.md](../../legend/LEGEND.md) → «Домашняя лаборатория / pet-проект»).
|
||||
|
||||
**Как этим пользоваться:** проходить модули по порядку, не пропускать практику — каждый следующий модуль опирается на состояние кластера, оставленное предыдущим. Когда весь курс пройден руками один раз, для повторения перед конкретным собеседованием используй более компактный [CASE.md](CASE.md) — он про то же самое, но без объяснений, как шпаргалка. Вопросы с готовыми ответами — в [QUESTIONS.md](QUESTIONS.md).
|
||||
|
||||
**Формат каждого модуля:** зачем это → теория-минимум → практика руками → самопроверка → как это в реальных проектах.
|
||||
|
||||
**Окружение курса** (одно на все модули, то же, что в CASE.md): Windows + Docker Desktop, кластер `kind` с именем `lab`, 1 control-plane + 2 worker. Не пересоздавай кластер между модулями без необходимости — большинство модулей продолжают работать в одном и том же кластере и в одних и тех же namespace (`demo`, `monitoring`), это ближе к тому, как выглядит реальная работа с уже существующим кластером.
|
||||
|
||||
---
|
||||
|
||||
## Модуль 0. Установка инструментов
|
||||
|
||||
### Зачем это
|
||||
|
||||
Три разных инструмента решают три разные задачи, и важно не путать их роли: **Docker** — где физически крутятся контейнеры, **kind** — как за секунды поднять тестовый K8s-кластер поверх Docker без отдельных виртуалок, **kubectl** — как разговаривать с любым K8s-кластером (хоть pet, хоть прод), **Helm** — как ставить готовые наборы манифестов одной командой вместо ручного `kubectl apply` на десяток файлов.
|
||||
|
||||
### Теория-минимум
|
||||
|
||||
`kind` = Kubernetes IN Docker: каждая «нода» кластера — это на самом деле Docker-контейнер, внутри которого запущен полноценный K8s. Это не то, как выглядит прод (там ноды — реальные VM/железо), но API и поведение объектов идентичны — то, что выучено на kind, переносится на любой кластер.
|
||||
|
||||
### Практика руками
|
||||
|
||||
```bash
|
||||
# Docker Desktop должен быть уже установлен и запущен — это фундамент, без него ничего не работает
|
||||
|
||||
# Windows, через choco (одной командой ставит все три инструмента)
|
||||
choco install kind kubernetes-cli kubernetes-helm
|
||||
|
||||
# проверить, что каждый инструмент реально встал
|
||||
docker --version
|
||||
kind --version
|
||||
kubectl version --client
|
||||
helm version
|
||||
```
|
||||
|
||||
### Самопроверка
|
||||
|
||||
- Все четыре команды `--version` отвечают без ошибок.
|
||||
- Понимаешь своими словами разницу между Docker, kind, kubectl и Helm — если нет, перечитай «Зачем это» выше.
|
||||
|
||||
### Как это в реальных проектах
|
||||
|
||||
В проде kind не используют (это чисто локальный/CI инструмент для тестов) — реальные кластеры разворачивают managed-сервисами облаков (EKS/GKE/managed K8s) или `kubeadm`/аналогами on-premise. kubectl и Helm — те же самые, что и в pet-кластере, это и есть смысл разработки на kind: инструменты один в один.
|
||||
|
||||
---
|
||||
|
||||
## Модуль 1. Зачем вообще Kubernetes
|
||||
|
||||
### Зачем это
|
||||
|
||||
Прежде чем учить объекты K8s, важно понимать проблему, которую он решает — иначе все дальнейшие абстракции выглядят как искусственная сложность ради сложности.
|
||||
|
||||
### Теория-минимум
|
||||
|
||||
Без оркестратора эксплуатация выглядит так: `docker run` руками на конкретной VM, и если контейнер упал — никто, кроме человека или самопального скрипта, его не перезапустит; если VM легла — сервис просто недоступен, пока кто-то не отреагирует. K8s переворачивает модель: администратор описывает **desired state** («хочу 3 реплики этого образа, слушающие порт 8080») декларативно в YAML, а control-plane кластера постоянно сверяет фактическое состояние с желаемым и сам исправляет расхождение — это и называется **self-healing**. Ключевая идея: не «выполни команду один раз», а «поддерживай вот такое состояние постоянно».
|
||||
|
||||
### Практика руками
|
||||
|
||||
```bash
|
||||
kind create cluster --name lab
|
||||
kubectl run demo --image=nginxdemos/hello --restart=Always
|
||||
kubectl get pods
|
||||
# демо self-healing: убиваем под руками
|
||||
kubectl delete pod demo
|
||||
kubectl get pods
|
||||
# NAME ... — под пересоздан автоматически (другой RESTARTS/AGE), потому что desired state требует, чтобы под существовал
|
||||
```
|
||||
|
||||
### Самопроверка
|
||||
|
||||
- Своими словами: что такое desired state и чем декларативный подход отличается от «выполнить команду»?
|
||||
- Почему после `kubectl delete pod demo` под появился снова?
|
||||
|
||||
*(Забегая вперёд: на самом деле голый `kubectl run` пересоздаётся не всегда — это будет явно разобрано в модуле 4 про Deployment. Здесь цель — просто увидеть идею self-healing на практике.)*
|
||||
|
||||
### Как это в реальных проектах
|
||||
|
||||
Эта идея — причина, почему K8s вообще существует: чем больше сервисов и нод, тем дороже человеку вручную следить за их состоянием. В резюме и легенде это прямая параллель с системами вроде systemd (`Restart=always` — тот же self-healing, но на уровне одной VM, не кластера) — see [../linux-bash/CASE.md](../linux-bash/CASE.md).
|
||||
|
||||
---
|
||||
|
||||
## Модуль 2. Архитектура кластера
|
||||
|
||||
### Зачем это
|
||||
|
||||
Вопрос «из чего состоит Kubernetes» — один из самых частых на собеседованиях (см. [QUESTIONS.md](QUESTIONS.md)). Нельзя понимать объекты (Pod, Service и т.д.), не понимая, кто внутри кластера отвечает за их существование.
|
||||
|
||||
### Теория-минимум
|
||||
|
||||
Кластер делится на **control-plane** (мозг) и **worker-ноды** (мышцы, где реально крутятся приложения):
|
||||
|
||||
- `kube-apiserver` — единственная точка входа для всех запросов (в том числе от `kubectl`); всё остальное общается только через него, напрямую в обход API никто ничего не меняет.
|
||||
- `etcd` — key-value хранилище, единственный источник правды о состоянии кластера (все объекты, их спеки и статусы).
|
||||
- `kube-scheduler` — решает, на какую worker-ноду поставить новый под (по ресурсам, тегам, ограничениям).
|
||||
- `kube-controller-manager` — набор контроллеров, каждый из которых следит за своим типом объектов и подгоняет реальность к желаемому состоянию (это и есть механизм self-healing из модуля 1).
|
||||
- `kubelet` (на каждой worker-ноде) — агент, который реально запускает/останавливает контейнеры на своей ноде через container runtime.
|
||||
- `kube-proxy` (на каждой worker-ноде) — настраивает сетевые правила, чтобы трафик до Service доходил до нужных подов.
|
||||
- `containerd` — сам container runtime, который непосредственно тянет образы и стартует контейнеры (аналог Docker engine, но легче).
|
||||
|
||||
### Практика руками
|
||||
|
||||
```bash
|
||||
# пересоздаём кластер с явной топологией — 1 control-plane + 2 worker, чтобы было что показать на схеме
|
||||
cat > kind-config.yaml << 'EOF'
|
||||
kind: Cluster
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
nodes:
|
||||
- role: control-plane
|
||||
- role: worker
|
||||
- role: worker
|
||||
EOF
|
||||
|
||||
kind delete cluster --name lab
|
||||
kind create cluster --name lab --config kind-config.yaml
|
||||
kubectl cluster-info
|
||||
kubectl get nodes -o wide
|
||||
|
||||
# все компоненты control-plane живут как поды в системном namespace kube-system
|
||||
kubectl get pods -n kube-system -o wide
|
||||
```
|
||||
|
||||
Найди в выводе `kube-apiserver-lab-control-plane`, `etcd-lab-control-plane`, `kube-scheduler-lab-control-plane`, `kube-controller-manager-lab-control-plane` — все живьём, не абстракция. `kube-proxy` увидишь как DaemonSet (по одному поду на каждую ноду — забегая вперёд к модулю 9 про DaemonSet).
|
||||
|
||||
### Самопроверка
|
||||
|
||||
- Назвать вслух 4 компонента control-plane и 2 компонента worker-ноды, объяснить роль каждого без подсматривания.
|
||||
- Почему `etcd` в pet-кластере находится на той же ноде, что и control-plane, а в проде его часто выносят отдельно? (Кворум при отказоустойчивости — нечётное число нод: 3 или 5, при потере части живой кворум продолжает работать, если жива больше половины.)
|
||||
|
||||
### Как это в реальных проектах
|
||||
|
||||
В managed-кластерах (EKS/GKE) весь control-plane скрыт от пользователя облаком — вы видите только worker-ноды. В on-premise-кластерах (ближе к тому, что было бы в закрытом контуре без облаков) control-plane разворачивается и обслуживается вручную или через `kubeadm`, и вопрос про размещение `etcd` становится реальным архитектурным решением, а не абстракцией.
|
||||
|
||||
---
|
||||
|
||||
## Модуль 3. Pod и kubectl-база
|
||||
|
||||
### Зачем это
|
||||
|
||||
Pod — минимальная единица деплоя в K8s. Нельзя запустить «просто контейнер» напрямую через API K8s — всегда через Pod. Здесь же нарабатывается базовый набор команд `kubectl`, которым пользуешься постоянно.
|
||||
|
||||
### Теория-минимум
|
||||
|
||||
Pod — это один или несколько контейнеров, которые всегда живут и умирают вместе, на одной ноде, с общей сетью (localhost между контейнерами внутри пода) и опционально общими volume. В большинстве случаев в поде один контейнер — многоконтейнерные поды (sidecar-паттерн) нужны реже, когда вспомогательный процесс должен жить бок о бок с основным (например, агент логирования).
|
||||
|
||||
Анатомия любого манифеста K8s — четыре ключа верхнего уровня:
|
||||
- `apiVersion` — версия API, к которой относится объект (у Pod это `v1`, у более сложных объектов часто `apps/v1` и т.д.).
|
||||
- `kind` — тип объекта (`Pod`, `Deployment`, `Service`...).
|
||||
- `metadata` — имя, namespace, labels, аннотации — «паспорт» объекта.
|
||||
- `spec` — собственно желаемое состояние: что именно нужно запустить.
|
||||
|
||||
### Практика руками
|
||||
|
||||
```yaml
|
||||
# pod.yaml
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: demo
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: demo-pod
|
||||
namespace: demo
|
||||
labels:
|
||||
app: demo
|
||||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: nginxdemos/hello:latest
|
||||
ports:
|
||||
- containerPort: 80
|
||||
```
|
||||
|
||||
```bash
|
||||
kubectl apply -f pod.yaml
|
||||
|
||||
kubectl -n demo get pods # список подов
|
||||
kubectl -n demo get pods -o wide # + на какой ноде, IP пода
|
||||
kubectl -n demo describe pod demo-pod # полная инфа: события, статус, причина проблем
|
||||
kubectl -n demo logs demo-pod # логи контейнера
|
||||
kubectl -n demo exec -it demo-pod -- sh # зайти внутрь контейнера интерактивно
|
||||
kubectl -n demo get pod demo-pod -o yaml # реальный полный манифест объекта (K8s дополнил своими полями)
|
||||
|
||||
kubectl -n demo delete pod demo-pod # удалить
|
||||
```
|
||||
|
||||
### Самопроверка
|
||||
|
||||
- Из четырёх ключей манифеста назвать, какой отвечает за «что мы хотим получить», а какой — за «как объект называется и где живёт».
|
||||
- Чем `logs` отличается от `describe`? (logs — вывод приложения; describe — метаданные/события/статус от самого K8s, годится для диагностики, почему под не стартует).
|
||||
- Что покажет `kubectl -n demo get pod demo-pod -o yaml` такого, чего нет в исходном `pod.yaml`? (K8s подставляет статус, IP, дополнительные системные поля — разница между тем, что ты запросил, и тем, что реально существует).
|
||||
|
||||
### Как это в реальных проектах
|
||||
|
||||
Голые Pod'ы в проде почти никогда не создают напрямую (кроме короткоживущих задач) — если под упадёт, его никто не пересоздаст, потому что за него никто не отвечает (в отличие от модуля 1, где `kubectl run` создавал под через неявный контроллер). Правильный способ — через Deployment, это модуль 4.
|
||||
|
||||
---
|
||||
|
||||
## Модуль 4. Deployment и ReplicaSet
|
||||
|
||||
### Зачем это
|
||||
|
||||
Это самый частый способ запускать stateless-приложения в K8s — и то, что даёт настоящий self-healing и rolling-обновления без даунтайма.
|
||||
|
||||
### Теория-минимум
|
||||
|
||||
**Deployment** управляет **ReplicaSet**, а ReplicaSet управляет подами — трёхуровневая иерархия. Deployment описывает: сколько реплик (`replicas`), как найти «свои» поды (`selector`), и шаблон, из которого их создавать (`template`, внутри — точно такой же `spec`, как у голого Pod). Если поменять образ в Deployment, он не убивает все поды разом — создаёт новый ReplicaSet и постепенно переключает трафик со старого на новый (rolling update), это и есть обновление без даунтайма.
|
||||
|
||||
### Практика руками
|
||||
|
||||
```yaml
|
||||
# deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: demo-app
|
||||
namespace: demo
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: demo-app
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: demo-app
|
||||
spec:
|
||||
containers:
|
||||
- name: demo-app
|
||||
image: nginxdemos/hello:latest
|
||||
ports:
|
||||
- containerPort: 80
|
||||
```
|
||||
|
||||
```bash
|
||||
kubectl apply -f deployment.yaml
|
||||
kubectl -n demo get deployment demo-app
|
||||
kubectl -n demo get replicaset # Deployment создал ReplicaSet
|
||||
kubectl -n demo get pods -o wide # ReplicaSet создал 3 пода
|
||||
|
||||
# масштабирование
|
||||
kubectl -n demo scale deployment demo-app --replicas=5
|
||||
kubectl -n demo get pods
|
||||
|
||||
# self-healing по-настоящему: убиваем один под
|
||||
kubectl -n demo delete pod <имя-любого-пода>
|
||||
kubectl -n demo get pods # ReplicaSet тут же пересоздал под — реплик снова 5
|
||||
|
||||
# rolling update
|
||||
kubectl -n demo set image deployment/demo-app demo-app=nginxdemos/hello:plain-text
|
||||
kubectl -n demo rollout status deployment/demo-app
|
||||
kubectl -n demo rollout history deployment/demo-app
|
||||
|
||||
# откат, если что-то пошло не так
|
||||
kubectl -n demo rollout undo deployment/demo-app
|
||||
```
|
||||
|
||||
### Самопроверка
|
||||
|
||||
- Нарисовать иерархию Deployment → ReplicaSet → Pod своими словами.
|
||||
- Что произойдёт с количеством ReplicaSet после `set image`? (появится новый ReplicaSet под новую версию, старый останется с 0 репликами — это и даёт `rollout undo`).
|
||||
- Чем rolling update отличается от простого «убить все старые поды и поднять новые»? (нет даунтайма — старые поды продолжают обслуживать трафик, пока новые не станут готовы).
|
||||
|
||||
### Как это в реальных проектах
|
||||
|
||||
Deployment — рабочая лошадка для 90% приложений в проде (stateless-сервисы). Rolling update с постепенным переключением — прямая параллель с тем, зачем в принципе нужен `readiness probe` (модуль 8): K8s не переключает трафик на новый под, пока тот не сообщит о готовности.
|
||||
|
||||
---
|
||||
|
||||
## Модуль 5. Service и сеть
|
||||
|
||||
### Зачем это
|
||||
|
||||
У подов IP-адреса нестабильны (под пересоздали — IP другой). Нужен стабильный способ достучаться до набора подов, не завязываясь на их конкретные IP — это и есть Service.
|
||||
|
||||
### Теория-минимум
|
||||
|
||||
**Service** — это стабильный виртуальный IP + DNS-имя, стоящее перед набором подов, отобранных по `selector` (по тем же labels, что использует Deployment). Три основных типа:
|
||||
- `ClusterIP` (по умолчанию) — доступен только внутри кластера, для внутреннего взаимодействия сервисов.
|
||||
- `NodePort` — открывает порт на каждой worker-ноде наружу кластера, для простого внешнего доступа без облачного балансировщика.
|
||||
- `LoadBalancer` — просит облако выдать внешний балансировщик (в pet-кластере kind не работает без эмуляции, поэтому для локального внешнего доступа используем `port-forward` или Ingress из модуля 6).
|
||||
|
||||
Внутри кластера любой под может обратиться к Service по DNS-имени вида `<service>.<namespace>.svc.cluster.local` (или просто `<service>`, если в том же namespace) — `kube-proxy` на каждой ноде транслирует это имя в конкретный IP пода по правилам iptables/IPVS, с балансировкой между всеми подходящими подами.
|
||||
|
||||
### Практика руками
|
||||
|
||||
```yaml
|
||||
# service.yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: demo-app-svc
|
||||
namespace: demo
|
||||
spec:
|
||||
selector:
|
||||
app: demo-app
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
type: ClusterIP
|
||||
```
|
||||
|
||||
```bash
|
||||
kubectl apply -f service.yaml
|
||||
kubectl -n demo get svc demo-app-svc
|
||||
kubectl -n demo get endpoints demo-app-svc # реальные IP подов, на которые сейчас указывает Service
|
||||
|
||||
# проверка снаружи через port-forward
|
||||
kubectl -n demo port-forward svc/demo-app-svc 8080:80
|
||||
curl http://localhost:8080 # в отдельном терминале
|
||||
|
||||
# проверка балансировки изнутри кластера — заходим в один под и стучимся в Service несколько раз
|
||||
kubectl -n demo run curl-test --image=curlimages/curl -it --rm -- sh
|
||||
# внутри: curl demo-app-svc.demo.svc.cluster.local, повторить несколько раз
|
||||
```
|
||||
|
||||
### Самопроверка
|
||||
|
||||
- Почему нельзя просто обращаться напрямую к IP пода в проде? (IP меняется при каждом пересоздании пода — Service даёт стабильную точку входа).
|
||||
- Чем `endpoints` Service отличаются от самого Service? (endpoints — динамический список реальных IP подходящих подов, обновляется автоматически при масштабировании/пересоздании).
|
||||
- В чём разница ClusterIP / NodePort / LoadBalancer одним предложением каждая.
|
||||
|
||||
### Как это в реальных проектах
|
||||
|
||||
Это ровно то, что подразумевается под вопросом «как работает трафик в K8s» (см. [QUESTIONS.md](QUESTIONS.md)): Service + kube-proxy — обязательный слой между внешним балансировщиком/Ingress и конкретными подами, в любой архитектуре, от pet-кластера до мультидатацентрового прода.
|
||||
|
||||
---
|
||||
|
||||
## Модуль 6. Ingress
|
||||
|
||||
### Зачем это
|
||||
|
||||
NodePort/port-forward годятся для лабы, но не масштабируются на реальный внешний трафик с доменными именами и путями (`/api`, `/app` на одном IP). Ingress — стандартный способ завести весь HTTP(S)-трафик в кластер через один вход с роутингом по host/path.
|
||||
|
||||
### Теория-минимум
|
||||
|
||||
**Ingress** — это правило роутинга (host/path → Service), но само по себе оно ничего не делает — нужен **Ingress Controller** (отдельное приложение внутри кластера, например NGINX Ingress Controller), которое читает Ingress-ресурсы и реально проксирует трафик. Полная цепочка запроса: клиент → Ingress Controller (по внешнему IP/порту) → на основе host/path выбирает нужный Service → Service выбирает под по `selector` → под отвечает.
|
||||
|
||||
### Практика руками
|
||||
|
||||
```bash
|
||||
# kind поставляется с готовым манифестом NGINX Ingress Controller под себя
|
||||
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
|
||||
kubectl -n ingress-nginx wait --for=condition=ready pod --selector=app.kubernetes.io/component=controller --timeout=120s
|
||||
```
|
||||
|
||||
```yaml
|
||||
# ingress.yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: demo-ingress
|
||||
namespace: demo
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
- host: demo.local
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: demo-app-svc
|
||||
port:
|
||||
number: 80
|
||||
```
|
||||
|
||||
```bash
|
||||
kubectl apply -f ingress.yaml
|
||||
kubectl -n demo get ingress
|
||||
|
||||
# добавить demo.local в hosts-файл (Windows: C:\Windows\System32\drivers\etc\hosts) → 127.0.0.1
|
||||
curl http://demo.local
|
||||
```
|
||||
|
||||
### Самопроверка
|
||||
|
||||
- Своими словами: чем Ingress отличается от Ingress Controller? (Ingress — декларативное правило-манифест; Controller — программа, которая это правило исполняет).
|
||||
- Нарисовать полную цепочку «клиент → под» через Ingress, включая все промежуточные звенья.
|
||||
|
||||
### Как это в реальных проектах
|
||||
|
||||
Это прямой ответ на реальный вопрос с собеседования «нарисуй два ЦОД под K8s с балансировщиком» (Реалист банк, см. [QUESTIONS.md](QUESTIONS.md)): внешний L4/L7-балансировщик перед ЦОДами → внутри каждого ЦОД Ingress Controller/Service → поды. Личная параллель с рабочим опытом — Nginx как балансировщик перед сервисами (см. [../nginx/CASE.md](../nginx/CASE.md), [../../legend/STORY.md](../../legend/STORY.md) → nginx.conf для x3 Mimir): Ingress Controller здесь и есть, по сути, Nginx, только управляемый декларативно через K8s-объекты, а не ручным редактированием `nginx.conf`.
|
||||
|
||||
---
|
||||
|
||||
## Модуль 7. ConfigMap и Secret
|
||||
|
||||
### Зачем это
|
||||
|
||||
Образ контейнера должен быть одинаковым во всех окружениях (dev/stage/prod) — конфигурация, которая между окружениями отличается, не должна быть зашита в образ. ConfigMap и Secret — стандартный способ передать конфиг снаружи образа.
|
||||
|
||||
### Теория-минимум
|
||||
|
||||
**ConfigMap** — для несекретных данных (URL, флаги, конфиг-файлы), **Secret** — для чувствительных значений (пароли, токены, ключи). Оба можно подключить к поду двумя способами: как переменные окружения (`envFrom`/`env`) или как смонтированный файл (`volumeMounts`). Важный технический нюанс: Secret по умолчанию хранит значения в **base64 — это кодирование, а не шифрование**. Любой, у кого есть доступ к API/etcd, может декодировать значение одной командой — реальная защита требует либо шифрования etcd at rest, либо внешних систем вроде Vault (см. [../vault/CASE.md](../vault/CASE.md)) или Sealed Secrets.
|
||||
|
||||
### Практика руками
|
||||
|
||||
```yaml
|
||||
# config-secret.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: app-config
|
||||
namespace: demo
|
||||
data:
|
||||
GREETING: "Привет из ConfigMap"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: app-secret
|
||||
namespace: demo
|
||||
type: Opaque
|
||||
stringData:
|
||||
API_KEY: "lab-secret-value"
|
||||
```
|
||||
|
||||
Подключаем к Deployment из модуля 4 через `envFrom`:
|
||||
|
||||
```yaml
|
||||
# добавить в spec.template.spec.containers[0] demo-app из deployment.yaml
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: app-config
|
||||
- secretRef:
|
||||
name: app-secret
|
||||
```
|
||||
|
||||
```bash
|
||||
kubectl apply -f config-secret.yaml
|
||||
kubectl apply -f deployment.yaml # обновлённый с envFrom
|
||||
|
||||
# проверка, что переменные реально попали в под
|
||||
kubectl -n demo exec -it <имя-пода> -- env | grep -E "GREETING|API_KEY"
|
||||
|
||||
# ключевая демонстрация: base64 — не шифрование
|
||||
kubectl -n demo get secret app-secret -o yaml
|
||||
# скопировать значение data.API_KEY и декодировать:
|
||||
echo "<base64-значение>" | base64 -d
|
||||
# получится исходный "lab-secret-value" — открытым текстом
|
||||
```
|
||||
|
||||
### Самопроверка
|
||||
|
||||
- Почему нельзя просто зашить конфиг в Dockerfile/образ? (тогда под каждое окружение нужен свой образ — теряется смысл «один образ везде»).
|
||||
- Объяснить вслух, почему `kubectl get secret -o yaml` + `base64 -d` — не взлом, а штатная возможность любого, у кого есть доступ к API.
|
||||
|
||||
### Как это в реальных проектах
|
||||
|
||||
Прямая параллель с рабочим опытом: `ansible-vault` шифрует секреты в Ansible-ролях (см. [../ansible/CASE.md](../ansible/CASE.md)) примерно с той же целью, ради которой в проде K8s-секреты часто заводят не как голый `Secret`, а через внешний Vault-интеграцию (модуль pet — [../vault/CASE.md](../vault/CASE.md)) или `Sealed Secrets`, чтобы решить проблему base64 ≠ шифрование.
|
||||
|
||||
---
|
||||
|
||||
## Модуль 8. Probes и ресурсы
|
||||
|
||||
### Зачем это
|
||||
|
||||
K8s не знает, «готово» ли приложение внутри контейнера, просто по факту, что процесс запущен — процесс может быть жив, но зависшим, или ещё прогревающимся. Probes дают K8s способ реально спросить приложение о его состоянии.
|
||||
|
||||
### Теория-минимум
|
||||
|
||||
Три вида проверок:
|
||||
- **livenessProbe** — «жив ли процесс вообще?» Если проверка не проходит несколько раз подряд — K8s убивает и пересоздаёт контейнер.
|
||||
- **readinessProbe** — «готов ли принимать трафик прямо сейчас?» Если не проходит — под не убивают, но Service временно перестаёт слать в него трафик (именно это делает rolling update из модуля 4 безопасным).
|
||||
- **startupProbe** — для медленно стартующих приложений: пока не пройдёт, liveness/readiness не проверяются (чтобы долгий старт не приняли за зависание).
|
||||
|
||||
**Ресурсы** (`requests`/`limits`): `requests` — сколько CPU/RAM гарантированно резервируется под контейнер (scheduler использует это число, чтобы решить, на какую ноду его поставить), `limits` — потолок, выше которого контейнер не может подняться (превышение по памяти → **OOMKilled**, превышение по CPU → троттлинг, не убийство). Комбинация requests/limits определяет **QoS-класс** пода (`Guaranteed`/`Burstable`/`BestEffort`) — от него зависит, что убьют первым при нехватке ресурсов на ноде.
|
||||
|
||||
### Практика руками
|
||||
|
||||
```yaml
|
||||
# probes.yaml — добавить в spec.template.spec.containers[0] demo-app
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 5
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "64Mi"
|
||||
limits:
|
||||
cpu: "250m"
|
||||
memory: "128Mi"
|
||||
```
|
||||
|
||||
```bash
|
||||
kubectl apply -f deployment.yaml
|
||||
kubectl -n demo describe pod <имя-пода> # секция Liveness/Readiness видна прямо в describe
|
||||
|
||||
# демо OOMKilled: временно поставить memory limit заведомо ниже, чем нужно приложению
|
||||
# (например memory: "8Mi"), применить, посмотреть:
|
||||
kubectl -n demo get pods
|
||||
kubectl -n demo describe pod <имя-пода> # Last State: Terminated, Reason: OOMKilled
|
||||
```
|
||||
|
||||
### Самопроверка
|
||||
|
||||
- В чём разница между liveness и readiness одним предложением каждая — и что K8s делает в каждом случае при провале проверки.
|
||||
- Что произойдёт, если задать `limits.memory` меньше, чем реально требуется приложению? (контейнер убьют по OOM, и он уйдёт в CrashLoopBackOff при повторных попытках — мост к модулю 9).
|
||||
|
||||
### Как это в реальных проектах
|
||||
|
||||
Правильно настроенные probes — то, что отличает «работающий на бумаге» деплой от реально безопасного rolling update: без readinessProbe K8s может начать слать трафик на под, который ещё не прогрелся, и получить ошибки у пользователей в момент релиза.
|
||||
|
||||
---
|
||||
|
||||
## Модуль 9. Диагностика поломок
|
||||
|
||||
### Зачем это
|
||||
|
||||
Это самый практический навык для собеседования: «покажи, как будешь диагностировать» — частый формат реальных вопросов (см. [QUESTIONS.md](QUESTIONS.md)). Здесь специально ломаем всё, что можно сломать, чтобы увидеть диагностику вживую, а не в теории.
|
||||
|
||||
### Теория-минимум
|
||||
|
||||
Три частых состояния поломанного пода:
|
||||
- **ImagePullBackOff** — K8s не может скачать указанный образ (опечатка в имени, несуществующий тег, нет доступа к registry).
|
||||
- **CrashLoopBackOff** — контейнер стартует и тут же падает, K8s пытается перезапустить снова и снова с растущей паузой между попытками.
|
||||
- **Pending** — под не может быть запланирован ни на одну ноду (не хватает ресурсов под `requests`, или не подходит ни одна нода по ограничениям).
|
||||
|
||||
### Практика руками
|
||||
|
||||
```bash
|
||||
# 1. ImagePullBackOff — несуществующий образ
|
||||
kubectl -n demo run broken1 --image=nginxdemos/this-image-does-not-exist:latest
|
||||
kubectl -n demo get pods
|
||||
kubectl -n demo describe pod broken1 # Events внизу прямо назовут причину
|
||||
|
||||
# 2. CrashLoopBackOff — команда, которая тут же завершается с ошибкой
|
||||
kubectl -n demo run broken2 --image=busybox --restart=Always -- sh -c "exit 1"
|
||||
kubectl -n demo get pods -w # смотрим, как RESTARTS растёт
|
||||
kubectl -n demo logs broken2 --previous # логи упавшего перед текущим рестартом контейнера
|
||||
kubectl -n demo describe pod broken2
|
||||
|
||||
# 3. Pending — запросить заведомо нереальные ресурсы
|
||||
kubectl -n demo run broken3 --image=nginx --requests='cpu=100,memory=100Gi' --restart=Always
|
||||
kubectl -n demo get pods # STATUS: Pending
|
||||
kubectl -n demo describe pod broken3 # Events: "0/3 nodes are available: insufficient cpu/memory"
|
||||
|
||||
# общая хронология по namespace — полезно, когда непонятно, с чего начать
|
||||
kubectl -n demo get events --sort-by=.lastTimestamp
|
||||
|
||||
# уборка после экспериментов
|
||||
kubectl -n demo delete pod broken1 broken2 broken3
|
||||
```
|
||||
|
||||
### Самопроверка
|
||||
|
||||
Чек-лист «под не работает — что смотреть по порядку» (проговорить вслух на реальном примере из практики выше):
|
||||
1. `kubectl get pods` — какой статус? (ImagePullBackOff / CrashLoopBackOff / Pending / Running-но-не-Ready).
|
||||
2. `kubectl describe pod` — секция Events внизу почти всегда прямо называет причину.
|
||||
3. Если контейнер стартовал и упал — `kubectl logs <pod> --previous`, посмотреть, что вывело само приложение перед падением.
|
||||
4. Если Pending — смотреть Events на предмет нехватки ресурсов или несовпадения ограничений с нодами.
|
||||
5. Если ничего не понятно — `kubectl get events --sort-by=.lastTimestamp` по всему namespace, искать связанные события.
|
||||
|
||||
### Как это в реальных проектах
|
||||
|
||||
Это прямая параллель с рабочим инцидент-флоу из [../../legend/STORY.md](../../legend/STORY.md) → «Как решается инцидент»: там формула «дашборд → логи (Kibana) → диагностика (systemctl/journalctl) → рестарт/эскалация», здесь та же логика, но инструмент — `kubectl describe`/`logs`/`events` вместо `systemctl`/`journalctl`. Смысл вопроса на собеседовании «как будешь диагностировать под» — не проверить знание конкретной команды, а проверить, есть ли системный порядок действий.
|
||||
|
||||
---
|
||||
|
||||
## Модуль 10. Хранилище: PV, PVC и StatefulSet
|
||||
|
||||
### Зачем это
|
||||
|
||||
Поды по умолчанию **эфемерны** — при пересоздании пода локальные данные внутри контейнера теряются. Для приложений с состоянием (базы данных, очереди) это неприемлемо, и K8s даёт отдельный слой абстракций для постоянного хранилища.
|
||||
|
||||
### Теория-минимум
|
||||
|
||||
- `emptyDir` — временный volume, живущий столько же, сколько под (переживает рестарт контейнера внутри пода, но не пересоздание самого пода) — полезен для промежуточных данных между контейнерами одного пода.
|
||||
- `PersistentVolume` (PV) — реальный кусок хранилища в кластере (диск/NFS/облачный volume), `PersistentVolumeClaim` (PVC) — запрос пода «дай мне столько-то места», который K8s сопоставляет с подходящим PV. `StorageClass` — шаблон, по которому PV создаются автоматически по запросу PVC (динамическое провижининг вместо ручного создания PV заранее).
|
||||
- **StatefulSet** vs **Deployment**: Deployment создаёт взаимозаменяемые поды с случайными именами и общим PVC (или без него) — годится для stateless. StatefulSet даёт каждому поду стабильное имя (`app-0`, `app-1`, ...) и **свой собственный** PVC, который переживает пересоздание конкретного пода — обязательно для баз данных и любых систем, где реплики не взаимозаменяемы (например, знают свою роль master/replica).
|
||||
|
||||
### Практика руками
|
||||
|
||||
```yaml
|
||||
# statefulset-demo.yaml — учебный пример на минимальном приложении, не на реальной БД
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: demo-headless
|
||||
namespace: demo
|
||||
spec:
|
||||
clusterIP: None # headless-сервис — нужен StatefulSet для стабильных DNS-имён подов
|
||||
selector:
|
||||
app: demo-stateful
|
||||
ports:
|
||||
- port: 80
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: demo-stateful
|
||||
namespace: demo
|
||||
spec:
|
||||
serviceName: demo-headless
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: demo-stateful
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: demo-stateful
|
||||
spec:
|
||||
containers:
|
||||
- name: demo
|
||||
image: nginxdemos/hello:latest
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /usr/share/nginx/html/data
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: data
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 100Mi
|
||||
```
|
||||
|
||||
```bash
|
||||
kubectl apply -f statefulset-demo.yaml
|
||||
kubectl -n demo get pods -l app=demo-stateful # demo-stateful-0, demo-stateful-1 — стабильные имена, не случайные
|
||||
kubectl -n demo get pvc # у каждого пода свой PVC
|
||||
kubectl -n demo get pv
|
||||
|
||||
# показать, что PVC переживает пересоздание своего пода
|
||||
kubectl -n demo delete pod demo-stateful-0
|
||||
kubectl -n demo get pods -l app=demo-stateful # пересоздался с тем же именем demo-stateful-0 и тем же PVC
|
||||
```
|
||||
|
||||
### Самопроверка
|
||||
|
||||
- Своими словами: почему для базы данных в K8s используют StatefulSet, а не Deployment?
|
||||
- Что произойдёт с данными в `emptyDir`, если под пересоздан целиком (не просто перезапущен контейнер внутри него)? (данные потеряются — emptyDir живёт не дольше самого пода).
|
||||
|
||||
### Как это в реальных проектах
|
||||
|
||||
Это прямой ответ на реальный вопрос с собеседования «как будешь разворачивать PostgreSQL на две ноды K8s» (см. [QUESTIONS.md](QUESTIONS.md)): StatefulSet + PVC на каждую реплику + обычно готовый оператор (CloudNativePG, Zalando Postgres Operator), который поверх StatefulSet настраивает master-replica топологию и Service, разделяющий трафик на запись/чтение. Принцип репликации сам по себе прогнан отдельно в Docker Compose — см. [../databases/CASE.md](../databases/CASE.md); K8s здесь — просто уровень оркестрации поверх той же логики.
|
||||
|
||||
---
|
||||
|
||||
## Модуль 11. Helm, часть 1 — пользователь чартов
|
||||
|
||||
### Зачем это
|
||||
|
||||
Реальные приложения (особенно готовые open-source системы вроде Prometheus/Grafana) состоят из десятков манифестов. Helm — золотой стандарт для их установки и обновления одной командой вместо ручного `kubectl apply -f` на каждый файл.
|
||||
|
||||
### Теория-минимум
|
||||
|
||||
- **Chart** — упакованный набор шаблонизированных манифестов K8s (аналог пакета в apt/npm).
|
||||
- **Release** — конкретный установленный в кластер экземпляр чарта (можно поставить один и тот же чарт несколько раз под разными именами релиза — например, две независимые инсталляции Prometheus).
|
||||
- **Repository** — источник, откуда качаются чарты (аналог apt-репозитория или npm registry).
|
||||
- `values.yaml` — файл с параметрами, которые чарт подставляет в свои шаблоны при установке — то, чем конкретная инсталляция отличается от дефолтной.
|
||||
|
||||
Ключевые команды: `helm repo add/update` (подключить и обновить репозиторий), `helm search repo` (найти чарт), `helm install` (поставить новый релиз), `helm upgrade` (обновить существующий релиз новыми values/версией), `helm rollback` (откатить релиз к прошлой ревизии — аналог `kubectl rollout undo`, но на уровне всего чарта), `helm uninstall` (удалить релиз).
|
||||
|
||||
### Практика руками
|
||||
|
||||
```bash
|
||||
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
|
||||
helm repo update
|
||||
helm search repo prometheus-community/kube-prometheus-stack
|
||||
|
||||
# посмотреть дефолтные values перед установкой — не ставить вслепую
|
||||
helm show values prometheus-community/kube-prometheus-stack > default-values.yaml
|
||||
|
||||
# ставим с переопределением — уменьшаем ресурсы под pet-кластер
|
||||
cat > my-values.yaml << 'EOF'
|
||||
grafana:
|
||||
adminPassword: "lab-admin"
|
||||
prometheus:
|
||||
prometheusSpec:
|
||||
resources:
|
||||
requests:
|
||||
cpu: 200m
|
||||
memory: 512Mi
|
||||
EOF
|
||||
|
||||
helm install monitoring prometheus-community/kube-prometheus-stack \
|
||||
--namespace monitoring --create-namespace \
|
||||
-f my-values.yaml
|
||||
|
||||
kubectl -n monitoring get pods
|
||||
helm list -n monitoring
|
||||
helm status monitoring -n monitoring
|
||||
|
||||
# обновление релиза — меняем values и применяем поверх существующей инсталляции
|
||||
helm upgrade monitoring prometheus-community/kube-prometheus-stack \
|
||||
--namespace monitoring \
|
||||
--set grafana.adminPassword=new-lab-admin \
|
||||
-f my-values.yaml
|
||||
|
||||
helm history monitoring -n monitoring
|
||||
# при необходимости: helm rollback monitoring 1 -n monitoring
|
||||
|
||||
kubectl -n monitoring port-forward svc/monitoring-grafana 3000:80
|
||||
```
|
||||
|
||||
### Самопроверка
|
||||
|
||||
- Своими словами объяснить разницу между Chart и Release (пакет vs конкретная установленная копия пакета).
|
||||
- Зачем перед `helm install` смотреть `helm show values`, а не сразу ставить с дефолтами вслепую?
|
||||
- Чем `helm upgrade` отличается от повторного `helm install`? (upgrade применяет изменения к существующему релизу с сохранением истории ревизий, install создаст конфликт с уже существующим релизом).
|
||||
|
||||
### Как это в реальных проектах
|
||||
|
||||
Это прямой ответ на реальный вопрос с собеседования Bi.Zone «как задеплоишь мониторинг на 300 000 серверов» (см. [../../legend/CAPACITY.md](../../legend/CAPACITY.md)): не вручную, а Helm-чартом с `values.yaml`, где параметризуются количество реплик, ресурсы, remote_write в Mimir — тот же принцип «Prometheus + Grafana», что и в рабочем стеке (см. [../monitoring/CASE.md](../monitoring/CASE.md)), но развёрнутый декларативно и параметризуемо, а не через docker-compose с ручными правками.
|
||||
|
||||
---
|
||||
|
||||
## Модуль 12. Helm, часть 2 — автор чарта
|
||||
|
||||
### Зачем это
|
||||
|
||||
Пользоваться готовыми чартами (модуль 11) — половина навыка. Уметь упаковать своё приложение в чарт — то, что отличает «ставил через Helm» от «понимаю, как Helm устроен изнутри», и именно это спрашивают на более глубоких технических собеседованиях.
|
||||
|
||||
### Теория-минимум
|
||||
|
||||
Структура чарта, созданного `helm create`:
|
||||
- `Chart.yaml` — метаданные чарта (имя, версия чарта, версия приложения).
|
||||
- `values.yaml` — дефолтные параметры, доступные в шаблонах как `.Values.*`.
|
||||
- `templates/` — сами манифесты K8s, но с шаблонизацией Go-template: `{{ .Values.replicaCount }}` подставит значение из values.yaml, `{{ include "chart.fullname" . }}` — переиспользуемый фрагмент из `_helpers.tpl`, `{{- if .Values.ingress.enabled }}...{{- end }}` — условное включение блока манифеста.
|
||||
- `_helpers.tpl` — вспомогательные именованные шаблоны (чтобы не дублировать логику вроде генерации имени релиза в каждом файле).
|
||||
- `NOTES.txt` — текст, который Helm печатает пользователю сразу после `helm install` (подсказка, как получить доступ к приложению).
|
||||
|
||||
### Практика руками
|
||||
|
||||
```bash
|
||||
helm create demo-chart
|
||||
```
|
||||
|
||||
Разобрать построчно, что `helm create` сгенерировал по умолчанию — открыть `demo-chart/Chart.yaml`, `demo-chart/values.yaml`, `demo-chart/templates/deployment.yaml`, `demo-chart/templates/_helpers.tpl` и сопоставить с манифестами, которые писались руками в модулях 3–7: `templates/deployment.yaml` — это тот же Deployment из модуля 4, но `replicas`, `image.repository`, `image.tag` заменены на `{{ .Values.replicaCount }}`, `{{ .Values.image.repository }}` и т.д.
|
||||
|
||||
```bash
|
||||
# проверка шаблона без реальной установки — что синтаксис корректен
|
||||
helm lint demo-chart
|
||||
|
||||
# рендер шаблонов локально — увидеть итоговый YAML, который получится после подстановки values
|
||||
helm template demo-chart
|
||||
|
||||
# рендер с переопределёнными values — увидеть разницу
|
||||
helm template demo-chart --set replicaCount=3 --set image.repository=nginxdemos/hello
|
||||
```
|
||||
|
||||
Адаптировать `values.yaml` и `templates/deployment.yaml` под приложение из модулей 3–7 (образ `nginxdemos/hello`, свой ConfigMap/Secret) и поставить своим чартом вместо ручных `kubectl apply`:
|
||||
|
||||
```bash
|
||||
helm install demo-release ./demo-chart --namespace demo --create-namespace
|
||||
kubectl -n demo get all -l app.kubernetes.io/instance=demo-release
|
||||
helm uninstall demo-release -n demo
|
||||
```
|
||||
|
||||
### Самопроверка
|
||||
|
||||
- Открыть `templates/deployment.yaml` сгенерированного чарта и найти в нём 3 места, где вместо жёстко заданного значения подставляется `.Values.*` — объяснить, зачем каждое из них параметризовано.
|
||||
- Чем `helm template` отличается от `helm install`? (template — просто рендерит YAML локально, ничего не применяет к кластеру; install — рендерит и сразу применяет).
|
||||
- Зачем нужен `_helpers.tpl`, если можно было бы просто продублировать `{{ .Release.Name }}-demo-chart` в каждом файле шаблонов?
|
||||
|
||||
### Как это в реальных проектах
|
||||
|
||||
Собственные чарты пишут, когда приложение — своё (не готовый open-source продукт вроде Prometheus), и его нужно раскатывать в несколько окружений с разными параметрами (dev/stage/prod) без копипаста манифестов — это прямая параллель с тем, как в рабочем стеке Ansible-роли параметризуются через переменные вместо копирования плейбуков под каждое окружение (см. [../ansible/CASE.md](../ansible/CASE.md)): тот же принцип «шаблон + параметры», другой уровень (конфигурация ОС/сервисов vs манифесты K8s).
|
||||
|
||||
---
|
||||
|
||||
## Модуль 13. Как это устроено в реальных проектах
|
||||
|
||||
### Зачем это
|
||||
|
||||
Финальный модуль — не новая техническая тема, а сборка всех модулей в целостную картину «как это выглядит в реальной команде», плюс честная граница pet-опыта.
|
||||
|
||||
### Теория-минимум
|
||||
|
||||
Типичный процесс в компании, использующей K8s+Helm:
|
||||
- Чарты (свои и сторонние) хранятся в git-репозитории вместе с манифестами инфраструктуры — это прямая параллель с рабочим опытом хранения Ansible-ролей в Gitea (см. [../../legend/LEGEND.md](../../legend/LEGEND.md)).
|
||||
- Раскатка идёт не руками с ноутбука разработчика, а через CI/CD-пайплайн (`helm upgrade` как шаг джобы) — параллель с TeamCity-раскаткой ролей/дашбордов из рабочего опыта (см. [../../legend/STORY.md](../../legend/STORY.md) → «Версионирование дашбордов»).
|
||||
- Разные `values-dev.yaml` / `values-staging.yaml` / `values-prod.yaml` под одно и то же приложение — тот же чарт, разные параметры на окружение.
|
||||
- **GitOps** (инструменты ArgoCD/Flux) — следующий шаг развития идеи: вместо того чтобы CI сам выполнял `helm upgrade`, в git-репозитории просто описывается желаемое состояние кластера, а отдельный контроллер внутри кластера постоянно сверяет его с реальностью и сам подтягивает изменения (тот же принцип desired state из модуля 1, но применённый к самому процессу деплоя). Здесь достаточно понимания идеи, не практики — pet-кластер для этого не поднимался.
|
||||
- **Umbrella-чарт** — чарт, который сам ничего не разворачивает напрямую, а просто объявляет зависимости от нескольких других чартов (например, «наше приложение» + «его конфигурация Redis» одним релизом) — используется, когда несколько связанных сервисов нужно ставить и версионировать вместе.
|
||||
|
||||
### Практика руками
|
||||
|
||||
Практики в этом модуле нет — это сборочный/рефлексивный модуль. Вместо неё: выписать на бумаге для своего pet-кластера полный путь «от `git commit` в репозитории чарта до работающего пода», используя реальные названия компонентов, которые прошли в модулях 0–12 (Deployment, Service, Helm release и т.д.) вместо абстрактных слов.
|
||||
|
||||
### Самопроверка
|
||||
|
||||
- Объяснить своими словами идею GitOps и чем она отличается от «CI-пайплайн просто выполняет helm upgrade».
|
||||
- Честно сформулировать для себя одним предложением, что из модуля 13 — реальный pet-опыт (структура чартов, values, helm upgrade/rollback), а что — только понимание на уровне «знаю, что это и зачем» (ArgoCD/Flux, продовые GitOps-пайплайны).
|
||||
|
||||
### Как это в реальных проектах
|
||||
|
||||
Это тот самый честный формат ответа на собеседовании, уже принятый в этом репозитории для других инструментов «на границе опыта» (см. [../../legend/DEPT_STACK.md](../../legend/DEPT_STACK.md) → «Как этим пользоваться на собеседовании»): по GitOps — «сам не разворачивал, но понимаю идею и зачем она нужна», без попытки выдать это за практический опыт.
|
||||
|
||||
---
|
||||
|
||||
## Выходной контроль
|
||||
|
||||
Курс пройден, когда пройдены оба пункта:
|
||||
|
||||
1. **Устный прогон [QUESTIONS.md](QUESTIONS.md)** — вслух, без подглядывания в готовые ответы; если запинаешься — вернуться к соответствующему модулю выше (не к готовому ответу), разобраться и сформулировать заново своими словами.
|
||||
2. **Две схемы на бумаге без подсказок**: архитектура кластера (control-plane/worker, модуль 2) и путь трафика (клиент → под, модуль 5–6).
|
||||
|
||||
### Таблица соответствия: вопрос из QUESTIONS.md → модуль курса
|
||||
|
||||
| Вопрос из QUESTIONS.md | Модуль |
|
||||
|---|---|
|
||||
| Из чего состоит Kubernetes? | 2 |
|
||||
| etcd будет на одной виртуалке с K8s или отдельно? | 2 |
|
||||
| Что такое DaemonSet? | 2 (kube-proxy как DaemonSet) |
|
||||
| Нарисуй два ЦОД под K8s с балансировщиком | 5, 6 |
|
||||
| Как будешь разворачивать PostgreSQL на две ноды K8s? | 10 |
|
||||
| Что такое Namespace и зачем он нужен? | 3 |
|
||||
| Чем отличается ConfigMap от Secret? | 7 |
|
||||
| Какой у тебя опыт с Helm и разворотом кластеров K8s? | 11, 12 |
|
||||
| Как работает трафик в K8s? | 5, 6 |
|
||||
| Как проверить логи и поды в кластере? | 3, 9 |
|
||||
|
||||
После прохождения курса — переходи к [CASE.md](CASE.md) как к компактному конспекту для повторения перед конкретным собеседованием.
|
||||
43
stack/kubernetes/QUESTIONS.md
Normal file
43
stack/kubernetes/QUESTIONS.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Вопросы: Kubernetes
|
||||
|
||||
Опираются на кейс: [CASE.md](CASE.md). Источники реальных вопросов — [Реалист банк.md](../../interview/Реалист%20банк.md), [Bi.Zone.md](../../interview/Bi.Zone.md), [Alfa-Bank.md](../../interview/Alfa-Bank.md), [VK Cloud.md](../../interview/VK%20Cloud.md), см. сводку в [../../interview/real-interviews.md](../../interview/real-interviews.md).
|
||||
|
||||
### Из чего состоит Kubernetes?
|
||||
|
||||
Control-plane — `kube-apiserver` (единая точка входа для всех операций), `etcd` (хранилище состояния кластера), `kube-scheduler` (распределяет поды по нодам), `kube-controller-manager` (следит за соответствием реального состояния желаемому). На worker-нодах — `kubelet` (управляет подами на своей ноде) и `kube-proxy` (сетевые правила доступа к Service), плюс container runtime (`containerd`). У себя в кластере (`kind`, 1 control-plane + 2 worker) я это видел напрямую через `kubectl get pods -n kube-system`.
|
||||
|
||||
### etcd будет на одной виртуалке с K8s или отдельно?
|
||||
|
||||
В маленьких/pet-инсталляциях (у меня в `kind`) etcd живёт на той же control-plane ноде. В проде для отказоустойчивости etcd обычно выносят на отдельные ноды нечётным числом (3 или 5) — так работает кворум: при потере части узлов кластер etcd продолжает принимать решения, если жива больше половины. Consul в этом контексте — альтернатива service discovery/конфиг-хранилища, не замена etcd как хранилища состояния самого K8s.
|
||||
|
||||
### Что такое DaemonSet?
|
||||
|
||||
Контроллер, который гарантирует, что на каждой (или на подмножестве по селектору) ноде кластера запущена ровно одна копия пода — используется для агентов уровня ноды: `kube-proxy`, CNI-плагин, node-exporter для мониторинга. В отличие от Deployment, DaemonSet не масштабируется числом реплик — он масштабируется вместе с числом нод: добавили ноду — на ней автоматически появился под DaemonSet.
|
||||
|
||||
### Нарисуй два ЦОД под K8s с балансировщиком — на каких виртуалках это будет работать?
|
||||
|
||||
Внешний балансировщик (L4/L7, например HAProxy/облачный LB) перед двумя ЦОД → в каждом ЦОД — набор worker-нод под control-plane (control-plane лучше растянуть между ЦОД нечётным числом инстансов ради кворума etcd, например 3: 2 в одном ЦОД + 1 в другом, или 3+2 при пяти) → внутри кластера трафик до пода идёт через Service (ClusterIP/NodePort) и `kube-proxy`, который резолвит запрос в конкретный под по iptables/IPVS-правилам. Каждый ЦОД — набор физических/виртуальных worker-нод с kubelet и containerd. Я такую схему целиком не эксплуатировал (моя лаба — 3 ноды на одной машине), но принцип балансировки и роль каждого компонента понимаю и воспроизвёл в миниатюре.
|
||||
|
||||
### Как будешь разворачивать PostgreSQL на две ноды K8s?
|
||||
|
||||
Через `StatefulSet` (не Deployment — под'ам БД нужны стабильные имена и отдельные PersistentVolume на каждую реплику) с master-replica топологией: обычно готовый оператор (например, CloudNativePG или Zalando Postgres Operator) заводит StatefulSet, PersistentVolumeClaim на каждую ноду и Service, разделяющий трафик на запись (к master) и на чтение (к репликам). Сам оператор в кластере не поднимал — но принцип репликации PostgreSQL прогнан отдельно в Docker Compose, см. [../databases/CASE.md](../databases/CASE.md); K8s тут просто уровень оркестрации поверх той же логики репликации.
|
||||
|
||||
### Что такое Namespace и зачем он нужен?
|
||||
|
||||
Логическая изоляция ресурсов внутри одного кластера — разделение по окружениям (dev/stage/prod) или командам, без поднятия отдельных кластеров. Объекты в разных namespace могут называться одинаково и не конфликтуют; ресурсы вроде Node и PersistentVolume — не namespaced, они общие на весь кластер. В своей лабе завёл отдельный namespace `demo` под тестовое приложение и отдельный `monitoring` под kube-prometheus-stack — чтобы не мешать их друг другу и явно видеть границу через `kubectl -n <namespace>`.
|
||||
|
||||
### Чем отличается ConfigMap от Secret?
|
||||
|
||||
ConfigMap — для несекретных конфигурационных данных (переменные окружения, конфиг-файлы), Secret — для чувствительных значений (пароли, токены, ключи). Технически Secret по умолчанию хранит значения в base64 — это кодирование, а не шифрование, то есть сам по себе Secret не «защищает» данные от того, кто имеет доступ к API/etcd. Для реальной защиты нужны либо шифрование etcd at rest, либо внешние системы вроде Vault (см. [../vault/CASE.md](../vault/CASE.md)) или Sealed Secrets. В своём манифесте завёл и ConfigMap, и Secret и явно проверил разницу через `kubectl get secret app-secret -o yaml` — значение там закодировано base64, не зашифровано.
|
||||
|
||||
### Какой у тебя опыт с Helm и разворотом кластеров K8s?
|
||||
|
||||
Helm — пакетный менеджер для K8s: чарт описывает набор шаблонизированных манифестов с параметрами в `values.yaml`, `helm install`/`upgrade` разворачивает/обновляет релиз одной командой вместо ручного `kubectl apply` на десяток файлов. У себя разворачивал `kube-prometheus-stack` через `helm install` в отдельный namespace — это дало сразу Prometheus + Grafana + Alertmanager + нужные ServiceMonitor-ресурсы с рабочими дефолтами, которые я после точечно переопределял через `--set`/`values.yaml`.
|
||||
|
||||
### Как работает трафик в K8s?
|
||||
|
||||
Клиент → внешний балансировщик/Ingress-контроллер (роутинг по host/path) → Service (стабильный виртуальный IP/DNS-имя, абстракция над множеством подов) → `kube-proxy` на нодах транслирует обращение к Service в адрес конкретного пода по правилам iptables/IPVS, с балансировкой между подами, которые матчатся по `selector` в Service. Между подами внутри кластера — плоская сеть через CNI-плагин, любой под может обратиться к любому другому по его Pod IP или через Service DNS-имя (`<service>.<namespace>.svc.cluster.local`).
|
||||
|
||||
### Как проверить логи и поды в кластере?
|
||||
|
||||
`kubectl get pods -A` — все поды по всем namespace; `kubectl -n <ns> logs <pod>` — логи контейнера, `--previous` — логи упавшего перед текущим рестартом контейнера; `kubectl -n <ns> describe pod <pod>` — события и причина, если под не стартует (`ImagePullBackOff`, `CrashLoopBackOff`); `kubectl get events --sort-by=.lastTimestamp` — хронология событий по всему namespace. Специально ронял под (указывал несуществующий образ) и разбирал `describe`, чтобы увидеть, как выглядит диагностика в реальности, а не в теории.
|
||||
Reference in New Issue
Block a user