当前位置:网站首页>Analyse approfondie de kubebuilder

Analyse approfondie de kubebuilder

2022-07-07 04:45:00 Chenxy02

Site Web de référence:​​​​​​​​​​​​​​​​​

​​​​​​Introduction - The Kubebuilder Book

Analyse approfondie Kubebuilder:Écrire CRD Plus simple - Oui.

​​​​​​​Guide de lecture

Recommandez à vos amis de lire les documents officiels ci - dessus.

J'ai envoyé un blog. Basé surKubebuilderDéveloppementOperator(Démarrer avec)_chenxy02Blog de-CSDNBlogs C'est noté.kubebuilderPour commencer à utiliser, Cet article vise à approfondir la compréhension kubebuilderShenzhen,En savoir pluskubebuilderCode du projet pour.

Concept de base

GVKs & GVRs

GVK = GroupVersionKind,GVR = GroupVersionResource

  • Pendant le codage, Les données sur les ressources sont stockées dans une structure (AppeléGo type)
    • En raison de plusieurs versions versionL'existence de(alpha1, beta1, v1Attendez.), Il existe des différences dans les structures de stockage entre les versions , Mais nous lui donnerons tous le même KindNom(Par exemple,Deployment)
    • Donc,, On encode SeulementkindNom(Par exemple:Deployment), Il n'est pas possible d'obtenir exactement quelle version de la structure elle utilise
    • Alors...,AdoptionGVK Obtenir une structure de stockage spécifique ,C'est - à - direGVKTrois messages pour(group/version/kind)Identifier unGo type(Structure)

Comment obtenir? —— AdoptionScheme,SchemeStockéGVKEtGo TypeRelations cartographiques pour

  • Lors de la création de la ressource ,Nous avons écrityaml,Soumettre une demande:
    • CompilationyamlEn cours, Nous écrirons apiversionEtkind,En fait, c'estGVK
    • Et le client(C'est nous.)AvecapiserverLa correspondance esthttpForme, Envoyer une demande à un http path

Envoyé à quelhttp pathEt alors??——C'esthttp pathEn fait, c'estGVR

  • /apis/batch/v1/nampspaces/default/job C'est ce que ça veut dire.default De l'espace de nomsjobRessources
  • Nous kubectl get po Heure C'est aussi le chemin demandé ,Aussi appeléGVR
  • En fait... GVR C'est par GVK Ça se transforme en —— AdoptionRESTCartographiéRESTMappersRéalisation

Scheme

Chaque groupeControllersJ'en ai besoin.Scheme,OffreKindsCorrespondantGo typesCartographie de,C'est - à - direCompte tenu deGo type Je savais que GVK,Compte tenu deGVK Je savais que Go type

Manager

Kubebuilder Éléments de base,Avec 3 Responsabilités:

  • Responsable de l'exécution de tous les Controllers;

  • Initialiser le partage cashes,ContientlistAndWathFonction

  • InitialisationclientsPourApi ServerCommunications

Cache

KubebuilderÉléments de base,Responsable deController Le processus est basé sur SchemeSynchroniserApi Server Tout ce qui ControllerAttention.GVKsDeGVRs,Son cœur estGVK->InformerCartographie de,Informer Sera responsable de l'écoute des correspondances GVKDeGVRsCréation de/Supprimer/Opération de mise à jour,Pour déclencherControllerDeReconcileLogique.

Controller

Kubebuilder Fichiers d'échafaudage générés pour nous ,Nous devons juste réaliserReconcileLa méthode est juste.

Clients

En cours de réalisationController Il est in évitable de créer certains types de ressources /Supprimer/Mise à jour,C'est par là.ClientsRéalisé, Où la fonction de requête la requête réelle est locale Cache, Accès direct aux opérations d'écriture Api Server

Index

Parce queController Oui, souvent. CacheEffectuer une requête,KubebuilderOffreIndex utilityVoilà.Cache L'efficacité de la requête est améliorée .

Finalizer

En général, Si la ressource est supprimée , Bien que nous puissions déclencher un événement de suppression , Mais cette fois, depuis Cache Impossible de lire les informations sur les objets supprimés à l'intérieur ,C'est comme ça., Cela a entraîné de nombreux travaux de nettoyage des ordures qui n'ont pas pu être effectués en raison d'une information insuffisante .

K8sDeFinalizer Les champs sont utilisés pour traiter cette situation .InK8sMoyenne,Tant que l'objetObjectMetaÀ l'intérieur.FinalizersPas vide,Pour cet objetdelete L'opération se transforme en updateFonctionnement,Plus précisémentupdate deletionTimestampChamp, Ça veut dire dire K8sDeGC“IndeletionTimestamp Après ce moment ,Tant queFinalizerVide, Supprimer immédiatement l'objet “.

Donc, l'utilisation générale de la pose est de créer un objet avec Finalizers C'est réglé.(Arbitraire string),Et on s'en occupe. DeletionTimestamp Pas vide update Fonctionnement(En fait, delete),Selon Finalizers La valeur de a fait tout pre-delete hook(Vous pouvez maintenant Cache Toute information lue à l'intérieur de l'objet supprimé )Après Finalizers Laisser en blanc.

OwnerReference

k8s GC Lors de la suppression d'un objet ,N'importe quoiownerReference Les objets qui sont cet objet sont effacés ,En même temps,,kubebuilder Prise en charge des modifications pour tous les objets DéclencheOwnerObjetcontrollerDeReconcileMéthodes.

Tous les concepts sont regroupés comme suit :

Lecture du code source

Le code suivant provient de  Basé surKubebuilderDéveloppementOperator(Démarrer avec)_chenxy02Blog de-CSDNBlogs

Demain.goC'est parti.

Kubebuilder Créé main.go Est l'entrée de l'ensemble du projet,La logique est simple.:

var (
	scheme   = runtime.NewScheme()
	setupLog = ctrl.Log.WithName("setup")
)

func init() {
	utilruntime.Must(clientgoscheme.AddToScheme(scheme))
	utilruntime.Must(webappv1.AddToScheme(scheme))
	//+kubebuilder:scaffold:scheme
}

func main() {
    ...	

	// 1、Manager
	mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
		Scheme:                 scheme,
		MetricsBindAddress:     metricsAddr,
		Port:                   9443,
		HealthProbeBindAddress: probeAddr,
		LeaderElection:         enableLeaderElection,
		LeaderElectionID:       "ecaf1259.my.domain",
	})
    ...

	// 2、init Reconciler(Controller)
	if err = (&controllers.GuestbookReconciler{
		Client: mgr.GetClient(),
		Scheme: mgr.GetScheme(),
	}).SetupWithManager(mgr); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "Guestbook")
		os.Exit(1)
	}
    ...	

	
	// 3、start Manager
	setupLog.Info("starting manager")
	if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
		setupLog.Error(err, "problem running manager")
		os.Exit(1)
	}
}

