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

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

    Сценарии

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

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

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

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

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

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

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

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

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

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

    Ключевые объекты

    ClusterResourceSet

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

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

    Компонент vSphere CPI

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

    machine config pool

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

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

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

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

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

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

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

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

    В базовом workflow:

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

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

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

    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.

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

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

    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

    Шаги

    Проверка среды

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

    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}'

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

    • Management cluster доступен.
    • Alauda Container Platform Kubeadm Provider и Alauda Container Platform VMware vSphere Infrastructure Provider запущены.
    • Аргументы controller включают ClusterResourceSet=true.
    • data.content в секрете public registry credential не пустой.

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

    • Адрес vCenter доступен по сети.
    • Имя пользователя и пароль 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 и секрета учетных данных vCenter

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

    Этот workflow сохраняет объекты workload cluster в namespace cpaas-system. В манифестах и командах ниже замените каждый placeholder <namespace> на cpaas-system.

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

    Создайте секрет учетных данных 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

    Создайте базовый манифест кластера с настройками сети workload cluster, конечной точкой 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>
      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, чтобы workload cluster автоматически получал конфигурацию и манифесты vSphere CPI после того, как workload API server станет доступен.

    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>"
    
            [Labels]
            zone = "k8s-zone"
            region = "k8s-region"
    
            [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
              # management-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 pools

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

    INFO

    Каждый слот узла объявляет свою NIC-структуру в network.primary (обязательно) и network.additional (необязательный список). networkName для основной NIC обязателен, а provider выводит имя узла Kubernetes, DNS SAN сертификата kubelet serving certificate и node-ip kubelet из hostname и разрешенных адресов основной 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: <cp_pool_name>
      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_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: <worker_pool_name>
      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. Замените placeholders в следующем полном шаблоне значениями, собранными в документе checklist.

    И cloneMode, и diskGiB остаются в шаблоне, потому что 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>
          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: <cp_pool_name>
            namespace: <namespace>
    ---
    apiVersion: controlplane.cluster.x-k8s.io/v1beta1
    kind: KubeadmControlPlane
    metadata:
      name: <cluster_name>
      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 template, 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>
          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: <worker_pool_name>
            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

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

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

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

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

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

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

    Проверка

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

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

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

    • vsphere-cloud-controller-manager появляется в workload cluster.
    • Узлы control plane и worker созданы.
    • Узлы в конечном итоге переходят в состояние Ready.

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

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

    kubectl -n <namespace> describe cluster <cluster_name>
    kubectl -n <namespace> describe vspherecluster <cluster_name>
    kubectl -n <namespace> describe kubeadmcontrolplane <cluster_name>
    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 не создается, проверьте, есть ли у controller требуемое право на удаление для связанных ресурсов ConfigMap и Secret.
    • Если сетевой плагин не установлен, проверьте наличие требуемых аннотаций кластера и то, что их обработали контроллеры платформы.
    • Если аннотация cpaas.io/registry-address отсутствует, проверьте public registry credential и платформенный controller, который внедряет аннотацию.
    • Если машина зависла в состоянии Provisioning, проверьте условия VSphereMachine для MachineConfigPoolReady — они показывают, не произошел ли сбой выделения слота из-за привязки pool или несоответствия datacenter.
    • Если VM ожидает назначение IP, проверьте VMware Tools, настройки статического IP и VSphereVM.status.addresses.
    • Если пространство datastore исчерпано, проверьте, не остались ли старые каталоги VM или файлы .vmdk в целевом datastore.
    • Если размер системного диска template не совпадает со значениями манифеста, сначала проверьте фактический режим clone. Когда VM была создана как linkedClone, системный диск остается равным размеру template, а diskGiB игнорируется. Только fullClone использует diskGiB, и в этом случае diskGiB не должен быть меньше размера диска template.
    • Если конечная точка control plane не поднимается, проверьте load balancer, VIP и порт 6443.
    • Если TLS-подключение к vCenter не удается, проверьте thumbprint, адрес vCenter и наличие proxy-настроек, мешающих подключению.

    При просмотре логов controller соблюдайте следующие правила:

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

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

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