...

Source file src/k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/topology_manager.go

Documentation: k8s.io/kubernetes/pkg/kubelet/cm/topologymanager

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package topologymanager
    18  
    19  import (
    20  	"fmt"
    21  	"time"
    22  
    23  	cadvisorapi "github.com/google/cadvisor/info/v1"
    24  	v1 "k8s.io/api/core/v1"
    25  	"k8s.io/klog/v2"
    26  	"k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask"
    27  	"k8s.io/kubernetes/pkg/kubelet/lifecycle"
    28  	"k8s.io/kubernetes/pkg/kubelet/metrics"
    29  )
    30  
    31  const (
    32  	// maxAllowableNUMANodes specifies the maximum number of NUMA Nodes that
    33  	// the TopologyManager supports on the underlying machine.
    34  	//
    35  	// At present, having more than this number of NUMA Nodes will result in a
    36  	// state explosion when trying to enumerate possible NUMAAffinity masks and
    37  	// generate hints for them. As such, if more NUMA Nodes than this are
    38  	// present on a machine and the TopologyManager is enabled, an error will
    39  	// be returned and the TopologyManager will not be loaded.
    40  	maxAllowableNUMANodes = 8
    41  	// ErrorTopologyAffinity represents the type for a TopologyAffinityError
    42  	ErrorTopologyAffinity = "TopologyAffinityError"
    43  )
    44  
    45  // TopologyAffinityError represents an resource alignment error
    46  type TopologyAffinityError struct{}
    47  
    48  func (e TopologyAffinityError) Error() string {
    49  	return "Resources cannot be allocated with Topology locality"
    50  }
    51  
    52  func (e TopologyAffinityError) Type() string {
    53  	return ErrorTopologyAffinity
    54  }
    55  
    56  // Manager interface provides methods for Kubelet to manage pod topology hints
    57  type Manager interface {
    58  	// PodAdmitHandler is implemented by Manager
    59  	lifecycle.PodAdmitHandler
    60  	// AddHintProvider adds a hint provider to manager to indicate the hint provider
    61  	// wants to be consulted with when making topology hints
    62  	AddHintProvider(HintProvider)
    63  	// AddContainer adds pod to Manager for tracking
    64  	AddContainer(pod *v1.Pod, container *v1.Container, containerID string)
    65  	// RemoveContainer removes pod from Manager tracking
    66  	RemoveContainer(containerID string) error
    67  	// Store is the interface for storing pod topology hints
    68  	Store
    69  }
    70  
    71  type manager struct {
    72  	//Topology Manager Scope
    73  	scope Scope
    74  }
    75  
    76  // HintProvider is an interface for components that want to collaborate to
    77  // achieve globally optimal concrete resource alignment with respect to
    78  // NUMA locality.
    79  type HintProvider interface {
    80  	// GetTopologyHints returns a map of resource names to a list of possible
    81  	// concrete resource allocations in terms of NUMA locality hints. Each hint
    82  	// is optionally marked "preferred" and indicates the set of NUMA nodes
    83  	// involved in the hypothetical allocation. The topology manager calls
    84  	// this function for each hint provider, and merges the hints to produce
    85  	// a consensus "best" hint. The hint providers may subsequently query the
    86  	// topology manager to influence actual resource assignment.
    87  	GetTopologyHints(pod *v1.Pod, container *v1.Container) map[string][]TopologyHint
    88  	// GetPodTopologyHints returns a map of resource names to a list of possible
    89  	// concrete resource allocations per Pod in terms of NUMA locality hints.
    90  	GetPodTopologyHints(pod *v1.Pod) map[string][]TopologyHint
    91  	// Allocate triggers resource allocation to occur on the HintProvider after
    92  	// all hints have been gathered and the aggregated Hint is available via a
    93  	// call to Store.GetAffinity().
    94  	Allocate(pod *v1.Pod, container *v1.Container) error
    95  }
    96  
    97  // Store interface is to allow Hint Providers to retrieve pod affinity
    98  type Store interface {
    99  	GetAffinity(podUID string, containerName string) TopologyHint
   100  	GetPolicy() Policy
   101  }
   102  
   103  // TopologyHint is a struct containing the NUMANodeAffinity for a Container
   104  type TopologyHint struct {
   105  	NUMANodeAffinity bitmask.BitMask
   106  	// Preferred is set to true when the NUMANodeAffinity encodes a preferred
   107  	// allocation for the Container. It is set to false otherwise.
   108  	Preferred bool
   109  }
   110  
   111  // IsEqual checks if TopologyHint are equal
   112  func (th *TopologyHint) IsEqual(topologyHint TopologyHint) bool {
   113  	if th.Preferred == topologyHint.Preferred {
   114  		if th.NUMANodeAffinity == nil || topologyHint.NUMANodeAffinity == nil {
   115  			return th.NUMANodeAffinity == topologyHint.NUMANodeAffinity
   116  		}
   117  		return th.NUMANodeAffinity.IsEqual(topologyHint.NUMANodeAffinity)
   118  	}
   119  	return false
   120  }
   121  
   122  // LessThan checks if TopologyHint `a` is less than TopologyHint `b`
   123  // this means that either `a` is a preferred hint and `b` is not
   124  // or `a` NUMANodeAffinity attribute is narrower than `b` NUMANodeAffinity attribute.
   125  func (th *TopologyHint) LessThan(other TopologyHint) bool {
   126  	if th.Preferred != other.Preferred {
   127  		return th.Preferred
   128  	}
   129  	return th.NUMANodeAffinity.IsNarrowerThan(other.NUMANodeAffinity)
   130  }
   131  
   132  var _ Manager = &manager{}
   133  
   134  // NewManager creates a new TopologyManager based on provided policy and scope
   135  func NewManager(topology []cadvisorapi.Node, topologyPolicyName string, topologyScopeName string, topologyPolicyOptions map[string]string) (Manager, error) {
   136  	// When policy is none, the scope is not relevant, so we can short circuit here.
   137  	if topologyPolicyName == PolicyNone {
   138  		klog.InfoS("Creating topology manager with none policy")
   139  		return &manager{scope: NewNoneScope()}, nil
   140  	}
   141  
   142  	opts, err := NewPolicyOptions(topologyPolicyOptions)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	klog.InfoS("Creating topology manager with policy per scope", "topologyPolicyName", topologyPolicyName, "topologyScopeName", topologyScopeName, "topologyPolicyOptions", opts)
   148  
   149  	numaInfo, err := NewNUMAInfo(topology, opts)
   150  	if err != nil {
   151  		return nil, fmt.Errorf("cannot discover NUMA topology: %w", err)
   152  	}
   153  
   154  	if topologyPolicyName != PolicyNone && len(numaInfo.Nodes) > maxAllowableNUMANodes {
   155  		return nil, fmt.Errorf("unsupported on machines with more than %v NUMA Nodes", maxAllowableNUMANodes)
   156  	}
   157  
   158  	var policy Policy
   159  	switch topologyPolicyName {
   160  
   161  	case PolicyBestEffort:
   162  		policy = NewBestEffortPolicy(numaInfo, opts)
   163  
   164  	case PolicyRestricted:
   165  		policy = NewRestrictedPolicy(numaInfo, opts)
   166  
   167  	case PolicySingleNumaNode:
   168  		policy = NewSingleNumaNodePolicy(numaInfo, opts)
   169  
   170  	default:
   171  		return nil, fmt.Errorf("unknown policy: \"%s\"", topologyPolicyName)
   172  	}
   173  
   174  	var scope Scope
   175  	switch topologyScopeName {
   176  
   177  	case containerTopologyScope:
   178  		scope = NewContainerScope(policy)
   179  
   180  	case podTopologyScope:
   181  		scope = NewPodScope(policy)
   182  
   183  	default:
   184  		return nil, fmt.Errorf("unknown scope: \"%s\"", topologyScopeName)
   185  	}
   186  
   187  	manager := &manager{
   188  		scope: scope,
   189  	}
   190  
   191  	return manager, nil
   192  }
   193  
   194  func (m *manager) GetAffinity(podUID string, containerName string) TopologyHint {
   195  	return m.scope.GetAffinity(podUID, containerName)
   196  }
   197  
   198  func (m *manager) GetPolicy() Policy {
   199  	return m.scope.GetPolicy()
   200  }
   201  
   202  func (m *manager) AddHintProvider(h HintProvider) {
   203  	m.scope.AddHintProvider(h)
   204  }
   205  
   206  func (m *manager) AddContainer(pod *v1.Pod, container *v1.Container, containerID string) {
   207  	m.scope.AddContainer(pod, container, containerID)
   208  }
   209  
   210  func (m *manager) RemoveContainer(containerID string) error {
   211  	return m.scope.RemoveContainer(containerID)
   212  }
   213  
   214  func (m *manager) Admit(attrs *lifecycle.PodAdmitAttributes) lifecycle.PodAdmitResult {
   215  	klog.InfoS("Topology Admit Handler", "podUID", attrs.Pod.UID, "podNamespace", attrs.Pod.Namespace, "podName", attrs.Pod.Name)
   216  	metrics.TopologyManagerAdmissionRequestsTotal.Inc()
   217  
   218  	startTime := time.Now()
   219  	podAdmitResult := m.scope.Admit(attrs.Pod)
   220  	metrics.TopologyManagerAdmissionDuration.Observe(float64(time.Since(startTime).Milliseconds()))
   221  
   222  	return podAdmitResult
   223  }
   224  

View as plain text