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 support for ingressv1.Spec.IngressClassName #2548

Closed

Conversation

stevesloka
Copy link
Member

Fixes #2146 by adding support for ingressv1.Spec.IngressClassName.

Signed-off-by: Steve Sloka [email protected]

@stevesloka stevesloka added this to the 1.5.0 milestone May 21, 2020
@stevesloka
Copy link
Member Author

I'm not in love with the package name annotations, but couldn't think up a better name. Up for discussion of ideas on that. =)

@codecov
Copy link

codecov bot commented May 21, 2020

Codecov Report

Merging #2548 into master will decrease coverage by 0.09%.
The diff coverage is 79.03%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2548      +/-   ##
==========================================
- Coverage   77.04%   76.94%   -0.10%     
==========================================
  Files          72       72              
  Lines        5723     5757      +34     
==========================================
+ Hits         4409     4430      +21     
- Misses       1226     1238      +12     
- Partials       88       89       +1     
Impacted Files Coverage Δ
cmd/contour/serve.go 2.55% <0.00%> (-0.04%) ⬇️
internal/k8s/clients.go 11.62% <ø> (ø)
internal/k8s/informers.go 0.00% <0.00%> (ø)
internal/dag/cache.go 95.93% <84.61%> (-2.24%) ⬇️
internal/annotation/annotations.go 100.00% <100.00%> (ø)
internal/dag/builder.go 91.93% <100.00%> (ø)
internal/k8s/statusaddress.go 86.95% <100.00%> (+0.14%) ⬆️

@stevesloka stevesloka removed this from the 1.5.0 milestone May 22, 2020
@stevesloka
Copy link
Member Author

I took off the 1.15 milestone since this is not critical to land in that release.

@jpeach jpeach self-requested a review May 23, 2020 00:28
Copy link
Contributor

@jpeach jpeach left a comment

Choose a reason for hiding this comment

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

If I' reading this correctly, this change looks for an Ingress class string in the spec.ingressclassname field. However, in the v1 API, this string is the name of an object of type IngressClass which represents a deployed Contour control plane.

@stevesloka
Copy link
Member Author

ha you're right @jpeach, I misread that spec. I'll get this fixed up. =)

@github-actions
Copy link

Marking this PR stale since there has been no activity for 14 days. It will be closed if there is no activity for another 90 days.

@github-actions github-actions bot added the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. label Jun 24, 2020
@stevesloka stevesloka force-pushed the ingressv1ingressclass branch from e90e82a to 4d8c977 Compare June 26, 2020 16:45
@stevesloka stevesloka removed the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. label Jun 26, 2020
@stevesloka stevesloka added this to the 1.6.0 milestone Jun 26, 2020
@stevesloka stevesloka added the release-note Denotes a PR that will be considered when it comes time to generate release notes. label Jun 26, 2020
@stevesloka stevesloka force-pushed the ingressv1ingressclass branch from 4d8c977 to 102de6f Compare June 26, 2020 16:54
@stevesloka stevesloka force-pushed the ingressv1ingressclass branch 4 times, most recently from 177b3b1 to 8b74bfb Compare June 26, 2020 18:04
@stevesloka stevesloka modified the milestones: 1.6.0, 1.7.0 Jun 29, 2020
@stevesloka stevesloka force-pushed the ingressv1ingressclass branch 2 times, most recently from 6d1d7bb to 9443d11 Compare June 30, 2020 17:26
@stevesloka
Copy link
Member Author

I'm not happy with the version check logic, going to look at implementing: #2219

@stevesloka stevesloka marked this pull request as draft June 30, 2020 19:27
@stevesloka
Copy link
Member Author

#2668 adds a dynamic lookup for resources. Once that merges, I'll rebase this PR to include that portion.

@stevesloka stevesloka marked this pull request as ready for review July 15, 2020 15:26
@stevesloka
Copy link
Member Author

The current implementation checks if the IngressClass api type exists, but doesn't set an error on the status of an object if used. The only feedback is a warning emitted when Contour starts.

This is for the use-case where a cluster doesn't have ingressClasses but a user tries to use them, which I think is an edge case but still valid.

@stevesloka stevesloka force-pushed the ingressv1ingressclass branch from 9443d11 to d98dc6c Compare July 15, 2020 16:29
@stevesloka stevesloka force-pushed the ingressv1ingressclass branch 3 times, most recently from 388cc95 to f7f7e4a Compare July 28, 2020 15:21
Copy link
Contributor

@jpeach jpeach left a comment

Choose a reason for hiding this comment

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

I made a first pass over this. I think it would really help to sketch out how we think about IngressClass deployments and how we want them to evolve over time. That larger context would help illuminate the decisions in this PR.

internal/dag/cache.go Outdated Show resolved Hide resolved
internal/dag/cache.go Outdated Show resolved Hide resolved
func MatchesIngressClass(o metav1.ObjectMetaAccessor, ic string) bool {

switch IngressClass(o) {
func MatchesIngressClass(objectClass, ic string) bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

The purpose of this function is to capture the complexity around matching resources to ingress classes. Now, that is distributed across here, (*KubernetesCache) matchesIngressClass() and (*StatusAddressUpdater) OnAdd(). Can we bring is all back so that there is exactly one place that does the ingress class match?

return *obj.Spec.IngressClassName
}
return ""
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This is what annotation.IngressClass() should do - given a generic resource, figure out and return the ingress class that it belongs to.

Copy link
Member Author

Choose a reason for hiding this comment

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

The problem is that we need to know if the annotation is defined at all, if it is then check if it matches. If it's not defined, then we can look at ingress class. That's why it's changed.

Copy link
Member

Choose a reason for hiding this comment

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

I agree that having one function that returns an object's ingress class would be really helpful since the logic is spread around a bunch now. It may need to also return a bool indicating if the class came from Spec.IngressClassName or not.

Copy link
Contributor

Choose a reason for hiding this comment

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

The problem is that we need to know if the annotation is defined at all, if it is then check if it matches. If it's not defined, then we can look at ingress class. That's why it's changed.

I think that we can still have it in one place though. The logic for IngressClass would be:

func IngressClass(o runtime.Object) string {
	a := o.GetObjectMeta().GetAnnotations()
	if class, ok := a["projectcontour.io/ingress.class"]; ok {
		return class
	}
	if class, ok := a["contour.heptio.com/ingress.class"]; ok {
		return class
	}
	if class, ok := a["kubernetes.io/ingress.class"]; ok {
		return class
	}

        switch obj := obj.(type) {
        case *v1beta1.Ingress:
                return obj.Spec.IngressClassName
        }

        return ""
}

This treats the ingress class the same no matter how it is specified. Then we can treat the issue of whether the ingress class matches our configuration separately. We may not want to propagate the default Contour matching behaviour across the the IngressClass resource.

}
if class, ok := a["kubernetes.io/ingress.class"]; ok {
return class
if class, ok := annotations["kubernetes.io/ingress.class"]; ok {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since you are working in the area, suggest adopting the v1beta1.AnnotationIngressClass constant.

}
if obj.Spec.IngressClassName != nil {
return *obj.Spec.IngressClassName
}
Copy link
Contributor

Choose a reason for hiding this comment

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

IMHO the Spec.IngressClassName should take precedence since it's not deprecated.

Copy link
Member Author

Choose a reason for hiding this comment

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

The docs used to say that annotations were to take precedence over the ingressclass, but now I can't find that reference. I know @youngnick mentioned the same on a previous call.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Yup you are right. From the spec:

// IngressClassName is the name of the IngressClass cluster resource. The
// associated IngressClass defines which controller will implement the
// resource. This replaces the deprecated kubernetes.io/ingress.class
// annotation. For backwards compatibility, when that annotation is set, it
// must be given precedence over this field. The controller may emit a
// warning if the field and annotation have different values.
// Implementations of this API should ignore Ingresses without a class
// specified.

Can we add a comment with the rationale and background?

@@ -139,13 +157,39 @@ func (kc *KubernetesCache) Insert(obj interface{}) bool {
case *v1.Service:
kc.services[k8s.NamespacedNameOf(obj)] = obj
return kc.serviceTriggersRebuild(obj)
case *v1beta1.IngressClass:
// Match only ingress classes that Contour should be able to own.
if obj.Spec.Controller == "projectcontour.io/ingress-controller" {
Copy link
Contributor

Choose a reason for hiding this comment

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

"projectcontour.io/ingress-controller" should be a named constant somewhere.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe this should be "projectcontour.io/contour" 🤷

Copy link
Member Author

Choose a reason for hiding this comment

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

The docs show it as ingress-controller and I've seen other folks do the same: https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class

…gress objects

Add support for IngressClass objects which match the controller name of 'projectcontour.io/ingress-controller'.
Any Ingress object which defines an 'Spec.IngressClassName', Contour will validate that a corresponding IngressClass
exists. Current ingress class logic for Contour is not affected and still take priority over any IngressClass logic.

Signed-off-by: Steve Sloka <[email protected]>
@stevesloka stevesloka force-pushed the ingressv1ingressclass branch from f7f7e4a to f8a0325 Compare July 29, 2020 14:08
@skriss
Copy link
Member

skriss commented Jul 29, 2020

👀

@@ -258,6 +258,13 @@ func doServe(log logrus.FieldLogger, ctx *serveContext) error {
}
}

// Check if the resource exists in the API server before setting up the informer.
if !clients.ResourceExists(k8s.IngressClassResources()...) {
log.WithField("InformOnResources", "Ingressclass Resources").Warnf("resources %v not found in api server", k8s.IngressClassResources())
Copy link
Member

Choose a reason for hiding this comment

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

this should probably be info-level since it's not required / the user hasn't explicitly opted into using it (unlike the service API types just above)

}
if obj.Spec.IngressClassName != nil {
return *obj.Spec.IngressClassName
}
Copy link
Member

Choose a reason for hiding this comment

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

if !annotation.MatchesIngressClass(typed, s.IngressClass) {
// check if the object has an annotation, which take precedence over spec.ingressclass
_, classAnnotation := annotation.IngressClass(typed.GetObjectMeta().GetAnnotations())
if !annotation.MatchesIngressClass(classAnnotation, s.IngressClass) {
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't seem like it'll correctly handle Ingresses with Spec.IngressClassName specified -- for example, if s.IngressClass != "" then won't all Ingresses that only have Spec.IngressClassName (no annotation) be filtered out here?


switch IngressClass(o) {
func MatchesIngressClass(objectClass, ic string) bool {
switch objectClass {
Copy link
Member

Choose a reason for hiding this comment

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

Not directly related to your PR, but I think this block of code would be easier to understand if we sort of inverted the logic, something like:

// if ic is specified, then the object's ingress class must match it
if ic != "" {
    return objectClass == ic
}

// if ic is empty, then the object's ingress class can either be empty
// or DEFAULT_INGRESS_CLASS
return objectClass == "" || objectClass == DEFAULT_INGRESS_CLASS

May be a matter of personal preference though, so take it or leave it.

kc.WithField("name", om.GetName()).
WithField("namespace", om.GetNamespace()).
WithField("kind", kind).
WithField("ingress-class", ic).
Copy link
Member

Choose a reason for hiding this comment

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

I think you want ic() here

return *obj.Spec.IngressClassName
}
return ""
}
Copy link
Member

Choose a reason for hiding this comment

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

I agree that having one function that returns an object's ingress class would be really helpful since the logic is spread around a bunch now. It may need to also return a bool indicating if the class came from Spec.IngressClassName or not.

// Check if this class matches a existing IngressClass.
_, ok := kc.ingressclasses[*ingressClassName]
return ok
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I thought I left a comment before, but I can't find it now :-/

IIUC, Contour will cache all IngressClass resources that match the controller name. Here, we will match any ingress class whose name matches the Spec.IngressClassName. So the result of this is that Contour will accept all resources which map to a Contour IngressClass regardless of the class name.

Did I get that right?

@youngnick youngnick removed this from the 1.7.0 milestone Aug 4, 2020
@github-actions
Copy link

Marking this PR stale since there has been no activity for 14 days. It will be closed if there is no activity for another 90 days.

@github-actions github-actions bot added the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. label Aug 19, 2020
@youngnick
Copy link
Member

I think this one won't be implementable until we've settled on a decision in #2809.

@jpeach jpeach closed this Aug 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. release-note Denotes a PR that will be considered when it comes time to generate release notes.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Ingress V1: support IngressClass
4 participants