-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Make generated pods only request the maximum necessary resources #723
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,7 @@ import ( | |
|
||
"go.uber.org/zap" | ||
corev1 "k8s.io/api/core/v1" | ||
"k8s.io/apimachinery/pkg/api/resource" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/runtime/schema" | ||
"k8s.io/client-go/kubernetes" | ||
|
@@ -74,6 +75,8 @@ var ( | |
VolumeSource: emptyVolumeSource, | ||
}} | ||
|
||
zeroQty = resource.MustParse("0") | ||
|
||
// Random byte reader used for pod name generation. | ||
// var for testing. | ||
randReader = rand.Reader | ||
|
@@ -175,6 +178,8 @@ func MakePod(taskRun *v1alpha1.TaskRun, taskSpec v1alpha1.TaskSpec, kubeclient k | |
initContainers := []corev1.Container{*cred} | ||
podContainers := []corev1.Container{} | ||
|
||
maxIndicesByResource := findMaxResourceRequest(taskSpec.Steps, corev1.ResourceCPU, corev1.ResourceMemory, corev1.ResourceEphemeralStorage) | ||
|
||
for i, step := range taskSpec.Steps { | ||
step.Env = append(implicitEnvVars, step.Env...) | ||
// TODO(mattmoor): Check that volumeMounts match volumes. | ||
|
@@ -203,6 +208,7 @@ func MakePod(taskRun *v1alpha1.TaskRun, taskSpec v1alpha1.TaskSpec, kubeclient k | |
if step.Name == names.SimpleNameGenerator.RestrictLength(fmt.Sprintf("%v%v", containerPrefix, entrypoint.InitContainerName)) { | ||
initContainers = append(initContainers, step) | ||
} else { | ||
zeroNonMaxResourceRequests(&step, i, maxIndicesByResource) | ||
podContainers = append(podContainers, step) | ||
} | ||
} | ||
|
@@ -264,3 +270,43 @@ func makeLabels(s *v1alpha1.TaskRun) map[string]string { | |
labels[pipeline.GroupName+pipeline.TaskRunLabelKey] = s.Name | ||
return labels | ||
} | ||
|
||
// zeroNonMaxResourceRequests zeroes out the container's cpu, memory, or | ||
// ephemeral storage resource requests if the container does not have the | ||
// largest request out of all containers in the pod. This is done because Tekton | ||
// overwrites each container's entrypoint to make containers effectively execute | ||
// one at a time, so we want pods to only request the maximum resources needed | ||
// at any single point in time. If no contianer has an explicit resource | ||
// request, all requests are set to 0. | ||
func zeroNonMaxResourceRequests(container *corev1.Container, containerIndex int, maxIndicesByResource map[corev1.ResourceName]int) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these functions are excellent! nice focused interface and clear short functions. My only too-late-to-the-party request would be to have unit tests covering these functions directly as well but im fully expecting to be ignored since im so late haha :D There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point! I'm going to file an issue for a followup I thought of the other day, and if I'm in this area again I'll add some unit tests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. awesome, that sounds great @dwnusbaum ❤️ !! |
||
if container.Resources.Requests == nil { | ||
container.Resources.Requests = corev1.ResourceList{} | ||
} | ||
for name, maxIdx := range maxIndicesByResource { | ||
if maxIdx != containerIndex { | ||
container.Resources.Requests[name] = zeroQty | ||
} | ||
} | ||
} | ||
|
||
// findMaxResourceRequest returns the index of the container with the maximum | ||
// request for the given resource from among the given set of containers. | ||
func findMaxResourceRequest(containers []corev1.Container, resourceNames ...corev1.ResourceName) map[corev1.ResourceName]int { | ||
maxIdxs := make(map[corev1.ResourceName]int, len(resourceNames)) | ||
maxReqs := make(map[corev1.ResourceName]resource.Quantity, len(resourceNames)) | ||
for _, name := range resourceNames { | ||
maxIdxs[name] = -1 | ||
maxReqs[name] = zeroQty | ||
} | ||
for i, c := range containers { | ||
for _, name := range resourceNames { | ||
maxReq := maxReqs[name] | ||
req, exists := c.Resources.Requests[name] | ||
if exists && req.Cmp(maxReq) > 0 { | ||
maxIdxs[name] = i | ||
maxReqs[name] = req | ||
} | ||
} | ||
} | ||
return maxIdxs | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,7 +22,6 @@ import ( | |
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/google/go-cmp/cmp/cmpopts" | ||
corev1 "k8s.io/api/core/v1" | ||
"k8s.io/apimachinery/pkg/api/resource" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
|
@@ -34,8 +33,10 @@ import ( | |
) | ||
|
||
var ( | ||
ignorePrivateResourceFields = cmpopts.IgnoreUnexported(resource.Quantity{}) | ||
nopContainer = corev1.Container{ | ||
resourceQuantityCmp = cmp.Comparer(func(x, y resource.Quantity) bool { | ||
return x.Cmp(y) == 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added this comparator in 3 places. It would be nice to have a default comparator with some helpful default settings for use in tests, but I didn't see any kind of util package or anything for that kind of thing. Is there a place for something like that? |
||
}) | ||
nopContainer = corev1.Container{ | ||
Name: "nop", | ||
Image: *nopImage, | ||
Command: []string{"/builder/tools/entrypoint"}, | ||
|
@@ -110,6 +111,13 @@ func TestMakePod(t *testing.T) { | |
Env: implicitEnvVars, | ||
VolumeMounts: implicitVolumeMounts, | ||
WorkingDir: workspaceDir, | ||
Resources: corev1.ResourceRequirements{ | ||
Requests: corev1.ResourceList{ | ||
corev1.ResourceCPU: resource.MustParse("0"), | ||
corev1.ResourceMemory: resource.MustParse("0"), | ||
corev1.ResourceEphemeralStorage: resource.MustParse("0"), | ||
}, | ||
}, | ||
}, | ||
nopContainer, | ||
}, | ||
|
@@ -149,6 +157,13 @@ func TestMakePod(t *testing.T) { | |
Env: implicitEnvVars, | ||
VolumeMounts: implicitVolumeMounts, | ||
WorkingDir: workspaceDir, | ||
Resources: corev1.ResourceRequirements{ | ||
Requests: corev1.ResourceList{ | ||
corev1.ResourceCPU: resource.MustParse("0"), | ||
corev1.ResourceMemory: resource.MustParse("0"), | ||
corev1.ResourceEphemeralStorage: resource.MustParse("0"), | ||
}, | ||
}, | ||
}, | ||
nopContainer, | ||
}, | ||
|
@@ -182,6 +197,13 @@ func TestMakePod(t *testing.T) { | |
Env: implicitEnvVars, | ||
VolumeMounts: implicitVolumeMounts, | ||
WorkingDir: workspaceDir, | ||
Resources: corev1.ResourceRequirements{ | ||
Requests: corev1.ResourceList{ | ||
corev1.ResourceCPU: resource.MustParse("0"), | ||
corev1.ResourceMemory: resource.MustParse("0"), | ||
corev1.ResourceEphemeralStorage: resource.MustParse("0"), | ||
}, | ||
}, | ||
}, | ||
nopContainer, | ||
}, | ||
|
@@ -215,6 +237,13 @@ func TestMakePod(t *testing.T) { | |
Env: implicitEnvVars, | ||
VolumeMounts: implicitVolumeMounts, | ||
WorkingDir: workspaceDir, | ||
Resources: corev1.ResourceRequirements{ | ||
Requests: corev1.ResourceList{ | ||
corev1.ResourceCPU: resource.MustParse("0"), | ||
corev1.ResourceMemory: resource.MustParse("0"), | ||
corev1.ResourceEphemeralStorage: resource.MustParse("0"), | ||
}, | ||
}, | ||
}, | ||
nopContainer, | ||
}, | ||
|
@@ -264,7 +293,7 @@ func TestMakePod(t *testing.T) { | |
t.Errorf("Pod name got %q, want %q", got.Name, wantName) | ||
} | ||
|
||
if d := cmp.Diff(&got.Spec, c.want, ignorePrivateResourceFields); d != "" { | ||
if d := cmp.Diff(&got.Spec, c.want, resourceQuantityCmp); d != "" { | ||
t.Errorf("Diff spec:\n%s", d) | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any thoughts on a better place to put this documentation or a better way to phrase it would be welcome!