Настройка Kubernetes
Серверы подготовлены. Теперь из них создаётся единый Kubernetes-кластер. Сначала запускается управляющий узел и внутренняя сеть, затем подключаются рабочие узлы, настраиваются внешний IP-адрес, HTTP-доступ, поддержка GPU и доступ к закрытому реестру контейнерных образов.
На этой странице
- 2.1. Создание управляющего узла Kubernetes
- 2.2. Настройка сети кластера с помощью Cilium
- 2.3. Подключение рабочих узлов
- 2.4. MetalLB
- 2.5. ingress-nginx
- 2.6. NVIDIA GPU Operator
- 2.7. Доступ к закрытому реестру контейнерных образов
- 2.8. cert-manager
- Проверка Kubernetes-платформы
2.1. Создание управляющего узла Kubernetes
Создаётся управляющий узел кластера, настраивается доступ kubectl и параметры журналирования kubelet. До установки сетевого компонента Cilium состояние NotReady является ожидаемым.
Инициализация кластера
Только на управляющем узле Kubernetes:
MASTER_IP="${CONTROL_PLANE_IP}"
POD_NETWORK="10.244.0.0/16"
SERVICE_NETWORK="10.16.0.0/16"
K8S_VERSION="v1.35.6"
kubeadm config images pull \
--kubernetes-version "${K8S_VERSION}" \
--cri-socket unix:///run/containerd/containerd.sock
kubeadm init \
--control-plane-endpoint "${MASTER_IP}:6443" \
--apiserver-advertise-address "${MASTER_IP}" \
--skip-phases="addon/kube-proxy" \
--pod-network-cidr="${POD_NETWORK}" \
--service-cidr="${SERVICE_NETWORK}" \
--cri-socket="unix:///run/containerd/containerd.sock" \
--kubernetes-version "${K8S_VERSION}"
Рабочие узлы на этом этапе ещё не подключаются.
Настройка kubectl
mkdir -p /root/.kube
cp /etc/kubernetes/admin.conf /root/.kube/config
chmod 600 /root/.kube/config
До установки Cilium управляющий узел ожидаемо NotReady.
Журналирование kubelet
На управляющем узле:
kubectl get cm -n kube-system kubelet-config -o json | sed '
s|cluster.local\\n|cluster.local\\ncontainerLogMaxFiles: 2\\n|g
s|cluster.local\\n|cluster.local\\ncontainerLogMaxSize: 10Mi\\n|g
' | kubectl replace -f -
echo "containerLogMaxFiles: 2" >> /var/lib/kubelet/config.yaml
echo "containerLogMaxSize: 10Mi" >> /var/lib/kubelet/config.yaml
systemctl restart kubelet
Важно: после установки каждого системного компонента необходимо дождаться завершения запуска и убедиться, что его Pod-ы находятся в состоянии Running/Ready. Только после этого переходить к следующему компоненту.
2.2. Настройка сети кластера с помощью Cilium
Cilium обеспечивает сетевое взаимодействие между Pod-ами и сервисами Kubernetes. После успешного запуска Cilium управляющий узел должен перейти в состояние Ready.
Используем Cilium 1.16.5.
helm repo add cilium https://helm.cilium.io/
helm repo update
CILIUM_VERSION="1.16.5"
API_SERVER_HOST="${CONTROL_PLANE_IP}"
API_SERVER_PORT="6443"
helm upgrade --install cilium cilium/cilium \
--version "${CILIUM_VERSION}" \
--namespace kube-system \
--set kubeProxyReplacement=true \
--set k8sServiceHost="${API_SERVER_HOST}" \
--set k8sServicePort="${API_SERVER_PORT}" \
--set operator.replicas=1 \
--set ipam.mode=kubernetes \
--set hubble.enabled=false \
--set l7Proxy=false
Проверка:
kubectl -n kube-system get pods -o wide
kubectl get nodes -o wide
Ожидается:
${CONTROL_PLANE_NODE} Ready
2.3. Подключение рабочих узлов
После готовности сети к кластеру подключаются узел инфраструктурных сервисов и узел с GPU. Затем им назначается метка для размещения прикладных компонентов.
После того как Cilium установлен и ${CONTROL_PLANE_NODE} перешёл в Ready, подключаем рабочие узлы.
На управляющем узле получить актуальную команду подключения:
kubeadm token create --print-join-command
На рабочих узлах выполнить полученную команду подключения:
kubeadm join ${CONTROL_PLANE_IP}:6443 \
--token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<HASH>
Включить kubelet:
systemctl enable kubelet
Проверка выполняется с управляющего узла:
kubectl get nodes -o wide
Ожидается:
${CONTROL_PLANE_NODE} Ready control-plane v1.35.6
${DB_NODE} Ready <none> v1.35.6
${GPU_NODE} Ready <none> v1.35.6
Проверить Cilium на всех узлах:
kubectl -n kube-system get ds cilium
kubectl -n kube-system get pods -l k8s-app=cilium -o wide
Метки для прикладных сервисов:
kubectl label node "${DB_NODE}" unica-role=app --overwrite
kubectl label node "${GPU_NODE}" unica-role=app --overwrite
2.4. MetalLB
Установка MetalLB
В локальной инфраструктуре нет облачного балансировщика нагрузки. MetalLB предоставляет сервисам Kubernetes внешний IP-адрес из физической сети.
Используем:
MetalLB 0.14.9
L2 mode
FRR disabled
один выделенный VIP /32
Добавить репозиторий Helm:
helm repo add metallb https://metallb.github.io/metallb
helm repo update
Зафиксировать:
METALLB_VERSION="0.14.9"
Создать пространство имён:
kubectl create namespace metallb-system
Разрешить для компонентов MetalLB режим с расширенными системными правами:
kubectl label namespace metallb-system \
pod-security.kubernetes.io/enforce=privileged \
pod-security.kubernetes.io/audit=privileged \
pod-security.kubernetes.io/warn=privileged
Установить MetalLB без FRR:
helm upgrade --install metallb metallb/metallb \
--namespace metallb-system \
--version "${METALLB_VERSION}" \
--set speaker.frr.enabled=false \
--set frrk8s.enabled=false
Проверить:
kubectl -n metallb-system get pods -o wide
Ожидается:
metallb-controller Running
metallb-speaker Running ${CONTROL_PLANE_NODE}
metallb-speaker Running ${DB_NODE}
metallb-speaker Running ${GPU_NODE}
Для стенда используется один заранее выделенный свободный IP-адрес. Сетевой интерфейс определяется автоматически по маршруту до этого адреса.
Для каждого стенда используется выделенный свободный IP из физической сети.
Для текущей конфигурации:
# METALLB_IP задаётся на шаге 1.1
Перед использованием необходимо убедиться, что IP свободен и не выдаётся DHCP.
Определение сетевого интерфейса
Имя сетевого интерфейса не задаётся вручную и не переносится из конфигурации другой площадки. Он определяется по маршруту до выбранного VIP:
METALLB_INTERFACE=$(
ip route get "${METALLB_IP}" |
awk '{for(i=1;i<=NF;i++) if ($i=="dev") {print $(i+1); exit}}'
)
Проверить:
echo "${METALLB_IP}"
echo "${METALLB_INTERFACE}"
Для текущей конфигурации ожидается:
${METALLB_IP}
eth0
На всех Kubernetes-узлах необходимо убедиться, что сеть VIP доступна через интерфейс с тем же именем:
ip route get "${METALLB_IP}"
Создание пула IP-адресов MetalLB
Пул определяет, какой внешний IP-адрес MetalLB может назначать сервисам типа LoadBalancer. Для данного стенда используется один заранее выделенный адрес ${METALLB_IP}.
cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: unica-pool
namespace: metallb-system
spec:
addresses:
- ${METALLB_IP}/32
EOF
Настройка объявления адреса в сети L2
L2Advertisement разрешает MetalLB объявлять выделенный IP-адрес в локальной сети через найденный ранее сетевой интерфейс. После этой настройки адрес сможет использоваться сервисом ingress-nginx.
cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: main
namespace: metallb-system
spec:
ipAddressPools:
- unica-pool
interfaces:
- ${METALLB_INTERFACE}
EOF
Проверить:
kubectl -n metallb-system get ipaddresspool
kubectl -n metallb-system get l2advertisement
Для текущей конфигурации:
unica-pool -> ${METALLB_IP}/32
main -> unica-pool / eth0
Важно: сам по себе ping VIP не является проверкой MetalLB. VIP начинает использоваться после появления Service type=LoadBalancer.
2.5. ingress-nginx
ingress-nginx принимает входящие HTTP-запросы на внешнем IP-адресе MetalLB и передаёт их сервисам внутри Kubernetes. После установки проверяется весь сетевой путь до контроллера.
Версии
Используются:
пакет Helm ingress-nginx 4.12.1
контроллер v1.12.1
Ingress запускается только на:
${GPU_NODE}
Пометить узел для ingress-nginx
ingress-nginx запускается только на ${GPU_NODE}. Метка ingress используется как селектор узла при установке контроллера.
kubectl label node ${GPU_NODE} ingress=
Проверить:
kubectl get node ${GPU_NODE} --show-labels | grep ingress
Добавить репозиторий Helm
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
Используем версию 4.12.1:
INGRESS_NGINX_VERSION="4.12.1"
# METALLB_IP задаётся на шаге 1.1
Установка ingress-nginx
Внешний доступ идёт через MetalLB.
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--version "${INGRESS_NGINX_VERSION}" \
--set controller.kind=DaemonSet \
--set-string 'controller.nodeSelector.ingress=' \
--set controller.service.enabled=true \
--set controller.service.type=LoadBalancer \
--set controller.service.externalTrafficPolicy=Local \
--set-string 'controller.service.annotations.metallb\.io/address-pool=unica-pool' \
--set-string "controller.service.annotations.metallb\.io/loadBalancerIPs=${METALLB_IP}" \
--set controller.config.body-size=50m \
--set controller.config.hsts=false \
--set controller.config.large-client-header-buffers="4 32k" \
--set controller.config.proxy-body-size=1024m \
--set controller.config.proxy-buffer-size=128k \
--set controller.config.proxy-buffers="4 256k" \
--set controller.config.proxy-busy-buffers-size=256k \
--set controller.config.proxy-connect-timeout="15" \
--set controller.config.proxy-read-timeout="300" \
--set controller.config.proxy-send-timeout="300" \
--set controller.config.server-name-hash-bucket-size="256" \
--set controller.config.worker-shutdown-timeout=10s
externalTrafficPolicy=Local сохраняет исходный IP клиента и, при использовании MetalLB в режиме L2 ограничивает обработку внешнего трафика узлами, на которых запущен локальный экземпляр ingress-nginx.
Проверка внешнего HTTP
До создания прикладных Ingress-ресурсов nginx-controller обычно отвечает стандартным 404, что достаточно для проверки сетевого пути:
curl -I --connect-timeout 5 "http://${METALLB_IP}"
Получение HTTP-ответа от ingress-nginx подтверждает прохождение трафика по цепочке:
client
-> ${METALLB_IP}
-> MetalLB L2
-> ${GPU_NODE}
-> ingress-nginx
2.6. NVIDIA GPU Operator
На стенде используется NVIDIA A100 PCIe 40GB, установленная на узле ${GPU_NODE}.
Драйвер NVIDIA и NVIDIA Container Toolkit не устанавливаются отдельными пакетами операционной системы. GPU Operator разворачивает драйвер в виде системного компонента Kubernetes, загружает необходимые модули на GPU-узле и управляет остальными компонентами NVIDIA. Поэтому наличие утилит NVIDIA непосредственно в пользовательском окружении хоста не требуется.
До установки GPU Operator достаточно убедиться, что операционная система видит устройство NVIDIA:
lspci -nn | grep -i nvidia
Команда должна вывести устройство NVIDIA. До установки GPU Operator драйвер NVIDIA на узле не установлен, поэтому nvidia-smi на хосте не используется.
Добавление репозитория Helm NVIDIA
На управляющем узле Kubernetes (${CONTROL_PLANE_NODE}):
helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo update
Пространство имён GPU Operator
kubectl create namespace gpu-operator
GPU Operator требует запуска компонентов с расширенными системными правами:
kubectl label --overwrite namespace gpu-operator \
pod-security.kubernetes.io/enforce=privileged
Установка NVIDIA GPU Operator
Используем GPU Operator v25.10.1 и драйвер NVIDIA 580.126.09:
GPU_OPERATOR_VERSION="v25.10.1"
NVIDIA_DRIVER_VERSION="580.126.09"
Установка:
helm upgrade --install gpu-operator nvidia/gpu-operator \
--namespace gpu-operator \
--version "${GPU_OPERATOR_VERSION}" \
--set driver.enabled=true \
--set driver.version="${NVIDIA_DRIVER_VERSION}" \
--set driver.kernelModuleType=auto \
--set driver.usePrecompiled=false \
--set toolkit.enabled=true \
--set nfd.enabled=true \
--set dcgmExporter.enabled=false \
--wait
GPU Operator разворачивает и сопровождает на GPU-узле следующие компоненты:
NVIDIA GPU Driver
NVIDIA Container Toolkit
NVIDIA Kubernetes Device Plugin
GPU Feature Discovery
Node Feature Discovery
MIG Manager
Validator
Проверка NVIDIA GPU Operator
После установки убедитесь, что компоненты GPU Operator запущены, а Kubernetes видит GPU-ресурсы узла:
kubectl -n gpu-operator get pods -o wide
kubectl get node "${GPU_NODE}" -o json | \
jq '.status.allocatable | with_entries(select(.key | startswith("nvidia.com")))'
Поскольку драйвер развёрнут через GPU Operator, проверка выполняется внутри Pod-а драйвера:
DRIVER_POD=$(kubectl -n gpu-operator get pod \
-l app=nvidia-driver-daemonset \
-o jsonpath='{.items[0].metadata.name}')
kubectl -n gpu-operator exec "${DRIVER_POD}" -c nvidia-driver-ctr -- nvidia-smi
Команда должна показать установленный драйвер и обнаруженную GPU. Отсутствие nvidia-smi в командной оболочке самого хоста при такой схеме не является ошибкой.
2.7. Доступ к закрытому реестру контейнерных образов
Прикладные образы и пакеты Helm находятся в закрытом реестре. Для доступа нужны адрес реестра, имя пользователя и пароль. Эти значения используются дважды: containerd — для загрузки контейнерных образов, Helm — для загрузки OCI-пакетов.
Подставьте выданные для стенда учётные данные:
REGISTRY="<registry-host>"
REGISTRY_USERNAME="<registry-user>"
REGISTRY_PASSWORD="<registry-password>"
Это переменные командной оболочки. Если настройка узлов и авторизация Helm выполняются в разных сеансах, задайте эти три переменные в каждом соответствующем сеансе перед выполнением команд ниже.
Настройка containerd
Настройку необходимо выполнить на узлах, на которых будут запускаться прикладные Pod-ы. В текущей схеме это ${DB_NODE} и ${GPU_NODE}.
На узле ${DB_NODE}:
cat >> /etc/containerd/config.toml <<EOF
[plugins."io.containerd.grpc.v1.cri".registry.configs."${REGISTRY}".auth]
username = "${REGISTRY_USERNAME}"
password = "${REGISTRY_PASSWORD}"
EOF
systemctl restart containerd
systemctl restart kubelet
На узле ${GPU_NODE} после установки NVIDIA GPU Operator:
cat >> /etc/containerd/conf.d/99-nvidia.toml <<EOF
[plugins."io.containerd.grpc.v1.cri".registry.configs."${REGISTRY}".auth]
username = "${REGISTRY_USERNAME}"
password = "${REGISTRY_PASSWORD}"
EOF
systemctl restart containerd
systemctl restart kubelet
crictl info | jq '.config.containerd.runtimes | keys'
Авторизация Helm
containerd и Helm используют независимую авторизацию. Поэтому на управляющем узле Kubernetes необходимо отдельно выполнить вход в реестр для Helm:
echo "${REGISTRY_PASSWORD}" | helm registry login "${REGISTRY}" \
--username "${REGISTRY_USERNAME}" \
--password-stdin
Проверить доступ к пакету Helm:
helm show chart oci://<REGISTRY>/<HELM_CHARTS_PATH>/rospartner-ai-files \
--version <FILES_CHART_VERSION>
Если команда выводит метаданные пакета, авторизация выполнена успешно. Helm хранит данные авторизации локально на управляющем узле в файле ~/.config/helm/registry/config.json.
2.8. cert-manager
cert-manager устанавливается как системный компонент Kubernetes и используется для управления сертификатами, необходимыми прикладным ресурсам.
Версия:
cert-manager v1.20.3
На управляющем узле Kubernetes (${CONTROL_PLANE_NODE}):
CERT_MANAGER_VERSION="v1.20.3"
helm upgrade --install cert-manager \
oci://quay.io/jetstack/charts/cert-manager \
--version "${CERT_MANAGER_VERSION}" \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=true \
--set prometheus.enabled=false
Проверка:
kubectl -n cert-manager get pods -o wide
Ожидается:
cert-manager 1/1 Running
cert-manager-cainjector 1/1 Running
cert-manager-webhook 1/1 Running
Проверка Kubernetes-платформы
Перед установкой инфраструктурного ПО убедитесь, что все узлы находятся в состоянии Ready, сетевые компоненты запущены, внешний IP-адрес доступен, GPU виден Kubernetes, а cert-manager работает.
Выполнить на управляющем узле Kubernetes:
kubectl get nodes -o wide
kubectl -n kube-system get pods -l k8s-app=cilium -o wide
kubectl -n metallb-system get pods -o wide
kubectl -n ingress-nginx get pods,svc -o wide
kubectl -n gpu-operator get pods -o wide
kubectl -n cert-manager get pods -o wide
Ожидаемый результат: все узлы находятся в состоянии Ready, системные Pod-ы — в состоянии Running/Ready, а сервис ingress-nginx получил внешний IP-адрес ${METALLB_IP}.