841 lines
63 KiB
Markdown
841 lines
63 KiB
Markdown
# Учебный курс: 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. Все команды курса — в bash-синтаксисе (heredoc, `base64 -d`, настоящий `curl`), поэтому выполнять их нужно в **Git Bash** (ставится вместе с Git for Windows), а не в PowerShell/cmd — там половина команд не сработает или сработает иначе (`curl` в PowerShell — алиас на `Invoke-WebRequest`). Docker Desktop стоит выделить не меньше 8 ГБ RAM — иначе стек мониторинга из модуля 12 повиснет в Pending. Не пересоздавай кластер между модулями без необходимости — большинство модулей продолжают работать в одном и том же кластере и в одних и тех же namespace (`demo`, `monitoring`), это ближе к тому, как выглядит реальная работа с уже существующим кластером.
|
||
|
||
Альтернативное окружение — прохождение курса на удалённом Ubuntu-сервере по SSH вместо Windows + Docker Desktop: спеки железа, бенчмарки и точечные адаптации команд по модулям — в [SERVER.md](SERVER.md). Текущий прогресс прохождения (какой модуль пройден, на чём остановился, состояние кластера) — в [PROGRESS.md](PROGRESS.md), построчный разбор каждой выполненной команды с объяснением аргументов — в [COMMANDS.md](COMMANDS.md), а как просто запустить/остановить/проверить стенд между сессиями без учебных объяснений — в [USAGE.md](USAGE.md).
|
||
|
||
---
|
||
|
||
## Модуль 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
|
||
# создаём под через Deployment — контроллер, который будет поддерживать desired state
|
||
# (голый `kubectl run demo --image=...` создал бы под без контроллера — его никто бы не пересоздал)
|
||
kubectl create deployment demo --image=nginxdemos/hello
|
||
kubectl get pods
|
||
# демо self-healing: убиваем под руками (имя вида demo-<хэш> — взять из вывода выше)
|
||
kubectl delete pod <имя-пода>
|
||
kubectl get pods
|
||
# под пересоздан автоматически с новым именем, потому что desired state требует, чтобы существовала 1 реплика
|
||
|
||
# уборка — дальше в курсе работаем в отдельном namespace
|
||
kubectl delete deployment demo
|
||
```
|
||
|
||
### Самопроверка
|
||
|
||
- Своими словами: что такое desired state и чем декларативный подход отличается от «выполнить команду»?
|
||
- Почему после `kubectl delete pod <имя>` под появился снова — и почему с другим именем?
|
||
|
||
*(Забегая вперёд: пересоздал под не «Kubernetes вообще», а конкретный контроллер — Deployment. Голый под, созданный через `kubectl run`, не пересоздаст никто — за него никто не отвечает. Иерархия Deployment → ReplicaSet → Pod подробно разобрана в модуле 4; здесь цель — просто увидеть идею 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, где под был создан через Deployment и контроллер его пересоздал). Правильный способ — через 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) как к компактному конспекту для повторения перед конкретным собеседованием.
|