Skip to content

Global Context Caching

By centralizing and pre-fetching heavy datasets, multiple policies can evaluate incoming mutation, validation, or generation requests instantly by adding a context entry using globalReference. The cached data is then accessible via the variable name assigned in that context entry (e.g., cached_configmaps).

HA Note: In high-availability deployments, each Kyverno replica maintains its own independent in-memory cache. Admission requests may be served by different replicas whose cache freshness can differ slightly between refresh cycles. This is expected behavior — design your policies to tolerate this per-replica eventual consistency.


Before configuring a GlobalContextEntry, ensure the following:

  • Kyverno >= 1.12.0 is installed in your cluster (GlobalContextEntry was introduced in this release). Verify your version with:

    Terminal window
    # Linux/macOS
    kubectl get deployment -n kyverno -o jsonpath='{range .items[*]}{.metadata.name}{": "}{.spec.template.spec.containers[0].image}{"\n"}{end}'
    # Windows PowerShell
    kubectl get deployment -n kyverno -o jsonpath="{range .items[*]}{.metadata.name}{': '}{.spec.template.spec.containers[0].image}{'\n'}{end}"
  • kubectl access with sufficient permissions to create cluster-scoped resources.

  • Kyverno ServiceAccount RBAC: For kubernetesResource mode, the Kyverno ServiceAccount must have get, list, and watch permissions on the target resource. This is especially important for custom resources (CRs).

API Version Note: GlobalContextEntry uses apiVersion: kyverno.io/v2. The older kyverno.io/v2alpha1 version is deprecated, but the GlobalContextEntry resource itself is fully supported and is not being deprecated — it continues to work with the CEL-based policy types.

Granting RBAC Permissions for Custom Resources (CRs)

Section titled “Granting RBAC Permissions for Custom Resources (CRs)”

If you are caching a custom resource (CR) served by a CRD — for example, a resource from a custom API group — append a rule to Kyverno’s existing ClusterRole using a JSON patch:

Terminal window
# Linux/macOS
kubectl patch clusterrole kyverno:admission-controller --type=json --patch '[{"op":"add","path":"/rules/-","value":{"apiGroups":["your.crd.group"],"resources":["yourresources"],"verbs":["get","list","watch"]}}]'
Terminal window
# Windows PowerShell
kubectl patch clusterrole kyverno:admission-controller --type=json --patch '[{"op":"add","path":"/rules/-","value":{"apiGroups":["your.crd.group"],"resources":["yourresources"],"verbs":["get","list","watch"]}}]'

Note: Replace your.crd.group and yourresources with your actual CRD API group and resource name (lowercased, pluralized). This appends to the existing rules without overwriting them. Standard resources like configmaps and secrets already have permissions by default — no patch needed.


graph TD
    subgraph "Standard Policy Evaluation"
        A[Incoming Request] --> B{Inline API Call}
        B -->|Wait| C[API Server / External Service]
        C -->|Latency| D[Evaluation Result]
    end

    subgraph "GlobalContextEntry"
        E[Background Worker] -->|Fetch| F[Global Memory Cache]
        G[Incoming Request] -->|Read| F
        F -->|near 0 ms Latency| H[Evaluation Result]
    end

Kyverno operates natively as a Kubernetes Admission Controller webhook. When a resource lifecycle event occurs (e.g., kubectl apply), Kyverno intercepts the request and must determine whether to allow or deny it with minimal latency.

The Problem: Reactive Inline Lookup Bottlenecks

Section titled “The Problem: Reactive Inline Lookup Bottlenecks”

Traditionally, when a policy requires data outside the immediate admission review payload—such as comparing an incoming image tag against an allowed private enterprise registry list stored in a cluster ConfigMap—it relies on an inline apiCall context variable.

Under high cluster density, if a continuous integration or continuous deployment (CI/CD) engine (e.g., Argo CD or Flux) executes a massive batch deployment of 200 microservices simultaneously:

  • Kyverno is forced to instantiate 200 separate, concurrent network calls to the Kubernetes API server or target external webhooks to retrieve identical tracking data.
  • This scenario creates severe internal network latency spikes, induces heavy connection-throttling at the API server layer, and heavily balloons cluster CPU utilization, occasionally prompting admission timeouts.

GlobalContextEntry decouples the policy execution pathway from external network dependencies entirely.

Instead of executing real-time fetches during admission evaluation, Kyverno delegates resource tracking to an asynchronous background worker loop:

  1. Background Collection: Kyverno queries the designated cluster resource or external target once, building a localized memory structure.
  2. Deterministic Refreshing: For apiCall mode, Kyverno polls the target at user-configured refreshInterval intervals. For kubernetesResource mode, Kyverno uses Kubernetes watch/informer mechanisms to automatically track resource changes without polling.
  3. Instant Lookup Execution: When the same batch of 200 microservices triggers policy evaluations, Kyverno serves the evaluation data directly out of local RAM cache. Network overhead drops to near 0 ms, ensuring horizontal stability at massive organizational scales.

