在 VMware vSphere 上创建集群
本文介绍如何通过向 ACP global 管理集群应用 Cluster API 清单,创建 VMware vSphere 业务集群。该操作步骤不会创建 global 集群本身。本文介绍一种受支持的最小拓扑,其中包含一个数据中心、每个节点一个 NIC,并通过 VSphereMachineConfigPool 分配静态 IP。
Provider 要求
从管理集群上的 provider AppRelease 中读取已安装的 provider 版本;不要根据 ACP 版本推断。请参阅识别已安装的 Provider 版本。
当控制器引用的 VSphereMachineTemplate 设置 machineConfigPoolRef 时,以下规则适用——本页介绍的固定 IP 拓扑。Provider 的准入 webhook 会对 CREATE 和 UPDATE 强制执行 KubeadmControlPlane 和 pool 规则,并对 DELETE 强制执行最后一条规则。
KubeadmControlPlane.spec.rolloutStrategy.rollingUpdate.maxSurge 是整数 0,且 spec.replicas 为 3 或更大。未设置 maxSurge 时,表示采用 Cluster API 默认值 1,该值不被接受。
MachineDeployment.spec.strategy.rollingUpdate.maxSurge 是 0。采用默认的 maxSurge 值 1 时,滚动更新需要额外的槽位,而已满的 pool 不具备该槽位,因此更新会停滞。strategy.type: OnDelete 无需设置 surge。
VSphereMachineConfigPool.spec.configs 至少声明一个槽位,且每个槽位都声明 network。绑定到同一个 Cluster 的所有 pool 中,主机名和主 IP 地址必须唯一;一个 pool 仅服务于一个控制器。
- 已处于
InUse 或 Released 状态的槽位会拒绝某些更改。请参阅已分配槽位的限制。
- 仍拥有
InUse 槽位的 pool 无法删除。
使用验证安装中的 webhook 检查,确认环境中已注册 webhook。如果 webhook 未注册,仍需满足相同的值要求:固定 IP 槽位模型没有可供 surge 机器使用的空闲槽位,因此不符合要求的滚动更新会停滞,而不是在开始前被拒绝。
场景
在以下场景中使用本文档:
- 您希望在环境中创建第一个基线 VMware vSphere 业务集群。
- 初始验证时,您使用一个数据中心,并为每个节点配置一个 NIC。
- 您希望在启用高级置放或网络功能之前,让首次部署尽可能简单。
本文档适用于以下部署模型:
- CAPV 直接连接到 vCenter。
- 控制平面节点和 worker 节点均使用
VSphereMachineConfigPool 分配静态 IP 和数据磁盘。
ClusterResourceSet 自动交付 vSphere CPI 组件。
- 首次验证使用一个数据中心,并为每个节点配置一个 NIC。
本文档不适用于以下场景:
- 依赖 vSphere Supervisor 或
vm-operator 的部署。
- 不使用
VSphereMachineConfigPool 的部署。
本文档针对当前平台环境编写。kube-ovn 交付路径依赖于平台控制器读取 Cluster 资源上的 annotation,因此本文档介绍的操作流程并非面向平台环境之外的通用独立 CAPV 部署指南。
如何使用本文档
- 完成基础设施和参数检查清单。
- 按照下文的操作步骤准备基线清单文件。
- 应用清单之前,仅添加所需的创建时拓扑变体。
- 应用完整的清单集并完成验证检查。
- 集群运行后,使用在 VMware vSphere 上管理节点进行扩容、机器模板更改和运行时拓扑更改。
基线是验证参考。如果目标集群需要多个可选拓扑功能,请一次引入并验证一项清单更改,再将它们组合使用。
先决条件
开始前,请确保满足以下条件:
- Cluster Bootstrap Provider 和 Infrastructure Provider for Virtualization Platform 已上传并安装到
global 集群。无论 global 集群本身运行在哪种 IaaS 平台上,此要求都适用。请参阅安装 Provider。
- 您已完成 VMware vSphere 基础设施准备。
global 集群可以访问 vCenter。
- 目标模板、网络、数据存储和 vCenter 资源池均可用。
- 已选择控制平面端点模式。对于外部 LoadBalancer,需满足端点契约。对于 Self-built VIP,需满足Self-built VIP 先决条件。集群创建后无法更改此模式,因此请现在做出决定:请参阅控制平面端点模式。
- 所有必需的静态 IP 地址均已分配、未被使用,并且未被同一
Cluster 绑定的其他 VSphereMachineConfigPool 中的 slot 声明。
- 已启用
ClusterResourceSet=true。
- 已确定此集群使用的镜像 registry。默认情况下,集群继承
global 集群的 registry;如果该 registry 需要身份验证,则必须提供有效的 public-registry-credential Secret。若要改为从专用 registry 拉取平台镜像,请在创建集群前按照为业务集群选择镜像 Registry操作;之后无法更改此绑定。
- 上述选定的 registry 可从业务节点访问,并包含所需的 Kube-OVN、CoreDNS 和 vSphere CPI 标签。
- 平台能够处理安装网络插件所需的集群 annotation。
VSphereCluster.spec.identityRef 引用的 vCenter 帐户满足安装 VMware vSphere Provider中的 Custom Fields 权限要求。
global 集群是活动管理集群,而不是灾难恢复备用集群。请参阅备用管理集群。
- 已规划任何专用节点池的角色标签和污点。它们声明在节点池的资源上,具体说明请参阅基础设施节点。
控制平面端点模式
每个集群都通过稳定的端点访问其 API server。编写 10-cluster.yaml 前,请先选择模式,因为之后无法更改。此选择会记录在 VSphereCluster.spec.controlPlaneLoadBalancer 中;不填写该字段本身也是一种选择:字段缺省表示使用外部 LoadBalancer。
Self-built VIP 仅可在创建时选择
Self-built VIP 从 provider v1.0.17 起提供,需要 ACP v4.4 或更高版本,并且仅适用于创建时启用了该功能的集群。现有集群无法启用此功能;provider 从早期版本升级到 v1.0.17 的集群也无法启用:VIP 会在第一个控制平面节点引导时注入,随后由 Alive 接管,因此无法原地迁移。这些集群会继续使用外部 LoadBalancer;若要改用 Self-built VIP,必须创建新集群。请参阅升级 Provider。
集群创建后,这三种模式都无法更改。该字段只能写入一次;请参阅controlPlaneLoadBalancer 字段。
建议:对于外部 LoadBalancer,请勿填写该字段,而不是写入 type: external。字段缺省即表示使用外部 LoadBalancer;仅为记录此意图而填写该字段,会将端点固定为相同地址的冻结副本。
对于 type: internal,先决条件、字段参考、清单代码块和验证命令请参阅使用 Self-built VIP。
关键对象

