In a vSphere Supervisor environment, when upgrading an existing workload cluster from KR 1.32.x to 1.33.x, you may notice that the upgrade stalls because the first control plane VM fails to initialize successfully. In this situation, the below command will demonstrate that one control plane node is stuck in a provisioned state:
While connected to the Supervisor cluster context, the following symptoms are observed:
kubectl get machine -n <affected cluster namespace> | grep <affected workload cluster-name>
cluster-namespace <control plane node 1> affected workload cluster-name Running #d#h v1.32.3+vmware.1-fips
cluster-namespace <control plane node 2> affected workload cluster-name Provisioned #m v1.33.1+vmware.1-fip
kubectl get machine -n <affected cluster namespace> | grep <affected workload cluster-name>
cluster-namespace <control plane node A> affected workload cluster-name Running #d#h v1.32.3+vmware.1-fips
cluster-namespace <control plane node B> affected workload cluster-name Running #d#h v1.32.3+vmware.1-fips
cluster-namespace <control plane node C> affected workload cluster-name Running #d#h v1.32.3+vmware.1-fips
cluster-namespace <control plane node D> affected workload cluster-name Provisioned #m v1.33.1+vmware.1-fip
Note: Workload cluster upgrades always begin with control plane nodes and will not proceed to the worker nodes until all control plane nodes have successfully upgraded.
While SSH into the stuck new control plane node on the desired KR 1.33 version:
The kube-apiserver is failing in Exited state and the following error is observed in the new control plane node's kube-apiserver logs:
crictl ps -a --name kube-apiserver
CONTAINER IMAGE CREATED STATE NAME
<api-server-container-id> <IMAGE ID> <creation time> Exited kube-apiserver
crictl logs <api-server-container-id>
Error: unknown flag: --cloud-provider
In a situation, crictl ps does not show any process and kubelet service fails to start.
kubelet.service - kubelet: machine-agent override
Loaded: loaded (/lib/systemd/system/kubelet.service; enabled; vendor preset: enabled)
Drop-In: /usr/lib/systemd/system/kubelet.service.d
└─10-kubeadm.conf, 11-resource-sizing.conf
/etc/systemd/system/kubelet.service.d
└─99-machine-agent.conf
Active: inactive (dead) since DAY YYYY-MM-DD HH:MM:DD UTC; s/min ago
Docs: https://kubernetes.io/docs/home/
https://github-vcf.devops.broadcom.net/vcf/vks-machine-agent
Process: 2449 ExecStartPre=/usr/libexec/kubernetes/kubelet-resource-sizing.sh (code=exited, status=0/SUCCESS)
Process: 2465 ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS --system-reserved=cpu=70m,memory=1>
Main PID: 2465 (code=exited, status=0/SUCCESS)
CPU: 3.210s
vSphere Supervisor
VKS 3.4 or above
The --cloud-provider API server flag was removed starting with Kubernetes v1.33.0.
If this flag is still present in a cluster’s configuration, the API server will fail to initialize due to clashing fields in the KubeadmControlPlane resource. A detailed explanation is given in KB: Steps to Perform Before Upgrading to ClusterClass 3.3.0 or KR 1.32.X to 1.33.X
This issue is fixed in vSphere Kubernetes Service 3.5.0, and the procedure below does not need to be carried out if the service is upgraded to v3.5.0 before cluster upgrade.
To resolve this issue, remove the deprecated --cloud-provider=external flag from the cluster configuration after the upgrade has stalled.
All commands below are executed from the supervisor cluster context.
Procedure
Connect into the Supervisor cluster context
Identify the cluster that is stuck and note its name:
kubectl get cluster <affected cluster name> -n <affected cluster namespace>
Locate and patch the KubeadmControlPlane (KCP) object for the affected cluster:
Retrieve the name of the KCP for the affected workload cluster:
kubectl get kcp -n <affected workload cluster namespace>
Review the kcp yaml and verify that there are entries for "f:cloud-provider":
kubectl get kcp <kcp-name> -n <namespace> -o yaml --show-managed-fields | grep "f:cloud-provider" -A4
or
kubectl get kcp <kcp-name> -n <namespace> --show-managed-fields -ojsonpath='{.metadata.managedFields}' | less
(You may only get output for two entries, whereby no entry has ‘f:encryption-provider-config’. In such a case, skip to step 4.)
The below is an example and the order may vary in your environment:
f:cloud-provider: {}
f:enable-admission-plugins: {}
f:encryption-provider-config: {}
f:profiling: {}
f:runtime-config: {}
f:tls-cipher-suites: {}
--
f:cloud-provider: {}
f:event-qps: {}
f:node-ip: {}
f:node-labels: {}
f:protect-kernel-defaults: {}
f:read-only-port: {}
--
f:cloud-provider: {}
f:event-qps: {}
f:node-ip: {}
f:node-labels: {}
f:protect-kernel-defaults: {}
f:read-only-port: {}
Run kubectl patch to remove API server flag:
Create a backup of the KCP object:
kubectl get kcp <kcp-name> -n <affected workload cluster namespace> -o yaml --show-managed-fields > kcp_backup.yaml
kubectl patch kcp <kcp-name> -n <namespace> --type=json -p='[{"op": "remove", "path": "/metadata/managedFields/REPLACEME"}]' --dry-run=server
If successful, the following message will be returned:
kubeadmcontrolplane.controlplane.cluster.x-k8s.io/<kcp-name> patched
If the above patch dry-run is successful, perform the below command without the dry-run flag:
kubectl patch kcp <kcp-name> -n <namespace> --type=json -p='[{"op": "remove", "path": "/metadata/managedFields/REPLACEME"}]'
kubectl edit kcp -n <kcp-name> <affected workload cluster namespace>
spec:
kubeadmConfigSpec:
clusterConfiguration:
apiServer:
extraArgs:
admission-control-config-file: /etc/kubernetes/extra-config/admission-control-config.yaml
allow-privileged: "true"
audit-log-maxage: "30"
audit-log-maxbackup: "10"
audit-log-maxsize: "100"
audit-log-path: /var/log/kubernetes/kube-apiserver.log
audit-policy-file: /etc/kubernetes/extra-config/audit-policy.yaml
authentication-token-webhook-config-file: /webhook/webhook-config.yaml
authentication-token-webhook-version: v1
client-ca-file: /etc/ssl/certs/extensions-tls.crt
cloud-provider: external
kubectl get machine -n <workload cluster namespace> | grep Provisioned
cluster-namespace <stuck provisioned machine> affected workload cluster-name Provisioned #m v1.33.1+vmware.1-fip
kubectl annotate machine -n <workload cluster namespace> <stuck provisioned machine> 'cluster.x-k8s.io/remediate-machine=""'
kubectl get machine -n <workload cluster namespace>
(In some instances, the stuck Machine resource is quickly deleted after editing the kcp, and before any user has time to annotate the machine for remediation.)
After all of the above steps are followed, the upgrade will progress as expected.
Preventing the issue on other clusters on vSphere Kubernetes Service v.3.4.0 and below
Follow the steps in KB: Steps to Perform Before Upgrading to ClusterClass 3.3.0 or KR 1.32.X to 1.33.X