Введение

Доброго времени суток. Меня зовут Андросов Михаил, по профессии я MLOps-инженер.

Я всегда использую Windows. Уже много лет я работаю через связку Visual Studio Code и WSL.
Мне регулярно приходится эксплуатировать GPU-зависимые приложения в кластере Kubernetes. Иногда так получается, что тестовые ресурсы заняты, а что-то проверить надо.
В моём домашнем ПК установлена NVIDIA RTX 4090 на 24 GB, что является довольно мощной видеокартой с большим объёмом памяти (для домашнего устройства).
Встал вопрос, как мне быстро развернуть домашний Kubernetes кластер с возможностью использовать мои GPU ресурсы? Решение есть!

В данной статье я покажу, как на Windows развернуть кластер Kubernetes из одного узла с поддержкой GPU и time-slicing, используя WSL.

Альтернативы WSL

Изучение просторов сети подсказало, что есть решение проброса GPU на Hyper-V (наверное, WSL под капотом его и использует).
Я его не проверял, мне это решение сходу кажется долгой дорогой - нужно создать ВМ, сконфигурировать ВМ, установить дистрибутив, скачать и установить драйверы.

Можно установить какой-нибудь дистрибутив Linux на свою домашнюю станцию. Но это тоже как-то неудобно.

Подготовка

Версия Windows

Вам понадобится любая современная версия Windows 10 или Windows 11. Если вы периодически устанавливаете обновления, то проблем не возникнет.

Установка WSL

Установить компоненты WSL и стандартный дистрибутив Ubuntu 24.04 можно одной командой.

wsl --install

⚠️ Для работы WSL должна быть включена виртуализация в BIOS/UEFI (Intel Virtualization Technology или AMD-V).

Подробности можно почитать у Microsoft

В статье буду производить развертывание кластера на дистрибутиве, запускаемом по умолчанию - Ubuntu 24.04.

Отключение swap в WSL

После установки WSL остановите его через PowerShell.

wsl --shutdown

Создайте файл C:\Users\%UserProfile%\.wslconfig (если его нет) и укажите в нём следующее значение.

[wsl2]
swap=0

Запуск дистрибутива в WSL

Нужный дистрибутив в WSL можно запустить через команду wsl в PowerShell или, как я сам его запускаю, через Visual Studio Code.

Возможность подключения через Visual Studio Code
Возможность подключения через Visual Studio Code

Установка свежих драйверов NVIDIA

Можете установить их вручную или через Nvidia App (пользуюсь, удобно).
Нужны обычные пользовательские драйверы - Game Ready.
https://www.nvidia.com/en-eu/geforce/drivers/

Статический IP адрес для WSL

Сеть WSL по умолчанию работает в режиме NAT. WSL при рестарте может получить новый IP-адрес, что помешает нормальной работе кластера. Необходимо зафиксировать IP адрес для интерфейса в системе внутри WSL.

Если у вас Windows 11

Есть множество способов задать статический IP адрес.
Например, включите networkingMode=mirrored в C:\Users\%UserProfile%\.wslconfig и задайте IP адрес из вашей домашней сети через сетевой адаптер в «Настройках параметров адаптера».

Если у вас Windows 10

Это мой сценарий, пока не переехал на Windows 11. Здесь уже сложнее.
Можно попробовать установить сетевой адаптер WSL в режим работы «Внешняя сеть» через Диспетчер Hyper-V или PowerShell. В таком случае, он, как и домашний ПК будет получать IP адрес от вашего роутера, который можно будет зафиксировать. Но это работает ненадежно и может не получиться. На разных ПК было по-разному, где-то получилось, где-то нет.
Есть разные способы решения проблемы, но я для себя нашёл самый быстрый и простой - это поднять интерфейс WireGuard внутри системы в WSL.

В любом случае, на этом подробно останавливаться не буду. Делайте так, как считаете правильным.

(Опционально) Установка и настройка WireGuard в WSL

Установите WireGuard и сгенерируйте ключ.

sudo apt update
sudo apt install -y wireguard wireguard-tools

sudo mkdir -p /etc/wireguard
wg genkey | sudo tee /etc/wireguard/wg0.key
sudo chmod 600 /etc/wireguard/wg0.key

Создайте конфигурационный файл wg0.conf.

sudo tee /etc/wireguard/wg0.conf >/dev/null <<EOF
[Interface]
Address = 10.8.0.1/32
ListenPort = 51820
PrivateKey = $(sudo cat /etc/wireguard/wg0.key)
EOF

И поднимите интерфейс wg0.

sudo systemctl enable --now wg-quick@wg0

