Инструкция «Kubernetes для LLM»: практика развёртывания одноузлового кластера на Rocky Linux 9 с поддержкой GPU.

Добро пожаловать в инструкцию по Kubernetes для LLM! Здесь — практические шаги для развёртывания одноузлового кластера на Rocky Linux 9 (или совместимых: AlmaLinux 9, RHEL 9) под запуск языковых моделей и приложений. Скрипты проверены в реальных условиях и позволяют быстро поднять кластер с поддержкой GPU, прокси и GitLab Runner.

Но давайте по порядку!

Сначала убедимся, что сервер готов: установлена Rocky Linux 9, при необходимости — драйверы NVIDIA (если планируете GPU). Для загрузки образов через прокси понадобится работающий прокси на порту 1080. После этого переходим к установке кластера.


Предварительные требования

Требование Описание
ОС Rocky Linux 9 (или AlmaLinux 9, RHEL 9)
Права Запуск команд от root (sudo)
Docker Установлен и запущен (для импорта образов в containerd)
NVIDIA Драйверы и nvidia-smi для работы GPU
Прокси Порт 1080 — если образы тянутся через прокси

Проверка перед стартом:

sudo dnf update -y
sudo dnf install -y curl wget jq git
docker --version
nvidia-smi  

Общий порядок установки

  1. Установка кластера — проверка окружения и установка kubeadm.
  2. Прокси — настройка containerd для загрузки образов через прокси (до загрузки образов NVIDIA и т.д.).
  3. NVIDIA Device Plugin — установка DaemonSet для доступа к GPU (образ тянется через прокси, если настроен).
  4. NVIDIA runtime в containerd — настройка runtime для подов с GPU.
  5. Проверка кластера — ноды, поды, GPU.
  6. Сеть (при проблемах) — настройка Flannel, CoreDNS, kube-proxy.
  7. Образы и registry — импорт образов из Docker в containerd или выгрузка в приватный registry.
  8. GitLab Runner — выдача kubeconfig и sudo для пользователя gitlab-runner.
  9. Хранилище — LVM для OpenEBS Local PV.

Установка и настройка

Шаг 1. Установка кластера

Проверка и установка кластера — рекомендуемая точка входа. Нужны Docker и containerd (при первой установке).

Установка kubeadm — основной шаг: подготовка системы, containerd, kubectl/kubeadm/kubelet (v1.33.6), инициализация кластера, Flannel, kubeconfig.

Важно: при уже существующем кластере сначала выполните сброс (раздел «Сброс кластера» ниже).

Подготовка системы (перед kubeadm init):

# Отключение swap
sudo swapoff -a
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab

# Модули ядра
echo -e "overlay\nbr_netfilter" | sudo tee /etc/modules-load.d/k8s.conf
sudo modprobe overlay
sudo modprobe br_netfilter

# Sysctl для сети
echo -e "net.bridge.bridge-nf-call-iptables  = 1\nnet.bridge.bridge-nf-call-ip6tables = 1\nnet.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/k8s.conf
sudo sysctl --system

# Containerd для Kubernetes (если ещё не настроен)
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl restart containerd
sudo systemctl enable containerd

Шаг 2. Прокси для containerd

Прокси для containerd — настройка загрузки образов через прокси. Выполнять до загрузки образов (NVIDIA Device Plugin и т.д.). Прокси должен слушать порт 1080.

# Проверка прокси
ss -tulpn | grep ":1080"

# Создание override для containerd
sudo mkdir -p /etc/systemd/system/containerd.service.d
sudo tee /etc/systemd/system/containerd.service.d/http-proxy.conf << 'EOF'
[Service]
Environment="HTTP_PROXY=socks5://127.0.0.1:1080"
Environment="HTTPS_PROXY=socks5://127.0.0.1:1080"
Environment="NO_PROXY=localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12"
EOF

sudo systemctl daemon-reload
sudo systemctl restart containerd

Шаг 3. NVIDIA Device Plugin

NVIDIA Device Plugin — DaemonSet для доступа к GPU. Кластер уже установлен. Если нужен прокси для образа nvcr.io — выполнить шаг 2 до этого.

export KUBECONFIG=/etc/kubernetes/super-admin.conf
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
  template:
    metadata:
      labels:
        name: nvidia-device-plugin-ds
    spec:
      hostNetwork: true
      tolerations:
      - key: nvidia.com/gpu
        operator: Exists
        effect: NoSchedule
      - operator: Exists
      containers:
      - image: nvcr.io/nvidia/k8s-device-plugin:v0.14.1
        imagePullPolicy: IfNotPresent
        name: nvidia-device-plugin-ctr
        args: ["--fail-on-init-error=false"]
        securityContext:
          privileged: true
        volumeMounts:
        - name: device-plugin
          mountPath: /var/lib/kubelet/device-plugins
        - name: dev
          mountPath: /dev
        - name: nvidia-libs
          mountPath: /usr/lib/x86_64-linux-gnu
          readOnly: true
        - name: nvidia-libs64
          mountPath: /usr/lib64
          readOnly: true
      volumes:
      - name: device-plugin
        hostPath:
          path: /var/lib/kubelet/device-plugins
      - name: dev
        hostPath:
          path: /dev
      - name: nvidia-libs
        hostPath:
          path: /usr/lib/x86_64-linux-gnu
      - name: nvidia-libs64
        hostPath:
          path: /usr/lib64
