Введение
Доброго времени суток. Меня зовут Андросов Михаил, по профессии я 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.

Установка свежих драйверов 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


Далее будут производиться привычные многим любителям 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 узел.

Подключение к 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, потому что не установлен сетевой плагин.


Установка сетевого плагина
Можете взять другой сетевой плагин. Для простоты и удобства беру Flannel.
Разверните самую свежую версию.
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
Подождите какое-то время и проверьте узел еще раз, он должен быть в состоянии 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 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

На этом подготовка 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.

Финальная проверка
Запустите 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

Если вывод есть - значит GPU доступно в кластере Kubernetes!
Заключение
Теперь можно использовать GPU как во взрослом кластере. Запускайте инференсы, ноутбуки, тестируйте Helm-чарты и делайте всё что хотели делать, но не могли делать без GPU.
Если убрать из статьи пояснения по командам и всё положить в скрипт, поднятие кластера с GPU на WSL можно сократить до каких-нибудь 10-15 минут времени.
Попробуйте, это очень удобно :)
Надеюсь, статья окажется для вас полезной.
denis_iii
По возможности, конечно лучше пробрасывать и запускать нативный линукс.
В WSL2 один специальный GPU linux драйвер ходит в другой windows драйвер и теряет в перфомансе и стабильности. Это видо в референсе vLLM. А под капотом WSL2 будет тот же Hyper-V.