mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 05:40:42 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			263 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			263 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| Copyright 2017 The Kubernetes Authors.
 | |
| 
 | |
| Licensed 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 checkpoint
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"k8s.io/klog/v2"
 | |
| 	"math/rand"
 | |
| 	"time"
 | |
| 
 | |
| 	apiv1 "k8s.io/api/core/v1"
 | |
| 	apiequality "k8s.io/apimachinery/pkg/api/equality"
 | |
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | |
| 	"k8s.io/apimachinery/pkg/fields"
 | |
| 	"k8s.io/apimachinery/pkg/runtime"
 | |
| 	"k8s.io/apimachinery/pkg/runtime/serializer"
 | |
| 	clientset "k8s.io/client-go/kubernetes"
 | |
| 	"k8s.io/client-go/tools/cache"
 | |
| 	kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1"
 | |
| 	kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/config"
 | |
| 	"k8s.io/kubernetes/pkg/kubelet/apis/config/scheme"
 | |
| 	"k8s.io/kubernetes/pkg/kubelet/kubeletconfig/status"
 | |
| 	utilcodec "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/codec"
 | |
| )
 | |
| 
 | |
| // Payload represents a local copy of a config source (payload) object
 | |
| type Payload interface {
 | |
| 	// UID returns a globally unique (space and time) identifier for the payload.
 | |
| 	// The return value is guaranteed non-empty.
 | |
| 	UID() string
 | |
| 
 | |
| 	// ResourceVersion returns a resource version for the payload.
 | |
| 	// The return value is guaranteed non-empty.
 | |
| 	ResourceVersion() string
 | |
| 
 | |
| 	// Files returns a map of filenames to file contents.
 | |
| 	Files() map[string]string
 | |
| 
 | |
| 	// object returns the underlying checkpointed object.
 | |
| 	object() interface{}
 | |
| }
 | |
| 
 | |
| // RemoteConfigSource represents a remote config source object that can be downloaded as a Checkpoint
 | |
| type RemoteConfigSource interface {
 | |
| 	// KubeletFilename returns the name of the Kubelet config file as it should appear in the keys of Payload.Files()
 | |
| 	KubeletFilename() string
 | |
| 
 | |
| 	// APIPath returns the API path to the remote resource.
 | |
| 	APIPath() string
 | |
| 
 | |
| 	// UID returns the globally unique identifier for the most recently downloaded payload targeted by the source.
 | |
| 	UID() string
 | |
| 
 | |
| 	// ResourceVersion returns the resource version of the most recently downloaded payload targeted by the source.
 | |
| 	ResourceVersion() string
 | |
| 
 | |
| 	// Download downloads the remote config source's target object and returns a Payload backed by the object,
 | |
| 	// or a sanitized failure reason and error if the download fails.
 | |
| 	// Download takes an optional store as an argument. If provided, Download will check this store for the
 | |
| 	// target object prior to contacting the API server.
 | |
| 	// Download updates the local UID and ResourceVersion tracked by this source, based on the downloaded payload.
 | |
| 	Download(client clientset.Interface, store cache.Store) (Payload, string, error)
 | |
| 
 | |
| 	// Informer returns an informer that can be used to detect changes to the remote config source
 | |
| 	Informer(client clientset.Interface, handler cache.ResourceEventHandlerFuncs) cache.SharedInformer
 | |
| 
 | |
| 	// Encode returns a []byte representation of the object behind the RemoteConfigSource
 | |
| 	Encode() ([]byte, error)
 | |
| 
 | |
| 	// NodeConfigSource returns a copy of the underlying apiv1.NodeConfigSource object.
 | |
| 	// All RemoteConfigSources are expected to be backed by a NodeConfigSource,
 | |
| 	// though the convenience methods on the interface will target the source
 | |
| 	// type that was detected in a call to NewRemoteConfigSource.
 | |
| 	NodeConfigSource() *apiv1.NodeConfigSource
 | |
| }
 | |
| 
 | |
| // NewRemoteConfigSource constructs a RemoteConfigSource from a v1/NodeConfigSource object
 | |
| // You should only call this with a non-nil config source.
 | |
| // Note that the API server validates Node.Spec.ConfigSource.
 | |