EOF

Шаг 4. NVIDIA runtime в containerd

NVIDIA runtime в containerd — runtime для подов с GPU. Требуются драйверы и nvidia-smi. Установить NVIDIA Container Toolkit: sudo dnf install -y nvidia-container-toolkit.

Добавить в /etc/containerd/config.toml после секции [plugins...runtimes.runc]:

        [plugins."io.containerd.cri.v1.runtime".containerd.runtimes.nvidia]
          runtime_type = "io.containerd.runc.v2"
          [plugins."io.containerd.cri.v1.runtime".containerd.runtimes.nvidia.options]
            BinaryName = "nvidia-container-runtime"

Перезапуск и создание RuntimeClass:

sudo systemctl daemon-reload
sudo systemctl restart containerd

export KUBECONFIG=/etc/kubernetes/super-admin.conf
kubectl apply -f - <<'EOF'
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: nvidia
handler: nvidia
EOF

Шаг 5. Сеть (при проблемах)

Настройка сети кластера — Flannel, CoreDNS, kube-proxy, subnet.env.

# Создание subnet.env для Flannel
sudo mkdir -p /run/flannel
sudo tee /run/flannel/subnet.env << 'EOF'
FLANNEL_NETWORK=10.244.0.0/16
FLANNEL_SUBNET=10.244.0.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true
EOF

# Установка Flannel (если ещё не установлен)
export KUBECONFIG=/etc/kubernetes/super-admin.conf
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

# Загрузка модулей IPVS для kube-proxy
sudo modprobe ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack

# Перезапуск CoreDNS и kube-proxy
kubectl delete pod -n kube-system -l k8s-app=kube-dns --ignore-not-found=true
kubectl delete pod -n kube-system -l k8s-app=kube-proxy --ignore-not-found=true

Когда использовать: после установки при проблемах с сетью или после перезагрузки сервера.


Шаг 6. Образы

Импорт образов из Docker в containerd — образ должен быть в Docker.

# Импорт одного образа: экспорт из Docker → импорт в containerd (namespace k8s.io)
IMAGE="llm-api-server:latest"
sudo docker save "$IMAGE" | sudo ctr -n=k8s.io images import -

# Проверка
sudo crictl images | grep llm

Когда использовать: если образы собираются в Docker, а кластер работает на containerd.


Выгрузка образов в registry — пушит образы кластера в приватный registry (URL по умолчанию registry.example.ru). Выполнять после настройки доступа к registry (docker login и т.п.).


Шаг 7. GitLab Runner

Kubeconfig для gitlab-runner — доступ пользователя gitlab-runner к кластеру (пользователь должен существовать).

GITLAB_RUNNER_HOME=$(getent passwd gitlab-runner | cut -d: -f6)
sudo mkdir -p "$GITLAB_RUNNER_HOME/.kube"
sudo chown gitlab-runner:gitlab-runner "$GITLAB_RUNNER_HOME/.kube"
sudo chmod 700 "$GITLAB_RUNNER_HOME/.kube"

# Копирование kubeconfig (один из путей)
sudo cp /etc/kubernetes/super-admin.conf "$GITLAB_RUNNER_HOME/.kube/config"
# или: sudo cp /root/.kube/config "$GITLAB_RUNNER_HOME/.kube/config"
sudo chown gitlab-runner:gitlab-runner "$GITLAB_RUNNER_HOME/.kube/config"
sudo chmod 600 "$GITLAB_RUNNER_HOME/.kube/config"

# Проверка
sudo -u gitlab-runner kubectl get nodes

Sudo для gitlab-runner — права для копирования kubeconfig и т.п.

sudo tee /etc/sudoers.d/gitlab-runner-k8s << 'EOF'
# Разрешения для gitlab-runner для работы с Kubernetes
gitlab-runner ALL=(ALL) NOPASSWD: /bin/cp /etc/kubernetes/super-admin.conf /home/gitlab-runner/.kube/config
gitlab-runner ALL=(ALL) NOPASSWD: /bin/cp /root/.kube/config /home/gitlab-runner/.kube/config
gitlab-runner ALL=(ALL) NOPASSWD: /bin/chown gitlab-runner\:gitlab-runner /home/gitlab-runner/.kube/config
gitlab-runner ALL=(ALL) NOPASSWD: /bin/chmod 600 /home/gitlab-runner/.kube/config
gitlab-runner ALL=(ALL) NOPASSWD: /bin/mkdir -p /home/gitlab-runner/.kube
EOF
sudo chmod 440 /etc/sudoers.d/gitlab-runner-k8s
sudo visudo -c -f /etc/sudoers.d/gitlab-runner-k8s