The fastest way to use GlobalContextEntry is a two-step process: define a cache entry, then reference it in a policy.

apiVersion: kyverno.io/v2
kind: GlobalContextEntry
metadata:
name: my-first-cache
spec:
kubernetesResource:
group: ''
version: v1
resource: configmaps
namespace: default
context:
- name: cached_configmaps
globalReference:
name: my-first-cache
jmesPath: '[].metadata.name'

{{ cached_configmaps }} is now available inside your policy rules with zero inline API call overhead. See Configuration Modes for the full schema reference.


A GlobalContextEntry must set exactly one of the following mutually exclusive source fields:

  • kubernetesResource: Monitors and maps native objects living within the cluster.
  • apiCall: Polls external endpoints or performs structured internal HTTP interactions.

Optionally, either mode can also include a projections block to pre-filter the cached data using JMESPath before policies consume it.

Use this mode to capture internal topology data, structural metadata, or shared configuration boundaries (e.g., ConfigMaps, Namespaces, or custom resource configurations).

Schema FieldValue Data TypeRequiredEngine Validation Constraints
groupstringConditionalThe Kubernetes API group (e.g., apps). Required for non-core resources. Use an empty string "" only when version is also v1 — this is the only valid core API combination. Any other pairing (e.g., group: "" with version: v1beta1) will fail schema validation.
versionstringYesThe explicit API version state (e.g., v1, v1beta1).
resourcestringYesMust be lowercased and pluralized (e.g., use configmaps or secrets, not ConfigMap).
namespacestringNoThe target namespace boundaries. If omitted, Kyverno tracks across all namespaces globally.

Important: Ensure the Kyverno ServiceAccount has the necessary RBAC permissions (get, list, watch) for the resources you are caching, especially when tracking custom resources (CRs).

apiVersion: kyverno.io/v2
kind: GlobalContextEntry
metadata:
name: configmap-cache
spec:
kubernetesResource:
group: ''
version: v1
resource: configmaps
namespace: default

Use this mode to extract and synchronize authorization lists, identity definitions, or operational constraints managed outside the local Kubernetes ecosystem.

Schema FieldDefault ValueEngine Validation Constraints
urlPathPath for querying the local Kubernetes API server. Mutually exclusive with service.url.
service.urlFull URL for external endpoints. Mutually exclusive with urlPath.
refreshInterval10mBackground polling cadence. Accepts duration strings (e.g., 30s, 5m, 2h). Must be > 0s.
retryLimit3Max retry attempts before the sync loop errors. Minimum value is 1.
methodGETHTTP method. Must be explicitly set to POST if a data payload array is provided.

Method Enforcement: If your apiCall profile passes a custom request payload under the data array parameter, the underlying HTTP schema engine enforces that the method parameter must be explicitly configured as POST.

Syntax Example: External Metadata Ingestion

Section titled “Syntax Example: External Metadata Ingestion”
apiVersion: kyverno.io/v2
kind: GlobalContextEntry
metadata:
name: corporate-teams-cache
spec:
apiCall:
service:
url: 'https://api.internal.corporate/v1/teams'
refreshInterval: 5m
retryLimit: 5

Caching large-scale external API payloads or extensive multi-namespace collections inside memory can stress system overhead. Kyverno supports projections using JMESPath so policies can work with a narrower, more focused view of that cached data.

Note on API Sources:

  • Use urlPath when querying the local Kubernetes API server (e.g., fetching internal cluster resources).
  • Use service.url when targeting external endpoints (e.g., external inventory or security services).

These two fields are mutually exclusive — only one may be defined per apiCall entry.

Projections act as a high-performance filtering layer, transforming raw complex structures into exact key-value primitives or explicit string arrays for policy consumption. This helps simplify policy evaluation and reduce the amount of data rules need to traverse, but it does not necessarily remove the underlying cached payload.

Important: The name of each projection must be different from the GlobalContextEntry’s own metadata.name. Using the same name will fail schema validation with: "A projection entry requires a name different from the global context entry name".

Example — if your entry is named my-k8s-cached-data, your projection name cannot also be my-k8s-cached-data. Use a descriptive sub-name like config-names instead.

Projections work with both urlPath (Kubernetes API server) and service.url (external endpoints). Here are both forms:

For Kubernetes API server responses — use urlPath:

apiVersion: kyverno.io/v2
kind: GlobalContextEntry
metadata:
name: my-k8s-cached-data
spec:
apiCall:
urlPath: '/api/v1/namespaces/default/configmaps'
projections:
- name: config-names
jmesPath: 'items[].metadata.name'

