Repository navigation
Graduating Ingress to V1 - #88041
Graduating Ingress to V1#88041robscott wants to merge 5 commits into
Conversation
|
This PR may require API review. If so, when the changes are ready, complete the pre-review checklist and request an API review. Status of requested reviews is tracked in the API Review project. |
There was a problem hiding this comment.
Can we inline this since we don't have a docstring associated with this?
There was a problem hiding this comment.
This pattern actually came from what I was doing for EndpointSlice validation. I was initially using inlined vars here, but Tim recommended moving to this style in that API review. I've grown to appreciate key values like this being highly visible like this, but can switch back to inline if that's preferred.
There was a problem hiding this comment.
we should be more specific concerning marking as default. We don't want to be mysterious here, people will have a hard time finding it.
There was a problem hiding this comment.
I'm not sure what Bowei's means here but marking an IngressClass as default works only if most of the implementations do respect this. If that's not the case, then you end up in a situation where user expects one thing, based on the k8s core contract but the reality is different.
We should force all controllers to respect it. Maybe this goes into the KEP.
There was a problem hiding this comment.
@bowei I have more specific documentation on the IngressClass resource - it was missing in one of the versions when you reviewed this. I was hoping to avoid duplicating that copy on both the Class field and the Class resource, but I can do that if you think it will help make this easier to understand.
@hbagdi This was a last minute addition to the KEP per Tim's suggestion. It mirrors the behavior of StorageClass. An admission controller watches Ingresses and sets a default value for Class if it's not already specified on the Ingress and there is an IngressClass resource that has been marked as default.
There was a problem hiding this comment.
I'm not saying the addition doesn't make sense and the behavior is incorrect.
But, this:
Implementations of this API may choose to ignore Ingresses without a class specified.
This seems like a problematic may to me.
We should define the behavior of controllers around what to do when the class is not set at all. If one controller ignores them, and user installs another one, and it takes over those Ingress resources, then that leads to a bad user experience. The guidelines here should be more strict.
There was a problem hiding this comment.
Yeah, I agree that it would be nice if we could be more specific here. Unfortunately we're limited to some extent by what already exists. Ingresses have already been valid without a class specified, and we can't make that use case invalid and still maintain backwards compatibility. At the same time, the behavior when this was unspecified was also not specified and therefore was inconsistent, so we're limited with what we can say here without requiring behavior to change dramatically here.
If there were a way to require class to be specified here, that sure would be ideal, but I just don't think we can unfortunately. I'm also open to ideas for how we could further specify how the lack of a class should be handled, I just haven't found anything that works well within the constraints we have (especially backwards compatibility).
There was a problem hiding this comment.
However, this is a V1 spec for which (AFAIK) every ingress controller will need code changes to support (even if it is just adding informers for the new types). This seems like the best opportunity to make another incompatible change :)
There was a problem hiding this comment.
James, the problem is that Ingress has been v1beta1 for so long that is essentially a GA API, and beta is only in the name. People rely on it like a v1 GA API and breaking the behavior will not go well for most users of the community.
Rob, you have a good point.
I get that we can't enforce things in the API but we can have stricter guidelines.
A solution I can think of is to replace may with should and then detail on the behavior we want implementors to follow.
There was a problem hiding this comment.
👍 I like that idea, thanks! I've replaced the may with should and I think that at least gets us closer to the goal here.
There was a problem hiding this comment.
tests can become fragile if you test for the error message
There was a problem hiding this comment.
That's definitely true, in this case I thought it was worthwhile though. This is an admission controller, and it's important not just that it prevents invalid configuration, but that it includes a clear explanation for why the configuration is invalid. In this case, this is testing an error message that is specific to this admission controller, something that would only change if the admission controller itself changed.
0464e11 to
618949f
Compare
|
If there is an specific request for SIG api-machinery to review, please tag me and let us know |
There was a problem hiding this comment.
typo: assigned to this
What should be the behavior when two Ingress classes have this behavior?
As simple as two controllers fight over it sounds good enough.
There was a problem hiding this comment.
NVM. Found that we do have validation around this.
There was a problem hiding this comment.
Yeah this is all just running through an admission controller that mirrors what was already done for StorageClass. If there are more than 1 IngressClasses marked as default in a cluster, we actually block creation of Ingresses until that has been resolved. I think a link will break when I push again, but this is all in plugin/pkg/admission/ingressclass.
There was a problem hiding this comment.
I'm not sure what Bowei's means here but marking an IngressClass as default works only if most of the implementations do respect this. If that's not the case, then you end up in a situation where user expects one thing, based on the k8s core contract but the reality is different.
We should force all controllers to respect it. Maybe this goes into the KEP.
There was a problem hiding this comment.
Question for my learning: Why do we add the new fields to older resources here?
Is it for some round-tripping behavior?
There was a problem hiding this comment.
We do this in networking.v1beta1 as well. 🤔
There was a problem hiding this comment.
Yep, it's a strange requirement to enable round-tripping as I understand it. Definitely makes this PR pretty repetitive.
There was a problem hiding this comment.
Thank you!
Just to make sure, if one goes through the upgrade, and the old and new resource both are available, then magically your old resource (extensions.Ingress) have the new fields as well, correct?
I'm not saying this is a problem but just want to understand this internal detail.
71bf126 to
2c6d391
Compare
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: robscott The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
9ac526b to
2a4fb56
Compare
|
/retest |
Per the Ingress v1 KEP, we are renaming the backend field in IngressSpec to clarify its intended use. Appropriate defaults are added to ensure backend is converted to the new field type. ingress: validate backend based on schema.GroupVersion Signed-off-by: Christopher M. Luciano <[email protected]>
Signed-off-by: Christopher M. Luciano <[email protected]>
|
/retest |
|
@thockin and @liggitt, whenever you have time, this PR is ready for a full API review. I believe it includes all the changes included in the associated Ingress V1 KEP now. Individual pieces of this enhancement have been reviewed independently in other smaller PRs, but now everything is combined and ready for 1 big review. |
| // IngressClass documentation. | ||
| // +optional | ||
| Backend *IngressBackend | ||
| Class *string |
There was a problem hiding this comment.
other places we have names of referenced objects, we call it <kind>Name, e.g.:
- pod.spec.runtimeClassName
- pod.spec.priorityClassName
- pod.spec.serviceAccountName
- pvc.spec.storageClassName
Is there a reason not to call this IngressClassName? That would also avoid potential pitfalls of generated clients in various languages choking on a field name that is a common keyword.
There was a problem hiding this comment.
Do we also expect ingress controllers to just watch for Ingress objects that match their class name? If so, we should add fieldSelector support for this (so a controller could list/watch ingresses?fieldSelector=spec.ingressClassName=foo, for example).
To do that:
- Make methods in the ingress strategy to:
- Assign the match predicate function as the ingress storage PredicateFunc (example)
- Call AddFieldLabelConversionFunc for each external version (example), mapping the fieldSelector field names to the internal field names. In this case, the external and shared field names would be identical:
metadata.name,metadata.namespace, andspec.ingressClassName(or whatever we end up naming the field)
When that's done, the following should work:
kubectl get ingresses.v1beta1.extensions --field-selector=metadata.name=$myname
kubectl get ingresses.v1beta1.extensions --field-selector=metadata.namespace=$mynamespace
kubectl get ingresses.v1beta1.extensions --field-selector=spec.ingressClassName=$myclassname
kubectl get ingresses.v1beta1.networking.k8s.io --field-selector=metadata.name=$myname
kubectl get ingresses.v1beta1.networking.k8s.io --field-selector=metadata.namespace=$mynamespace
kubectl get ingresses.v1beta1.networking.k8s.io --field-selector=spec.ingressClassName=$myclassname
kubectl get ingresses.v1.networking.k8s.io --field-selector=metadata.name=$myname
kubectl get ingresses.v1.networking.k8s.io --field-selector=metadata.namespace=$mynamespace
kubectl get ingresses.v1.networking.k8s.io --field-selector=spec.ingressClassName=$myclassname
There was a problem hiding this comment.
Agree that ingressClassName works better here, will work on changing that.
The relationship between class name and controllers is not as straightforward as it might seem. The class name here refers to the name of the IngressClass resource, which then specifies the controller name. The idea was to allow for a single controller to implement multiple classes of ingress, such as external and internal load balancers. So in this case, we'd likely want IngressClass to be selectable in this same way. At that point, controllers would watch IngressClass resources with their controller name, then modify their watch on Ingress resources to include any with those class names. Does that sound reasonable?
| // Spec is the desired state of the IngressClass. | ||
| // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status | ||
| // +optional | ||
| Spec IngressClassSpec |
There was a problem hiding this comment.
Spec without status is interesting... no other *Class types I see have a spec/status split. Is there a particular future use case you have in mind here?
There was a problem hiding this comment.
We originally proposed a status as part of this resource but pulled it for now with the idea that it could be added in the future. We want this resource to mirror the GatewayClass resource as closely as possible, and the status there is still a work in progress. Ideally once that stabilized, we'd bring the same status back into IngressClass. The two major things we want to enable a controller to indicate are:
- That it is implementing a given class
- That parameters associated with the given class are valid/invalid
That may evolve as the discussion around GatewayClass evolves, but we wanted to leave room for status here as well.
|
|
||
| func SetDefaults_HTTPIngressPath(obj *networkingv1.HTTPIngressPath) { | ||
| if obj.PathType == nil { | ||
| obj.PathType = &defaultPathType |
There was a problem hiding this comment.
Is this the default we want for v1 or do we want to require pathType to be explicit when creating via v1 (no default, make required in validation)?
There was a problem hiding this comment.
Although I'd initially been working towards making pathType a required field with no default, we ended up deciding against that to avoid making such a significant change as part of a v1 release. This would just make it more work for end users to move to the v1 API - this field would need to be set multiple times (1 per path) for each Ingress.
I was also hesitant to drag out the Ingress v1 graduation process as the work on Service APIs was gaining momentum. As I understand it, requiring the pathType field would require another beta release in 1.18 before we could reach v1 in 1.19. I think there's relatively minimal value gained by that approach. For more context:
There was a problem hiding this comment.
This would just make it more work for end users to move to the v1 API - this field would need to be set multiple times (1 per path) for each Ingress.
Propagating an ambiguous meaning of path as a default to v1 doesn't seem great.
As I understand it, requiring the pathType field would require another beta release in 1.18 before we could reach v1 in 1.19.
If the v1beta1 versions default it, and v1 does not, then validation can require it without affecting v1beta1 API users. No additional release is required.
| Stub: `{"metadata": {"name": "ingress1"}, "spec": {"backend": {"serviceName": "service", "servicePort": 5000}}}`, | ||
| ExpectedEtcdPath: "/registry/ingress/" + namespace + "/ingress1", | ||
| ExpectedGVK: gvkP("networking.k8s.io", "v1beta1", "Ingress"), | ||
| ExpectedGVK: gvkP("networking.k8s.io", "v1", "Ingress"), |
There was a problem hiding this comment.
revert this to v1beta1 for 1.18
| resources := []schema.GroupVersionResource{ | ||
| batch.Resource("cronjobs").WithVersion("v1beta1"), | ||
| networking.Resource("ingresses").WithVersion("v1beta1"), | ||
| networking.Resource("ingresses").WithVersion("v1"), |
There was a problem hiding this comment.
This can't change to v1 until v1.19 (open a tracking issue to switch it). Changing it in a single release means writes via a 1.18 server in a skewed multi-API-server cluster will make ingresses unreadable by a 1.17 server that doesn't understand v1. The etcd storage integration test catches this and should change back to expecting v1beta1 to be stored for 1.18.
| func (a *claimDefaulterPlugin) Admit(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error { | ||
| if attr.GetResource().GroupResource() != networkingv1.Resource("ingresses") { | ||
| fmt.Println(attr.GetResource().GroupResource()) | ||
| fmt.Println(networkingv1.Resource("ingresses")) |
| ingress, ok := attr.GetObject().(*networkingv1.Ingress) | ||
| // if we can't convert then we don't handle this object so just return | ||
| if !ok { | ||
| return nil |
There was a problem hiding this comment.
If we get here this is an error we should log (we thought we recognized the resource, but the object type did not match).
That also means there are no integration or e2e tests verifying this works, since all actual invocations of this admission plugin would have returned here
| // Admit sets the default value of a Ingress's class if the user did not specify | ||
| // a class. | ||
| func (a *claimDefaulterPlugin) Admit(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error { | ||
| if attr.GetResource().GroupResource() != networkingv1.Resource("ingresses") { |
There was a problem hiding this comment.
is this intentionally only setting the class field for requests made to the v1 API? I expected it for requests to the v1beta1 APIs as well
| return nil | ||
| } | ||
|
|
||
| ingress, ok := attr.GetObject().(*networkingv1.Ingress) |
There was a problem hiding this comment.
admission deals with internal types, so this will be a networking.Ingress object
| @@ -67,11 +69,14 @@ func (ingressStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Ob | |||
|
|
|||
There was a problem hiding this comment.
If an update of an ingress with a spec.class value drops the spec.class value in the new incoming object (which a client using an old version of client-go would do), should we preserve the existing value in PrepareForUpdate? alternately, should the ingressclass admission plugin also populate the default class on update for ingresses that don't have it or the class annotation set?
|
Add deprecation notices to networking.k8s.io/v1beta1 Ingress (and update the deprecation notice in extensions/v1beta1 Ingress to point to v1):
And add matching deprecation language to the release-note in the PR description |
|
@robscott: The following test failed, say
Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here. |
|
Talked with @robscott today about the new fields added (pathType and class). When we've promoted objects to v1 in the past, we've done so with the previous release able to fully round-trip all fields. That ensures anyone using the v1 API can use all fields without data loss regardless of skew/rollback scenarios. By promoting to v1 and adding new fields at the same time, skew/rollback scenarios drop data. We've worked hard to avoid that in stable APIs since 1.14. Options:
|
| // Parameters is a link to a resource containing additional configuration | ||
| // for the controller. This is optional if the controller does not require | ||
| // extra parameters. Example configuration resources include | ||
| // `core.ConfigMap` or a controller specific Custom Resource. |
There was a problem hiding this comment.
nit / query; does this mean:
| // `core.ConfigMap` or a controller specific Custom Resource. | |
| // `core.ConfigMap` or a controller specific CustomResourceDefinition. |
?
sftim
left a comment
There was a problem hiding this comment.
A set of nits around naming.
I recommend writing TLS instead of SSL. It's true that people often write these terms interchangeably; however, TLS is now about 20 years old; I'd be happy to see “SSL” laid to rest in formal documentation.
Also, there's no such thing as SSL SNI; you need TLS to negotiate it.
| // +optional | ||
| repeated string hosts = 1; | ||
|
|
||
| // SecretName is the name of the secret used to terminate SSL traffic on 443. |
There was a problem hiding this comment.
nit:
| // SecretName is the name of the secret used to terminate SSL traffic on 443. | |
| // SecretName is the name of the secret used to terminate TLS traffic on port 443. |
| repeated string hosts = 1; | ||
|
|
||
| // SecretName is the name of the secret used to terminate SSL traffic on 443. | ||
| // Field is left optional to allow SSL routing based on SNI hostname alone. |
There was a problem hiding this comment.
| // Field is left optional to allow SSL routing based on SNI hostname alone. | |
| // Field is left optional to allow TLS routing based on SNI hostname alone. |
SSL doesn't have SNI; TLS does.
|
|
||
| // Ingress is a collection of rules that allow inbound connections to reach the | ||
| // endpoints defined by a backend. An Ingress can be configured to give services | ||
| // externally-reachable urls, load balance traffic, terminate SSL, offer name |
There was a problem hiding this comment.
nit:
| // externally-reachable urls, load balance traffic, terminate SSL, offer name | |
| // externally-reachable urls, load balance traffic, terminate TLS, offer name |
|
|
||
| // Ingress is a collection of rules that allow inbound connections to reach the | ||
| // endpoints defined by a backend. An Ingress can be configured to give services | ||
| // externally-reachable urls, load balance traffic, terminate SSL, offer name |
There was a problem hiding this comment.
nit:
| // externally-reachable urls, load balance traffic, terminate SSL, offer name | |
| // externally-reachable urls, load balance traffic, terminate TLS, offer name |
| // Ingress, if left unspecified. | ||
| // +optional | ||
| Hosts []string `json:"hosts,omitempty" protobuf:"bytes,1,rep,name=hosts"` | ||
| // SecretName is the name of the secret used to terminate SSL traffic on 443. |
There was a problem hiding this comment.
| // SecretName is the name of the secret used to terminate SSL traffic on 443. | |
| // SecretName is the name of the secret used to terminate TLS traffic on port 443. |
| // +optional | ||
| Hosts []string `json:"hosts,omitempty" protobuf:"bytes,1,rep,name=hosts"` | ||
| // SecretName is the name of the secret used to terminate SSL traffic on 443. | ||
| // Field is left optional to allow SSL routing based on SNI hostname alone. |
There was a problem hiding this comment.
| // Field is left optional to allow SSL routing based on SNI hostname alone. | |
| // Field is left optional to allow TLS routing based on SNI hostname alone. |
| @@ -11527,6 +11572,239 @@ | |||
| ], | |||
| "type": "object" | |||
| }, | |||
| "io.k8s.api.networking.v1.Ingress": { | |||
| "description": "Ingress is a collection of rules that allow inbound connections to reach the endpoints defined by a backend. An Ingress can be configured to give services externally-reachable urls, load balance traffic, terminate SSL, offer name based virtual hosting etc.", | |||
There was a problem hiding this comment.
| "description": "Ingress is a collection of rules that allow inbound connections to reach the endpoints defined by a backend. An Ingress can be configured to give services externally-reachable urls, load balance traffic, terminate SSL, offer name based virtual hosting etc.", | |
| "description": "Ingress is a collection of rules that allow inbound connections to reach the endpoints defined by a backend. An Ingress can be configured to give services externally-reachable urls, load balance traffic, terminate TLS, offer name based virtual hosting etc.", |
| "type": "array" | ||
| }, | ||
| "secretName": { | ||
| "description": "SecretName is the name of the secret used to terminate SSL traffic on 443. Field is left optional to allow SSL routing based on SNI hostname alone. If the SNI host in a listener conflicts with the \"Host\" header field used by an IngressRule, the SNI host is used for termination and value of the Host header is used for routing.", |
There was a problem hiding this comment.
| "description": "SecretName is the name of the secret used to terminate SSL traffic on 443. Field is left optional to allow SSL routing based on SNI hostname alone. If the SNI host in a listener conflicts with the \"Host\" header field used by an IngressRule, the SNI host is used for termination and value of the Host header is used for routing.", | |
| "description": "SecretName is the name of the secret used to terminate TLS traffic on port 443. Field is left optional to allow TLS routing based on SNI hostname alone. If the SNI host in a listener conflicts with the \"Host\" header field used by an IngressRule, the SNI host is used for termination and value of the Host header is used for routing.", |
| } | ||
|
|
||
| var map_Ingress = map[string]string{ | ||
| "": "Ingress is a collection of rules that allow inbound connections to reach the endpoints defined by a backend. An Ingress can be configured to give services externally-reachable urls, load balance traffic, terminate SSL, offer name based virtual hosting etc.", |
There was a problem hiding this comment.
nit:
| "": "Ingress is a collection of rules that allow inbound connections to reach the endpoints defined by a backend. An Ingress can be configured to give services externally-reachable urls, load balance traffic, terminate SSL, offer name based virtual hosting etc.", | |
| "": "Ingress is a collection of rules that allow inbound connections to reach the endpoints defined by a backend. An Ingress can be configured to give services externally-reachable urls, load balance traffic, terminate TLS, offer name based virtual hosting etc.", |
| var map_IngressTLS = map[string]string{ | ||
| "": "IngressTLS describes the transport layer security associated with an Ingress.", | ||
| "hosts": "Hosts are a list of hosts included in the TLS certificate. The values in this list must match the name/s used in the tlsSecret. Defaults to the wildcard host setting for the loadbalancer controller fulfilling this Ingress, if left unspecified.", | ||
| "secretName": "SecretName is the name of the secret used to terminate SSL traffic on 443. Field is left optional to allow SSL routing based on SNI hostname alone. If the SNI host in a listener conflicts with the \"Host\" header field used by an IngressRule, the SNI host is used for termination and value of the Host header is used for routing.", |
There was a problem hiding this comment.
nit:
| "secretName": "SecretName is the name of the secret used to terminate SSL traffic on 443. Field is left optional to allow SSL routing based on SNI hostname alone. If the SNI host in a listener conflicts with the \"Host\" header field used by an IngressRule, the SNI host is used for termination and value of the Host header is used for routing.", | |
| "secretName": "SecretName is the name of the secret used to terminate TLS traffic on port 443. Field is left optional to allow TLS routing based on SNI hostname alone. If the SNI host in a listener conflicts with the \"Host\" header field used by an IngressRule, the SNI host is used for termination and value of the Host header is used for routing.", |
|
Closing this PR in favor of separate PRs. One for IngressClass has already been created, while @cmluciano is working on another one covering PathType and additional backend types. All the feedback here is being incorporated into those PRs. /close |
|
@robscott: Closed this PR. DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
What type of PR is this?
/kind api-change
What this PR does / why we need it:
This graduates Ingress to v1.
Does this PR introduce a user-facing change?:
Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.:
/cc @bowei @cmluciano
/sig network
/priority important-soon