顶层的 Cluster 引用 VSphereCluster 以及控制平面或 worker 控制器。这些控制器引用 VSphereMachineTemplate 对象。每个模板引用一个 VSphereMachineConfigPool,其插槽为运行时的 Machine、VSphereMachine 和 VSphereVM 对象提供主机名、静态网络配置和持久磁盘。
ClusterResourceSet
ClusterResourceSet 是 global 集群中的 Cluster API 资源。workload API server 变为可访问后,它会将所引用的 ConfigMap 和 Secret 资源应用到 workload cluster。
在此工作流中,使用 ClusterResourceSet 自动交付 vSphere CPI 资源。
vSphere CPI 组件
vSphere CPI 组件通过 ClusterResourceSet 交付到 workload cluster。它将 workload 节点连接到 vSphere 基础设施,使集群能够报告基础设施身份并完成云 provider 初始化。
Provider 管理的清单字段
您所应用的对象中,有些字段在创建后由 provider 管理。了解这些字段可以避免升级期间发生仓库冲突。
- 您应用
KubeadmControlPlane 后,provider 会对其进行 patch。预期 CoreDNS 和 kube-proxy 跳过 annotation 会被设置为 "true",并且 spec.kubeadmConfigSpec.clusterConfiguration.dns.imageRepository 会由控制器设置。
- 请自行设置
dns.imageTag,并保持 dns.imageRepository 未设置。 提供目标 ACP 版本发布的 CoreDNS 标签:对于 ACP v4.4 及更高版本,使用暂存于 acp/coredns 下的标签;对于低于 v4.4 的版本,使用 tkestack/coredns 下的标签。provider 会读取标签中的 -v<acp> 后缀来选择该仓库,因此不带该后缀的标签会解析到 tkestack/coredns;在 ACP v4.4 或更高版本中,该仓库不包含此标签,CoreDNS Pod 会停留在 ImagePullBackOff。
- kube-proxy 仓库根据 Kubernetes 版本派生:在
v1.35 及更高版本中为 acp/k8s,低于该版本时为 tkestack。在 chart v4.4 及更高版本中,Kube-OVN chart 为 acp/chart-kube-ovn,低于该版本时为 acp/chart-cpaas-kube-ovn。
- 三者的 Registry 前缀均来自
Cluster annotation cpaas.io/registry-address。
- workload cluster 的
kube-system/kube-proxy DaemonSet 和 kube-system/coredns Deployment 镜像仓库也会进行 reconcile,但仅在控制平面 rollout 完成后进行。KubeadmControlPlane 达到目标版本与这些工作负载更改仓库之间存在延迟是正常现象。
- 这些受管理组件的镜像拉取凭证来自 workload cluster 的
cpaas-system/sentry ServiceAccount。
WARNING
请勿删除 CoreDNS 和 kube-proxy 跳过 annotation,也不要重新应用省略这些 annotation 的已保存 KubeadmControlPlane 清单。在 provider 同时管理 CoreDNS 或 kube-proxy 的情况下,将其交还给 kubeadm 管理会产生仓库冲突,导致镜像来回切换。
machine config pool
machine config pool 是 VSphereMachineConfigPool 自定义资源。在基线工作流中:
- 一个 machine config pool 用于控制平面节点。
- 一个 machine config pool 用于 worker 节点。
每个节点插槽都包含主机名、数据中心、静态 IP 分配以及可选的数据磁盘定义。
对于网络配置,请区分以下字段:
networkName 是 vCenter 网络或端口组名称。
deviceName 是 guest 操作系统内的 NIC 名称。
如果设置了 deviceName,CAPV 会将该值写入生成的 guest 网络元数据。如果未设置,当前实现通常会按 NIC 顺序使用 eth0、eth1 和 eth2 等 NIC 名称。
还请区分以下值格式:
- 节点 IP 地址与前缀长度搭配使用,例如
10.10.10.11/24。
- 网关字段仅包含网关 IP 地址,例如
10.10.10.1。
spec.configs 至少必须声明一个插槽,并且每个插槽都必须声明 network,且其中的 network.primary.networkName 不得为空。
每个插槽可以声明两个磁盘列表。persistentDisks[] 会在 VM 替换后保留,并重新挂载到替换之前机器的那台机器上;ephemeralDisks[] 会随 VM 一起删除,并以空磁盘重新创建。标准集合将 /var/cpaas 声明为持久磁盘,将 /var/lib/kubelet、/var/lib/containerd 和 /var/lib/etcd 声明为临时磁盘。在一个插槽内,两个列表中的磁盘名称和挂载路径共享同一个命名空间。ephemeralDisks[] 在 provider v1.0.17 中新增,因此此拆分仅适用于在 v1.0.17 或更高版本上创建的集群。请参阅选择持久磁盘或临时磁盘。
一个池只能服务一个控制器。 机器分配插槽时,provider 会在 status.consumerRef 中记录所属的 KubeadmControlPlane 或 MachineDeployment。引用同一池的第二个控制器不会被接受。控制平面使用一个池,并为每个 MachineDeployment 使用一个池。
VM 模板要求
此工作流使用的 VM 模板应满足以下最低要求:
- 它使用目标平台环境所需的操作系统。
- 它包含
cloud-init。
- 它包含 VMware Tools 或
open-vm-tools。
- 它包含
containerd。
- 它包含 kubeadm 引导所需的基线组件。
- 它在
/root/images/ 下包含预先导出的容器镜像 tar 文件。这些文件会在 kubeadm 运行前由 capv-load-local-images.sh 导入 containerd,因此节点引导不依赖于从远程 Registry 拉取镜像。
- 这些 tar 文件包含 OS 模板随附的 kubeadm 控制平面镜像,包括 etcd 和 kube-apiserver;镜像位于
clusterConfiguration.imageRepository、Kubernetes 版本和组件镜像标签生成的准确镜像引用下。尽管 etcd 和 kube-apiserver 作为静态 Pod 运行,但它们仍需要容器镜像。缺少本地引用或引用不匹配时,运行时会改为尝试从配置的 Registry 拉取镜像。
/root/images/*.tar 文件必须包含 sandbox(pause)镜像,且其引用必须与 /etc/containerd/config.toml 中配置的 sandbox_image 值(containerd v1)或 sandbox 值(containerd v2)完全匹配。例如,如果 containerd 配置为 sandbox_image = "registry.example.com/tkestack/pause:3.10",则其中一个 tar 文件必须包含该准确的镜像引用。不匹配会导致 containerd 从网络拉取 sandbox 镜像,这会违背本地预加载的目的,并在 air-gapped 环境中失败。
静态 IP 配置、主机名注入和其他初始化设置取决于 cloud-init。节点 IP 报告取决于 guest tools。
本地文件布局
业务集群命名
业务 cluster_name 不得为 global。该名称保留给 global 集群,重复使用会导致业务集群的资源与 cpaas-system 中 global 集群的资源发生冲突。global- 前缀保留给 global 集群 DR 工作流所拥有的资源;请参阅通用先决条件。请勿将 global- 用于业务集群资源,因为故障转移操作可能会将这些资源选作属于 global 集群的资源。
按照惯例,CAPI Cluster 和 provider 集群资源(VSphereCluster)应始终命名为 <cluster_name>;非根 CAPI 和 provider 资源(KubeadmControlPlane、KubeadmConfigTemplate、MachineDeployment、VSphereMachineTemplate、VSphereMachineConfigPool 等)应使用 <cluster_name>- 作为前缀——例如,示例清单使用 <cluster_name>-kcp 和 <cluster_name>-md-0。这是建议,而非控制器强制执行的规则,但这样可以避免多个业务集群位于 cpaas-system 时发生同一命名空间内的冲突,并使操作期间的资源归属一目了然。
创建本地工作目录,并按以下布局存放清单:
capv-cluster/
├── 00-namespace.yaml
├── 10-cluster.yaml
├── 15-vsphere-cpi-clusterresourceset.yaml
├── 16-vspheremachineconfigpool-control-plane.yaml
├── 17-vspheremachineconfigpool-worker.yaml
├── 18-failure-domains.yaml # only when failure domains are enabled
├── 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 name
kubectl -n cpaas-system get cluster global \
-o jsonpath='{.metadata.annotations.cpaas\.io/registry-address}{"\n"}'
kubectl -n kube-public get configmap etcd-sync -o name 2>/dev/null || echo "not standby"
导出 Cluster 注解返回的永久平台 Registry 地址,以及 CPI 和 kubeadm 清单使用的镜像 Registry。如果两个占位符指向同一个 Registry,则两个变量使用相同的值。
export CLUSTER_REGISTRY_ADDRESS="<registry_address>"
export IMAGE_REGISTRY_ADDRESS="<image_registry>"
# Repository names depend on the target version. The values below are the
# ACP v4.4 and later ones; for an earlier target, change them to:
# Kube-OVN chart -> acp/chart-cpaas-kube-ovn
# CoreDNS -> tkestack/coredns
export KUBE_OVN_CHART_REPO="acp/chart-kube-ovn"
export COREDNS_REPO="acp/coredns"
curl -sk "https://${CLUSTER_REGISTRY_ADDRESS}/v2/${KUBE_OVN_CHART_REPO}/tags/list"
curl -sk "https://${IMAGE_REGISTRY_ADDRESS}/v2/${COREDNS_REPO}/tags/list"
curl -sk "https://${IMAGE_REGISTRY_ADDRESS}/v2/acp/cloud-provider-vsphere/tags/list"
如果响应为 NAME_UNKNOWN,表示仓库名称与目标版本不匹配;请根据上方映射更正 KUBE_OVN_CHART_REPO 或 COREDNS_REPO,然后重试,而不要将其视为缺少标签。
上面列出的仓库边界与 provider 在运行时应用的边界相同;有关选择规则,请参阅清单中由 provider 管理的字段;有关权威图表名称表,请参阅在控制平面之前升级 Kube-OVN。
/v2/ 片段仅是 Registry HTTP API URL 的一部分。在业务集群 Cluster 清单中,将 Registry 注解设置为 <registry_address>,不带 scheme 或路径。
确认以下结果:
global 集群可访问。
- Alauda Container Platform Cluster Bootstrap Provider 和 Alauda Container Platform Infrastructure Provider for Virtualization Platform 均在运行。
- 控制器参数包含
ClusterResourceSet=true。
public-registry-credential Secret 存在;此操作步骤不会打印其内容。
global 集群不是灾难恢复备用集群。kube-public/etcd-sync ConfigMap 不得存在。请参阅备用管理集群。
- 永久平台 Registry 包含所需的 Kube-OVN、CoreDNS 和 vSphere CPI 标签。
- VM 模板通过了可执行的本地镜像验证,其中包含准确的 etcd、kube-apiserver 和 sandbox 镜像引用。以静态 Pod 方式部署并不会免除镜像要求。
继续之前,还请验证以下事项:
- vCenter 服务器地址可访问。
- vCenter 用户名和密码有效。
- 指纹正确。
- 模板名称正确。
- 可在目标数据中心解析该模板。
- 如果 VM 克隆为
fullClone,模板系统磁盘不得大于清单稍后使用的 diskGiB 值。如果 CAPV 完成 linkedClone,系统磁盘大小将保持为模板的大小,并忽略 diskGiB。
- 模板中已安装 VMware Tools 或
open-vm-tools。
- 外部 LoadBalancer 符合规划控制平面端点中有关第 4 层侦听器、后端、运行状况检查、可达性和所有权的要求。
- 如果选择了
type: internal,则应满足自建 VIP 先决条件:VIP 尚未被占用;dvPortGroup 安全策略或 NSX-T 分布式防火墙规则未阻止 VRRP 和免费 ARP;模板能够加载 ip_vs;并且 alive 插件可部署到 global 集群。
创建命名空间和 vCenter 凭证 Secret
创建用于存储业务集群对象的命名空间。
此工作流将业务集群对象存储在 cpaas-system 命名空间中。在以下清单和命令中,将每个 <namespace> 占位符替换为 cpaas-system。
00-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: <namespace>
应用命名空间:
kubectl apply -f 00-namespace.yaml
创建 VSphereCluster.spec.identityRef 引用的 vCenter 凭证 Secret。
请使用编辑器编写凭证文件,而不要在 shell 提示符中输入这些值。shell 会将整个命令(包括密码)记录在历史记录中;使用编辑器写入文件则不会留下此类副本。请将文件保存在 capv-cluster/ 之外,以免被误认为清单。
vsphere-credentials.env
username=<vsphere_username>
password=<vsphere_password>
每行的格式均为 key=value。请勿为值加引号——kubectl 会将引号字符按原样处理。值中可以包含 =;每行中只有第一个 = 用于分隔键和值。
限制文件权限,从文件创建 Secret,然后删除文件:
chmod 600 ../vsphere-credentials.env
VSPHERE_SECRET_NAME="<credentials_secret_name>"
VSPHERE_NAMESPACE="<namespace>"
kubectl create secret generic "${VSPHERE_SECRET_NAME}" \
--namespace "${VSPHERE_NAMESPACE}" \
--from-env-file=../vsphere-credentials.env
rm -f ../vsphere-credentials.env
请勿使用 kubectl apply 创建凭证 Secret
客户端侧 apply 会将其应用的清单(包括 vCenter 密码)存储在 Secret 自身的 kubectl.kubernetes.io/last-applied-configuration 注解中。kubectl create 不会写入此类注解。无论值是以 stringData 还是 base64 data 形式写入,情况都一样;base64 是编码,不是保护。本文后续的 CPI 凭证也适用相同规则。
创建 Cluster 和 VSphereCluster 对象
创建基础集群清单,其中包含业务集群网络设置、控制平面端点和 vCenter 连接设置。将 cpaas.io/registry-address 设置为永久平台 Registry,格式为 <registry_address>(仅限 <host>:<port>)。
以下清单使用外部 LoadBalancer,这是本页的基准配置。对于自建 VIP,请将 在 10-cluster.yaml 中启用自建 VIP 中的 spec.controlPlaneLoadBalancer 块添加到 VSphereCluster 对象中。
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"
# Optional. Bind this cluster to a dedicated image registry instead of the
# registry of the `global` cluster. Read only at cluster creation.
# cpaas.io/registry-reference: <registry_credential_name>
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>
cpaas.io/registry-address: <registry_address>
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,以便业务集群 API 服务器变为可访问后,业务集群自动接收 vSphere CPI 配置和清单。
INFO
在基准工作流中,VSphereCluster.spec.failureDomainSelector 有意不设置,且 CPI vsphere.conf 不包含 [Labels] 块。只有启用故障域后才需要同时配置这两项,具体说明请参阅多个数据中心和故障域。在 vsphere.conf 中添加 [Labels],但没有与之匹配的 VSphereFailureDomain 对象,会导致 CPI 查找不存在的 zone 和 region 标签。
INFO
vSphere CPI 的 TLS 绕过选项为 insecure-flag。请在 <cluster_name>-vsphere-cpi-config 的 [Global] 部分中保留 insecure-flag = "1",以便在 vCenter 证书为自签名证书或业务集群节点不信任该证书时,CPI 仍能连接。对于未设置自身 insecure-flag 的 vCenter 条目,CPI 会应用全局值。
WARNING
CPI 的 ConfigMap、Secret 和 ClusterResourceSet 资源必须与 Cluster 资源位于同一命名空间中。本指南使用的命名空间为 cpaas-system。ClusterResourceSet 只能匹配自身命名空间中的集群;将其部署到其他命名空间会导致资源交付被静默阻止。
INFO
Cluster 注解中的 kube-ovn 配置由平台控制器使用。本文档不会直接安装网络插件。
TIP
此清单较长,且在 data 字段中嵌套了 YAML。应用前请先验证清单:kubectl apply --dry-run=client -f 15-vsphere-cpi-clusterresourceset.yaml。
此清单声明两个 ConfigMap 和 ClusterResourceSet。它不会声明 ClusterResourceSet 也会引用的 CPI 凭证 Secret——该 Secret 包含 vCenter 密码,将在下一步单独创建。
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 = "1"
datacenters = "<cpi_datacenters>"
[VirtualCenter "<vsphere_server>"]
---
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>/acp/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
创建 CPI 凭证 Secret。它封装了 vSphere CPI 在业务集群上读取的 vsphere-cloud-secret,因此其负载中包含 vCenter 密码。出于本页前面所述的原因,请使用编辑器编写负载,并将其保存在 capv-cluster/ 之外,以免被误认为清单:
vsphere-cpi-secret-payload.txt
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>
限制文件权限,从文件创建 Secret,然后删除文件:
chmod 600 ../vsphere-cpi-secret-payload.txt
CLUSTER_NAME="<cluster_name>"
kubectl create secret generic "${CLUSTER_NAME}-vsphere-cpi-secret" \
--namespace "${VSPHERE_NAMESPACE}" \
--type=addons.cluster.x-k8s.io/resource-set \
--from-file=data=../vsphere-cpi-secret-payload.txt
rm -f ../vsphere-cpi-secret-payload.txt
此处使用的 <cluster_name> 应与本页其他位置一致。创建 vCenter 凭证时已设置 VSPHERE_NAMESPACE;如果在新的 shell 中操作,请重新设置。Secret 的唯一键必须为 data,该键由 --from-file=data= 生成。
然后应用清单:
kubectl apply -f 15-vsphere-cpi-clusterresourceset.yaml
创建 machine config pool
创建控制平面 machine config pool。
INFO
每个节点槽位都在 network.primary(必需)和 network.additional(可选列表)下声明其 NIC 布局。主 NIC 的 networkName 为必需项,provider 会根据 hostname 和解析出的主 NIC 地址,推导出 Kubernetes 节点名称、kubelet 服务证书 DNS SAN 以及 kubelet node-ip。hostname 必须是有效的 DNS-1123 子域名。
INFO
deviceName 为可选项。如果不需要强制指定 guest NIC 名称,请从每个节点槽位中删除 deviceName 行。provider 会根据 NIC 顺序分配 eth0、eth1 等 NIC 名称。
WARNING
VSphereMachineConfigPool 中的 dns 条目会保留在静态网络配置中,但在受影响的 VMware 部署中,它们可能无法可靠地更新 guest 操作系统的 /etc/resolv.conf。因此,以下控制平面和 worker 引导清单也会通过 kubeadm files 显式写入 /etc/resolv.conf。
槽位验证规则
provider 会验证以下内容:
spec.configs 至少声明一个槽位,且每个槽位都声明了 network,其中 network.primary.networkName 不为空。
- 每个磁盘声明的
sizeGiB 不小于 1。
- 如果设置了持久磁盘的
unitNumber,其值必须介于 0 和 15 之间,且绝不能为 7,因为该值保留给 SCSI 控制器。
- 在一个槽位内,
persistentDisks 和 ephemeralDisks 中的磁盘名称和挂载路径必须唯一。
- 在池内以及同一命名空间中绑定到同一
Cluster 的所有其他池之间,主机名和主 IP 地址都必须唯一。
已分配槽位限制
槽位的 status.configStatuses[].state 一旦变为 InUse 或 Released,就不能移除该槽位、更改其 network.primary.ip 或 .ipv6、从 persistentDisks[] 中移除磁盘,也不能更改持久磁盘的 sizeGiB 或 mountPath。
请勿向状态为 InUse 或 Released 的槽位添加磁盘:provider 无法将磁盘挂接到正在运行的节点,并会报告该节点的错误;而添加的持久磁盘在节点被替换之前无法再次移除。请在第一个 Machine 接管槽位之前规划该槽位的所有磁盘。
请在第一个 Machine 分配槽位之前规划主机名和地址。有关扩容和运行时更改,请参阅管理 VMware vSphere 上的节点。
16-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
ephemeralDisks:
- name: var-lib-kubelet
sizeGiB: <cp_var_lib_kubelet_size_gib>
mountPath: /var/lib/kubelet
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
- 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
ephemeralDisks:
- name: var-lib-kubelet
sizeGiB: <cp_var_lib_kubelet_size_gib>
mountPath: /var/lib/kubelet
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
- 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
ephemeralDisks:
- name: var-lib-kubelet
sizeGiB: <cp_var_lib_kubelet_size_gib>
mountPath: /var/lib/kubelet
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
创建 worker machine config pool。
17-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
ephemeralDisks:
- name: var-lib-kubelet
sizeGiB: <worker_var_lib_kubelet_size_gib>
mountPath: /var/lib/kubelet
fsFormat: ext4
- name: var-lib-containerd
sizeGiB: <worker_var_lib_containerd_size_gib>
mountPath: /var/lib/containerd
fsFormat: ext4
应用两个清单:
kubectl apply -f 16-vspheremachineconfigpool-control-plane.yaml
kubectl apply -f 17-vspheremachineconfigpool-worker.yaml
创建 Machines 之前,请验证池绑定和槽位状态:
kubectl -n <namespace> get vspheremachineconfigpool
kubectl -n <namespace> get vspheremachineconfigpool <cluster_name>-cp-pool \
-o jsonpath='{range .status.conditions[*]}{.type}={.status} ({.reason}){"\n"}{end}'
kubectl -n <namespace> get vspheremachineconfigpool <cluster_name>-cp-pool -o yaml
kubectl -n <namespace> get vspheremachineconfigpool <cluster_name>-worker-pool -o yaml
列表输出包含 TOTAL、AVAILABLE 和 ALLOCATED 列,分别取自 status.total、status.available 和 status.allocated。创建 Machines 之前,预期值为 ClusterRefReady=True、VCenterAvailable=True、MembersValid=True、MembersUnique=True 和 Ready=True。
SlotAvailable 有意不包含在 Ready 摘要中。即使池中的所有槽位都已分配,该池仍会报告 Ready=True 和 SlotAvailable=False;在 maxSurge: 0 部署过程中,这种组合是正常的,并不矛盾。
该池也是持久磁盘替换状态的权威来源。VM 被替换时,其槽位会从 InUse 变为 Released;引用同一现有池的替换 Machine 可以立即重用该槽位及其 VMDK。releaseDelayHours 控制槽位保持未使用时的回收行为;它不是重用延迟。
请勿将创建新的池名称作为 Machine 替换时的常规重试方式。不同的 VSphereMachineConfigPool.metadata.name 会创建相互独立的槽位和 VMDK,因此反复拆除和重新部署可能会暂时占用多套完整磁盘。在容量受限的数据存储上再次部署之前,请等待先前的池及其 VMDK 完成由 provider 管理的删除。有关拆除检查,请参阅持久磁盘生命周期。实时状态信号为 status.persistentDiskStatuses[].phase,其值包括 Creating、Attached、Available、Reclaiming、Reclaimed 和 Error。status.configStatuses[].reclaimStatus 子树以及 spec.configs[].persistentDisks[].volumePath 和 .diskUUID 字段已弃用、被冻结,并计划移除。
应用可选的故障域对象
基准单数据中心拓扑可跳过此步骤。
如果启用了故障域,请先完成多个数据中心和故障域,然后在创建控制平面之前应用故障域对象:
kubectl apply -f 18-failure-domains.yaml
kubectl get vspherefailuredomain,vspheredeploymentzone
确认集群引用的每个 VSphereFailureDomain 和 VSphereDeploymentZone 都存在。部署区域可用之前,请勿继续。以下内容应作为一组配置进行管理:
18-failure-domains.yaml 中的 VSphereFailureDomain 和 VSphereDeploymentZone 对象
10-cluster.yaml 中的 VSphereCluster.spec.failureDomainSelector
- CPI
15-vsphere-cpi-clusterresourceset.yaml 中的 [Labels] 块
未启用故障域时,请勿将 failureDomainSelector 或 CPI [Labels] 块添加到基准清单中。
创建控制平面对象
创建 VSphereMachineTemplate 和 KubeadmControlPlane 对象。将以下完整模板中的占位符替换为检查清单文档中收集的值。
Kubernetes 1.35 kubelet 设置
基准控制平面和 worker 清单省略了 imagePullCredentialsVerificationPolicy,适用于 Kubernetes 1.34 或更早版本。
对于 Kubernetes 1.35 或更高版本,在应用任一清单之前,请将以下字段添加到 20-control-plane.yaml 和 30-workers-md-0.yaml 中的 KubeletConfiguration JSON:
"imagePullCredentialsVerificationPolicy": "NeverVerify",
cloneMode 和 diskGiB 在模板中都会保留,因为 CAPV 同时接受这两个字段。实际上,只有当实际克隆操作为 fullClone 时,diskGiB 才会影响系统磁盘。如果 cloneMode 为 linkedClone 且模板具有可用快照,CAPV 将完成链接克隆,系统磁盘大小保持与源模板相同。如果不存在可用快照,CAPV 将回退到 fullClone,并再次应用 diskGiB。
系统磁盘与持久化磁盘是分开的
VSphereMachineTemplate.spec.template.spec.diskGiB 仅设置 VM 系统磁盘的大小,而不是节点上所有磁盘的总容量。
数据磁盘需要分别在 VSphereMachineConfigPool.spec.configs[].persistentDisks[] 和 .ephemeralDisks[] 下声明。不要将数据磁盘大小加到 diskGiB 中;否则,VM 可能会获得更大的系统磁盘以及单独的数据磁盘,导致实际容量翻倍。
对于 fullClone,diskGiB 必须大于或等于 OS 镜像模板中的系统磁盘大小。对于 linkedClone,系统磁盘保持模板大小,并忽略 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/resolv.conf
owner: "root:root"
append: false
permissions: "0644"
content: |
nameserver <nic1_dns_1>
- 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
flex-volume-plugin-dir: "/opt/libexec/kubernetes/kubelet-plugins/volume/exec/"
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
volume-plugin-dir: "/opt/libexec/kubernetes/kubelet-plugins/volume/exec/"
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
rolloutStrategy.rollingUpdate.maxSurge: 0 以及 3 或更高的 replicas 值并非风格选择。基础设施模板设置了 machineConfigPoolRef 的 KubeadmControlPlane 需要同时配置这两项。请参阅Provider Requirements。
应用清单:
kubectl apply -f 20-control-plane.yaml
创建 worker 对象
创建 worker machine 模板、bootstrap 模板和 MachineDeployment。
基线 worker kubelet patch 会省略仅适用于 Kubernetes 1.35 的字段。如果所选版本为 Kubernetes 1.35 或更高版本,请按照控制平面步骤中的说明,在此处以及 20-control-plane.yaml 中添加 imagePullCredentialsVerificationPolicy: NeverVerify。
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/resolv.conf
owner: "root:root"
append: false
permissions: "0644"
content: |
nameserver <nic1_dns_1>
- 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
对于设置了 machineConfigPoolRef 的 MachineDeployment,同样需要 strategy.rollingUpdate.maxSurge: 0。在默认 1 的 maxSurge 下,滚动更新需要一个额外槽位,而完整的池没有该槽位,因此会停滞。示例使用 maxUnavailable: 1。
应用清单:
kubectl apply -f 30-workers-md-0.yaml
在基线工作流中,请注意以下 worker 特定规则:
- 主 worker 清单默认不会设置
failureDomain,因为基线工作流假定使用单个数据中心。如果需要让 worker MachineDeployment 运行在特定的 VSphereDeploymentZone 中,请按照多个数据中心和故障域中的说明添加 failureDomain。
- 某些环境会向
KubeadmConfigTemplate 添加额外的运行时镜像替换命令或服务重启命令。这些命令有意未包含在基线示例中。只有当环境中的平台要求明确需要这些命令时,才添加它们。
等待集群就绪
应用所有清单后,集群创建过程将异步执行。使用以下命令监控进度:
kubectl -n <namespace> get cluster,kubeadmcontrolplane,machinedeployment,machine -w
等待 KubeadmControlPlane 报告预期数量的就绪副本,并且所有 Machine 对象都进入 Running 阶段,然后再继续验证。
验证
使用以下命令验证集群创建工作流。
- 检查
global 集群中的 CPI 交付资源:
kubectl -n <namespace> get clusterresourceset
kubectl -n <namespace> get clusterresourcesetbinding
- 导出业务集群 kubeconfig:
kubectl -n <namespace> get secret <cluster_name>-kubeconfig -o jsonpath='{.data.value}' | base64 -d > /tmp/<cluster_name>.kubeconfig
- 检查 vSphere CPI daemonset 是否已在业务集群中创建:
kubectl --kubeconfig=/tmp/<cluster_name>.kubeconfig -n kube-system get daemonset
- 检查
global 集群对象:
kubectl -n <namespace> get cluster,vspherecluster,kubeadmcontrolplane,machinedeployment,machine,vspheremachine,vspherevm
- 检查业务集群节点:
kubectl --kubeconfig=/tmp/<cluster_name>.kubeconfig get nodes -o wide
- 检查两个列表的池槽位计数器和每个磁盘的状态:
kubectl -n <namespace> get vspheremachineconfigpool
kubectl -n <namespace> get vspheremachineconfigpool <cluster_name>-cp-pool \
-o jsonpath='{range .status.persistentDiskStatuses[*]}{.hostname}{" "}{.name}{" "}{.phase}{" unit="}{.unitNumber}{"\n"}{end}'
kubectl -n <namespace> get vspheremachineconfigpool <cluster_name>-cp-pool \
-o jsonpath='{range .status.ephemeralDiskStatuses[*]}{.hostname}{" "}{.name}{" unit="}{.unitNumber}{"\n"}{end}'
确认以下结果:
vsphere-cloud-controller-manager 出现在业务集群中。
- 控制平面和 worker 节点均已创建。
- 节点最终变为
Ready。
- 每个池均报告
Ready=True,且其 ALLOCATED 计数等于从该池分配的运行中 Machine 数量。
- 已分配槽位上的每个持久磁盘均报告
phase: Attached。
- 已分配槽位上的每个临时磁盘都显示在
status.ephemeralDiskStatuses[] 中,并分配有单元号。临时磁盘没有阶段;它们会随 VM 一起创建。
故障排除
当工作流失败时,首先使用以下命令:
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 注解,请验证其是否设置为不带 scheme 或 /v2/ 的永久 <registry_address>。然后验证为其提供支持的凭证:当集群继承 global 集群的 registry 时,验证 public-registry-credential;当集群使用专用 registry 时,验证由 cpaas.io/registry-reference 指定名称的 Secret。
- 如果 Machine 卡在
Provisioning,请检查 VSphereMachine 中针对 MachineConfigPoolReady 的条件——它会显示槽位分配是否因池绑定或数据中心不匹配而失败。然后读取池自身的条件:MembersValid、MembersUnique、PersistentDisksReady、ClusterRefReady 和 VCenterAvailable。
- 如果 Machine 始终无法完成 bootstrap,请检查
VSphereVM 和 VSphereMachine 上的 BootstrapReady 条件。原因 BootstrapSecretGetFailed 表示 bootstrap Secret 缺失或无法读取;BootstrapSecretContentInvalid 表示该 Secret 存在但无法使用。在检查 vCenter 之前,请检查该 Machine 的 KubeadmConfig 及其 bootstrap 数据 Secret。
- 如果节点在 vCenter 中被关闭电源后一直保持关闭,这是预期行为。
VSphereVM 会在首次成功开机后锁存 InitialPowerOnCompleted=True,并且永远不会清除;从此以后,已关闭电源的 VM 会被视为有意执行的带外操作,不会再次自动开机。PoweredOn=False 会传播到 VSphereVM.Ready,然后传播到 VSphereMachine.Ready,因此报告未就绪是正确的。在 vCenter 中启动该 VM 即可恢复;无需执行 rollout。rollout 创建的替代 VM 未设置该锁存值,因此其首次启动不受影响。
- 如果
VSphereMachine 在没有明显原因的情况下清除 Ready,请注意:当后端 VSphereVM、其 providerID 或其网络信息不可用时,Ready 会被清除,并且现在可以观测到带外电源变更。在将其视为控制器故障之前,请读取 VSphereVM 条件。
- 如果完全没有任何 reconcile 且条件也没有变化,请检查管理集群是否为灾难恢复备用集群。请参阅备用管理集群。
- 如果 VM 正在等待 IP 分配,请验证 VMware Tools、静态 IP 设置和
VSphereVM.status.addresses。
- 如果业务集群的
Node 对象仍然没有 spec.providerID,请先验证 CPI 交付资源,然后检查是否存在重复的 vCenter guest hostname。当同一数据中心中的旧 VM 仍报告与新节点相同的 guest hostname 时,cloud-provider-vsphere 可能回退到节点名称查找,缓存旧 VM,并因 VM IP 与 kubelet 节点 IP 不匹配而拒绝新节点。请检查 leader vsphere-cloud-controller-manager 日志、节点 SystemUUID、实际 VM UUID,以及 vCenter guest hostname/IP 值。修复或移除重复 hostname 或旧 VM 冲突后,重启业务集群的 vsphere-cloud-controller-manager Pod,以清除错误的内存缓存:
kubectl --kubeconfig=/tmp/<cluster_name>.kubeconfig -n kube-system delete pod \
-l k8s-app=vsphere-cloud-controller-manager
- 如果 datastore 空间耗尽,请先列出所有当前存在和正在终止的
VSphereMachineConfigPool 对象,并将其名称与每个 VSphereMachineTemplate.spec.template.spec.machineConfigPoolRef.name 进行比较。不同的池名称拥有相互独立的槽位和 VMDK;因此,重复部署可能会累积多组磁盘。然后检查 status.configStatuses[] 中的 state 和 lastReleasedTime,以及 status.persistentDiskStatuses[] 中的 phase、lastError、retryAfter 和 taskRef,同时检查相关 Events 和 vCenter 附加状态。insufficient disk available 可确认 datastore 容量故障,但其本身不能证明回收失败。在复用或 provider-managed 回收等待期间,不要手动删除 VMDK 或移除池 finalizer。
- 不要从
status.persistentDiskStatuses[] 中删除 phase: Reclaimed 条目。这是一个有意保留的 tombstone:后端 VMDK 已经不存在,而该记录会阻止控制器根据冻结的 spec volumePath 重新为磁盘播种。删除它会产生播种、删除、重新播种的循环,使池 finalizer 永远无法清除。
- 如果模板系统磁盘大小与清单值不匹配,请先检查实际的克隆模式。当 VM 创建为
linkedClone 时,系统磁盘会保持模板的大小,并忽略 diskGiB。只有 fullClone 使用 diskGiB;在这种情况下,diskGiB 不得小于模板磁盘大小。
- 如果控制平面端点未启动,请验证 TCP
6443 透传、所有控制平面后端、HTTPS /healthz 结果、DNS 和证书 SAN,以及从 global 集群和控制平面节点的可达性。
- 如果与 vCenter 的 TLS 连接失败,请验证 thumbprint、vCenter 地址,以及代理设置是否干扰连接。
查看控制器日志时,请遵循以下规则:
deploy/capi-controller-manager 在 global 集群的 cpaas-system namespace 中运行。
- 不要使用业务集群 kubeconfig 检查
capi-controller-manager 日志。
- 如果平台控制器处理集群网络注解,也请检查平台 network-controller 日志和平台 cluster-lifecycle-controller 日志。
Admission 拒绝
apply 或 edit 可能会在创建任何对象之前被拒绝。将拒绝消息中的片段与下表进行匹配。
创建时拓扑变体
基线清单有意从一个数据中心和一个 NIC 开始。当初始拓扑需要 Self-built 控制平面 VIP、额外 NIC、多个数据中心、故障域或额外数据磁盘时,请在创建集群前使用以下变体。每次应用一个变体,并在组合多个变体前验证完整的清单集合。
使用 Self-built VIP
当集群使用 VSphereCluster.spec.controlPlaneLoadBalancer.type: internal 而不是外部 LoadBalancer 创建时,请使用此变体。先在控制平面端点模式中确定模式;集群创建后无法更改。
Self-built VIP 前提条件
在选择 type: internal 前完成以下所有操作。
- 平台版本为 ACP
v4.4 或更高版本。无论 provider 版本如何,较早的平台版本都不支持 Self-built VIP。
- VIP 必须为 IPv4、当前未被占用,并且与控制平面节点 IP 位于同一 Layer-2 广播域中。不接受 IPv6 和双栈端点。
- VIP 不能是此集群
VSphereMachineConfigPool 对象中声明的槽位地址(network.primary.ip 或 network.additional[].ip),也不能位于任何外部 IPAM 池范围内。provider 会检查槽位地址,但无法读取外部 IPAM 池,因此不会检测到该冲突。
vrid 在该 Layer-2 域中必须唯一。
- 网络路径必须允许控制平面节点之间的 VRRP(IP 协议
112、多播 224.0.0.18)和免费 ARP。确认 dvPortGroup 安全策略以及任何 NSX-T 分布式防火墙规则未阻止这些流量。
- VM 模板必须公开 Linux IPVS 子系统(
ip_vs 模块系列可加载),并允许 Alive 设置 net.ipv4.conf.all.arp_accept=1 和 net.ipv4.vs.conntrack=1。
alive 集群插件必须存在,并且可部署到 global 集群。
provider 本身不携带 Alive。它会在 global 集群上创建一个 ModuleInfo,平台再据此渲染业务 cpaas-system/alive AppRelease。请在 global 集群上确认该插件:
kubectl get clustermodule global -o jsonpath='{.spec.version}{"\n"}'
kubectl get moduleplugin alive -o jsonpath='{.status.targetClusterVersions}{"\n"}'
kubectl get moduleplugin alive -o jsonpath='{.status.latestVersion}{"\n"}'
kubectl get moduleconfig alive-<alive_version> -o jsonpath='{.status.readyForDeploy}{"\n"}'
provider 按以下顺序解析 <alive_version>:如果已设置,则先使用 provider deployment 上的 --plugin-alive-version 覆盖值,然后使用 ModulePlugin/alive 条目 status.targetClusterVersions[<ClusterModule spec.version>].version,最后才使用 status.latestVersion。先读取与第一条命令打印的版本匹配的 targetClusterVersions 条目;仅当 map 中没有该版本的条目时,才使用 latestVersion。
业务集群自身的 ClusterModule 在集群创建前不存在,因此在规划时读取 global 集群的版本作为规划值。这只是最接近的可用代理值,并非保证:集群创建后,使用 kubectl get clustermodule <cluster_name> 读取其自身版本,并针对该版本解析到的 alive 版本重复执行 readyForDeploy 检查。请在控制平面完成引导前执行此操作,因为之后无法更改 type: internal。创建 global 集群本身时,请改为从安装包确认组件版本和 alive 插件是否存在。
最后一条命令必须针对解析选定的版本打印 true。如果缺少 ModulePlugin/alive,或者缺少匹配的 ModuleConfig,或其报告 false,则无法在此环境中安装 Self-built VIP。请改用外部 LoadBalancer 创建集群,并保持 spec.controlPlaneLoadBalancer 不存在。请联系 Alauda 技术支持获取与平台版本匹配的插件包。
controlPlaneLoadBalancer 字段
controlPlaneLoadBalancer 只能写入一次
provider 仅在创建集群时接受 spec.controlPlaneLoadBalancer。之后,type、host、port、vrid 和 interface 都会被冻结,该字段无法清除,创建时未设置该字段的集群也无法再添加。修正错误需要重建集群。
这会产生两个后果。在多 NIC 节点上,应在创建时显式设置 interface——错误的自动检测接口之后无法通过修补进行更改。此外,host 和 port 必须描述与 spec.controlPlaneEndpoint 相同的地址;不接受不匹配的配置。
vrid 范围由 provider 的 admission webhook 强制执行,而不是由 CRD schema 强制执行。客户端 dry run 不会联系任何服务器,因此既不会捕获此规则,也不会捕获 schema 规则;请使用 kubectl apply --dry-run=server 在不创建对象的情况下查看拒绝信息。
在 10-cluster.yaml 中启用 Self-built VIP
对于 type: internal,将以下代码块添加到创建 Cluster 和 VSphereCluster 对象中的 VSphereCluster 对象。基线清单中的其他内容保持不变。
spec:
controlPlaneLoadBalancer:
type: internal
host: "<vip>" # must equal spec.controlPlaneEndpoint.host
port: <api_server_port> # must equal spec.controlPlaneEndpoint.port
vrid: <vrid>
interface: "<vip_interface>" # set explicitly on multi-NIC nodes
provider 还会为你协调业务集群的 kube-system/kube-proxy 配置,设置 ipvs.strictARP: true 并将 <vip>/32 添加到 ipvs.excludeCIDRs,然后滚动更新 kube-proxy DaemonSet。不要手动还原这些值;provider 会重新应用这些值,并再次滚动更新 DaemonSet。
验证 Self-built VIP
kubectl -n <namespace> get vspherecluster <cluster_name> \
-o jsonpath='{range .status.conditions[*]}{.type}={.status} ({.reason}){"\n"}{end}'
kubectl --kubeconfig=/tmp/<cluster_name>.kubeconfig -n kube-system get pods -l app=alive -o wide
kubectl --kubeconfig=/tmp/<cluster_name>.kubeconfig -n cpaas-system get apprelease alive
kubectl --kubeconfig=/tmp/<cluster_name>.kubeconfig get --raw='/version'
预期会看到 SelfBuiltLoadBalancerReady=True、每个控制平面节点上一个 Alive pod,以及通过 VIP 成功返回 API 响应,且无需禁用 TLS 验证。
Alive pod 运行在 kube-system 中,而不是 cpaas-system 中;cpaas-system 保存平台根据 provider 的 ModuleInfo 渲染的 alive AppRelease。SelfBuiltLoadBalancerReady 覆盖整个链路、AppRelease 和 pod,因此当其处于 False 时,请检查两个 namespace。
INFO
SelfBuiltLoadBalancerReady 仅报告 VIP 链路,不会阻止 VSphereCluster.status.ready,因此基础设施集群可能报告已就绪,而 VIP 仍在协调中。条件原因包括 SelfBuiltLoadBalancerReady、SelfBuiltLoadBalancerReconciling、SelfBuiltLoadBalancerNotReady 和 InvalidSelfBuiltLoadBalancerConfiguration。
在 VIP 尚未就绪时,provider 还会延迟provider 管理的清单字段中所述的业务集群 CoreDNS 和 kube-proxy 仓库协调。
添加第二个 NIC
当节点需要额外的管理、存储或服务网络时,请扩展以下资源中的清单:
16-vspheremachineconfigpool-control-plane.yaml
17-vspheremachineconfigpool-worker.yaml
20-control-plane.yaml
30-workers-md-0.yaml
- 启用故障域时使用
18-failure-domains.yaml
每个节点槽位都在 network.primary 和 network.additional 下声明其 NIC 布局。主 NIC 用于派生 kubelet node-ip,并仍作为节点的主要身份;额外 NIC 按列出的顺序合并到主 NIC 之后。
将第二个 NIC 添加到 machine config pool 中的每个控制平面节点槽位:
network:
primary:
networkName: "<nic1_network_name>"
deviceName: "<nic1_device_name>"
ip: "<master_01_nic1_ip>/<nic1_prefix>"
gateway: "<nic1_gateway>"
dns:
- "<nic1_dns_1>"
additional:
- networkName: "<nic2_network_name>"
deviceName: "<nic2_device_name>"
ip: "<master_01_nic2_ip>/<nic2_prefix>"
gateway: "<nic2_gateway>"
dns:
- "<nic2_dns_1>"
对 worker 节点槽位应用相同模式:
network:
primary:
networkName: "<nic1_network_name>"
deviceName: "<nic1_device_name>"
ip: "<worker_01_nic1_ip>/<nic1_prefix>"
gateway: "<nic1_gateway>"
dns:
- "<nic1_dns_1>"
additional:
- networkName: "<nic2_network_name>"
deviceName: "<nic2_device_name>"
ip: "<worker_01_nic2_ip>/<nic2_prefix>"
gateway: "<nic2_gateway>"
dns:
- "<nic2_dns_1>"
将第二个 NIC 添加到 machine template:
network:
devices:
- networkName: "<nic1_network_name>"
- networkName: "<nic2_network_name>"
如果添加第二个 NIC 后节点使用的 DNS 服务器发生变化,请更新 20-control-plane.yaml 和 30-workers-md-0.yaml 中的 /etc/resolv.conf 文件条目。machine config pool 网络块中的 dns 值不会替代显式的 /etc/resolv.conf 引导文件条目。
如果启用了故障域,请更新 VSphereFailureDomain.spec.topology.networks 中的网络列表:
topology:
networks:
- <nic1_network_name>
- <nic2_network_name>
定义第二个 NIC 的值时,请在基础设施检查清单和清单中准备以下占位符:
<master_01_nic2_ip>
<master_02_nic2_ip>
<master_03_nic2_ip>
<worker_01_nic2_ip>
- 同时扩展 worker 池时使用
<worker_02_nic2_ip>
对于运行中的集群,在替换节点时同时更改全部三个网络定义。请参阅运行时拓扑更改。
多个数据中心和故障域
当需要跨不同 vCenter 数据中心或计算集群放置节点时,请使用多个数据中心和故障域。
以下原则适用:
- 一个集群可以定义多个
VSphereFailureDomain 对象。
- 每个
VSphereDeploymentZone 引用一个 VSphereFailureDomain。
- 控制平面使用
VSphereCluster.spec.failureDomainSelector。
- 当 worker 必须面向特定部署区域时,worker
MachineDeployment 使用 spec.template.spec.failureDomain。
为第一个数据中心准备以下占位符:
<compute_cluster_1>
<default_datastore_1>
<resource_pool_path_1>
<fd_name_1>
<dz_name_1>
为第二个数据中心准备以下占位符:
<dc_name_2>
<fd_name_2>
<dz_name_2>
<compute_cluster_2>
<default_datastore_2>
<resource_pool_path_2>
如果添加第三个数据中心,请继续使用相同的占位符模式:
<dc_name_3>
<fd_name_3>
<dz_name_3>
<compute_cluster_3>
<default_datastore_3>
<resource_pool_path_3>
在 18-failure-domains.yaml 中创建故障域对象。启用故障域时,第一个数据中心还需要 VSphereFailureDomain 和 VSphereDeploymentZone:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: VSphereFailureDomain
metadata:
name: <fd_name_1>
spec:
region:
name: region-a
type: Datacenter
tagCategory: k8s-region
autoConfigure: true
zone:
name: zone-1
type: ComputeCluster
tagCategory: k8s-zone
autoConfigure: true
topology:
datacenter: <default_datacenter>
computeCluster: <compute_cluster_1>
datastore: <default_datastore_1>
networks:
- <nic1_network_name>
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: VSphereDeploymentZone
metadata:
name: <dz_name_1>
spec:
server: <vsphere_server>
failureDomain: <fd_name_1>
controlPlane: true
placementConstraint:
resourcePool: <resource_pool_path_1>
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: VSphereFailureDomain
metadata:
name: <fd_name_2>
spec:
region:
name: region-a
type: Datacenter
tagCategory: k8s-region
autoConfigure: true
zone:
name: zone-2
type: ComputeCluster
tagCategory: k8s-zone
autoConfigure: true
topology:
datacenter: <dc_name_2>
computeCluster: <compute_cluster_2>
datastore: <default_datastore_2>
networks:
- <nic1_network_name>
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: VSphereDeploymentZone
metadata:
name: <dz_name_2>
spec:
server: <vsphere_server>
failureDomain: <fd_name_2>
controlPlane: true
placementConstraint:
resourcePool: <resource_pool_path_2>
将 failureDomainSelector 添加到 10-cluster.yaml 中的 VSphereCluster spec,以启用跨可用故障域的控制平面选择:
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>"
failureDomainSelector: {}
空选择器 {} 会匹配所有具有 controlPlane: true 的 VSphereDeploymentZone。使用匹配标签将控制平面限制到部分区域。
同时将 [Labels] 块添加到 CPI ConfigMap,以便 vSphere CPI 在业务节点上发布匹配的区域和地域标签。键必须与 VSphereFailureDomain.spec.zone.tagCategory 和 VSphereFailureDomain.spec.region.tagCategory 中使用的 tagCategory 值匹配。更新 15-vsphere-cpi-clusterresourceset.yaml 中的 vsphere.conf 数据:
vsphere.conf: |
[Global]
secret-name = "vsphere-cloud-secret"
secret-namespace = "kube-system"
service-account = "cloud-controller-manager"
port = "443"
insecure-flag = "1"
datacenters = "<cpi_datacenters>"
[Labels]
zone = "k8s-zone"
region = "k8s-region"
[VirtualCenter "<vsphere_server>"]
必须同时启用 failureDomainSelector 和 CPI 的 [Labels] 块。只添加其中一个会使集群处于不一致状态:节点会获得无法解析的区域或地域标签,或者控制平面无法选择部署目标。
当 worker MachineDeployment 必须固定到一个部署目标时,设置 worker 部署区域。将 failureDomain 添加到 30-workers-md-0.yaml 中的 spec.template.spec:
apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineDeployment
metadata:
name: <cluster_name>-md-0
namespace: <namespace>
spec:
clusterName: <cluster_name>
replicas: <worker_replicas>
template:
spec:
clusterName: <cluster_name>
failureDomain: <worker_failure_domain>
version: "<k8s_version>"
# ... rest of spec
为 <worker_failure_domain> 使用 VSphereDeploymentZone 名称,而不是 VSphereFailureDomain 名称。
启用多个数据中心前,确认满足以下所有前提条件:
- 模板已同步到每个目标数据中心。
- 每个目标数据中心都能解析网络名称。
- 每个目标数据中心都能解析数据存储名称。
- vSphere CPI 数据中心列表涵盖每个目标数据中心。
添加数据磁盘
基线部署包含以下必需数据磁盘:
- 控制平面节点(每个节点 4 个磁盘):
persistentDisks[] 中的 var-cpaas,以及 ephemeralDisks[] 中的 var-lib-kubelet、var-lib-containerd 和 var-lib-etcd。不要删除其中任何磁盘。
- Worker 节点(每个节点 3 个磁盘):
persistentDisks[] 中的 var-cpaas,以及 ephemeralDisks[] 中的 var-lib-kubelet 和 var-lib-containerd。不要删除其中任何磁盘。
VSphereMachineTemplate.spec.template.spec.diskGiB 是系统磁盘大小,而不是 VM 的总磁盘容量。将数据磁盘保留在 VSphereMachineConfigPool.spec.configs[].persistentDisks[] 和 .ephemeralDisks[] 下。除非有意增大系统磁盘,否则不要将数据磁盘大小添加到 diskGiB。
如果节点需要超出必需集合的额外数据磁盘,请将更多条目追加到对应 VSphereMachineConfigPool 节点槽位中的匹配列表:数据必须在 VM 替换后保留时使用 persistentDisks,可重建时使用 ephemeralDisks。以下可选字段与此特别相关:
mountPath:如果设置,磁盘会格式化并挂载到指定路径。如果省略,磁盘会作为原始设备附加,并在 /dev/disk/by-capv/<name> 处创建符号链接,以便外部进程在运行时管理该磁盘。
wipeFilesystem:仅适用于持久磁盘。当 true 时,新 VM 首次启动会擦除磁盘内容。正常重启和手动服务重启不受影响。默认为 false。
要附加原始磁盘而不进行格式化或挂载,请省略 mountPath 和 fsFormat:
persistentDisks:
# ...standard persistent disks...
- name: app-data
sizeGiB: 50
磁盘可在 guest OS 中通过 /dev/disk/by-capv/app-data 访问。在滚动更新期间,同一个 VMDK 会重新附加到新 VM,并重新创建符号链接。磁盘不会自动格式化或挂载;应用负责在运行时管理该磁盘。
选择持久磁盘或临时磁盘
一个槽位声明两个磁盘列表:spec.configs[].persistentDisks[] 和 spec.configs[].ephemeralDisks[]。
对于不应位于系统磁盘上的可重建节点本地数据,请使用 ephemeralDisks,包括标准的 /var/lib/kubelet、/var/lib/containerd 和 /var/lib/etcd 磁盘,以及任何临时空间。对于任何丢失即意味着数据丢失的内容,请使用 persistentDisks。
将标准磁盘保留在其声明的列表中
/var/cpaas 必须保留在 persistentDisks[] 中:它包含必须在 VM 替换后保留的平台状态。/var/lib/kubelet、/var/lib/containerd 和 /var/lib/etcd 属于 ephemeralDisks[];它们会在每个新 VM 上重建,将其声明为持久磁盘会把过期的 kubelet、运行时和 etcd 成员数据带到替换节点上。
此拆分是在创建时选择的。ephemeralDisks[] 在 provider v1.0.17 中新增,因此,在较早 provider 上创建的集群完全无法使用标准临时磁盘集:它将 /var/cpaas、/var/lib/containerd 和 /var/lib/etcd 声明为持久磁盘,并在 etcd 磁盘上设置 wipeFilesystem: true。该集群会保留其布局:升级 provider 不会迁移任何磁盘。请参阅升级 Provider。
以下示例在标准 Worker 磁盘集旁添加一个临时 scratch 磁盘:
persistentDisks:
- name: var-cpaas
sizeGiB: <worker_var_cpaas_size_gib>
mountPath: /var/cpaas
fsFormat: ext4
ephemeralDisks:
- name: var-lib-kubelet
sizeGiB: <worker_var_lib_kubelet_size_gib>
mountPath: /var/lib/kubelet
fsFormat: ext4
- name: var-lib-containerd
sizeGiB: <worker_var_lib_containerd_size_gib>
mountPath: /var/lib/containerd
fsFormat: ext4
- name: scratch
sizeGiB: 50
mountPath: /var/scratch
fsFormat: ext4
同一槽位的两个列表中的磁盘名称和挂载路径必须唯一。
固定 SCSI 单元号
持久磁盘上的 unitNumber 可在 VM 重新创建期间保持 guest 设备顺序稳定。有效值为从 0 到 15,但不包括为 SCSI 控制器保留的 7;每个值在槽位内也必须唯一。
如果省略 unitNumber,控制器会在克隆时分配一个值,将其记录在 status.persistentDiskStatuses[].unitNumber 中,并在 VM 重新创建时复用该值。
临时磁盘完全没有 unitNumber 字段。控制器会在每次克隆时分配其单元号,并在 status.ephemeralDiskStatuses[].unitNumber 中报告该单元号,因此无法固定标准的 /var/lib/kubelet、/var/lib/containerd 和 /var/lib/etcd 磁盘。
重定位 pod 日志目录
当槽位中的任何磁盘挂载到 /var/lib/containerd 时,guest 磁盘协调脚本会将 /var/log/pods 指向该磁盘上的目录,使 pod 日志写入数据磁盘,而不是根文件系统。在标准布局中,该磁盘是临时磁盘,因此重定位的 pod 日志会随其所属 VM 一起销毁;需要保留的内容请发送到平台日志存储中。
WARNING
此重定位仅通过 guestinfo 用户数据写入 guest,且仅在首次启动前进行。升级 provider 不会将其补写到现有 machine 上;节点滚动并替换时才会应用此更改。请参阅不更改 Kubernetes 版本的 OS 映像更新。要就地修复正在运行的节点,需要使用带外脚本,这不属于此操作流程。
验证创建时变体
应用拓扑变体后,使用以下命令验证集群状态:
kubectl -n <namespace> get cluster,vspherecluster,kubeadmcontrolplane,machinedeployment,machine,vspheremachine,vspherevm
kubectl --kubeconfig=/tmp/<cluster_name>.kubeconfig get nodes -o wide
kubectl -n <namespace> get vspheremachineconfigpool <pool_name> \
-o jsonpath='{range .status.ephemeralDiskStatuses[*]}{.hostname}{" "}{.name}{" unit="}{.unitNumber}{"\n"}{end}'
确认以下结果:
- 目标资源中反映了 placement、NIC 或磁盘定义。
- 新节点进入
Ready 状态。
- 更改后现有节点仍保持正常。
- 每个声明的临时磁盘都已分配单元号,并可在 guest 中通过
/dev/disk/by-capv/<name> 进行寻址。
后续步骤
有关 worker 扩容以及后续模板或运行时拓扑变更,请参阅 在 VMware vSphere 上管理节点。每次仅应用一项变更,并在同一集群中合并多项变更之前验证结果。