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 app-root annotation support for kubernetes ingress #2522

Merged
merged 3 commits into from Feb 19, 2018

Conversation

yue9944882
Copy link
Contributor

@yue9944882 yue9944882 commented Dec 4, 2017

What does this PR do?

Rediret only root path elsewhere by ingress annotation. Like Nginx Ingress Controller documented. Implementing this annotation will only perform ReplacePath on / path on all hosts present in the annotated ingress.

ingress.kubernetes.io/app-root or traefik.ingress.kubernetes.io/app-root

Motivation

We catch this use case in production deployment.

More

  • Added/updated tests
  • Added/updated documentation

Additional Notes

Seems like many new annotation support will be added to Kubernetes provider on 1.5 release, I hope this annotation can also be supported.

@yue9944882 yue9944882 requested a review from a team as a code owner December 4, 2017 17:33
@yue9944882 yue9944882 changed the title Add app-root annotation support: Add app-root annotation support for kubernetes ingress Dec 4, 2017
@ldez
Copy link
Member

ldez commented Dec 8, 2017

WDYT @containous/kubernetes ?

@yue9944882
Copy link
Contributor Author

PTAL thx

@errm
Copy link
Contributor

errm commented Dec 8, 2017

I can see that this replicates the behaviour of the ngnix controller... but

It seems a bit strange that this would only work when the path is set to / without looking at the config the the nginx controller I would have assumed that it would be possible to set the path so that e.g. if path is /foo and app-root is /bar visiting /foo returns a 302 to /foo/bar/

I mean this does keep it consistent with how people expect this particular annotation to work so this is probably fine, and in any case I don't think implementing in this way precludes making this smarter in the future.

So um yeah I think this will be fine, but will require a note in the documentation that gets added to say this annotation only has any effect when the path is /

Other than that this seems reasonable in that it is only small change, follows the existing "defacto" standard and if not enabled has no way of effecting anything seriously.

So yeah with docs + tests I will be 👍

@timoreimann
Copy link
Contributor

What keeps us from implementing the feature such that it would also work when a path other than / is set (assuming that Nginx also works like that)?

@errm
Copy link
Contributor

errm commented Dec 9, 2017

@timoreimann
Copy link
Contributor

timoreimann commented Dec 9, 2017

@aledbf could you shed some light on why the nginx.ingress.kubernetes.io/app-root annotation only works when the root path (/) is found? Any particular reason why a path /foo is not replaced like /app-root/foo?

Thanks!

@yue9944882
Copy link
Contributor Author

yue9944882 commented Dec 9, 2017

In my case, this annotation helps when I have a home page serving at "a.com/foo" but I want to redirect all "a.com" root requests to "a.com/foo", so that every time I want to browse this page I don't need to complete the full path manually which can be painful.. And at times, especially when the full path is rather long, typos will waste time. This will mostly happen when we put webpages on some static resouce servers. e.g. Apache?Nginx?
Although this redirection is also performed by most of backend servers. But forcing it explicitly on gateway will surely be needed in some use cases.
@timoreimann @errm

@yue9944882
Copy link
Contributor Author

yue9944882 commented Dec 9, 2017

BTW, I will add some tests but IDK where can I document this annotation? Should I add documentation into code/function comment?

@timoreimann
Copy link
Contributor

timoreimann commented Dec 9, 2017

@yue9944882 Documentation of the annotation should go into docs/configuration/backends/kubernetes.md. If you think that a more elaborate description or example also makes sense, you can additionally extend the Kubernetes guide at docs/user-guide/kubernetes.md.