Выполните проверки и убедитесь, что всё работает.

systemctl status wg-quick@wg0
ping 10.8.0.1
Служба запущена
Служба запущена
Ping есть
Ping есть

Далее будут производиться привычные многим любителям Kubernetes настройки.

Подготовка системы для Kubernetes

Обновите вашу систему в WSL и установите перечисленные пакеты.

sudo apt update && sudo apt upgrade -y
sudo apt install -y apt-transport-https ca-certificates curl gnupg

Включите нужные модули ядра.

sudo tee /etc/modules-load.d/k8s.conf <<EOF
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

И сетевые параметры.

sudo tee /etc/sysctl.d/k8s.conf <<EOF
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF

sudo sysctl --system

Подключение репозиториев, установка и настройка containerd

Нужно подключить репозитории Docker и Kubernetes.
На момент чтения статьи, проверьте, какая версия Kubernetes самая свежая и подставьте её версию.

Kubernetes репозиторий.

ver="v1.34"
curl -fsSL https://pkgs.k8s.io/core:/stable:/$ver/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
sudo chmod 644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg # allow unprivileged APT programs to read this keyring
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/$ver/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo chmod 644 /etc/apt/sources.list.d/kubernetes.list  # helps tools such as command-not-found to work correctly

Docker репозиторий.

sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Signed-By: /etc/apt/keyrings/docker.asc
EOF

Установите containerd.

sudo apt update
sudo apt install containerd.io -y

По умолчанию, конфигурационный файл containerd почти пустой.
Его необходимо сгенерировать и включить использование cgroup.

sudo containerd config default | sudo tee /etc/containerd/config.toml > /dev/null
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml

Теперь нужно перезапустить службу containerd и добавить её в автозагрузку.

sudo systemctl restart containerd
sudo systemctl enable containerd

Осталось установить компоненты Kubernetes.

sudo apt install kubelet kubeadm kubectl -y

Инициализация Kubernetes кластера

Если у вас Windows 11 и/или вы как то иначе сделали себе статичный IP адрес в WSL, подставьте его.

myip="10.8.0.1"
sudo kubeadm init --apiserver-advertise-address=$myip --pod-network-cidr=10.244.0.0/16

Результатом должен стать поднятый control-plane узел.

Control-plane успешно инициализирован
Control-plane успешно инициализирован

Подключение к Kubernetes кластеру

Скопируйте конфигурационный файл с контекстом себе в домашний каталог.

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

Теперь можно проверить, что kubectl работает.

kubectl get no

Состояние узла точно будет NotReady, потому что не установлен сетевой плагин.

Состояние узла NotReady
Состояние узла NotReady
Состояние (describe) узла подсказывает, что сеть не готова
Состояние (describe) узла подсказывает, что сеть не готова

Установка сетевого плагина

Можете взять другой сетевой плагин. Для простоты и удобства беру Flannel.
Разверните самую свежую версию.

kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml

Подождите какое-то время и проверьте узел еще раз, он должен быть в состоянии Ready.

Теперь узел в состоянии Ready
Теперь узел в состоянии Ready

Снятие taint с узла

Рекомендую сразу снять taint control-plane с узла, чтобы не приходилось дальше везде указывать tolerations.

kubectl taint no --all node-role.kubernetes.io/control-plane-

Кластер запущен и работает. Теперь подготовим его для работы с GPU.


Подготовка GPU к работе в кластере Kubernetes

Проверка доступности GPU в WSL

На самом деле, при самом первом старте системы в WSL можно сразу проверить что ваша GPU доступна.
Для этого нужно выполнить команду nvidia-smi и получить вывод по вашей GPU (если у вас нет проблем с драйверами).

Чтобы работала команда nvidia-smi, никаких доп. компонентов устанавливать не нужно
Чтобы работала команда nvidia-smi, никаких доп. компонентов устанавливать не нужно

Что остается сделать:

  • Установить NVIDIA Container Toolkit.

  • Настроить containerd.

  • Установить и сконфигурировать NVIDIA Device Plugin.

Установка NVIDIA Container Toolkit и настройка containerd

Добавьте репозиторий от Nvidia.

curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
&& curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
   sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
   sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list

Установите NVIDIA Container Toolkit.
Проверьте актуальную версию в инструкции.

