ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • K8S Operator indexer 사용해서 성능 높이기
    K8S 2022. 12. 19. 18:00

     

     

    operator를 만들 때, 만약 controller가 어떤 오브젝트의 특정 필드를 자주 찾아야 한다면

    go client의 인덱싱을 이용하여 성능을 높일 수 있다. 

     

    go client는 제일 처음 object를 List를 한번 해오고 이후부터는 watch를 통해서 변화를 감지한다. 

    이후 shared index informer에서 watch해서 변화된 내용을 work queue에 담아 controller로 보낸 후, 

    controller의 reconcile loop에서 비즈니스 로직이 수행된다. 

    shared index informer에는 캐시를 저장하는데 이는 reconcile 루프에서 indexer를 통해서 접근할 수 있다. 

    여기서 indexer는 특정 키를 기준으로 캐시에 index를 할 수있게 만든 것이다. 

     

    예시 1) 

    다음 컨트롤러에서는 job 오브젝트의 .metadata.controller 필드로 자주 찾아야 하는 경우다.

    이때 go client의 인덱싱을 이용하여 성능을 높일 수 있다.

    func (r *CronJobReconciler) SetupWithManager(mgr ctrl.Manager) error {
    	// set up a real clock, since we're not in a test
    	if r.Clock == nil {
    		r.Clock = realClock{}
    	}
    	
        // indexer를 가져와서 필드 index를 만드는 부분
    	if err := mgr.GetFieldIndexer().IndexField(context.Background(), &kbatch.Job{}, ".metadata.controller", func(rawObj client.Object) []string {
    		// grab the job object, extract the owner...
    		job := rawObj.(*kbatch.Job)
    		owner := metav1.GetControllerOf(job)
    		if owner == nil {
    			return nil
    		}
    		// ...make sure it's a CronJob...
    		if owner.APIVersion != apiGVStr || owner.Kind != "CronJob" {
    			return nil
    		}
    
    		// ...and if so, return it
    		return []string{owner.Name}
    	}); err != nil {
    		return err
    	}
    
    	return ctrl.NewControllerManagedBy(mgr).
    		For(&batchv1.CronJob{}).
    		Owns(&kbatch.Job{}).
    		Complete(r)
    }

     

     

    다음은 인덱스로 지정해놓은 label을 가지고 job을 List하는 부분이다. 

    if err := r.List(ctx, &childJobs, client.InNamespace(req.Namespace), client.MatchingFields{jobOwnerKey: req.Name}); err != nil {
    	log.Error(err, "unable to list child Jobs")
    	return ctrl.Result{}, err
    }

     

     

    예시 2)

    다음은 configmap이 바뀌면 알아서 최신 deployment로 갱신해주는 configDeployment 오퍼레이터라는 예시를 가져온 것이다. 

    configDeployment의 spec.configMap을 자주 찾아야 하므로 해당 필드를 indexing할 것이다. 

    const (
        configMapField = ".spec.configMap"
    )

     

    SetupWithManager 함수에서 다음과 같이 configDeployment의 .spec.configMap 필드에 대해 인덱스를 만든다. 

        if err := mgr.GetFieldIndexer().IndexField(context.Background(), &appsv1.ConfigDeployment{}, configMapField, func(rawObj client.Object) []string {
            // Extract the ConfigMap name from the ConfigDeployment Spec, if one is provided
            configDeployment := rawObj.(*appsv1.ConfigDeployment)
            if configDeployment.Spec.ConfigMap == "" {
                return nil
            }
            return []string{configDeployment.Spec.ConfigMap}
        }); err != nil {
            return err
        }

     

    다음은 configmap을 watch하여 configmap에 변화가 생기면 findObjectForConfigMap 함수에서 configmap을 참조하는 configDeployment를 찾아 request로 만들어 configDeployment queue에 넣는 코드다. 

        return ctrl.NewControllerManagedBy(mgr).
            For(&appsv1.ConfigDeployment{}).
            Owns(&kapps.Deployment{}).
            Watches(
                &source.Kind{Type: &corev1.ConfigMap{}},
                handler.EnqueueRequestsFromMapFunc(r.findObjectsForConfigMap),
                builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
            ).
            Complete(r)
    }

     

    findObjectForConfigMap 함수는 이렇게 생겼다.

    위에서 만들어놓은 인덱스를 통해 configDeployment를 효율적으로 찾은 후, 찾은 configDeployment를 queue에 넣는다.  

    func (r *ConfigDeploymentReconciler) findObjectsForConfigMap(configMap client.Object) []reconcile.Request {
        attachedConfigDeployments := &appsv1.ConfigDeploymentList{}
        listOps := &client.ListOptions{
            FieldSelector: fields.OneTermEqualSelector(configMapField, configMap.GetName()),
            Namespace:     configMap.GetNamespace(),
        }
        err := r.List(context.TODO(), attachedConfigDeployments, listOps)
        if err != nil {
            return []reconcile.Request{}
        }
    
        requests := make([]reconcile.Request, len(attachedConfigDeployments.Items))
        for i, item := range attachedConfigDeployments.Items {
            requests[i] = reconcile.Request{
                NamespacedName: types.NamespacedName{
                    Name:      item.GetName(),
                    Namespace: item.GetNamespace(),
                },
            }
        }
        return requests
    }

     

     

    참고로 나의 어림짐작도 어느정도 들어가 있기 때문에 틀릴 수 있다. 

     

    Reference

    https://book.kubebuilder.io/reference/watching-resources/externally-managed.html

    https://www.youtube.com/watch?v=sfv7YpxgK20 

    https://devocean.sk.com/blog/techBoardDetail.do?ID=164260 

    반응형

    댓글

Designed by Tistory.