Comme vous pouvez le voir dans init Dans la méthode, nous allons webappv1Inscrivez - vous àSchemeC'est parti.,C'est comme ça.CacheJe le savais.watchQui est - ce?,main La logique de la méthode est essentiellement ManagerDe:

  1. Initialisé unManager;
  2. Oui.Manager De Client Transmis à Controller,Et appelle SetupWithManager Méthode entrée Manager En cours Controller Initialisation de;
  3. DémarrageManager

ManagerInitialisation

ManagerLe Code d'initialisation est le suivant:

// New returns a new Manager for creating Controllers.
func New(config *rest.Config, options Options) (Manager, error) {
	// Set default values for options fields
	options = setOptionsDefaults(options)

	cluster, err := cluster.New(config, func(clusterOptions *cluster.Options) {
		clusterOptions.Scheme = options.Scheme
		clusterOptions.MapperProvider = options.MapperProvider
		clusterOptions.Logger = options.Logger
		clusterOptions.SyncPeriod = options.SyncPeriod
		clusterOptions.Namespace = options.Namespace
		clusterOptions.NewCache = options.NewCache
		clusterOptions.ClientBuilder = options.ClientBuilder
		clusterOptions.ClientDisableCacheFor = options.ClientDisableCacheFor
		clusterOptions.DryRunClient = options.DryRunClient
		clusterOptions.EventBroadcaster = options.EventBroadcaster
	})
	
    ...    

	return &controllerManager{
		cluster:                 cluster,
		recorderProvider:        recorderProvider,
		resourceLock:            resourceLock,
		metricsListener:         metricsListener,
		metricsExtraHandlers:    metricsExtraHandlers,
		logger:                  options.Logger,
		elected:                 make(chan struct{}),
		port:                    options.Port,
		host:                    options.Host,
		certDir:                 options.CertDir,
		leaseDuration:           *options.LeaseDuration,
		renewDeadline:           *options.RenewDeadline,
		retryPeriod:             *options.RetryPeriod,
		healthProbeListener:     healthProbeListener,
		readinessEndpointName:   options.ReadinessEndpointName,
		livenessEndpointName:    options.LivenessEndpointName,
		gracefulShutdownTimeout: *options.GracefulShutdownTimeout,
		internalProceduresStop:  make(chan struct{}),
		leaderElectionStopped:   make(chan struct{}),
	}, nil
}

