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

Add helpers for iterating containers in a pod #79176

Merged
merged 2 commits into from
Jun 25, 2019

Conversation

verb
Copy link
Contributor

@verb verb commented Jun 19, 2019

What type of PR is this?

/kind cleanup

What this PR does / why we need it: Iterating through all of the containers in a pod is common, and code is often duplicated to iterate through both Containers and InitContainers. This will get worse with additional container types such as Ephemeral Containers.

This PR adds helpers based on the existing VisitPodConfigmapNames/VisitPodSecretNames. There are two flavors: one utility supporting early exit and one supporting pathspec intended for use with validation.

In addition to adding VisitContainers(), this PR updates many of the instances where Containers and InitContainers were iterated in immediate succession. This PR explicitly avoids updating the kubelet because of the frequency with which the kubelet treats Containers and InitContainers differently. I will follow up with a separate PR to a sig-node reviewer to update the kubelet.

Which issue(s) this PR fixes:
WIP #27140

Special notes for your reviewer:

Does this PR introduce a user-facing change?:

NONE

@k8s-ci-robot k8s-ci-robot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. release-note-none Denotes a PR that doesn't merit a release note. kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. needs-priority Indicates a PR lacks a `priority/foo` label and requires one. area/release-eng Issues or PRs related to the Release Engineering subproject sig/apps Categorizes an issue or PR as relevant to SIG Apps. sig/auth Categorizes an issue or PR as relevant to SIG Auth. sig/release Categorizes an issue or PR as relevant to SIG Release. sig/storage Categorizes an issue or PR as relevant to SIG Storage. and removed needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. labels Jun 19, 2019
@verb verb force-pushed the debug-iterate-containers branch from a7a5c0d to e9dcb9f Compare June 19, 2019 17:39
@verb verb changed the title WIP: Add helpers for iterating containers in a pod Add helpers for iterating containers in a pod Jun 19, 2019
@k8s-ci-robot k8s-ci-robot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jun 19, 2019
@verb
Copy link
Contributor Author

verb commented Jun 19, 2019

This PR forked from #59416

/assign @smarterclayton

