Skip to content

Commit fc9581e

Browse files
committed
Prevent LoadBalancer updates on follower.
Start watching Service updates only after becoming leader, when starting loadBalancerStatusWriter. This avoids piling up unprocessed Service updates on follower instance, consuming memory and eventually causing an out-of-memory condition and killing Contour process. Signed-off-by: Tero Saarni <[email protected]>
1 parent 7e2dcaf commit fc9581e

File tree

4 files changed

+61
-25
lines changed

4 files changed

+61
-25
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed a memory leak in Contour follower instance due to unprocessed LoadBalancer status updates.

cmd/contour/ingressstatus.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
core_v1 "k8s.io/api/core/v1"
2323
networking_v1 "k8s.io/api/networking/v1"
2424
"k8s.io/apimachinery/pkg/types"
25+
client_go_cache "k8s.io/client-go/tools/cache"
2526
"sigs.k8s.io/controller-runtime/pkg/cache"
2627
"sigs.k8s.io/controller-runtime/pkg/client"
2728
gatewayapi_v1 "sigs.k8s.io/gateway-api/apis/v1"
@@ -55,13 +56,41 @@ type loadBalancerStatusWriter struct {
5556
statusUpdater k8s.StatusUpdater
5657
ingressClassNames []string
5758
gatewayRef *types.NamespacedName
59+
statusAddress string
60+
serviceName string
61+
serviceNamespace string
5862
}
5963

6064
func (isw *loadBalancerStatusWriter) NeedLeaderElection() bool {
6165
return true
6266
}
6367

6468
func (isw *loadBalancerStatusWriter) Start(ctx context.Context) error {
69+
// Register an informer to watch envoy's service if we haven't been given static details.
70+
if lbAddress := isw.statusAddress; len(lbAddress) > 0 {
71+
isw.log.WithField("loadbalancer-address", lbAddress).Info("Using supplied information for Ingress status")
72+
isw.lbStatus <- parseStatusFlag(lbAddress)
73+
} else {
74+
// Register Service informer to watch for status updates.
75+
var serviceHandler client_go_cache.ResourceEventHandler = &k8s.ServiceStatusLoadBalancerWatcher{
76+
ServiceName: isw.serviceName,
77+
LBStatus: isw.lbStatus,
78+
Log: isw.log.WithField("context", "serviceStatusLoadBalancerWatcher"),
79+
}
80+
var handler client_go_cache.ResourceEventHandler = serviceHandler
81+
if isw.serviceNamespace != "" {
82+
serviceHandler = k8s.NewNamespaceFilter([]string{isw.serviceNamespace}, handler)
83+
}
84+
inf, err := isw.cache.GetInformer(ctx, &core_v1.Service{})
85+
if err != nil {
86+
isw.log.WithError(err).Fatal("failed to get Service informer")
87+
}
88+
_, err = inf.AddEventHandler(serviceHandler)
89+
if err != nil {
90+
isw.log.WithError(err).Fatal("failed to add Service event handler")
91+
}
92+
}
93+
6594
u := &k8s.StatusAddressUpdater{
6695
Logger: func() logrus.FieldLogger {
6796
// Configure the StatusAddressUpdater logger.

cmd/contour/serve.go

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -700,36 +700,14 @@ func (s *Server) doServe() error {
700700
ingressClassNames: ingressClassNames,
701701
gatewayRef: gatewayRef,
702702
statusUpdater: sh.Writer(),
703+
statusAddress: contourConfiguration.Ingress.StatusAddress,
704+
serviceName: contourConfiguration.Envoy.Service.Name,
705+
serviceNamespace: contourConfiguration.Envoy.Service.Namespace,
703706
}
704707
if err := s.mgr.Add(lbsw); err != nil {
705708
return err
706709
}
707710

708-
// Register an informer to watch envoy's service if we haven't been given static details.
709-
if lbAddress := contourConfiguration.Ingress.StatusAddress; len(lbAddress) > 0 {
710-
s.log.WithField("loadbalancer-address", lbAddress).Info("Using supplied information for Ingress status")
711-
lbsw.lbStatus <- parseStatusFlag(lbAddress)
712-
} else {
713-
serviceHandler := &k8s.ServiceStatusLoadBalancerWatcher{
714-
ServiceName: contourConfiguration.Envoy.Service.Name,
715-
LBStatus: lbsw.lbStatus,
716-
Log: s.log.WithField("context", "serviceStatusLoadBalancerWatcher"),
717-
}
718-
719-
var handler cache.ResourceEventHandler = serviceHandler
720-
if contourConfiguration.Envoy.Service.Namespace != "" {
721-
handler = k8s.NewNamespaceFilter([]string{contourConfiguration.Envoy.Service.Namespace}, handler)
722-
}
723-
724-
if err := s.informOnResource(&core_v1.Service{}, handler); err != nil {
725-
s.log.WithError(err).WithField("resource", "services").Fatal("failed to create informer")
726-
}
727-
728-
s.log.WithField("envoy-service-name", contourConfiguration.Envoy.Service.Name).
729-
WithField("envoy-service-namespace", contourConfiguration.Envoy.Service.Namespace).
730-
Info("Watching Service for Ingress status")
731-
}
732-
733711
xdsServer := &xdsServer{
734712
log: s.log,
735713
registry: s.registry,

internal/k8s/statusaddress_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func TestServiceStatusLoadBalancerWatcherOnAdd(t *testing.T) {
4141
LBStatus: lbstatus,
4242
Log: fixture.NewTestLogger(t),
4343
}
44+
sw.OnElectedLeader()
4445

4546
recv := func() (core_v1.LoadBalancerStatus, bool) {
4647
select {
@@ -89,6 +90,7 @@ func TestServiceStatusLoadBalancerWatcherOnUpdate(t *testing.T) {
8990
LBStatus: lbstatus,
9091
Log: fixture.NewTestLogger(t),
9192
}
93+
sw.OnElectedLeader()
9294

9395
recv := func() (core_v1.LoadBalancerStatus, bool) {
9496
select {
@@ -139,6 +141,7 @@ func TestServiceStatusLoadBalancerWatcherOnDelete(t *testing.T) {
139141
LBStatus: lbstatus,
140142
Log: fixture.NewTestLogger(t),
141143
}
144+
sw.OnElectedLeader()
142145

143146
recv := func() (core_v1.LoadBalancerStatus, bool) {
144147
select {
@@ -179,6 +182,31 @@ func TestServiceStatusLoadBalancerWatcherOnDelete(t *testing.T) {
179182
assert.Equal(t, want, got)
180183
}
181184

185+
func TestServiceStatusLoadBalancerWatcherNoNotificationsOnFollower(t *testing.T) {
186+
lbstatus := make(chan core_v1.LoadBalancerStatus, 1)
187+
188+
sw := ServiceStatusLoadBalancerWatcher{
189+
ServiceName: "envoy",
190+
LBStatus: lbstatus,
191+
Log: fixture.NewTestLogger(t),
192+
}
193+
194+
recv := func() bool {
195+
select {
196+
case <-sw.LBStatus:
197+
return true
198+
default:
199+
return false
200+
}
201+
}
202+
203+
// assert that when not elected leader, no notifications are sent.
204+
var svc core_v1.Service
205+
svc.Name = "envoy"
206+
sw.OnAdd(&svc, false)
207+
assert.False(t, recv())
208+
}
209+
182210
func TestStatusAddressUpdater(t *testing.T) {
183211
const objName = "someobjfoo"
184212

0 commit comments

Comments
 (0)