sudo apt update
export NVIDIA_CONTAINER_TOOLKIT_VERSION=1.18.1-1
sudo apt-get install -y \
     nvidia-container-toolkit=${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
     nvidia-container-toolkit-base=${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
     libnvidia-container-tools=${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
     libnvidia-container1=${NVIDIA_CONTAINER_TOOLKIT_VERSION}

Теперь нужно сконфигурировать containerd новым runtime Nvidia с помощью nvidia-ctk.

После конфигурации, службу containerd нужно обязательно перезапустить.

sudo nvidia-ctk runtime configure --runtime=containerd --set-as-default --config=/etc/containerd/config.toml
sudo systemctl restart containerd
sudo systemctl status containerd
Вывод от nvidia-ctk
Вывод от nvidia-ctk

На этом подготовка GPU в системе для Kubernetes завершена.

Установка и настройка NVIDIA Device Plugin

Можно установить NVIDIA Device Plugin сразу из этого манифеста и больше ничего не делать.

Но для локальных экспериментов, одной GPU мне будет мало и я хочу дополнительно настроить Time-Slicing, что бы можно было запросить у кластера хотя бы 2 GPU ресурса.

Для этого нужно создать ConfigMap , указанный ниже.

kubectl apply -f - <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
  name: nvidia-device-plugin-config
  namespace: kube-system
data:
  config.yaml: |-
    version: v1
    sharing:
      timeSlicing:
        resources:
        - name: nvidia.com/gpu
          replicas: 2 # сколько "кусков" будет из одной GPU
EOF

Теперь нужно создать DaemonSet, где подключим созданную конфигурацию для NVIDIA Device Plugin через ConfigMap.
Проверьте актуальную версию образа k8s-device-plugin и подставьте свежую, если уже вышла.

kubectl apply -f - <<'EOF'
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: nvidia-device-plugin-daemonset
  namespace: kube-system
spec:
  selector:
    matchLabels:
      name: nvidia-device-plugin-ds
  updateStrategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        name: nvidia-device-plugin-ds
    spec:
      tolerations:
      - key: nvidia.com/gpu
        operator: Exists
        effect: NoSchedule
      priorityClassName: "system-node-critical"
      containers:
      - image: nvcr.io/nvidia/k8s-device-plugin:v0.18.0
        name: nvidia-device-plugin-ctr
        env:
        - name: CONFIG_FILE
          value: /config/config.yaml
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop: ["ALL"]
        volumeMounts:
        - name: kubelet-device-plugins-dir
          mountPath: /var/lib/kubelet/device-plugins
        - name: nvidia-device-plugin-config
          mountPath: /config
          readOnly: true
      volumes:
      - name: kubelet-device-plugins-dir
        hostPath:
          path: /var/lib/kubelet/device-plugins
          type: Directory
      - name: nvidia-device-plugin-config
        configMap:
          name: nvidia-device-plugin-config
EOF

После запуска pod'a, проверьте его логи. Не должно быть ошибок по инициализации вашей GPU.

kubectl -n kube-system logs ds/nvidia-device-plugin-daemonset
В случае возникновения ошибок, это будет сразу видно в логе последним сообщением
В случае возникновения ошибок, это будет сразу видно в логе последним сообщением

Также при выполнении команды kubectl describe no , теперь вы должны увидеть 2 доступных GPU.

Можно запрашивать у Kubernetes ресурс GPU
Можно запрашивать у Kubernetes ресурс GPU

Финальная проверка

Запустите pod, который выполнит в контейнере команду nvidia-smi.

kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: nvidia-smi
spec:
  restartPolicy: OnFailure
  containers:
    - name: nvidia-smi
      image: nvidia/cuda:13.0.2-runtime-ubuntu24.04
      command: ["nvidia-smi"]
      resources:
        limits:
          nvidia.com/gpu: 1
EOF

Скачивание образа займет какое-то время. Как контейнер отработает, проверьте его логи.

kubectl logs nvidia-smi
Вывод nvidia-smi внутри pod'a
Вывод nvidia-smi внутри pod'a

Если вывод есть - значит GPU доступно в кластере Kubernetes!

Заключение

Теперь можно использовать GPU как во взрослом кластере. Запускайте инференсы, ноутбуки, тестируйте Helm-чарты и делайте всё что хотели делать, но не могли делать без GPU.
Если убрать из статьи пояснения по командам и всё положить в скрипт, поднятие кластера с GPU на WSL можно сократить до каких-нибудь 10-15 минут времени.
Попробуйте, это очень удобно :)
Надеюсь, статья окажется для вас полезной.

Комментарии (1)


  1. denis_iii
    07.12.2025 13:22

    По возможности, конечно лучше пробрасывать и запускать нативный линукс.
    В WSL2 один специальный GPU linux драйвер ходит в другой windows драйвер и теряет в перфомансе и стабильности. Это видо в референсе vLLM. А под капотом WSL2 будет тот же Hyper-V.