Доработка документации

This commit is contained in:
Tot Maxim
2026-07-18 23:33:26 +03:00
parent 40ff73f647
commit 53032f1fd7
6 changed files with 479 additions and 8 deletions

View File

@@ -638,6 +638,303 @@ gitea http: 302
---
### 24. Модуль 2 — проверка CI-стека + `kind-config.yaml` новой топологии
Начиная с этой записи, команды выполнял **ты сам** напрямую в интерактивной SSH-сессии на сервере (login shell, `~/.local/bin` уже в `PATH`), а мне присылал готовый лог поэтому команды ниже идут без SSH-обвязки из пункта 0.
**Команда:**
```bash
docker ps --filter name=gitea --format '{{.Names}}\t{{.Status}}'
cat > ~/k8s/kind-config.yaml << 'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 80
hostPort: 8080
- containerPort: 443
hostPort: 8443
- role: worker
- role: worker
EOF
cat ~/k8s/kind-config.yaml
```
**Разбор аргументов:**
- `docker ps --filter name=gitea --format '{{.Names}}\t{{.Status}}'` сужает список контейнеров до тех, чьё имя содержит `gitea`, и печатает только имя и статус быстрая проверка перед началом работы, что боевой CI-стек жив (чек-лист из SERVER.md).
- `cat > файл << 'EOF' ... EOF` heredoc: всё, что между `<< 'EOF'` и завершающим `EOF`, построчно записывается в файл через `cat`; кавычки вокруг `'EOF'` отключают подстановку переменных/спецсимволов внутри блока (не нужна здесь, но привычка на будущее для блоков с `$`).
- `role: control-plane` / `role: worker` три записи в списке `nodes` = kind создаст три Docker-контейнера, один control-plane и два worker (топология модуля 2 из LEARNING.md).
- `extraPortMappings` внутри control-plane-ноды пробрасывает порты хоста на порты внутри этого контейнера-ноды: `hostPort: 8080` `containerPort: 80` и `hostPort: 8443` `containerPort: 443`. Сделано сразу в модуле 2 (адаптация из SERVER.md), а не только в модуле 6 (когда понадобится Ingress) иначе пришлось бы пересоздавать кластер повторно. Именно 8080/8443, а не 80/443, потому что порты 80/443 хоста уже заняты Gitea/nginx (см. `ss -tulpn` в сессии 80/443/3000/3030/222/4400/9090 заняты, 8080/8443 свободны).
**Получен ответ:**
```
gitea_runner Up 7 hours
gitea Up 7 hours (healthy)
gitea_db Up 7 hours (healthy)
gitea_runner_armhf Up 7 hours
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 80
hostPort: 8080
- containerPort: 443
hostPort: 8443
- role: worker
- role: worker
```
**Значит:** CI-стек в порядке, можно продолжать. Файл `kind-config.yaml` записан корректно готов к использованию в `kind create cluster --config`.
---
### 25. Модуль 2 — пересоздание кластера с топологией control-plane + 2 worker
**Команда:**
```bash
kind delete cluster --name lab
kind create cluster --name lab --config kind-config.yaml
kubectl cluster-info --context kind-lab
```
**Разбор аргументов:**
- `kind delete cluster --name lab` удаляет старый однонодовый кластер (созданный в модуле 0-1 без конфига) вместе с его Docker-контейнером, чтобы освободить имя `lab` под новую топологию.
- `kind create cluster --name lab --config kind-config.yaml` `--config` вместо голого `--name` заставляет kind читать топологию и extraPortMappings из файла, а не создавать кластер дефолтной однонодовой конфигурацией.
- `kubectl cluster-info --context kind-lab` печатает адрес API-сервера и CoreDNS для явно указанного контекста `kind-lab` (kind сам создаёт и переключает kubectl-контекст с таким именем при каждом `create cluster`).
**Получен ответ:**
```
Deleting cluster "lab" ...
Deleted nodes: ["lab-control-plane"]
Creating cluster "lab" ...
✓ Ensuring node image (kindest/node:v1.36.1) 🖼
✓ Preparing nodes 📦 📦 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
✓ Joining worker nodes 🚜
Set kubectl context to "kind-lab"
Kubernetes control plane is running at https://127.0.0.1:36761
CoreDNS is running at https://127.0.0.1:36761/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
```
**Значит:** пересоздание прошло без ошибок и быстро (образ ноды `kindest/node:v1.36.1` уже был в кэше Docker с прошлого раза не пришлось скачивать заново). Обрати внимание на новый шаг в выводе, которого не было при однонодовом создании: `✓ Joining worker nodes` именно он добавляет `lab-worker`/`lab-worker2` к кластеру после того, как control-plane уже поднят. kubectl-контекст переключён на `kind-lab` автоматически.
---
### 26. Модуль 2 — проверка нод
**Команда:**
```bash
kubectl get nodes -o wide
```
**Получен ответ:**
```
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
lab-control-plane Ready control-plane 57s v1.36.1 172.22.0.4 <none> Debian GNU/Linux 13 (trixie) 6.8.0-136-generic (amd64) containerd://2.3.1
lab-worker Ready <none> 46s v1.36.1 172.22.0.2 <none> Debian GNU/Linux 13 (trixie) 6.8.0-136-generic (amd64) containerd://2.3.1
lab-worker2 Ready <none> 47s v1.36.1 172.22.0.3 <none> Debian GNU/Linux 13 (trixie) 6.8.0-136-generic (amd64) containerd://2.3.1
```
**Значит:** все 3 ноды поднялись и перешли в `Ready` за 46-57 секунд быстрее, чем однонодовый кластер с нуля (модуль 0, ~53с только на control-plane), потому что образ ноды уже был в кэше. Колонка `ROLES` подтверждает топологию: одна `control-plane`, две `<none>` (worker kind не проставляет им явную роль-метку по умолчанию, в отличие от control-plane). `KERNEL-VERSION` (6.8.0-136) новее, чем было зафиксировано в SERVER.md на момент установки (6.8.0-124) сервер обновлялся ядром между сессиями, это не связано с kind и не требует действий.
---
### 27. Модуль 2 — компоненты control-plane и DaemonSet'ы вживую
**Команда:**
```bash
kubectl get pods -n kube-system -o wide
```
**Получен ответ:**
```
NAME READY STATUS RESTARTS AGE NODE
coredns-589f44dc88-s8m6z 1/1 Running 0 86s lab-control-plane
coredns-589f44dc88-xlkk7 1/1 Running 0 86s lab-control-plane
etcd-lab-control-plane 1/1 Running 0 93s lab-control-plane
kindnet-62npg 1/1 Running 0 86s lab-control-plane
kindnet-9lsrg 1/1 Running 0 86s lab-worker2
kindnet-xwjqx 1/1 Running 0 85s lab-worker
kube-apiserver-lab-control-plane 1/1 Running 0 93s lab-control-plane
kube-controller-manager-lab-control-plane 1/1 Running 0 93s lab-control-plane
kube-proxy-6ltl9 1/1 Running 0 85s lab-worker
kube-proxy-9z5x9 1/1 Running 0 86s lab-control-plane
kube-proxy-h7xtp 1/1 Running 0 86s lab-worker2
kube-scheduler-lab-control-plane 1/1 Running 0 93s lab-control-plane
```
**Значит:** ровно та картина, которую описывает теория модуля 2. Четыре компонента control-plane (`kube-apiserver`, `etcd`, `kube-controller-manager`, `kube-scheduler`) все с суффиксом `-lab-control-plane` в имени и все в колонке `NODE` только на `lab-control-plane`, других экземпляров нет. Два DaemonSet'а `kindnet-*` (CNI, сетевая связность между подами на разных нодах) и `kube-proxy-*` (сетевые правила для Service, разбирается в модуле 5) у каждого ровно по одному поду на каждую из 3 нод (control-plane и оба worker), что и есть определение DaemonSet: «один под на каждую подходящую ноду». `coredns-*` не DaemonSet, а обычный Deployment на 2 реплики; обе оказались на `lab-control-plane`, потому что worker-ноды пока полностью пустые, и scheduler разместил их там, где выгоднее (это никак не привязано к ролям нод просто текущее решение scheduler'а по ресурсам).
Устная самопроверка модуля 2 (3 вопроса из LEARNING.md компоненты control-plane, компоненты worker-ноды, почему etcd в проде выносят отдельно с нечётным числом нод) пройдена развёрнуто и верно. Дополнительно самостоятельно разобрана разница iptables/nftables применительно к двум режимам работы `kube-proxy` не входит в буквальную программу модуля 2, но напрямую относится к роли `kube-proxy` и пригодится в модуле 5 (Service и сеть).
---
### 28. Модуль 3 — `pod.yaml` (Namespace + Pod) и создание
**Команда:**
```bash
cat > ~/k8s/pod.yaml << 'EOF'
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
EOF
kubectl apply -f ~/k8s/pod.yaml
```
**Разбор аргументов:**
- Один файл, два объекта через разделитель `---` YAML позволяет описать несколько документов в одном файле, `kubectl apply -f` применяет их по порядку сверху вниз. Namespace идёт первым не случайно: Pod ссылается на `namespace: demo` во втором документе, а этот namespace должен уже существовать (или быть создан в той же команде apply) к моменту создания Pod.
- Четыре ключа верхнего уровня из теории модуля 3: `apiVersion` (версия API `v1` для обоих базовых типов), `kind` (тип объекта), `metadata` (имя/namespace/labels «паспорт»), `spec` (только у Pod желаемое состояние: какой контейнер запускать).
- `labels: app: demo` метка на Pod, пока не используется явно, но именно так Service/Deployment в следующих модулях будут находить нужные поды через selector.
**Получен ответ:**
```
namespace/demo created
pod/demo-pod created
```
**Значит:** оба объекта созданы с первого раза, ошибок в манифесте нет.
---
### 29. Модуль 3 — базовый набор команд: get, describe, logs, exec
**Команда:**
```bash
kubectl -n demo get pods
kubectl -n demo get pods -o wide
kubectl -n demo describe pod demo-pod
kubectl -n demo logs demo-pod
kubectl -n demo exec -it demo-pod -- sh
```
**Разбор аргументов:**
- `-n demo` на каждой команде явно указывает namespace (без него kubectl смотрит в `default`, где ничего нет).
- `get pods -o wide` `-o wide` добавляет колонки `IP` и `NODE` к базовому выводу.
- `describe pod` в отличие от `get`, разворачивает объект полностью: условия (`Conditions`), примонтированные volume, и главное секцию `Events` (хронологический лог того, что control-plane и kubelet делали с этим подом: `Scheduled` `Pulling`/`Pulled` `Created` `Started`).
- `logs demo-pod` без указания контейнера (работает, потому что в поде он один); печатает stdout/stderr процесса внутри контейнера.
- `exec -it ... -- sh` `-i` (interactive) + `-t` (tty) дают полноценный интерактивный терминал внутри контейнера; `sh`, а не `bash`, потому что образ `nginxdemos/hello` собран на Alpine (slim-образ без bash).
**Получен ответ:**
```
NAME READY STATUS RESTARTS AGE
demo-pod 1/1 Running 0 25s
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
demo-pod 1/1 Running 0 31s 10.244.2.2 lab-worker <none> <none>
Node: lab-worker/172.22.0.2
Status: Running
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 89s default-scheduler Successfully assigned demo/demo-pod to lab-worker
Normal Pulling 88s kubelet Pulling image "nginxdemos/hello:latest"
Normal Pulled 82s kubelet Successfully pulled image ... in 6.311s
Normal Created 82s kubelet Container created
Normal Started 82s kubelet Container started
2026/07/18 19:54:20 [notice] 1#1: nginx/1.29.1
2026/07/18 19:54:20 [notice] 1#1: start worker process 27..30
/ # exit
```
**Значит:** под запланирован планировщиком на `lab-worker` (control-plane исключён taint'ом), образ скачался за 6.3с, контейнер стартовал и поднял 4 worker-процесса nginx. `logs` показал именно вывод приложения (nginx), `describe`/`Events` действия самого Kubernetes вокруг пода как объекта; это и есть разница между ними. `exec` подтвердил, что контейнер полноценно интерактивен.
---
### 30. Модуль 3 — полный манифест объекта (`-o yaml`)
**Команда:**
```bash
kubectl -n demo get pod demo-pod -o yaml
```
**Разбор аргументов:**
- `-o yaml` просит API-сервер отдать объект целиком в том виде, в каком он реально хранится в etcd, а не только колонки таблицы `get pods`.
**Получен ответ (сокращённо, полный лог — в сессии):**
```
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{...исходный JSON манифеста...}
resourceVersion: "2565"
uid: ea8061a9-eca7-4cc0-91d0-86b6e87af510
spec:
containers:
- imagePullPolicy: Always
...
nodeName: lab-worker
restartPolicy: Always
schedulerName: default-scheduler
serviceAccountName: default
terminationGracePeriodSeconds: 30
tolerations: [...]
volumes:
- name: kube-api-access-p5g68
projected: {...}
status:
conditions: [...]
containerStatuses: [...]
phase: Running
podIP: 10.244.2.2
hostIP: 172.22.0.2
```
**Значит:** написанные 12 строк манифеста развернулись примерно в 120. Сверх исходного `pod.yaml` появилось: служебные поля `metadata` (`uid`, `resourceVersion`, аннотация с сохранённым исходным манифестом на ней основан three-way merge при следующих `apply`); дефолты в `spec`, которые Kubernetes подставил сам (`imagePullPolicy`, `restartPolicy`, `schedulerName`, `serviceAccountName`, `tolerations`, автомонтированный volume с токеном сервис-аккаунта, `nodeName` результат работы планировщика); и целиком раздел `status`, которого в манифесте нет вообще это runtime-состояние, которое пишет kubelet, наблюдая за реальным контейнером. Итог: `spec` желаемое состояние (то, что попросили, плюс дефолты), `status` фактическое состояние (что control loop реально наблюдает) основа declarative-модели Kubernetes.
---
### 31. Модуль 3 — удаление голого Pod (без self-healing)
**Команда:**
```bash
kubectl -n demo delete pod demo-pod
kubectl -n demo get pods
```
**Разбор аргументов:**
- В отличие от модуля 1 (Deployment), здесь Pod создан напрямую, без контроллера сверху.
**Получен ответ:**
```
pod "demo-pod" deleted from demo namespace
No resources found in demo namespace.
```
**Значит:** под удалился и не пересоздался namespace `demo` пуст. Это и есть отличие голого Pod от Pod'а под управлением Deployment/ReplicaSet: `restartPolicy: Always` перезапускает контейнер *внутри* пода при его падении, но если исчезает сам Pod (удалён вручную или нода, на которой он жил, выходит из строя), пересоздать его некому за это отвечает контроллер (ReplicaSet), которого здесь нет. Отсюда правило курса: голые Pod'ы в проде почти не используют, кроме короткоживущих задач.
Устная самопроверка модуля 3 (роли ключей манифеста, `logs` vs `describe`, что добавляет `-o yaml`, плюс доп. вопрос про поведение при падении ноды) пройдена развёрнуто и верно.
---
## Соглашение на будущее
Начиная с этой сессии, каждая команда, которую я выполняю в рамках курса (не только сегодня), добавляется в этот файл новой датированной секцией в том же формате: задача команда разбор аргументов полученный ответ что он значит. `PROGRESS.md` при этом остаётся коротким журналом-указателем со ссылкой на соответствующий раздел здесь.