endpoints.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package proxy
  14. import (
  15. "net"
  16. "reflect"
  17. "strconv"
  18. "sync"
  19. "time"
  20. "k8s.io/klog"
  21. v1 "k8s.io/api/core/v1"
  22. discovery "k8s.io/api/discovery/v1beta1"
  23. "k8s.io/apimachinery/pkg/types"
  24. "k8s.io/apimachinery/pkg/util/sets"
  25. "k8s.io/client-go/tools/record"
  26. "k8s.io/kubernetes/pkg/proxy/metrics"
  27. utilproxy "k8s.io/kubernetes/pkg/proxy/util"
  28. utilnet "k8s.io/utils/net"
  29. )
  30. var supportedEndpointSliceAddressTypes = sets.NewString(
  31. string(discovery.AddressTypeIP), // IP is a deprecated address type
  32. string(discovery.AddressTypeIPv4),
  33. string(discovery.AddressTypeIPv6),
  34. )
  35. // BaseEndpointInfo contains base information that defines an endpoint.
  36. // This could be used directly by proxier while processing endpoints,
  37. // or can be used for constructing a more specific EndpointInfo struct
  38. // defined by the proxier if needed.
  39. type BaseEndpointInfo struct {
  40. Endpoint string // TODO: should be an endpointString type
  41. // IsLocal indicates whether the endpoint is running in same host as kube-proxy.
  42. IsLocal bool
  43. Topology map[string]string
  44. }
  45. var _ Endpoint = &BaseEndpointInfo{}
  46. // String is part of proxy.Endpoint interface.
  47. func (info *BaseEndpointInfo) String() string {
  48. return info.Endpoint
  49. }
  50. // GetIsLocal is part of proxy.Endpoint interface.
  51. func (info *BaseEndpointInfo) GetIsLocal() bool {
  52. return info.IsLocal
  53. }
  54. // GetTopology returns the topology information of the endpoint.
  55. func (info *BaseEndpointInfo) GetTopology() map[string]string {
  56. return info.Topology
  57. }
  58. // IP returns just the IP part of the endpoint, it's a part of proxy.Endpoint interface.
  59. func (info *BaseEndpointInfo) IP() string {
  60. return utilproxy.IPPart(info.Endpoint)
  61. }
  62. // Port returns just the Port part of the endpoint.
  63. func (info *BaseEndpointInfo) Port() (int, error) {
  64. return utilproxy.PortPart(info.Endpoint)
  65. }
  66. // Equal is part of proxy.Endpoint interface.
  67. func (info *BaseEndpointInfo) Equal(other Endpoint) bool {
  68. return info.String() == other.String() && info.GetIsLocal() == other.GetIsLocal()
  69. }
  70. func newBaseEndpointInfo(IP string, port int, isLocal bool, topology map[string]string) *BaseEndpointInfo {
  71. return &BaseEndpointInfo{
  72. Endpoint: net.JoinHostPort(IP, strconv.Itoa(port)),
  73. IsLocal: isLocal,
  74. Topology: topology,
  75. }
  76. }
  77. type makeEndpointFunc func(info *BaseEndpointInfo) Endpoint
  78. // EndpointChangeTracker carries state about uncommitted changes to an arbitrary number of
  79. // Endpoints, keyed by their namespace and name.
  80. type EndpointChangeTracker struct {
  81. // lock protects items.
  82. lock sync.Mutex
  83. // hostname is the host where kube-proxy is running.
  84. hostname string
  85. // items maps a service to is endpointsChange.
  86. items map[types.NamespacedName]*endpointsChange
  87. // makeEndpointInfo allows proxier to inject customized information when processing endpoint.
  88. makeEndpointInfo makeEndpointFunc
  89. // endpointSliceCache holds a simplified version of endpoint slices.
  90. endpointSliceCache *EndpointSliceCache
  91. // isIPv6Mode indicates if change tracker is under IPv6/IPv4 mode. Nil means not applicable.
  92. isIPv6Mode *bool
  93. recorder record.EventRecorder
  94. // Map from the Endpoints namespaced-name to the times of the triggers that caused the endpoints
  95. // object to change. Used to calculate the network-programming-latency.
  96. lastChangeTriggerTimes map[types.NamespacedName][]time.Time
  97. }
  98. // NewEndpointChangeTracker initializes an EndpointsChangeMap
  99. func NewEndpointChangeTracker(hostname string, makeEndpointInfo makeEndpointFunc, isIPv6Mode *bool, recorder record.EventRecorder, endpointSlicesEnabled bool) *EndpointChangeTracker {
  100. ect := &EndpointChangeTracker{
  101. hostname: hostname,
  102. items: make(map[types.NamespacedName]*endpointsChange),
  103. makeEndpointInfo: makeEndpointInfo,
  104. isIPv6Mode: isIPv6Mode,
  105. recorder: recorder,
  106. lastChangeTriggerTimes: make(map[types.NamespacedName][]time.Time),
  107. }
  108. if endpointSlicesEnabled {
  109. ect.endpointSliceCache = NewEndpointSliceCache(hostname, isIPv6Mode, recorder, makeEndpointInfo)
  110. }
  111. return ect
  112. }
  113. // Update updates given service's endpoints change map based on the <previous, current> endpoints pair. It returns true
  114. // if items changed, otherwise return false. Update can be used to add/update/delete items of EndpointsChangeMap. For example,
  115. // Add item
  116. // - pass <nil, endpoints> as the <previous, current> pair.
  117. // Update item
  118. // - pass <oldEndpoints, endpoints> as the <previous, current> pair.
  119. // Delete item
  120. // - pass <endpoints, nil> as the <previous, current> pair.
  121. func (ect *EndpointChangeTracker) Update(previous, current *v1.Endpoints) bool {
  122. endpoints := current
  123. if endpoints == nil {
  124. endpoints = previous
  125. }
  126. // previous == nil && current == nil is unexpected, we should return false directly.
  127. if endpoints == nil {
  128. return false
  129. }
  130. metrics.EndpointChangesTotal.Inc()
  131. namespacedName := types.NamespacedName{Namespace: endpoints.Namespace, Name: endpoints.Name}
  132. ect.lock.Lock()
  133. defer ect.lock.Unlock()
  134. change, exists := ect.items[namespacedName]
  135. if !exists {
  136. change = &endpointsChange{}
  137. change.previous = ect.endpointsToEndpointsMap(previous)
  138. ect.items[namespacedName] = change
  139. }
  140. if t := getLastChangeTriggerTime(endpoints.Annotations); !t.IsZero() {
  141. ect.lastChangeTriggerTimes[namespacedName] = append(ect.lastChangeTriggerTimes[namespacedName], t)
  142. }
  143. change.current = ect.endpointsToEndpointsMap(current)
  144. // if change.previous equal to change.current, it means no change
  145. if reflect.DeepEqual(change.previous, change.current) {
  146. delete(ect.items, namespacedName)
  147. // Reset the lastChangeTriggerTimes for the Endpoints object. Given that the network programming
  148. // SLI is defined as the duration between a time of an event and a time when the network was
  149. // programmed to incorporate that event, if there are events that happened between two
  150. // consecutive syncs and that canceled each other out, e.g. pod A added -> pod A deleted,
  151. // there will be no network programming for them and thus no network programming latency metric
  152. // should be exported.
  153. delete(ect.lastChangeTriggerTimes, namespacedName)
  154. }
  155. metrics.EndpointChangesPending.Set(float64(len(ect.items)))
  156. return len(ect.items) > 0
  157. }
  158. // EndpointSliceUpdate updates given service's endpoints change map based on the <previous, current> endpoints pair.
  159. // It returns true if items changed, otherwise return false. Will add/update/delete items of EndpointsChangeMap.
  160. // If removeSlice is true, slice will be removed, otherwise it will be added or updated.
  161. func (ect *EndpointChangeTracker) EndpointSliceUpdate(endpointSlice *discovery.EndpointSlice, removeSlice bool) bool {
  162. if !supportedEndpointSliceAddressTypes.Has(string(endpointSlice.AddressType)) {
  163. klog.V(4).Infof("EndpointSlice address type not supported by kube-proxy: %s", endpointSlice.AddressType)
  164. return false
  165. }
  166. // This should never happen
  167. if endpointSlice == nil {
  168. klog.Error("Nil endpointSlice passed to EndpointSliceUpdate")
  169. return false
  170. }
  171. namespacedName, _, err := endpointSliceCacheKeys(endpointSlice)
  172. if err != nil {
  173. klog.Warningf("Error getting endpoint slice cache keys: %v", err)
  174. return false
  175. }
  176. metrics.EndpointChangesTotal.Inc()
  177. ect.lock.Lock()
  178. defer ect.lock.Unlock()
  179. changeNeeded := ect.endpointSliceCache.updatePending(endpointSlice, removeSlice)
  180. if changeNeeded {
  181. metrics.EndpointChangesPending.Inc()
  182. if t := getLastChangeTriggerTime(endpointSlice.Annotations); !t.IsZero() {
  183. ect.lastChangeTriggerTimes[namespacedName] =
  184. append(ect.lastChangeTriggerTimes[namespacedName], t)
  185. }
  186. }
  187. return changeNeeded
  188. }
  189. // checkoutChanges returns a list of pending endpointsChanges and marks them as
  190. // applied.
  191. func (ect *EndpointChangeTracker) checkoutChanges() []*endpointsChange {
  192. ect.lock.Lock()
  193. defer ect.lock.Unlock()
  194. metrics.EndpointChangesPending.Set(0)
  195. if ect.endpointSliceCache != nil {
  196. return ect.endpointSliceCache.checkoutChanges()
  197. }
  198. changes := []*endpointsChange{}
  199. for _, change := range ect.items {
  200. changes = append(changes, change)
  201. }
  202. ect.items = make(map[types.NamespacedName]*endpointsChange)
  203. return changes
  204. }
  205. // checkoutTriggerTimes applies the locally cached trigger times to a map of
  206. // trigger times that have been passed in and empties the local cache.
  207. func (ect *EndpointChangeTracker) checkoutTriggerTimes(lastChangeTriggerTimes *map[types.NamespacedName][]time.Time) {
  208. ect.lock.Lock()
  209. defer ect.lock.Unlock()
  210. for k, v := range ect.lastChangeTriggerTimes {
  211. prev, ok := (*lastChangeTriggerTimes)[k]
  212. if !ok {
  213. (*lastChangeTriggerTimes)[k] = v
  214. } else {
  215. (*lastChangeTriggerTimes)[k] = append(prev, v...)
  216. }
  217. }
  218. ect.lastChangeTriggerTimes = make(map[types.NamespacedName][]time.Time)
  219. }
  220. // getLastChangeTriggerTime returns the time.Time value of the
  221. // EndpointsLastChangeTriggerTime annotation stored in the given endpoints
  222. // object or the "zero" time if the annotation wasn't set or was set
  223. // incorrectly.
  224. func getLastChangeTriggerTime(annotations map[string]string) time.Time {
  225. // TODO(#81360): ignore case when Endpoint is deleted.
  226. if _, ok := annotations[v1.EndpointsLastChangeTriggerTime]; !ok {
  227. // It's possible that the Endpoints object won't have the
  228. // EndpointsLastChangeTriggerTime annotation set. In that case return
  229. // the 'zero value', which is ignored in the upstream code.
  230. return time.Time{}
  231. }
  232. val, err := time.Parse(time.RFC3339Nano, annotations[v1.EndpointsLastChangeTriggerTime])
  233. if err != nil {
  234. klog.Warningf("Error while parsing EndpointsLastChangeTriggerTimeAnnotation: '%s'. Error is %v",
  235. annotations[v1.EndpointsLastChangeTriggerTime], err)
  236. // In case of error val = time.Zero, which is ignored in the upstream code.
  237. }
  238. return val
  239. }
  240. // endpointsChange contains all changes to endpoints that happened since proxy
  241. // rules were synced. For a single object, changes are accumulated, i.e.
  242. // previous is state from before applying the changes, current is state after
  243. // applying the changes.
  244. type endpointsChange struct {
  245. previous EndpointsMap
  246. current EndpointsMap
  247. }
  248. // UpdateEndpointMapResult is the updated results after applying endpoints changes.
  249. type UpdateEndpointMapResult struct {
  250. // HCEndpointsLocalIPSize maps an endpoints name to the length of its local IPs.
  251. HCEndpointsLocalIPSize map[types.NamespacedName]int
  252. // StaleEndpoints identifies if an endpoints service pair is stale.
  253. StaleEndpoints []ServiceEndpoint
  254. // StaleServiceNames identifies if a service is stale.
  255. StaleServiceNames []ServicePortName
  256. // List of the trigger times for all endpoints objects that changed. It's used to export the
  257. // network programming latency.
  258. // NOTE(oxddr): this can be simplified to []time.Time if memory consumption becomes an issue.
  259. LastChangeTriggerTimes map[types.NamespacedName][]time.Time
  260. }
  261. // Update updates endpointsMap base on the given changes.
  262. func (em EndpointsMap) Update(changes *EndpointChangeTracker) (result UpdateEndpointMapResult) {
  263. result.StaleEndpoints = make([]ServiceEndpoint, 0)
  264. result.StaleServiceNames = make([]ServicePortName, 0)
  265. result.LastChangeTriggerTimes = make(map[types.NamespacedName][]time.Time)
  266. em.apply(
  267. changes, &result.StaleEndpoints, &result.StaleServiceNames, &result.LastChangeTriggerTimes)
  268. // TODO: If this will appear to be computationally expensive, consider
  269. // computing this incrementally similarly to endpointsMap.
  270. result.HCEndpointsLocalIPSize = make(map[types.NamespacedName]int)
  271. localIPs := em.getLocalEndpointIPs()
  272. for nsn, ips := range localIPs {
  273. result.HCEndpointsLocalIPSize[nsn] = len(ips)
  274. }
  275. return result
  276. }
  277. // EndpointsMap maps a service name to a list of all its Endpoints.
  278. type EndpointsMap map[ServicePortName][]Endpoint
  279. // endpointsToEndpointsMap translates single Endpoints object to EndpointsMap.
  280. // This function is used for incremental updated of endpointsMap.
  281. //
  282. // NOTE: endpoints object should NOT be modified.
  283. func (ect *EndpointChangeTracker) endpointsToEndpointsMap(endpoints *v1.Endpoints) EndpointsMap {
  284. if endpoints == nil {
  285. return nil
  286. }
  287. endpointsMap := make(EndpointsMap)
  288. // We need to build a map of portname -> all ip:ports for that portname.
  289. // Explode Endpoints.Subsets[*] into this structure.
  290. for i := range endpoints.Subsets {
  291. ss := &endpoints.Subsets[i]
  292. for i := range ss.Ports {
  293. port := &ss.Ports[i]
  294. if port.Port == 0 {
  295. klog.Warningf("ignoring invalid endpoint port %s", port.Name)
  296. continue
  297. }
  298. svcPortName := ServicePortName{
  299. NamespacedName: types.NamespacedName{Namespace: endpoints.Namespace, Name: endpoints.Name},
  300. Port: port.Name,
  301. Protocol: port.Protocol,
  302. }
  303. for i := range ss.Addresses {
  304. addr := &ss.Addresses[i]
  305. if addr.IP == "" {
  306. klog.Warningf("ignoring invalid endpoint port %s with empty host", port.Name)
  307. continue
  308. }
  309. // Filter out the incorrect IP version case.
  310. // Any endpoint port that contains incorrect IP version will be ignored.
  311. if ect.isIPv6Mode != nil && utilnet.IsIPv6String(addr.IP) != *ect.isIPv6Mode {
  312. // Emit event on the corresponding service which had a different
  313. // IP version than the endpoint.
  314. utilproxy.LogAndEmitIncorrectIPVersionEvent(ect.recorder, "endpoints", addr.IP, endpoints.Namespace, endpoints.Name, "")
  315. continue
  316. }
  317. isLocal := addr.NodeName != nil && *addr.NodeName == ect.hostname
  318. baseEndpointInfo := newBaseEndpointInfo(addr.IP, int(port.Port), isLocal, nil)
  319. if ect.makeEndpointInfo != nil {
  320. endpointsMap[svcPortName] = append(endpointsMap[svcPortName], ect.makeEndpointInfo(baseEndpointInfo))
  321. } else {
  322. endpointsMap[svcPortName] = append(endpointsMap[svcPortName], baseEndpointInfo)
  323. }
  324. }
  325. klog.V(3).Infof("Setting endpoints for %q to %+v", svcPortName, formatEndpointsList(endpointsMap[svcPortName]))
  326. }
  327. }
  328. return endpointsMap
  329. }
  330. // apply the changes to EndpointsMap and updates stale endpoints and service-endpoints pair. The `staleEndpoints` argument
  331. // is passed in to store the stale udp endpoints and `staleServiceNames` argument is passed in to store the stale udp service.
  332. // The changes map is cleared after applying them.
  333. // In addition it returns (via argument) and resets the lastChangeTriggerTimes for all endpoints
  334. // that were changed and will result in syncing the proxy rules.
  335. func (em EndpointsMap) apply(ect *EndpointChangeTracker, staleEndpoints *[]ServiceEndpoint,
  336. staleServiceNames *[]ServicePortName, lastChangeTriggerTimes *map[types.NamespacedName][]time.Time) {
  337. if ect == nil {
  338. return
  339. }
  340. changes := ect.checkoutChanges()
  341. for _, change := range changes {
  342. em.unmerge(change.previous)
  343. em.merge(change.current)
  344. detectStaleConnections(change.previous, change.current, staleEndpoints, staleServiceNames)
  345. }
  346. ect.checkoutTriggerTimes(lastChangeTriggerTimes)
  347. }
  348. // Merge ensures that the current EndpointsMap contains all <service, endpoints> pairs from the EndpointsMap passed in.
  349. func (em EndpointsMap) merge(other EndpointsMap) {
  350. for svcPortName := range other {
  351. em[svcPortName] = other[svcPortName]
  352. }
  353. }
  354. // Unmerge removes the <service, endpoints> pairs from the current EndpointsMap which are contained in the EndpointsMap passed in.
  355. func (em EndpointsMap) unmerge(other EndpointsMap) {
  356. for svcPortName := range other {
  357. delete(em, svcPortName)
  358. }
  359. }
  360. // GetLocalEndpointIPs returns endpoints IPs if given endpoint is local - local means the endpoint is running in same host as kube-proxy.
  361. func (em EndpointsMap) getLocalEndpointIPs() map[types.NamespacedName]sets.String {
  362. localIPs := make(map[types.NamespacedName]sets.String)
  363. for svcPortName, epList := range em {
  364. for _, ep := range epList {
  365. if ep.GetIsLocal() {
  366. nsn := svcPortName.NamespacedName
  367. if localIPs[nsn] == nil {
  368. localIPs[nsn] = sets.NewString()
  369. }
  370. localIPs[nsn].Insert(ep.IP())
  371. }
  372. }
  373. }
  374. return localIPs
  375. }
  376. // detectStaleConnections modifies <staleEndpoints> and <staleServices> with detected stale connections. <staleServiceNames>
  377. // is used to store stale udp service in order to clear udp conntrack later.
  378. func detectStaleConnections(oldEndpointsMap, newEndpointsMap EndpointsMap, staleEndpoints *[]ServiceEndpoint, staleServiceNames *[]ServicePortName) {
  379. for svcPortName, epList := range oldEndpointsMap {
  380. if svcPortName.Protocol != v1.ProtocolUDP {
  381. continue
  382. }
  383. for _, ep := range epList {
  384. stale := true
  385. for i := range newEndpointsMap[svcPortName] {
  386. if newEndpointsMap[svcPortName][i].Equal(ep) {
  387. stale = false
  388. break
  389. }
  390. }
  391. if stale {
  392. klog.V(4).Infof("Stale endpoint %v -> %v", svcPortName, ep.String())
  393. *staleEndpoints = append(*staleEndpoints, ServiceEndpoint{Endpoint: ep.String(), ServicePortName: svcPortName})
  394. }
  395. }
  396. }
  397. for svcPortName, epList := range newEndpointsMap {
  398. if svcPortName.Protocol != v1.ProtocolUDP {
  399. continue
  400. }
  401. // 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.
  402. if len(epList) > 0 && len(oldEndpointsMap[svcPortName]) == 0 {
  403. *staleServiceNames = append(*staleServiceNames, svcPortName)
  404. }
  405. }
  406. }