Skip to content

Commit 8f0b6b5

Browse files
Merge pull request #655 from DataDog/alm/backport-pr44527
[CMPT-3823] Backport fix for KVStore service resolution Co-authored-by: 41ks <alex.melhem@datadoghq.com>
2 parents 106ac96 + da1616e commit 8f0b6b5

4 files changed

Lines changed: 57 additions & 2 deletions

File tree

pkg/datapath/linux/ipsec/cell_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import (
4040
"github.com/cilium/cilium/pkg/kvstore"
4141
"github.com/cilium/cilium/pkg/kvstore/store"
4242
"github.com/cilium/cilium/pkg/loadbalancer"
43+
"github.com/cilium/cilium/pkg/loadbalancer/reflectors"
4344
"github.com/cilium/cilium/pkg/loadbalancer/writer"
4445
"github.com/cilium/cilium/pkg/maps/encrypt"
4546
"github.com/cilium/cilium/pkg/mtu"
@@ -133,15 +134,18 @@ func TestPrivileged_TestIPSecCell(t *testing.T) {
133134
source.Cell,
134135
watchers.Cell,
135136
dial.ServiceResolverCell,
137+
reflectors.K8sReflectorCell,
136138
clustermesh.Cell,
137139
writer.Cell,
138140
ipset.Cell,
139141
k8s.ResourcesCell,
142+
k8s.PodTableCell,
140143
node.LocalNodeStoreTestCell,
141144
k8sClient.FakeClientCell(),
142145
kvstore.Cell(kvstore.DisabledBackendName),
143146

144147
cell.Provide(
148+
reflectors.NetnsCookieSupportFunc,
145149
newIPsecAgent,
146150
newIPsecConfig,
147151

pkg/dial/resolver.go

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
package dial
55

66
import (
7+
"cmp"
78
"context"
89
"fmt"
910
"iter"
11+
"log/slog"
1012
"math/rand/v2"
1113
"net/netip"
1214
"slices"
@@ -17,14 +19,17 @@ import (
1719
"github.com/cilium/hive/cell"
1820
"github.com/cilium/hive/job"
1921
"github.com/cilium/statedb"
22+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2023
"k8s.io/apimachinery/pkg/types"
2124

2225
cmtypes "github.com/cilium/cilium/pkg/clustermesh/types"
26+
k8sClient "github.com/cilium/cilium/pkg/k8s/client"
2327
"github.com/cilium/cilium/pkg/k8s/resource"
2428
slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1"
2529
"github.com/cilium/cilium/pkg/loadbalancer"
2630
"github.com/cilium/cilium/pkg/loadbalancer/reflectors"
2731
"github.com/cilium/cilium/pkg/lock"
32+
"github.com/cilium/cilium/pkg/logging/logfields"
2833
"github.com/cilium/cilium/pkg/time"
2934
)
3035

@@ -126,15 +131,23 @@ var _ Resolver = (*lbServiceResolver)(nil)
126131

127132
// lbServiceResolver maps DNS names matching Kubernetes services to the
128133
// corresponding ClusterIP address using Table[*Frontend].
134+
// If the frontend table lookup fails, it falls back to fetching the service
135+
// directly from the kube-apiserver.
129136
type lbServiceResolver struct {
130137
db *statedb.DB
131138
frontends statedb.Table[*loadbalancer.Frontend]
139+
cs k8sClient.Clientset
140+
log *slog.Logger
132141
}
133142

134-
func newLBServiceResolver(jg job.Group, db *statedb.DB, frontends statedb.Table[*loadbalancer.Frontend]) Resolver {
143+
// A dependency on reflector.K8sReflectorRegistered is used to ensure the order of initialization.
144+
// Otherwise, the resolver could be used before the frontends table is initialized.
145+
func newLBServiceResolver(_ reflectors.K8sReflectorRegistered, jg job.Group, db *statedb.DB, frontends statedb.Table[*loadbalancer.Frontend], cs k8sClient.Clientset, log *slog.Logger) Resolver {
135146
return &lbServiceResolver{
136147
db: db,
137148
frontends: frontends,
149+
cs: cs,
150+
log: log,
138151
}
139152
}
140153

@@ -152,8 +165,11 @@ func (sr *lbServiceResolver) resolve(ctx context.Context, host string) string {
152165
// Wait for the frontends table to be initialized from k8s. We can't check that
153166
// the table has been initialized by all initializers since at least ClusterMesh
154167
// uses [Resolve] to look up KVStore address.
168+
// If the frontends table is not initialized, fallback to kube-apiserver.
155169
txn := sr.db.ReadTxn()
156170
init, waitInit := sr.frontends.Initialized(txn)
171+
// We give the frontends table 5 seconds to be initialized to avoid a deadlock.
172+
initTimeout := time.After(5 * time.Second)
157173
for !init {
158174
pending := sr.frontends.PendingInitializers(txn)
159175
if !slices.ContainsFunc(pending, func(s string) bool { return strings.HasPrefix(s, reflectors.K8sInitializerPrefix) }) {
@@ -164,6 +180,13 @@ func (sr *lbServiceResolver) resolve(ctx context.Context, host string) string {
164180
return host
165181
case <-waitInit:
166182
init = true
183+
case <-initTimeout:
184+
sr.log.Warn(
185+
"Frontends table not initialized, falling back to kube-apiserver",
186+
logfields.K8sNamespace, nsname.Namespace,
187+
logfields.K8sSvcName, nsname.Name,
188+
)
189+
return cmp.Or(sr.resolveFromAPIServer(ctx, nsname), host)
167190
case <-time.After(100 * time.Millisecond):
168191
}
169192
txn = sr.db.ReadTxn()
@@ -183,6 +206,22 @@ func (sr *lbServiceResolver) resolve(ctx context.Context, host string) string {
183206
return host
184207
}
185208

209+
// resolveFromAPIServer fetches the service directly from the kube-apiserver
210+
// as a fallback when the frontends table takes too long to be initialized.
211+
func (sr *lbServiceResolver) resolveFromAPIServer(ctx context.Context, nsname types.NamespacedName) string {
212+
svc, err := sr.cs.Slim().CoreV1().Services(nsname.Namespace).Get(ctx, nsname.Name, metav1.GetOptions{})
213+
if err != nil {
214+
return ""
215+
}
216+
217+
if _, err := netip.ParseAddr(svc.Spec.ClusterIP); err != nil {
218+
// The ClusterIP is not a valid IP address (e.g., headless service)
219+
return ""
220+
}
221+
222+
return svc.Spec.ClusterIP
223+
}
224+
186225
func ServiceURLToNamespacedName(host string) (types.NamespacedName, error) {
187226
tokens := strings.Split(host, ".")
188227
if len(tokens) < 2 {

pkg/loadbalancer/reflectors/k8s.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,9 @@ var K8sReflectorCell = cell.Module(
6565
"k8s-reflector",
6666
"Reflects load-balancing state from Kubernetes",
6767

68+
cell.Provide(provideK8sReflector),
6869
cell.ProvidePrivate(newEventStream),
69-
cell.Invoke(RegisterK8sReflector),
70+
cell.Invoke(func(_ K8sReflectorRegistered) {}),
7071
)
7172

7273
type reflectorParams struct {
@@ -88,6 +89,13 @@ type reflectorParams struct {
8889
SVCMetrics SVCMetrics `optional:"true"`
8990
}
9091

92+
type K8sReflectorRegistered struct{}
93+
94+
func provideK8sReflector(p reflectorParams) K8sReflectorRegistered {
95+
RegisterK8sReflector(p)
96+
return K8sReflectorRegistered{}
97+
}
98+
9199
func (p reflectorParams) waitTime() time.Duration {
92100
if p.TestConfig != nil {
93101
// Use a much lower wait time in tests to trigger more edge cases and make them faster.

pkg/wireguard/agent/cell_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import (
4040
"github.com/cilium/cilium/pkg/kvstore"
4141
"github.com/cilium/cilium/pkg/kvstore/store"
4242
"github.com/cilium/cilium/pkg/loadbalancer"
43+
"github.com/cilium/cilium/pkg/loadbalancer/reflectors"
4344
"github.com/cilium/cilium/pkg/loadbalancer/writer"
4445
"github.com/cilium/cilium/pkg/mtu"
4546
"github.com/cilium/cilium/pkg/node"
@@ -119,16 +120,19 @@ func TestPrivileged_TestWireGuardCell(t *testing.T) {
119120
source.Cell,
120121
watchers.Cell,
121122
dial.ServiceResolverCell,
123+
reflectors.K8sReflectorCell,
122124
clustermesh.Cell,
123125
writer.Cell,
124126
ipset.Cell,
125127
k8s.ResourcesCell,
128+
k8s.PodTableCell,
126129
cell.Config(envoyCfg.SecretSyncConfig{}),
127130
k8sClient.FakeClientCell(),
128131
kvstore.Cell(kvstore.DisabledBackendName),
129132
node.LocalNodeStoreTestCell,
130133

131134
cell.Provide(
135+
reflectors.NetnsCookieSupportFunc,
132136
newWireguardAgent,
133137
newWireguardConfig,
134138

0 commit comments

Comments
 (0)