44package dial
55
66import (
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.
129136type 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+
186225func ServiceURLToNamespacedName (host string ) (types.NamespacedName , error ) {
187226 tokens := strings .Split (host , "." )
188227 if len (tokens ) < 2 {
0 commit comments