-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Implement merging strategy #77
Conversation
@thetechnick I see two options on how to proceed with this pull request:
Let me know your thoughts |
@pleshakov data:
merging-strategy: NeverMerge #(emit an error on colliding ingress definitions) would be quite easy to add to the implementation, but as stated in #76 I would prefer the merging as the default setting. |
@thetechnick Rather than to enable/disable it via the ConfigMap, I suggest to do it via the controller command-line arguments. I'm not sure that letting users to change it on-the-fly is a good idea: what will happen to the existing Ingress resources when merging is enabled? This will require to add more logic to the controller to regenerate the whole configuration. The related feature is to emit errors when this option is disabled and an Ingress with the existing hostname is added. I assume that something in Configurator must track all the existing hostnames. Also, I think it's better to not change the |
@pleshakov Is there any downside to generate one file for each server object than a file for each ingress object? |
@thetechnick
looks good
I don't see any downside |
6c710f1
to
ea702c5
Compare
@pleshakov |
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.
Please see my comments
locationMap[location.Path] = location | ||
} | ||
|
||
if merge.SSL { |
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.
Every additional Ingress replaces the settings of the previous ones. I think we should keep the settings of the oldest Ingress object if they exist.
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.
I am not happy with this but it is necessary for kube-lego to work properly and is quite close to the behavior of the contrib/nginx controller.
For the first merge of the kube-lego managed ingress object this works, but not for every following one.
When you first create your ingress object, the ingress object of kube-lego will be created afterwards.
So every thing works as expected.
But if you create a new ingress object after this, it does not work anymore, because the ingress object of kube-lego is only updated to include a rule to newly created hosts. This leads to the situation that the kube-lego ingress is actually older than the "original" one. If we use the oldest ingress as a base here, all settings that you have made to your own ingress object are lost (even to enable SSL...).
This is why I have implemented this merging behavoir of "adding" settings together, so the settings are never removed in the merge, only added.
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.
The settings of new Ingress objects replace the settings of the previous objects. Can we add new settings only if the old ones don't exist?
@@ -74,7 +78,8 @@ func (cnf *Configurator) updateCertificates(ingEx *IngressEx) map[string]string | |||
|
|||
return pems | |||
} | |||
func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]string) IngressNginxConfig { | |||
|
|||
func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]string) []IngressNginxConfig { |
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.
Since we're moving to a single NGINX config per hostname, we can drop the IngressNginxConfig
type IngressNginxConfig struct {
Upstreams []Upstream
Servers []Server
}
and use Server
instead, provided that we moved the array of upstreams to the Server.
@@ -171,7 +176,18 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri | |||
servers = append(servers, server) | |||
} | |||
|
|||
return IngressNginxConfig{Upstreams: upstreamMapToSlice(upstreams), Servers: servers} | |||
result := []IngressNginxConfig{} |
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.
We can eliminate this steps. We generate Servers that include upstreams and then return those Servers
"k8s.io/kubernetes/pkg/apis/extensions" | ||
) | ||
|
||
type NeverMerger struct { |
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.
We shouldn't have the NeverMerger, because it doesn't do merging, but keeps track of hostnames and Ingress resources. This doesn't fit into merging. I suggest to add its functionality to the Configurator.
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.
Its something that implements the Merger interface and is used the same way, only the behavior is different.
I would like to leave it like this because we can switch out different implementations of the merging engine if things change.
I do not like the name, though.
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.
The fact that we have the Merger interface is great! We can easily replace one Merger with another one. However, I'd prefer the hostname checking not to be a part of it, because it's a different thing, though you can make it fit into the Merger interface.
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.
Ok so you want to check for duplicated hostnames in the generated Servers in any case?
My understanding was that the output of the Mergers Merge()
method cannot contain hostname/server name collisions anymore, because that is the whole purpose of this component.
Adding a additional collision check afterwards will only be redundant self validation, which should be put into the unittests of the Merger implementation.
Edit: We could also rename this component to something like Deduplicator
, if this matches the behavior better ;)
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.
Adding a additional collision check afterwards will only be redundant self validation, which should be put into the unittests of the Merger implementation.
I think a conditional statement in the configurator is what we need:
if merging is enabled then do the merging, using one of the implementations
else do the hostname checking, if any error -- ignore the Ingress and report the error
Edit: We could also rename this component to something like Deduplicator, if this matches the behavior better ;)
but it will still have the interface of the meger
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.
I understand what you want to do, but I do not see any good reason to do this...
if cnf.merger == nil {
// use the logic in NeverMerger, but implemented in the Configurator
}
Switching out the implementation of the interface in this case seems the most clean solution to me.
Maybe you can add a small example or explain why you want the logic to reside in the Configurator.
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.
Correct, something like this:
if cnf.merger == nil {
// use the logic in NeverMerger, but implemented in the Configurator
} else {
// use merger
}
// write config files
The logic doesn't have to be implemented in the Configurator, it can be implemented in another Component.
Yes, when using only Merger, it looks more clean and it's less code, I agree. However, the functionality of NeverMerger doesn't fit into the Merger interface. The interface is different, something like this :
AddConfigs(ingName string , configs []IngressNginxConfig) error
GetConfigNames(ingName string) ([]string, error)
If it was another Merging algorithm, then yes. But NeverMerger simple checks duplicated hostnames and keeps tract of Ingresses and corresponding configuration files.
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.
AddConfigs(ingName string , configs []IngressNginxConfig) error
GetConfigNames(ingName string) ([]string, error)
But this is as the Merger interface of:
Merge(*extensions.Ingress, []IngressNginxConfig) []IngressNginxConfig
Separate(string) ([]IngressNginxConfig, []string)
So this is essentially about the right semantic for the component(s) that are responsible for "merging". I still think that using a single component that is responsible for the "collision prevention" (however the component called), is the right way to do ...
I see two strategies for "collision prevention" here:
- Throw an error and reject colliding objects (NeverMerger)
- Merge colliding objects by rules compatible with the contrib ingress controller (OldestFirstMerger)
Those strategies and the concrete implementations need the exact same interface:
After we removed the IngressNginxConfig object: s/IngressNginxConfig/Server/
(the ingress object in RemoveConfigs can be replaced by its name "namespace/name")
// A new ingress is added, do something to prevent collisions and return the "collision free" configs I can use
AddConfigs(ing *extensions.Ingress, []IngressNginxConfig) (changed []IngressNginxConfig)
// A ingress object is removed, so colliding objects are no longer blocked.
// Return the configs that need to be rewritten (changed) or are no longer blocked by the removed ingress and
// the configs we can safely remove
RemoveConfigs(ing *extensions.Ingress) (changed []IngressNginxConfig, deleted []IngressNginxConfig)
I would like to rename the Interface and the Components to:
Merger
> CollisionHandler
NeverMerger
> DenyCollisionHandler
OldestFirstMerger
> MergingCollisionHandler
The interface of CollisionHandler
will change to the above example.
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.
I would like to rename the Interface and the Components to:
Merger > CollisionHandler
NeverMerger > DenyCollisionHandler
OldestFirstMerger > MergingCollisionHandler
The interface of CollisionHandler will change to the above example.
Looks good to me!
The methods should also return an error, so that the collision error can be returned, for example
@@ -35,8 +37,10 @@ func (cnf *Configurator) AddOrUpdateIngress(name string, ingEx *IngressEx) { | |||
defer cnf.lock.Unlock() | |||
|
|||
pems := cnf.updateCertificates(ingEx) | |||
nginxCfg := cnf.generateNginxCfg(ingEx, pems) | |||
cnf.nginx.AddOrUpdateIngress(name, nginxCfg) | |||
generatedConfigs := cnf.generateNginxCfg(ingEx, pems) |
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.
After we generate Configs we should check if any of the hostnames already exists in NGINX, if merging is disabled. If it does, the AddOrUpdateIngress should return an error and the controller should return the Ingress resource. Please see the comment about the NeverMerger.
@pleshakov |
cb9cfd2
to
ff40883
Compare
@pleshakov |
I tried to run it, but immediately got the panic: Then, the Then, once the map and the template are fixed, the following Ingress resources are causing troubles: The one with default backend: apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: cafe-ingress
spec:
backend:
serviceName: tea-svc
servicePort: 80
rules:
- host: cafe1.example.com
http:
paths:
- path: /tea
backend:
serviceName: tea-svc
servicePort: 80
- path: /coffee
backend:
serviceName: coffee-svc
servicePort: 80
- host: cafe2.example.com
http:
paths:
- path: /tea
backend:
serviceName: tea-svc
servicePort: 80
- path: /coffee
backend:
serviceName: coffee-svc
servicePort: 80 The resulting NGINX config has the following: location / {
proxy_http_version 1.1;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
client_max_body_size 1m;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering on;
proxy_pass http://; The one with empty host: apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: test-default-backend
spec:
backend:
serviceName: testsvc
servicePort: 80 For this one, the name of the generated config file is DenyCollisionHandler The approach with sorting Ingress objects by timestamp is interesting. However, I'm wondering if it is necessary. Why not to simply Ignore ingresses that contain duplicated hostnames? Also, currently you can accept some hostnames of an Ingress but reject the duplicated ones. Why not to reject such Ingresses completely with all their hostnames? |
I am so sorry, I accidentally tested a older version on my cluster, because I forgot to The nil pointer, template and the default server filename are now fixed my latest commits. |
@thetechnick If it helps you, we can have multiple pull requests: one for refactoring the config generation per server, one for the deny handler, one for the merging handler. |
@pleshakov Preferably there are unittests for the main components in place, but I cannot add them due to my own time constraints. |
@thetechnick |
Closing because of time constraints |
Is this feature implemented? |
mergeable Ingress has been implemented if that is what you are referring to: https://github.com/nginxinc/kubernetes-ingress/tree/v2.1.0/examples/mergeable-ingress-types |
I have implemented a first version to fix issue #76, some cleanup ist still needed
This pull request introduces a new component, the intermixer ("suggestions for a better name are welcome :P "), this component will act as a final layer for the generated IngressNginxConfig.
The core of this strategy is to generate a file in the conf.d/ folder for each server and not for each ingress.
After receiving the IngressNginxConfig of a new/updated ingress object, the intermixer performs the following steps:
Notes: