proxy endpoints part changes
This commit is contained in:
parent
9e85b526cb
commit
6edcf02d9e
243
pkg/proxy/endpoints.go
Normal file
243
pkg/proxy/endpoints.go
Normal file
@ -0,0 +1,243 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
)
|
||||
|
||||
// EndpointChangeTracker carries state about uncommitted changes to an arbitrary number of
|
||||
// Endpoints, keyed by their namespace and name.
|
||||
type EndpointChangeTracker struct {
|
||||
// lock protects items.
|
||||
lock sync.Mutex
|
||||
// hostname is the host where kube-proxy is running.
|
||||
hostname string
|
||||
// items maps a service to is endpointsChange.
|
||||
items map[types.NamespacedName]*endpointsChange
|
||||
}
|
||||
|
||||
// NewEndpointChangeTracker initializes an EndpointsChangeMap
|
||||
func NewEndpointChangeTracker(hostname string) *EndpointChangeTracker {
|
||||
return &EndpointChangeTracker{
|
||||
hostname: hostname,
|
||||
items: make(map[types.NamespacedName]*endpointsChange),
|
||||
}
|
||||
}
|
||||
|
||||
// Update updates given service's endpoints change map based on the <previous, current> endpoints pair. It returns true
|
||||
// if items changed, otherwise return false. Update can be used to add/update/delete items of EndpointsChangeMap. For example,
|
||||
// Add item
|
||||
// - pass <nil, endpoints> as the <previous, current> pair.
|
||||
// Update item
|
||||
// - pass <oldEndpoints, endpoints> as the <previous, current> pair.
|
||||
// Delete item
|
||||
// - pass <endpoints, nil> as the <previous, current> pair.
|
||||
func (ect *EndpointChangeTracker) Update(previous, current *api.Endpoints, makeEndpoints func(IP string, port int, isLocal bool) Endpoint) bool {
|
||||
endpoints := current
|
||||
if endpoints == nil {
|
||||
endpoints = previous
|
||||
}
|
||||
// previous == nil && current == nil is unexpected, we should return false directly.
|
||||
if endpoints == nil {
|
||||
return false
|
||||
}
|
||||
namespacedName := types.NamespacedName{Namespace: endpoints.Namespace, Name: endpoints.Name}
|
||||
|
||||
ect.lock.Lock()
|
||||
defer ect.lock.Unlock()
|
||||
|
||||
change, exists := ect.items[namespacedName]
|
||||
if !exists {
|
||||
change = &endpointsChange{}
|
||||
change.previous = endpointsToEndpointsMap(previous, ect.hostname, makeEndpoints)
|
||||
ect.items[namespacedName] = change
|
||||
}
|
||||
change.current = endpointsToEndpointsMap(current, ect.hostname, makeEndpoints)
|
||||
// if change.previous equal to change.current, it means no change
|
||||
if reflect.DeepEqual(change.previous, change.current) {
|
||||
delete(ect.items, namespacedName)
|
||||
}
|
||||
return len(ect.items) > 0
|
||||
}
|
||||
|
||||
// endpointsChange contains all changes to endpoints that happened since proxy rules were synced. For a single object,
|
||||
// changes are accumulated, i.e. previous is state from before applying the changes,
|
||||
// current is state after applying the changes.
|
||||
type endpointsChange struct {
|
||||
previous EndpointsMap
|
||||
current EndpointsMap
|
||||
}
|
||||
|
||||
// UpdateEndpointMapResult is the updated results after applying endpoints changes.
|
||||
type UpdateEndpointMapResult struct {
|
||||
// HCEndpointsLocalIPSize maps an endpoints name to the length of its local IPs.
|
||||
HCEndpointsLocalIPSize map[types.NamespacedName]int
|
||||
// StaleEndpoints identifies if an endpoints service pair is stale.
|
||||
StaleEndpoints []ServiceEndpoint
|
||||
// StaleServiceNames identifies if a service is stale.
|
||||
StaleServiceNames []ServicePortName
|
||||
}
|
||||
|
||||
// UpdateEndpointsMap updates endpointsMap base on the given changes.
|
||||
func UpdateEndpointsMap(endpointsMap EndpointsMap, changes *EndpointChangeTracker) (result UpdateEndpointMapResult) {
|
||||
result.StaleEndpoints = make([]ServiceEndpoint, 0)
|
||||
result.StaleServiceNames = make([]ServicePortName, 0)
|
||||
|
||||
endpointsMap.apply(changes, &result.StaleEndpoints, &result.StaleServiceNames)
|
||||
|
||||
// TODO: If this will appear to be computationally expensive, consider
|
||||
// computing this incrementally similarly to endpointsMap.
|
||||
result.HCEndpointsLocalIPSize = make(map[types.NamespacedName]int)
|
||||
localIPs := GetLocalEndpointIPs(endpointsMap)
|
||||
for nsn, ips := range localIPs {
|
||||
result.HCEndpointsLocalIPSize[nsn] = len(ips)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// EndpointsMap maps a service to one of its endpoint.
|
||||
type EndpointsMap map[ServicePortName][]Endpoint
|
||||
|
||||
// endpointsToEndpointsMap translates single Endpoints object to EndpointsMap.
|
||||
// This function is used for incremental updated of endpointsMap.
|
||||
//
|
||||
// NOTE: endpoints object should NOT be modified.
|
||||
func endpointsToEndpointsMap(endpoints *api.Endpoints, hostname string, makeEndpoints func(IP string, port int, isLocal bool) Endpoint) EndpointsMap {
|
||||
if endpoints == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
endpointsMap := make(EndpointsMap)
|
||||
// We need to build a map of portname -> all ip:ports for that
|
||||
// portname. Explode Endpoints.Subsets[*] into this structure.
|
||||
for i := range endpoints.Subsets {
|
||||
ss := &endpoints.Subsets[i]
|
||||
for i := range ss.Ports {
|
||||
port := &ss.Ports[i]
|
||||
if port.Port == 0 {
|
||||
glog.Warningf("ignoring invalid endpoint port %s", port.Name)
|
||||
continue
|
||||
}
|
||||
svcPortName := ServicePortName{
|
||||
NamespacedName: types.NamespacedName{Namespace: endpoints.Namespace, Name: endpoints.Name},
|
||||
Port: port.Name,
|
||||
}
|
||||
for i := range ss.Addresses {
|
||||
addr := &ss.Addresses[i]
|
||||
if addr.IP == "" {
|
||||
glog.Warningf("ignoring invalid endpoint port %s with empty host", port.Name)
|
||||
continue
|
||||
}
|
||||
isLocal := addr.NodeName != nil && *addr.NodeName == hostname
|
||||
epInfo := makeEndpoints(addr.IP, int(port.Port), isLocal)
|
||||
endpointsMap[svcPortName] = append(endpointsMap[svcPortName], epInfo)
|
||||
}
|
||||
if glog.V(3) {
|
||||
newEPList := []string{}
|
||||
for _, ep := range endpointsMap[svcPortName] {
|
||||
newEPList = append(newEPList, ep.String())
|
||||
}
|
||||
glog.Infof("Setting endpoints for %q to %+v", svcPortName, newEPList)
|
||||
}
|
||||
}
|
||||
}
|
||||
return endpointsMap
|
||||
}
|
||||
|
||||
// apply the changes to EndpointsMap and updates stale endpoints and service-endpoints pair. The `staleEndpoints` argument
|
||||
// is passed in to store the stale udp endpoints and `staleServiceNames` argument is passed in to store the stale udp service.
|
||||
// The changes map is cleared after applying them.
|
||||
func (endpointsMap EndpointsMap) apply(changes *EndpointChangeTracker, staleEndpoints *[]ServiceEndpoint, staleServiceNames *[]ServicePortName) {
|
||||
if changes == nil {
|
||||
return
|
||||
}
|
||||
changes.lock.Lock()
|
||||
defer changes.lock.Unlock()
|
||||
for _, change := range changes.items {
|
||||
endpointsMap.Unmerge(change.previous)
|
||||
endpointsMap.Merge(change.current)
|
||||
detectStaleConnections(change.previous, change.current, staleEndpoints, staleServiceNames)
|
||||
}
|
||||
changes.items = make(map[types.NamespacedName]*endpointsChange)
|
||||
}
|
||||
|
||||
// Merge ensures that the current EndpointsMap contains all <service, endpoints> pairs from the EndpointsMap passed in.
|
||||
func (em EndpointsMap) Merge(other EndpointsMap) {
|
||||
for svcPortName := range other {
|
||||
em[svcPortName] = other[svcPortName]
|
||||
}
|
||||
}
|
||||
|
||||
// Unmerge removes the <service, endpoints> pairs from the current EndpointsMap which are contained in the EndpointsMap passed in.
|
||||
func (em EndpointsMap) Unmerge(other EndpointsMap) {
|
||||
for svcPortName := range other {
|
||||
delete(em, svcPortName)
|
||||
}
|
||||
}
|
||||
|
||||
// GetLocalEndpointIPs returns endpoints IPs if given endpoint is local - local means the endpoint is running in same host as kube-proxy.
|
||||
func GetLocalEndpointIPs(endpointsMap EndpointsMap) map[types.NamespacedName]sets.String {
|
||||
localIPs := make(map[types.NamespacedName]sets.String)
|
||||
for svcPortName, epList := range endpointsMap {
|
||||
for _, ep := range epList {
|
||||
if ep.IsLocal() {
|
||||
nsn := svcPortName.NamespacedName
|
||||
if localIPs[nsn] == nil {
|
||||
localIPs[nsn] = sets.NewString()
|
||||
}
|
||||
localIPs[nsn].Insert(ep.IP())
|
||||
}
|
||||
}
|
||||
}
|
||||
return localIPs
|
||||
}
|
||||
|
||||
// detectStaleConnections modifies <staleEndpoints> and <staleServices> with detected stale connections. <staleServiceNames>
|
||||
// is used to store stale udp service in order to clear udp conntrack later.
|
||||
func detectStaleConnections(oldEndpointsMap, newEndpointsMap EndpointsMap, staleEndpoints *[]ServiceEndpoint, staleServiceNames *[]ServicePortName) {
|
||||
for svcPortName, epList := range oldEndpointsMap {
|
||||
for _, ep := range epList {
|
||||
stale := true
|
||||
for i := range newEndpointsMap[svcPortName] {
|
||||
if newEndpointsMap[svcPortName][i].Equal(ep) {
|
||||
stale = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if stale {
|
||||
glog.V(4).Infof("Stale endpoint %v -> %v", svcPortName, ep.String())
|
||||
*staleEndpoints = append(*staleEndpoints, ServiceEndpoint{Endpoint: ep.String(), ServicePortName: svcPortName})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for svcPortName, epList := range newEndpointsMap {
|
||||
// For udp service, if its backend changes from 0 to non-0. There may exist a conntrack entry that could blackhole traffic to the service.
|
||||
if len(epList) > 0 && len(oldEndpointsMap[svcPortName]) == 0 {
|
||||
*staleServiceNames = append(*staleServiceNames, svcPortName)
|
||||
}
|
||||
}
|
||||
}
|
1247
pkg/proxy/endpoints_test.go
Normal file
1247
pkg/proxy/endpoints_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -47,6 +47,7 @@ func IPPart(s string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// PortPart returns just the port part of an endpoint string.
|
||||
func PortPart(s string) (int, error) {
|
||||
// Must be IP:port
|
||||
_, port, err := net.SplitHostPort(s)
|
||||
|
Loading…
Reference in New Issue
Block a user