| func NewRemoteConfigSource(source *apiv1.NodeConfigSource) (RemoteConfigSource, string, error) {
 | |
| 	// NOTE: Even though the API server validates the config, we check whether all *known* fields are
 | |
| 	// nil here, so that if a new API server allows a new config source type, old clients can send
 | |
| 	// an error message rather than crashing due to a nil pointer dereference.
 | |
| 
 | |
| 	// Exactly one reference subfield of the config source must be non-nil.
 | |
| 	// Currently ConfigMap is the only reference subfield.
 | |
| 	if source.ConfigMap == nil {
 | |
| 		return nil, status.AllNilSubfieldsError, fmt.Errorf("%s, NodeConfigSource was: %#v", status.AllNilSubfieldsError, source)
 | |
| 	}
 | |
| 	return &remoteConfigMap{source}, "", nil
 | |
| }
 | |
| 
 | |
| // DecodeRemoteConfigSource is a helper for using the apimachinery to decode serialized RemoteConfigSources;
 | |
| // e.g. the metadata stored by checkpoint/store/fsstore.go
 | |
| func DecodeRemoteConfigSource(data []byte) (RemoteConfigSource, error) {
 | |
| 	// Decode the remote config source. We want this to be non-strict
 | |
| 	// so we don't error out on newer API keys.
 | |
| 	_, codecs, err := scheme.NewSchemeAndCodecs(serializer.DisableStrict)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	obj, err := runtime.Decode(codecs.UniversalDecoder(), data)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to decode, error: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	// for now we assume we are trying to load an kubeletconfigv1beta1.SerializedNodeConfigSource,
 | |
| 	// this may need to be extended if e.g. a new version of the api is born
 | |
| 	cs, ok := obj.(*kubeletconfiginternal.SerializedNodeConfigSource)
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("failed to cast decoded remote config source to *k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig.SerializedNodeConfigSource")
 | |
| 	}
 | |
| 
 | |
| 	// we use the v1.NodeConfigSource type on internal and external, so no need to convert to external here
 | |
| 	source, _, err := NewRemoteConfigSource(&cs.Source)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return source, nil
 | |
| }
 | |
| 
 | |
| // EqualRemoteConfigSources is a helper for comparing remote config sources by
 | |
| // comparing the underlying API objects for semantic equality.
 | |
| func EqualRemoteConfigSources(a, b RemoteConfigSource) bool {
 | |
| 	if a != nil && b != nil {
 | |
| 		return apiequality.Semantic.DeepEqual(a.NodeConfigSource(), b.NodeConfigSource())
 | |
| 	}
 | |
| 	return a == b
 | |
| }
 | |
| 
 | |
| // remoteConfigMap implements RemoteConfigSource for v1/ConfigMap config sources
 | |
| type remoteConfigMap struct {
 | |
| 	source *apiv1.NodeConfigSource
 | |
| }
 | |
| 
 | |
| var _ RemoteConfigSource = (*remoteConfigMap)(nil)
 | |
| 
 | |
| func (r *remoteConfigMap) KubeletFilename() string {
 | |
| 	return r.source.ConfigMap.KubeletConfigKey
 | |
| }
 | |
| 
 | |
| const configMapAPIPathFmt = "/api/v1/namespaces/%s/configmaps/%s"
 | |
| 
 | |
| func (r *remoteConfigMap) APIPath() string {
 | |
| 	ref := r.source.ConfigMap
 | |
| 	return fmt.Sprintf(configMapAPIPathFmt, ref.Namespace, ref.Name)
 | |
| }
 | |
| 
 | |
| func (r *remoteConfigMap) UID() string {
 | |
| 	return string(r.source.ConfigMap.UID)
 | |
| }
 | |
| 
 | |
| func (r *remoteConfigMap) ResourceVersion() string {
 | |
| 	return r.source.ConfigMap.ResourceVersion
 | |
| }
 | |
| 
 | |