For external endpoint responses — use service.url:

apiVersion: kyverno.io/v2
kind: GlobalContextEntry
metadata:
name: my-external-cached-data
spec:
apiCall:
service:
url: 'https://api.internal.corporate/v1/teams'
refreshInterval: 5m
projections:
- name: team-ids
jmesPath: 'teams[].id'

urlPath and service.url are mutually exclusive — only one may be defined per apiCall entry. Use urlPath for in-cluster Kubernetes API resources and service.url for all external HTTP endpoints.

Reference a GlobalContextEntry in your policy context using globalReference. Use the jmesPath field to filter the cached payload at reference time:

context:
- name: cached_configmaps
globalReference:
name: shared-config-cache # GlobalContextEntry name
jmesPath: '[].metadata.name' # filter applied at reference time

JMESPath shape reminder:

  • kubernetesResource mode → use [].metadata.name
  • apiCall.urlPath mode → use items[].metadata.name

If you defined projections in your GlobalContextEntry spec, reference a projection by appending .<projection-name> to globalReference.name:

context:
- name: myData
globalReference:
name: my-k8s-cached-data.config-names # <GlobalContextEntry>.<projections[].name>

Eventual Consistency: Caches are updated asynchronously. Policy evaluations may briefly use slightly stale data between refresh cycles. Design your policy logic to tolerate this window, particularly for rapidly changing resources.

Production Security — External API Calls:

  • Store credentials in Kubernetes Secrets, never hardcoded in the resource spec.
  • Ensure all external endpoints are protected with valid TLS certificates.

Inline apiCall vs. GlobalContextEntry — When to Use Which:

FeatureInline apiCallGlobalContextEntry
PerformanceHigh overhead (one call per request)High performance (served from RAM cache)
Use CaseReal-time, volatile dataStatic or slowly changing data
ScalabilityCan overwhelm API servers under loadSignificantly reduces API server load
Data FreshnessAlways currentEventually consistent

If a GlobalContextEntry cannot be loaded or refreshed (for example, the external endpoint is unreachable and retryLimit is exhausted), policies referencing it via globalReference may hit a context/variable evaluation error until a successful sync occurs.

Monitor status.conditions and status.lastRefreshTime on the resource, and check Kyverno controller logs for globalcontext errors in production clusters.

1. Referencing Shared ConfigMap Data Across Multiple Policies

Section titled “1. Referencing Shared ConfigMap Data Across Multiple Policies”

This scenario caches cluster-wide configuration mappings centrally so multiple running rules can cross-reference them without individual cluster query costs.

apiVersion: kyverno.io/v2
kind: GlobalContextEntry
metadata:
name: shared-config-cache
spec:
kubernetesResource:
group: ''
version: v1
resource: configmaps
namespace: default

JMESPath Structure Note: The shape of cached data differs by mode:

  • kubernetesResource mode returns a raw array. Use [].metadata.name
  • apiCall.urlPath mode returns a Kubernetes API envelope with items[]. Use items[].metadata.name

Using the wrong shape silently returns an empty result. Check your cached payload first with kubectl get globalcontextentries <name> -o yaml.

apiVersion: kyverno.io/v1 # ClusterPolicy stays on v1 — this is correct
kind: ClusterPolicy
metadata:
name: require-configmap-via-gctx
spec:
validationFailureAction: Enforce
background: false
rules:
- name: configmap-must-exist
match:
any:
- resources:
kinds:
- Deployment
context:
- name: cached_configmaps
globalReference:
name: shared-config-cache
jmesPath: '[].metadata.name'
validate:
message: "Deployment initialization rejected. Required 'app-config' asset missing from GlobalContext cache."
deny:
conditions:
any:
- key: 'app-config'
operator: NotIn
value: '{{ cached_configmaps }}'

2. Caching Approved Container Registries (External API)

Section titled “2. Caching Approved Container Registries (External API)”

This implementation ensures cluster deployments pull strictly from vetted, enterprise-controlled image domains stored on an external inventory catalog manager.

apiVersion: kyverno.io/v2
kind: GlobalContextEntry
metadata:
name: approved-registries-cache
spec:
apiCall:
service:
url: 'https://api.corporate.internal/v1/registries'
refreshInterval: 30m
retryLimit: 3

Assuming the API returns: {"allowed": ["internal-registry.io", "gcr.io/vetted-project"]}

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-registries-global
spec:
validationFailureAction: Enforce
background: false
rules:
- name: check-image-registry
match:
any:
- resources:
kinds:
- Pod
context:
- name: allowed_registries
globalReference:
name: approved-registries-cache
jmesPath: 'allowed'
validate:
message: 'The container image registry is not approved by enterprise security policy.'
foreach:
- list: 'request.object.spec.containers'
deny:
conditions:
any:
- key: "{{ split(element.image, '/')[0] }}"
operator: NotIn
value: '{{ allowed_registries }}'

