7

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.

Ehab Qadah
  • 570
  • 4
  • 12
A.R.K.S
  • 1,692
  • 5
  • 18
  • 40
  • As far as I remember we do support patching with full resource object. We don't support patching with raw json, but I think it should work the way you're doing it. Are you sure you have correct `Deployment` being received from `getK8sObjectFromYamlConfig()` ? – Rohan Kumar May 21 '20 at 15:57
  • Thanks @RohanKumar . In the response that I get, I printed it out and it has not updated the image. Also the pod description after patch shows the old image. Does the typecasting look OK? `Deployment deploymentSnippet = (Deployment) getK8sObjectUsingYamlConfig(deploymentYaml);` – A.R.K.S May 21 '20 at 16:01
  • @RohanKumar I also check `if(deploymentSnippet instanceof Deployment)` before it calls k8s patch call. and it does not complain. Updated original post with the latest code I have. – A.R.K.S May 21 '20 at 16:05
  • I am using fabric8 version 4.3.1. A little old.. may be this is implemented recently? I will try with latest version – A.R.K.S May 21 '20 at 16:15
  • Is this project hosted somewhere so that I can try to reproduce this ? Maybe a simple reproducer of patching something into a Depoyment? – Rohan Kumar May 21 '20 at 16:15
  • Hi @RohanKumar, no, unfortunately this is behind firewall and we have a lot of helper methods to read the yaml file and convert it to `Deployment` type. I am assuming all these worker methods are OK because `instanceof Deployment` passes . Can you please point me to an example usage or documentation on this patch() API? Am I missing someh=thing n my patch snippet that is mandatory probably? – A.R.K.S May 21 '20 at 21:48
  • More info: To load yaml file and get a Deployment object back, we are using loadYaml helper methods from kubernetes-api 3.0.12 jar. Is there a github link to this project? Is this an active project and the APIs from 3.0.12 version is compatible with kubernetes-client 4.10.1? – A.R.K.S May 21 '20 at 22:57
  • Hi @RohanKumar, I re-read your 1st comment - "As far as I remember we do support patching with full resource object." . What does "full resource object" mean? My original deployment has two containers in it. I am trying to patch only one of them. Would that qualify as a full resource object? Or should I mention the other image name and url too? – A.R.K.S May 22 '20 at 02:06
  • This was my mistake that the Deployment object being returned was the one that is up and running and not the one from the yaml snippet.I resolved it and now I am down to the real issue. Updating the original post. – A.R.K.S May 22 '20 at 19:04
  • oh, cool. I'm glad that it finally worked out for you :-) . I was really busy yesterday so couldn't find time to look into your issue. – Rohan Kumar May 23 '20 at 06:43
  • Thanks @RohanKumar . Do you have any recommendation on what libraries I can use to convert my patch YAML to a valid K8s object ? The loadYaml APIs I have (taken from kubernetes-api jar) seem to return null when it gets a patch YAML. – A.R.K.S May 23 '20 at 17:39
  • Umm, `kubernetes-api` jar has been marked as deprecated. Are you using it for some other operation also? I think we should port this loadYaml class into fabric8 Kubernetes client Serialization package and fix it there – Rohan Kumar May 24 '20 at 06:09
  • @RohanKumar I see. I couldn't find a `deprecated` tag on this link : https://mvnrepository.com/artifact/io.fabric8/kubernetes-api we use some util methods from kubernetesHelper.. Is there an alternative library you would recommend to convert patch yamls into k8s objects? – A.R.K.S May 24 '20 at 19:43
  • If I add `kind: Deployment` to my patch yaml, the `loadYaml()` method works but then `patch()` method fails saying the selectors doesn't match the labels. But initially when I had all the labels and selectors, the `patch()` fails saying these fields are immutable. What are the mandatory fields in a patch yaml for `patch()` method to work. Some documentation would be helpful.. – A.R.K.S May 24 '20 at 20:49

2 Answers2

2

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();
}
Rohan Kumar
  • 5,427
  • 8
  • 25
  • 40
1

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

Thiago Arrais
  • 33,360
  • 7
  • 30
  • 34
A.R.K.S
  • 1,692
  • 5
  • 18
  • 40