• k8s CustomResource


    rest api

     kubectl api-resources 

    kind: CustomResourceDefinition

    [root@bogon deploy]# grep 'kind: CustomResourceDefinition' -rn *
    cluster.karmada.io_clusters.yaml:4:kind: CustomResourceDefinition
    multicluster.x-k8s.io_serviceexports.yaml:15:kind: CustomResourceDefinition
    multicluster.x-k8s.io_serviceimports.yaml:15:kind: CustomResourceDefinition
    policy.karmada.io_clusteroverridepolicies.yaml:4:kind: CustomResourceDefinition
    policy.karmada.io_clusterpropagationpolicies.yaml:4:kind: CustomResourceDefinition
    policy.karmada.io_overridepolicies.yaml:4:kind: CustomResourceDefinition
    policy.karmada.io_propagationpolicies.yaml:4:kind: CustomResourceDefinition
    policy.karmada.io_replicaschedulingpolicies.yaml:4:kind: CustomResourceDefinition
    work.karmada.io_clusterresourcebindings.yaml:4:kind: CustomResourceDefinition
    work.karmada.io_resourcebindings.yaml:4:kind: CustomResourceDefinition
    work.karmada.io_works.yaml:4:kind: CustomResourceDefinition
    [root@bogon deploy]# 

    scheme.AddKnownTypes(

    // Adds the list of known types to Scheme.
    func addKnownTypes(scheme *runtime.Scheme) error {
            scheme.AddKnownTypes(SchemeGroupVersion,
                    &Cluster{},
                    &ClusterList{},
            )
            // AddToGroupVersion allows the serialization of client types like ListOptions.
            v1.AddToGroupVersion(scheme, SchemeGroupVersion)
            return nil
    }

    demo1

    https://github.com/ysku/my-k8s-custom-controller/blob/master/pkg/controller/pod.go

    package controller
    
    import (
        log "github.com/sirupsen/logrus"
        v1 "k8s.io/api/core/v1"
        "k8s.io/client-go/informers"
        "k8s.io/client-go/tools/cache"
        "k8s.io/client-go/util/workqueue"
    )
    
    func NewPodLoggingController(factory informers.SharedInformerFactory) *LoggingController {
        informer := factory.Core().V1().Pods().Informer()
        informer.AddEventHandler(
            cache.ResourceEventHandlerFuncs{
                AddFunc:    podAdd,
                UpdateFunc: podUpdate,
                DeleteFunc: podDelete,
            },
        )
        queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())
        return NewLoggingController("pod", queue, informer)
    }
    
    func podAdd(obj interface{}) {
        pod := obj.(*v1.Pod)
        log.Printf("[podAdd] namespace:%s, name:%s, labels:%v", pod.Namespace, pod.Name, pod.GetLabels())
    }
    
    func podUpdate(old, new interface{}) {
        oldPod := old.(*v1.Pod)
        newPod := new.(*v1.Pod)
        log.Printf("[podUpdate] old, namespace:%s, name:%s, labels:%v", oldPod.Namespace, oldPod.Name, oldPod.GetLabels())
        log.Printf("[podUpdate] new, namespace:%s, name:%s, labels:%v", newPod.Namespace, newPod.Name, newPod.GetLabels())
    }
    
    func podDelete(obj interface{}) {
        pod := obj.(*v1.Pod)
        log.Printf("[podDelete] namespace:%s, name:%s, labels:%v", pod.Namespace, pod.Name, pod.GetLabels())
    }

    demo2

    https://github.com/aws/aws-app-mesh-controller-for-k8s/blob/1bcc239c0586c99c18636c1143a8066f94f4136a/pkg/k8s/pod_wrapper.go

    package k8s
    
    import (
        "fmt"
    
        v1 "k8s.io/api/core/v1"
        apimeta "k8s.io/apimachinery/pkg/api/meta"
        "k8s.io/apimachinery/pkg/labels"
        "k8s.io/apimachinery/pkg/types"
        "sigs.k8s.io/controller-runtime/pkg/client"
    )
    
    // PodsRepository represents an interface with all the common operations on pod objects
    type PodsRepository interface {
        GetPod(namespace string, name string) (*v1.Pod, error)
        ListPodsWithMatchingLabels(opts client.ListOptions) (*v1.PodList, error)
    }
    
    // podsRepository is the wrapper object with the client
    type podsRepository struct {
        customController *CustomController
    }
    
    // NewPodsRepository returns a new PodsRepository
    func NewPodsRepository(customController *CustomController) PodsRepository {
        return &podsRepository{
            customController: customController,
        }
    }
    
    // GetPod returns the pod object using NamespacedName
    func (k *podsRepository) GetPod(namespace string, name string) (*v1.Pod, error) {
        nsName := types.NamespacedName{
            Namespace: namespace,
            Name:      name,
        }.String()
        obj, exists, err := k.customController.GetDataStore().GetByKey(nsName)
        if err != nil {
            return nil, err
        }
        if !exists {
            return nil, fmt.Errorf("failed to find pod %s", nsName)
        }
        return obj.(*v1.Pod), nil
    }
    
    // ListPods return list of pods within a Namespace having Matching Labels
    // ListOptions.LabelSelector must be specified to return pods with matching labels
    // ListOptions.Namespace will scope result list to a given namespace
    func (k *podsRepository) ListPodsWithMatchingLabels(opts client.ListOptions) (*v1.PodList, error) {
        var items []interface{}
        var err error
    
        if opts.Namespace != "" {
            items, err = k.customController.GetDataStore().ByIndex(NamespaceIndexKey, opts.Namespace)
        } else {
            items = k.customController.GetDataStore().List()
        }
        if err != nil {
            return nil, err
        }
    
        podList := &v1.PodList{}
    
        var labelSel labels.Selector
        if opts.LabelSelector != nil {
            labelSel = opts.LabelSelector
        }
    
        for _, item := range items {
            pod, ok := item.(*v1.Pod)
            if !ok {
                return nil, fmt.Errorf("cache contained %T, which is not a Pod", item)
            }
    
            meta, err := apimeta.Accessor(pod)
            if err != nil {
                return nil, err
            }
            if labelSel != nil {
                lbls := labels.Set(meta.GetLabels())
                if !labelSel.Matches(lbls) {
                    continue
                }
            }
            podList.Items = append(podList.Items, *pod)
        }
        return podList, nil
    }

     demo3

    https://github.com/ikruglov/kube-custom-monitor/blob/e128afacf04e5bb32ab29f4a6ef350511edcd9a4/slo/slo.go

    func (pm *podStartupLatencyDataMonitor) Run(stop <-chan struct{}) error {
        eventInformer := pm.eventInformer.Informer()
        eventInformer.AddEventHandlerWithResyncPeriod(
            cache.ResourceEventHandlerFuncs{
                AddFunc:    func(obj interface{}) { pm.handleEvent(obj.(*api_v1.Event)) },
                UpdateFunc: func(old, new interface{}) { pm.handleEvent(new.(*api_v1.Event)) },
            },
            0*time.Second, // disable resync
        )
    
        podInformer := pm.podInformer.Informer()
        podInformer.AddEventHandlerWithResyncPeriod(
            cache.ResourceEventHandlerFuncs{
                AddFunc: func(obj interface{}) {
                    pm.handlePodUpdate(obj.(*api_v1.Pod))
                },
                UpdateFunc: func(old, new interface{}) {
                    pm.handlePodUpdate(new.(*api_v1.Pod))
                },
                DeleteFunc: func(obj interface{}) {
                    pod, ok := obj.(*api_v1.Pod)
                    if !ok {
                        if d, ok := obj.(cache.DeletedFinalStateUnknown); ok {
                            if pod, ok = d.Obj.(*api_v1.Pod); !ok {
                                glog.Errorf("failed to cast embedded object from tombstone to *v1.Pod: %v", d.Obj)
                                return
                            }
                        } else {
                            glog.Errorf("failed to cast observed object to *v1.Pod: %v", obj)
                            return
                        }
                    }
    
                    pm.handlePodDelete(pod)
                },
            },
            0*time.Second, // disable resync
        )
    
        go eventInformer.Run(stop)
        go podInformer.Run(stop)
        if !cache.WaitForCacheSync(stop, podInformer.HasSynced, eventInformer.HasSynced) {
            return fmt.Errorf("failed to wait for caches to sync")
        }
    
        pm.initDone = true
    
        go func() {
            wait.Until(func() {
                pm.Lock()
                defer pm.Unlock()
    
                now := time.Now()
                for podKey, when := range pm.toDelete {
                    if when.Before(now) {
                        glog.V(1).Infof("cleanup pod %s", podKey)
                        delete(pm.toDelete, podKey)
                        delete(pm.pods, podKey)
                    }
                }
            }, 15*time.Second, stop)
        }()
    
        return nil
    }
    /*
     * Licensed to the Apache Software Foundation (ASF) under one or more
     * contributor license agreements.  See the NOTICE file distributed with
     * this work for additional information regarding copyright ownership.
     * The ASF licenses this file to You under the Apache License, Version 2.0
     * (the "License"); you may not use this file except in compliance with
     * the License.  You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    package watchers
    
    import (
        "reflect"
        "time"
    
        api "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/apimachinery/pkg/fields"
        "k8s.io/apimachinery/pkg/labels"
        "k8s.io/client-go/kubernetes"
        cache "k8s.io/client-go/tools/cache"
    )
    
    // ServiceUpdate struct
    type ServiceUpdate struct {
        Service *api.Service
        Op      Operation
    }
    
    // ServiceWatcher struct
    type ServiceWatcher struct {
        serviceController cache.Controller
        serviceLister     cache.Indexer
        broadcaster       *Broadcaster
    }
    
    // ServiceUpdatesHandler interface
    type ServiceUpdatesHandler interface {
        OnServiceUpdate(serviceUpdate *ServiceUpdate)
    }
    
    func (svcw *ServiceWatcher) addEventHandler(obj interface{}) {
        service, ok := obj.(*api.Service)
        if !ok {
            return
        }
        svcw.broadcaster.Notify(&ServiceUpdate{Op: ADD, Service: service})
    }
    
    func (svcw *ServiceWatcher) deleteEventHandler(obj interface{}) {
        service, ok := obj.(*api.Service)
        if !ok {
            return
        }
        svcw.broadcaster.Notify(&ServiceUpdate{Op: REMOVE, Service: service})
    }
    
    func (svcw *ServiceWatcher) updateEventHandler(oldObj, newObj interface{}) {
        service, ok := newObj.(*api.Service)
        if !ok {
            return
        }
        if !reflect.DeepEqual(newObj, oldObj) {
            svcw.broadcaster.Notify(&ServiceUpdate{Op: UPDATE, Service: service})
        }
    }
    
    // RegisterHandler for register service update interface
    func (svcw *ServiceWatcher) RegisterHandler(handler ServiceUpdatesHandler) {
        svcw.broadcaster.Add(ListenerFunc(func(instance interface{}) {
            handler.OnServiceUpdate(instance.(*ServiceUpdate))
        }))
    }
    
    // ListBySelector for list services with labels
    func (svcw *ServiceWatcher) ListBySelector(set map[string]string) (ret []*api.Service, err error) {
        selector := labels.SelectorFromSet(set)
        err = cache.ListAll(svcw.serviceLister, selector, func(m interface{}) {
            ret = append(ret, m.(*api.Service))
        })
        return ret, err
    }
    
    // HasSynced return true if serviceController.HasSynced()
    func (svcw *ServiceWatcher) HasSynced() bool {
        return svcw.serviceController.HasSynced()
    }
    
    // StartServiceWatcher start watching updates for services from Kuberentes API server
    func StartServiceWatcher(clientset kubernetes.Interface, resyncPeriod time.Duration, stopCh <-chan struct{}) (*ServiceWatcher, error) {
        svcw := ServiceWatcher{}
    
        eventHandler := cache.ResourceEventHandlerFuncs{
            AddFunc:    svcw.addEventHandler,
            DeleteFunc: svcw.deleteEventHandler,
            UpdateFunc: svcw.updateEventHandler,
        }
    
        svcw.broadcaster = NewBroadcaster()
        lw := cache.NewListWatchFromClient(clientset.CoreV1().RESTClient(), "services", metav1.NamespaceAll, fields.Everything())
        svcw.serviceLister, svcw.serviceController = cache.NewIndexerInformer(
            lw,
            &api.Service{}, resyncPeriod, eventHandler,
            cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
        )
        go svcw.serviceController.Run(stopCh)
        return &svcw, nil
    }

    configmaps

    // addConfigMapHandler adds the handler for config maps to the controller
    func (lbc *LoadBalancerController) addConfigMapHandler(handlers cache.ResourceEventHandlerFuncs, namespace string) {
            lbc.configMapLister.Store, lbc.configMapController = cache.NewInformer(
                    cache.NewListWatchFromClient(
                            lbc.client.CoreV1().RESTClient(),
                            "configmaps",
                            namespace,
                            fields.Everything()),
                    &api_v1.ConfigMap{},
                    lbc.resync,
                    handlers,
            )
            lbc.cacheSyncs = append(lbc.cacheSyncs, lbc.configMapController.HasSynced)
    }

    NewCustomController

      
    // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
    //
    // Licensed under the Apache License, Version 2.0 (the "License"). You may
    // not use this file except in compliance with the License. A copy of the
    // License is located at
    //
    //     http://aws.amazon.com/apache2.0/
    //
    // or in the "license" file accompanying this file. This file is distributed
    // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
    // express or implied. See the License for the specific language governing
    // permissions and limitations under the License.
    
    package k8s
    
    import (
        "context"
        "time"
    
        "github.com/go-logr/logr"
        apimeta "k8s.io/apimachinery/pkg/api/meta"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/apimachinery/pkg/runtime"
        "k8s.io/apimachinery/pkg/watch"
        "k8s.io/client-go/kubernetes"
        "k8s.io/client-go/tools/cache"
    )
    
    // Converter for converting k8s object and object list used in watches and list operation
    type Converter interface {
        // ConvertObject takes an object and returns the modified object which will be
        // stored in the data store
        ConvertObject(originalObj interface{}) (convertedObj interface{}, err error)
        // ConvertList takes an object and returns the modified list of objects which
        // will be returned to the Simple Pager function to aggregate the list pagination
        // response
        ConvertList(originalList interface{}) (convertedList interface{}, err error)
        // Resource returns the K8s resource name to list/watch
        Resource() string
        // ResourceType returns the k8s object to list/watch
        ResourceType() runtime.Object
    }
    
    // Controller Interface implemented by PodController
    type Controller interface {
        // StartController starts the controller. Will block the calling routine
        StartController(dataStore cache.Indexer, stopChanel chan struct{})
        // GetDataStore returns the data store once it has synced with the API Server
        GetDataStore() cache.Indexer
    }
    
    // CustomController is an Informer which converts Pod Objects and notifies corresponding event handlers via Channels
    type CustomController struct {
        // clientSet is the kubernetes client set
        clientSet *kubernetes.Clientset
        // pageLimit is the number of objects returned per page on a list operation
        pageLimit int64
        // namespace to list/watch for
        namespace string
        // converter is the converter implementation that converts the k8s
        // object before storing in the data store
        converter Converter
        // resyncPeriod how often to sync using list with the API Server
        resyncPeriod time.Duration
        // retryOnError whether item should be retried on error. Should remain false in usual use case
        retryOnError bool
        // queue is the Delta FIFO queue
        queue *cache.DeltaFIFO
        // podEventNotificationChan channel will be notified for all pod events
        eventNotificationChan chan<- GenericEvent
    
        // log for custom controller
        log logr.Logger
        // controller is the K8s Controller
        controller cache.Controller
        // dataStore with the converted k8s object. It should not be directly accessed and used with
        // the exposed APIs
        dataStore cache.Indexer
    }
    
    // NewCustomController returns a new podController object
    func NewCustomController(clientSet *kubernetes.Clientset, pageLimit int64, namesspace string, converter Converter, resyncPeriod time.Duration,
        retryOnError bool, eventNotificationChan chan<- GenericEvent, log logr.Logger) *CustomController {
        c := &CustomController{
            clientSet:             clientSet,
            pageLimit:             pageLimit,
            namespace:             namesspace,
            converter:             converter,
            resyncPeriod:          resyncPeriod,
            retryOnError:          retryOnError,
            eventNotificationChan: eventNotificationChan,
            log:                   log,
        }
        c.dataStore = cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{NamespaceIndexKey: NamespaceKeyIndexFunc()})
        c.queue = cache.NewDeltaFIFO(cache.MetaNamespaceKeyFunc, c.dataStore)
        return c
    }
    
    // StartController starts the custom controller by doing a list and watch on the specified k8s
    // resource. The controller would store the converted k8s object in the provided indexer. The
    // stop channel should be notified to stop the controller
    func (c *CustomController) StartController(stopChanel <-chan struct{}) {
        config := &cache.Config{
            Queue: c.queue,
            ListerWatcher: newListWatcher(c.clientSet.CoreV1().RESTClient(),
                c.converter.Resource(), c.namespace, c.pageLimit, c.converter),
            ObjectType:       c.converter.ResourceType(),
            FullResyncPeriod: c.resyncPeriod,
            RetryOnError:     c.retryOnError,
            Process: func(obj interface{}) error {
                // from oldest to newest
                for _, d := range obj.(cache.Deltas) {
                    // Strip down the pod object and keep only the required details
                    convertedObj, err := c.converter.ConvertObject(d.Object)
                    if err != nil {
                        return err
                    }
                    switch d.Type {
                    case cache.Sync, cache.Added, cache.Updated:
                        if old, exists, err := c.dataStore.Get(convertedObj); err == nil && exists {
                            if err := c.dataStore.Update(convertedObj); err != nil {
                                return err
                            }
                            if err := c.notifyChannelOnUpdate(old, convertedObj); err != nil {
                                return err
                            }
                        } else if err == nil && !exists {
                            if err := c.dataStore.Add(convertedObj); err != nil {
                                return err
                            }
                            if err := c.notifyChannelOnCreate(convertedObj); err != nil {
                                return err
                            }
                        } else {
                            return err
                        }
                    case cache.Deleted:
                        if err := c.dataStore.Delete(convertedObj); err != nil {
                            return err
                        }
                        if err := c.notifyChannelOnDelete(convertedObj); err != nil {
                            return err
                        }
                    }
                }
                return nil
            },
        }
        c.controller = cache.New(config)
    
        // Run the controller
        c.controller.Run(stopChanel)
    }
    
    // GetDataStore returns the data store when it has successfully synced with API Server
    func (c *CustomController) GetDataStore() cache.Indexer {
        // Custom data store, it should not be accessed directly as the cache could be out of sync
        // on startup. Must be accessed from the pod controller's data store instead
        // TODO: we should refactor this in the future, as this approach will make controllers to run without having pod synced.
        // (It thus blocks when pod information is accessed)
        for c.controller == nil || (!c.controller.HasSynced() && c.controller.LastSyncResourceVersion() == "") {
            c.log.Info("waiting for controller to sync")
            time.Sleep(time.Second * 5)
        }
        return c.dataStore
    }
    
    // newListWatcher returns a list watcher with a custom list function that converts the
    // response for each page using the converter function and returns a general watcher
    func newListWatcher(restClient cache.Getter, resource string, namespace string, limit int64,
        converter Converter) *cache.ListWatch {
    
        listFunc := func(options metav1.ListOptions) (runtime.Object, error) {
            ctx := context.Background()
    
            list, err := restClient.Get().
                Namespace(namespace).
                Resource(resource).
                // This needs to be done because just setting the limit using option's
                // Limit is being overridden and the response is returned without pagination.
                VersionedParams(&metav1.ListOptions{
                    Limit:    limit,
                    Continue: options.Continue,
                }, metav1.ParameterCodec).
                Do(ctx).
                Get()
    
            if err != nil {
                return list, err
            }
            // Strip down the the list before passing the paginated response back to
            // the pager function
            convertedList, err := converter.ConvertList(list)
            return convertedList.(runtime.Object), err
        }
    
        // We don't need to modify the watcher, we will strip down the k8s object in the ProcessFunc
        // before storing the object in the data store.
        watchFunc := func(options metav1.ListOptions) (watch.Interface, error) {
            ctx := context.Background()
            options.Watch = true
    
            return restClient.Get().
                Namespace(namespace).
                Resource(resource).
                VersionedParams(&options, metav1.ParameterCodec).
                Watch(ctx)
        }
        return &cache.ListWatch{ListFunc: listFunc, WatchFunc: watchFunc}
    }
    
    // notifyChannelOnCreate notifies the add event on the appropriate channel
    func (c *CustomController) notifyChannelOnCreate(obj interface{}) error {
        meta, err := apimeta.Accessor(obj)
        if err != nil {
            return err
        }
        c.eventNotificationChan <- GenericEvent{
            EventType: CREATE,
            Meta:      meta,
            Object:    obj.(runtime.Object),
        }
        return nil
    }
    
    // notifyChannelOnCreate notifies the add event on the appropriate channel
    func (c *CustomController) notifyChannelOnUpdate(oldObj, newObj interface{}) error {
        oldMeta, err := apimeta.Accessor(oldObj)
        if err != nil {
            return err
        }
    
        newMeta, err := apimeta.Accessor(newObj)
        if err != nil {
            return err
        }
    
        c.eventNotificationChan <- GenericEvent{
            EventType: UPDATE,
            OldMeta:   oldMeta,
            OldObject: oldObj.(runtime.Object),
            Meta:      newMeta,
            Object:    newObj.(runtime.Object),
        }
        return nil
    }
    
    // notifyChannelOnDelete notifies the delete event on the appropriate channel
    func (c *CustomController) notifyChannelOnDelete(obj interface{}) error {
        meta, err := apimeta.Accessor(obj)
        if err != nil {
            return err
        }
        c.eventNotificationChan <- GenericEvent{
            EventType: DELETE,
            OldMeta:   meta,
            OldObject: obj.(runtime.Object),
        }
        return nil
    }
    package pkg
    
    import (
        "context"
        "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    
        "k8s.io/apimachinery/pkg/util/wait"
        "k8s.io/client-go/informers"
        "k8s.io/client-go/kubernetes"
        corev1lister "k8s.io/client-go/listers/core/v1"
        "k8s.io/client-go/tools/cache"
        "k8s.io/client-go/util/workqueue"
        "time"
        "k8s.io/klog"
    )
    
    // PodReconciler is an interface to annotate pods with the current timestamp.
    type PodReconciler interface {
        // Run starts the reconciler.
        Run(ctx context.Context, workers int)
    }
    
    // podReconciler implements the podReconciler interface.
    // This implementation listens on pod add and update events.
    // It adds the timestamp, as an annotation, to Pods if it
    // doesnt already exist.
    // If waitForAnnotation is set, this implementation only adds
    // the timestamp to Pods that are annotated with "add-timestamp".
    type podReconciler struct {
        k8sclient kubernetes.Interface
        podQueue workqueue.RateLimitingInterface
        podLister corev1lister.PodLister
        podSynced cache.InformerSynced
        waitForAnnotation bool
    }
    
    // NewPodReconciler initializes and returns an implementation of the PodReconciler interface.
    // If the namespaceToWatch parameter is specified, it sets up the reconciler to listen only
    // on the given namespace.
    func NewPodReconciler(client kubernetes.Interface, namespaceToWatch string, waitForAnnotation bool) (PodReconciler, error) {
        klog.Infof("Initializing Pod annotation controller. Listening to Pods on namespace %s", namespaceToWatch)
        // Create new informer factory. If no namespace is specified, the informer is set up to listen on all namespaces.
        informerFactory := informers.NewSharedInformerFactoryWithOptions(client, 0, informers.WithNamespace(namespaceToWatch))
        // Set up workqueue for Pod add and update events
        queue := workqueue.NewRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(time.Second, 5*time.Minute))
        podInformer := informerFactory.Core().V1().Pods()
        pr := &podReconciler{
            k8sclient: client,
            podQueue:  queue,
            podLister: podInformer.Lister(),
            podSynced: podInformer.Informer().HasSynced,
            waitForAnnotation: waitForAnnotation,
        }
    
        // Set up event handlers for Pod Add and Update events
        podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
            AddFunc:    pr.podAdd,
            UpdateFunc: pr.podUpdate,
            DeleteFunc: nil,
        })
    
        // Channel that can be used to destroy the informer.
        stopCh := make(chan struct{})
        informerFactory.Start(stopCh)
        if !cache.WaitForCacheSync(stopCh, pr.podSynced) {
            return nil, nil
        }
        klog.Infof("Initialized Pod annotation controller.")
        return pr, nil
    }
    func (pr *podReconciler) podAdd(obj interface{}) {
        objKey, err := getPodKey(obj)
        if err != nil {
            klog.Errorf("failed to get pod key with error %v", err)
            return
        }
        klog.V(4).Infof("Adding Pod %s to workqueue", objKey)
        pr.podQueue.Add(objKey)
    }
    
    
    func (pr *podReconciler) podUpdate(oldObj, newObj interface{}) {
        oldPod, ok := oldObj.(*v1.Pod)
        if !ok || oldPod == nil {
            return
        }
    
        newPod, ok := newObj.(*v1.Pod)
        if !ok || newPod == nil {
            return
        }
    
        pr.podAdd(newObj)
    }
    
    
    func (pr *podReconciler) Run(ctx context.Context, workers int) {
        defer pr.podQueue.ShutDown()
    
        klog.Infof("Setting up Pod Reconciler to run with %s threads", workers)
        stopCh := ctx.Done()
    
        for i := 0; i < workers; i++ {
            go wait.Until(pr.podReconcileWorker, 0, stopCh)
        }
    
        <-stopCh
    }
    
    // podReconcileWorker gets items from the workqueue and attempts to reconcile them.
    // If reconciliation fails, it adds the item back to the workqueue.
    func (pr *podReconciler) podReconcileWorker() {
        key, quit := pr.podQueue.Get()
        if quit {
            return
        }
        defer pr.podQueue.Done(key)
    
        if err := pr.reconcile(key.(string)); err != nil {
            // Put PVC back to the queue so that we can retry later.
            pr.podQueue.AddRateLimited(key)
        } else {
            pr.podQueue.Forget(key)
        }
    }
    
    // reconcile adds the current timestamp as an annotation to a Pod and logs the operation to stdout.
    func (pr *podReconciler) reconcile (key string) error {
        klog.Infof("Reconciling Pod %s", key)
        namespace, name, err := cache.SplitMetaNamespaceKey(key)
        if err != nil {
            klog.Errorf("failed to split key with error %v", err)
            return err
        }
        pod, err := pr.podLister.Pods(namespace).Get(name)
        if err != nil {
            klog.Errorf("failed to get Pod %s/%s with error %v", namespace, name, err)
            return err
        }
        podAnnotations := pod.GetAnnotations()
        if podAnnotations == nil {
            podAnnotations = make(map[string]string)
        }
        klog.V(4).Infof("Existing annotations on Pod %s/%s: %v", namespace, name, podAnnotations)
        if pr.waitForAnnotation {
            if _, ok := podAnnotations["add-timestamp"]; !ok {
                klog.V(4).Infof("Annotation add-timestamp doesnt exist, igonoring ...")
                return nil
            }
        }
        if _, ok := podAnnotations["timestamp"]; !ok {
            // Add timestamp to Pod
            podAnnotations["timestamp"] = time.Now().String()
            pod.SetAnnotations(podAnnotations)
            _, err = pr.k8sclient.CoreV1().Pods(namespace).Update(context.TODO(), pod, metav1.UpdateOptions{})
            if err != nil {
                klog.Errorf("failed to update Pod %s/%s with timestamp due to error %v", namespace, name, err)
                return err
            }
            klog.Infof("Added timestamp %v to Pod %s/%s", podAnnotations["timestamp"], namespace, name)
        }
        return nil
    }
    
    func getPodKey(obj interface{}) (string, error) {
        if unknown, ok := obj.(cache.DeletedFinalStateUnknown); ok && unknown.Obj != nil {
            obj = unknown.Obj
        }
        objKey, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
        return objKey, err
    }

    GetPod

    package k8s
    
    import (
        "context"
        "fmt"
        "os"
    
        "github.com/ericchiang/k8s"
        corev1 "github.com/ericchiang/k8s/apis/core/v1"
    )
    
    const (
        podNamespaceEnvVar = "KUBERNETES_POD_NAMESPACE"
        podNameEnvVar      = "KUBERNETES_POD_NAME"
    )
    
    // Client contains methods to access k8s API
    type Client interface {
        // GetPod returns current pod data.
        GetPod(ctx context.Context) (*corev1.Pod, error)
    }
    
    var clientProvider = func() (Client, error) {
        k8sClient, err := k8s.NewInClusterClient()
    
        return &defaultClient{k8sClient: k8sClient}, err
    }
    
    type defaultClient struct {
        k8sClient *k8s.Client
    }
    
    // GetPod returns k8s Pod information
    func (c *defaultClient) GetPod(ctx context.Context) (*corev1.Pod, error) {
        podNamespace := os.Getenv(podNamespaceEnvVar)
        podName := os.Getenv(podNameEnvVar)
    
        pod := &corev1.Pod{}
        if err := c.k8sClient.Get(ctx, podNamespace, podName, pod); err != nil {
            return nil, fmt.Errorf("unable to get pod data from API: %s", err)
        }
    
        return pod, nil
    }

    GetPod

    // GetService returns the definition of a specific service.
    // It returns an error on any problem.
    func (in *K8SClient) GetService(namespace, serviceName string) (*core_v1.Service, error) {
        return in.k8s.CoreV1().Services(namespace).Get(serviceName, emptyGetOptions)
    }
    
    // GetEndpoints return the list of endpoint of a specific service.
    // It returns an error on any problem.
    func (in *K8SClient) GetEndpoints(namespace, serviceName string) (*core_v1.Endpoints, error) {
        return in.k8s.CoreV1().Endpoints(namespace).Get(serviceName, emptyGetOptions)
    }
    
    // GetPods returns the pods definitions for a given set of labels.
    // An empty labelSelector will fetch all pods found per a namespace.
    // It returns an error on any problem.
    func (in *K8SClient) GetPods(namespace, labelSelector string) ([]core_v1.Pod, error) {
        // An empty selector is ambiguous in the go client, could mean either "select all" or "select none"
        // Here we assume empty == select all
        // (see also https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors)
        if pods, err := in.k8s.CoreV1().Pods(namespace).List(meta_v1.ListOptions{LabelSelector: labelSelector}); err == nil {
            return pods.Items, nil
        } else {
            return []core_v1.Pod{}, err
        }
    }
    
    // GetPod returns the pod definitions for a given pod name.
    // It returns an error on any problem.
    func (in *K8SClient) GetPod(namespace, name string) (*core_v1.Pod, error) {
        if pod, err := in.k8s.CoreV1().Pods(namespace).Get(name, emptyGetOptions); err != nil {
            return nil, err
        } else {
            return pod, nil
        }
    }
    
    // GetPod returns the pod definitions for a given pod name.
    // It returns an error on any problem.
    func (in *K8SClient) GetPodLogs(namespace, name string, opts *core_v1.PodLogOptions) (*PodLogs, error) {
        req := in.k8s.CoreV1().RESTClient().Get().Namespace(namespace).Name(name).Resource("pods").SubResource("log").VersionedParams(opts, scheme.ParameterCodec)
    
        readCloser, err := req.Stream()
        if err != nil {
            return nil, err
        }
    
        defer readCloser.Close()
        buf := new(bytes.Buffer)
        _, err = buf.ReadFrom(readCloser)
        if err != nil {
            return nil, err
        }
    
        return &PodLogs{Logs: buf.String()}, nil
    }
    
    func (in *K8SClient) GetCronJobs(namespace string) ([]batch_v1beta1.CronJob, error) {
        if cjList, err := in.k8s.BatchV1beta1().CronJobs(namespace).List(emptyListOptions); err == nil {
            return cjList.Items, nil
        } else {
            return []batch_v1beta1.CronJob{}, err
        }
    }
    
    func (in *K8SClient) GetJobs(namespace string) ([]batch_v1.Job, error) {
        if jList, err := in.k8s.BatchV1().Jobs(namespace).List(emptyListOptions); err == nil {
            return jList.Items, nil
        } else {
            return []batch_v1.Job{}, err
        }
    }
    / GetConfigMap fetches and returns the specified ConfigMap definition
    // from the cluster
    func (in *K8SClient) GetConfigMap(namespace, configName string) (*core_v1.ConfigMap, error) {
        configMap, err := in.k8s.CoreV1().ConfigMaps(namespace).Get(configName, emptyGetOptions)
        if err != nil {
            return &core_v1.ConfigMap{}, err
        }
    
        return configMap, nil
    }
    
    // GetNamespace fetches and returns the specified namespace definition
    // from the cluster
    func (in *K8SClient) GetNamespace(namespace string) (*core_v1.Namespace, error) {
        ns, err := in.k8s.CoreV1().Namespaces().Get(namespace, emptyGetOptions)
        if err != nil {
            return &core_v1.Namespace{}, err
        }
    
        return ns, nil
    }
    
    // GetServerVersion fetches and returns information about the version Kubernetes that is running
    func (in *K8SClient) GetServerVersion() (*version.Info, error) {
        return in.k8s.Discovery().ServerVersion()
    }
    
    // GetNamespaces returns a list of all namespaces of the cluster.
    // It returns a list of all namespaces of the cluster.
    // It returns an error on any problem.
    func (in *K8SClient) GetNamespaces(labelSelector string) ([]core_v1.Namespace, error) {
        var listOptions meta_v1.ListOptions
    
        // Apply labelSelector filtering if specified
        if labelSelector != "" {
            listOptions = meta_v1.ListOptions{LabelSelector: labelSelector}
        } else {
            listOptions = emptyListOptions
        }
    
        namespaces, err := in.k8s.CoreV1().Namespaces().List(listOptions)
        if err != nil {
            return nil, err
        }
    
        return namespaces.Items, nil
    }
    
    // GetProject fetches and returns the definition of the project with
    // the specified name by querying the cluster API. GetProject will fail
    // if the underlying cluster is not Openshift.
    func (in *K8SClient) GetProject(name string) (*osproject_v1.Project, error) {
        result := &osproject_v1.Project{}
    
        err := in.k8s.RESTClient().Get().Prefix("apis", "project.openshift.io", "v1", "projects", name).Do().Into(result)
    
        if err != nil {
            return nil, err
        }
    
        return result, nil
    }
    
    func (in *K8SClient) GetProjects(labelSelector string) ([]osproject_v1.Project, error) {
        result := &osproject_v1.ProjectList{}
    
        request := in.k8s.RESTClient().Get().Prefix("apis", "project.openshift.io", "v1", "projects")
    
        // Apply label selector filtering if specified
        if labelSelector != "" {
            request.Param("labelSelector", labelSelector)
        }
    
        err := request.Do().Into(result)
    
        if err != nil {
            return nil, err
        }
    
        return result.Items, nil
    }
    
    func (in *K8SClient) IsOpenShift() bool {
        if in.isOpenShift == nil {
            isOpenShift := false
            _, err := in.k8s.RESTClient().Get().AbsPath("/apis/project.openshift.io").Do().Raw()
            if err == nil {
                isOpenShift = true
            }
            in.isOpenShift = &isOpenShift
        }
        return *in.isOpenShift
    }
    
    // GetServices returns a list of services for a given namespace.
    // If selectorLabels is defined the list of services is filtered for those that matches Services selector labels.
    // It returns an error on any problem.
    func (in *K8SClient) GetServices(namespace string, selectorLabels map[string]string) ([]core_v1.Service, error) {
        var allServices []core_v1.Service
    
        if allServicesList, err := in.k8s.CoreV1().Services(namespace).List(emptyListOptions); err == nil {
            allServices = allServicesList.Items
        } else {
            return []core_v1.Service{}, err
        }
    
        if selectorLabels == nil {
            return allServices, nil
        }
        var services []core_v1.Service
        for _, svc := range allServices {
            svcSelector := labels.Set(svc.Spec.Selector).AsSelector()
            if !svcSelector.Empty() && svcSelector.Matches(labels.Set(selectorLabels)) {
                services = append(services, svc)
            }
        }
        return services, nil
    }
    
    // GetDeployment returns the definition of a specific deployment.
    // It returns an error on any problem.
    func (in *K8SClient) GetDeployment(namespace, deploymentName string) (*apps_v1.Deployment, error) {
        return in.k8s.AppsV1().Deployments(namespace).Get(deploymentName, emptyGetOptions)
    }
    
    // GetRoute returns the external URL endpoint of a specific route name.
    // It returns an error on any problem.
    func (in *K8SClient) GetRoute(namespace, name string) (*osroutes_v1.Route, error) {
        result := &osroutes_v1.Route{}
        err := in.k8s.RESTClient().Get().Prefix("apis", "route.openshift.io", "v1").Namespace(namespace).Resource("routes").SubResource(name).Do().Into(result)
        if err != nil {
            return nil, err
        }
        return result, nil
    }
    
    // GetDeployments returns an array of deployments for a given namespace.
    // It returns an error on any problem.
    func (in *K8SClient) GetDeployments(namespace string) ([]apps_v1.Deployment, error) {
        if depList, err := in.k8s.AppsV1().Deployments(namespace).List(emptyListOptions); err == nil {
            return depList.Items, nil
        } else {
            return []apps_v1.Deployment{}, err
        }
    }
    
    // GetDeployments returns an array of deployments for a given namespace and a set of labels.
    // An empty labelSelector will fetch all Deployments for a namespace.
    // It returns an error on any problem.
    func (in *K8SClient) GetDeploymentsByLabel(namespace string, labelSelector string) ([]apps_v1.Deployment, error) {
        listOptions := meta_v1.ListOptions{LabelSelector: labelSelector}
        if depList, err := in.k8s.AppsV1().Deployments(namespace).List(listOptions); err == nil {
            return depList.Items, nil
        } else {
            return []apps_v1.Deployment{}, err
        }
    }
    
    // GetDeployment returns the definition of a specific deployment.
    // It returns an error on any problem.
    func (in *K8SClient) GetDeploymentConfig(namespace, deploymentconfigName string) (*osapps_v1.DeploymentConfig, error) {
        result := &osapps_v1.DeploymentConfig{}
        err := in.k8s.RESTClient().Get().Prefix("apis", "apps.openshift.io", "v1").Namespace(namespace).Resource("deploymentconfigs").SubResource(deploymentconfigName).Do().Into(result)
        if err != nil {
            return nil, err
        }
        return result, nil
    }
    
    // GetDeployments returns an array of deployments for a given namespace.
    // An empty labelSelector will fetch all Deployments for a namespace.
    // It returns an error on any problem.
    func (in *K8SClient) GetDeploymentConfigs(namespace string) ([]osapps_v1.DeploymentConfig, error) {
        result := &osapps_v1.DeploymentConfigList{}
        err := in.k8s.RESTClient().Get().Prefix("apis", "apps.openshift.io", "v1").Namespace(namespace).Resource("deploymentconfigs").Do().Into(result)
        if err != nil {
            return nil, err
        }
        return result.Items, nil
    }
    
    func (in *K8SClient) GetReplicaSets(namespace string) ([]apps_v1.ReplicaSet, error) {
        if rsList, err := in.k8s.AppsV1().ReplicaSets(namespace).List(emptyListOptions); err == nil {
            return rsList.Items, nil
        } else {
            return []apps_v1.ReplicaSet{}, err
        }
    }
    
    func (in *K8SClient) GetStatefulSet(namespace string, statefulsetName string) (*apps_v1.StatefulSet, error) {
        return in.k8s.AppsV1().StatefulSets(namespace).Get(statefulsetName, emptyGetOptions)
    }
    
    func (in *K8SClient) GetStatefulSets(namespace string) ([]apps_v1.StatefulSet, error) {
        if ssList, err := in.k8s.AppsV1().StatefulSets(namespace).List(emptyListOptions); err == nil {
            return ssList.Items, nil
        } else {
            return []apps_v1.StatefulSet{}, err
        }
    }
    
    func (in *K8SClient) GetReplicationControllers(namespace string) ([]core_v1.ReplicationController, error) {
        if rcList, err := in.k8s.CoreV1().ReplicationControllers(namespace).List(emptyListOptions); err == nil {
            return rcList.Items, nil
        } else {
            return []core_v1.ReplicationController{}, err
        }
    }
    
    // GetService returns the definition of a specific service.
    // It returns an error on any problem.
    func (in *K8SClient) GetService(namespace, serviceName string) (*core_v1.Service, error) {
        return in.k8s.CoreV1().Services(namespace).Get(serviceName, emptyGetOptions)
    }
    
    // GetEndpoints return the list of endpoint of a specific service.
    // It returns an error on any problem.
    func (in *K8SClient) GetEndpoints(namespace, serviceName string) (*core_v1.Endpoints, error) {
        return in.k8s.CoreV1().Endpoints(namespace).Get(serviceName, emptyGetOptions)
    }
    
    // GetPods returns the pods definitions for a given set of labels.
    // An empty labelSelector will fetch all pods found per a namespace.
    // It returns an error on any problem.
    func (in *K8SClient) GetPods(namespace, labelSelector string) ([]core_v1.Pod, error) {
        // An empty selector is ambiguous in the go client, could mean either "select all" or "select none"
        // Here we assume empty == select all
        // (see also https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors)
        if pods, err := in.k8s.CoreV1().Pods(namespace).List(meta_v1.ListOptions{LabelSelector: labelSelector}); err == nil {
            return pods.Items, nil
        } else {
            return []core_v1.Pod{}, err
        }
    }
    
    // GetPod returns the pod definitions for a given pod name.
    // It returns an error on any problem.
    func (in *K8SClient) GetPod(namespace, name string) (*core_v1.Pod, error) {
        if pod, err := in.k8s.CoreV1().Pods(namespace).Get(name, emptyGetOptions); err != nil {
            return nil, err
        } else {
            return pod, nil
        }
    }
    
    // GetPod returns the pod definitions for a given pod name.
    // It returns an error on any problem.
    func (in *K8SClient) GetPodLogs(namespace, name string, opts *core_v1.PodLogOptions) (*PodLogs, error) {
        req := in.k8s.CoreV1().RESTClient().Get().Namespace(namespace).Name(name).Resource("pods").SubResource("log").VersionedParams(opts, scheme.ParameterCodec)
    
        readCloser, err := req.Stream()
        if err != nil {
            return nil, err
        }
    
        defer readCloser.Close()
        buf := new(bytes.Buffer)
        _, err = buf.ReadFrom(readCloser)
        if err != nil {
            return nil, err
        }
    
        return &PodLogs{Logs: buf.String()}, nil
    }
    
    func (in *K8SClient) GetCronJobs(namespace string) ([]batch_v1beta1.CronJob, error) {
        if cjList, err := in.k8s.BatchV1beta1().CronJobs(namespace).List(emptyListOptions); err == nil {
            return cjList.Items, nil
        } else {
            return []batch_v1beta1.CronJob{}, err
        }
    }
    
    func (in *K8SClient) GetJobs(namespace string) ([]batch_v1.Job, error) {
        if jList, err := in.k8s.BatchV1().Jobs(namespace).List(emptyListOptions); err == nil {
            return jList.Items, nil
        } else {
            return []batch_v1.Job{}, err
        }
    }
    E0906 10:44:14.807664   13796 reflector.go:127] pkg/mod/k8s.io/client-go@v0.19.0/tools/cache/reflector.go:156: Failed to watch *v1alpha1.Waitdeployment: the server could not find the requested resource (get waitdeployments.qbox.io)
    E0906 10:44:15.959997   13796 reflector.go:127] pkg/mod/k8s.io/client-go@v0.19.0/tools/cache/reflector.go:156: Failed to watch *v1alpha1.Waitdeployment: failed to list *v1alpha1.Waitdeployment: the server could not find the requested resource (get waitdeployments.qbox.io)
    E0906 10:44:18.241173   13796 reflector.go:127] pkg/mod/k8s.io/client-go@v0.19.0/tools/cache/reflector.go:156: Failed to watch *v1alpha1.Waitdeployment: failed to list *v1alpha1.Waitdeployment: the server could not find the requested resource (get waitdeployments.qbox.io)
    E0906 10:44:23.641205   13796 reflector.go:127] pkg/mod/k8s.io/client-go@v0.19.0/tools/cache/reflector.go:156: Failed to watch *v1alpha1.Waitdeployment: failed to list *v1alpha1.Waitdeployment: the server could not find the requested resource (get waitdeployments.qbox.io)
    E0906 10:44:30.463055   13796 reflector.go:127] pkg/mod/k8s.io/client-go@v0.19.0/tools/cache/reflector.go:156: Failed to watch *v1alpha1.Waitdeployment: failed to list *v1alpha1.Waitdeployment: the server could not find the requested resource (get waitdeployments.qbox.io)
    E0906 10:44:42.829257   13796 controller.go:281] Waitdeployment 'default/wdtest' in work queue no longer exists

    processNextWorkItem

    E0906 10:44:42.829257 13796 controller.go:281] Waitdeployment 'default/wdtest' in work queue no longer exists
    I0906 10:44:42.829321 13796 controller.go:254] Successfully synced 'default/wdtest'

    / processNextWorkItem 从 workqueue 中获取一个任务并最终调用 syncHandler 执行她
    func (c *Client) processNextWorkItem() bool {
            obj, shutdown := c.workqueue.Get()
            if shutdown {
                    return false
            }
    
            // 这里写成函数形式是为了方便里面能直接调用 defer
            err := func(obj interface{}) error {
                    // 通过调用 Done 方法可以通知 workqueue 完成了这个任务
                    defer c.workqueue.Done(obj)
                    var key string
                    var ok bool
                    // We expect strings to come off the workqueue. These are of the
                    // form namespace/name. We do this as the delayed nature of the
                    // workqueue means the items in the informer cache may actually be
                    // more up to date that when the item was initially put onto the
                    // workqueue.
                    if key, ok = obj.(string); !ok {
                            // 通过调用 Forget 方法可以避免任务被再次入队,比如调用一个任务出错后,为了避免
                            // 它再次放入队列底部并在 back-off 后再次尝试,可以调用这个方法
                            c.workqueue.Forget(obj)
                            utilRuntime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj))
                            return nil
                    }
                    // Run the syncHandler, passing it the namespace/name string of the
                    // Foo resource to be synced.
                    if err := c.syncHandler(key); err != nil {
                            // Put the item back on the workqueue to handle any transient errors.
                            c.workqueue.AddRateLimited(key)
                            return fmt.Errorf("error syncing '%s': %s, requeuing", key, err.Error())
                    }
                    // Finally, if no error occurs we Forget this item so it does not
                    // get queued again until another change happens.
                    c.workqueue.Forget(obj)
                    klog.Infof("Successfully synced '%s'", key)
                    return nil
            }(obj)
    
            if err != nil {
                    utilRuntime.HandleError(err)
                    return true
            }
    
            return true
    }
    // syncHandler compares the actual state with the desired, and attempts to
    // converge the two. It then updates the Status block of the Waitdeployment
    // resource with the current status of the resource.
    func (c *Client) syncHandler(key string) error {
            // Convert the namespace/name string into a distinct namespace and name
            ns, n, err := cache.SplitMetaNamespaceKey(key)
            if err != nil {
                    utilRuntime.HandleError(fmt.Errorf("invalid resource key: %s", key))
                    return nil
            }
            waitDeployment, err := c.waitDeploymentsLister.Waitdeployments(ns).Get(n)
            if err != nil {
                    // The Waitdeployment resource may no longer exist, in which case we stop
                    // processing.
                    if errors.IsNotFound(err) {
                            utilRuntime.HandleError(fmt.Errorf("Waitdeployment '%s' in work queue no longer exists", key))
                            return nil
                    }
                    return err
            }
    
            deployment, err := c.checkAndStartDeployment(waitDeployment)
            if err != nil {
                    return err
            }
    
            // If the Deployment is not controlled by this waitDeployment resource,
            // we should log a warning to the event recorder and return error msg.
            if !metav1.IsControlledBy(deployment, waitDeployment) {
                    msg := fmt.Sprintf(MessageResourceExists, deployment.Name)
                    c.recorder.Event(waitDeployment, coreV1.EventTypeWarning, ErrResourceExists, msg)
                    return fmt.Errorf(msg)
            }
    
            // update deployment
            deployment, err = c.kubeClient.AppsV1().Deployments(waitDeployment.Namespace).Update(c.ctx, newDeployment(waitDeployment), metav1.UpdateOptions{})
            if err != nil {
                    return err
            }
    
            // update waitDeployment
            _, err = c.customClient.QboxV1alpha1().Waitdeployments(waitDeployment.Namespace).Update(c.ctx, waitDeployment.DeepCopy(), metav1.UpdateOptions{})
            if err != nil {
                    return err
            }
    
            c.recorder.Event(waitDeployment, coreV1.EventTypeNormal, SuccessSynced, MessageResourceSynced)
            return nil
    }

    kubectl delete -f artifacts/waitdeployment.yaml

    customresourcedefinition.apiextensions.k8s.io "waitdeployments.qbox.io" deleted

    (dlv) b QboxV1alpha1
    Breakpoint 2 (enabled) set at 0xcdc070 for github.com/xwen-winnie/crd_demo/kube/client/clientset/versioned.(*Clientset).QboxV1alpha1() ./kube/client/clientset/versioned/clientset.go:44
    (dlv) c
    > github.com/xwen-winnie/crd_demo/kube/client/clientset/versioned.(*Clientset).QboxV1alpha1() ./kube/client/clientset/versioned/clientset.go:44 (hits goroutine(94):1 total:1) (PC: 0xcdc070)
    Warning: debugging optimized function
        39:         qboxV1alpha1 *qboxv1alpha1.QboxV1alpha1Client
        40: }
        41:
        42: // QboxV1alpha1 retrieves the QboxV1alpha1Client
        43: func (c *Clientset) QboxV1alpha1() qboxv1alpha1.QboxV1alpha1Interface {
    =>  44:         return c.qboxV1alpha1
        45: }
        46:
        47: // Discovery retrieves the DiscoveryClient
        48: func (c *Clientset) Discovery() discovery.DiscoveryInterface {
        49:         if c == nil {
    (dlv) bt
     0  0x0000000000cdc070 in github.com/xwen-winnie/crd_demo/kube/client/clientset/versioned.(*Clientset).QboxV1alpha1
        at ./kube/client/clientset/versioned/clientset.go:44
     1  0x0000000000cdd8b4 in github.com/xwen-winnie/crd_demo/kube/client/informers/externalversions/qbox/v1alpha1.NewFilteredWaitdeploymentInformer.func2
        at ./kube/client/informers/externalversions/qbox/v1alpha1/waitdeployment.go:71
     2  0x0000000000c114c4 in k8s.io/client-go/tools/cache.(*ListWatch).Watch
        at /opt/gopath/pkg/mod/k8s.io/client-go@v0.19.0/tools/cache/listwatch.go:111
     3  0x0000000000c134a4 in k8s.io/client-go/tools/cache.(*Reflector).ListAndWatch
        at /opt/gopath/pkg/mod/k8s.io/client-go@v0.19.0/tools/cache/reflector.go:402
     4  0x0000000000c1afd4 in k8s.io/client-go/tools/cache.(*Reflector).Run.func1
        at /opt/gopath/pkg/mod/k8s.io/client-go@v0.19.0/tools/cache/reflector.go:209
     5  0x000000000068f544 in k8s.io/apimachinery/pkg/util/wait.BackoffUntil.func1
        at /opt/gopath/pkg/mod/k8s.io/apimachinery@v0.19.3/pkg/util/wait/wait.go:155
     6  0x000000000068e6e4 in k8s.io/apimachinery/pkg/util/wait.BackoffUntil
        at /opt/gopath/pkg/mod/k8s.io/apimachinery@v0.19.3/pkg/util/wait/wait.go:156
     7  0x0000000000c12e8c in k8s.io/client-go/tools/cache.(*Reflector).Run
        at /opt/gopath/pkg/mod/k8s.io/client-go@v0.19.0/tools/cache/reflector.go:208
     8  0x0000000000c1ddd0 in k8s.io/client-go/tools/cache.(*Reflector).Run-fm
        at /opt/gopath/pkg/mod/k8s.io/client-go@v0.19.0/tools/cache/reflector.go:206
     9  0x000000000068f3e0 in k8s.io/apimachinery/pkg/util/wait.(*Group).StartWithChannel.func1
        at /opt/gopath/pkg/mod/k8s.io/apimachinery@v0.19.3/pkg/util/wait/wait.go:56
    10  0x000000000068f4a4 in k8s.io/apimachinery/pkg/util/wait.(*Group).Start.func1
        at /opt/gopath/pkg/mod/k8s.io/apimachinery@v0.19.3/pkg/util/wait/wait.go:73
    11  0x000000000006fb84 in runtime.goexit
        at /usr/local/go/src/runtime/asm_arm64.s:1148
    (dlv) 

    checkAndStartDeployment

    kubectl create -f  artifacts/waitdeployment_test.yaml
    waitdeployment.qbox.io/wdtest created
    (dlv) c
    > github.com/xwen-winnie/crd_demo/kube.doTCPProbe() ./kube/controller.go:333 (hits goroutine(146):1 total:2) (PC: 0xce8c40)
    Warning: debugging optimized function
       328:         }
       329:         return deployment, nil
       330: }
       331:
       332: // tcp 连接检测代码
    => 333: func doTCPProbe(addr string, timeout time.Duration) error {
       334:         conn, err := net.DialTimeout("tcp", addr, timeout)
       335:         if err != nil {
       336:                 return err
       337:         }
       338:         err = conn.Close()
    (dlv) bt
     0  0x0000000000ce8c40 in github.com/xwen-winnie/crd_demo/kube.doTCPProbe
        at ./kube/controller.go:333
     1  0x0000000000ce899c in github.com/xwen-winnie/crd_demo/kube.(*Client).checkAndStartDeployment
        at ./kube/controller.go:317
     2  0x0000000000ce8520 in github.com/xwen-winnie/crd_demo/kube.(*Client).syncHandler
        at ./kube/controller.go:287
     3  0x0000000000ce9a88 in github.com/xwen-winnie/crd_demo/kube.(*Client).processNextWorkItem.func1
        at ./kube/controller.go:246
     4  0x0000000000ce8318 in github.com/xwen-winnie/crd_demo/kube.(*Client).processNextWorkItem
        at ./kube/controller.go:256
     5  0x0000000000ce82a8 in github.com/xwen-winnie/crd_demo/kube.(*Client).runWorker
        at ./kube/controller.go:215
     6  0x0000000000ce9e28 in github.com/xwen-winnie/crd_demo/kube.(*Client).runWorker-fm
        at ./kube/controller.go:214
     7  0x000000000068f544 in k8s.io/apimachinery/pkg/util/wait.BackoffUntil.func1
        at /opt/gopath/pkg/mod/k8s.io/apimachinery@v0.19.3/pkg/util/wait/wait.go:155
     8  0x000000000068e6e4 in k8s.io/apimachinery/pkg/util/wait.BackoffUntil
        at /opt/gopath/pkg/mod/k8s.io/apimachinery@v0.19.3/pkg/util/wait/wait.go:156
     9  0x000000000068e658 in k8s.io/apimachinery/pkg/util/wait.JitterUntil
        at /opt/gopath/pkg/mod/k8s.io/apimachinery@v0.19.3/pkg/util/wait/wait.go:133
    10  0x000000000068e5b8 in k8s.io/apimachinery/pkg/util/wait.Until
        at /opt/gopath/pkg/mod/k8s.io/apimachinery@v0.19.3/pkg/util/wait/wait.go:90
    11  0x000000000006fb84 in runtime.goexit
        at /usr/local/go/src/runtime/asm_arm64.s:1148
    (dlv) p addr
    "192.168.1.5:30080"
    (dlv) 
    func (c *Client) syncHandler(key string) error {
            // Convert the namespace/name string into a distinct namespace and name
            ns, n, err := cache.SplitMetaNamespaceKey(key)
            if err != nil {
                    utilRuntime.HandleError(fmt.Errorf("invalid resource key: %s", key))
                    return nil
            }
            waitDeployment, err := c.waitDeploymentsLister.Waitdeployments(ns).Get(n)
            if err != nil {
                    // The Waitdeployment resource may no longer exist, in which case we stop
                    // processing.
                    if errors.IsNotFound(err) {
                            utilRuntime.HandleError(fmt.Errorf("Waitdeployment '%s' in work queue no longer exists", key))
                            return nil
                    }
                    return err
            }
    
            deployment, err := c.checkAndStartDeployment(waitDeployment)
            if err != nil {
                    return err
            }
    (dlv) p waitDeployment
    *github.com/xwen-winnie/crd_demo/kube/apis/qbox/v1alpha1.Waitdeployment {
            TypeMeta: k8s.io/apimachinery/pkg/apis/meta/v1.TypeMeta {Kind: "", APIVersion: ""},
            ObjectMeta: k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta {
                    Name: "wdtest",
                    GenerateName: "",
                    Namespace: "default",
                    SelfLink: "/apis/qbox.io/v1alpha1/namespaces/default/waitdeployments/wdtest",
                    UID: "1b1ea378-360f-405e-b374-a42ba7267a75",
                    ResourceVersion: "16289989",
                    Generation: 1,
                    CreationTimestamp: (*"k8s.io/apimachinery/pkg/apis/meta/v1.Time")(0x40001e7988),
                    DeletionTimestamp: *k8s.io/apimachinery/pkg/apis/meta/v1.Time nil,
                    DeletionGracePeriodSeconds: *int64 nil,
                    Labels: map[string]string [...],
                    Annotations: map[string]string nil,
                    OwnerReferences: []k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference len: 0, cap: 0, nil,
                    Finalizers: []string len: 0, cap: 0, nil,
                    ClusterName: "",
                    ManagedFields: []k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry len: 1, cap: 1, [
                            (*"k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry")(0x40003a85a0),
                    ],},
            Spec: k8s.io/api/apps/v1.DeploymentSpec {
                    Replicas: *1,
                    Selector: *(*"k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector")(0x40001aad80),
                    Template: (*"k8s.io/api/core/v1.PodTemplateSpec")(0x40001e7a28),
                    Strategy: (*"k8s.io/api/apps/v1.DeploymentStrategy")(0x40001e7d00),
                    MinReadySeconds: 0,
                    RevisionHistoryLimit: *int32 nil,
                    Paused: false,
                    ProgressDeadlineSeconds: *int32 nil,},
            Status: k8s.io/api/apps/v1.DeploymentStatus {
                    ObservedGeneration: 0,
                    Replicas: 0,
                    UpdatedReplicas: 0,
                    ReadyReplicas: 0,
                    AvailableReplicas: 0,
                    UnavailableReplicas: 0,
                    Conditions: []k8s.io/api/apps/v1.DeploymentCondition len: 0, cap: 0, nil,
                    CollisionCount: *int32 nil,},
            WaitProbe: github.com/xwen-winnie/crd_demo/kube/apis/qbox/v1alpha1.WaitProbe {
                    Address: "192.168.1.5:30080",
                    Timeout: k8s.io/klog/v2.flushInterval (5000000000),},}
    (dlv) 

    type Waitdeployment struct

    root@ubuntu:~/crd_demo# cat  kube/apis/qbox/v1alpha1/types.go 
    package v1alpha1
    
    import (
            "time"
    
            appv1 "k8s.io/api/apps/v1"
            metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    )
    
    // +genclient
    // +k8s:openapi-gen=true
    // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
    
    // WaitDeployment customize deployment resource definition
    type Waitdeployment struct {
            metav1.TypeMeta   `json:",inline"`
            metav1.ObjectMeta `json:"metadata,omitempty"`
            Spec              appv1.DeploymentSpec   `json:"spec,omitempty"`
            Status            appv1.DeploymentStatus `json:"status,omitempty"`
            WaitProbe         WaitProbe              `json:"waitProbe,omitempty"`
    }
    
    type WaitProbe struct {
            Address string        `json:"address,omitempty"`
            Timeout time.Duration `json:"timeout,omitempty"`
    }
    
    // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
    
    type WaitdeploymentList struct {
            metav1.TypeMeta `json:",inline"`
            metav1.ListMeta `json:"metadata,omitempty"`
    
            Items []Waitdeployment `json:"items"`
    }
    root@ubuntu:~/crd_demo# cat   artifacts/waitdeployment_test.yaml
    apiVersion: qbox.io/v1alpha1
    kind: Waitdeployment
    waitProbe:
      address: 192.168.1.5:30080
      timeout: 5000000000
    metadata:
      name: wdtest
      labels:
        app: wdtest
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: wdtest
      template:
        metadata:
          labels:
            app: wdtest
        spec:
          containers:
            - name: nginx
              image: nginx:latest
              ports:
                - containerPort: 80
    root@ubuntu:~/crd_demo# kubectl create -f  artifacts/waitdeployment_test.yaml
    waitdeployment.qbox.io/wdtest created
    root@ubuntu:~/crd_demo# kubectl get pods
    NAME                            READY   STATUS    RESTARTS   AGE
    apache-app-84f76964b5-fgsc7     1/1     Running   3          27d
    apache-app-84f76964b5-kt5cx     1/1     Running   1          31d
    coffee-5f56ff9788-plfcq         1/1     Running   1          10d
    coffee-5f56ff9788-zs2f7         1/1     Running   0          10d
    example-foo-54dc4db9fc-fmsqn    1/1     Running   3          27d
    igh-agent-67d94498c6-dwtsg      1/1     Running   0          2d19h
    nginx-app-56b5bb67cc-mkfct      1/1     Running   3          27d
    nginx-app-56b5bb67cc-s9jtk      1/1     Running   1          31d
    nginx-karmada-f89759699-qcztn   1/1     Running   0          12d
    nginx-karmada-f89759699-vn47h   1/1     Running   0          12d
    tea-69c99ff568-hdcbl            1/1     Running   0          10d
    tea-69c99ff568-p59d6            1/1     Running   0          10d
    tea-69c99ff568-tm9q6            1/1     Running   0          10d
    wdtest-b9488c899-bw4sm          1/1     Running   0          51s
    web2-7cdf5dffb-26xrn            1/1     Running   3          32d
    web3-c9654466d-xwb5j            1/1     Running   3          32d
    root@ubuntu:~/crd_demo# kubectl get pods | grep wdtest
    wdtest-b9488c899-bw4sm          1/1     Running   0          69s
    root@ubuntu:~/crd_demo# kubectl delete  -f  artifacts/waitdeployment_test.yaml
    waitdeployment.qbox.io "wdtest" deleted
    root@ubuntu:~/crd_demo# cat   artifacts/waitdeployment_test.yaml
    apiVersion: qbox.io/v1alpha1
    kind: Waitdeployment
    waitProbe:
      address: 10.111.63.105:80
      timeout: 5000000000
    metadata:
      name: wdtest
      labels:
        app: wdtest
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: wdtest
      template:
        metadata:
          labels:
            app: wdtest
        spec:
          containers:
            - name: nginx
              image: nginx:latest
              ports:
                - containerPort: 80
    root@ubuntu:~/crd_demo# kubectl get svc
    NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
    apache-svc   ClusterIP   10.111.63.105    <none>        80/TCP     31d
    coffee-svc   ClusterIP   10.101.87.73     <none>        80/TCP     10d
    kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP    66d
    nginx-svc    ClusterIP   10.103.182.145   <none>        80/TCP     31d
    tea-svc      ClusterIP   10.103.138.254   <none>        80/TCP     10d
    web2         ClusterIP   10.99.87.66      <none>        8097/TCP   31d
    web3         ClusterIP   10.107.70.171    <none>        8097/TCP   31d
    root@ubuntu:~/crd_demo# 
    root@ubuntu:~/crd_demo# dlv attach 13796
    Type 'help' for list of commands.
    (dlv) b syncHandler
    Breakpoint 1 (enabled) set at 0xce8390 for github.com/xwen-winnie/crd_demo/kube.(*Client).syncHandler() ./kube/controller.go:269
    (dlv) c
    > github.com/xwen-winnie/crd_demo/kube.(*Client).syncHandler() ./kube/controller.go:269 (hits goroutine(146):1 total:1) (PC: 0xce8390)
    Warning: debugging optimized function
       264: }
       265:
       266: // syncHandler compares the actual state with the desired, and attempts to
       267: // converge the two. It then updates the Status block of the Waitdeployment
       268: // resource with the current status of the resource.
    => 269: func (c *Client) syncHandler(key string) error {
       270:         // Convert the namespace/name string into a distinct namespace and name
       271:         ns, n, err := cache.SplitMetaNamespaceKey(key)
       272:         if err != nil {
       273:                 utilRuntime.HandleError(fmt.Errorf("invalid resource key: %s", key))
       274:                 return nil
    (dlv) s
    > github.com/xwen-winnie/crd_demo/kube.(*Client).syncHandler() ./kube/controller.go:271 (PC: 0xce83a4)
    Warning: debugging optimized function
       266: // syncHandler compares the actual state with the desired, and attempts to
       267: // converge the two. It then updates the Status block of the Waitdeployment
       268: // resource with the current status of the resource.
       269: func (c *Client) syncHandler(key string) error {
       270:         // Convert the namespace/name string into a distinct namespace and name
    => 271:         ns, n, err := cache.SplitMetaNamespaceKey(key)
       272:         if err != nil {
       273:                 utilRuntime.HandleError(fmt.Errorf("invalid resource key: %s", key))
       274:                 return nil
       275:         }
       276:         waitDeployment, err := c.waitDeploymentsLister.Waitdeployments(ns).Get(n)
    (dlv) n
    > github.com/xwen-winnie/crd_demo/kube.(*Client).syncHandler() ./kube/controller.go:272 (PC: 0xce83cc)
    Warning: debugging optimized function
       267: // converge the two. It then updates the Status block of the Waitdeployment
       268: // resource with the current status of the resource.
       269: func (c *Client) syncHandler(key string) error {
       270:         // Convert the namespace/name string into a distinct namespace and name
       271:         ns, n, err := cache.SplitMetaNamespaceKey(key)
    => 272:         if err != nil {
       273:                 utilRuntime.HandleError(fmt.Errorf("invalid resource key: %s", key))
       274:                 return nil
       275:         }
       276:         waitDeployment, err := c.waitDeploymentsLister.Waitdeployments(ns).Get(n)
       277:         if err != nil {
    (dlv) n
    > github.com/xwen-winnie/crd_demo/kube.(*Client).syncHandler() ./kube/controller.go:276 (PC: 0xce83d8)
    Warning: debugging optimized function
       271:         ns, n, err := cache.SplitMetaNamespaceKey(key)
       272:         if err != nil {
       273:                 utilRuntime.HandleError(fmt.Errorf("invalid resource key: %s", key))
       274:                 return nil
       275:         }
    => 276:         waitDeployment, err := c.waitDeploymentsLister.Waitdeployments(ns).Get(n)
       277:         if err != nil {
       278:                 // The Waitdeployment resource may no longer exist, in which case we stop
       279:                 // processing.
       280:                 if errors.IsNotFound(err) {
       281:                         utilRuntime.HandleError(fmt.Errorf("Waitdeployment '%s' in work queue no longer exists", key))
    (dlv) p ns
    "default"
    (dlv) p n
    "wdtest"
    (dlv) n
    > github.com/xwen-winnie/crd_demo/kube.(*Client).syncHandler() ./kube/controller.go:277 (PC: 0xce8428)
    Warning: debugging optimized function
       272:         if err != nil {
       273:                 utilRuntime.HandleError(fmt.Errorf("invalid resource key: %s", key))
       274:                 return nil
       275:         }
       276:         waitDeployment, err := c.waitDeploymentsLister.Waitdeployments(ns).Get(n)
    => 277:         if err != nil {
       278:                 // The Waitdeployment resource may no longer exist, in which case we stop
       279:                 // processing.
       280:                 if errors.IsNotFound(err) {
       281:                         utilRuntime.HandleError(fmt.Errorf("Waitdeployment '%s' in work queue no longer exists", key))
       282:                         return nil
    (dlv) p waitDeployment
    *github.com/xwen-winnie/crd_demo/kube/apis/qbox/v1alpha1.Waitdeployment {
            TypeMeta: k8s.io/apimachinery/pkg/apis/meta/v1.TypeMeta {Kind: "", APIVersion: ""},
            ObjectMeta: k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta {
                    Name: "wdtest",
                    GenerateName: "",
                    Namespace: "default",
                    SelfLink: "/apis/qbox.io/v1alpha1/namespaces/default/waitdeployments/wdtest",
                    UID: "c9a7382f-e51e-4184-afcd-981510ee3238",
                    ResourceVersion: "16294085",
                    Generation: 1,
                    CreationTimestamp: (*"k8s.io/apimachinery/pkg/apis/meta/v1.Time")(0x400021af88),
                    DeletionTimestamp: *k8s.io/apimachinery/pkg/apis/meta/v1.Time nil,
                    DeletionGracePeriodSeconds: *int64 nil,
                    Labels: map[string]string [...],
                    Annotations: map[string]string nil,
                    OwnerReferences: []k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference len: 0, cap: 0, nil,
                    Finalizers: []string len: 0, cap: 0, nil,
                    ClusterName: "",
                    ManagedFields: []k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry len: 1, cap: 1, [
                            (*"k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry")(0x40001901e0),
                    ],},
            Spec: k8s.io/api/apps/v1.DeploymentSpec {
                    Replicas: *1,
                    Selector: *(*"k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector")(0x40007c2160),
                    Template: (*"k8s.io/api/core/v1.PodTemplateSpec")(0x400021b028),
                    Strategy: (*"k8s.io/api/apps/v1.DeploymentStrategy")(0x400021b300),
                    MinReadySeconds: 0,
                    RevisionHistoryLimit: *int32 nil,
                    Paused: false,
                    ProgressDeadlineSeconds: *int32 nil,},
            Status: k8s.io/api/apps/v1.DeploymentStatus {
                    ObservedGeneration: 0,
                    Replicas: 0,
                    UpdatedReplicas: 0,
                    ReadyReplicas: 0,
                    AvailableReplicas: 0,
                    UnavailableReplicas: 0,
                    Conditions: []k8s.io/api/apps/v1.DeploymentCondition len: 0, cap: 0, nil,
                    CollisionCount: *int32 nil,},
            WaitProbe: github.com/xwen-winnie/crd_demo/kube/apis/qbox/v1alpha1.WaitProbe {
                    Address: "10.111.63.105:80",
                    Timeout: k8s.io/klog/v2.flushInterval (5000000000),},}
    (dlv) n
    > github.com/xwen-winnie/crd_demo/kube.(*Client).syncHandler() ./kube/controller.go:287 (PC: 0xce8510)
    Warning: debugging optimized function
       282:                         return nil
       283:                 }
       284:                 return err
       285:         }
       286:
    => 287:         deployment, err := c.checkAndStartDeployment(waitDeployment)
       288:         if err != nil {
       289:                 return err
       290:         }
       291:
       292:         // If the Deployment is not controlled by this waitDeployment resource,
    (dlv) n
    > github.com/xwen-winnie/crd_demo/kube.(*Client).syncHandler() ./kube/controller.go:288 (PC: 0xce852c)
    Warning: debugging optimized function
       283:                 }
       284:                 return err
       285:         }
       286:
       287:         deployment, err := c.checkAndStartDeployment(waitDeployment)
    => 288:         if err != nil {
       289:                 return err
       290:         }
       291:
       292:         // If the Deployment is not controlled by this waitDeployment resource,
       293:         // we should log a warning to the event recorder and return error msg.
    (dlv) p  deployment
    *k8s.io/api/apps/v1.Deployment {
            TypeMeta: k8s.io/apimachinery/pkg/apis/meta/v1.TypeMeta {Kind: "", APIVersion: ""},
            ObjectMeta: k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta {
                    Name: "wdtest",
                    GenerateName: "",
                    Namespace: "default",
                    SelfLink: "/apis/apps/v1/namespaces/default/deployments/wdtest",
                    UID: "afffa50a-79c6-4d54-b8b1-f55a4a947777",
                    ResourceVersion: "16294277",
                    Generation: 1,
                    CreationTimestamp: (*"k8s.io/apimachinery/pkg/apis/meta/v1.Time")(0x4000646508),
                    DeletionTimestamp: *k8s.io/apimachinery/pkg/apis/meta/v1.Time nil,
                    DeletionGracePeriodSeconds: *int64 nil,
                    Labels: map[string]string [...],
                    Annotations: map[string]string nil,
                    OwnerReferences: []k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference len: 1, cap: 1, [
                            (*"k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference")(0x4000250050),
                    ],
                    Finalizers: []string len: 0, cap: 0, nil,
                    ClusterName: "",
                    ManagedFields: []k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry len: 1, cap: 1, [
                            (*"k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry")(0x40002500a0),
                    ],},
            Spec: k8s.io/api/apps/v1.DeploymentSpec {
                    Replicas: *1,
                    Selector: *(*"k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector")(0x40003a4400),
                    Template: (*"k8s.io/api/core/v1.PodTemplateSpec")(0x40006465a8),
                    Strategy: (*"k8s.io/api/apps/v1.DeploymentStrategy")(0x4000646880),
                    MinReadySeconds: 0,
                    RevisionHistoryLimit: *10,
                    Paused: false,
                    ProgressDeadlineSeconds: *600,},
            Status: k8s.io/api/apps/v1.DeploymentStatus {
                    ObservedGeneration: 0,
                    Replicas: 0,
                    UpdatedReplicas: 0,
                    ReadyReplicas: 0,
                    AvailableReplicas: 0,
                    UnavailableReplicas: 0,
                    Conditions: []k8s.io/api/apps/v1.DeploymentCondition len: 0, cap: 0, nil,
                    CollisionCount: *int32 nil,},}
    (dlv) n
    > github.com/xwen-winnie/crd_demo/kube.(*Client).syncHandler() ./kube/controller.go:294 (PC: 0xce8534)
    Warning: debugging optimized function
       289:                 return err
       290:         }
       291:
       292:         // If the Deployment is not controlled by this waitDeployment resource,
       293:         // we should log a warning to the event recorder and return error msg.
    => 294:         if !metav1.IsControlledBy(deployment, waitDeployment) {
       295:                 msg := fmt.Sprintf(MessageResourceExists, deployment.Name)
       296:                 c.recorder.Event(waitDeployment, coreV1.EventTypeWarning, ErrResourceExists, msg)
       297:                 return fmt.Errorf(msg)
       298:         }
       299:
    (dlv) nn
    Command failed: command not available
    (dlv) n
    > github.com/xwen-winnie/crd_demo/kube.(*Client).syncHandler() ./kube/controller.go:301 (PC: 0xce8564)
    Warning: debugging optimized function
       296:                 c.recorder.Event(waitDeployment, coreV1.EventTypeWarning, ErrResourceExists, msg)
       297:                 return fmt.Errorf(msg)
       298:         }
       299:
       300:         // update deployment
    => 301:         deployment, err = c.kubeClient.AppsV1().Deployments(waitDeployment.Namespace).Update(c.ctx, newDeployment(waitDeployment), metav1.UpdateOptions{})
       302:         if err != nil {
       303:                 return err
       304:         }
       305:
       306:         // update waitDeployment
    (dlv) p waitDeployment
    *github.com/xwen-winnie/crd_demo/kube/apis/qbox/v1alpha1.Waitdeployment {
            TypeMeta: k8s.io/apimachinery/pkg/apis/meta/v1.TypeMeta {Kind: "", APIVersion: ""},
            ObjectMeta: k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta {
                    Name: "wdtest",
                    GenerateName: "",
                    Namespace: "default",
                    SelfLink: "/apis/qbox.io/v1alpha1/namespaces/default/waitdeployments/wdtest",
                    UID: "c9a7382f-e51e-4184-afcd-981510ee3238",
                    ResourceVersion: "16294085",
                    Generation: 1,
                    CreationTimestamp: (*"k8s.io/apimachinery/pkg/apis/meta/v1.Time")(0x400021af88),
                    DeletionTimestamp: *k8s.io/apimachinery/pkg/apis/meta/v1.Time nil,
                    DeletionGracePeriodSeconds: *int64 nil,
                    Labels: map[string]string [...],
                    Annotations: map[string]string nil,
                    OwnerReferences: []k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference len: 0, cap: 0, nil,
                    Finalizers: []string len: 0, cap: 0, nil,
                    ClusterName: "",
                    ManagedFields: []k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry len: 1, cap: 1, [
                            (*"k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry")(0x40001901e0),
                    ],},
            Spec: k8s.io/api/apps/v1.DeploymentSpec {
                    Replicas: *1,
                    Selector: *(*"k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector")(0x40007c2160),
                    Template: (*"k8s.io/api/core/v1.PodTemplateSpec")(0x400021b028),
                    Strategy: (*"k8s.io/api/apps/v1.DeploymentStrategy")(0x400021b300),
                    MinReadySeconds: 0,
                    RevisionHistoryLimit: *int32 nil,
                    Paused: false,
                    ProgressDeadlineSeconds: *int32 nil,},
            Status: k8s.io/api/apps/v1.DeploymentStatus {
                    ObservedGeneration: 0,
                    Replicas: 0,
                    UpdatedReplicas: 0,
                    ReadyReplicas: 0,
                    AvailableReplicas: 0,
                    UnavailableReplicas: 0,
                    Conditions: []k8s.io/api/apps/v1.DeploymentCondition len: 0, cap: 0, nil,
                    CollisionCount: *int32 nil,},
            WaitProbe: github.com/xwen-winnie/crd_demo/kube/apis/qbox/v1alpha1.WaitProbe {
                    Address: "10.111.63.105:80",
                    Timeout: k8s.io/klog/v2.flushInterval (5000000000),},}
    (dlv) p  c.kubeClient
    k8s.io/client-go/kubernetes.Interface(*k8s.io/client-go/kubernetes.Clientset) *{
            DiscoveryClient: *k8s.io/client-go/discovery.DiscoveryClient {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,
                    LegacyPrefix: "/api",},
            admissionregistrationV1: *k8s.io/client-go/kubernetes/typed/admissionregistration/v1.AdmissionregistrationV1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            admissionregistrationV1beta1: *k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1.AdmissionregistrationV1beta1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            appsV1: *k8s.io/client-go/kubernetes/typed/apps/v1.AppsV1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            appsV1beta1: *k8s.io/client-go/kubernetes/typed/apps/v1beta1.AppsV1beta1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            appsV1beta2: *k8s.io/client-go/kubernetes/typed/apps/v1beta2.AppsV1beta2Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            authenticationV1: *k8s.io/client-go/kubernetes/typed/authentication/v1.AuthenticationV1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            authenticationV1beta1: *k8s.io/client-go/kubernetes/typed/authentication/v1beta1.AuthenticationV1beta1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            authorizationV1: *k8s.io/client-go/kubernetes/typed/authorization/v1.AuthorizationV1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            authorizationV1beta1: *k8s.io/client-go/kubernetes/typed/authorization/v1beta1.AuthorizationV1beta1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            autoscalingV1: *k8s.io/client-go/kubernetes/typed/autoscaling/v1.AutoscalingV1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            autoscalingV2beta1: *k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1.AutoscalingV2beta1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            autoscalingV2beta2: *k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2.AutoscalingV2beta2Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            batchV1: *k8s.io/client-go/kubernetes/typed/batch/v1.BatchV1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            batchV1beta1: *k8s.io/client-go/kubernetes/typed/batch/v1beta1.BatchV1beta1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            batchV2alpha1: *k8s.io/client-go/kubernetes/typed/batch/v2alpha1.BatchV2alpha1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            certificatesV1: *k8s.io/client-go/kubernetes/typed/certificates/v1.CertificatesV1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            certificatesV1beta1: *k8s.io/client-go/kubernetes/typed/certificates/v1beta1.CertificatesV1beta1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            coordinationV1beta1: *k8s.io/client-go/kubernetes/typed/coordination/v1beta1.CoordinationV1beta1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            coordinationV1: *k8s.io/client-go/kubernetes/typed/coordination/v1.CoordinationV1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            coreV1: *k8s.io/client-go/kubernetes/typed/core/v1.CoreV1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            discoveryV1alpha1: *k8s.io/client-go/kubernetes/typed/discovery/v1alpha1.DiscoveryV1alpha1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            discoveryV1beta1: *k8s.io/client-go/kubernetes/typed/discovery/v1beta1.DiscoveryV1beta1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            eventsV1: *k8s.io/client-go/kubernetes/typed/events/v1.EventsV1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            eventsV1beta1: *k8s.io/client-go/kubernetes/typed/events/v1beta1.EventsV1beta1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            extensionsV1beta1: *k8s.io/client-go/kubernetes/typed/extensions/v1beta1.ExtensionsV1beta1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            flowcontrolV1alpha1: *k8s.io/client-go/kubernetes/typed/flowcontrol/v1alpha1.FlowcontrolV1alpha1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            networkingV1: *k8s.io/client-go/kubernetes/typed/networking/v1.NetworkingV1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            networkingV1beta1: *k8s.io/client-go/kubernetes/typed/networking/v1beta1.NetworkingV1beta1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            nodeV1alpha1: *k8s.io/client-go/kubernetes/typed/node/v1alpha1.NodeV1alpha1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            nodeV1beta1: *k8s.io/client-go/kubernetes/typed/node/v1beta1.NodeV1beta1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            policyV1beta1: *k8s.io/client-go/kubernetes/typed/policy/v1beta1.PolicyV1beta1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            rbacV1: *k8s.io/client-go/kubernetes/typed/rbac/v1.RbacV1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            rbacV1beta1: *k8s.io/client-go/kubernetes/typed/rbac/v1beta1.RbacV1beta1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            rbacV1alpha1: *k8s.io/client-go/kubernetes/typed/rbac/v1alpha1.RbacV1alpha1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            schedulingV1alpha1: *k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1.SchedulingV1alpha1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            schedulingV1beta1: *k8s.io/client-go/kubernetes/typed/scheduling/v1beta1.SchedulingV1beta1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            schedulingV1: *k8s.io/client-go/kubernetes/typed/scheduling/v1.SchedulingV1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            settingsV1alpha1: *k8s.io/client-go/kubernetes/typed/settings/v1alpha1.SettingsV1alpha1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            storageV1beta1: *k8s.io/client-go/kubernetes/typed/storage/v1beta1.StorageV1beta1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            storageV1: *k8s.io/client-go/kubernetes/typed/storage/v1.StorageV1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},
            storageV1alpha1: *k8s.io/client-go/kubernetes/typed/storage/v1alpha1.StorageV1alpha1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},}
    (dlv) n
    > github.com/xwen-winnie/crd_demo/kube.(*Client).syncHandler() ./kube/controller.go:302 (PC: 0xce8628)
    Warning: debugging optimized function
       297:                 return fmt.Errorf(msg)
       298:         }
       299:
       300:         // update deployment
       301:         deployment, err = c.kubeClient.AppsV1().Deployments(waitDeployment.Namespace).Update(c.ctx, newDeployment(waitDeployment), metav1.UpdateOptions{})
    => 302:         if err != nil {
       303:                 return err
       304:         }
       305:
       306:         // update waitDeployment
       307:         _, err = c.customClient.QboxV1alpha1().Waitdeployments(waitDeployment.Namespace).Update(c.ctx, waitDeployment.DeepCopy(), metav1.UpdateOptions{})
    (dlv) n
    > github.com/xwen-winnie/crd_demo/kube.(*Client).syncHandler() ./kube/controller.go:307 (PC: 0xce862c)
    Warning: debugging optimized function
       302:         if err != nil {
       303:                 return err
       304:         }
       305:
       306:         // update waitDeployment
    => 307:         _, err = c.customClient.QboxV1alpha1().Waitdeployments(waitDeployment.Namespace).Update(c.ctx, waitDeployment.DeepCopy(), metav1.UpdateOptions{})
       308:         if err != nil {
       309:                 return err
       310:         }
       311:
       312:         c.recorder.Event(waitDeployment, coreV1.EventTypeNormal, SuccessSynced, MessageResourceSynced)
    (dlv) p c.customClient
    github.com/xwen-winnie/crd_demo/kube/client/clientset/versioned.Interface(*github.com/xwen-winnie/crd_demo/kube/client/clientset/versioned.Clientset) *{
            DiscoveryClient: *k8s.io/client-go/discovery.DiscoveryClient {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,
                    LegacyPrefix: "/api",},
            qboxV1alpha1: *github.com/xwen-winnie/crd_demo/kube/client/clientset/versioned/typed/qbox/v1alpha1.QboxV1alpha1Client {
                    restClient: k8s.io/client-go/rest.Interface(*k8s.io/client-go/rest.RESTClient) ...,},}
    (dlv) n
    > github.com/xwen-winnie/crd_demo/kube.(*Client).syncHandler() ./kube/controller.go:308 (PC: 0xce8708)
    Warning: debugging optimized function
       303:                 return err
       304:         }
       305:
       306:         // update waitDeployment
       307:         _, err = c.customClient.QboxV1alpha1().Waitdeployments(waitDeployment.Namespace).Update(c.ctx, waitDeployment.DeepCopy(), metav1.UpdateOptions{})
    => 308:         if err != nil {
       309:                 return err
       310:         }
       311:
       312:         c.recorder.Event(waitDeployment, coreV1.EventTypeNormal, SuccessSynced, MessageResourceSynced)
       313:         return nil
    (dlv) n
    > github.com/xwen-winnie/crd_demo/kube.(*Client).syncHandler() ./kube/controller.go:312 (PC: 0xce8724)
    Warning: debugging optimized function
       307:         _, err = c.customClient.QboxV1alpha1().Waitdeployments(waitDeployment.Namespace).Update(c.ctx, waitDeployment.DeepCopy(), metav1.UpdateOptions{})
       308:         if err != nil {
       309:                 return err
       310:         }
       311:
    => 312:         c.recorder.Event(waitDeployment, coreV1.EventTypeNormal, SuccessSynced, MessageResourceSynced)
       313:         return nil
       314: }
       315:
       316: func (c *Client) checkAndStartDeployment(waitDeployment *v1alpha1.Waitdeployment) (*appsV1.Deployment, error) {
       317:         err := doTCPProbe(waitDeployment.WaitProbe.Address, waitDeployment.WaitProbe.Timeout)
    (dlv) p SuccessSynced

    handleObject

    root@ubuntu:~/crd_demo# dlv attach 13796
    Type 'help' for list of commands.
    (dlv) b handleObject
    Breakpoint 1 (enabled) set at 0xce9270 for github.com/xwen-winnie/crd_demo/kube.(*Client).handleObject() ./kube/controller.go:372
    (dlv) c
    > github.com/xwen-winnie/crd_demo/kube.(*Client).handleObject() ./kube/controller.go:372 (hits goroutine(45):1 total:1) (PC: 0xce9270)
    Warning: debugging optimized function
       367: // have an appropriate OwnerReference, it will simply be skipped.
       368: //
       369: // 将任何实现 metav1.Object 的资源并尝试找到“拥有”它的 Waitdeployment 资源。
       370: // 它通过查看对象 metadata.ownerReferences 字段以获取适当的 OwnerReference 来完成此操作。
       371: // 然后将要处理的 Waitdeployment 资源加入队列。 如果对象没有合适的 OwnerReference,它将被简单地跳过。
    => 372: func (c *Client) handleObject(obj interface{}) {
       373:         object, ok := obj.(metav1.Object)
       374:         if !ok {
       375:                 tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
       376:                 if !ok {
       377:                         utilRuntime.HandleError(fmt.Errorf("error decoding object, invalid type"))
    (dlv) s
    > github.com/xwen-winnie/crd_demo/kube.(*Client).handleObject() ./kube/controller.go:373 (PC: 0xce9284)
    Warning: debugging optimized function
       368: //
       369: // 将任何实现 metav1.Object 的资源并尝试找到“拥有”它的 Waitdeployment 资源。
       370: // 它通过查看对象 metadata.ownerReferences 字段以获取适当的 OwnerReference 来完成此操作。
       371: // 然后将要处理的 Waitdeployment 资源加入队列。 如果对象没有合适的 OwnerReference,它将被简单地跳过。
       372: func (c *Client) handleObject(obj interface{}) {
    => 373:         object, ok := obj.(metav1.Object)
       374:         if !ok {
       375:                 tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
       376:                 if !ok {
       377:                         utilRuntime.HandleError(fmt.Errorf("error decoding object, invalid type"))
       378:                         return
    (dlv) n
    > github.com/xwen-winnie/crd_demo/kube.(*Client).handleObject() ./kube/controller.go:374 (PC: 0xce92b0)
    Warning: debugging optimized function
       369: // 将任何实现 metav1.Object 的资源并尝试找到“拥有”它的 Waitdeployment 资源。
       370: // 它通过查看对象 metadata.ownerReferences 字段以获取适当的 OwnerReference 来完成此操作。
       371: // 然后将要处理的 Waitdeployment 资源加入队列。 如果对象没有合适的 OwnerReference,它将被简单地跳过。
       372: func (c *Client) handleObject(obj interface{}) {
       373:         object, ok := obj.(metav1.Object)
    => 374:         if !ok {
       375:                 tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
       376:                 if !ok {
       377:                         utilRuntime.HandleError(fmt.Errorf("error decoding object, invalid type"))
       378:                         return
       379:                 }
    (dlv) p obj
    interface {}(*k8s.io/api/apps/v1.Deployment) *{
            TypeMeta: k8s.io/apimachinery/pkg/apis/meta/v1.TypeMeta {Kind: "", APIVersion: ""},
            ObjectMeta: k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta {
                    Name: "wdtest",
                    GenerateName: "",
                    Namespace: "default",
                    SelfLink: "/apis/apps/v1/namespaces/default/deployments/wdtest",
                    UID: "d5b338a4-a4a1-4cba-8984-45bc40973d86",
                    ResourceVersion: "16296642",
                    Generation: 1,
                    CreationTimestamp: (*"k8s.io/apimachinery/pkg/apis/meta/v1.Time")(0x4000b0e988),
                    DeletionTimestamp: *k8s.io/apimachinery/pkg/apis/meta/v1.Time nil,
                    DeletionGracePeriodSeconds: *int64 nil,
                    Labels: map[string]string [...],
                    Annotations: map[string]string nil,
                    OwnerReferences: []k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference len: 1, cap: 1, [
                            (*"k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference")(0x4000dbe690),
                    ],
                    Finalizers: []string len: 0, cap: 0, nil,
                    ClusterName: "",
                    ManagedFields: []k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry len: 1, cap: 1, [
                            (*"k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry")(0x4000dbe6e0),
                    ],},
            Spec: k8s.io/api/apps/v1.DeploymentSpec {
                    Replicas: *1,
                    Selector: *(*"k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector")(0x40008c0500),
                    Template: (*"k8s.io/api/core/v1.PodTemplateSpec")(0x4000b0ea28),
                    Strategy: (*"k8s.io/api/apps/v1.DeploymentStrategy")(0x4000b0ed00),
                    MinReadySeconds: 0,
                    RevisionHistoryLimit: *10,
                    Paused: false,
                    ProgressDeadlineSeconds: *600,},
            Status: k8s.io/api/apps/v1.DeploymentStatus {
                    ObservedGeneration: 0,
                    Replicas: 0,
                    UpdatedReplicas: 0,
                    ReadyReplicas: 0,
                    AvailableReplicas: 0,
                    UnavailableReplicas: 0,
                    Conditions: []k8s.io/api/apps/v1.DeploymentCondition len: 0, cap: 0, nil,
                    CollisionCount: *int32 nil,},}
    (dlv) n
    > github.com/xwen-winnie/crd_demo/kube.(*Client).handleObject() ./kube/controller.go:388 (PC: 0xce92b4)
    Warning: debugging optimized function
       383:                         return
       384:                 }
       385:                 klog.V(4).Infof("Recovered deleted object '%s' from tombstone", object.GetName())
       386:         }
       387:
    => 388:         klog.V(4).Infof("Processing object: %s", object.GetName())
       389:         if ownerRef := metav1.GetControllerOf(object); ownerRef != nil {
       390:                 // If this object is not owned by a Foo, we should not do anything more with it.
       391:                 if ownerRef.Kind != WaitdeploymentKind {
       392:                         return
       393:                 }
    (dlv) n
    > github.com/xwen-winnie/crd_demo/kube.(*Client).handleObject() ./kube/controller.go:389 (PC: 0xce936c)
    Warning: debugging optimized function
       384:                 }
       385:                 klog.V(4).Infof("Recovered deleted object '%s' from tombstone", object.GetName())
       386:         }
       387:
       388:         klog.V(4).Infof("Processing object: %s", object.GetName())
    => 389:         if ownerRef := metav1.GetControllerOf(object); ownerRef != nil {
       390:                 // If this object is not owned by a Foo, we should not do anything more with it.
       391:                 if ownerRef.Kind != WaitdeploymentKind {
       392:                         return
       393:                 }
       394:                 waitDeployment, err := c.waitDeploymentsLister.Waitdeployments(object.GetNamespace()).Get(ownerRef.Name)
    (dlv) n
    > github.com/xwen-winnie/crd_demo/kube.(*Client).handleObject() ./kube/controller.go:391 (PC: 0xce9370)
    Warning: debugging optimized function
       386:         }
       387:
       388:         klog.V(4).Infof("Processing object: %s", object.GetName())
       389:         if ownerRef := metav1.GetControllerOf(object); ownerRef != nil {
       390:                 // If this object is not owned by a Foo, we should not do anything more with it.
    => 391:                 if ownerRef.Kind != WaitdeploymentKind {
       392:                         return
       393:                 }
       394:                 waitDeployment, err := c.waitDeploymentsLister.Waitdeployments(object.GetNamespace()).Get(ownerRef.Name)
       395:                 if err != nil {
       396:                         klog.V(4).Infof("ignoring orphaned object '%s' of foo '%s'", object.GetSelfLink(), ownerRef.Name)
    (dlv) p  ownerRef
    *k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference {
            APIVersion: "qbox.io/v1alpha1",
            Kind: "Waitdeployment",
            Name: "wdtest",
            UID: "a09005c9-b27c-48f1-a1a0-0358ee14c659",
            Controller: *true,
            BlockOwnerDeletion: *true,}
    (dlv) n
    > github.com/xwen-winnie/crd_demo/kube.(*Client).handleObject() ./kube/controller.go:394 (PC: 0xce93d4)
    Warning: debugging optimized function
       389:         if ownerRef := metav1.GetControllerOf(object); ownerRef != nil {
       390:                 // If this object is not owned by a Foo, we should not do anything more with it.
       391:                 if ownerRef.Kind != WaitdeploymentKind {
       392:                         return
       393:                 }
    => 394:                 waitDeployment, err := c.waitDeploymentsLister.Waitdeployments(object.GetNamespace()).Get(ownerRef.Name)
       395:                 if err != nil {
       396:                         klog.V(4).Infof("ignoring orphaned object '%s' of foo '%s'", object.GetSelfLink(), ownerRef.Name)
       397:                         return
       398:                 }
       399:                 c.enqueueWaitdeployment(waitDeployment)
    (dlv) n
    > github.com/xwen-winnie/crd_demo/kube.(*Client).handleObject() ./kube/controller.go:395 (PC: 0xce9430)
    Warning: debugging optimized function
       390:                 // If this object is not owned by a Foo, we should not do anything more with it.
       391:                 if ownerRef.Kind != WaitdeploymentKind {
       392:                         return
       393:                 }
       394:                 waitDeployment, err := c.waitDeploymentsLister.Waitdeployments(object.GetNamespace()).Get(ownerRef.Name)
    => 395:                 if err != nil {
       396:                         klog.V(4).Infof("ignoring orphaned object '%s' of foo '%s'", object.GetSelfLink(), ownerRef.Name)
       397:                         return
       398:                 }
       399:                 c.enqueueWaitdeployment(waitDeployment)
       400:                 return
    (dlv) n
    > github.com/xwen-winnie/crd_demo/kube.(*Client).handleObject() ./kube/controller.go:399 (PC: 0xce9510)
    Warning: debugging optimized function
       394:                 waitDeployment, err := c.waitDeploymentsLister.Waitdeployments(object.GetNamespace()).Get(ownerRef.Name)
       395:                 if err != nil {
       396:                         klog.V(4).Infof("ignoring orphaned object '%s' of foo '%s'", object.GetSelfLink(), ownerRef.Name)
       397:                         return
       398:                 }
    => 399:                 c.enqueueWaitdeployment(waitDeployment)
       400:                 return
       401:         }
       402: }
       403:
       404: // enqueueWaitdeployment takes a enqueueWaitdeployment resource and converts
    (dlv) b syncHandler

    handleObject syncHandler

    (dlv) c
    > github.com/xwen-winnie/crd_demo/kube.(*Client).handleObject() ./kube/controller.go:372 (hits goroutine(45):5 total:5) (PC: 0xce9270)
    Warning: debugging optimized function
    > github.com/xwen-winnie/crd_demo/kube.(*Client).syncHandler() ./kube/controller.go:269 (hits goroutine(146):1 total:1) (PC: 0xce8390)
    Warning: debugging optimized function
       264: }
       265:
       266: // syncHandler compares the actual state with the desired, and attempts to
       267: // converge the two. It then updates the Status block of the Waitdeployment
       268: // resource with the current status of the resource.
    => 269: func (c *Client) syncHandler(key string) error {
       270:         // Convert the namespace/name string into a distinct namespace and name
       271:         ns, n, err := cache.SplitMetaNamespaceKey(key)
       272:         if err != nil {
       273:                 utilRuntime.HandleError(fmt.Errorf("invalid resource key: %s", key))
       274:                 return nil
    (dlv) 

     Create  Deployment

    func (c *Client) checkAndStartDeployment(waitDeployment *v1alpha1.Waitdeployment) (*appsV1.Deployment, error) {
            err := doTCPProbe(waitDeployment.WaitProbe.Address, waitDeployment.WaitProbe.Timeout)
            if err != nil {
                    return nil, err
            }
            deployment, err := c.deploymentsLister.Deployments(waitDeployment.Namespace).Get(waitDeployment.Name)
            if errors.IsNotFound(err) {
                    klog.Infof("Waitdeployment not exist, create a new deployment %s in namespace %s", waitDeployment.Name, waitDeployment.Namespace)
                    deployment, err = c.kubeClient.AppsV1().Deployments(waitDeployment.Namespace).Create(c.ctx, newDeployment(waitDeployment), metav1.CreateOptions{})
            }
            if err != nil {
                    return nil, err
            }
            return deployment, nil
    }
    (dlv) n
    > github.com/xwen-winnie/crd_demo/kube.(*Client).syncHandler() ./kube/controller.go:288 (PC: 0xce852c)
    Warning: debugging optimized function
       283:                 }
       284:                 return err
       285:         }
       286:
       287:         deployment, err := c.checkAndStartDeployment(waitDeployment)
    => 288:         if err != nil {
       289:                 return err
       290:         }
       291:
       292:         // If the Deployment is not controlled by this waitDeployment resource,
       293:         // we should log a warning to the event recorder and return error msg.
    (dlv) p deployment
    *k8s.io/api/apps/v1.Deployment {
            TypeMeta: k8s.io/apimachinery/pkg/apis/meta/v1.TypeMeta {Kind: "", APIVersion: ""},
            ObjectMeta: k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta {
                    Name: "wdtest",
                    GenerateName: "",
                    Namespace: "default",
                    SelfLink: "/apis/apps/v1/namespaces/default/deployments/wdtest",
                    UID: "621ef39c-3863-4f39-9e6e-2a3370d096e8",
                    ResourceVersion: "16739532",
                    Generation: 1,
                    CreationTimestamp: (*"k8s.io/apimachinery/pkg/apis/meta/v1.Time")(0x400071a088),
                    DeletionTimestamp: *k8s.io/apimachinery/pkg/apis/meta/v1.Time nil,
                    DeletionGracePeriodSeconds: *int64 nil,
                    Labels: map[string]string [...],
                    Annotations: map[string]string nil,
                    OwnerReferences: []k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference len: 1, cap: 1, [
                            (*"k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference")(0x40009f4000),
                    ],
                    Finalizers: []string len: 0, cap: 0, nil,
                    ClusterName: "",
                    ManagedFields: []k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry len: 1, cap: 1, [
                            (*"k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry")(0x40009f4050),
                    ],},
            Spec: k8s.io/api/apps/v1.DeploymentSpec {
                    Replicas: *1,
                    Selector: *(*"k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector")(0x400058e3a0),
                    Template: (*"k8s.io/api/core/v1.PodTemplateSpec")(0x400071a128),
                    Strategy: (*"k8s.io/api/apps/v1.DeploymentStrategy")(0x400071a400),
                    MinReadySeconds: 0,
                    RevisionHistoryLimit: *10,
                    Paused: false,
                    ProgressDeadlineSeconds: *600,},
            Status: k8s.io/api/apps/v1.DeploymentStatus {
                    ObservedGeneration: 0,
                    Replicas: 0,
                    UpdatedReplicas: 0,
                    ReadyReplicas: 0,
                    AvailableReplicas: 0,
                    UnavailableReplicas: 0,
                    Conditions: []k8s.io/api/apps/v1.DeploymentCondition len: 0, cap: 0, nil,
                    CollisionCount: *int32 nil,},}
    (dlv) 

    获取 k8s node 节点信息

    //获取NODE
    fmt.Println("####### 获取node ######")
    nodes, err := clientset.CoreV1().Nodes().List(metav1.ListOptions{})
    if err != nil {
        panic(err)
    }
    for _,nds := range nodes.Items {
        fmt.Printf("NodeName: %s
    ", nds.Name)
    }
    
    //获取 指定NODE 的详细信息
    fmt.Println("
     ####### node详细信息 ######")
    nodeName := "k8s-master2"
    nodeRel, err := clientset.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{})
    if err != nil {
        panic(err)
    }
    fmt.Printf("Name: %s 
    ", nodeRel.Name)
    fmt.Printf("CreateTime: %s 
    ", nodeRel.CreationTimestamp)
    fmt.Printf("NowTime: %s 
    ", nodeRel.Status.Conditions[0].LastHeartbeatTime)
    fmt.Printf("kernelVersion: %s 
    ", nodeRel.Status.NodeInfo.KernelVersion)
    fmt.Printf("SystemOs: %s 
    ", nodeRel.Status.NodeInfo.OSImage)
    fmt.Printf("Cpu: %s 
    ", nodeRel.Status.Capacity.Cpu())
    fmt.Printf("docker: %s 
    ", nodeRel.Status.NodeInfo.ContainerRuntimeVersion)
    // fmt.Printf("Status: %s 
    ", nodeRel.Status.Conditions[len(nodes.Items[0].Status.Conditions)-1].Type)
    fmt.Printf("Status: %s 
    ", nodeRel.Status.Conditions[len(nodeRel.Status.Conditions)-1].Type)
    fmt.Printf("Mem: %s 
    ", nodeRel.Status.Allocatable.Memory().String())
     

    Creating your own admission controller

  • 相关阅读:
    JS自定义功能函数实现动态添加网址参数修改网址参数值
    伍、ajax
    类的静态方法(函数)中为什么不能调用非静态成员(属性)?
    android 数据存储 SharePreferences 简单使用
    实现多线程的方式
    线程、进程概念与Android系统组件的关系
    通知—Notifications
    活动栏—Action Bar
    Android菜单—Menu
    对话框控件—Dialog
  • 原文地址:https://www.cnblogs.com/dream397/p/15177061.html
Copyright © 2020-2023  润新知