Как стать автором
Обновить

Capi + talos в openstack? Не вопрос

Время на прочтение9 мин
Количество просмотров1.5K

Абстракт

Когда я был маленьким и глупым, в своей домашней лаборатории я развернул kuber‑barick — его возможностей хватало на баловство с нейронками и обучением крутить поды, но при попытке поднять второй (например, для стейджинга) всё упёрлось в дефицит железа и ручную пляску с самоподписными сертификатами. Решил перейти на более автоматный подход: наткнулся на Talos и Cluster API (CAPI). В статье покажу, как их связка в OpenStack позволяет быстро, повторяемо и без лишних ручных движений разворачивать мультикластер.

Мой хомлаб полигон из нутри.
Мой хомлаб полигон из нутри.

Что вообще за существа такие Talos и CAPI

Talos — это минималистичная и полностью упрощённая immutable операционная система, созданная специально для развёртывания и управления Kubernetes-кластером. Про неё тут уже были статьи

Cluster API — это официальный проект CNCF, предоставляющий декларативный подход к управлению жизненным циклом Kubernetes-кластеров независимо от инфраструктуры. Про этого зверя статья была лишь одна. Возмутительно, и пора исправить!

Поехали!

Из Openstack понадобится такой список

  • Keystone

  • Glance

  • Placement

  • Cinder

  • Nova

  • Neutron

  • Octavia

В случае если видеокарты не нужны можно идти к установке. А если они для проекта нужны то заострю внимание лишь на Nova. Пришлось добавить туда PCI passthrough.
В /etc/default/grub Надо подкинуть vfio-pci к всем pci-e девайсом видеокарт. Vendor и product id от карточки к карточке разный, даже у одной и той же модели, поэтому надо посмотреть вывод lspci -nn | grep NVIDIA

GRUB_CMDLINE_LINUX="... vfio-pci.ids=10de:1e02,10de:10f7,10de:1ad6,10de:1ad7 rd.driver.blacklist=nouveau,nvidiafb,snd_hda_intel,xhci_hcd,i2c_nvidia_gpu"

В /etc/nova/nova.conf на воркеры с видеокартами надо докинуть алиас

[pci]
passthrough_whitelist = {\"vendor_id\":\"10de\",\"product_id\":\"1e02\"}
passthrough_whitelist = {\"vendor_id\":\"10de\",\"product_id\":\"10f7\"}
passthrough_whitelist = {\"vendor_id\":\"10de\",\"product_id\":\"1ad6\"}
passthrough_whitelist = {\"vendor_id\":\"10de\",\"product_id\":\"1ad7\"}

alias = {\"vendor_id\":\"10de\",\"product_id\":\"1e02\",\"name\":\"gpu\"}
alias = {\"vendor_id\":\"10de\",\"product_id\":\"10f7\",\"name\":\"gpu_audio\"}
alias = {\"vendor_id\":\"10de\",\"product_id\":\"1ad6\",\"name\":\"usb_controller\"}
alias = {\"vendor_id\":\"10de\",\"product_id\":\"1ad7\",\"name\":\"ucsi_controller\"}

Создаем типы инстансов для GPU

#!/bin/bash
. .secrets

flavors=(
  # ========== GPU Optimized (G-серия) ==========
  "401 8 16384 40 g1.tiny"
  "402 16 32768 40 g1.small"
  "403 24 65536 40 g1.medium"
  "404 32 131072 40 g1.large"
  "405 32 196608 40 g1.xlarge"
)

for flavor in "${flavors[@]}"; do
    openstack flavor create --id "$id" --vcpus "$vcpus" --ram "$ram" --disk "$disk" "$name" \
      --property "pci_passthrough:alias"="gpu:1,gpu_audio:1,usb_controller:1,ucsi_controller:1" > /dev/null
done

Установка: подготовка вспомогательного k8s

Создадим Kubernetes для создания Kubernetes-а. Для примера Kubernetes in docker на микровиртуалке ubuntu в openstack. Как депенденси KinD нужен docker. Поэтому в скрипт я вложил инструкцию отсюда.

curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.17.0/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind

 
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
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

# Add the repository to Apt sources:
echo   "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
    $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" |   sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

sudo usermod -aG docker $USER

sudo reboot #чтобы сокет докера встал с нужными правами

Стартуем KinD:

kind create cluster --name management 

Устанавливаем клиент clusterctl пользуясь руководством

curl -L https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.9.6/clusterctl-linux-amd64 -o clusterctl
chmod +x ./clusterctl
sudo mv ./clusterctl /usr/local/bin/clusterctl

Инициализируем clusterctl и необходимый для установки openstack-resource-controller

clusterctl init --infrastructure openstack --bootstrap talos --control-plane talos --core cluster-api
kubectl apply -f https://github.com/k-orc/openstack-resource-controller/releases/download/v1.0.1/install.yaml

Создаем секрет с clouds.yaml

clouds:
  openstack:
    auth:
      auth_url: http://controller:5000/v3/
      username: "username"
      password: "pa$$word"
      project_id: "project_id"
      project_name: "project_name"
      user_domain_name: "Default"
    region_name: "nova"
    interface: "public"
    identity_api_version: 3

Добавляем его в темповый кубер

kubectl create secret generic cloud-config --namespace=kube-system --from-file=clouds.yaml="$HOME/.config/openstack/clouds.yaml" --from-literal=cacert="" --dry-run=client -o yaml | kubectl apply -f -

Установка: YAML‑программируем таргетный k8s

Создадим базовый набор CRD‑шек

cluster.yaml

---
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
  name: k8s-cluster
  namespace: kube-system
spec:
  clusterNetwork:
    pods:
      cidrBlocks:
      - 192.168.0.0/16
    serviceDomain: cluster.local
  controlPlaneRef:
    apiVersion: controlplane.cluster.x-k8s.io/v1alpha3
    kind: TalosControlPlane
    name: k8s-cluster-control-plane
  infrastructureRef:
    apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
    kind: OpenStackCluster
    name: k8s-cluster
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: OpenStackCluster
metadata:
  name: k8s-cluster
  namespace: kube-system
spec:
  apiServerLoadBalancer:
    enabled: true
  externalNetwork:
    id: # НАДО УКАЗАТЬ ID ВНЕШНЕЙ СЕТИ OPENSTACK #
  identityRef:
    cloudName: openstack
    name: k8s-cluster-cloud-config
  managedSecurityGroups:
    allNodesSecurityGroupRules:
      - name: allow-port-talos
        direction: ingress
        etherType: IPv4
        portRangeMin: 50000
        portRangeMax: 50001
        protocol: tcp
        remoteIPPrefix: 0.0.0.0/0
      - name: allow-pod-traffic # Далее правила для Flannel #
        direction: ingress
        etherType: IPv4
        portRangeMin: 1
        portRangeMax: 65535
        protocol: tcp
        remoteIPPrefix: 192.168.0.0/16
      - name: allow-pod-traffic-udp
        direction: ingress
        etherType: IPv4
        portRangeMin: 1
        portRangeMax: 65535
        protocol: udp
        remoteIPPrefix: 192.168.0.0/16
      - name: allow-all-traffic-tcp
        direction: ingress
        etherType: IPv4
        portRangeMin: 1
        portRangeMax: 65535
        protocol: tcp
        remoteIPPrefix: 10.10.20.0/24
      - name: allow-all-traffic-udp
        direction: ingress
        etherType: IPv4
        portRangeMin: 1
        portRangeMax: 65535
        protocol: udp
        remoteIPPrefix: 10.10.20.0/24
  managedSubnets:
  - cidr: 10.10.20.0/24
    dnsNameservers:
    - # НАДО УКАЗАТЬ DNS С КОТОРЫМ РАБОТАЕТ OPENSTACK #

Описываем воркеры

workers.yaml

---
apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineDeployment
metadata:
  name: k8s-cluster-md-0
  namespace: kube-system
spec:
  clusterName: k8s-cluster
  replicas: 2
  selector:
    matchLabels:
      machine-deployment: k8s-cluster-md-0
  template:
    metadata:
      labels:
        cluster.x-k8s.io/cluster-name: k8s-cluster
        machine-deployment: k8s-cluster-md-0
    spec:
      bootstrap:
        configRef:
          apiVersion: bootstrap.cluster.x-k8s.io/v1alpha3
          kind: TalosConfigTemplate
          name: k8s-cluster-md-0
      clusterName: k8s-cluster
      failureDomain: nova # МОЖЕТ ОТЛИЧАТЬСЯ #
      infrastructureRef:
        apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
        kind: OpenStackMachineTemplate
        name: k8s-cluster-md-0
      version: v1.30.0
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: OpenStackMachineTemplate
metadata:
  name: k8s-cluster-md-0
  namespace: kube-system
spec:
  template:
    spec:
      flavor: m1.large
      image:
        filter:
          name: talos
      sshKeyName: pgp-card
      rootVolume:
        sizeGiB: 50
        type: __SSD__
---
apiVersion: bootstrap.cluster.x-k8s.io/v1alpha3
kind: TalosConfigTemplate
metadata:
  name: k8s-cluster-md-0
  namespace: kube-system
  labels:
    cluster.x-k8s.io/cluster-name: k8s-cluster
spec:
  template:
    spec:
      generateType: worker
      talosVersion: v1.9.5
      strategicPatches:
        - |
          - op: replace
            path: /machine/install
            value:
              disk: /dev/vda
          - op: add
            path: /machine/kubelet/extraArgs
            value:
              cloud-provider: external


---
apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineDeployment
metadata:
  name: k8s-cluster-md-gpu-0
  namespace: kube-system
spec:
  clusterName: k8s-cluster
  replicas: 2
  selector:
    matchLabels:
      machine-deployment: k8s-cluster-md-gpu-0
  template:
    metadata:
      labels:
        cluster.x-k8s.io/cluster-name: k8s-cluster
        machine-deployment: k8s-cluster-md-gpu-0
    spec:
      bootstrap:
        configRef:
          apiVersion: bootstrap.cluster.x-k8s.io/v1alpha3
          kind: TalosConfigTemplate
          name: k8s-cluster-md-gpu-0
      clusterName: k8s-cluster
      failureDomain: nova # МОЖЕТ ОТЛИЧАТЬСЯ #
      infrastructureRef:
        apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
        kind: OpenStackMachineTemplate
        name: k8s-cluster-md-gpu-0
      version: v1.30.0
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: OpenStackMachineTemplate
metadata:
  name: k8s-cluster-md-gpu-0
  namespace: kube-system
spec:
  template:
    spec:
      flavor: g1.small # МОЖЕТ ОТЛИЧАТЬСЯ #
      image:
        filter:
          name: talos # МОЖЕТ ОТЛИЧАТЬСЯ #
      sshKeyName: pgp-card # МОЖЕТ ОТЛИЧАТЬСЯ #
      rootVolume:
        sizeGiB: 50
        type: __SSD__ # МОЖЕТ ОТЛИЧАТЬСЯ #

---
apiVersion: bootstrap.cluster.x-k8s.io/v1alpha3
kind: TalosConfigTemplate
metadata:
  name: k8s-cluster-md-gpu-0
  namespace: kube-system
  labels:
    cluster.x-k8s.io/cluster-name: k8s-cluster
spec:
  template:
    spec:
      generateType: worker
      talosVersion: v1.9.5
      strategicPatches:
        - |
          - op: replace
            path: /machine/install
            value:
              disk: /dev/vda # МОЖЕТ ОТЛИЧАТЬСЯ ОБРАЗ. СГЕНЕРИРУЙТЕ СВОЙ В TALOS FABRIC# 
              image: factory.talos.dev/installer/26124abcbd408be693df9fe852c80ef1e6cc178e34d7d7d8430a28d1130b4227:v1.9.5
          - op: add
            path: /machine/kernel
            value: {}
          - op: add
            path: /machine/kubelet/extraArgs
            value:
              cloud-provider: external
          - op: add
            path: /machine/kernel/modules
            value:
              - name: nvidia
              - name: nvidia_uvm
              - name: nvidia_drm
              - name: nvidia_modeset
          - op: add
            path: /machine/sysctls
            value:
              net.core.bpf_jit_harden: "1"
              

Контролплейны

controlplane.yaml

---
apiVersion: controlplane.cluster.x-k8s.io/v1alpha3
kind: TalosControlPlane
metadata:
    name: k8s-cluster-control-plane
    namespace: kube-system
    labels:
        cluster.x-k8s.io/cluster-name: k8s-cluster
spec:
  version: v1.30.0
  replicas: 3
  infrastructureTemplate:
    apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
    kind: OpenStackMachineTemplate
    name: k8s-cluster-control-plane
    namespace: kube-system
  controlPlaneConfig:
    controlplane:
      generateType: controlplane
      strategicPatches:
        - |
          - op: replace
            path: /machine/install
            value:
              disk: /dev/vda
          - op: add
            path: /machine/kubelet/extraArgs
            value:
              cloud-provider: external
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: OpenStackMachineTemplate
metadata:
  name: k8s-cluster-control-plane
  namespace: kube-system
spec:
  template:
    spec:
      flavor: m1.medium # МОЖЕТ ОТЛИЧАТЬСЯ #
      image:
        filter:
          name: talos # МОЖЕТ ОТЛИЧАТЬСЯ #
      sshKeyName: pgp-card # МОЖЕТ ОТЛИЧАТЬСЯ #
      rootVolume:
        sizeGiB: 50
        type: __SSD__ # МОЖЕТ ОТЛИЧАТЬСЯ #

Применяем

kubectl apply -f .

Мигрируем контроль в таргетный кубер

В openstack уже появится новая сеть. ее надо подключить к темповому куберу чтобы он смог настроить все ноды. Чтобы получить kubeconfig нужно выполнить команду

clusterctl get kubeconfig k8s-cluster -n kube-system > k8s_kubeconfig

Дальше для таргетного кластера k8s_kubeconfig аналогично статье:

Установка Cloud Provider OpenStack

Для того, чтобы наш кластер kubernetes мог ходить в OpenStack и создавать там диски или балансировщики, необходимо поставить Cloud Provider OpenStack. Там есть совершенно разные плагины, лично меня интересовал только OpenStack Cloud Controller Manager для создания балансировщиков и Cinder CSI Plugin, чтобы при создании PV, диски сами создавались в OpenStack и монтировались к нужному узлу.

Для работоспособности необходимо создать файл cloud-config с конфигурацией и кредами от OpenStack

[Global]
auth-url=OS_AUTH_URL
username=OS_USERNAME
password=OS_PASSWORD
tenant-name=OS_PROJECT_NAME
domain-name=default
project-domain-name=OS_PROJECT_DOMAIN_ID
user-domain-name=OS_USER_DOMAIN_ID
region=OS_REGION_NAME

[LoadBalancer]
lb-version=v2
subnet-id=<subnet_id> # ID подсети, из которой балансировщик будет выделять IP 
floating-network-id=<floating_network_id> # ID сети в которой существует вышеуказанная подсеть 
flavor-id=<flavor_id> # ID тарифа балансировщика
member-subnet-id=<member_subnet_id> # ID сети, в которой будут создаваться member. В моем случае это подсеть сети talos-k8s.
create-monitor=true
monitor-delay=5s
monitor-timeout=3s
monitor-max-retries=3

[BlockStorage]
trust-device-path=false
ignore-volume-az=true

OpenStack Cloud Controller Manager

Чтобы поставить этот модуль достаточно выполнить команды из документации.

  1. Создать секрет из файла cloud-config описанного выше.

    kubectl create secret -n kube-system generic cloud-config --from-file=cloud.conf
  2. Пометить namespace для необходимых прав доступа, создать RBAC ресурсы и openstack-cloud-controller-manager daemonset.

    kubectl label namespace kube-system pod-security.kubernetes.io/enforce=privileged
    kubectl apply -f https://raw.githubusercontent.com/kubernetes/cloud-provider-openstack/master/manifests/controller-manager/cloud-controller-manager-roles.yaml
    kubectl apply -f https://raw.githubusercontent.com/kubernetes/cloud-provider-openstack/master/manifests/controller-manager/cloud-controller-manager-role-bindings.yaml
    kubectl apply -f https://raw.githubusercontent.com/kubernetes/cloud-provider-openstack/master/manifests/controller-manager/openstack-cloud-controller-manager-ds.yaml

Проверяем статус что кластру хорошо и он готов к миграции

clusterctl describe cluster k8s-cluster -n kube-system
clusterctl move -n kube-system --to-kubeconfig ./k8s_kubeconfig

Готово!

Удаляем временую вм.
Теперь можно создавать узлы в Kubernetes просто патчами сущностей MachineDeployment и в узком пуле железа создавать другие кластеры.

Для удобства записал видео гайд.

Спасибо за внимание

Теги:
Хабы:
+6
Комментарии0

Публикации

Работа

DevOps инженер
30 вакансий

Ближайшие события