• Русский
  • Создание кластера VMware vSphere в кластере global

    В этом документе объясняется, как создать рабочий кластер VMware vSphere из кластера global с использованием стандартного режима CAPV, который напрямую подключается к vCenter. Процедура охватывает минимально поддерживаемую топологию с одним datacenter, одним NIC на узел и статическим назначением IP через VSphereMachineConfigPool.

    Сценарии

    Используйте этот документ в следующих сценариях:

    • Вы хотите создать первый базовый рабочий кластер VMware vSphere в своей среде.
    • Для первоначальной проверки вы используете один datacenter и один NIC на узел.
    • Вы хотите сохранить первое развертывание простым перед включением расширенных возможностей размещения или сети.

    Этот документ применим к следующей модели развертывания:

    • CAPV подключается напрямую к vCenter.
    • И control plane, и worker-узлы используют VSphereMachineConfigPool для статического назначения IP и data disk.
    • ClusterResourceSet автоматически доставляет компонент vSphere CPI.
    • Первая проверка использует один datacenter и один NIC на узел.

    Этот документ не применим к следующим сценариям:

    • Развертывание, которое зависит от vSphere Supervisor или vm-operator.
    • Развертывание, которое не использует VSphereMachineConfigPool.
    • Первоначальное развертывание, в котором одновременно включаются несколько datacenter, несколько NIC и сложные расширения дисков.

    Этот документ написан для текущей платформенной среды. Путь доставки kube-ovn зависит от контроллеров платформы, которые обрабатывают аннотации ресурса Cluster, поэтому этот сценарий не предназначен для использования как универсальное руководство по автономному развертыванию CAPV вне контекста платформы.

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

    Перед началом убедитесь, что выполнены следующие условия:

    1. Вы завершили сбор значений в Подготовка параметров для кластера VMware vSphere.
    2. Кластер global может подключаться к vCenter.
    3. Доступны целевой template, сети, datastore и resource pool vCenter.
    4. Control plane VIP и load balancer готовы.
    5. Все требуемые статические IP-адреса выделены и не используются.
    6. Включено ClusterResourceSet=true.
    7. В платформе уже настроена корректная конфигурация публичного registry.
    8. Платформа может обработать аннотации кластера, необходимые для установки сетевого плагина.

    Основные объекты

    ClusterResourceSet

    ClusterResourceSet — это ресурс Cluster API в кластере global. После того как workload API server становится доступным, он применяет к рабочему кластеру указанные ресурсы ConfigMap и Secret.

    В этом сценарии ClusterResourceSet используется для автоматической доставки ресурсов vSphere CPI.

    Компонент vSphere CPI

    Компонент vSphere CPI доставляется в рабочий кластер через ClusterResourceSet. Он подключает workload-узлы к инфраструктуре vSphere, чтобы кластер мог сообщать идентификаторы инфраструктуры и завершать инициализацию cloud-provider.

    machine config pool

    Machine config pool — это пользовательский ресурс VSphereMachineConfigPool. В базовом сценарии:

    • Один machine config pool используется для узлов control plane.
    • Один machine config pool используется для worker-узлов.

    Каждый слот узла включает hostname, datacenter, назначение статического IP и необязательные определения data disk.

    Для настройки сети различайте следующие поля:

    • networkName — это имя сети или port group в vCenter.
    • deviceName — это имя NIC внутри guest operating system.

    Если deviceName задан, CAPV записывает это значение в сгенерированные метаданные guest-network. Если оно не указано, текущая реализация обычно использует имена NIC, такие как eth0, eth1 и eth2, в порядке их расположения.

    Также различайте следующие форматы значений:

    • IP-адрес узла используется вместе с длиной префикса, например 10.10.10.11/24.
    • Поле gateway содержит только IP-адрес шлюза, например 10.10.10.1.

    В базовом сценарии:

    • Один VSphereMachineConfigPool используется для узлов control plane.
    • Один VSphereMachineConfigPool используется для worker-узлов.

    Требования к VM template

    VM template, используемый в этом сценарии, должен соответствовать следующим минимальным требованиям:

    1. Он использует требуемую operating system для целевой платформенной среды.
    2. В него включен cloud-init.
    3. В него включены VMware Tools или open-vm-tools.
    4. В него включен containerd.
    5. В него включены базовые компоненты, необходимые для bootstrap kubeadm.
    6. В него включены предварительно экспортированные container image tar-файлы в каталоге /root/images/. Эти файлы импортируются в containerd с помощью capv-load-local-images.sh перед запуском kubeadm, поэтому bootstrap узла не зависит от загрузки образов из удаленного registry.
    7. Файлы /root/images/*.tar обязательно должны включать sandbox (pause) image, ссылка на который в точности совпадает со значением sandbox_image (containerd v1) или sandbox (containerd v2), настроенным в /etc/containerd/config.toml. Например, если containerd настроен с sandbox_image = "registry.example.com/tkestack/pause:3.10", один из tar-файлов должен содержать именно такую ссылку на image. Несоответствие приводит к тому, что containerd будет загружать sandbox image из сети, что сводит на нет смысл локального предварительного загрузки и приводит к сбою в средах без доступа к сети.

    Конфигурация статического IP, внедрение hostname и другие параметры инициализации зависят от cloud-init. Отчёт об IP-адресе узла зависит от guest tools.

    Локальная структура файлов

    Именование рабочего кластера

    Рабочий cluster_name не должен быть global. Это имя зарезервировано для кластера global, и его повторное использование приводит к конфликту ресурсов рабочего кластера с ресурсами кластера global в cpaas-system. Префикс global- зарезервирован для ресурсов, которыми владеет DR-workflow кластера global; см. Общие предварительные требования. Не используйте global- для ресурсов рабочего кластера, поскольку операции failover могут выбирать эти ресурсы так, как будто они принадлежат кластеру global.

    В качестве соглашения именования оставляйте ресурс CAPI Cluster и ресурс provider cluster (VSphereCluster) с точным именем <cluster_name>, а ресурсы CAPI и provider, не являющиеся корневыми (KubeadmControlPlane, KubeadmConfigTemplate, MachineDeployment, VSphereMachineTemplate, VSphereMachineConfigPool и т. д.), префиксуйте <cluster_name>- — например, в приведенных примерах манифестов используются <cluster_name>-kcp и <cluster_name>-md-0. Это рекомендация, а не правило, enforced контроллером, но она предотвращает конфликты в одной namespace, когда несколько рабочих кластеров находятся в cpaas-system, и делает владение ресурсами очевидным во время операций.

    Создайте локальный рабочий каталог и сохраните манифесты со следующей структурой:

    capv-cluster/
    ├── 00-namespace.yaml
    ├── 01-vsphere-credentials-secret.yaml
    ├── 02-vspheremachineconfigpool-control-plane.yaml
    ├── 03-vspheremachineconfigpool-worker.yaml
    ├── 10-cluster.yaml
    ├── 15-vsphere-cpi-clusterresourceset.yaml
    ├── 20-control-plane.yaml
    └── 30-workers-md-0.yaml

    Для создания каталога используйте следующие команды:

    mkdir -p ./capv-cluster
    cd ./capv-cluster

    Шаги

    Проверка окружения

    Выполните следующие команды из кластера global, чтобы проверить минимальные предварительные требования:

    kubectl get ns
    kubectl get minfo -l cpaas.io/module-name=cluster-api-provider-vsphere
    kubectl get minfo -l cpaas.io/module-name=cluster-api-provider-kubeadm
    kubectl -n cpaas-system get deploy capi-controller-manager -o jsonpath='{.spec.template.spec.containers[0].args}'
    kubectl -n cpaas-system get secret public-registry-credential -o jsonpath='{.data.content}'

    Подтвердите следующие результаты:

    • Кластер global доступен.
    • Запущены Alauda Container Platform Kubeadm Provider и Alauda Container Platform VMware vSphere Infrastructure Provider.
    • Аргументы контроллера включают ClusterResourceSet=true.
    • Значение data.content для учетных данных public registry не пустое.

    Перед продолжением также проверьте следующие пункты:

    • Доступен адрес vCenter server.
    • Имя пользователя и пароль vCenter корректны.
    • Thumbprint корректен.
    • Имя template корректно.
    • Template разрешается в целевом datacenter.
    • Если VM клонируется как fullClone, системный диск template не больше значения diskGiB, которое используется позже в манифестах. Если CAPV выполняет linkedClone, размер системного диска остается равным размеру template, а diskGiB игнорируется.
    • В template установлены VMware Tools или open-vm-tools.
    • VIP существует, а порт 6443 доступен из среды выполнения.
    • Ясно определена модель владения load balancer для обслуживания real-server.

    Создание namespace и secret с учетными данными vCenter

    Создайте namespace, в котором будут храниться объекты рабочего кластера.

    В этом сценарии объекты рабочего кластера хранятся в namespace cpaas-system. В манифестах и командах ниже замените каждый шаблон <namespace> на cpaas-system.

    00-namespace.yaml
    apiVersion: v1
    kind: Namespace
    metadata:
      name: <namespace>

    Создайте secret с учетными данными vCenter, на который ссылается VSphereCluster.spec.identityRef.

    01-vsphere-credentials-secret.yaml
    apiVersion: v1
    kind: Secret
    metadata:
      name: <credentials_secret_name>
      namespace: <namespace>
    type: Opaque
    stringData:
      username: "<vsphere_username>"
      password: "<vsphere_password>"

    Примените оба манифеста:

    kubectl apply -f 00-namespace.yaml
    kubectl apply -f 01-vsphere-credentials-secret.yaml

    Создание объектов Cluster и VSphereCluster

    Создайте базовый манифест кластера с настройками сети рабочего кластера, endpoint control plane и параметрами подключения к vCenter.

    10-cluster.yaml
    apiVersion: cluster.x-k8s.io/v1beta1
    kind: Cluster
    metadata:
      name: <cluster_name>
      namespace: <namespace>
      labels:
        cluster.x-k8s.io/cluster-name: <cluster_name>
        cluster-type: VSphere
        addons.cluster.x-k8s.io/vsphere-cpi: "enabled"
      annotations:
        capi.cpaas.io/resource-group-version: infrastructure.cluster.x-k8s.io/v1beta1
        capi.cpaas.io/resource-kind: VSphereCluster
        cpaas.io/sentry-deploy-type: Baremetal
        cpaas.io/alb-address-type: ClusterAddress
        cpaas.io/network-type: kube-ovn
        cpaas.io/kube-ovn-version: <kube_ovn_version>
        cpaas.io/kube-ovn-join-cidr: <kube_ovn_join_cidr>
    spec:
      clusterNetwork:
        pods:
          cidrBlocks:
          - <pod_cidr>
        services:
          cidrBlocks:
          - <service_cidr>
      controlPlaneRef:
        apiVersion: controlplane.cluster.x-k8s.io/v1beta1
        kind: KubeadmControlPlane
        name: <cluster_name>-kcp
      infrastructureRef:
        apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
        kind: VSphereCluster
        name: <cluster_name>
    ---
    apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
    kind: VSphereCluster
    metadata:
      name: <cluster_name>
      namespace: <namespace>
    spec:
      controlPlaneEndpoint:
        host: "<vip>"
        port: <api_server_port>
      identityRef:
        kind: Secret
        name: <credentials_secret_name>
      server: "<vsphere_server>"
      thumbprint: "<thumbprint>"

    Примените манифест:

    kubectl apply -f 10-cluster.yaml

    Создание ресурсов доставки vSphere CPI

    Создайте ClusterResourceSet, чтобы рабочий кластер получал конфигурацию и манифесты vSphere CPI автоматически после того, как workload API server станет доступным.

    INFO

    В базовом сценарии VSphereCluster.spec.failureDomainSelector намеренно не задан, а в CPI vsphere.conf не включен блок [Labels]. Оба параметра требуются только после включения failure domain; настройте их вместе, как описано в Сценарии расширения. Добавление [Labels] в vsphere.conf без соответствующих объектов VSphereFailureDomain приводит к тому, что CPI ищет теги zone и region, которых не существует.

    WARNING

    Ресурсы CPI ConfigMap, Secret и ClusterResourceSet должны создаваться в том же namespace, что и ресурс Cluster. В этом руководстве таким namespace является cpaas-system. ClusterResourceSet может сопоставляться только с кластерами в своем namespace; если развернуть его в другом namespace, доставка ресурсов будет незаметно заблокирована.

    INFO

    Конфигурация kube-ovn в аннотациях Cluster обрабатывается контроллерами платформы. Этот документ не устанавливает сетевой плагин напрямую.

    TIP

    Этот манифест длинный и содержит вложенный YAML внутри полей data. Перед применением проверьте манифест: kubectl apply --dry-run=client -f 15-vsphere-cpi-clusterresourceset.yaml.

    15-vsphere-cpi-clusterresourceset.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: <cluster_name>-vsphere-cpi-config
      namespace: <namespace>
    data:
      data: |
        apiVersion: v1
        kind: ConfigMap
        metadata:
          name: cloud-config
          namespace: kube-system
        data:
          vsphere.conf: |
            [Global]
            secret-name = "vsphere-cloud-secret"
            secret-namespace = "kube-system"
            service-account = "cloud-controller-manager"
            port = "443"
            insecure-flag = "<cpi_insecure_flag>"
            datacenters = "<cpi_datacenters>"
    
            [VirtualCenter "<vsphere_server>"]
    ---
    apiVersion: v1
    kind: Secret
    metadata:
      name: <cluster_name>-vsphere-cpi-secret
      namespace: <namespace>
    type: addons.cluster.x-k8s.io/resource-set
    stringData:
      data: |
        apiVersion: v1
        kind: Secret
        metadata:
          name: vsphere-cloud-secret
          namespace: kube-system
        type: Opaque
        stringData:
          <vsphere_server>.username: <vsphere_username>
          <vsphere_server>.password: <vsphere_password>
    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: <cluster_name>-vsphere-cpi-manifests
      namespace: <namespace>
    data:
      data: |
        apiVersion: v1
        kind: ServiceAccount
        metadata:
          name: cloud-controller-manager
          namespace: kube-system
        automountServiceAccountToken: false
        ---
        apiVersion: rbac.authorization.k8s.io/v1
        kind: ClusterRole
        metadata:
          name: system:cloud-controller-manager
        rules:
        - apiGroups: [""]
          resources: ["events"]
          verbs: ["create", "patch", "update"]
        - apiGroups: [""]
          resources: ["nodes"]
          verbs: ["*"]
        - apiGroups: [""]
          resources: ["nodes/status"]
          verbs: ["patch"]
        - apiGroups: [""]
          resources: ["services"]
          verbs: ["list", "patch", "update", "watch"]
        - apiGroups: [""]
          resources: ["services/status"]
          verbs: ["patch"]
        - apiGroups: [""]
          resources: ["serviceaccounts"]
          verbs: ["create", "get", "list", "watch", "update"]
        - apiGroups: [""]
          resources: ["persistentvolumes"]
          verbs: ["get", "list", "update", "watch"]
        - apiGroups: [""]
          resources: ["endpoints"]
          verbs: ["create", "get", "list", "watch", "update"]
        - apiGroups: [""]
          resources: ["secrets"]
          verbs: ["get", "list", "watch"]
        - apiGroups: ["coordination.k8s.io"]
          resources: ["leases"]
          verbs: ["get", "list", "watch", "create", "update"]
        ---
        apiVersion: rbac.authorization.k8s.io/v1
        kind: RoleBinding
        metadata:
          name: servicecatalog.k8s.io:apiserver-authentication-reader
          namespace: kube-system
        roleRef:
          apiGroup: rbac.authorization.k8s.io
          kind: Role
          name: extension-apiserver-authentication-reader
        subjects:
        - apiGroup: ""
          kind: ServiceAccount
          name: cloud-controller-manager
          namespace: kube-system
        - apiGroup: ""
          kind: User
          name: cloud-controller-manager
        ---
        apiVersion: rbac.authorization.k8s.io/v1
        kind: ClusterRoleBinding
        metadata:
          name: system:cloud-controller-manager
        roleRef:
          apiGroup: rbac.authorization.k8s.io
          kind: ClusterRole
          name: system:cloud-controller-manager
        subjects:
        - kind: ServiceAccount
          name: cloud-controller-manager
          namespace: kube-system
        - kind: User
          name: cloud-controller-manager
        ---
        apiVersion: apps/v1
        kind: DaemonSet
        metadata:
          annotations:
            scheduler.alpha.kubernetes.io/critical-pod: ""
          labels:
            component: cloud-controller-manager
            tier: control-plane
            k8s-app: vsphere-cloud-controller-manager
          name: vsphere-cloud-controller-manager
          namespace: kube-system
        spec:
          selector:
            matchLabels:
              k8s-app: vsphere-cloud-controller-manager
          updateStrategy:
            type: RollingUpdate
          template:
            metadata:
              labels:
                component: cloud-controller-manager
                k8s-app: vsphere-cloud-controller-manager
            spec:
              securityContext:
                runAsUser: 1001
              automountServiceAccountToken: true
              # Optional: required when the CPI image is stored in a private
              # registry that needs authentication. The platform automatically
              # syncs a dockerconfigjson secret named "global-registry-auth"
              # into every namespace of the workload cluster when the
              # `global` cluster secret "public-registry-credential"
              # (data.content) is configured. If your environment does not
              # use a private registry, remove the imagePullSecrets block.
              imagePullSecrets:
              - name: global-registry-auth
              serviceAccountName: cloud-controller-manager
              hostNetwork: true
              tolerations:
              - operator: Exists
              - key: node.cloudprovider.kubernetes.io/uninitialized
                value: "true"
                effect: NoSchedule
              - key: node-role.kubernetes.io/master
                effect: NoSchedule
              - key: node.kubernetes.io/not-ready
                effect: NoSchedule
                operator: Exists
              containers:
              - name: vsphere-cloud-controller-manager
                image: <image_registry>/ait/cloud-provider-vsphere:<cpi_image_tag>
                args:
                - --v=2
                - --cloud-provider=vsphere
                - --cloud-config=/etc/cloud/vsphere.conf
                volumeMounts:
                - mountPath: /etc/cloud
                  name: vsphere-config-volume
                  readOnly: true
                resources:
                  requests:
                    cpu: 200m
              volumes:
              - name: vsphere-config-volume
                configMap:
                  name: cloud-config
        ---
        apiVersion: v1
        kind: Service
        metadata:
          labels:
            component: cloud-controller-manager
          name: vsphere-cloud-controller-manager
          namespace: kube-system
        spec:
          type: NodePort
          ports:
          - port: 43001
            protocol: TCP
            targetPort: 43001
          selector:
            component: cloud-controller-manager
    ---
    apiVersion: addons.cluster.x-k8s.io/v1beta1
    kind: ClusterResourceSet
    metadata:
      name: <cluster_name>-vsphere-cpi
      namespace: <namespace>
    spec:
      strategy: Reconcile
      clusterSelector:
        matchLabels:
          addons.cluster.x-k8s.io/vsphere-cpi: "enabled"
      resources:
      - name: <cluster_name>-vsphere-cpi-config
        kind: ConfigMap
      - name: <cluster_name>-vsphere-cpi-secret
        kind: Secret
      - name: <cluster_name>-vsphere-cpi-manifests
        kind: ConfigMap

    Примените манифест:

    kubectl apply -f 15-vsphere-cpi-clusterresourceset.yaml

    Создание machine config pool

    Создайте machine config pool для control plane.

    INFO

    Каждый слот узла описывает свою NIC-раскладку в network.primary (обязательно) и network.additional (необязательный список). networkName для primary NIC обязателен, а provider определяет имя Kubernetes node, DNS SAN для kubelet serving certificate и node-ip kubelet на основе hostname и вычисленных адресов primary NIC. Значение hostname должно быть допустимым DNS-1123 subdomain.

    INFO

    deviceName является необязательным. Если вам не нужно принудительно задавать имя NIC внутри guest, удалите строку deviceName из каждого слота узла. Provider назначает имена NIC, такие как eth0, eth1, в порядке NIC.

    02-vspheremachineconfigpool-control-plane.yaml
    apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
    kind: VSphereMachineConfigPool
    metadata:
      name: <cluster_name>-cp-pool
      namespace: <namespace>
    spec:
      clusterRef:
        apiVersion: cluster.x-k8s.io/v1beta1
        kind: Cluster
        name: <cluster_name>
      datacenter: "<default_datacenter>"
      releaseDelayHours: <release_delay_hours>
      configs:
      - hostname: "<cp_node_name_1>"
        datacenter: "<master_01_datacenter>"
        network:
          primary:
            networkName: "<nic1_network_name>"
            deviceName: "<nic1_device_name>"
            ip: "<master_01_nic1_ip>/<nic1_prefix>"
            gateway: "<nic1_gateway>"
            dns:
            - "<nic1_dns_1>"
        persistentDisks:
        - name: var-cpaas
          sizeGiB: <cp_var_cpaas_size_gib>
          mountPath: /var/cpaas
          fsFormat: ext4
        - name: var-lib-containerd
          sizeGiB: <cp_var_lib_containerd_size_gib>
          mountPath: /var/lib/containerd
          fsFormat: ext4
        - name: var-lib-etcd
          sizeGiB: <cp_var_lib_etcd_size_gib>
          mountPath: /var/lib/etcd
          fsFormat: ext4
          wipeFilesystem: true
      - hostname: "<cp_node_name_2>"
        datacenter: "<master_02_datacenter>"
        network:
          primary:
            networkName: "<nic1_network_name>"
            deviceName: "<nic1_device_name>"
            ip: "<master_02_nic1_ip>/<nic1_prefix>"
            gateway: "<nic1_gateway>"
            dns:
            - "<nic1_dns_1>"
        persistentDisks:
        - name: var-cpaas
          sizeGiB: <cp_var_cpaas_size_gib>
          mountPath: /var/cpaas
          fsFormat: ext4
        - name: var-lib-containerd
          sizeGiB: <cp_var_lib_containerd_size_gib>
          mountPath: /var/lib/containerd
          fsFormat: ext4
        - name: var-lib-etcd
          sizeGiB: <cp_var_lib_etcd_size_gib>
          mountPath: /var/lib/etcd
          fsFormat: ext4
          wipeFilesystem: true
      - hostname: "<cp_node_name_3>"
        datacenter: "<master_03_datacenter>"
        network:
          primary:
            networkName: "<nic1_network_name>"
            deviceName: "<nic1_device_name>"
            ip: "<master_03_nic1_ip>/<nic1_prefix>"
            gateway: "<nic1_gateway>"
            dns:
            - "<nic1_dns_1>"
        persistentDisks:
        - name: var-cpaas
          sizeGiB: <cp_var_cpaas_size_gib>
          mountPath: /var/cpaas
          fsFormat: ext4
        - name: var-lib-containerd
          sizeGiB: <cp_var_lib_containerd_size_gib>
          mountPath: /var/lib/containerd
          fsFormat: ext4
        - name: var-lib-etcd
          sizeGiB: <cp_var_lib_etcd_size_gib>
          mountPath: /var/lib/etcd
          fsFormat: ext4
          wipeFilesystem: true

    Создайте machine config pool для worker-узлов.

    03-vspheremachineconfigpool-worker.yaml
    apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
    kind: VSphereMachineConfigPool
    metadata:
      name: <cluster_name>-worker-pool
      namespace: <namespace>
    spec:
      clusterRef:
        apiVersion: cluster.x-k8s.io/v1beta1
        kind: Cluster
        name: <cluster_name>
      datacenter: "<default_datacenter>"
      releaseDelayHours: <release_delay_hours>
      configs:
      - hostname: "<worker_node_name_1>"
        datacenter: "<worker_01_datacenter>"
        network:
          primary:
            networkName: "<nic1_network_name>"
            deviceName: "<nic1_device_name>"
            ip: "<worker_01_nic1_ip>/<nic1_prefix>"
            gateway: "<nic1_gateway>"
            dns:
            - "<nic1_dns_1>"
        persistentDisks:
        - name: var-cpaas
          sizeGiB: <worker_var_cpaas_size_gib>
          mountPath: /var/cpaas
          fsFormat: ext4
        - name: var-lib-containerd
          sizeGiB: <worker_var_lib_containerd_size_gib>
          mountPath: /var/lib/containerd
          fsFormat: ext4

    Примените оба манифеста:

    kubectl apply -f 02-vspheremachineconfigpool-control-plane.yaml
    kubectl apply -f 03-vspheremachineconfigpool-worker.yaml

    Создание объектов control plane

    Создайте объекты VSphereMachineTemplate и KubeadmControlPlane. Замените шаблоны в следующем полном template значениями, собранными в документе с контрольным списком.

    cloneMode и diskGiB по-прежнему присутствуют в template, поскольку CAPV принимает оба поля. На практике diskGiB влияет на системный диск только тогда, когда фактическая операция клонирования — это fullClone. Если cloneMode равен linkedClone и template имеет пригодный snapshot, CAPV завершает linked clone, и размер системного диска остается равным размеру исходного template. Если пригодный snapshot отсутствует, CAPV переходит на fullClone, и diskGiB снова применяется.

    20-control-plane.yaml
    apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
    kind: VSphereMachineTemplate
    metadata:
      name: <cluster_name>-control-plane
      namespace: <namespace>
    spec:
      template:
        spec:
          server: "<vsphere_server>"
          template: "<template_name>"
          cloneMode: <clone_mode>
          folder: "<vm_folder>"
          datastore: "<cp_system_datastore>"
          diskGiB: <cp_system_disk_gib>
          memoryMiB: <cp_memory_mib>
          numCPUs: <cp_num_cpus>
          os: Linux
          powerOffMode: <power_off_mode>
          network:
            devices:
            - networkName: "<nic1_network_name>"
          machineConfigPoolRef:
            apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
            kind: VSphereMachineConfigPool
            name: <cluster_name>-cp-pool
            namespace: <namespace>
    ---
    apiVersion: controlplane.cluster.x-k8s.io/v1beta1
    kind: KubeadmControlPlane
    metadata:
      name: <cluster_name>-kcp
      namespace: <namespace>
    spec:
      rolloutStrategy:
        type: RollingUpdate
        rollingUpdate:
          maxSurge: 0
      version: "<k8s_version>"
      replicas: <cp_replicas>
      machineTemplate:
        nodeDrainTimeout: 1m
        nodeDeletionTimeout: 5m
        metadata:
          labels:
            node-role.kubernetes.io/control-plane: ""
        infrastructureRef:
          apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
          kind: VSphereMachineTemplate
          name: <cluster_name>-control-plane
      kubeadmConfigSpec:
        users:
        - name: boot
          sudo: ALL=(ALL) NOPASSWD:ALL
          sshAuthorizedKeys:
          - "<ssh_public_key>"
        files:
        - path: /etc/kubernetes/admission/psa-config.yaml
          owner: "root:root"
          permissions: "0644"
          content: |
            apiVersion: apiserver.config.k8s.io/v1
            kind: AdmissionConfiguration
            plugins:
            - name: PodSecurity
              configuration:
                apiVersion: pod-security.admission.config.k8s.io/v1
                kind: PodSecurityConfiguration
                defaults:
                  enforce: "privileged"
                  enforce-version: "latest"
                  audit: "baseline"
                  audit-version: "latest"
                  warn: "baseline"
                  warn-version: "latest"
                exemptions:
                  usernames: []
                  runtimeClasses: []
                  namespaces:
                  - kube-system
                  - <namespace>
        - path: /etc/kubernetes/patches/kubeletconfiguration0+strategic.json
          owner: "root:root"
          permissions: "0644"
          content: |
            {
              "apiVersion": "kubelet.config.k8s.io/v1beta1",
              "kind": "KubeletConfiguration",
              "protectKernelDefaults": true,
              "streamingConnectionIdleTimeout": "5m",
              "tlsCertFile": "/etc/kubernetes/pki/kubelet.crt",
              "tlsPrivateKeyFile": "/etc/kubernetes/pki/kubelet.key"
            }
        # Generate the encryption key with: head -c 32 /dev/urandom | base64
        - path: /etc/kubernetes/encryption-provider.conf
          owner: "root:root"
          append: false
          permissions: "0644"
          content: |
            apiVersion: apiserver.config.k8s.io/v1
            kind: EncryptionConfiguration
            resources:
            - resources:
              - secrets
              providers:
              - aescbc:
                  keys:
                  - name: key1
                    secret: <encryption_provider_secret>
        - path: /etc/kubernetes/audit/policy.yaml
          owner: "root:root"
          append: false
          permissions: "0644"
          content: |
            apiVersion: audit.k8s.io/v1
            kind: Policy
            omitStages:
            - "RequestReceived"
            rules:
            - level: None
              users:
              - system:kube-controller-manager
              - system:kube-scheduler
              - system:serviceaccount:kube-system:endpoint-controller
              verbs: ["get", "update"]
              namespaces: ["kube-system"]
              resources:
              - group: ""
                resources: ["endpoints"]
            - level: None
              nonResourceURLs:
              - /healthz*
              - /version
              - /swagger*
            - level: None
              resources:
              - group: ""
                resources: ["events"]
            - level: None
              resources:
              - group: "devops.alauda.io"
            - level: None
              verbs: ["get", "list", "watch"]
            - level: None
              resources:
              - group: "coordination.k8s.io"
                resources: ["leases"]
            - level: None
              resources:
              - group: "authorization.k8s.io"
                resources: ["subjectaccessreviews", "selfsubjectaccessreviews"]
              - group: "authentication.k8s.io"
                resources: ["tokenreviews"]
            - level: None
              resources:
              - group: "app.alauda.io"
                resources: ["imagewhitelists"]
              - group: "k8s.io"
                resources: ["namespaceoverviews"]
            - level: Metadata
              resources:
              - group: ""
                resources: ["secrets", "configmaps"]
            - level: Metadata
              resources:
              - group: "operator.connectors.alauda.io"
                resources: ["installmanifests"]
              - group: "operators.katanomi.dev"
                resources: ["katanomis"]
            - level: RequestResponse
              resources:
              - group: ""
              - group: "aiops.alauda.io"
              - group: "apps"
              - group: "app.k8s.io"
              - group: "authentication.istio.io"
              - group: "auth.alauda.io"
              - group: "autoscaling"
              - group: "asm.alauda.io"
              - group: "clusterregistry.k8s.io"
              - group: "crd.alauda.io"
              - group: "infrastructure.alauda.io"
              - group: "monitoring.coreos.com"
              - group: "operators.coreos.com"
              - group: "networking.istio.io"
              - group: "extensions.istio.io"
              - group: "install.istio.io"
              - group: "security.istio.io"
              - group: "telemetry.istio.io"
              - group: "opentelemetry.io"
              - group: "networking.k8s.io"
              - group: "portal.alauda.io"
              - group: "rbac.authorization.k8s.io"
              - group: "storage.k8s.io"
              - group: "tke.cloud.tencent.com"
              - group: "devopsx.alauda.io"
              - group: "core.katanomi.dev"
              - group: "deliveries.katanomi.dev"
              - group: "integrations.katanomi.dev"
              - group: "artifacts.katanomi.dev"
              - group: "builds.katanomi.dev"
              - group: "versioning.katanomi.dev"
              - group: "sources.katanomi.dev"
              - group: "tekton.dev"
              - group: "operator.tekton.dev"
              - group: "eventing.knative.dev"
              - group: "flows.knative.dev"
              - group: "messaging.knative.dev"
              - group: "operator.knative.dev"
              - group: "sources.knative.dev"
              - group: "operator.devops.alauda.io"
              - group: "flagger.app"
              - group: "jaegertracing.io"
              - group: "velero.io"
                resources: ["deletebackuprequests"]
              - group: "connectors.alauda.io"
              - group: "operator.connectors.alauda.io"
                resources: ["connectorscores", "connectorsgits", "connectorsocis"]
            - level: Metadata
        - path: /usr/local/bin/capv-load-local-images.sh
          owner: "root:root"
          permissions: "0755"
          content: |
            #!/bin/bash
            set -euo pipefail
            until mountpoint -q /var/lib/containerd; do
              echo "waiting for /var/lib/containerd mount"
              sleep 1
            done
            systemctl restart containerd
            until systemctl is-active --quiet containerd; do
              echo "waiting for containerd"
              sleep 1
            done
            if [ ! -d "/root/images" ]; then
              echo "ERROR: /root/images directory not found" >&2
              exit 1
            fi
            image_count=0
            for image_file in /root/images/*.tar; do
              if [ -f "$image_file" ]; then
                echo "importing image: $image_file"
                ctr -n k8s.io images import "$image_file"
                image_count=$((image_count + 1))
              fi
            done
            if [ "$image_count" -eq 0 ]; then
              echo "ERROR: no tar files found in /root/images" >&2
              exit 1
            fi
            echo "imported $image_count images"
        preKubeadmCommands:
        - hostnamectl set-hostname "{{ ds.meta_data.hostname }}"
        - echo "::1         ipv6-localhost ipv6-loopback localhost6 localhost6.localdomain6" >/etc/hosts
        - echo "127.0.0.1   {{ ds.meta_data.hostname }} {{ local_hostname }} localhost localhost.localdomain localhost4 localhost4.localdomain4" >>/etc/hosts
        - while ! ip route | grep -q "default via"; do sleep 1; done; echo "NetworkManager started"
        - /usr/local/bin/capv-load-local-images.sh
        postKubeadmCommands:
        - chmod 600 /var/lib/kubelet/config.yaml
        clusterConfiguration:
          imageRepository: <image_registry>/tkestack
          dns:
            imageTag: <dns_image_tag>
          etcd:
            local:
              imageTag: <etcd_image_tag>
          apiServer:
            extraArgs:
              admission-control-config-file: /etc/kubernetes/admission/psa-config.yaml
              audit-log-format: json
              audit-log-maxage: "30"
              audit-log-maxbackup: "10"
              audit-log-maxsize: "200"
              audit-log-mode: batch
              audit-log-path: /etc/kubernetes/audit/audit.log
              audit-policy-file: /etc/kubernetes/audit/policy.yaml
              encryption-provider-config: /etc/kubernetes/encryption-provider.conf
              kubelet-certificate-authority: /etc/kubernetes/pki/ca.crt
              profiling: "false"
              tls-cipher-suites: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
              tls-min-version: VersionTLS12
            extraVolumes:
            - hostPath: /etc/kubernetes
              mountPath: /etc/kubernetes
              name: vol-dir-0
              pathType: Directory
          controllerManager:
            extraArgs:
              bind-address: "::"
              cloud-provider: external
              profiling: "false"
              tls-min-version: VersionTLS12
          scheduler:
            extraArgs:
              bind-address: "::"
              profiling: "false"
              tls-min-version: VersionTLS12
        initConfiguration:
          nodeRegistration:
            criSocket: /var/run/containerd/containerd.sock
            ignorePreflightErrors:
            - ImagePull
            kubeletExtraArgs:
              cloud-provider: external
              node-labels: kube-ovn/role=master
            name: '{{ local_hostname }}'
          patches:
            directory: /etc/kubernetes/patches
        joinConfiguration:
          nodeRegistration:
            criSocket: /var/run/containerd/containerd.sock
            ignorePreflightErrors:
            - ImagePull
            kubeletExtraArgs:
              cloud-provider: external
              node-labels: kube-ovn/role=master
              volume-plugin-dir: "/opt/libexec/kubernetes/kubelet-plugins/volume/exec/"
            name: '{{ local_hostname }}'
          patches:
            directory: /etc/kubernetes/patches

    Примените манифест:

    kubectl apply -f 20-control-plane.yaml

    Создание объектов worker

    Создайте шаблон worker machine, bootstrap template и MachineDeployment.

    30-workers-md-0.yaml
    apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
    kind: VSphereMachineTemplate
    metadata:
      name: <cluster_name>-worker
      namespace: <namespace>
    spec:
      template:
        spec:
          server: "<vsphere_server>"
          template: "<template_name>"
          cloneMode: <clone_mode>
          folder: "<vm_folder>"
          datastore: "<worker_system_datastore>"
          diskGiB: <worker_system_disk_gib>
          memoryMiB: <worker_memory_mib>
          numCPUs: <worker_num_cpus>
          os: Linux
          powerOffMode: <power_off_mode>
          network:
            devices:
            - networkName: "<nic1_network_name>"
          machineConfigPoolRef:
            apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
            kind: VSphereMachineConfigPool
            name: <cluster_name>-worker-pool
            namespace: <namespace>
    ---
    apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
    kind: KubeadmConfigTemplate
    metadata:
      name: <cluster_name>-worker-bootstrap
      namespace: <namespace>
    spec:
      template:
        spec:
          files:
          - path: /etc/kubernetes/patches/kubeletconfiguration0+strategic.json
            owner: "root:root"
            permissions: "0644"
            content: |
              {
                "apiVersion": "kubelet.config.k8s.io/v1beta1",
                "kind": "KubeletConfiguration",
                "protectKernelDefaults": true,
                "staticPodPath": null,
                "streamingConnectionIdleTimeout": "5m",
                "tlsCertFile": "/etc/kubernetes/pki/kubelet.crt",
                "tlsPrivateKeyFile": "/etc/kubernetes/pki/kubelet.key"
              }
          - path: /usr/local/bin/capv-load-local-images.sh
            owner: "root:root"
            permissions: "0755"
            content: |
              #!/bin/bash
              set -euo pipefail
              until mountpoint -q /var/lib/containerd; do
                echo "waiting for /var/lib/containerd mount"
                sleep 1
              done
              systemctl restart containerd
              until systemctl is-active --quiet containerd; do
                echo "waiting for containerd"
                sleep 1
              done
              if [ ! -d "/root/images" ]; then
                echo "ERROR: /root/images directory not found" >&2
                exit 1
              fi
              image_count=0
              for image_file in /root/images/*.tar; do
                if [ -f "$image_file" ]; then
                  echo "importing image: $image_file"
                  ctr -n k8s.io images import "$image_file"
                  image_count=$((image_count + 1))
                fi
              done
              if [ "$image_count" -eq 0 ]; then
                echo "ERROR: no tar files found in /root/images" >&2
                exit 1
              fi
              echo "imported $image_count images"
          joinConfiguration:
            nodeRegistration:
              criSocket: /var/run/containerd/containerd.sock
              ignorePreflightErrors:
              - ImagePull
              kubeletExtraArgs:
                cloud-provider: external
                volume-plugin-dir: "/opt/libexec/kubernetes/kubelet-plugins/volume/exec/"
              name: '{{ local_hostname }}'
            patches:
              directory: /etc/kubernetes/patches
          preKubeadmCommands:
          - hostnamectl set-hostname "{{ ds.meta_data.hostname }}"
          - echo "::1         ipv6-localhost ipv6-loopback localhost6 localhost6.localdomain6" >/etc/hosts
          - echo "127.0.0.1   {{ ds.meta_data.hostname }} {{ local_hostname }} localhost localhost.localdomain localhost4 localhost4.localdomain4" >>/etc/hosts
          - while ! ip route | grep -q "default via"; do sleep 1; done; echo "NetworkManager started"
          - /usr/local/bin/capv-load-local-images.sh
          postKubeadmCommands:
          - chmod 600 /var/lib/kubelet/config.yaml
          users:
          - name: boot
            sudo: ALL=(ALL) NOPASSWD:ALL
            sshAuthorizedKeys:
            - "<ssh_public_key>"
    ---
    apiVersion: cluster.x-k8s.io/v1beta1
    kind: MachineDeployment
    metadata:
      name: <cluster_name>-md-0
      namespace: <namespace>
    spec:
      clusterName: <cluster_name>
      replicas: <worker_replicas>
      strategy:
        type: RollingUpdate
        rollingUpdate:
          maxSurge: 0
          maxUnavailable: 1
      selector:
        matchLabels:
          nodepool: md-0
      template:
        metadata:
          labels:
            cluster.x-k8s.io/cluster-name: <cluster_name>
            nodepool: md-0
        spec:
          clusterName: <cluster_name>
          version: "<k8s_version>"
          bootstrap:
            configRef:
              apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
              kind: KubeadmConfigTemplate
              name: <cluster_name>-worker-bootstrap
          infrastructureRef:
            apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
            kind: VSphereMachineTemplate
            name: <cluster_name>-worker

    Примените манифест:

    kubectl apply -f 30-workers-md-0.yaml

    В базовом сценарии обратите внимание на следующие правила для worker:

    • failureDomain по умолчанию не задается в основном манифесте worker, поскольку базовый сценарий предполагает один datacenter. Если вам нужно, чтобы worker MachineDeployment размещался в определенной VSphereDeploymentZone, добавьте failureDomain, как описано в Сценарии расширения.
    • В некоторых средах в KubeadmConfigTemplate добавляются дополнительные команды замены runtime-image или команды перезапуска сервисов. Эти команды намеренно не включены в базовый пример. Добавляйте их только тогда, когда требования платформы в вашей среде явно этого требуют.

    Ожидание готовности кластера

    После применения всех манифестов создание кластера происходит асинхронно. Отслеживайте прогресс с помощью:

    kubectl -n <namespace> get cluster,kubeadmcontrolplane,machinedeployment,machine -w

    Дождитесь, пока KubeadmControlPlane сообщит ожидаемое количество ready replicas, и все объекты Machine перейдут в фазу Running, прежде чем продолжать проверку.

    Проверка

    Используйте следующие команды для проверки процесса создания кластера.

    1. Проверьте ресурсы доставки CPI в кластере global:
      kubectl -n <namespace> get clusterresourceset
      kubectl -n <namespace> get clusterresourcesetbinding
    2. Экспортируйте kubeconfig рабочего кластера:
      kubectl -n <namespace> get secret <cluster_name>-kubeconfig -o jsonpath='{.data.value}' | base64 -d > /tmp/<cluster_name>.kubeconfig
    3. Проверьте, создан ли daemonset vSphere CPI в рабочем кластере:
      kubectl --kubeconfig=/tmp/<cluster_name>.kubeconfig -n kube-system get daemonset
    4. Проверьте объекты кластера global:
      kubectl -n <namespace> get cluster,vspherecluster,kubeadmcontrolplane,machinedeployment,machine,vspheremachine,vspherevm
    5. Проверьте рабочие узлы:
      kubectl --kubeconfig=/tmp/<cluster_name>.kubeconfig get nodes -o wide

    Подтвердите следующие результаты:

    • vsphere-cloud-controller-manager присутствует в рабочем кластере.
    • Созданы узлы control plane и worker.
    • В конечном итоге узлы становятся Ready.

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

    Если сценарий завершается сбоем, сначала используйте следующие команды:

    kubectl -n <namespace> describe cluster <cluster_name>
    kubectl -n <namespace> describe vspherecluster <cluster_name>
    kubectl -n <namespace> describe kubeadmcontrolplane <cluster_name>-kcp
    kubectl -n <namespace> describe machinedeployment <cluster_name>-md-0
    kubectl -n <namespace> get cluster,vspherecluster,kubeadmcontrolplane,machinedeployment,machine,vspheremachine,vspherevm
    kubectl -n cpaas-system logs deploy/capi-controller-manager

    В первую очередь проверьте следующее:

    • Если ресурсы CPI не доставляются, проверьте ClusterResourceSet=true, ClusterResourceSet и ClusterResourceSetBinding.
    • Если ClusterResourceSet существует, но ClusterResourceSetBinding не создается, проверьте, есть ли у контроллера необходимое разрешение на удаление для связанных ресурсов ConfigMap и Secret.
    • Если сетевой плагин не установлен, проверьте, что требуемые аннотации кластера присутствуют и что контроллеры платформы их обработали.
    • Если аннотация cpaas.io/registry-address отсутствует, проверьте public registry credential и контроллер платформы, который внедряет аннотацию.
    • Если машина зависла в Provisioning, проверьте условия VSphereMachine для MachineConfigPoolReady — они показывают, произошел ли сбой выделения слота из-за привязки к пулу или несоответствия datacenter.
    • Если VM ожидает назначения IP, проверьте VMware Tools, параметры статического IP и VSphereVM.status.addresses.
    • Если объекты workload Node остаются без spec.providerID, сначала проверьте ресурсы доставки CPI, а затем проверьте наличие дублирующихся guest hostnames в vCenter. Когда старая VM в том же datacenter по-прежнему сообщает тот же guest hostname, что и новый узел, cloud-provider-vsphere может перейти к поиску по node-name, закэшировать старую VM и отклонить новый узел, поскольку IP VM не совпадает с IP node kubelet. Проверьте логи лидера vsphere-cloud-controller-manager, SystemUUID узла, реальный UUID VM и значения guest hostname/IP в vCenter. После того как вы исправите или удалите дублирующийся hostname либо конфликт со старой VM, перезапустите Pods vsphere-cloud-controller-manager рабочего кластера, чтобы очистить некорректный in-memory cache:
      kubectl --kubeconfig=/tmp/<cluster_name>.kubeconfig -n kube-system delete pod \
        -l k8s-app=vsphere-cloud-controller-manager
    • Если пространство datastore исчерпано, проверьте, остались ли старые каталоги VM или файлы .vmdk в целевом datastore.
    • Если размер системного диска template не соответствует значениям в манифесте, сначала проверьте фактический режим клонирования. Когда VM была создана как linkedClone, системный диск остается равным размеру template, а diskGiB игнорируется. Только fullClone использует diskGiB, и в этом случае diskGiB не должен быть меньше размера диска template.
    • Если endpoint control plane не поднимается, проверьте load balancer, VIP и порт 6443.
    • Если TLS-соединение с vCenter не удается, проверьте thumbprint, адрес vCenter и не мешают ли подключению настройки proxy.

    При анализе логов контроллера используйте следующие правила:

    • deploy/capi-controller-manager работает в namespace cpaas-system кластера global.
    • Не используйте kubeconfig рабочего кластера для просмотра логов capi-controller-manager.
    • Если контроллеры платформы обрабатывают аннотации сети кластера, также просмотрите логи platform network-controller и platform cluster-lifecycle-controller.

    Следующие шаги

    После запуска базовой топологии продолжите с Сценариями расширения, если вам нужен второй NIC, несколько datacenter, failure domain, дополнительные data disk или больше worker replicas.