Je vois. Principalement pour créerCacheAvecClients Attends, attends un exemple :

CréationCache

Cache Le Code d'initialisation est le suivant:

// New initializes and returns a new Cache.
func New(config *rest.Config, opts Options) (Cache, error) {
	opts, err := defaultOpts(config, opts)
	if err != nil {
		return nil, err
	}
	im := internal.NewInformersMap(config, opts.Scheme, opts.Mapper, *opts.Resync, opts.Namespace)
	return &informerCache{InformersMap: im}, nil
}

// NewInformersMap creates a new InformersMap that can create informers for
// both structured and unstructured objects.
func NewInformersMap(config *rest.Config,
	scheme *runtime.Scheme,
	mapper meta.RESTMapper,
	resync time.Duration,
	namespace string) *InformersMap {

	return &InformersMap{
		structured:   newStructuredInformersMap(config, scheme, mapper, resync, namespace),
		unstructured: newUnstructuredInformersMap(config, scheme, mapper, resync, namespace),
		metadata:     newMetadataInformersMap(config, scheme, mapper, resync, namespace),

		Scheme: scheme,
	}
}

Je vois.CachePrincipalement crééInformersMap, Scheme Chacun à l'intérieur GVK Les deux ont créé des Informer,Adoption informersByGVK C'est map Fais - le. GVK À Informer Cartographie de,ChaqueInformerSera basé surListWatch La paire de fonctions correspond à GVKEn coursListEtWatch.

Création Clients

Création Clients C'est simple.:

// defaultNewClient creates the default caching client
func defaultNewClient(cache cache.Cache, config *rest.Config, options client.Options) (client.Client, error) {
    // Create the Client for Write operations.
    c, err := client.New(config, options)
    if err != nil {
        return nil, err
    }
    return &client.DelegatingClient{
        Reader: &client.DelegatingReader{
            CacheReader:  cache,
            ClientReader: c,
        },
        Writer:       c,
        StatusClient: c,
    }, nil
}

L'opération de lecture utilise Cache,Les opérations d'écriture utilisent K8s go-client Connexion directe.

ControllerInitialisation

Regarde par là.ControllerDémarrage:

// SetupWithManager sets up the controller with the Manager.
func (r *GuestbookReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&webappv1.Guestbook{}).
		Complete(r)
}

En service BuilderMode,NewControllerManagerByEtFor Toutes les méthodes sont données à BuilderPasser le ginseng, Le plus important est la dernière méthode Complete,La logique est:

func (blder *Builder) Build(r reconcile.Reconciler) (manager.Manager, error) {
...
    // Set the Manager
    if err := blder.doManager(); err != nil {
        return nil, err
    }
    // Set the ControllerManagedBy
    if err := blder.doController(r); err != nil {
        return nil, err
    }
    // Set the Watch
    if err := blder.doWatch(); err != nil {
        return nil, err
    }
...
    return blder.mgr, nil
}

Surtout pour voirdoControllerEtdoWatchMéthodes:

doControllerMéthodes

func NewUnmanaged(name string, mgr manager.Manager, options Options) (Controller, error) {
    ...

	// Inject dependencies into Reconciler
	if err := mgr.SetFields(options.Reconciler); err != nil {
		return nil, err
	}

	// Create controller with dependencies set
	return &controller.Controller{
		Do: options.Reconciler,
		MakeQueue: func() workqueue.RateLimitingInterface {
			return workqueue.NewNamedRateLimitingQueue(options.RateLimiter, name)
		},
		MaxConcurrentReconciles: options.MaxConcurrentReconciles,
		CacheSyncTimeout:        options.CacheSyncTimeout,
		SetFields:               mgr.SetFields,
		Name:                    name,
		Log:                     options.Log.WithName("controller").WithName(name),
	}, nil
}

La méthode initialise un Controller, Quelques paramètres importants sont passés :

  • Do: Reconcile Logique;
  • Cache:Cherche.InformerInscriptionWatch
  • Queue:WatchRessourcesCUDCache d'événements

doWatchMéthodes

