Merge pull request #3341 from Shopify/canary_upstream
Add canary annotation and alternative backends for traffic shaping
This commit is contained in:
commit
17cad51e47
18 changed files with 859 additions and 23 deletions
|
|
@ -268,6 +268,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
|
|||
if host == "" {
|
||||
host = defServerName
|
||||
}
|
||||
|
||||
server := servers[host]
|
||||
if server == nil {
|
||||
server = servers[defServerName]
|
||||
|
|
@ -300,13 +301,15 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
|
|||
}
|
||||
|
||||
for _, path := range rule.HTTP.Paths {
|
||||
upsName := fmt.Sprintf("%v-%v-%v",
|
||||
ing.Namespace,
|
||||
path.Backend.ServiceName,
|
||||
path.Backend.ServicePort.String())
|
||||
upsName := upstreamName(ing.Namespace, path.Backend.ServiceName, path.Backend.ServicePort)
|
||||
|
||||
ups := upstreams[upsName]
|
||||
|
||||
// Backend is not referenced to by a server
|
||||
if ups.NoServer {
|
||||
continue
|
||||
}
|
||||
|
||||
nginxPath := rootLocation
|
||||
if path.Path != "" {
|
||||
nginxPath = path.Path
|
||||
|
|
@ -420,6 +423,11 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if anns.Canary.Enabled {
|
||||
glog.Infof("Canary ingress %v detected. Finding eligible backends to merge into.", ing.Name)
|
||||
mergeAlternativeBackends(ing, upstreams, servers)
|
||||
}
|
||||
}
|
||||
|
||||
aUpstreams := make([]*ingress.Backend, 0, len(upstreams))
|
||||
|
|
@ -508,10 +516,7 @@ func (n *NGINXController) createUpstreams(data []*extensions.Ingress, du *ingres
|
|||
|
||||
var defBackend string
|
||||
if ing.Spec.Backend != nil {
|
||||
defBackend = fmt.Sprintf("%v-%v-%v",
|
||||
ing.Namespace,
|
||||
ing.Spec.Backend.ServiceName,
|
||||
ing.Spec.Backend.ServicePort.String())
|
||||
defBackend = upstreamName(ing.Namespace, ing.Spec.Backend.ServiceName, ing.Spec.Backend.ServicePort)
|
||||
|
||||
glog.V(3).Infof("Creating upstream %q", defBackend)
|
||||
upstreams[defBackend] = newUpstream(defBackend)
|
||||
|
|
@ -537,6 +542,16 @@ func (n *NGINXController) createUpstreams(data []*extensions.Ingress, du *ingres
|
|||
}
|
||||
}
|
||||
|
||||
// configure traffic shaping for canary
|
||||
if anns.Canary.Enabled {
|
||||
upstreams[defBackend].NoServer = true
|
||||
upstreams[defBackend].TrafficShapingPolicy = ingress.TrafficShapingPolicy{
|
||||
Weight: anns.Canary.Weight,
|
||||
Header: anns.Canary.Header,
|
||||
Cookie: anns.Canary.Cookie,
|
||||
}
|
||||
}
|
||||
|
||||
if len(upstreams[defBackend].Endpoints) == 0 {
|
||||
endps, err := n.serviceEndpoints(svcKey, ing.Spec.Backend.ServicePort.String())
|
||||
upstreams[defBackend].Endpoints = append(upstreams[defBackend].Endpoints, endps...)
|
||||
|
|
@ -558,10 +573,7 @@ func (n *NGINXController) createUpstreams(data []*extensions.Ingress, du *ingres
|
|||
}
|
||||
|
||||
for _, path := range rule.HTTP.Paths {
|
||||
name := fmt.Sprintf("%v-%v-%v",
|
||||
ing.Namespace,
|
||||
path.Backend.ServiceName,
|
||||
path.Backend.ServicePort.String())
|
||||
name := upstreamName(ing.Namespace, path.Backend.ServiceName, path.Backend.ServicePort)
|
||||
|
||||
if _, ok := upstreams[name]; ok {
|
||||
continue
|
||||
|
|
@ -595,6 +607,16 @@ func (n *NGINXController) createUpstreams(data []*extensions.Ingress, du *ingres
|
|||
}
|
||||
}
|
||||
|
||||
// configure traffic shaping for canary
|
||||
if anns.Canary.Enabled {
|
||||
upstreams[name].NoServer = true
|
||||
upstreams[name].TrafficShapingPolicy = ingress.TrafficShapingPolicy{
|
||||
Weight: anns.Canary.Weight,
|
||||
Header: anns.Canary.Header,
|
||||
Cookie: anns.Canary.Cookie,
|
||||
}
|
||||
}
|
||||
|
||||
if len(upstreams[name].Endpoints) == 0 {
|
||||
endp, err := n.serviceEndpoints(svcKey, path.Backend.ServicePort.String())
|
||||
if err != nil {
|
||||
|
|
@ -970,6 +992,63 @@ func (n *NGINXController) createServers(data []*extensions.Ingress,
|
|||
return servers
|
||||
}
|
||||
|
||||
// Compares an Ingress of a potential alternative backend's rules with each existing server and finds matching host + path pairs.
|
||||
// If a match is found, we know that this server should back the alternative backend and add the alternative backend
|
||||
// to a backend's alternative list.
|
||||
// If no match is found, then the serverless backend is deleted.
|
||||
func mergeAlternativeBackends(ing *extensions.Ingress, upstreams map[string]*ingress.Backend,
|
||||
servers map[string]*ingress.Server) {
|
||||
|
||||
// merge catch-all alternative backends
|
||||
if ing.Spec.Backend != nil {
|
||||
upsName := upstreamName(ing.Namespace, ing.Spec.Backend.ServiceName, ing.Spec.Backend.ServicePort)
|
||||
|
||||
ups := upstreams[upsName]
|
||||
|
||||
defLoc := servers[defServerName].Locations[0]
|
||||
|
||||
glog.Infof("matching backend %v found for alternative backend %v",
|
||||
upstreams[defLoc.Backend].Name, ups.Name)
|
||||
|
||||
upstreams[defLoc.Backend].AlternativeBackends =
|
||||
append(upstreams[defLoc.Backend].AlternativeBackends, ups.Name)
|
||||
}
|
||||
|
||||
for _, rule := range ing.Spec.Rules {
|
||||
for _, path := range rule.HTTP.Paths {
|
||||
upsName := upstreamName(ing.Namespace, path.Backend.ServiceName, path.Backend.ServicePort)
|
||||
|
||||
ups := upstreams[upsName]
|
||||
|
||||
merged := false
|
||||
|
||||
server := servers[rule.Host]
|
||||
|
||||
// find matching paths
|
||||
for _, location := range server.Locations {
|
||||
if location.Backend == defUpstreamName {
|
||||
continue
|
||||
}
|
||||
|
||||
if location.Path == path.Path && !upstreams[location.Backend].NoServer {
|
||||
glog.Infof("matching backend %v found for alternative backend %v",
|
||||
upstreams[location.Backend].Name, ups.Name)
|
||||
|
||||
upstreams[location.Backend].AlternativeBackends =
|
||||
append(upstreams[location.Backend].AlternativeBackends, ups.Name)
|
||||
|
||||
merged = true
|
||||
}
|
||||
}
|
||||
|
||||
if !merged {
|
||||
glog.Warningf("unable to find real backend for alternative backend %v. Deleting.", ups.Name)
|
||||
delete(upstreams, ups.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// extractTLSSecretName returns the name of the Secret containing a SSL
|
||||
// certificate for the given host name, or an empty string.
|
||||
func extractTLSSecretName(host string, ing *extensions.Ingress,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue