Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added kubeReserved calculation support for mixed instance NodeGroups #2378

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 33 additions & 3 deletions pkg/nodebootstrap/userdata.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ func clusterDNS(spec *api.ClusterConfig, ng *api.NodeGroup) string {
return "10.100.0.10"
}

func getKubeReserved(info InstanceTypeInfo) api.InlineDocument {
return api.InlineDocument{
"ephemeral-storage": info.DefaultStorageToReserve(),
"cpu": info.DefaultCPUToReserve(),
"memory": info.DefaultMemoryToReserve(),
}
}

func makeKubeletConfigYAML(spec *api.ClusterConfig, ng *api.NodeGroup) ([]byte, error) {
data, err := Asset("kubelet.yaml")
if err != nil {
Expand All @@ -110,12 +118,34 @@ func makeKubeletConfigYAML(spec *api.ClusterConfig, ng *api.NodeGroup) ([]byte,

// Set default reservations if specs about instance is available
if info, ok := instanceTypeInfos[ng.InstanceType]; ok {
// This is a NodeGroup with a single instanceType defined
if _, ok := obj["kubeReserved"]; !ok {
obj["kubeReserved"] = api.InlineDocument{}
}
obj["kubeReserved"].(api.InlineDocument)["ephemeral-storage"] = info.DefaultStorageToReserve()
obj["kubeReserved"].(api.InlineDocument)["cpu"] = info.DefaultCPUToReserve()
obj["kubeReserved"].(api.InlineDocument)["memory"] = info.DefaultMemoryToReserve()
obj["kubeReserved"] = getKubeReserved(info)
} else if ng.InstancesDistribution != nil {
// This is a NodeGroup using mixed instance types
var minCPU, minRAM int64
for _, instanceType := range ng.InstancesDistribution.InstanceTypes {
if info, ok := instanceTypeInfos[instanceType]; ok {
if instanceCPU := info.CPU; minCPU == 0 || instanceCPU < minCPU {
minCPU = instanceCPU
}
if instanceRAM := info.Memory; minRAM == 0 || instanceRAM < minRAM {
minRAM = instanceRAM
}
}
}
if minCPU > 0 && minRAM > 0 {
info = InstanceTypeInfo{
Memory: minRAM,
CPU: minCPU,
}
if _, ok := obj["kubeReserved"]; !ok {
obj["kubeReserved"] = api.InlineDocument{}
}
obj["kubeReserved"] = getKubeReserved(info)
}
}

// Add extra configuration from configfile
Expand Down
85 changes: 85 additions & 0 deletions pkg/nodebootstrap/userdata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ var _ = Describe("User data", func() {
Expect(errUnmarshal).ToNot(HaveOccurred())
})

It("does not contain default kube reservations for unknown instances", func() {
ng.InstanceType = "dne.small"
data, err := makeKubeletConfigYAML(clusterConfig, ng)
Expect(err).ToNot(HaveOccurred())

kubelet := kubeletapi.KubeletConfiguration{}
err = yaml.UnmarshalStrict(data, &kubelet)
Expect(err).ToNot(HaveOccurred())
Expect(kubelet.KubeReserved).To(BeNil())
})

It("contains default kube reservations", func() {
ng.InstanceType = "i3.metal"
data, err := makeKubeletConfigYAML(clusterConfig, ng)
Expand All @@ -61,8 +72,76 @@ var _ = Describe("User data", func() {
}))
})

It("contains default kube reservations for mixed instance NGs", func() {
ng.InstancesDistribution = &api.NodeGroupInstancesDistribution{}
ng.InstancesDistribution.InstanceTypes = []string{
"c5.xlarge",
"c5.2xlarge",
"c5.4xlarge",
}
data, err := makeKubeletConfigYAML(clusterConfig, ng)
Expect(err).ToNot(HaveOccurred())

kubelet := kubeletapi.KubeletConfiguration{}
err = yaml.UnmarshalStrict(data, &kubelet)
Expect(err).ToNot(HaveOccurred())
Expect(kubelet.KubeReserved).To(Equal(map[string]string{
"ephemeral-storage": "1Gi",
"cpu": "80m",
"memory": "1843Mi",
}))
})

It("contains default kube reservations for mixed instance NGs with at least one known instance type", func() {
ng.InstancesDistribution = &api.NodeGroupInstancesDistribution{}
ng.InstancesDistribution.InstanceTypes = []string{
"c5.xlarge",
"dne.small",
"dne.large",
}
data, err := makeKubeletConfigYAML(clusterConfig, ng)
Expect(err).ToNot(HaveOccurred())

kubelet := kubeletapi.KubeletConfiguration{}
err = yaml.UnmarshalStrict(data, &kubelet)
Expect(err).ToNot(HaveOccurred())
Expect(kubelet.KubeReserved).To(Equal(map[string]string{
"ephemeral-storage": "1Gi",
"cpu": "80m",
"memory": "1843Mi",
}))
})

It("the kubelet config contains the overwritten values", func() {
ng.KubeletExtraConfig = &api.InlineDocument{
"kubeReserved": &map[string]string{
"cpu": "300m",
"memory": "300Mi",
"ephemeral-storage": "1Gi",
},
"featureGates": map[string]bool{
"HugePages": false,
"DynamicKubeletConfig": true,
},
}
data, err := makeKubeletConfigYAML(clusterConfig, ng)
Expect(err).ToNot(HaveOccurred())

kubelet := &kubeletapi.KubeletConfiguration{}

errUnmarshal := yaml.UnmarshalStrict(data, kubelet)
Expect(errUnmarshal).ToNot(HaveOccurred())

Expect(kubelet.KubeReserved).ToNot(BeNil())
Expect(kubelet.KubeReserved["cpu"]).To(Equal("300m"))
Expect(kubelet.KubeReserved["memory"]).To(Equal("300Mi"))
Expect(kubelet.KubeReserved["ephemeral-storage"]).To(Equal("1Gi"))
Expect(kubelet.FeatureGates["HugePages"]).To(Equal(false))
Expect(kubelet.FeatureGates["DynamicKubeletConfig"]).To(Equal(true))
Expect(kubelet.FeatureGates["RotateKubeletServerCertificate"]).To(Equal(false))
})

It("the kubelet config contains the overwritten values for mixed instance NGs", func() {
ng.KubeletExtraConfig = &api.InlineDocument{
"kubeReserved": &map[string]string{
"cpu": "300m",
Expand All @@ -74,6 +153,12 @@ var _ = Describe("User data", func() {
"DynamicKubeletConfig": true,
},
}
ng.InstancesDistribution = &api.NodeGroupInstancesDistribution{}
ng.InstancesDistribution.InstanceTypes = []string{
"c5.xlarge",
"c5.2xlarge",
"c5.4xlarge",
}
data, err := makeKubeletConfigYAML(clusterConfig, ng)
Expect(err).ToNot(HaveOccurred())

Expand Down
32 changes: 20 additions & 12 deletions userdocs/src/usage/customizing-the-kubelet.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@

## Customizing kubelet configuration

System resources can be reserved through the configuration of the kubelet. This is recommended, because in the case
System resources can be reserved through the configuration of the kubelet. This is recommended, because in the case
of resource starvation the kubelet might not be able to evict pods and eventually make the node become `NotReady`. To
do this, config files can include the `kubeletExtraConfig` field which accepts a free form yaml that will be embedded
do this, config files can include the `kubeletExtraConfig` field which accepts a free form yaml that will be embedded
into the `kubelet.yaml`.

Some fields in the `kubelet.yaml` are set by eksctl and therefore are not overwritable, such as the `address`,

Some fields in the `kubelet.yaml` are set by eksctl and therefore are not overwritable, such as the `address`,
`clusterDomain`, `authentication`, `authorization`, or `serverTLSBootstrap`.

The following example config file creates a nodegroup that reserves `300m` vCPU, `300Mi` of memory and `1Gi` of
ephemeral-storage for the kubelet; `300m` vCPU, `300Mi` of memory and `1Gi`of ephemeral storage for OS system
daemons; and kicks in eviction of pods when there is less than `200Mi` of memory available or less than 10% of the
The following example config file creates a nodegroup that reserves `300m` vCPU, `300Mi` of memory and `1Gi` of
ephemeral-storage for the kubelet; `300m` vCPU, `300Mi` of memory and `1Gi`of ephemeral storage for OS system
daemons; and kicks in eviction of pods when there is less than `200Mi` of memory available or less than 10% of the
root filesystem.

```yaml
Expand Down Expand Up @@ -48,12 +48,20 @@ nodeGroups:

In this example, given instances of type `m5a.xlarge` which have 4 vCPUs and 16GiB of memory, the `Allocatable` amount
of CPUs would be 3.4 and 15.4 GiB of memory. In addition, the `DynamicKubeletConfig` feature gate is also enabled. It is
important to know that the values specified in the config file for the the fields in `kubeletExtraconfig` will
important to know that the values specified in the config file for the the fields in `kubeletExtraconfig` will
completely overwrite the default values specified by eksctl. However, omitting one or more `kubeReserved` parameters
will cause the missing parameters to be defaulted to sane values based on the aws instance type being used.

!!!warning
By default `eksctl` sets `featureGates.RotateKubeletServerCertificate=true`, but when custom `featureGates` are provided, it will be unset. You should always include
`featureGates.RotateKubeletServerCertificate=true`, unless you have to disable it.

### A note on the `kubeReserved` calculation for NodeGroups with mixed instances

While it is generally recommended to configure a mixed instance NodeGroup to use instances with the same CPU and RAM
configuration; that's not a strict requirement. Therefore the `kubeReserved` calculation uses the _smallest instance_ in
the `InstanceDistribution.InstanceTypes` field. This way NodeGroups with disparate instance types will not reserve too
many resources on the smallest instance. However, this could lead to a reservation that is too small for the largest
instance type.

!!! Warning
By default `eksctl` sets `featureGates.RotateKubeletServerCertificate=true`, but when custom `featureGates` are
provided, it will be unset. You should always include `featureGates.RotateKubeletServerCertificate=true`, unless
you have to disable it.