pkg/api/pod/util.go Outdated Show resolved Hide resolved
for _, ctr := range pod.Spec.Containers {
if err := safeToApplyPodPresetsOnContainer(&ctr, podPresets); err != nil {
pods.VisitContainers(&pod.Spec, func(c *api.Container, _ *field.Path) {
if err := safeToApplyPodPresetsOnContainer(c, podPresets); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to my earlier comment, how would a reviewer tell this case apart from the "get error out of the closure" case? Structurally if the only difference is a single : I worry that it's not obvious.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Maybe using something that looks like err across the boundary of a closure is an anti-pattern. Perhaps err should be reserved for local scope since that's how it's used 95% of the time.

Requiring a more verbose retErr or allErrs could signal the reviewer to the difference.

I've updated all instances of this in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reflecting on it a bit further, an even more defensive method would be to use both:

var retErr error
podutil.VisitContainers(&pod.Spec, func(c *v1.Container) bool {
  if err := checkContainer(c); err != nil {
    retErr = err
    return false
  }
  return true
})

I don't think this is really necessary, though, so I didn't include it in my update.

Copy link
Contributor

@smarterclayton smarterclayton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some comments

@verb verb force-pushed the debug-iterate-containers branch from e9dcb9f to a0b57ad Compare June 21, 2019 08:39
}
})
if len(allErrs) > 0 {
// TODO: consider using utilerrors.NewAggregate(allErrs)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smarterclayton I didn't want to change the behavior of this plugin without discussing it with someone, but I noticed other plugins use utilerrors.NewAggregate(allErrs) rather than returning the first error.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm, if we do this then we have to evaluate all images on all pods, vs just the first invalid one. However, in a successful case we have to check all of them.

I'm ok with the change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But can you open a follow up and we can get the plugin authors to discuss?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened #79378

if c.Name == containerName {
return true
hasContainer = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunate here that we miss early exit

@smarterclayton
Copy link
Contributor

/approve
/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Jun 25, 2019
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: smarterclayton, verb

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jun 25, 2019
@k8s-ci-robot k8s-ci-robot merged commit 1215aa7 into kubernetes:master Jun 25, 2019
@tedyu
Copy link
Contributor

tedyu commented Aug 21, 2019

For some use case, such as PodRequestsAndLimits() :

for _, container := range pod.Spec.InitContainers {
   maxResourceList(reqs, container.Resources.Requests)
   maxResourceList(limits, container.Resources.Limits)

The operation performed on init container is different from that performed on normal container.

@verb @smarterclayton
Do you think it is worth adding a parameter, on top of *v1.Container, which indicates the type of container: normal, init, etc.

Here is a sketch:

https://pastebin.com/6xb0PUG1

@verb verb deleted the debug-iterate-containers branch August 22, 2019 08:42
@verb
Copy link
Contributor Author

verb commented Aug 22, 2019

tl;dr I'm not sure it's common enough to warrant the added complexity, but I could be convinced.

@tedyu I'm in favor of making VisitContainer more useful because it reduces repetition when one wants to iterate through all (current and future) container types, but in this case there's no repeated code to remove.

So far your example seems representative, so we'd replace:

for _, container := range pod.Spec.Containers {
	addResourceList(reqs, container.Resources.Requests)
	addResourceList(limits, container.Resources.Limits)
}
// init containers define the minimum of any resource
for _, container := range pod.Spec.InitContainers {
	maxResourceList(reqs, container.Resources.Requests)
	maxResourceList(limits, container.Resources.Limits)
}

with:

podutil.VisitContainers(&pod.Spec, func(c *Container, t ContainerType) bool {
	switch t {
	case podutil.Container:
		addResourceList(reqs, container.Resources.Requests)
		addResourceList(limits, container.Resources.Limits)
	// init containers define the minimum of any resource
	case podutil.InitContainer:
		maxResourceList(reqs, container.Resources.Requests)
		maxResourceList(limits, container.Resources.Limits)
	}
	return true
})

I usually don't like this because we've introduced a Kubernetes-specific abstraction for no real reason. Any Go programmer can read the range statements, but they'd have to know about VisitContainers() specifically, and introducing closures adds room for bugs.

That being said, it can be easily inferred what VisitContainers does here and I think it might even be more readable if you're familiar with VisitContainers. It would make adding a new container type safer because we can easily reference all locations we may need to update for the new type.

For me it really hinges on whether it would see use in places that frequently iterate the container lists to different effect, like the kubelet. @yujuhong what do you think?


Side thought:

I can imagine a reviewer objecting to the unnecessary iteration of the other container types (in this case, ephemeral containers). I think that's an acceptable trade off for readability until analysis shows this to be the bottleneck, but that thinking led me to omit the early return from VisitContainersWithPath().

We could solve that with a bitmask, something like:

VisitContainers(&pod.Spec, Container|InitContainer, func(c *Container, t ContainerType) bool {
    switch t {
    case InitContainer: maxResourceList()
    ...

but I think it's probably premature optimization.

@tedyu
Copy link
Contributor

tedyu commented Aug 22, 2019

Thanks @verb for detailed reply.

For part 1, I agree with you - that's why I didn't send out PR.

My understanding is that, if operations for two out of 3 (maybe more in the future) container types is common, the new parameter would help reduce duplicate code.

I will continue looking for proper use case(s).

@tedyu
Copy link
Contributor

tedyu commented Aug 22, 2019

For part 2, I think passing bitmask allows skipping feature gate checking for new container type(s).
That seems to be a benefit.

@verb
Copy link
Contributor Author

verb commented Aug 22, 2019

It would make adding a new container type safer because we can easily reference all locations we may need to update for the new type.

Thinking about this a little more, if our goal was to consolidate container iterations for correctness, then the bitmask would be required.

@verb
Copy link
Contributor Author

verb commented Aug 23, 2019

Something for the record: @msau42 better statement the argument of using VisitContainers for correctness than I did in #81838 (comment), I think.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. area/release-eng Issues or PRs related to the Release Engineering subproject cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. lgtm "Looks good to me", indicates that a PR is ready to be merged. needs-priority Indicates a PR lacks a `priority/foo` label and requires one. release-note-none Denotes a PR that doesn't merit a release note. sig/apps Categorizes an issue or PR as relevant to SIG Apps. sig/auth Categorizes an issue or PR as relevant to SIG Auth. sig/release Categorizes an issue or PR as relevant to SIG Release. sig/storage Categorizes an issue or PR as relevant to SIG Storage. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants