mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-31 15:25:57 +00:00
Merge pull request #24836 from Clarifai/gpu-impl
Automatic merge from submit-queue WIP v0 NVIDIA GPU support ```release-note * Alpha support for scheduling pods on machines with NVIDIA GPUs whose kubelets use the `--experimental-nvidia-gpus` flag, using the alpha.kubernetes.io/nvidia-gpu resource ``` Implements part of #24071 for #23587 I am not familiar with the scheduler enough to know what to do with the scores. Mostly punting for now. Missing items from the implementation plan: limitranger, rkt support, kubectl support and docs cc @erictune @davidopp @dchen1107 @vishh @Hui-Zhi @gopinatht
This commit is contained in:
commit
08440b5dcc
@ -106,6 +106,7 @@ func NewKubeletServer() *KubeletServer {
|
|||||||
MaxPerPodContainerCount: 2,
|
MaxPerPodContainerCount: 2,
|
||||||
MaxOpenFiles: 1000000,
|
MaxOpenFiles: 1000000,
|
||||||
MaxPods: 110,
|
MaxPods: 110,
|
||||||
|
NvidiaGPUs: 0,
|
||||||
MinimumGCAge: unversioned.Duration{Duration: 1 * time.Minute},
|
MinimumGCAge: unversioned.Duration{Duration: 1 * time.Minute},
|
||||||
NetworkPluginDir: "/usr/libexec/kubernetes/kubelet-plugins/net/exec/",
|
NetworkPluginDir: "/usr/libexec/kubernetes/kubelet-plugins/net/exec/",
|
||||||
NetworkPluginName: "",
|
NetworkPluginName: "",
|
||||||
@ -227,6 +228,7 @@ func (s *KubeletServer) AddFlags(fs *pflag.FlagSet) {
|
|||||||
fs.BoolVar(&s.BabysitDaemons, "babysit-daemons", s.BabysitDaemons, "If true, the node has babysitter process monitoring docker and kubelet.")
|
fs.BoolVar(&s.BabysitDaemons, "babysit-daemons", s.BabysitDaemons, "If true, the node has babysitter process monitoring docker and kubelet.")
|
||||||
fs.MarkDeprecated("babysit-daemons", "Will be removed in a future version.")
|
fs.MarkDeprecated("babysit-daemons", "Will be removed in a future version.")
|
||||||
fs.Int32Var(&s.MaxPods, "max-pods", s.MaxPods, "Number of Pods that can run on this Kubelet.")
|
fs.Int32Var(&s.MaxPods, "max-pods", s.MaxPods, "Number of Pods that can run on this Kubelet.")
|
||||||
|
fs.Int32Var(&s.NvidiaGPUs, "experimental-nvidia-gpus", s.NvidiaGPUs, "Number of NVIDIA GPU devices on this node. Only 0 (default) and 1 are currently supported.")
|
||||||
fs.StringVar(&s.DockerExecHandlerName, "docker-exec-handler", s.DockerExecHandlerName, "Handler to use when executing a command in a container. Valid values are 'native' and 'nsenter'. Defaults to 'native'.")
|
fs.StringVar(&s.DockerExecHandlerName, "docker-exec-handler", s.DockerExecHandlerName, "Handler to use when executing a command in a container. Valid values are 'native' and 'nsenter'. Defaults to 'native'.")
|
||||||
fs.StringVar(&s.NonMasqueradeCIDR, "non-masquerade-cidr", s.NonMasqueradeCIDR, "Traffic to IPs outside this range will use IP masquerade.")
|
fs.StringVar(&s.NonMasqueradeCIDR, "non-masquerade-cidr", s.NonMasqueradeCIDR, "Traffic to IPs outside this range will use IP masquerade.")
|
||||||
fs.StringVar(&s.PodCIDR, "pod-cidr", "", "The CIDR to use for pod IP addresses, only used in standalone mode. In cluster mode, this is obtained from the master.")
|
fs.StringVar(&s.PodCIDR, "pod-cidr", "", "The CIDR to use for pod IP addresses, only used in standalone mode. In cluster mode, this is obtained from the master.")
|
||||||
|
@ -228,6 +228,7 @@ func UnsecuredKubeletConfig(s *options.KubeletServer) (*KubeletConfig, error) {
|
|||||||
MaxOpenFiles: s.MaxOpenFiles,
|
MaxOpenFiles: s.MaxOpenFiles,
|
||||||
MaxPerPodContainerCount: int(s.MaxPerPodContainerCount),
|
MaxPerPodContainerCount: int(s.MaxPerPodContainerCount),
|
||||||
MaxPods: int(s.MaxPods),
|
MaxPods: int(s.MaxPods),
|
||||||
|
NvidiaGPUs: int(s.NvidiaGPUs),
|
||||||
MinimumGCAge: s.MinimumGCAge.Duration,
|
MinimumGCAge: s.MinimumGCAge.Duration,
|
||||||
Mounter: mounter,
|
Mounter: mounter,
|
||||||
NetworkPluginName: s.NetworkPluginName,
|
NetworkPluginName: s.NetworkPluginName,
|
||||||
@ -558,6 +559,7 @@ func SimpleKubelet(client *clientset.Clientset,
|
|||||||
MaxOpenFiles: 1024,
|
MaxOpenFiles: 1024,
|
||||||
MaxPerPodContainerCount: 2,
|
MaxPerPodContainerCount: 2,
|
||||||
MaxPods: maxPods,
|
MaxPods: maxPods,
|
||||||
|
NvidiaGPUs: 0,
|
||||||
MinimumGCAge: minimumGCAge,
|
MinimumGCAge: minimumGCAge,
|
||||||
Mounter: mount.New(),
|
Mounter: mount.New(),
|
||||||
NodeStatusUpdateFrequency: nodeStatusUpdateFrequency,
|
NodeStatusUpdateFrequency: nodeStatusUpdateFrequency,
|
||||||
@ -750,6 +752,7 @@ type KubeletConfig struct {
|
|||||||
NodeLabels map[string]string
|
NodeLabels map[string]string
|
||||||
NodeStatusUpdateFrequency time.Duration
|
NodeStatusUpdateFrequency time.Duration
|
||||||
NonMasqueradeCIDR string
|
NonMasqueradeCIDR string
|
||||||
|
NvidiaGPUs int
|
||||||
OOMAdjuster *oom.OOMAdjuster
|
OOMAdjuster *oom.OOMAdjuster
|
||||||
OSInterface kubecontainer.OSInterface
|
OSInterface kubecontainer.OSInterface
|
||||||
PodCIDR string
|
PodCIDR string
|
||||||
@ -860,6 +863,7 @@ func CreateAndInitKubelet(kc *KubeletConfig) (k KubeletBootstrap, pc *config.Pod
|
|||||||
kc.PodCIDR,
|
kc.PodCIDR,
|
||||||
kc.ReconcileCIDR,
|
kc.ReconcileCIDR,
|
||||||
kc.MaxPods,
|
kc.MaxPods,
|
||||||
|
kc.NvidiaGPUs,
|
||||||
kc.DockerExecHandler,
|
kc.DockerExecHandler,
|
||||||
kc.ResolverConfig,
|
kc.ResolverConfig,
|
||||||
kc.CPUCFSQuota,
|
kc.CPUCFSQuota,
|
||||||
|
@ -91,6 +91,7 @@ kubelet
|
|||||||
--eviction-soft="": A set of eviction thresholds (e.g. memory.available<1.5Gi) that if met over a corresponding grace period would trigger a pod eviction.
|
--eviction-soft="": A set of eviction thresholds (e.g. memory.available<1.5Gi) that if met over a corresponding grace period would trigger a pod eviction.
|
||||||
--eviction-soft-grace-period="": A set of eviction grace periods (e.g. memory.available=1m30s) that correspond to how long a soft eviction threshold must hold before triggering a pod eviction.
|
--eviction-soft-grace-period="": A set of eviction grace periods (e.g. memory.available=1m30s) that correspond to how long a soft eviction threshold must hold before triggering a pod eviction.
|
||||||
--experimental-flannel-overlay[=false]: Experimental support for starting the kubelet with the default overlay network (flannel). Assumes flanneld is already running in client mode. [default=false]
|
--experimental-flannel-overlay[=false]: Experimental support for starting the kubelet with the default overlay network (flannel). Assumes flanneld is already running in client mode. [default=false]
|
||||||
|
--experimental-nvidia-gpus=0: Number of NVIDIA GPU devices on this node. Only 0 (default) and 1 are currently supported.
|
||||||
--file-check-frequency=20s: Duration between checking config files for new data
|
--file-check-frequency=20s: Duration between checking config files for new data
|
||||||
--google-json-key="": The Google Cloud Platform Service Account JSON Key to use for authentication.
|
--google-json-key="": The Google Cloud Platform Service Account JSON Key to use for authentication.
|
||||||
--hairpin-mode="promiscuous-bridge": How should the kubelet setup hairpin NAT. This allows endpoints of a Service to loadbalance back to themselves if they should try to access their own Service. Valid values are "promiscuous-bridge", "hairpin-veth" and "none".
|
--hairpin-mode="promiscuous-bridge": How should the kubelet setup hairpin NAT. This allows endpoints of a Service to loadbalance back to themselves if they should try to access their own Service. Valid values are "promiscuous-bridge", "hairpin-veth" and "none".
|
||||||
|
@ -127,6 +127,7 @@ executor-path
|
|||||||
executor-suicide-timeout
|
executor-suicide-timeout
|
||||||
experimental-flannel-overlay
|
experimental-flannel-overlay
|
||||||
experimental-keystone-url
|
experimental-keystone-url
|
||||||
|
experimental-nvidia-gpus
|
||||||
experimental-prefix
|
experimental-prefix
|
||||||
external-hostname
|
external-hostname
|
||||||
external-ip
|
external-ip
|
||||||
|
@ -49,6 +49,13 @@ func (self *ResourceList) Pods() *resource.Quantity {
|
|||||||
return &resource.Quantity{}
|
return &resource.Quantity{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *ResourceList) NvidiaGPU() *resource.Quantity {
|
||||||
|
if val, ok := (*self)[ResourceNvidiaGPU]; ok {
|
||||||
|
return &val
|
||||||
|
}
|
||||||
|
return &resource.Quantity{}
|
||||||
|
}
|
||||||
|
|
||||||
func GetContainerStatus(statuses []ContainerStatus, name string) (ContainerStatus, bool) {
|
func GetContainerStatus(statuses []ContainerStatus, name string) (ContainerStatus, bool) {
|
||||||
for i := range statuses {
|
for i := range statuses {
|
||||||
if statuses[i].Name == name {
|
if statuses[i].Name == name {
|
||||||
|
@ -1922,6 +1922,11 @@ type NodeResources struct {
|
|||||||
// ResourceName is the name identifying various resources in a ResourceList.
|
// ResourceName is the name identifying various resources in a ResourceList.
|
||||||
type ResourceName string
|
type ResourceName string
|
||||||
|
|
||||||
|
// Resource names must be not more than 63 characters, consisting of upper- or lower-case alphanumeric characters,
|
||||||
|
// with the -, _, and . characters allowed anywhere, except the first or last character.
|
||||||
|
// The default convention, matching that for annotations, is to use lower-case names, with dashes, rather than
|
||||||
|
// camel case, separating compound words.
|
||||||
|
// Fully-qualified resource typenames are constructed from a DNS-style subdomain, followed by a slash `/` and a name.
|
||||||
const (
|
const (
|
||||||
// CPU, in cores. (500m = .5 cores)
|
// CPU, in cores. (500m = .5 cores)
|
||||||
ResourceCPU ResourceName = "cpu"
|
ResourceCPU ResourceName = "cpu"
|
||||||
@ -1929,6 +1934,8 @@ const (
|
|||||||
ResourceMemory ResourceName = "memory"
|
ResourceMemory ResourceName = "memory"
|
||||||
// Volume size, in bytes (e,g. 5Gi = 5GiB = 5 * 1024 * 1024 * 1024)
|
// Volume size, in bytes (e,g. 5Gi = 5GiB = 5 * 1024 * 1024 * 1024)
|
||||||
ResourceStorage ResourceName = "storage"
|
ResourceStorage ResourceName = "storage"
|
||||||
|
// NVIDIA GPU, in devices. Alpha, might change: although fractional and allowing values >1, only one whole device per node is assigned.
|
||||||
|
ResourceNvidiaGPU ResourceName = "alpha.kubernetes.io/nvidia-gpu"
|
||||||
// Number of Pods that may be running on this Node: see ResourcePods
|
// Number of Pods that may be running on this Node: see ResourcePods
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2307,6 +2307,11 @@ type NodeAddress struct {
|
|||||||
// ResourceName is the name identifying various resources in a ResourceList.
|
// ResourceName is the name identifying various resources in a ResourceList.
|
||||||
type ResourceName string
|
type ResourceName string
|
||||||
|
|
||||||
|
// Resource names must be not more than 63 characters, consisting of upper- or lower-case alphanumeric characters,
|
||||||
|
// with the -, _, and . characters allowed anywhere, except the first or last character.
|
||||||
|
// The default convention, matching that for annotations, is to use lower-case names, with dashes, rather than
|
||||||
|
// camel case, separating compound words.
|
||||||
|
// Fully-qualified resource typenames are constructed from a DNS-style subdomain, followed by a slash `/` and a name.
|
||||||
const (
|
const (
|
||||||
// CPU, in cores. (500m = .5 cores)
|
// CPU, in cores. (500m = .5 cores)
|
||||||
ResourceCPU ResourceName = "cpu"
|
ResourceCPU ResourceName = "cpu"
|
||||||
@ -2314,6 +2319,9 @@ const (
|
|||||||
ResourceMemory ResourceName = "memory"
|
ResourceMemory ResourceName = "memory"
|
||||||
// Volume size, in bytes (e,g. 5Gi = 5GiB = 5 * 1024 * 1024 * 1024)
|
// Volume size, in bytes (e,g. 5Gi = 5GiB = 5 * 1024 * 1024 * 1024)
|
||||||
ResourceStorage ResourceName = "storage"
|
ResourceStorage ResourceName = "storage"
|
||||||
|
// NVIDIA GPU, in devices. Alpha, might change: although fractional and allowing values >1, only one whole device per node is assigned.
|
||||||
|
ResourceNvidiaGPU ResourceName = "alpha.kubernetes.io/nvidia-gpu"
|
||||||
|
// Number of Pods that may be running on this Node: see ResourcePods
|
||||||
)
|
)
|
||||||
|
|
||||||
// ResourceList is a set of (resource name, quantity) pairs.
|
// ResourceList is a set of (resource name, quantity) pairs.
|
||||||
|
@ -2489,6 +2489,7 @@ func validateBasicResource(quantity resource.Quantity, fldPath *field.Path) fiel
|
|||||||
func ValidateResourceRequirements(requirements *api.ResourceRequirements, fldPath *field.Path) field.ErrorList {
|
func ValidateResourceRequirements(requirements *api.ResourceRequirements, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
limPath := fldPath.Child("limits")
|
limPath := fldPath.Child("limits")
|
||||||
|
reqPath := fldPath.Child("requests")
|
||||||
for resourceName, quantity := range requirements.Limits {
|
for resourceName, quantity := range requirements.Limits {
|
||||||
fldPath := limPath.Key(string(resourceName))
|
fldPath := limPath.Key(string(resourceName))
|
||||||
// Validate resource name.
|
// Validate resource name.
|
||||||
@ -2499,12 +2500,14 @@ func ValidateResourceRequirements(requirements *api.ResourceRequirements, fldPat
|
|||||||
// Check that request <= limit.
|
// Check that request <= limit.
|
||||||
requestQuantity, exists := requirements.Requests[resourceName]
|
requestQuantity, exists := requirements.Requests[resourceName]
|
||||||
if exists {
|
if exists {
|
||||||
if quantity.Cmp(requestQuantity) < 0 {
|
// For GPUs, require that no request be set.
|
||||||
|
if resourceName == api.ResourceNvidiaGPU {
|
||||||
|
allErrs = append(allErrs, field.Invalid(reqPath, requestQuantity.String(), "cannot be set"))
|
||||||
|
} else if quantity.Cmp(requestQuantity) < 0 {
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath, quantity.String(), "must be greater than or equal to request"))
|
allErrs = append(allErrs, field.Invalid(fldPath, quantity.String(), "must be greater than or equal to request"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reqPath := fldPath.Child("requests")
|
|
||||||
for resourceName, quantity := range requirements.Requests {
|
for resourceName, quantity := range requirements.Requests {
|
||||||
fldPath := reqPath.Key(string(resourceName))
|
fldPath := reqPath.Key(string(resourceName))
|
||||||
// Validate resource name.
|
// Validate resource name.
|
||||||
|
@ -1383,6 +1383,22 @@ func TestValidateContainers(t *testing.T) {
|
|||||||
},
|
},
|
||||||
ImagePullPolicy: "IfNotPresent",
|
ImagePullPolicy: "IfNotPresent",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "resources-test-with-gpu",
|
||||||
|
Image: "image",
|
||||||
|
Resources: api.ResourceRequirements{
|
||||||
|
Requests: api.ResourceList{
|
||||||
|
api.ResourceName(api.ResourceCPU): resource.MustParse("10"),
|
||||||
|
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
|
||||||
|
},
|
||||||
|
Limits: api.ResourceList{
|
||||||
|
api.ResourceName(api.ResourceCPU): resource.MustParse("10"),
|
||||||
|
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
|
||||||
|
api.ResourceName(api.ResourceNvidiaGPU): resource.MustParse("1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "resources-request-limit-simple",
|
Name: "resources-request-limit-simple",
|
||||||
Image: "image",
|
Image: "image",
|
||||||
@ -1606,6 +1622,25 @@ func TestValidateContainers(t *testing.T) {
|
|||||||
ImagePullPolicy: "IfNotPresent",
|
ImagePullPolicy: "IfNotPresent",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"Resource can only have GPU limit": {
|
||||||
|
{
|
||||||
|
Name: "resources-request-limit-edge",
|
||||||
|
Image: "image",
|
||||||
|
Resources: api.ResourceRequirements{
|
||||||
|
Requests: api.ResourceList{
|
||||||
|
api.ResourceName(api.ResourceCPU): resource.MustParse("10"),
|
||||||
|
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
|
||||||
|
api.ResourceName(api.ResourceNvidiaGPU): resource.MustParse("1"),
|
||||||
|
},
|
||||||
|
Limits: api.ResourceList{
|
||||||
|
api.ResourceName(api.ResourceCPU): resource.MustParse("10"),
|
||||||
|
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
|
||||||
|
api.ResourceName(api.ResourceNvidiaGPU): resource.MustParse("1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
},
|
||||||
|
},
|
||||||
"Request limit simple invalid": {
|
"Request limit simple invalid": {
|
||||||
{
|
{
|
||||||
Name: "abc-123",
|
Name: "abc-123",
|
||||||
|
@ -276,6 +276,7 @@ func DeepCopy_componentconfig_KubeletConfiguration(in KubeletConfiguration, out
|
|||||||
out.HairpinMode = in.HairpinMode
|
out.HairpinMode = in.HairpinMode
|
||||||
out.BabysitDaemons = in.BabysitDaemons
|
out.BabysitDaemons = in.BabysitDaemons
|
||||||
out.MaxPods = in.MaxPods
|
out.MaxPods = in.MaxPods
|
||||||
|
out.NvidiaGPUs = in.NvidiaGPUs
|
||||||
out.DockerExecHandlerName = in.DockerExecHandlerName
|
out.DockerExecHandlerName = in.DockerExecHandlerName
|
||||||
out.PodCIDR = in.PodCIDR
|
out.PodCIDR = in.PodCIDR
|
||||||
out.ResolverConfig = in.ResolverConfig
|
out.ResolverConfig = in.ResolverConfig
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -290,6 +290,8 @@ type KubeletConfiguration struct {
|
|||||||
BabysitDaemons bool `json:"babysitDaemons"`
|
BabysitDaemons bool `json:"babysitDaemons"`
|
||||||
// maxPods is the number of pods that can run on this Kubelet.
|
// maxPods is the number of pods that can run on this Kubelet.
|
||||||
MaxPods int32 `json:"maxPods"`
|
MaxPods int32 `json:"maxPods"`
|
||||||
|
// nvidiaGPUs is the number of NVIDIA GPU devices on this node.
|
||||||
|
NvidiaGPUs int32 `json:"nvidiaGPUs"`
|
||||||
// dockerExecHandlerName is the handler to use when executing a command
|
// dockerExecHandlerName is the handler to use when executing a command
|
||||||
// in a container. Valid values are 'native' and 'nsenter'. Defaults to
|
// in a container. Valid values are 'native' and 'nsenter'. Defaults to
|
||||||
// 'native'.
|
// 'native'.
|
||||||
|
@ -701,8 +701,8 @@ func (dsc *DaemonSetsController) nodeShouldRunDaemonPod(node *api.Node, ds *exte
|
|||||||
}
|
}
|
||||||
pods = append(pods, pod)
|
pods = append(pods, pod)
|
||||||
}
|
}
|
||||||
_, notFittingCPU, notFittingMemory := predicates.CheckPodsExceedingFreeResources(pods, node.Status.Allocatable)
|
_, notFittingCPU, notFittingMemory, notFittingNvidiaGPU := predicates.CheckPodsExceedingFreeResources(pods, node.Status.Allocatable)
|
||||||
if len(notFittingCPU)+len(notFittingMemory) != 0 {
|
if len(notFittingCPU)+len(notFittingMemory)+len(notFittingNvidiaGPU) != 0 {
|
||||||
dsc.eventRecorder.Eventf(ds, api.EventTypeNormal, "FailedPlacement", "failed to place pod on %q: insufficent free resources", node.ObjectMeta.Name)
|
dsc.eventRecorder.Eventf(ds, api.EventTypeNormal, "FailedPlacement", "failed to place pod on %q: insufficent free resources", node.ObjectMeta.Name)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -359,6 +359,8 @@ type RunContainerOptions struct {
|
|||||||
Envs []EnvVar
|
Envs []EnvVar
|
||||||
// The mounts for the containers.
|
// The mounts for the containers.
|
||||||
Mounts []Mount
|
Mounts []Mount
|
||||||
|
// The host devices mapped into the containers.
|
||||||
|
Devices []string
|
||||||
// The port mappings for the containers.
|
// The port mappings for the containers.
|
||||||
PortMappings []PortMapping
|
PortMappings []PortMapping
|
||||||
// If the container has specified the TerminationMessagePath, then
|
// If the container has specified the TerminationMessagePath, then
|
||||||
|
@ -569,6 +569,7 @@ func (dm *DockerManager) runContainer(
|
|||||||
memoryLimit := container.Resources.Limits.Memory().Value()
|
memoryLimit := container.Resources.Limits.Memory().Value()
|
||||||
cpuRequest := container.Resources.Requests.Cpu()
|
cpuRequest := container.Resources.Requests.Cpu()
|
||||||
cpuLimit := container.Resources.Limits.Cpu()
|
cpuLimit := container.Resources.Limits.Cpu()
|
||||||
|
nvidiaGPULimit := container.Resources.Limits.NvidiaGPU()
|
||||||
var cpuShares int64
|
var cpuShares int64
|
||||||
// If request is not specified, but limit is, we want request to default to limit.
|
// If request is not specified, but limit is, we want request to default to limit.
|
||||||
// API server does this for new containers, but we repeat this logic in Kubelet
|
// API server does this for new containers, but we repeat this logic in Kubelet
|
||||||
@ -580,6 +581,16 @@ func (dm *DockerManager) runContainer(
|
|||||||
// of CPU shares.
|
// of CPU shares.
|
||||||
cpuShares = milliCPUToShares(cpuRequest.MilliValue())
|
cpuShares = milliCPUToShares(cpuRequest.MilliValue())
|
||||||
}
|
}
|
||||||
|
var devices []dockercontainer.DeviceMapping
|
||||||
|
if nvidiaGPULimit.Value() != 0 {
|
||||||
|
// Experimental. For now, we hardcode /dev/nvidia0 no matter what the user asks for
|
||||||
|
// (we only support one device per node).
|
||||||
|
devices = []dockercontainer.DeviceMapping{
|
||||||
|
{"/dev/nvidia0", "/dev/nvidia0", "mrw"},
|
||||||
|
{"/dev/nvidiactl", "/dev/nvidiactl", "mrw"},
|
||||||
|
{"/dev/nvidia-uvm", "/dev/nvidia-uvm", "mrw"},
|
||||||
|
}
|
||||||
|
}
|
||||||
podHasSELinuxLabel := pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SELinuxOptions != nil
|
podHasSELinuxLabel := pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SELinuxOptions != nil
|
||||||
binds := makeMountBindings(opts.Mounts, podHasSELinuxLabel)
|
binds := makeMountBindings(opts.Mounts, podHasSELinuxLabel)
|
||||||
// The reason we create and mount the log file in here (not in kubelet) is because
|
// The reason we create and mount the log file in here (not in kubelet) is because
|
||||||
@ -615,6 +626,7 @@ func (dm *DockerManager) runContainer(
|
|||||||
Memory: memoryLimit,
|
Memory: memoryLimit,
|
||||||
MemorySwap: -1,
|
MemorySwap: -1,
|
||||||
CPUShares: cpuShares,
|
CPUShares: cpuShares,
|
||||||
|
Devices: devices,
|
||||||
},
|
},
|
||||||
SecurityOpt: securityOpts,
|
SecurityOpt: securityOpts,
|
||||||
}
|
}
|
||||||
|
@ -206,6 +206,7 @@ func NewMainKubelet(
|
|||||||
podCIDR string,
|
podCIDR string,
|
||||||
reconcileCIDR bool,
|
reconcileCIDR bool,
|
||||||
maxPods int,
|
maxPods int,
|
||||||
|
nvidiaGPUs int,
|
||||||
dockerExecHandler dockertools.ExecHandler,
|
dockerExecHandler dockertools.ExecHandler,
|
||||||
resolverConfig string,
|
resolverConfig string,
|
||||||
cpuCFSQuota bool,
|
cpuCFSQuota bool,
|
||||||
@ -332,6 +333,7 @@ func NewMainKubelet(
|
|||||||
nonMasqueradeCIDR: nonMasqueradeCIDR,
|
nonMasqueradeCIDR: nonMasqueradeCIDR,
|
||||||
reconcileCIDR: reconcileCIDR,
|
reconcileCIDR: reconcileCIDR,
|
||||||
maxPods: maxPods,
|
maxPods: maxPods,
|
||||||
|
nvidiaGPUs: nvidiaGPUs,
|
||||||
syncLoopMonitor: atomic.Value{},
|
syncLoopMonitor: atomic.Value{},
|
||||||
resolverConfig: resolverConfig,
|
resolverConfig: resolverConfig,
|
||||||
cpuCFSQuota: cpuCFSQuota,
|
cpuCFSQuota: cpuCFSQuota,
|
||||||
@ -710,6 +712,9 @@ type Kubelet struct {
|
|||||||
// Maximum Number of Pods which can be run by this Kubelet
|
// Maximum Number of Pods which can be run by this Kubelet
|
||||||
maxPods int
|
maxPods int
|
||||||
|
|
||||||
|
// Number of NVIDIA GPUs on this node
|
||||||
|
nvidiaGPUs int
|
||||||
|
|
||||||
// Monitor Kubelet's sync loop
|
// Monitor Kubelet's sync loop
|
||||||
syncLoopMonitor atomic.Value
|
syncLoopMonitor atomic.Value
|
||||||
|
|
||||||
@ -2941,9 +2946,10 @@ func (kl *Kubelet) setNodeStatusMachineInfo(node *api.Node) {
|
|||||||
// TODO(roberthbailey): This is required for test-cmd.sh to pass.
|
// TODO(roberthbailey): This is required for test-cmd.sh to pass.
|
||||||
// See if the test should be updated instead.
|
// See if the test should be updated instead.
|
||||||
node.Status.Capacity = api.ResourceList{
|
node.Status.Capacity = api.ResourceList{
|
||||||
api.ResourceCPU: *resource.NewMilliQuantity(0, resource.DecimalSI),
|
api.ResourceCPU: *resource.NewMilliQuantity(0, resource.DecimalSI),
|
||||||
api.ResourceMemory: resource.MustParse("0Gi"),
|
api.ResourceMemory: resource.MustParse("0Gi"),
|
||||||
api.ResourcePods: *resource.NewQuantity(int64(kl.maxPods), resource.DecimalSI),
|
api.ResourcePods: *resource.NewQuantity(int64(kl.maxPods), resource.DecimalSI),
|
||||||
|
api.ResourceNvidiaGPU: *resource.NewQuantity(int64(kl.nvidiaGPUs), resource.DecimalSI),
|
||||||
}
|
}
|
||||||
glog.Errorf("Error getting machine info: %v", err)
|
glog.Errorf("Error getting machine info: %v", err)
|
||||||
} else {
|
} else {
|
||||||
@ -2952,6 +2958,8 @@ func (kl *Kubelet) setNodeStatusMachineInfo(node *api.Node) {
|
|||||||
node.Status.Capacity = cadvisor.CapacityFromMachineInfo(info)
|
node.Status.Capacity = cadvisor.CapacityFromMachineInfo(info)
|
||||||
node.Status.Capacity[api.ResourcePods] = *resource.NewQuantity(
|
node.Status.Capacity[api.ResourcePods] = *resource.NewQuantity(
|
||||||
int64(kl.maxPods), resource.DecimalSI)
|
int64(kl.maxPods), resource.DecimalSI)
|
||||||
|
node.Status.Capacity[api.ResourceNvidiaGPU] = *resource.NewQuantity(
|
||||||
|
int64(kl.nvidiaGPUs), resource.DecimalSI)
|
||||||
if node.Status.NodeInfo.BootID != "" &&
|
if node.Status.NodeInfo.BootID != "" &&
|
||||||
node.Status.NodeInfo.BootID != info.BootID {
|
node.Status.NodeInfo.BootID != info.BootID {
|
||||||
// TODO: This requires a transaction, either both node status is updated
|
// TODO: This requires a transaction, either both node status is updated
|
||||||
|
@ -2569,14 +2569,16 @@ func TestUpdateNewNodeStatus(t *testing.T) {
|
|||||||
KubeProxyVersion: version.Get().String(),
|
KubeProxyVersion: version.Get().String(),
|
||||||
},
|
},
|
||||||
Capacity: api.ResourceList{
|
Capacity: api.ResourceList{
|
||||||
api.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
|
api.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
|
||||||
api.ResourceMemory: *resource.NewQuantity(10E9, resource.BinarySI),
|
api.ResourceMemory: *resource.NewQuantity(10E9, resource.BinarySI),
|
||||||
api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
|
api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
|
||||||
|
api.ResourceNvidiaGPU: *resource.NewQuantity(0, resource.DecimalSI),
|
||||||
},
|
},
|
||||||
Allocatable: api.ResourceList{
|
Allocatable: api.ResourceList{
|
||||||
api.ResourceCPU: *resource.NewMilliQuantity(1800, resource.DecimalSI),
|
api.ResourceCPU: *resource.NewMilliQuantity(1800, resource.DecimalSI),
|
||||||
api.ResourceMemory: *resource.NewQuantity(9900E6, resource.BinarySI),
|
api.ResourceMemory: *resource.NewQuantity(9900E6, resource.BinarySI),
|
||||||
api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
|
api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
|
||||||
|
api.ResourceNvidiaGPU: *resource.NewQuantity(0, resource.DecimalSI),
|
||||||
},
|
},
|
||||||
Addresses: []api.NodeAddress{
|
Addresses: []api.NodeAddress{
|
||||||
{Type: api.NodeLegacyHostIP, Address: "127.0.0.1"},
|
{Type: api.NodeLegacyHostIP, Address: "127.0.0.1"},
|
||||||
@ -2800,14 +2802,16 @@ func TestUpdateExistingNodeStatus(t *testing.T) {
|
|||||||
KubeProxyVersion: version.Get().String(),
|
KubeProxyVersion: version.Get().String(),
|
||||||
},
|
},
|
||||||
Capacity: api.ResourceList{
|
Capacity: api.ResourceList{
|
||||||
api.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
|
api.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
|
||||||
api.ResourceMemory: *resource.NewQuantity(20E9, resource.BinarySI),
|
api.ResourceMemory: *resource.NewQuantity(20E9, resource.BinarySI),
|
||||||
api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
|
api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
|
||||||
|
api.ResourceNvidiaGPU: *resource.NewQuantity(0, resource.DecimalSI),
|
||||||
},
|
},
|
||||||
Allocatable: api.ResourceList{
|
Allocatable: api.ResourceList{
|
||||||
api.ResourceCPU: *resource.NewMilliQuantity(1800, resource.DecimalSI),
|
api.ResourceCPU: *resource.NewMilliQuantity(1800, resource.DecimalSI),
|
||||||
api.ResourceMemory: *resource.NewQuantity(19900E6, resource.BinarySI),
|
api.ResourceMemory: *resource.NewQuantity(19900E6, resource.BinarySI),
|
||||||
api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
|
api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
|
||||||
|
api.ResourceNvidiaGPU: *resource.NewQuantity(0, resource.DecimalSI),
|
||||||
},
|
},
|
||||||
Addresses: []api.NodeAddress{
|
Addresses: []api.NodeAddress{
|
||||||
{Type: api.NodeLegacyHostIP, Address: "127.0.0.1"},
|
{Type: api.NodeLegacyHostIP, Address: "127.0.0.1"},
|
||||||
@ -3071,14 +3075,16 @@ func TestUpdateNodeStatusWithRuntimeStateError(t *testing.T) {
|
|||||||
KubeProxyVersion: version.Get().String(),
|
KubeProxyVersion: version.Get().String(),
|
||||||
},
|
},
|
||||||
Capacity: api.ResourceList{
|
Capacity: api.ResourceList{
|
||||||
api.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
|
api.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
|
||||||
api.ResourceMemory: *resource.NewQuantity(10E9, resource.BinarySI),
|
api.ResourceMemory: *resource.NewQuantity(10E9, resource.BinarySI),
|
||||||
api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
|
api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
|
||||||
|
api.ResourceNvidiaGPU: *resource.NewQuantity(0, resource.DecimalSI),
|
||||||
},
|
},
|
||||||
Allocatable: api.ResourceList{
|
Allocatable: api.ResourceList{
|
||||||
api.ResourceCPU: *resource.NewMilliQuantity(1800, resource.DecimalSI),
|
api.ResourceCPU: *resource.NewMilliQuantity(1800, resource.DecimalSI),
|
||||||
api.ResourceMemory: *resource.NewQuantity(9900E6, resource.BinarySI),
|
api.ResourceMemory: *resource.NewQuantity(9900E6, resource.BinarySI),
|
||||||
api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
|
api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
|
||||||
|
api.ResourceNvidiaGPU: *resource.NewQuantity(0, resource.DecimalSI),
|
||||||
},
|
},
|
||||||
Addresses: []api.NodeAddress{
|
Addresses: []api.NodeAddress{
|
||||||
{Type: api.NodeLegacyHostIP, Address: "127.0.0.1"},
|
{Type: api.NodeLegacyHostIP, Address: "127.0.0.1"},
|
||||||
|
@ -19,9 +19,10 @@ package predicates
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
podCountResourceName string = "PodCount"
|
podCountResourceName string = "PodCount"
|
||||||
cpuResourceName string = "CPU"
|
cpuResourceName string = "CPU"
|
||||||
memoryResoureceName string = "Memory"
|
memoryResoureceName string = "Memory"
|
||||||
|
nvidiaGpuResourceName string = "NvidiaGpu"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -346,8 +346,9 @@ func (c *VolumeZoneChecker) predicate(pod *api.Pod, nodeInfo *schedulercache.Nod
|
|||||||
}
|
}
|
||||||
|
|
||||||
type resourceRequest struct {
|
type resourceRequest struct {
|
||||||
milliCPU int64
|
milliCPU int64
|
||||||
memory int64
|
memory int64
|
||||||
|
nvidiaGPU int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func getResourceRequest(pod *api.Pod) resourceRequest {
|
func getResourceRequest(pod *api.Pod) resourceRequest {
|
||||||
@ -356,19 +357,23 @@ func getResourceRequest(pod *api.Pod) resourceRequest {
|
|||||||
requests := container.Resources.Requests
|
requests := container.Resources.Requests
|
||||||
result.memory += requests.Memory().Value()
|
result.memory += requests.Memory().Value()
|
||||||
result.milliCPU += requests.Cpu().MilliValue()
|
result.milliCPU += requests.Cpu().MilliValue()
|
||||||
|
result.nvidiaGPU += requests.NvidiaGPU().Value()
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckPodsExceedingFreeResources(pods []*api.Pod, allocatable api.ResourceList) (fitting []*api.Pod, notFittingCPU, notFittingMemory []*api.Pod) {
|
func CheckPodsExceedingFreeResources(pods []*api.Pod, allocatable api.ResourceList) (fitting []*api.Pod, notFittingCPU, notFittingMemory, notFittingNvidiaGPU []*api.Pod) {
|
||||||
totalMilliCPU := allocatable.Cpu().MilliValue()
|
totalMilliCPU := allocatable.Cpu().MilliValue()
|
||||||
totalMemory := allocatable.Memory().Value()
|
totalMemory := allocatable.Memory().Value()
|
||||||
|
totalNvidiaGPU := allocatable.NvidiaGPU().Value()
|
||||||
milliCPURequested := int64(0)
|
milliCPURequested := int64(0)
|
||||||
memoryRequested := int64(0)
|
memoryRequested := int64(0)
|
||||||
|
nvidiaGPURequested := int64(0)
|
||||||
for _, pod := range pods {
|
for _, pod := range pods {
|
||||||
podRequest := getResourceRequest(pod)
|
podRequest := getResourceRequest(pod)
|
||||||
fitsCPU := (totalMilliCPU - milliCPURequested) >= podRequest.milliCPU
|
fitsCPU := (totalMilliCPU - milliCPURequested) >= podRequest.milliCPU
|
||||||
fitsMemory := (totalMemory - memoryRequested) >= podRequest.memory
|
fitsMemory := (totalMemory - memoryRequested) >= podRequest.memory
|
||||||
|
fitsNVidiaGPU := (totalNvidiaGPU - nvidiaGPURequested) >= podRequest.nvidiaGPU
|
||||||
if !fitsCPU {
|
if !fitsCPU {
|
||||||
// the pod doesn't fit due to CPU request
|
// the pod doesn't fit due to CPU request
|
||||||
notFittingCPU = append(notFittingCPU, pod)
|
notFittingCPU = append(notFittingCPU, pod)
|
||||||
@ -379,9 +384,15 @@ func CheckPodsExceedingFreeResources(pods []*api.Pod, allocatable api.ResourceLi
|
|||||||
notFittingMemory = append(notFittingMemory, pod)
|
notFittingMemory = append(notFittingMemory, pod)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if !fitsNVidiaGPU {
|
||||||
|
// the pod doesn't fit due to NvidiaGPU request
|
||||||
|
notFittingNvidiaGPU = append(notFittingNvidiaGPU, pod)
|
||||||
|
continue
|
||||||
|
}
|
||||||
// the pod fits
|
// the pod fits
|
||||||
milliCPURequested += podRequest.milliCPU
|
milliCPURequested += podRequest.milliCPU
|
||||||
memoryRequested += podRequest.memory
|
memoryRequested += podRequest.memory
|
||||||
|
nvidiaGPURequested += podRequest.nvidiaGPU
|
||||||
fitting = append(fitting, pod)
|
fitting = append(fitting, pod)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -403,12 +414,13 @@ func PodFitsResources(pod *api.Pod, nodeInfo *schedulercache.NodeInfo) (bool, er
|
|||||||
newInsufficientResourceError(podCountResourceName, 1, int64(len(nodeInfo.Pods())), allowedPodNumber)
|
newInsufficientResourceError(podCountResourceName, 1, int64(len(nodeInfo.Pods())), allowedPodNumber)
|
||||||
}
|
}
|
||||||
podRequest := getResourceRequest(pod)
|
podRequest := getResourceRequest(pod)
|
||||||
if podRequest.milliCPU == 0 && podRequest.memory == 0 {
|
if podRequest.milliCPU == 0 && podRequest.memory == 0 && podRequest.nvidiaGPU == 0 {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
totalMilliCPU := allocatable.Cpu().MilliValue()
|
totalMilliCPU := allocatable.Cpu().MilliValue()
|
||||||
totalMemory := allocatable.Memory().Value()
|
totalMemory := allocatable.Memory().Value()
|
||||||
|
totalNvidiaGPU := allocatable.NvidiaGPU().Value()
|
||||||
|
|
||||||
if totalMilliCPU < podRequest.milliCPU+nodeInfo.RequestedResource().MilliCPU {
|
if totalMilliCPU < podRequest.milliCPU+nodeInfo.RequestedResource().MilliCPU {
|
||||||
return false,
|
return false,
|
||||||
@ -418,6 +430,10 @@ func PodFitsResources(pod *api.Pod, nodeInfo *schedulercache.NodeInfo) (bool, er
|
|||||||
return false,
|
return false,
|
||||||
newInsufficientResourceError(memoryResoureceName, podRequest.memory, nodeInfo.RequestedResource().Memory, totalMemory)
|
newInsufficientResourceError(memoryResoureceName, podRequest.memory, nodeInfo.RequestedResource().Memory, totalMemory)
|
||||||
}
|
}
|
||||||
|
if totalNvidiaGPU < podRequest.nvidiaGPU+nodeInfo.RequestedResource().NvidiaGPU {
|
||||||
|
return false,
|
||||||
|
newInsufficientResourceError(nvidiaGpuResourceName, podRequest.nvidiaGPU, nodeInfo.RequestedResource().NvidiaGPU, totalNvidiaGPU)
|
||||||
|
}
|
||||||
glog.V(10).Infof("Schedule Pod %+v on Node %+v is allowed, Node is running only %v out of %v Pods.",
|
glog.V(10).Infof("Schedule Pod %+v on Node %+v is allowed, Node is running only %v out of %v Pods.",
|
||||||
podName(pod), node.Name, len(nodeInfo.Pods()), allowedPodNumber)
|
podName(pod), node.Name, len(nodeInfo.Pods()), allowedPodNumber)
|
||||||
return true, nil
|
return true, nil
|
||||||
|
@ -71,21 +71,23 @@ func (pvs FakePersistentVolumeInfo) GetPersistentVolumeInfo(pvID string) (*api.P
|
|||||||
return nil, fmt.Errorf("Unable to find persistent volume: %s", pvID)
|
return nil, fmt.Errorf("Unable to find persistent volume: %s", pvID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeResources(milliCPU int64, memory int64, pods int64) api.NodeResources {
|
func makeResources(milliCPU int64, memory int64, nvidiaGPUs int64, pods int64) api.NodeResources {
|
||||||
return api.NodeResources{
|
return api.NodeResources{
|
||||||
Capacity: api.ResourceList{
|
Capacity: api.ResourceList{
|
||||||
api.ResourceCPU: *resource.NewMilliQuantity(milliCPU, resource.DecimalSI),
|
api.ResourceCPU: *resource.NewMilliQuantity(milliCPU, resource.DecimalSI),
|
||||||
api.ResourceMemory: *resource.NewQuantity(memory, resource.BinarySI),
|
api.ResourceMemory: *resource.NewQuantity(memory, resource.BinarySI),
|
||||||
api.ResourcePods: *resource.NewQuantity(pods, resource.DecimalSI),
|
api.ResourcePods: *resource.NewQuantity(pods, resource.DecimalSI),
|
||||||
|
api.ResourceNvidiaGPU: *resource.NewQuantity(nvidiaGPUs, resource.DecimalSI),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeAllocatableResources(milliCPU int64, memory int64, pods int64) api.ResourceList {
|
func makeAllocatableResources(milliCPU int64, memory int64, nvidiaGPUs int64, pods int64) api.ResourceList {
|
||||||
return api.ResourceList{
|
return api.ResourceList{
|
||||||
api.ResourceCPU: *resource.NewMilliQuantity(milliCPU, resource.DecimalSI),
|
api.ResourceCPU: *resource.NewMilliQuantity(milliCPU, resource.DecimalSI),
|
||||||
api.ResourceMemory: *resource.NewQuantity(memory, resource.BinarySI),
|
api.ResourceMemory: *resource.NewQuantity(memory, resource.BinarySI),
|
||||||
api.ResourcePods: *resource.NewQuantity(pods, resource.DecimalSI),
|
api.ResourcePods: *resource.NewQuantity(pods, resource.DecimalSI),
|
||||||
|
api.ResourceNvidiaGPU: *resource.NewQuantity(nvidiaGPUs, resource.DecimalSI),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,8 +97,9 @@ func newResourcePod(usage ...resourceRequest) *api.Pod {
|
|||||||
containers = append(containers, api.Container{
|
containers = append(containers, api.Container{
|
||||||
Resources: api.ResourceRequirements{
|
Resources: api.ResourceRequirements{
|
||||||
Requests: api.ResourceList{
|
Requests: api.ResourceList{
|
||||||
api.ResourceCPU: *resource.NewMilliQuantity(req.milliCPU, resource.DecimalSI),
|
api.ResourceCPU: *resource.NewMilliQuantity(req.milliCPU, resource.DecimalSI),
|
||||||
api.ResourceMemory: *resource.NewQuantity(req.memory, resource.BinarySI),
|
api.ResourceMemory: *resource.NewQuantity(req.memory, resource.BinarySI),
|
||||||
|
api.ResourceNvidiaGPU: *resource.NewQuantity(req.nvidiaGPU, resource.DecimalSI),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -159,7 +162,7 @@ func TestPodFitsResources(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range enoughPodsTests {
|
for _, test := range enoughPodsTests {
|
||||||
node := api.Node{Status: api.NodeStatus{Capacity: makeResources(10, 20, 32).Capacity, Allocatable: makeAllocatableResources(10, 20, 32)}}
|
node := api.Node{Status: api.NodeStatus{Capacity: makeResources(10, 20, 0, 32).Capacity, Allocatable: makeAllocatableResources(10, 20, 0, 32)}}
|
||||||
test.nodeInfo.SetNode(&node)
|
test.nodeInfo.SetNode(&node)
|
||||||
|
|
||||||
fits, err := PodFitsResources(test.pod, test.nodeInfo)
|
fits, err := PodFitsResources(test.pod, test.nodeInfo)
|
||||||
@ -204,7 +207,7 @@ func TestPodFitsResources(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range notEnoughPodsTests {
|
for _, test := range notEnoughPodsTests {
|
||||||
node := api.Node{Status: api.NodeStatus{Capacity: api.ResourceList{}, Allocatable: makeAllocatableResources(10, 20, 1)}}
|
node := api.Node{Status: api.NodeStatus{Capacity: api.ResourceList{}, Allocatable: makeAllocatableResources(10, 20, 0, 1)}}
|
||||||
test.nodeInfo.SetNode(&node)
|
test.nodeInfo.SetNode(&node)
|
||||||
|
|
||||||
fits, err := PodFitsResources(test.pod, test.nodeInfo)
|
fits, err := PodFitsResources(test.pod, test.nodeInfo)
|
||||||
@ -1529,7 +1532,7 @@ func TestRunGeneralPredicates(t *testing.T) {
|
|||||||
newResourcePod(resourceRequest{milliCPU: 9, memory: 19})),
|
newResourcePod(resourceRequest{milliCPU: 9, memory: 19})),
|
||||||
node: &api.Node{
|
node: &api.Node{
|
||||||
ObjectMeta: api.ObjectMeta{Name: "machine1"},
|
ObjectMeta: api.ObjectMeta{Name: "machine1"},
|
||||||
Status: api.NodeStatus{Capacity: makeResources(10, 20, 32).Capacity, Allocatable: makeAllocatableResources(10, 20, 32)},
|
Status: api.NodeStatus{Capacity: makeResources(10, 20, 0, 32).Capacity, Allocatable: makeAllocatableResources(10, 20, 0, 32)},
|
||||||
},
|
},
|
||||||
fits: true,
|
fits: true,
|
||||||
wErr: nil,
|
wErr: nil,
|
||||||
@ -1541,12 +1544,39 @@ func TestRunGeneralPredicates(t *testing.T) {
|
|||||||
newResourcePod(resourceRequest{milliCPU: 5, memory: 19})),
|
newResourcePod(resourceRequest{milliCPU: 5, memory: 19})),
|
||||||
node: &api.Node{
|
node: &api.Node{
|
||||||
ObjectMeta: api.ObjectMeta{Name: "machine1"},
|
ObjectMeta: api.ObjectMeta{Name: "machine1"},
|
||||||
Status: api.NodeStatus{Capacity: makeResources(10, 20, 32).Capacity, Allocatable: makeAllocatableResources(10, 20, 32)},
|
Status: api.NodeStatus{Capacity: makeResources(10, 20, 0, 32).Capacity, Allocatable: makeAllocatableResources(10, 20, 0, 32)},
|
||||||
},
|
},
|
||||||
fits: false,
|
fits: false,
|
||||||
wErr: newInsufficientResourceError("CPU", 8, 5, 10),
|
wErr: newInsufficientResourceError("CPU", 8, 5, 10),
|
||||||
test: "not enough cpu resource",
|
test: "not enough cpu resource",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
pod: &api.Pod{},
|
||||||
|
nodeInfo: schedulercache.NewNodeInfo(
|
||||||
|
newResourcePod(resourceRequest{milliCPU: 9, memory: 19})),
|
||||||
|
node: &api.Node{Status: api.NodeStatus{Capacity: makeResources(10, 20, 1, 32).Capacity, Allocatable: makeAllocatableResources(10, 20, 1, 32)}},
|
||||||
|
fits: true,
|
||||||
|
wErr: nil,
|
||||||
|
test: "no resources/port/host requested always fits on GPU machine",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pod: newResourcePod(resourceRequest{milliCPU: 3, memory: 1, nvidiaGPU: 1}),
|
||||||
|
nodeInfo: schedulercache.NewNodeInfo(
|
||||||
|
newResourcePod(resourceRequest{milliCPU: 5, memory: 10, nvidiaGPU: 1})),
|
||||||
|
node: &api.Node{Status: api.NodeStatus{Capacity: makeResources(10, 20, 1, 32).Capacity, Allocatable: makeAllocatableResources(10, 20, 1, 32)}},
|
||||||
|
fits: false,
|
||||||
|
wErr: newInsufficientResourceError("NvidiaGpu", 1, 1, 1),
|
||||||
|
test: "not enough GPU resource",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pod: newResourcePod(resourceRequest{milliCPU: 3, memory: 1, nvidiaGPU: 1}),
|
||||||
|
nodeInfo: schedulercache.NewNodeInfo(
|
||||||
|
newResourcePod(resourceRequest{milliCPU: 5, memory: 10, nvidiaGPU: 0})),
|
||||||
|
node: &api.Node{Status: api.NodeStatus{Capacity: makeResources(10, 20, 1, 32).Capacity, Allocatable: makeAllocatableResources(10, 20, 1, 32)}},
|
||||||
|
fits: true,
|
||||||
|
wErr: nil,
|
||||||
|
test: "enough GPU resource",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
pod: &api.Pod{
|
pod: &api.Pod{
|
||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
@ -1556,7 +1586,7 @@ func TestRunGeneralPredicates(t *testing.T) {
|
|||||||
nodeInfo: schedulercache.NewNodeInfo(),
|
nodeInfo: schedulercache.NewNodeInfo(),
|
||||||
node: &api.Node{
|
node: &api.Node{
|
||||||
ObjectMeta: api.ObjectMeta{Name: "machine1"},
|
ObjectMeta: api.ObjectMeta{Name: "machine1"},
|
||||||
Status: api.NodeStatus{Capacity: makeResources(10, 20, 32).Capacity, Allocatable: makeAllocatableResources(10, 20, 32)},
|
Status: api.NodeStatus{Capacity: makeResources(10, 20, 0, 32).Capacity, Allocatable: makeAllocatableResources(10, 20, 0, 32)},
|
||||||
},
|
},
|
||||||
fits: false,
|
fits: false,
|
||||||
wErr: ErrPodNotMatchHostName,
|
wErr: ErrPodNotMatchHostName,
|
||||||
@ -1567,7 +1597,7 @@ func TestRunGeneralPredicates(t *testing.T) {
|
|||||||
nodeInfo: schedulercache.NewNodeInfo(newPodWithPort(123)),
|
nodeInfo: schedulercache.NewNodeInfo(newPodWithPort(123)),
|
||||||
node: &api.Node{
|
node: &api.Node{
|
||||||
ObjectMeta: api.ObjectMeta{Name: "machine1"},
|
ObjectMeta: api.ObjectMeta{Name: "machine1"},
|
||||||
Status: api.NodeStatus{Capacity: makeResources(10, 20, 32).Capacity, Allocatable: makeAllocatableResources(10, 20, 32)},
|
Status: api.NodeStatus{Capacity: makeResources(10, 20, 0, 32).Capacity, Allocatable: makeAllocatableResources(10, 20, 0, 32)},
|
||||||
},
|
},
|
||||||
fits: false,
|
fits: false,
|
||||||
wErr: ErrPodNotFitsHostPorts,
|
wErr: ErrPodNotFitsHostPorts,
|
||||||
|
@ -43,8 +43,9 @@ type NodeInfo struct {
|
|||||||
|
|
||||||
// Resource is a collection of compute resource.
|
// Resource is a collection of compute resource.
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
MilliCPU int64
|
MilliCPU int64
|
||||||
Memory int64
|
Memory int64
|
||||||
|
NvidiaGPU int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNodeInfo returns a ready to use empty NodeInfo object.
|
// NewNodeInfo returns a ready to use empty NodeInfo object.
|
||||||
@ -115,9 +116,10 @@ func (n *NodeInfo) String() string {
|
|||||||
|
|
||||||
// addPod adds pod information to this NodeInfo.
|
// addPod adds pod information to this NodeInfo.
|
||||||
func (n *NodeInfo) addPod(pod *api.Pod) {
|
func (n *NodeInfo) addPod(pod *api.Pod) {
|
||||||
cpu, mem, non0_cpu, non0_mem := calculateResource(pod)
|
cpu, mem, nvidia_gpu, non0_cpu, non0_mem := calculateResource(pod)
|
||||||
n.requestedResource.MilliCPU += cpu
|
n.requestedResource.MilliCPU += cpu
|
||||||
n.requestedResource.Memory += mem
|
n.requestedResource.Memory += mem
|
||||||
|
n.requestedResource.NvidiaGPU += nvidia_gpu
|
||||||
n.nonzeroRequest.MilliCPU += non0_cpu
|
n.nonzeroRequest.MilliCPU += non0_cpu
|
||||||
n.nonzeroRequest.Memory += non0_mem
|
n.nonzeroRequest.Memory += non0_mem
|
||||||
n.pods = append(n.pods, pod)
|
n.pods = append(n.pods, pod)
|
||||||
@ -130,9 +132,10 @@ func (n *NodeInfo) removePod(pod *api.Pod) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cpu, mem, non0_cpu, non0_mem := calculateResource(pod)
|
cpu, mem, nvidia_gpu, non0_cpu, non0_mem := calculateResource(pod)
|
||||||
n.requestedResource.MilliCPU -= cpu
|
n.requestedResource.MilliCPU -= cpu
|
||||||
n.requestedResource.Memory -= mem
|
n.requestedResource.Memory -= mem
|
||||||
|
n.requestedResource.NvidiaGPU -= nvidia_gpu
|
||||||
n.nonzeroRequest.MilliCPU -= non0_cpu
|
n.nonzeroRequest.MilliCPU -= non0_cpu
|
||||||
n.nonzeroRequest.Memory -= non0_mem
|
n.nonzeroRequest.Memory -= non0_mem
|
||||||
|
|
||||||
@ -152,15 +155,17 @@ func (n *NodeInfo) removePod(pod *api.Pod) error {
|
|||||||
return fmt.Errorf("no corresponding pod in pods")
|
return fmt.Errorf("no corresponding pod in pods")
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculateResource(pod *api.Pod) (cpu int64, mem int64, non0_cpu int64, non0_mem int64) {
|
func calculateResource(pod *api.Pod) (cpu int64, mem int64, nvidia_gpu int64, non0_cpu int64, non0_mem int64) {
|
||||||
for _, c := range pod.Spec.Containers {
|
for _, c := range pod.Spec.Containers {
|
||||||
req := c.Resources.Requests
|
req := c.Resources.Requests
|
||||||
cpu += req.Cpu().MilliValue()
|
cpu += req.Cpu().MilliValue()
|
||||||
mem += req.Memory().Value()
|
mem += req.Memory().Value()
|
||||||
|
nvidia_gpu += req.NvidiaGPU().Value()
|
||||||
|
|
||||||
non0_cpu_req, non0_mem_req := priorityutil.GetNonzeroRequests(&req)
|
non0_cpu_req, non0_mem_req := priorityutil.GetNonzeroRequests(&req)
|
||||||
non0_cpu += non0_cpu_req
|
non0_cpu += non0_cpu_req
|
||||||
non0_mem += non0_mem_req
|
non0_mem += non0_mem_req
|
||||||
|
// No non-zero resources for GPUs
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user