How the registry is extracted: split(element.image, '/')[0] splits the full image string (e.g., internal-registry.io/myapp:v1) on / and takes the first segment as the registry hostname. This avoids nested template expressions which Kyverno cannot evaluate inside a foreach loop.

Ensure your allowed_registries list contains registry hostnames only (e.g., "internal-registry.io", "gcr.io"), not full image paths.

3. Caching RBAC or Organizational Metadata

Section titled “3. Caching RBAC or Organizational Metadata”

Useful for performance-heavy validation structures, like tracking dynamic team metadata roles across specific namespace boundaries.

apiVersion: kyverno.io/v2
kind: GlobalContextEntry
metadata:
name: rbac-team-cache
spec:
kubernetesResource:
group: 'rbac.authorization.k8s.io'
version: v1
resource: clusterrolebindings
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: validate-team-namespace-access
spec:
validationFailureAction: Enforce
background: false
rules:
- name: restrict-namespace-creation
match:
any:
- resources:
kinds:
- Namespace
context:
- name: cluster_bindings
globalReference:
name: rbac-team-cache
jmesPath: "[].subjects[?kind=='User'][].name"
validate:
message: 'User initiating namespace creation is not registered in cluster role metadata.'
deny:
conditions:
any:
- key: '{{ request.userInfo.username }}'
operator: NotIn
value: '{{ cluster_bindings }}'

Note: gctxentry is always registered as a short name for GlobalContextEntry (defined in the Kyverno CRD via shortName=gctxentry). You can confirm all available Kyverno resource short names on your cluster with: kubectl api-resources | grep kyverno

Terminal window
# Linux/macOS
kubectl api-resources | grep kyverno
# Windows
kubectl api-resources | findstr kyverno

Use the following steps to confirm your GlobalContextEntry is actively syncing and your policies can consume its data correctly.

Terminal window
kubectl get globalcontextentries <entry-name> -o yaml

Look for the status block in the output. A healthy entry looks like this:

status:
conditions:
- type: Ready
status: 'True'
reason: Succeeded
message: 'GlobalContextEntry synced successfully'

Key fields to inspect:

FieldWhat to Check
status.conditions[].type: ReadyMust be "True". Any other value means the cache is not serving data.
status.conditions[].reasonSucceeded = healthy. Failed = check the message field for the error.
status.lastRefreshTimeConfirms the last successful sync. If this is stale, the background worker may be stuck.
Terminal window
kubectl get events -n kyverno --field-selector \
involvedObject.kind=GlobalContextEntry,involvedObject.name=<entry-name>

This surfaces sync failures, retry-limit exhaustion, or RBAC denial events directly without needing to dig through logs.

First, discover the exact component label your installation uses:

Terminal window
kubectl get pods -n kyverno --show-labels

Look for the app.kubernetes.io/component value in the output. Then use it to stream logs:

Terminal window
kubectl logs -n kyverno \
-l app.kubernetes.io/component=<your-component-label> \
--since=10m \
| grep -i globalcontext

Note: Replace <your-component-label> with the value found above. The most common value is admission-controller for standard Kyverno installations, but this may differ in custom deployments.

4. Inspect the Source Payload Shape (for building JMESPath)

Section titled “4. Inspect the Source Payload Shape (for building JMESPath)”

GlobalContextEntry status does not expose the cached payload. To validate the data shape and build correct JMESPath expressions, fetch the source directly:

Terminal window
# kubernetesResource mode (example)
kubectl get <resource> -n <namespace> -o json | jq .
# apiCall.urlPath mode (example)
kubectl get --raw "<urlPath>" | jq .

To remove a GlobalContextEntry:

Terminal window
kubectl delete globalcontextentries <entry-name>

Note: Deleting a GlobalContextEntry that is actively referenced by running policies will cause those policies to return a variable evaluation error on the next admission request. Always update or remove dependent policies first.


When working with GlobalContextEntry, misconfigurations typically manifest as empty context variables inside evaluations or synchronization blockages.

Symptom / ErrorRoot CauseRemediation Steps
GlobalContextEntry creation fails with validation/schema errorsSingular configuration constraint violation.Ensure you configured kubernetesResource or apiCall. Defining both simultaneously violates resource schemas.
Resource tracking results in completely empty arraysIncorrect resource naming specification.The resource field must be lowercased and pluralized. Use configmaps instead of ConfigMap.
Policies return variable evaluation error on global contextsKyverno controllers lack appropriate RBAC clearance.Verify Kyverno’s ClusterRole has get, list, and watch permissions for that specific API group.
External API caching loops fail continuouslyIncorrect configuration of custom data variables.If supplying a payload under apiCall, explicitly set method: POST.