| func (r *remoteConfigMap) Download(client clientset.Interface, store cache.Store) (Payload, string, error) {
 | |
| 	var (
 | |
| 		cm  *apiv1.ConfigMap
 | |
| 		err error
 | |
| 	)
 | |
| 	// check the in-memory store for the ConfigMap, so we can skip unnecessary downloads
 | |
| 	if store != nil {
 | |
| 		klog.InfoS("Kubelet config controller checking in-memory store for remoteConfigMap", "apiPath", r.APIPath())
 | |
| 		cm, err = getConfigMapFromStore(store, r.source.ConfigMap.Namespace, r.source.ConfigMap.Name)
 | |
| 		if err != nil {
 | |
| 			// just log the error, we'll attempt a direct download instead
 | |
| 			klog.ErrorS(err, "Kubelet config controller failed to check in-memory store for remoteConfigMap", "apiPath", r.APIPath())
 | |
| 		} else if cm != nil {
 | |
| 			klog.InfoS("Kubelet config controller found remoteConfigMap in in-memory store", "apiPath", r.APIPath(), "configMapUID", cm.UID, "resourceVersion", cm.ResourceVersion)
 | |
| 		} else {
 | |
| 			klog.InfoS("Kubelet config controller did not find remoteConfigMap in in-memory store", "apiPath", r.APIPath())
 | |
| 		}
 | |
| 	}
 | |
| 	// if we didn't find the ConfigMap in the in-memory store, download it from the API server
 | |
| 	if cm == nil {
 | |
| 		klog.InfoS("Kubelet config controller attempting to download remoteConfigMap", "apiPath", r.APIPath())
 | |
| 		cm, err = client.CoreV1().ConfigMaps(r.source.ConfigMap.Namespace).Get(context.TODO(), r.source.ConfigMap.Name, metav1.GetOptions{})
 | |
| 		if err != nil {
 | |
| 			return nil, status.DownloadError, fmt.Errorf("%s, error: %v", status.DownloadError, err)
 | |
| 		}
 | |
| 		klog.InfoS("Kubelet config controller successfully downloaded remoteConfigMap", "apiPath", r.APIPath(), "configMapUID", cm.UID, "resourceVersion", cm.ResourceVersion)
 | |
| 	} // Assert: Now we have a non-nil ConfigMap
 | |
| 	// construct Payload from the ConfigMap
 | |
| 	payload, err := NewConfigMapPayload(cm)
 | |
| 	if err != nil {
 | |
| 		// We only expect an error here if ObjectMeta is lacking UID or ResourceVersion. This should
 | |
| 		// never happen on objects in the informer's store, or objects downloaded from the API server
 | |
| 		// directly, so we report InternalError.
 | |
| 		return nil, status.InternalError, fmt.Errorf("%s, error: %v", status.InternalError, err)
 | |
| 	}
 | |
| 	// update internal UID and ResourceVersion based on latest ConfigMap
 | |
| 	r.source.ConfigMap.UID = cm.UID
 | |
| 	r.source.ConfigMap.ResourceVersion = cm.ResourceVersion
 | |
| 	return payload, "", nil
 | |
| }
 | |
| 
 | |
| func (r *remoteConfigMap) Informer(client clientset.Interface, handler cache.ResourceEventHandlerFuncs) cache.SharedInformer {
 | |
| 	// select ConfigMap by name
 | |
| 	fieldSelector := fields.OneTermEqualSelector("metadata.name", r.source.ConfigMap.Name)
 | |
| 
 | |
| 	// add some randomness to resync period, which can help avoid controllers falling into lock-step
 | |
| 	minResyncPeriod := 15 * time.Minute
 | |
| 	factor := rand.Float64() + 1
 | |
| 	resyncPeriod := time.Duration(float64(minResyncPeriod.Nanoseconds()) * factor)
 | |
| 
 | |
| 	lw := cache.NewListWatchFromClient(client.CoreV1().RESTClient(), "configmaps", r.source.ConfigMap.Namespace, fieldSelector)
 | |
| 
 | |
| 	informer := cache.NewSharedInformer(lw, &apiv1.ConfigMap{}, resyncPeriod)
 | |
| 	informer.AddEventHandler(handler)
 | |
| 
 | |
| 	return informer
 | |
| }
 | |
| 
 | |
| func (r *remoteConfigMap) Encode() ([]byte, error) {
 | |
| 	encoder, err := utilcodec.NewKubeletconfigYAMLEncoder(kubeletconfigv1beta1.SchemeGroupVersion)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	data, err := runtime.Encode(encoder, &kubeletconfigv1beta1.SerializedNodeConfigSource{Source: *r.source})
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return data, nil
 | |
| }
 | |
| 
 | |
| func (r *remoteConfigMap) NodeConfigSource() *apiv1.NodeConfigSource {
 | |
| 	return r.source.DeepCopy()
 | |
| }
 | |
| 
 | |
| func getConfigMapFromStore(store cache.Store, namespace, name string) (*apiv1.ConfigMap, error) {
 | |
| 	key := fmt.Sprintf("%s/%s", namespace, name)
 | |
| 	obj, ok, err := store.GetByKey(key)
 | |
| 	if err != nil || !ok {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	cm, ok := obj.(*apiv1.ConfigMap)
 | |
| 	if !ok {
 | |
| 		err := fmt.Errorf("failed to cast object %s from informer's store to ConfigMap", key)
 | |
| 		klog.ErrorS(err, "Kubelet config controller")
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return cm, nil
 | |
| }
 |