func (blder *Builder) doWatch() error {
	// Reconcile type
	typeForSrc, err := blder.project(blder.forInput.object, blder.forInput.objectProjection)
	if err != nil {
		return err
	}
	src := &source.Kind{Type: typeForSrc}
	hdler := &handler.EnqueueRequestForObject{}
	allPredicates := append(blder.globalPredicates, blder.forInput.predicates...)
	if err := blder.ctrl.Watch(src, hdler, allPredicates...); err != nil {
		return err
	}

	// Watches the managed types
	for _, own := range blder.ownsInput {
		typeForSrc, err := blder.project(own.object, own.objectProjection)
		if err != nil {
			return err
		}
		src := &source.Kind{Type: typeForSrc}
		hdler := &handler.EnqueueRequestForOwner{
			OwnerType:    blder.forInput.object,
			IsController: true,
		}
		allPredicates := append([]predicate.Predicate(nil), blder.globalPredicates...)
		allPredicates = append(allPredicates, own.predicates...)
		if err := blder.ctrl.Watch(src, hdler, allPredicates...); err != nil {
			return err
		}
	}

	// Do the watch requests
	for _, w := range blder.watchesInput {
		allPredicates := append([]predicate.Predicate(nil), blder.globalPredicates...)
		allPredicates = append(allPredicates, w.predicates...)

		// If the source of this watch is of type *source.Kind, project it.
		if srckind, ok := w.src.(*source.Kind); ok {
			typeForSrc, err := blder.project(srckind.Type, w.objectProjection)
			if err != nil {
				return err
			}
			srckind.Type = typeForSrc
		}

		if err := blder.ctrl.Watch(w.src, w.eventhandler, allPredicates...); err != nil {
			return err
		}
	}
	return nil
}

Vous pouvez voir la méthode à Ben ControllerResponsableCRDC'est fait.watch, Et en même temps watchBenCRD Autres ressources gérées ,C'estmanagedObjectsPeut passerControllerInitialisationBuilderDeOwnsMéthode entrée,En parlant deWatch Nous nous préoccupons de deux logiques :

1、Enregistréhandler

type EnqueueRequestForObject struct{}
// Create implements EventHandler
func (e *EnqueueRequestForObject) Create(evt event.CreateEvent, q workqueue.RateLimitingInterface) {
        ...
    q.Add(reconcile.Request{NamespacedName: types.NamespacedName{
        Name:      evt.Meta.GetName(),
        Namespace: evt.Meta.GetNamespace(),
    }})
}
// Update implements EventHandler
func (e *EnqueueRequestForObject) Update(evt event.UpdateEvent, q workqueue.RateLimitingInterface) {
    if evt.MetaOld != nil {
        q.Add(reconcile.Request{NamespacedName: types.NamespacedName{
            Name:      evt.MetaOld.GetName(),
            Namespace: evt.MetaOld.GetNamespace(),
        }})
    } else {
        enqueueLog.Error(nil, "UpdateEvent received with no old metadata", "event", evt)
    }
    if evt.MetaNew != nil {
        q.Add(reconcile.Request{NamespacedName: types.NamespacedName{
            Name:      evt.MetaNew.GetName(),
            Namespace: evt.MetaNew.GetNamespace(),
        }})
    } else {
        enqueueLog.Error(nil, "UpdateEvent received with no new metadata", "event", evt)
    }
}
// Delete implements EventHandler
func (e *EnqueueRequestForObject) Delete(evt event.DeleteEvent, q workqueue.RateLimitingInterface) {
        ...
    q.Add(reconcile.Request{NamespacedName: types.NamespacedName{
        Name:      evt.Meta.GetName(),
        Namespace: evt.Meta.GetNamespace(),
    }})
}

Je vois.Kubebuilder Pour les Handler C'est l'objet qui va changer NamespacedNameDans la file d'attente,Si dansReconcile La logique exige un jugement pour créer /Mise à jour/Supprimer, Il faut avoir sa propre logique de jugement .

2、Processus d'inscription

// Watch implements controller.Controller
func (c *Controller) Watch(src source.Source, evthdler handler.EventHandler, prct ...predicate.Predicate) error {
    ...
    log.Info("Starting EventSource", "controller", c.Name, "source", src)
    return src.Start(evthdler, c.Queue, prct...)
}
// Start is internal and should be called only by the Controller to register an EventHandler with the Informer
// to enqueue reconcile.Requests.
func (is *Informer) Start(handler handler.EventHandler, queue workqueue.RateLimitingInterface,
    ...
    is.Informer.AddEventHandler(internal.EventHandler{Queue: queue, EventHandler: handler, Predicates: prct})
    return nil
}

La nôtre.Handler Inscription physique à InformerLà - haut, Pour que toute la logique s'enchaîne ,AdoptionCache Nous avons créé tous les SchemeÀ l'intérieurGVKsDeInformers,Et ça correspond.GVKDeControllerEnregistréWatch HandlerTo the correspondingInformer, Ça correspond à GVK Tout changement dans les ressources à l'intérieur déclenchera Handler, Écrivez l'événement de changement à ControllerDans la file d'attente des événements pour, Après ça, notre ReconcileMéthodes.​​​​​​​

原网站

版权声明
本文为[Chenxy02]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/188/202207062213253142.html

随机推荐