Skip to content

Commit 6c276c8

Browse files
committed
app/vlagent: pre-fetch all namespaces at startup to avoid blocking log reading
This change improves performance and security by fetching all namespace metadata once at application startup instead of making on-demand network calls during log processing. Key changes: - Add getAllNamespaces() method to fetch all namespaces in a single API call - Pre-fetch and cache all namespace metadata in startKubernetesCollector() - Remove getNamespace() on-demand fetching to eliminate runtime network calls - Change getNamespace() to cache-only lookup returning (namespace, bool) - Skip pods from namespaces not in cache to prevent processing logs with incomplete metadata that could bypass excludeFilter Benefits: - Eliminates network call latency during log reading (no blocking) - Prevents vlagent from appearing hung during network issues - Ensures excludeFilter has complete namespace labels/annotations for security-sensitive filtering (e.g., audit logs) - Simplifies code by removing timeout/retry logic - Fail-fast: vlagent exits if unable to fetch namespaces at startup Note: If a namespace is created after vlagent starts, pods in that namespace will be skipped until vlagent is restarted. This is acceptable as it ensures we never process logs without complete metadata. Updates VictoriaMetrics#949
1 parent a59fef5 commit 6c276c8

2 files changed

Lines changed: 53 additions & 48 deletions

File tree

app/vlagent/kubernetescollector/client.go

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,37 @@ type namespace struct {
293293
Metadata namespaceMetadata `json:"metadata"`
294294
}
295295

296+
type namespaceList struct {
297+
Items []namespace `json:"items"`
298+
}
299+
300+
// getAllNamespaces returns all namespaces in the Kubernetes cluster.
301+
//
302+
// See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#list-namespace-v1-core
303+
func (c *kubeAPIClient) getAllNamespaces(ctx context.Context) ([]namespace, error) {
304+
req := c.mustCreateRequest(ctx, http.MethodGet, "/api/v1/namespaces", nil)
305+
resp, err := c.c.Do(req)
306+
if err != nil {
307+
return nil, fmt.Errorf("cannot do %q GET request: %w", req.URL.String(), err)
308+
}
309+
defer resp.Body.Close()
310+
311+
if resp.StatusCode != http.StatusOK {
312+
payload, err := io.ReadAll(resp.Body)
313+
if err != nil {
314+
payload = []byte(err.Error())
315+
}
316+
return nil, fmt.Errorf("unexpected status code %d from %q; response: %q", resp.StatusCode, req.URL.String(), payload)
317+
}
318+
319+
var nsl namespaceList
320+
if err := json.NewDecoder(resp.Body).Decode(&nsl); err != nil {
321+
return nil, fmt.Errorf("cannot decode response body: %w", err)
322+
}
323+
324+
return nsl.Items, nil
325+
}
326+
296327
// getNodes returns the list of node names in the Kubernetes cluster.
297328
//
298329
// See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#list-node-v1-core
@@ -351,33 +382,6 @@ func (c *kubeAPIClient) getNodeByName(ctx context.Context, nodeName string) (nod
351382
return n, nil
352383
}
353384

354-
// getNamespace returns the namespace with the given name.
355-
//
356-
// See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#read-namespace-v1-core
357-
func (c *kubeAPIClient) getNamespace(ctx context.Context, namespaceName string) (namespace, error) {
358-
req := c.mustCreateRequest(ctx, http.MethodGet, "/api/v1/namespaces/"+namespaceName, nil)
359-
resp, err := c.c.Do(req)
360-
if err != nil {
361-
return namespace{}, fmt.Errorf("cannot do %q GET request: %w", req.URL.String(), err)
362-
}
363-
defer resp.Body.Close()
364-
365-
if resp.StatusCode != http.StatusOK {
366-
payload, err := io.ReadAll(resp.Body)
367-
if err != nil {
368-
payload = []byte(err.Error())
369-
}
370-
return namespace{}, fmt.Errorf("unexpected status code %d from %q; response: %q", resp.StatusCode, req.URL.String(), payload)
371-
}
372-
373-
var ns namespace
374-
if err := json.NewDecoder(resp.Body).Decode(&ns); err != nil {
375-
return namespace{}, fmt.Errorf("cannot decode response body: %w", err)
376-
}
377-
378-
return ns, nil
379-
}
380-
381385
func (c *kubeAPIClient) mustCreateRequest(ctx context.Context, method, urlPath string, args url.Values) *http.Request {
382386
req, err := http.NewRequestWithContext(ctx, method, "/", nil)
383387
if err != nil {

app/vlagent/kubernetescollector/collector.go

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ func startKubernetesCollector(client *kubeAPIClient, currentNodeName, logsPath,
6666
namespaces: make(map[string]namespace),
6767
}
6868

69+
namespaces, err := client.getAllNamespaces(ctx)
70+
if err != nil {
71+
cancel()
72+
return nil, fmt.Errorf("cannot fetch namespaces: %w; this is required for proper filtering", err)
73+
}
74+
for _, ns := range namespaces {
75+
kc.namespaces[ns.Metadata.Name] = ns
76+
}
77+
6978
storage := &remotewrite.Storage{}
7079
newProcessor := func(commonFields []logstorage.Field) processor {
7180
return newLogFileProcessor(storage, commonFields)
@@ -176,8 +185,12 @@ func (kc *kubernetesCollector) startWatchCluster(ctx context.Context, resourceVe
176185
}
177186

178187
func (kc *kubernetesCollector) startReadPodLogs(pod pod) {
188+
ns, ok := kc.getNamespace(pod.Metadata.Namespace)
189+
if !ok {
190+
return
191+
}
192+
179193
startRead := func(pc podContainer, cs containerStatus) {
180-
ns := kc.getNamespace(pod.Metadata.Namespace)
181194
commonFields := getCommonFields(kc.currentNode, ns, pod, cs)
182195

183196
filePath := kc.getLogFilePath(pod, pc, cs)
@@ -273,27 +286,15 @@ func (kc *kubernetesCollector) stop() {
273286
kc.fileCollector.stop()
274287
}
275288

276-
func (kc *kubernetesCollector) getNamespace(namespaceName string) namespace {
289+
// getNamespace returns namespace metadata from cache.
290+
// Returns (namespace, false) if the namespace is not in cache.
291+
// For security reasons, we must not process logs without complete namespace metadata,
292+
// as excludeFilter may rely on namespace labels/annotations to filter sensitive logs.
293+
// We only use the pre-fetched namespace list from startup and do not make additional
294+
// network calls at runtime to avoid blocking log reading.
295+
func (kc *kubernetesCollector) getNamespace(namespaceName string) (namespace, bool) {
277296
kc.namespacesLock.Lock()
278297
ns, ok := kc.namespaces[namespaceName]
279298
kc.namespacesLock.Unlock()
280-
if ok {
281-
return ns
282-
}
283-
284-
ns, err := kc.client.getNamespace(kc.ctx, namespaceName)
285-
if err != nil {
286-
logger.Errorf("cannot get namespace %q metadata: %s", namespaceName, err)
287-
return namespace{
288-
Metadata: namespaceMetadata{
289-
Name: namespaceName,
290-
},
291-
}
292-
}
293-
294-
kc.namespacesLock.Lock()
295-
kc.namespaces[namespaceName] = ns
296-
kc.namespacesLock.Unlock()
297-
298-
return ns
299+
return ns, ok
299300
}

0 commit comments

Comments
 (0)