@@ -153,18 +153,44 @@ type endpointsInfo struct {
153
153
}
154
154
155
155
// returns a new serviceInfo struct
156
- func newServiceInfo (service proxy.ServicePortName ) * serviceInfo {
157
- return & serviceInfo {
158
- sessionAffinityType : api .ServiceAffinityNone , // default
159
- stickyMaxAgeMinutes : 180 , // TODO: paramaterize this in the API.
156
+ func newServiceInfo (serviceName proxy.ServicePortName , port * api.ServicePort , service * api.Service ) * serviceInfo {
157
+ onlyNodeLocalEndpoints := apiservice .NeedsHealthCheck (service ) && featuregate .DefaultFeatureGate .ExternalTrafficLocalOnly () && (service .Spec .Type == api .ServiceTypeLoadBalancer || service .Spec .Type == api .ServiceTypeNodePort )
158
+ info := & serviceInfo {
159
+ clusterIP : net .ParseIP (service .Spec .ClusterIP ),
160
+ port : int (port .Port ),
161
+ protocol : port .Protocol ,
162
+ nodePort : int (port .NodePort ),
163
+ // Deep-copy in case the service instance changes
164
+ loadBalancerStatus : * api .LoadBalancerStatusDeepCopy (& service .Status .LoadBalancer ),
165
+ sessionAffinityType : service .Spec .SessionAffinity ,
166
+ stickyMaxAgeMinutes : 180 , // TODO: paramaterize this in the API.
167
+ externalIPs : make ([]string , len (service .Spec .ExternalIPs )),
168
+ loadBalancerSourceRanges : make ([]string , len (service .Spec .LoadBalancerSourceRanges )),
169
+ onlyNodeLocalEndpoints : onlyNodeLocalEndpoints ,
170
+ }
171
+ copy (info .loadBalancerSourceRanges , service .Spec .LoadBalancerSourceRanges )
172
+ copy (info .externalIPs , service .Spec .ExternalIPs )
173
+
174
+ if info .onlyNodeLocalEndpoints {
175
+ p := apiservice .GetServiceHealthCheckNodePort (service )
176
+ if p == 0 {
177
+ glog .Errorf ("Service does not contain necessary annotation %v" ,
178
+ apiservice .BetaAnnotationHealthCheckNodePort )
179
+ } else {
180
+ info .healthCheckNodePort = int (p )
181
+ }
160
182
}
183
+
184
+ return info
161
185
}
162
186
187
+ type proxyServiceMap map [proxy.ServicePortName ]* serviceInfo
188
+
163
189
// Proxier is an iptables based proxy for connections between a localhost:lport
164
190
// and services that provide the actual backends.
165
191
type Proxier struct {
166
192
mu sync.Mutex // protects the following fields
167
- serviceMap map [proxy. ServicePortName ] * serviceInfo
193
+ serviceMap proxyServiceMap
168
194
endpointsMap map [proxy.ServicePortName ][]* endpointsInfo
169
195
portsMap map [localPort ]closeable
170
196
haveReceivedServiceUpdate bool // true once we've seen an OnServiceUpdate event
@@ -278,7 +304,7 @@ func NewProxier(ipt utiliptables.Interface,
278
304
}
279
305
280
306
return & Proxier {
281
- serviceMap : make (map [proxy. ServicePortName ] * serviceInfo ),
307
+ serviceMap : make (proxyServiceMap ),
282
308
endpointsMap : make (map [proxy.ServicePortName ][]* endpointsInfo ),
283
309
portsMap : make (map [localPort ]closeable ),
284
310
syncPeriod : syncPeriod ,
@@ -382,44 +408,6 @@ func CleanupLeftovers(ipt utiliptables.Interface) (encounteredError bool) {
382
408
return encounteredError
383
409
}
384
410
385
- func (proxier * Proxier ) sameConfig (info * serviceInfo , service * api.Service , port * api.ServicePort ) bool {
386
- if info .protocol != port .Protocol || info .port != int (port .Port ) || info .nodePort != int (port .NodePort ) {
387
- return false
388
- }
389
- if ! info .clusterIP .Equal (net .ParseIP (service .Spec .ClusterIP )) {
390
- return false
391
- }
392
- if ! ipsEqual (info .externalIPs , service .Spec .ExternalIPs ) {
393
- return false
394
- }
395
- if ! api .LoadBalancerStatusEqual (& info .loadBalancerStatus , & service .Status .LoadBalancer ) {
396
- return false
397
- }
398
- if info .sessionAffinityType != service .Spec .SessionAffinity {
399
- return false
400
- }
401
- onlyNodeLocalEndpoints := apiservice .NeedsHealthCheck (service ) && featuregate .DefaultFeatureGate .ExternalTrafficLocalOnly ()
402
- if info .onlyNodeLocalEndpoints != onlyNodeLocalEndpoints {
403
- return false
404
- }
405
- if ! reflect .DeepEqual (info .loadBalancerSourceRanges , service .Spec .LoadBalancerSourceRanges ) {
406
- return false
407
- }
408
- return true
409
- }
410
-
411
- func ipsEqual (lhs , rhs []string ) bool {
412
- if len (lhs ) != len (rhs ) {
413
- return false
414
- }
415
- for i := range lhs {
416
- if lhs [i ] != rhs [i ] {
417
- return false
418
- }
419
- }
420
- return true
421
- }
422
-
423
411
// Sync is called to immediately synchronize the proxier state to iptables
424
412
func (proxier * Proxier ) Sync () {
425
413
proxier .mu .Lock ()
@@ -438,18 +426,18 @@ func (proxier *Proxier) SyncLoop() {
438
426
}
439
427
}
440
428
441
- // OnServiceUpdate tracks the active set of service proxies.
442
- // They will be synchronized using syncProxyRules()
443
- func (proxier * Proxier ) OnServiceUpdate (allServices []api.Service ) {
444
- start := time .Now ()
445
- defer func () {
446
- glog .V (4 ).Infof ("OnServiceUpdate took %v for %d services" , time .Since (start ), len (allServices ))
447
- }()
448
- proxier .mu .Lock ()
449
- defer proxier .mu .Unlock ()
450
- proxier .haveReceivedServiceUpdate = true
429
+ type healthCheckPort struct {
430
+ namespace types.NamespacedName
431
+ nodeport int
432
+ }
451
433
452
- activeServices := make (map [proxy.ServicePortName ]bool ) // use a map as a set
434
+ // Accepts a list of Services and the existing service map. Returns the new
435
+ // service map, a list of healthcheck ports to add to or remove from the health
436
+ // checking listener service, and a set of stale UDP services.
437
+ func buildServiceMap (allServices []api.Service , oldServiceMap proxyServiceMap ) (proxyServiceMap , []healthCheckPort , []healthCheckPort , sets.String ) {
438
+ newServiceMap := make (proxyServiceMap )
439
+ healthCheckAdd := make ([]healthCheckPort , 0 )
440
+ healthCheckDel := make ([]healthCheckPort , 0 )
453
441
454
442
for i := range allServices {
455
443
service := & allServices [i ]
@@ -463,6 +451,11 @@ func (proxier *Proxier) OnServiceUpdate(allServices []api.Service) {
463
451
glog .V (3 ).Infof ("Skipping service %s due to clusterIP = %q" , svcName , service .Spec .ClusterIP )
464
452
continue
465
453
}
454
+ // Even if ClusterIP is set, ServiceTypeExternalName services don't get proxied
455
+ if service .Spec .Type == api .ServiceTypeExternalName {
456
+ glog .V (3 ).Infof ("Skipping service %s due to Type=ExternalName" , svcName )
457
+ continue
458
+ }
466
459
467
460
for i := range service .Spec .Ports {
468
461
servicePort := & service .Spec .Ports [i ]
@@ -471,72 +464,80 @@ func (proxier *Proxier) OnServiceUpdate(allServices []api.Service) {
471
464
NamespacedName : svcName ,
472
465
Port : servicePort .Name ,
473
466
}
474
- activeServices [serviceName ] = true
475
- info , exists := proxier .serviceMap [serviceName ]
476
- if exists && proxier .sameConfig (info , service , servicePort ) {
477
- // Nothing changed.
478
- continue
479
- }
480
- if exists {
481
- // Something changed.
482
- glog .V (3 ).Infof ("Something changed for service %q: removing it" , serviceName )
483
- delete (proxier .serviceMap , serviceName )
467
+
468
+ info := newServiceInfo (serviceName , servicePort , service )
469
+ oldInfo , exists := oldServiceMap [serviceName ]
470
+ equal := reflect .DeepEqual (info , oldInfo )
471
+ if ! exists {
472
+ glog .V (1 ).Infof ("Adding new service %q at %s:%d/%s" , serviceName , info .clusterIP , servicePort .Port , servicePort .Protocol )
473
+ } else if ! equal {
474
+ glog .V (1 ).Infof ("Updating existing service %q at %s:%d/%s" , serviceName , info .clusterIP , servicePort .Port , servicePort .Protocol )
484
475
}
485
- serviceIP := net .ParseIP (service .Spec .ClusterIP )
486
- glog .V (1 ).Infof ("Adding new service %q at %s:%d/%s" , serviceName , serviceIP , servicePort .Port , servicePort .Protocol )
487
- info = newServiceInfo (serviceName )
488
- info .clusterIP = serviceIP
489
- info .port = int (servicePort .Port )
490
- info .protocol = servicePort .Protocol
491
- info .nodePort = int (servicePort .NodePort )
492
- info .externalIPs = service .Spec .ExternalIPs
493
- // Deep-copy in case the service instance changes
494
- info .loadBalancerStatus = * api .LoadBalancerStatusDeepCopy (& service .Status .LoadBalancer )
495
- info .sessionAffinityType = service .Spec .SessionAffinity
496
- info .loadBalancerSourceRanges = service .Spec .LoadBalancerSourceRanges
497
- info .onlyNodeLocalEndpoints = apiservice .NeedsHealthCheck (service ) && featuregate .DefaultFeatureGate .ExternalTrafficLocalOnly () && (service .Spec .Type == api .ServiceTypeLoadBalancer || service .Spec .Type == api .ServiceTypeNodePort )
498
- if info .onlyNodeLocalEndpoints {
499
- p := apiservice .GetServiceHealthCheckNodePort (service )
500
- if p == 0 {
501
- glog .Errorf ("Service does not contain necessary annotation %v" ,
502
- apiservice .BetaAnnotationHealthCheckNodePort )
476
+
477
+ if ! exists || ! equal {
478
+ if info .onlyNodeLocalEndpoints && info .healthCheckNodePort > 0 {
479
+ healthCheckAdd = append (healthCheckAdd , healthCheckPort {serviceName .NamespacedName , info .healthCheckNodePort })
503
480
} else {
504
- glog .V (4 ).Infof ("Adding health check for %+v, port %v" , serviceName .NamespacedName , p )
505
- info .healthCheckNodePort = int (p )
506
- // Turn on healthcheck responder to listen on the health check nodePort
507
- healthcheck .AddServiceListener (serviceName .NamespacedName , info .healthCheckNodePort )
481
+ healthCheckDel = append (healthCheckDel , healthCheckPort {serviceName .NamespacedName , 0 })
508
482
}
509
- } else {
510
- glog .V (4 ).Infof ("Deleting health check for %+v" , serviceName .NamespacedName )
511
- // Delete healthcheck responders, if any, previously listening for this service
512
- healthcheck .DeleteServiceListener (serviceName .NamespacedName , 0 )
513
483
}
514
- proxier .serviceMap [serviceName ] = info
515
484
485
+ newServiceMap [serviceName ] = info
516
486
glog .V (4 ).Infof ("added serviceInfo(%s): %s" , serviceName , spew .Sdump (info ))
517
487
}
518
488
}
519
489
520
490
staleUDPServices := sets .NewString ()
521
491
// Remove serviceports missing from the update.
522
- for name , info := range proxier . serviceMap {
523
- if ! activeServices [name ] {
492
+ for name , info := range oldServiceMap {
493
+ if _ , exists := newServiceMap [name ]; ! exists {
524
494
glog .V (1 ).Infof ("Removing service %q" , name )
525
495
if info .protocol == api .ProtocolUDP {
526
496
staleUDPServices .Insert (info .clusterIP .String ())
527
497
}
528
- delete (proxier .serviceMap , name )
529
498
if info .onlyNodeLocalEndpoints && info .healthCheckNodePort > 0 {
530
- // Remove ServiceListener health check nodePorts from the health checker
531
- // TODO - Stats
532
- glog .V (4 ).Infof ("Deleting health check for %+v, port %v" , name .NamespacedName , info .healthCheckNodePort )
533
- healthcheck .DeleteServiceListener (name .NamespacedName , info .healthCheckNodePort )
499
+ healthCheckDel = append (healthCheckDel , healthCheckPort {name .NamespacedName , info .healthCheckNodePort })
534
500
}
535
501
}
536
502
}
537
- proxier .syncProxyRules ()
538
- proxier .deleteServiceConnections (staleUDPServices .List ())
539
503
504
+ return newServiceMap , healthCheckAdd , healthCheckDel , staleUDPServices
505
+ }
506
+
507
+ // OnServiceUpdate tracks the active set of service proxies.
508
+ // They will be synchronized using syncProxyRules()
509
+ func (proxier * Proxier ) OnServiceUpdate (allServices []api.Service ) {
510
+ start := time .Now ()
511
+ defer func () {
512
+ glog .V (4 ).Infof ("OnServiceUpdate took %v for %d services" , time .Since (start ), len (allServices ))
513
+ }()
514
+ proxier .mu .Lock ()
515
+ defer proxier .mu .Unlock ()
516
+ proxier .haveReceivedServiceUpdate = true
517
+
518
+ newServiceMap , hcAdd , hcDel , staleUDPServices := buildServiceMap (allServices , proxier .serviceMap )
519
+ for _ , hc := range hcAdd {
520
+ glog .V (4 ).Infof ("Adding health check for %+v, port %v" , hc .namespace , hc .nodeport )
521
+ // Turn on healthcheck responder to listen on the health check nodePort
522
+ // FIXME: handle failures from adding the service
523
+ healthcheck .AddServiceListener (hc .namespace , hc .nodeport )
524
+ }
525
+ for _ , hc := range hcDel {
526
+ // Remove ServiceListener health check nodePorts from the health checker
527
+ // TODO - Stats
528
+ glog .V (4 ).Infof ("Deleting health check for %+v, port %v" , hc .namespace , hc .nodeport )
529
+ // FIXME: handle failures from deleting the service
530
+ healthcheck .DeleteServiceListener (hc .namespace , hc .nodeport )
531
+ }
532
+
533
+ if len (newServiceMap ) != len (proxier .serviceMap ) || ! reflect .DeepEqual (newServiceMap , proxier .serviceMap ) {
534
+ proxier .serviceMap = newServiceMap
535
+ proxier .syncProxyRules ()
536
+ } else {
537
+ glog .V (4 ).Infof ("Skipping proxy iptables rule sync on service update because nothing changed" )
538
+ }
539
+
540
+ proxier .deleteServiceConnections (staleUDPServices .List ())
540
541
}
541
542
542
543
// Generate a list of ip strings from the list of endpoint infos
0 commit comments