Fabric8 KubernetesClient多次更新PVC因volumeName为空触发禁止错误
问题:使用fabric8 kubernetes-client多次应用PVC YAML失败
问题场景
需要多次应用以下PVC YAML文件:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-pvc spec: storageClassName: my-storage-class accessModes: - ReadWriteMany resources: requests: storage: 1Gi
使用createOrReplace()方法执行:
Config config = new ConfigBuilder() .withMasterUrl("https://my-kubernetes-root:6443") .withNamespace("my-namespace") .withOauthToken(token) .withTrustCerts(true) .build(); KubernetesClient client = new DefaultKubernetesClient(config); ClasspathResource resource = new ClasspathResource("my-pvc.yaml"); client.load(resource.getInputStream()).createOrReplace(); // 首次执行成功 TimeUnit.MINUTES.sleep(1); // 此期间K8s动态分配volumeName client.load(resource.getInputStream()).createOrReplace(); // 第二次执行失败
报错信息
第二次执行触发以下错误:
io.fabric8.kubernetes.client.KubernetesClientException: Failure executing: PUT at: https://my-kubernetes-root:6443/api/v1/namespaces/my-namespace/persistentvolumeclaims/my-pvc. Message: PersistentVolumeClaim "my-pvc" is invalid: spec: Forbidden: spec is immutable after creation except resources.requests for bound claims core.PersistentVolumeClaimSpec{ AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteMany"}, Selector: nil, Resources: core.ResourceRequirements{Requests: core.ResourceList{s"storage": {i: resource.int64Amount{value: 1073741824}, s: "1Gi", Format: "BinarySI"}}}, - VolumeName: "", + VolumeName: "pvc-b79ebfcb-d5cb-4450-9f17-d69ec10b8712", StorageClassName: &"my-storage-class", VolumeMode: &"Filesystem", DataSource: nil, }
对比kubectl的表现
- 使用不含
volumeName的YAML执行kubectl apply可多次成功; - 如果YAML中指定
volumeName: "",首次执行成功,第二次执行会触发相同报错。
解决方案
1. 使用apply()方法替代createOrReplace()
apply()的逻辑和kubectl apply完全一致,会对比本地资源定义与集群中现有资源的差异,仅更新允许修改的字段,不会因为未指定的空值字段引发冲突。修改后的代码:
// 替换原来的createOrReplace()为apply() client.load(resource.getInputStream()).apply();
2. 手动合并PVC对象(适用于必须用createOrReplace的场景)
如果业务场景要求必须使用createOrReplace(),可以先获取集群中已绑定的PVC,将本地加载的PVC的可修改字段(仅resources.requests)合并到现有对象,再执行替换:
ClasspathResource resource = new ClasspathResource("my-pvc.yaml"); // 加载本地PVC定义 PersistentVolumeClaim localPvc = client.load(resource.getInputStream()).get().get(0); // 获取集群中现有PVC PersistentVolumeClaim existingPvc = client.persistentVolumeClaims().withName(localPvc.getMetadata().getName()).get(); if (existingPvc != null) { // 仅合并可修改的资源请求字段 existingPvc.getSpec().setResources(localPvc.getSpec().getResources()); client.persistentVolumeClaims().replace(existingPvc); } else { // 不存在则创建 client.persistentVolumeClaims().create(localPvc); }
3. 清理加载后的PVC对象空值字段
加载YAML时,未显式指定的字段会被解析为空字符串(如volumeName: ""),我们可以手动将这些空值字段设为null,避免与集群中已分配的真实值冲突:
ClasspathResource resource = new ClasspathResource("my-pvc.yaml"); PersistentVolumeClaim pvc = client.load(resource.getInputStream()).get().get(0); // 将spec中的空值volumeName设为null if (pvc.getSpec().getVolumeName() != null && pvc.getSpec().getVolumeName().isEmpty()) { pvc.getSpec().setVolumeName(null); } client.persistentVolumeClaims().createOrReplace(pvc);
内容的提问来源于stack exchange,提问作者lance-java
相关产品推荐
相关产品推荐