@yue9944882 yue9944882 force-pushed the add_kubernetes_app_root branch 2 times, most recently from f47495a to ca907db Compare December 10, 2017 05:57
@@ -158,6 +158,7 @@ The following security annotations can be applied to the ingress object to add s
| `ingress.kubernetes.io/public-key:VALUE` | Adds pinned HTST public key header. |
| `ingress.kubernetes.io/referrer-policy:VALUE` | Adds referrer policy header. |
| `ingress.kubernetes.io/is-development:false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
| `ingress.kubernetes.io/app-root:VALUE` | Explicitly make Ingress Controller redirects `/` requests to the defined path. |
Copy link
Contributor

Choose a reason for hiding this comment

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

As we're documenting the Ingress controller, can we simplify this to

Redirects all requests for / to the defined path.

and also have another sentence to clearly describe what happens for non-root paths:

Non-root paths will not be affected by this annotation and handled normally.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Make perfect sense. 👍

@@ -385,6 +386,10 @@ func getRuleForPath(pa v1beta1.HTTPIngressPath, i *v1beta1.Ingress) string {
rules = append(rules, ruleTypeReplacePath+":"+rewriteTarget)
}

// HACK: When multiple "ReplacePath" present, only the one at the last order of rule string will work
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be handled less implicitly: we should either log a warning when we observe two ReplacePath rules being used; or even log an error and ignore the Ingress in order to force the user to make a conscious decision.

I'm not sure if it's possible technically, but doesn't Traefik support applying a series of replacement rules already? In that case, we could let them resolve naturally. The downside is that it makes things more complicated to understand.

Whatever we do, we must document the behavior beyond a code comment. And the word HACK should get dropped.

@dtomcej @errm thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

https://github.com/containous/traefik/blob/328be161d6e8cd04c1066d5884e4bf35f396b40f/server/rules.go#L97
https://github.com/containous/traefik/blob/328be161d6e8cd04c1066d5884e4bf35f396b40f/server/rules.go#L184
I believe traefik doesn't support a series of replacement rules yet. And the last ReplacePath will override all the previous ReplacePath declaration because the rules is iterated as an array in the order of their occurrence in the rule-string. WDYT?

@yue9944882
Copy link
Contributor Author

@timoreimann PTAL thx

@yue9944882
Copy link
Contributor Author

yue9944882 commented Dec 10, 2017

@ldez @timoreimann @errm BTW, this PR should be a feature-request. Seems like I should have launched an issue or a discussion on Slack asking for further permission before this PR. Sorry for my negligence.

Copy link
Contributor

@timoreimann timoreimann left a comment

Choose a reason for hiding this comment

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

One more change request and one general note left.

We need to decide if we want to treat multiple replacement rules as warnings with last-rewrite-wins semantics, or as errors where we skip the offending Ingress. Having thought a bit more about it, I'm sort of leaning towards the latter with my rationale being that an explicit annotation indicates a specifically desirable behavior. Not being able to implement this behavior might be better exposed through a clear error.

I'm open to debate though.

@@ -158,6 +158,7 @@ The following security annotations can be applied to the ingress object to add s
| `ingress.kubernetes.io/public-key:VALUE` | Adds pinned HTST public key header. |
| `ingress.kubernetes.io/referrer-policy:VALUE` | Adds referrer policy header. |
| `ingress.kubernetes.io/is-development:false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
| `ingress.kubernetes.io/app-root:VALUE` | Redirects all requests for / to the defined path. On the other hand, non-root paths will not be affected by this annotation and handled normally. |
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we drop the on the other hand part? I think it's better to keep the prose to the necessary minimum in the formal documentation.

