Kubernetes для самых маленьких. Часть 36: Как Windows-узлы работают в кластере?
Все части серии «Kubernetes для самых маленьких»В прошлой части мы обновили стенд до Kubernetes 1.37.2 и научились выводить узлы на обслуживание по одному. Все узлы до сих пор были одинаковыми: Ubuntu Server, containerd, ядро Linux. Сегодня начинается блок о гибридном кластере, где рядом с Linux-воркерами работает воркер на Windows Server.
Зачем вообще тащить Windows в кластер? Затем, что далеко не всё, из чего собраны приложения, кроссплатформенно, и кроссплатформенность программы заканчивается там, где заканчивается самая «непереносимая» из её зависимостей. Современный .NET (5 и новее) давно работает на Linux, код собирается под любую ОС, но запускается приложение только там, где есть всё, что оно тянет за собой. А тянуть оно может то, что существует только для Windows: компонент COM, зарегистрированный в реестре, библиотеку для документов или печати, построенную на графической подсистеме Windows, SDK оборудования или стороннего сервиса в виде Windows DLL. Одна такая зависимость — и «кроссплатформенный» сервис становится Windows-сервисом. Если её можно заменить, Linux-контейнер окажется проще и дешевле. Но когда замены нет, а остальная инфраструктура уже живёт в Kubernetes, Windows-узлы позволяют обслуживать такие сервисы тем же способом: Deployment, Service, Ingress, те же kubectl и те же проверки здоровья.
Тяжёлые монолиты на .NET Framework 4.x с IIS в контейнеры обычно не переносят: образ Server Core с IIS и .NET Framework весит несколько гигабайт, долго скачивается на каждый новый узел и обновляется вместе с ОС каждый месяц. Такие приложения чаще остаются на виртуальных машинах, а Windows-узлы кластера достаются небольшим сервисам, которым Windows нужна из-за одной-двух зависимостей. Поэтому и примеры в этом блоке — на компактной основе Nano Server, а не на Server Core.
Блок состоит из пяти частей. Эта — об устройстве Windows-узла: что на нём работает вместо namespaces, cgroups и netfilter, почему версия образа должна совпадать с версией ОС, как поды попадают на «свои» узлы и чего на Windows нет. Часть 37 — сеть Windows-узлов и выбор сетевого плагина, часть 38 — сборка гибридного кластера с kubeadm, часть 39 — рабочие нагрузки, хранилище и наблюдаемость, часть 40 — доменные учётные записи gMSA для подов. Как устроены kubelet, CRI и под на Linux, разобрано в части 2 и части 6; здесь всё время будем сравнивать с ними.
Учебный кластер серии в этом блоке остаётся как был. Весь блок работает на отдельном тестовом кластере: Linux control plane k8s-lab-master-1 (192.168.10.23), два Linux-воркера k8s-lab-node-1 (192.168.10.24) и k8s-lab-node-2 (192.168.10.26) — все на Ubuntu Server 26.04 — и один Windows-воркер k8s-lab-win-1.example.com (192.168.10.25) на Windows Server 2025 Datacenter в варианте Server Core (без графической оболочки, сборка 10.0.26100). Сеть подов у тестового кластера своя, 172.21.0.0/16, чтобы его адреса в выводе не путались с адресами основного (172.20.0.0/16). Почему для Windows понадобился отдельный кластер, а не ещё один узел в учебном, — в части 37, как этот кластер собрать — в части 38. Сегодня кластера ещё нет, и эта часть — об устройстве: из чего состоит Windows-узел, что в нём заменяет знакомые по Linux механизмы и какие правила из этого следуют. Всё описанное мы увидим своими глазами в части 38, когда соберём кластер и подключим к нему Windows-узел.
Общая картина
┌─ k8s-lab-master-1 (Linux) ─────────────────────────────────────────┐
│ etcd, kube-apiserver, kube-scheduler, kube-controller-manager │
│ static pods; kubelet и containerd — службы systemd │
└───────────────────────────────┬────────────────────────────────────┘
│ 6443: watch, статусы, Lease
│ 10250: logs, exec, port-forward
┌────────────────┴──────────────────┐
▼ ▼
┌─ k8s-lab-node-1 ──────────────────┐ ┌─ k8s-lab-win-1 ───────────────────┐
│ Ubuntu Server 26.04 │ │ Windows Server 2025, Server Core │
│ kubelet, containerd: systemd │ │ kubelet, containerd: службы │
│ shim-runc-v2 → runc │ │ shim-runhcs-v1 → HCS │
│ namespaces, cgroups │ │ server silo, job object │
│ сетевой плагин, kube-proxy: │ │ сетевой плагин, kube-proxy: │
│ privileged, hostNetwork │ │ HostProcess-контейнеры │
│ правила nftables │ │ HNS → VFP в Hyper-V vSwitch │
└───────────────────────────────────┘ └───────────────────────────────────┘
Главное, что видно на схеме: с точки зрения API Windows-узел — такой же узел. Его kubelet подключается к kube-apiserver по тому же порту 6443 с тем же сертификатом system:node:k8s-lab-win-1, продлевает Lease в kube-node-lease, получает через watch поды со своим именем в spec.nodeName и отчитывается об их статусе. Планировщик, контроллеры, EndpointSlice, RBAC работают с ним без изменений. Различия — ниже уровня API: как kubelet запускается, чем создаются контейнеры, кто настраивает сеть.
Control plane — только на Linux. Документация Kubernetes прямо говорит, что Windows-узлы бывают только рабочими. Причин несколько. Компоненты control plane в кластере kubeadm — статические поды с hostNetwork: true (часть 2), а обычный Windows-контейнер не может работать в сети узла. etcd, kube-apiserver и остальные компоненты собираются и тестируются проектом под Linux. Наконец, большинство дополнений — CoreDNS, metrics-server, cert-manager, ingress-контроллеры, общекластерные компоненты сетевых плагинов — существуют только в виде Linux-образов. Поэтому гибридный кластер — это всегда Linux-мастера, Linux-воркеры для системных компонентов и Windows-воркеры для Windows-приложений. В тестовом кластере эту роль играют k8s-lab-node-1 и k8s-lab-node-2.
Версии Windows Server
Kubernetes 1.37 поддерживает Windows-узлы на двух версиях Windows Server:
| Версия | Сборка | Тег базовых образов | Основная поддержка Microsoft | Расширенная |
|---|---|---|---|---|
| Windows Server 2022 | 10.0.20348 | ltsc2022 |
до 13 октября 2026 | до 14 октября 2031 |
| Windows Server 2025 | 10.0.26100 | ltsc2025 |
до ноября 2029 | до ноября 2034 |
Обе версии — LTSC (Long-Term Servicing Channel): выпуски с долгой поддержкой, которые выходят раз в несколько лет и получают ежемесячные обновления безопасности. Номер версии Windows состоит из четырёх частей: 10.0.26100.<ревизия>. Сборка (26100) меняется только с выходом новой версии ОС, ревизия растёт с каждым ежемесячным накопительным обновлением. Как увидим ниже, для контейнеров важна именно сборка.
Windows Server 2022 уже на расширенной поддержке (только исправления безопасности), поэтому новые узлы разумно ставить на 2025, как в тестовом кластере.
Для узла лучше подходит Server Core: без графической оболочки ОС занимает меньше места и памяти, получает меньше обновлений и перезагрузок, а поверхность атаки меньше. Управляется он из PowerShell — локально, через PowerShell Remoting (часть 14 серии «Windows Server для самых маленьких») или по SSH; установка Server Core разобрана в первой части той серии.
Как устроен Windows-контейнер
На Linux контейнер собирается из примитивов ядра, которыми может пользоваться любой процесс с нужными правами: runc вызывает clone() с флагами новых namespaces, создаёт cgroup в /sys/fs/cgroup, монтирует overlayfs и запускает процесс. На Windows таких открытых примитивов нет. Контейнер создаёт системная служба — HCS (Host Compute Service, служба vmcompute, «Hyper-V Host Compute Service»). У неё есть API: «создай вычислительную систему с такими слоями, такой сетевой конечной точкой, такими лимитами», «запусти в ней процесс», «останови». Всё, что запускает контейнеры на Windows — Docker, containerd, — работает через HCS, обычно с помощью Go-библиотеки hcsshim от Microsoft.
Что HCS делает внутри. Основа — job object (объект задания), механизм ядра Windows, существующий задолго до контейнеров. Job object объединяет группу процессов: все процессы, порождённые членом задания, тоже попадают в него, а на задание целиком можно наложить ограничения — долю процессорного времени, лимит памяти, число процессов, приоритет ввода-вывода — и одной операцией завершить все его процессы. По роли это ближе всего к cgroup в Linux (и не имеет ничего общего с объектом Job в Kubernetes из части 9).
Для контейнера job object расширяется до server silo — так Windows называет job object, у которого есть собственные копии пространств имён ОС (дальше для краткости просто silo):
- файловая система: драйвер-фильтр wcifs (Windows Container Isolation File System) собирает диск
C:контейнера из слоёв образа и записываемого слоя контейнера — по смыслу так же, как overlayfs на Linux; - реестр: свои кусты
HKLM\SOFTWARE,HKLM\SYSTEMи остальные, взятые из образа; изменения в реестре контейнера не видны хосту; - пространство имён Object Manager: именованные объекты ядра (мьютексы, события, секции памяти, символические ссылки вроде
\DosDevices\C:) у каждого контейнера свои; - идентификаторы процессов и потоков;
- сеть: свой сетевой отсек (network compartment) — изолированный набор интерфейсов, маршрутов и таблиц, аналог сетевого namespace; о нём — в части 37.
Документация Microsoft называет это изоляцией пространств имён, и по смыслу это действительно namespaces, только собранные в один объект и создаваемые одной службой, а не набором системных вызовов.
Свои системные процессы
Есть и принципиальное отличие от Linux. В Linux граница между ядром и пользовательским пространством — это системные вызовы, и она стабильна десятилетиями: бинарник, собранный под старое ядро, работает на новом. В Windows граница проходит иначе: приложения вызывают не ядро, а системные DLL (ntdll.dll, kernel32.dll и другие) и системные службы, и протокол между ними и ядром меняется от сборки к сборке. Поэтому Windows-контейнер несёт в образе всю пользовательскую часть ОС — системные библиотеки и службы — и запускает свой набор системных процессов:
┌─ хост k8s-lab-win-1 ─────────────────────────────────────────────────┐
│ ┌─ server silo: контейнер aspnetapp ───────────────────────────────┐ │
│ │ smss.exe csrss.exe wininit.exe services.exe lsass.exe │ │
│ │ svchost.exe … CExecSvc.exe AspNetCoreRazorApp.exe │ │
│ │ │ │
│ │ C:\ wcifs: слои образа + записываемый слой контейнера │ │
│ │ реестр свои кусты из образа │ │
│ │ объекты своё пространство имён Object Manager │ │
│ │ сеть отсек пода, vNIC в Hyper-V vSwitch │ │
│ │ ресурсы job object: лимиты процессора и памяти │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ общее ядро сборки 10.0.26100: ntoskrnl.exe, драйверы │
└──────────────────────────────────────────────────────────────────────┘
csrss.exe (подсистема Win32), wininit.exe (запуск сеанса), services.exe (диспетчер служб), lsass.exe (проверка подлинности и политики безопасности) — те же процессы, что запускаются при загрузке обычной Windows, только внутри silo. Запускает их, как и в обычной Windows, диспетчер сеансов smss.exe, и у контейнера он свой: живёт всё время работы контейнера и входит в его объект задания. Работает он в сеансе 0, поэтому выборка процессов по сеансу контейнера его не покажет. CExecSvc.exe (Container Execution Service) — служба, через которую HCS запускает в контейнере новые процессы: по запросу kubectl exec именно она создаёт внутри cmd.exe или другую программу из образа. А AspNetCoreRazorApp.exe — само приложение: пример веб-приложения на .NET из образа Microsoft на основе Nano Server, которым мы будем пользоваться в этом блоке. Отсюда два следствия. Образы Windows большие: базовый образ Server Core весит несколько гигабайт, минимальный Nano Server — около 200 МБ в сжатом виде, но в нём нет ни PowerShell, ни .NET Framework, ни IIS. И версия пользовательской части в образе должна подходить к ядру хоста — об этом следующий раздел.
Изоляция процессов и Hyper-V
У Windows-контейнеров два режима изоляции:
- process isolation (изоляция процессов) — описанное выше: silo на общем ядре хоста. Процессы контейнера — обычные процессы хоста, их видно в
Get-Processна узле, только в своём сеансе (колонкаSI). Это тот же подход, что у Linux-контейнеров; - Hyper-V isolation — каждый контейнер запускается в облегчённой служебной виртуальной машине со своим ядром Windows. Граница — гипервизор, а не ядро, и версия ядра в ВМ берётся под образ, поэтому образ может быть старше хоста.
Kubernetes поддерживает только изоляцию процессов. Документация говорит прямо: под Windows-контейнерами понимаются контейнеры с process isolation, Hyper-V isolation не поддерживается. В конфигурации containerd для Windows есть оба обработчика — runhcs-wcow-process (изоляция процессов, по умолчанию) и runhcs-wcow-hypervisor (Hyper-V), — и технически RuntimeClass можно направить на второй, но такую конфигурацию проект Kubernetes не тестирует и не поддерживает: не работают как задумано ограничения ресурсов, метрики, тома. Всё дальнейшее — про изоляцию процессов.
Версия образа и версия хоста
Раз образ несёт пользовательскую часть ОС, а ядро общее, они должны совпадать. Правило Microsoft для изоляции процессов: сборка ОС в образе должна совпадать со сборкой хоста. Ревизии совпадать не обязаны (это требование отменили начиная с Windows Server 1809): узел с обновлением за октябрь запустит образ, собранный на базе сентябрьского. Но образ ltsc2022 (10.0.20348) на хосте 10.0.26100 — другая сборка.
Из правила есть одно задокументированное исключение: Windows Server 2025 запускает с изоляцией процессов образы Windows Server 2022. В обратную сторону не работает: хост 2022 образ ltsc2025 не запустит, а образы 2019 и 2016 на хосте 2025 работают только с Hyper-V isolation, то есть в Kubernetes — никак. Документация Kubernetes при этом формулирует правило строго: версия ОС хоста должна совпадать с версией базового образа. В тестовом кластере мы этого и держимся: под k8s-lab-win-1 образы собираются на базе ltsc2025. Исключение полезно как запас на время перехода с 2022 на 2025, но строить на нём постоянную схему не стоит.
| Хост \ образ | ltsc2019 |
ltsc2022 |
ltsc2025 |
|---|---|---|---|
| Windows Server 2022 | нет | да | нет |
| Windows Server 2025 | нет | да (исключение) | да |
Если под всё-таки получил неподходящий образ, ошибку видно в событиях пода. Когда у образа есть манифест для нескольких платформ, containerd не найдёт подходящий вариант ещё на скачивании (no match for platform in manifest). Когда образ одноплатформенный, он скачается, но HCS откажется запускать контейнер с ошибкой «The operating system of the container does not match the operating system of the host» (0xc0370101), и под уйдёт в CrashLoopBackOff.
Защищаются от этого два способа. Первый — планирование: на каждом Windows-узле kubelet ставит метку node.kubernetes.io/windows-build со сборкой (10.0.26100 у нас), и поды выбирают узлы по ней — ниже это сделает RuntimeClass. Второй — мультиплатформенные образы: один тег, под которым лежат варианты для Linux и для разных сборок Windows, а в манифесте Windows-варианта указано поле os.version. containerd на узле выбирает вариант своей сборки. Как собирать такие образы, будет в части 39, там же — что делать с образами при переходе узлов на новую версию Windows Server.
kubelet и containerd на Windows
В части 2 kubelet был службой systemd, а containerd слушал Unix-сокет. На Windows роли те же, а механизмы свои.
Linux-узел Windows-узел
kubelet: юнит systemd kubelet: служба Windows
│ CRI по /run/containerd/ │ CRI по именованному каналу
│ containerd.sock │ \\.\pipe\containerd-containerd
▼ ▼
containerd: юнит systemd containerd: служба Windows
│ │
▼ ▼
containerd-shim-runc-v2 containerd-shim-runhcs-v1.exe
│ запуск runc │ hcsshim: вызовы HCS
▼ ▼
runc: clone(), cgroupfs HCS: служба vmcompute
│ │
▼ ▼
ядро: namespaces, cgroups ядро: server silo, job object,
wcifs, сетевой отсек
CNI → veth, маршрут, nftables CNI → HNS: служба hns → VFP
Службы Windows. Аналог systemd в Windows — диспетчер управления службами (Service Control Manager, процесс services.exe). Служба — исполняемый файл, зарегистрированный в SCM с параметрами: тип запуска (автоматически при загрузке, вручную, отключена), учётная запись, от имени которой она работает, зависимости от других служб и действия при сбое (перезапустить через N секунд — то, что в systemd делает Restart=always). Процесс службы должен уметь разговаривать с SCM: сообщать, что запустился, принимать команды остановки. containerd регистрирует себя сам командой containerd.exe --register-service и работает от имени LocalSystem — встроенной учётной записи с полными правами на машине, как root. kubelet тоже умеет работать службой (флаг --windows-service); распространённые скрипты установки из проекта sig-windows-tools вместо этого запускают его через обёртку nssm, которая превращает в службу любую программу. Какой вариант выбрать и как прописать зависимость kubelet от containerd — в части 38.
CRI через именованный канал. Вместо Unix-сокета containerd на Windows принимает gRPC-запросы CRI на именованном канале (named pipe) \\.\pipe\containerd-containerd. Именованный канал — объект ядра Windows для обмена данными между процессами, с именем в пространстве \\.\pipe\ и списком доступа, по смыслу то же, что Unix-сокет в файловой системе. В конфигурации kubelet он записывается как npipe:////./pipe/containerd-containerd. Сам протокол CRI тот же: RunPodSandbox, CreateContainer, PullImage и т. д.
Shim и отсутствие runc. Для каждого пода containerd запускает containerd-shim-runhcs-v1.exe. Роль у него та же, что у containerd-shim-runc-v2 на Linux: он родитель контейнеров пода, держит их ввод-вывод и переживает перезапуск containerd. Но runc нет: shim через hcsshim просит HCS создать silo и запустить в нём процесс. Какой обработчик использовать, kubelet передаёт в RunPodSandbox — это поле handler RuntimeClass, о котором ниже. По умолчанию в containerd для Windows выбран runhcs-wcow-process (WCOW — Windows Containers on Windows).
Pause-контейнер есть и здесь: образ registry.k8s.io/pause мультиплатформенный, с вариантами для Windows. Песочница пода — это контейнер pause с сетевым отсеком и конечной точкой HNS, к которым присоединяются остальные контейнеры пода. Общей у контейнеров Windows-пода может быть только сеть: общих IPC, пространства процессов (shareProcessNamespace) и UTS, как в части 6, на Windows нет.
Каталоги. Пути kubelet повторяют Linux с диском впереди: C:\var\lib\kubelet (каталоги подов, конфигурация config.yaml, сертификаты в pki), C:\etc\kubernetes (kubelet.conf, CA кластера), C:\var\log\pods (логи контейнеров в том же формате CRI, что на Linux), CNI-плагины в C:\opt\cni\bin, их конфигурация — в C:\etc\cni\net.d. Образы и слои containerd хранит в C:\ProgramData\containerd.
Отличия kubelet. Часть возможностей kubelet на Windows не работает, потому что опирается на cgroups:
- нет cgroup пода и иерархии QoS: kubelet запускается с
cgroupsPerQOS: falseи пустымenforceNodeAllocatable; kubeReservedиsystemReservedтолько уменьшаютallocatableузла для планировщика, но ничего не резервируют на уровне ОС: никакая граница не мешает подам съесть эту память;- kubelet не выселяет поды при нехватке памяти (OOM eviction) и не выставляет условие
PIDPressure; - зато есть флаг
--windows-priorityclass: приоритет процесса kubelet в планировщике Windows. Документация советуетABOVE_NORMAL_PRIORITY_CLASS, чтобы нагруженные поды не оставили kubelet без процессорного времени и он не перестал продлевать Lease.
А сеть?
Сеть — самое непохожее в Windows-узле, и у неё будет своя часть. Здесь достаточно знать, кто за неё отвечает. На Linux сеть пода — это veth-пара, маршрут и правила nftables, которые CNI-плагин и kube-proxy создают сами. На Windows ни netfilter, ни veth нет: сеть контейнеров строит системная служба HNS (Host Networking Service, служба hns) на виртуальном коммутаторе Hyper-V — так же, как контейнеры создаёт HCS, — а правила на портах коммутатора исполняет его расширение VFP (Virtual Filtering Platform). CNI-плагин и kube-proxy на Windows-узле не пишут правила сами, а просят HNS. Как это устроено, какие сетевые плагины так умеют и что из этого следует для нашего кластера — в части 37.
HostProcess-контейнеры
Агент сетевого плагина и kube-proxy на Linux-узлах — привилегированные поды с hostNetwork: true (часть 2, часть 30): им нужно настраивать сеть самого узла. На Windows привилегированных контейнеров нет: изоляция пространств имён в silo включена всегда, отключить её флагом нельзя. Чтобы системные компоненты всё равно можно было ставить DaemonSet-ами, в Kubernetes есть HostProcess-контейнеры (стабильны с 1.26).
HostProcess-контейнер — это не silo, а обычный процесс хоста внутри job object. От контейнера у него остаются образ (доставка программы через реестр), жизненный цикл пода, тома и лимиты ресурсов задания. Изоляции нет: процесс видит файловую систему, реестр, процессы и сеть узла и работает от имени служебной учётной записи хоста. Содержимое образа доступно процессу по пути из переменной CONTAINER_SANDBOX_MOUNT_POINT, а тома с containerd 1.7 и новее монтируются по своим mountPath, как в обычных контейнерах.
apiVersion: v1
kind: Pod
metadata:
name: hpc-shell
namespace: kube-system
spec:
os:
name: windows
securityContext:
windowsOptions:
hostProcess: true
runAsUserName: "NT AUTHORITY\\SYSTEM"
hostNetwork: true
nodeSelector:
kubernetes.io/os: windows
tolerations:
- key: os
operator: Equal
value: windows
effect: NoSchedule
containers:
- name: shell
image: mcr.microsoft.com/oss/kubernetes/windows-host-process-containers-base-image:v1.0.0
command: ["powershell.exe", "-Command", "Start-Sleep -Seconds 3600"]
Обязательные поля:
windowsOptions.hostProcess: true— на уровне пода или каждого контейнера; смешивать HostProcess- и обычные контейнеры в одном поде нельзя;hostNetwork: true— единственный случай, когда Windows-под работает в сети узла;runAsUserName— одна из служебных учётных записей хоста:NT AUTHORITY\SYSTEM(LocalSystem, полные права),NT AUTHORITY\Local service(минимальные права, вход в сеть анонимно),NT AUTHORITY\NetworkService(минимальные права на машине, в сеть ходит с учётной записью компьютера) или, с containerd 1.7 и новее, локальная группа: тогда под получает временного пользователя в этой группе. Документация просит выбирать LocalService везде, где хватает его прав.
Базовый образ windows-host-process-containers-base-image почти пустой: процессу и так доступны программы хоста, в том числе powershell.exe, а сборка образа не важна, потому что silo нет. Такой под — консоль администратора узла через API, равнозначная привилегированному поду на Linux. Pod Security запрещает hostProcess уже на уровне baseline, такие поды допустимы только в неймспейсах уровня privileged, таких как kube-system.
Как поды попадают на свои узлы
В гибридном кластере две задачи: Windows-поды должны попадать только на Windows-узлы подходящей сборки, а Linux-поды — не попадать на Windows-узлы. Механизмы — те же метки, taints и nodeSelector из части 13, плюс два новых поля.
Метки узла и .spec.os.name
kubelet сам ставит узлу метки kubernetes.io/os (linux или windows), kubernetes.io/arch (amd64) и на Windows — node.kubernetes.io/windows-build со сборкой ОС. Значения для двух поддерживаемых версий: 10.0.20348 (2022) и 10.0.26100 (2025).
Поле пода .spec.os.name (linux или windows) говорит, для какой ОС написан под. Оно делает три вещи:
- проверка в API: для пода с
os.name: windowskube-apiserver отклоняет поля, которые на Windows не имеют смысла, —hostPID,hostIPC,shareProcessNamespace, Linux-поляsecurityContext(список ниже). Ошибка приходит сразу приkubectl apply, а не загадочным сбоем на узле; - проверка на узле: kubelet откажется запускать под, если
os.nameне совпадает с ОС узла, — под получит статусFailedс причинойPodOSNotSupported; - Pod Security: уровень
restrictedдля подов сos.name: windowsне требует трёх Linux-проверок —allowPrivilegeEscalation: false, профиля seccomp иcapabilities.drop: [ALL].
Чего это поле не делает — не влияет на планирование. Планировщик его не читает: под с os.name: windows без nodeSelector спокойно назначится на Linux-узел и там упадёт. Выбор узла — по-прежнему метки и taints.
Taint на Windows-узлах
Обратная задача важнее, чем кажется. В рабочем кластере десятки манифестов без nodeSelector — в основном кластере серии это hello, Traefik, cert-manager, всё из Helm-чартов. С появлением Windows-узла планировщик считает его подходящим для любого из этих подов, kubelet не находит в Linux-образе варианта для Windows, и под висит в ErrImagePull. Переписывать все чарты долго и ненадёжно, поэтому документация советует повесить на Windows-узлы taint, а Windows-подам дать на него toleration. Ключ и значение — договорённость, а не встроенное имя; общепринятый вариант — os=windows с эффектом NoSchedule. В части 38 узел получит его сразу при регистрации в кластере, чтобы между появлением узла и установкой taint на него не успел попасть ни один чужой под.
С таким taint обычные поды на Windows-узел не попадут. Исключение — поды с toleration на всё (operator: Exists без ключа), а это как раз системные DaemonSet-ы: сборщики логов, узловые плагины CSI, speaker MetalLB. Им taint не помеха, и на Windows-узле они получат свой под с Linux-образом, который никогда не запустится. Таким DaemonSet-ам нужен nodeSelector: kubernetes.io/os: linux. kube-proxy от kubeadm его уже содержит, остальные DaemonSet'ы тестового кластера мы проверим в части 38.
RuntimeClass
Windows-поду теперь нужно три вещи: nodeSelector по ОС, nodeSelector по сборке и toleration на taint. Писать их в каждый манифест неудобно, и при появлении узлов на другой версии Windows придётся менять все манифесты. Это решает RuntimeClass — объект уровня кластера, который описывает среду выполнения:
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: windows-2025
handler: runhcs-wcow-process
scheduling:
nodeSelector:
kubernetes.io/os: windows
kubernetes.io/arch: amd64
node.kubernetes.io/windows-build: "10.0.26100"
tolerations:
- key: os
operator: Equal
value: windows
effect: NoSchedule
handler— имя обработчика в конфигурации containerd, которое kubelet передаёт вRunPodSandbox.runhcs-wcow-process— изоляция процессов. На Linux тот же механизм выбирает, например, gVisor или Kata Containers вместо runc;scheduling.nodeSelectorиscheduling.tolerations— то, что добавится в под.
Под ссылается на класс одной строкой runtimeClassName: windows-2025. При создании пода плагин допуска RuntimeClass в kube-apiserver объединяет nodeSelector класса с nodeSelector пода (если ключи конфликтуют, под отклоняется) и добавляет tolerations класса к tolerations пода. Планировщик видит уже готовый под с обычными полями:
Deployment web: runtimeClassName: windows-2025, os.name: windows
│ ReplicaSet создаёт под
▼
kube-apiserver, admission RuntimeClass
+ nodeSelector: kubernetes.io/os=windows, kubernetes.io/arch=amd64,
node.kubernetes.io/windows-build=10.0.26100
+ tolerations: os=windows:NoSchedule
│
▼
kube-scheduler: фильтры NodeAffinity и TaintToleration
k8s-lab-master-1 метка os=linux, taint control-plane — не подходит
k8s-lab-node-1 метка os=linux — не подходит
k8s-lab-win-1 метки совпали, taint допускается — подходит
│
▼
kubelet k8s-lab-win-1: os.name совпадает с ОС узла
CRI RunPodSandbox, handler runhcs-wcow-process
Когда в кластере появятся узлы на Windows Server 2022, рядом ляжет класс windows-2022 с windows-build: "10.0.20348", и каждое приложение будет выбирать класс под сборку своего образа. При переводе узлов на новую версию меняется класс в манифесте вместе с тегом образа — одна строка вместо трёх полей.
Чего на Windows нет
Сводка того, что в Windows-поде не работает или работает иначе. Часть отклоняет API-сервер (если задан os.name: windows), часть просто не действует.
| Возможность | На Windows | Что вместо |
|---|---|---|
privileged: true |
нет: изоляцию silo нельзя отключить | HostProcess-контейнеры |
hostNetwork: true |
только для HostProcess | — |
hostPID, hostIPC, shareProcessNamespace |
нет, отклоняется API | у контейнеров пода общая только сеть |
runAsUser, runAsGroup, supplementalGroups, fsGroup |
нет, отклоняется API | windowsOptions.runAsUserName |
capabilities, seccompProfile, seLinuxOptions, procMount, allowPrivilegeEscalation, sysctls |
нет, отклоняется API | права определяются учётной записью и её группами |
AppArmor, user namespaces (hostUsers: false) |
нет | — |
readOnlyRootFilesystem |
нет: системным процессам контейнера нужна запись в реестр и на диск | — |
| HugePages | нет | — |
emptyDir с medium: Memory |
нет | обычный emptyDir на диске |
блочные тома volumeDevices, mountPropagation |
нет | — |
| Secret в томе | пишется на диск узла открытым текстом, а не в tmpfs | ACL на каталог, BitLocker на диске узла |
| OOM killer | нет | выделение памяти сверх лимита завершается ошибкой |
Пользователь контейнера. Вместо UID у Windows-контейнера имя учётной записи. В базовых образах две встроенные: ContainerAdministrator (администратор внутри контейнера) и ContainerUser (обычный пользователь). Образы на основе Server Core по умолчанию работают от ContainerAdministrator, на основе Nano Server — от ContainerUser. Сменить можно полем securityContext.windowsOptions.runAsUserName. Эти учётные записи существуют только внутри контейнера: у контейнера своя база учётных записей (SAM), и ContainerAdministrator не администратор хоста. Доменная учётная запись поду даётся через gMSA — часть 40.
runAsNonRoot: true на Windows тоже работает: kubelet считает «root» учётную запись ContainerAdministrator и откажется запускать контейнер от её имени. А restricted требует runAsNonRoot и для Windows-подов. Поэтому образ на Server Core, который работает от имени администратора контейнера (например, IIS со службой W3SVC), в неймспейс с уровнем restricted, как demo из части 30, не пройдёт, а образ на Nano Server, работающий от ContainerUser, пройдёт, если указать в поде runAsNonRoot: true. Наши Windows-примеры будут на Nano Server, и неймспейс win-demo для них получит тот же уровень restricted, что и demo.
Ресурсы: job objects вместо cgroups
На Linux kubelet переводит requests и limits в параметры cgroup (часть 10): cpu.weight для гарантированной доли процессора, cpu.max для потолка, memory.max для лимита памяти, после которого контейнер убивает OOM killer. На Windows всё это — свойства job object контейнера, и набор беднее:
limits.cpuпревращается в потолок процессорного времени задания: долю от всех процессоров узла в единицах 1/10000. Лимит500mна узле с четырьмя процессорами — это 1250, то есть 12,5 % суммарного времени;limits.memory— лимит выделенной (committed) памяти задания. Процесс, который пытается выделить память сверх лимита, получает ошибку выделения, а что будет дальше — решает само приложение; обычно оно падает с исключением нехватки памяти;requestsнужны только планировщику: гарантированной минимальной доли процессора Windows дать не может, весов, какcpu.weight, kubelet не задаёт.
Windows не переподписывает память, а файл подкачки обязателен: узел, где поды без лимитов съели всю память, не убивает процессы, а уходит в подкачку и замедляется целиком — вместе с kubelet. Поэтому документация советует всегда задавать Windows-подам limits, оставлять системе не меньше 2 ГиБ через systemReserved и поднимать приоритет kubelet.
Windows-узел встроен в кластер на уровне API так же, как Linux-узел, но внутри него всё своё: службы вместо юнитов systemd, HCS и silo вместо runc и namespaces, job objects вместо cgroups, HostProcess вместо привилегированных подов. Самое непохожее из этого — сеть.
Источники
- Windows containers in Kubernetes — поддерживаемые версии Windows Server, совместимость полей Pod API, ограничения узлов;
- Guide for scheduling Windows containers in Kubernetes —
.spec.os.name, taints, меткаwindows-build, RuntimeClass; - Resource Management for Windows nodes — job objects, память, процессор,
--windows-priorityclass; - Security For Windows Nodes и Pod Security Standards —
runAsUserName, Secret на диске, послабленияrestrictedдля Windows; - Create a Windows HostProcess Pod — HostProcess-контейнеры;
- Microsoft: Isolation modes и Windows container version compatibility — изоляция и матрица совместимости версий;
- sig-windows-tools — скрипты подготовки Windows-узлов.
Дальше — сеть Windows-узлов и выбор сетевого плагина: как HNS и VFP строят сеть подов, чем режимы l2bridge и overlay отличаются от Transparent и NAT, какие плагины работают на Windows и в каких режимах — Flannel, Calico, Antrea, ovn-kubernetes, облачные Azure CNI и Amazon VPC CNI, — kube-proxy в режиме winkernel и DSR, ограничения сети Windows-узлов и почему для гибридного кластера мы выбрали отдельный тестовый кластер, сразу собранный в VXLAN без BGP, а перевод учебного кластера с VXLANCrossSubnet и BGP оставили альтернативой со своими рисками.
Оставить комментарий могут только зарегистрированные пользователи.
Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.