Does Fabric8io K8s java client support patch() or rollingupdate() using YAML snippets?

Viewed 1258

I am trying to program the patching/rolling upgrade of k8s apps by taking deployment snippets as input. I use patch() method to apply the snippet onto an existing deployment as part of rollingupdate using fabric8io's k8s client APIS.. Fabric8.io kubernetes-client version 4.10.1 I'm also using some loadYaml helper methods from kubernetes-api 3.0.12.

Here is my sample snippet - adminpatch.yaml file:

    kind: Deployment   
    spec:
      strategy:
        type: RollingUpdate
        rollingUpdate:
          maxSurge: 1
          maxUnavailable: 0     
      template:
        spec:
          containers:
            - name: ${PATCH_IMAGE_NAME}
              image: ${PATCH_IMAGE_URL}
              imagePullPolicy: Always

I'm sending the above file content (with all the placeholders replaced) to patchDeployment() method as string. Here is my call to fabric8 patch() method:

     public static String patchDeployment(String deploymentName, String namespace, String deploymentYaml) {
    try {
    Deployment deploymentSnippet = (Deployment) getK8sObject(deploymentYaml);
    if(deploymentSnippet instanceof Deployment) {
            logger.debug("Valid deployment object.");
    Deployment deployment = getK8sClient().apps().deployments().inNamespace(namespace).withName(deploymentName)
        .rolling().patch(deploymentSnippet);
    System.out.println(deployment.toString());
    return getLastConfig(deployment.getMetadata(), deployment);
    }
    } catch (Exception Ex) {
      Ex.printStackTrace();
    }
      return "Failed";
  }

It throws the below exception:

> io.fabric8.kubernetes.client.KubernetesClientException: Failure
> executing: PATCH at:
> https://10.44.4.126:6443/apis/apps/v1/namespaces/default/deployments/patch-demo.
> Message: Deployment.apps "patch-demo" is invalid: spec.selector:
> Invalid value:
> v1.LabelSelector{MatchLabels:map[string]string{"app":"nginx",
> "deployment":"3470574ffdbd6e88d426a77dd951ed45"},
> MatchExpressions:[]v1.LabelSelectorRequirement(nil)}: field is
> immutable. Received status: Status(apiVersion=v1, code=422,
> details=StatusDetails(causes=[StatusCause(field=spec.selector,
> message=Invalid value:
> v1.LabelSelector{MatchLabels:map[string]string{"app":"nginx",
> "deployment":"3470574ffdbd6e88d426a77dd951ed45"},
> MatchExpressions:[]v1.LabelSelectorRequirement(nil)}: field is
> immutable, reason=FieldValueInvalid, additionalProperties={})],
> group=apps, kind=Deployment, name=patch-demo, retryAfterSeconds=null,
> uid=null, additionalProperties={}), kind=Status,
> message=Deployment.apps "patch-demo" is invalid: spec.selector:
> Invalid value:
> v1.LabelSelector{MatchLabels:map[string]string{"app":"nginx",
> "deployment":"3470574ffdbd6e88d426a77dd951ed45"},
> MatchExpressions:[]v1.LabelSelectorRequirement(nil)}: field is
> immutable, metadata=ListMeta(_continue=null, remainingItemCount=null,
> resourceVersion=null, selfLink=null, additionalProperties={}),
> reason=Invalid, status=Failure, additionalProperties={}).

I also tried the original snippet(with labels and selectors) with kubectl patch deployment <DEPLOYMENT_NAME> -n <MY_NAMESPACE> --patch "$(cat adminpatch.yaml) and this applies the same snippet fine.

I could not get much documentation on fabric8io k8s client patch() java API. Any help will be appreciated.

2 Answers

Here is the related bug in Fabric8io rolling API: https://github.com/fabric8io/kubernetes-client/issues/1868

As of now, one way I found to achieve patching with fabri8io APIs is to :

  1. Get the running deployment object
  2. add/replace containers in it with new containers
  3. use the createOrReplace() API to redeploy the deployment object

But your patch understandably could be more than just an update to the containers field. In that case, processing each editable field becomes messy.

I went ahead with using the official K8s client's patchNamespacedDeployment() API to implement patching. https://github.com/kubernetes-client/java/blob/356109457499862a581a951a710cd808d0b9c622/examples/src/main/java/io/kubernetes/client/examples/PatchExample.java

With latest improvements in Fabric8 Kubernetes Client, you can do it both via patch() and rolling() API apart from using createOrReplace() which is mentioned in older answer.

Patching JSON/Yaml String using patch() call:

As per latest release v5.4.0, Fabric8 Kubernetes Client does support patch via raw string. It can be either YAML or JSON, see PatchTest.java. Here is an example using raw JSON string to update image of a Deployment:

try (KubernetesClient kubernetesClient = new DefaultKubernetesClient()) {
  kubernetesClient.apps().deployments()
    .inNamespace(deployment.getMetadata().getNamespace())
    .withName(deployment.getMetadata().getName())
    .patch("{\"spec\":{\"template\":{\"spec\":{\"containers\":[{\"name\":\"patch-demo-ctr-2\",\"image\":\"redis\"}]}}}}");
}

Rolling Update to change container image:

However, if you just want to do rolling update; You might want to use rolling() API instead. Here is how it would look like for updating image of an existing Deployment:

try (KubernetesClient client = new DefaultKubernetesClient()) {
    // ... Create Deployment 

    // Update Deployment for a single container Deployment
    client.apps().deployments()
            .inNamespace(namespace)
            .withName(deployment.getMetadata().getName())
            .rolling()
            .updateImage("gcr.io/google-samples/hello-app:2.0");
}

Rolling Update to change multiple images in multi-container Deployment:

If you want to update Deployment with multiple containers. You would need to use updateImage(Map<String, String>) method instead. Here is an example of it's usage:

try (KubernetesClient client = new DefaultKubernetesClient()) {
    Map<String, String> containerToImageMap = new HashMap<>();
    containerToImageMap.put("nginx", "stable-perl");
    containerToImageMap.put("hello", "hello-world:linux");
    client.apps().deployments()
            .inNamespace(namespace)
            .withName("multi-container-deploy")
            .rolling()
            .updateImage(containerToImageMap);
}

Rolling Update Restart an existing Deployment

If you need to restart your existing Deployment you can just use the rolling().restart() DSL method like this:

try (KubernetesClient client = new DefaultKubernetesClient()) {
    client.apps().deployments()
            .inNamespace(namespace)
            .withName(deployment.getMetadata().getName())
            .rolling()
            .restart();
}
Related