kubernetes-sigs / cli-utils

This repo contains binaries that built from libraries in cli-runtime.
Apache License 2.0
154 stars 77 forks source link

Bug: kstatus/status returns incorrect "Current" result immediately after DaemonSet apply #548

Closed rohitagarwal003 closed 2 years ago

rohitagarwal003 commented 2 years ago

We have a use case where we want to create a DaemonSet and wait for its replicas to be ready before moving forward with the rest of the work. I was trying to use the kstatus/status library to do that. I thought I would apply the DaemonSet and then continuously check the result returned by status.Compute().

However, immediately after applying the DaemonSet, the status.Compute() method returns a Current result seemingly because the status is: status:map[currentNumberScheduled:0 desiredNumberScheduled:0 numberMisscheduled:0 numberReady:0]]}

Is this expected? Should we add an exception case to return an InProgress result when desiredNumberScheduled == 0?

Here's the code I was using:

package main

import (
    "context"
    "flag"
    "log"
    "os"

    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    serializeryaml "k8s.io/apimachinery/pkg/runtime/serializer/yaml"
    "k8s.io/apimachinery/pkg/types"
    "k8s.io/client-go/discovery"
    "k8s.io/client-go/discovery/cached/memory"
    "k8s.io/client-go/dynamic"
    "k8s.io/client-go/restmapper"
    "k8s.io/client-go/tools/clientcmd"
    "sigs.k8s.io/cli-utils/pkg/kstatus/status"
)

func main() {
    kubeconfig := flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
    manifest := flag.String("manifest", "", "absolute path to the daemonset manifest")
    flag.Parse()

    dsBytes, err := os.ReadFile(*manifest)
    if err != nil {
        log.Fatalf("Can't read %q: %v", *manifest, err)
    }

    obj := &unstructured.Unstructured{}
    _, gvk, err := serializeryaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme).Decode(dsBytes, nil, obj)
    if err != nil {
        log.Fatalf("Can't decode yaml: %v", err)
    }
    result, err := status.Compute(obj)
    if err != nil {
        log.Fatalf("Error computing status: %v", err)
    }
    log.Printf("Result Before: %v", result)

    config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
    if err != nil {
        log.Fatalf("Can't create *rest.Config to talk to the cluster: %v", err)
    }

    dynamicClient, err := dynamic.NewForConfig(config)
    if err != nil {
        log.Fatalf("Can't create a dynamic client: %v", err)
    }

    discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
    if err != nil {
        log.Fatalf("Can't create a discovery client: %v", err)
    }
    mapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(discoveryClient))

    mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
    if err != nil {
        log.Fatalf("Can't find GVR for GVK: %v", err)
    }

    if newObj, err := dynamicClient.Resource(mapping.Resource).Namespace(obj.GetNamespace()).Patch(context.TODO(), obj.GetName(), types.ApplyPatchType, dsBytes, metav1.PatchOptions{FieldManager: "deployer"}); err != nil {
        log.Fatalf("Failed to apply patch: %v", err)
    } else {
        result, err := status.Compute(newObj)
        if err != nil {
            log.Fatalf("Error computing new status: %v", err)
        }
        log.Printf("Result After: %v", result)
    }
}

Here's the output of the run:

$ ./main --kubeconfig admin.conf --manifest=ds.yaml
2022/02/17 08:37:15 Result Before: &{InProgress Missing .status.desiredNumberScheduled [{Reconciling True NoDesiredNumber Missing .status.desiredNumberScheduled}]}
2022/02/17 08:37:15 Result After: &{Current All replicas scheduled as expected. Replicas: 0 []}

Because I had a "bad" DaemonSet that didn't start, if I run the program again, it worked correctly:

$ ./main --kubeconfig admin.conf --manifest=ds.yaml
2022/02/17 08:37:30 Result Before: &{InProgress Missing .status.desiredNumberScheduled [{Reconciling True NoDesiredNumber Missing .status.desiredNumberScheduled}]}
2022/02/17 08:37:30 Result After: &{InProgress Available: 0/2 [{Reconciling True LessAvailable Available: 0/2}]}
rohitagarwal003 commented 2 years ago

Maybe we can use status.desiredNumberScheduled == 0 && status.observedGeneration == 0 as a signal that DaemonSet controller hasn't acted on the manifest?

rohitagarwal003 commented 2 years ago

/cc @mortent @ash2k @karlkfi