Требования: пользователь gitlab-runner уже создан, кластер установлен.


Шаг 8. Хранилище (OpenEBS Local PV LVM)

LVM для OpenEBS Local PV — подготовка диска и Volume Group для OpenEBS.

  • Использует диск /dev/nvme0n1 (второй NVMe).
  • Создаёт Physical Volume и Volume Group openebs-vg. Все данные на диске будут удалены (подтверждение при запуске).

Когда использовать: один раз на каждом узле перед установкой OpenEBS Local PV LVM. Диск /dev/nvme0n1, все данные на нём будут удалены.

# Проверка: диск не смонтирован и не в LVM
lsblk /dev/nvme0n1
mount | grep nvme0n1   # должно быть пусто

# Удаление подписей с диска (осторожно!)
sudo wipefs -a /dev/nvme0n1
sudo partprobe /dev/nvme0n1

# Создание Physical Volume и Volume Group
sudo pvcreate -y /dev/nvme0n1
sudo vgcreate openebs-vg /dev/nvme0n1

# Проверка
sudo pvs
sudo vgs openebs-vg

Сброс кластера (при переустановке)

Сброс кластера — полное удаление кластера (kubelet, поды, конфигурация, etcd, iptables, CNI).

⚠️ Внимание: все данные кластера будут удалены.

# Остановка и сброс
export KUBECONFIG=/etc/kubernetes/super-admin.conf
kubectl delete -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml 2>/dev/null || true
sudo systemctl stop kubelet
sudo rm -rf /etc/kubernetes/manifests/*.yaml
sudo kubeadm reset -f

# Очистка iptables и данных
sudo iptables -F && sudo iptables -t nat -F && sudo iptables -t mangle -F && sudo iptables -X
sudo ipvsadm -C 2>/dev/null || true
sudo rm -rf /etc/cni/net.d /var/lib/cni /var/lib/etcd /etc/kubernetes /root/.kube /run/flannel
sudo rm -f /etc/systemd/system/kubelet.service /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
sudo systemctl daemon-reload

# Сетевые интерфейсы
sudo ip link delete cni0 2>/dev/null || true
sudo ip link delete flannel.1 2>/dev/null || true

После сброса выполнить заново Шаг 1 (Установка кластера) и при необходимости Шаг 5 (Сеть).


Порядок выполнения по сценариям

Минимальная установка (без GPU)

  1. Шаг 1 — Установка кластера (подготовка системы и полная установка через kubeadm — см. README в папке для полной последовательности).
  2. Шаг 5 — Сеть (при проблемах с подами или сетью).

Проверка:

kubectl get nodes
kubectl get pods -n kube-system

Полная установка (с GPU)

  1. Шаг 1 — Установка кластера.
  2. Шаг 2 — Прокси для containerd (если образы тянутся через прокси).
  3. Шаг 3 — NVIDIA Device Plugin (образ nvcr.io тянется через прокси, если настроен).
  4. Шаг 4 — NVIDIA runtime в containerd.
  5. Шаг 5 — Сеть (при проблемах).
  6. Шаг 6 — Импорт образов из Docker в containerd (при необходимости).

Проверка:

kubectl get nodes
kubectl get pods -n kube-system
kubectl describe node | grep -A 3 "nvidia.com/gpu"

Устранение неполадок

Кластер не поднимается / поды не стартуют

  • Проверить kubelet: systemctl status kubelet, journalctl -u kubelet -n 100.
  • Проверить системные поды: kubectl get pods -n kube-system.
  • Шаг 5 — выполнить команды из раздела «Сеть (при проблемах)».

GPU не виден в кластере

  • На хосте: nvidia-smi.
  • В кластере: выполнить Шаг 3 (NVIDIA Device Plugin) и Шаг 4 (NVIDIA runtime в containerd) в этом порядке.
  • Проверка: kubectl get pods -n kube-system | grep nvidia-device-plugin.

Образы не тянутся (сеть / прокси)

  • Если используется прокси: убедиться, что он слушает порт 1080, выполнить Шаг 2 (Прокси для containerd).
  • Если образы собраны в Docker: выполнить Шаг 6 (Импорт образов из Docker в containerd).

Нужна чистая переустановка

Выполнить команды из раздела «Сброс кластера», затем Шаг 1 (Установка кластера) и при необходимости Шаг 5 (Сеть).

Поды не доходят до API (dial tcp 10.96.0.1:443: i/o timeout)

  • Проверить CoreDNS и Flannel: kubectl get pods -n kube-system, логи kube-dns и Flannel.
  • При необходимости вручную создать /run/flannel/subnet.env (команды в разделе «Настройка сети кластера») и перезапустить CoreDNS/kube-proxy.