service.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  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. "fmt"
  16. "net"
  17. "reflect"
  18. "strings"
  19. "sync"
  20. "k8s.io/klog"
  21. "k8s.io/api/core/v1"
  22. "k8s.io/apimachinery/pkg/types"
  23. "k8s.io/apimachinery/pkg/util/sets"
  24. "k8s.io/client-go/tools/record"
  25. apiservice "k8s.io/kubernetes/pkg/api/v1/service"
  26. "k8s.io/kubernetes/pkg/proxy/metrics"
  27. utilproxy "k8s.io/kubernetes/pkg/proxy/util"
  28. utilnet "k8s.io/utils/net"
  29. )
  30. // BaseServiceInfo contains base information that defines a service.
  31. // This could be used directly by proxier while processing services,
  32. // or can be used for constructing a more specific ServiceInfo struct
  33. // defined by the proxier if needed.
  34. type BaseServiceInfo struct {
  35. clusterIP net.IP
  36. port int
  37. protocol v1.Protocol
  38. nodePort int
  39. loadBalancerStatus v1.LoadBalancerStatus
  40. sessionAffinityType v1.ServiceAffinity
  41. stickyMaxAgeSeconds int
  42. externalIPs []string
  43. loadBalancerSourceRanges []string
  44. healthCheckNodePort int
  45. onlyNodeLocalEndpoints bool
  46. topologyKeys []string
  47. }
  48. var _ ServicePort = &BaseServiceInfo{}
  49. // String is part of ServicePort interface.
  50. func (info *BaseServiceInfo) String() string {
  51. return fmt.Sprintf("%s:%d/%s", info.clusterIP, info.port, info.protocol)
  52. }
  53. // ClusterIP is part of ServicePort interface.
  54. func (info *BaseServiceInfo) ClusterIP() net.IP {
  55. return info.clusterIP
  56. }
  57. // Port is part of ServicePort interface.
  58. func (info *BaseServiceInfo) Port() int {
  59. return info.port
  60. }
  61. // SessionAffinityType is part of the ServicePort interface.
  62. func (info *BaseServiceInfo) SessionAffinityType() v1.ServiceAffinity {
  63. return info.sessionAffinityType
  64. }
  65. // StickyMaxAgeSeconds is part of the ServicePort interface
  66. func (info *BaseServiceInfo) StickyMaxAgeSeconds() int {
  67. return info.stickyMaxAgeSeconds
  68. }
  69. // Protocol is part of ServicePort interface.
  70. func (info *BaseServiceInfo) Protocol() v1.Protocol {
  71. return info.protocol
  72. }
  73. // LoadBalancerSourceRanges is part of ServicePort interface
  74. func (info *BaseServiceInfo) LoadBalancerSourceRanges() []string {
  75. return info.loadBalancerSourceRanges
  76. }
  77. // HealthCheckNodePort is part of ServicePort interface.
  78. func (info *BaseServiceInfo) HealthCheckNodePort() int {
  79. return info.healthCheckNodePort
  80. }
  81. // NodePort is part of the ServicePort interface.
  82. func (info *BaseServiceInfo) NodePort() int {
  83. return info.nodePort
  84. }
  85. // ExternalIPStrings is part of ServicePort interface.
  86. func (info *BaseServiceInfo) ExternalIPStrings() []string {
  87. return info.externalIPs
  88. }
  89. // LoadBalancerIPStrings is part of ServicePort interface.
  90. func (info *BaseServiceInfo) LoadBalancerIPStrings() []string {
  91. var ips []string
  92. for _, ing := range info.loadBalancerStatus.Ingress {
  93. ips = append(ips, ing.IP)
  94. }
  95. return ips
  96. }
  97. // OnlyNodeLocalEndpoints is part of ServicePort interface.
  98. func (info *BaseServiceInfo) OnlyNodeLocalEndpoints() bool {
  99. return info.onlyNodeLocalEndpoints
  100. }
  101. // TopologyKeys is part of ServicePort interface.
  102. func (info *BaseServiceInfo) TopologyKeys() []string {
  103. return info.topologyKeys
  104. }
  105. func (sct *ServiceChangeTracker) newBaseServiceInfo(port *v1.ServicePort, service *v1.Service) *BaseServiceInfo {
  106. onlyNodeLocalEndpoints := false
  107. if apiservice.RequestsOnlyLocalTraffic(service) {
  108. onlyNodeLocalEndpoints = true
  109. }
  110. var stickyMaxAgeSeconds int
  111. if service.Spec.SessionAffinity == v1.ServiceAffinityClientIP {
  112. // Kube-apiserver side guarantees SessionAffinityConfig won't be nil when session affinity type is ClientIP
  113. stickyMaxAgeSeconds = int(*service.Spec.SessionAffinityConfig.ClientIP.TimeoutSeconds)
  114. }
  115. info := &BaseServiceInfo{
  116. clusterIP: net.ParseIP(service.Spec.ClusterIP),
  117. port: int(port.Port),
  118. protocol: port.Protocol,
  119. nodePort: int(port.NodePort),
  120. sessionAffinityType: service.Spec.SessionAffinity,
  121. stickyMaxAgeSeconds: stickyMaxAgeSeconds,
  122. onlyNodeLocalEndpoints: onlyNodeLocalEndpoints,
  123. topologyKeys: service.Spec.TopologyKeys,
  124. }
  125. if sct.isIPv6Mode == nil {
  126. info.externalIPs = make([]string, len(service.Spec.ExternalIPs))
  127. info.loadBalancerSourceRanges = make([]string, len(service.Spec.LoadBalancerSourceRanges))
  128. copy(info.loadBalancerSourceRanges, service.Spec.LoadBalancerSourceRanges)
  129. copy(info.externalIPs, service.Spec.ExternalIPs)
  130. // Deep-copy in case the service instance changes
  131. info.loadBalancerStatus = *service.Status.LoadBalancer.DeepCopy()
  132. } else {
  133. // Filter out the incorrect IP version case.
  134. // If ExternalIPs, LoadBalancerSourceRanges and LoadBalancerStatus Ingress on service contains incorrect IP versions,
  135. // only filter out the incorrect ones.
  136. var incorrectIPs []string
  137. info.externalIPs, incorrectIPs = utilproxy.FilterIncorrectIPVersion(service.Spec.ExternalIPs, *sct.isIPv6Mode)
  138. if len(incorrectIPs) > 0 {
  139. utilproxy.LogAndEmitIncorrectIPVersionEvent(sct.recorder, "externalIPs", strings.Join(incorrectIPs, ","), service.Namespace, service.Name, service.UID)
  140. }
  141. info.loadBalancerSourceRanges, incorrectIPs = utilproxy.FilterIncorrectCIDRVersion(service.Spec.LoadBalancerSourceRanges, *sct.isIPv6Mode)
  142. if len(incorrectIPs) > 0 {
  143. utilproxy.LogAndEmitIncorrectIPVersionEvent(sct.recorder, "loadBalancerSourceRanges", strings.Join(incorrectIPs, ","), service.Namespace, service.Name, service.UID)
  144. }
  145. // Obtain Load Balancer Ingress IPs
  146. var ips []string
  147. for _, ing := range service.Status.LoadBalancer.Ingress {
  148. ips = append(ips, ing.IP)
  149. }
  150. if len(ips) > 0 {
  151. correctIPs, incorrectIPs := utilproxy.FilterIncorrectIPVersion(ips, *sct.isIPv6Mode)
  152. if len(incorrectIPs) > 0 {
  153. utilproxy.LogAndEmitIncorrectIPVersionEvent(sct.recorder, "Load Balancer ingress IPs", strings.Join(incorrectIPs, ","), service.Namespace, service.Name, service.UID)
  154. }
  155. // Create the LoadBalancerStatus with the filtererd IPs
  156. for _, ip := range correctIPs {
  157. info.loadBalancerStatus.Ingress = append(info.loadBalancerStatus.Ingress, v1.LoadBalancerIngress{IP: ip})
  158. }
  159. }
  160. }
  161. if apiservice.NeedsHealthCheck(service) {
  162. p := service.Spec.HealthCheckNodePort
  163. if p == 0 {
  164. klog.Errorf("Service %s/%s has no healthcheck nodeport", service.Namespace, service.Name)
  165. } else {
  166. info.healthCheckNodePort = int(p)
  167. }
  168. }
  169. return info
  170. }
  171. type makeServicePortFunc func(*v1.ServicePort, *v1.Service, *BaseServiceInfo) ServicePort
  172. // serviceChange contains all changes to services that happened since proxy rules were synced. For a single object,
  173. // changes are accumulated, i.e. previous is state from before applying the changes,
  174. // current is state after applying all of the changes.
  175. type serviceChange struct {
  176. previous ServiceMap
  177. current ServiceMap
  178. }
  179. // ServiceChangeTracker carries state about uncommitted changes to an arbitrary number of
  180. // Services, keyed by their namespace and name.
  181. type ServiceChangeTracker struct {
  182. // lock protects items.
  183. lock sync.Mutex
  184. // items maps a service to its serviceChange.
  185. items map[types.NamespacedName]*serviceChange
  186. // makeServiceInfo allows proxier to inject customized information when processing service.
  187. makeServiceInfo makeServicePortFunc
  188. // isIPv6Mode indicates if change tracker is under IPv6/IPv4 mode. Nil means not applicable.
  189. isIPv6Mode *bool
  190. recorder record.EventRecorder
  191. }
  192. // NewServiceChangeTracker initializes a ServiceChangeTracker
  193. func NewServiceChangeTracker(makeServiceInfo makeServicePortFunc, isIPv6Mode *bool, recorder record.EventRecorder) *ServiceChangeTracker {
  194. return &ServiceChangeTracker{
  195. items: make(map[types.NamespacedName]*serviceChange),
  196. makeServiceInfo: makeServiceInfo,
  197. isIPv6Mode: isIPv6Mode,
  198. recorder: recorder,
  199. }
  200. }
  201. // Update updates given service's change map based on the <previous, current> service pair. It returns true if items changed,
  202. // otherwise return false. Update can be used to add/update/delete items of ServiceChangeMap. For example,
  203. // Add item
  204. // - pass <nil, service> as the <previous, current> pair.
  205. // Update item
  206. // - pass <oldService, service> as the <previous, current> pair.
  207. // Delete item
  208. // - pass <service, nil> as the <previous, current> pair.
  209. func (sct *ServiceChangeTracker) Update(previous, current *v1.Service) bool {
  210. svc := current
  211. if svc == nil {
  212. svc = previous
  213. }
  214. // previous == nil && current == nil is unexpected, we should return false directly.
  215. if svc == nil {
  216. return false
  217. }
  218. metrics.ServiceChangesTotal.Inc()
  219. namespacedName := types.NamespacedName{Namespace: svc.Namespace, Name: svc.Name}
  220. sct.lock.Lock()
  221. defer sct.lock.Unlock()
  222. change, exists := sct.items[namespacedName]
  223. if !exists {
  224. change = &serviceChange{}
  225. change.previous = sct.serviceToServiceMap(previous)
  226. sct.items[namespacedName] = change
  227. }
  228. change.current = sct.serviceToServiceMap(current)
  229. // if change.previous equal to change.current, it means no change
  230. if reflect.DeepEqual(change.previous, change.current) {
  231. delete(sct.items, namespacedName)
  232. }
  233. metrics.ServiceChangesPending.Set(float64(len(sct.items)))
  234. return len(sct.items) > 0
  235. }
  236. // UpdateServiceMapResult is the updated results after applying service changes.
  237. type UpdateServiceMapResult struct {
  238. // HCServiceNodePorts is a map of Service names to node port numbers which indicate the health of that Service on this Node.
  239. // The value(uint16) of HCServices map is the service health check node port.
  240. HCServiceNodePorts map[types.NamespacedName]uint16
  241. // UDPStaleClusterIP holds stale (no longer assigned to a Service) Service IPs that had UDP ports.
  242. // Callers can use this to abort timeout-waits or clear connection-tracking information.
  243. UDPStaleClusterIP sets.String
  244. }
  245. // UpdateServiceMap updates ServiceMap based on the given changes.
  246. func UpdateServiceMap(serviceMap ServiceMap, changes *ServiceChangeTracker) (result UpdateServiceMapResult) {
  247. result.UDPStaleClusterIP = sets.NewString()
  248. serviceMap.apply(changes, result.UDPStaleClusterIP)
  249. // TODO: If this will appear to be computationally expensive, consider
  250. // computing this incrementally similarly to serviceMap.
  251. result.HCServiceNodePorts = make(map[types.NamespacedName]uint16)
  252. for svcPortName, info := range serviceMap {
  253. if info.HealthCheckNodePort() != 0 {
  254. result.HCServiceNodePorts[svcPortName.NamespacedName] = uint16(info.HealthCheckNodePort())
  255. }
  256. }
  257. return result
  258. }
  259. // ServiceMap maps a service to its ServicePort.
  260. type ServiceMap map[ServicePortName]ServicePort
  261. // serviceToServiceMap translates a single Service object to a ServiceMap.
  262. //
  263. // NOTE: service object should NOT be modified.
  264. func (sct *ServiceChangeTracker) serviceToServiceMap(service *v1.Service) ServiceMap {
  265. if service == nil {
  266. return nil
  267. }
  268. svcName := types.NamespacedName{Namespace: service.Namespace, Name: service.Name}
  269. if utilproxy.ShouldSkipService(svcName, service) {
  270. return nil
  271. }
  272. if len(service.Spec.ClusterIP) != 0 {
  273. // Filter out the incorrect IP version case.
  274. // If ClusterIP on service has incorrect IP version, service itself will be ignored.
  275. if sct.isIPv6Mode != nil && utilnet.IsIPv6String(service.Spec.ClusterIP) != *sct.isIPv6Mode {
  276. utilproxy.LogAndEmitIncorrectIPVersionEvent(sct.recorder, "clusterIP", service.Spec.ClusterIP, service.Namespace, service.Name, service.UID)
  277. return nil
  278. }
  279. }
  280. serviceMap := make(ServiceMap)
  281. for i := range service.Spec.Ports {
  282. servicePort := &service.Spec.Ports[i]
  283. svcPortName := ServicePortName{NamespacedName: svcName, Port: servicePort.Name, Protocol: servicePort.Protocol}
  284. baseSvcInfo := sct.newBaseServiceInfo(servicePort, service)
  285. if sct.makeServiceInfo != nil {
  286. serviceMap[svcPortName] = sct.makeServiceInfo(servicePort, service, baseSvcInfo)
  287. } else {
  288. serviceMap[svcPortName] = baseSvcInfo
  289. }
  290. }
  291. return serviceMap
  292. }
  293. // apply the changes to ServiceMap and update the stale udp cluster IP set. The UDPStaleClusterIP argument is passed in to store the
  294. // udp protocol service cluster ip when service is deleted from the ServiceMap.
  295. func (sm *ServiceMap) apply(changes *ServiceChangeTracker, UDPStaleClusterIP sets.String) {
  296. changes.lock.Lock()
  297. defer changes.lock.Unlock()
  298. for _, change := range changes.items {
  299. sm.merge(change.current)
  300. // filter out the Update event of current changes from previous changes before calling unmerge() so that can
  301. // skip deleting the Update events.
  302. change.previous.filter(change.current)
  303. sm.unmerge(change.previous, UDPStaleClusterIP)
  304. }
  305. // clear changes after applying them to ServiceMap.
  306. changes.items = make(map[types.NamespacedName]*serviceChange)
  307. metrics.ServiceChangesPending.Set(0)
  308. }
  309. // merge adds other ServiceMap's elements to current ServiceMap.
  310. // If collision, other ALWAYS win. Otherwise add the other to current.
  311. // In other words, if some elements in current collisions with other, update the current by other.
  312. // It returns a string type set which stores all the newly merged services' identifier, ServicePortName.String(), to help users
  313. // tell if a service is deleted or updated.
  314. // The returned value is one of the arguments of ServiceMap.unmerge().
  315. // ServiceMap A Merge ServiceMap B will do following 2 things:
  316. // * update ServiceMap A.
  317. // * produce a string set which stores all other ServiceMap's ServicePortName.String().
  318. // For example,
  319. // - A{}
  320. // - B{{"ns", "cluster-ip", "http"}: {"172.16.55.10", 1234, "TCP"}}
  321. // - A updated to be {{"ns", "cluster-ip", "http"}: {"172.16.55.10", 1234, "TCP"}}
  322. // - produce string set {"ns/cluster-ip:http"}
  323. // - A{{"ns", "cluster-ip", "http"}: {"172.16.55.10", 345, "UDP"}}
  324. // - B{{"ns", "cluster-ip", "http"}: {"172.16.55.10", 1234, "TCP"}}
  325. // - A updated to be {{"ns", "cluster-ip", "http"}: {"172.16.55.10", 1234, "TCP"}}
  326. // - produce string set {"ns/cluster-ip:http"}
  327. func (sm *ServiceMap) merge(other ServiceMap) sets.String {
  328. // existingPorts is going to store all identifiers of all services in `other` ServiceMap.
  329. existingPorts := sets.NewString()
  330. for svcPortName, info := range other {
  331. // Take ServicePortName.String() as the newly merged service's identifier and put it into existingPorts.
  332. existingPorts.Insert(svcPortName.String())
  333. _, exists := (*sm)[svcPortName]
  334. if !exists {
  335. klog.V(1).Infof("Adding new service port %q at %s", svcPortName, info.String())
  336. } else {
  337. klog.V(1).Infof("Updating existing service port %q at %s", svcPortName, info.String())
  338. }
  339. (*sm)[svcPortName] = info
  340. }
  341. return existingPorts
  342. }
  343. // filter filters out elements from ServiceMap base on given ports string sets.
  344. func (sm *ServiceMap) filter(other ServiceMap) {
  345. for svcPortName := range *sm {
  346. // skip the delete for Update event.
  347. if _, ok := other[svcPortName]; ok {
  348. delete(*sm, svcPortName)
  349. }
  350. }
  351. }
  352. // unmerge deletes all other ServiceMap's elements from current ServiceMap. We pass in the UDPStaleClusterIP strings sets
  353. // for storing the stale udp service cluster IPs. We will clear stale udp connection base on UDPStaleClusterIP later
  354. func (sm *ServiceMap) unmerge(other ServiceMap, UDPStaleClusterIP sets.String) {
  355. for svcPortName := range other {
  356. info, exists := (*sm)[svcPortName]
  357. if exists {
  358. klog.V(1).Infof("Removing service port %q", svcPortName)
  359. if info.Protocol() == v1.ProtocolUDP {
  360. UDPStaleClusterIP.Insert(info.ClusterIP().String())
  361. }
  362. delete(*sm, svcPortName)
  363. } else {
  364. klog.Errorf("Service port %q doesn't exists", svcPortName)
  365. }
  366. }
  367. }