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: windows kube-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-узлов и выбор сетевого плагина: как 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 оставили альтернативой со своими рисками.

Оставить комментарий могут только зарегистрированные пользователи.

Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.