Без облака адрес сервису типа LoadBalancer выдавать некому, поэтому ставим MetalLB: его controller берёт адрес из IPAddressPool (у нас 192.168.10.200–220) и пишет его в status.loadBalancer.ingress, а speaker на узлах объявляет его в сеть. В режиме L2 за адрес отвечает один узел-лидер, выбранный по хешу среди живых speaker'ов: он отвечает на ARP MAC-адресом своего интерфейса, а при смене лидера новый лидер рассылает gratuitous ARP — на стенде через доли секунды после ухода speaker'а. В режиме BGP каждый подходящий узел объявляет роутеру маршрут /32, и роутер раскладывает трафик по узлам через ECMP — на стенде это проверено с роутером на FRR. Трафик MetalLB не проксирует — пакет попадает в правила kube-proxy и уходит DNAT на под.
Имена в кластере разрешает CoreDNS — Deployment coredns за сервисом kube-dns с адресом 10.96.0.10: плагин kubernetes строит ответы из watch на Service и EndpointSlice, а всё остальное плагин forward пересылает DNS-серверам узла. Сервис получает имя <сервис>.<неймспейс>.svc.cluster.local с A-записью на ClusterIP, headless-сервис — адреса всех готовых подов, именованные порты — SRV-записи. В /etc/resolv.conf пода kubelet пишет nameserver 10.96.0.10, суффиксы search и ndots:5, поэтому внешнее имя вроде api.github.com сначала пробуется со всеми суффиксами кластера: на нашем стенде это четыре запроса вместо одного.
Service даёт группе подов постоянный виртуальный адрес ClusterIP, которого нет ни на одном интерфейсе: он существует только как ключ в правилах netfilter, которые kube-proxy пишет на каждом узле. Пакет на ClusterIP ещё до маршрутизации получает DNAT на адрес одного из готовых подов, выбранного случайно один раз на соединение, а conntrack возвращает ответ от имени сервиса. Список готовых подов ведёт контроллер EndpointSlice по readiness-пробам; в режиме nftables сервис находится за одно обращение к verdict map service-ips.
Kubernetes сам сеть не строит, а требует от плагина CNI: у каждого пода свой IP, любой под достаёт до любого без NAT, узел достаёт до своих подов. На стенде это делает Calico: под соединён с узлом veth-парой cali…, адрес у него /32, шлюз — фиктивный 169.254.1.1, за который узел отвечает по proxy ARP, а дальше пакет маршрутизирует ядро узла. Между узлами одной подсети пакеты идут без инкапсуляции по маршрутам к блокам /26, которые Felix прописывает на каждом узле по данным IPAM; VXLAN нужен только между подсетями, а наружу поды выходят через natOutgoing.
kube-scheduler берёт под с пустым spec.nodeName, отсеивает узлы фильтрами (место по requests, taints, affinity, topology spread), оценивает оставшиеся и записывает выбор одним объектом Binding. Решение принимается один раз: работающие поды он не переселяет, а под с уже заполненным nodeName вообще не видит. На выбор влияют поля пода — nodeSelector, affinity, tolerations, topologySpreadConstraints и priorityClassName, — а планово освободить узел позволяют cordon, drain и PodDisruptionBudget.
Роли FSMO — пять операций, которые в многохозяйском каталоге выполняет только один контроллер-владелец, потому что конфликт в них недопустим: Schema Master и Domain Naming Master на лес, PDC Emulator, RID Master и Infrastructure Master на каждый домен. Владелец записан в атрибуте fSMORoleOwner и меняется командой Move-ADDirectoryServerOperationMasterRole: переносом при живом владельце или с -Force, когда владелец не вернётся, — тогда командлет захватывает роль. После захвата Schema, Domain Naming или RID Master старый владелец в сеть не возвращается: его вычищают из каталога и ставят заново.
OpenSSH Server в Windows Server 2025 уже установлен: его запускают (Start-Service sshd, Set-Service sshd -StartupType Automatic) и добавляют профиль Domain в правило файрвола для порта 22, которое создано только для Private. Входить могут Administrators и члены встроенной группы OpenSSH Users, а ключи администраторов лежат в общем C:\ProgramData\ssh\administrators_authorized_keys, доступном только SYSTEM и Administrators. PowerShell делают оболочкой параметром реестра DefaultShell, вход по Kerberos включают через GSSAPI, а PowerShell 7 умеет Remoting поверх SSH с -HostName.
ConfigMap хранит настройки парами «ключ — значение»; Secret устроен так же, но его значения записаны в base64, тома с ним kubelet держит в tmpfs, а чтение ограничивает RBAC. В под оба попадают переменными окружения или файлами тома: файлы kubelet обновляет при изменении объекта атомарной подменой ссылки ..data, а переменные и файлы через subPath не меняются до перезапуска контейнера. base64 — не шифрование: в etcd секреты лежат открытым текстом, пока не включено шифрование при хранении через EncryptionConfiguration.
Неймспейс — область уникальности имён и граница для API: в нём действуют права RBAC, квоты, LimitRange и сетевые политики, но сеть, узлы и DNS он сам по себе не изолирует, а удаляется через финализатор kubernetes, когда контроллер вычистит содержимое. Метки — пары «ключ — значение» для отбора: по селекторам matchLabels и matchExpressions контроллеры находят свои поды, а kubectl -l фильтрует объекты на стороне API-сервера. Аннотации хранят данные, по которым не выбирают: причину изменения, хеш коммита, настройки внешних контроллеров.
requests — то, что контейнеру гарантировано: по их сумме планировщик решает, поместится ли под в allocatable узла, а request CPU становится весом cpu.weight. limits — потолок: лимит CPU превращается в квоту cpu.max, за которой потоки замораживаются до конца периода (throttling), лимит памяти — в memory.max, за которым ядро убивает контейнер (OOMKilled). По requests и limits под получает класс QoS — Guaranteed, Burstable или BestEffort, — от которого зависят oom_score_adj и порядок выселения при нехватке памяти.