@@ -381,10 +382,18 @@ func getRuleForPath(pa v1beta1.HTTPIngressPath, i *v1beta1.Ingress) string {

rules := []string{ruleType + ":" + pa.Path}

rewrited := false
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be called rewritten.

if rewriteTarget := i.Annotations[annotationKubernetesRewriteTarget]; rewriteTarget != "" {
rules = append(rules, ruleTypeReplacePath+":"+rewriteTarget)
rewrited = true
Copy link
Contributor

Choose a reason for hiding this comment

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

I just realized that we also have the case where the rule is explicitly set to ReplacePath. We can either handle that in this PR too or do a follow up.

@yue9944882
Copy link
Contributor Author

yue9944882 commented Dec 10, 2017

@timoreimann As far as I'm concerned, if we just skip the offending Ingresses, it might cause some unexpected accident or loss especially in production environment, e.g. making one or more web-site managed in the same Ingress unavailable.

How about put an error log and ignore this annotation when at least one ReplacePath rule already exists?

@timoreimann
Copy link
Contributor

timoreimann commented Dec 10, 2017

@yue9944882 my hypothesis is that failing hard and early in case the Ingress spec isn't consistent helps surfacing the problem more quickly than if we try to make the best out of it somehow. It's true that the latter could help making the Ingress work sometimes or even most of the time but may also cause it to fail in subtle, hard-to-spot corner cases. Personally I'd expect users to verify their Ingresses function properly prior to rolling them out to production systems, which seems easier to do when they don't work at all in case some error exists.

Those are my two cents. Let's hear more voices before making a decision.

@yue9944882
Copy link
Contributor Author

@timoreimann PTAL thx

templateObjects.Frontends[baseName].Routes[pa.Path] = types.Route{
rule, err := getRuleForPath(pa, i)
if err != nil {
log.Errorf("fail to get rule from ingress path: %s", err)
Copy link
Contributor

Choose a reason for hiding this comment

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

Please update the log message as following:

log.Errorf("Failed to get rule for ingress %s/%s: %s", i.Namespace, i.Name, err)

(grammar/style of the first word + inclusion of the Ingress object's namespace and name)

templateObjects.Frontends[baseName].Routes[pa.Path] = types.Route{
rule, err := getRuleForPath(pa, i)
if err != nil {
log.Errorf("fail to get rule from ingress path: %s", err)
Copy link
Contributor

Choose a reason for hiding this comment

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

My code coverage tool tells me that this line is not tested. Could you check please?

if rewriteTarget := getStringValue(i.Annotations, annotationKubernetesRewriteTarget, ""); rewriteTarget != "" {
if pathReplaceAnnotation != "" {
return "", fmt.Errorf("rewrite-target must not be used together with annotation %q", pathReplaceAnnotation)
Copy link
Contributor

Choose a reason for hiding this comment

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

I failed to see it in my previous review round, but this part seems to be missing code coverage too.

@yue9944882
Copy link
Contributor Author

@timoreimann @ldez PTAL thx

Copy link
Contributor

@timoreimann timoreimann left a comment

Choose a reason for hiding this comment

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

LGTM. Thanks a ton! 👏

rule, err := getRuleForPath(pa, i)
if err != nil {
log.Errorf("Failed to get rule for ingress %s/%s: %s", i.Namespace, i.Name, err)
delete(templateObjects.Frontends, r.Host+pa.Path)
Copy link
Member

Choose a reason for hiding this comment

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

could you replace r.Host+pa.Path by baseName

continue
}
if rule != "" {
templateObjects.Frontends[r.Host+pa.Path].Routes[pa.Path] = types.Route{
Copy link
Member

Choose a reason for hiding this comment

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

could you replace r.Host+pa.Path by baseName

@@ -127,6 +127,7 @@ The following general annotations are applicable on the Ingress object:
| `traefik.ingress.kubernetes.io/rewrite-target: /users` | Replaces each matched Ingress path with the specified one, and adds the old path to the `X-Replaced-Path` header. |
| `traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip` | Override the default frontend rule type. Default: `PathPrefix`. |
| `traefik.ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"` | A comma-separated list of IP ranges permitted for access. all source IPs are permitted if the list is empty or a single range is ill-formatted. |
| `traefik.ingress.kubernetes.io/app-root: "/index.html"` | Redirects all requests for / to the defined path. Non-root paths will not be affected by this annotation and handled normally. This annotation may not be combined with the ReplacePath rule type or any other annotation leveraging that rule type. Trying to do so leads to an error and the corresponding Ingress object being ignored. |
Copy link
Member

Choose a reason for hiding this comment

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

 Redirects all requests for `/` to the defined path. Non-root paths will not be affected by this annotation and handled normally. This annotation may not be combined with the `ReplacePath` rule type or any other annotation leveraging that rule type. Trying to do so leads to an error and the corresponding Ingress object being ignored.

Copy link
Member

@ldez ldez left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Member

@mmatur mmatur left a comment

Choose a reason for hiding this comment

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

LGTM

Rediret only root path elsewhere by ingress annotation

Output error log when app-root and path violates

Ignore app-root when ReplacePath already exists

move docs to another section & rephrase error msg

chore: rephrase docs & errror msg
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants