OPA Gatekeeper: Policy as Code for Kubernetes#
Gatekeeper is a Kubernetes-native policy engine built on Open Policy Agent (OPA). It runs as a validating admission webhook and evaluates policies written in Rego against every matching API request. Instead of deploying raw Rego files to an OPA server, Gatekeeper uses Custom Resource Definitions: you define policies as ConstraintTemplates and instantiate them as Constraints. This makes policy management declarative, auditable, and version-controlled.
Architecture#
Gatekeeper deploys as a set of pods in the gatekeeper-system namespace. It registers a ValidatingWebhookConfiguration with the API server so that matching requests are forwarded to Gatekeeper for evaluation. Gatekeeper also runs a periodic audit process that checks existing resources against all active constraints, catching resources that were created before a policy was deployed.
The two core CRDs are:
- ConstraintTemplate – defines the Rego logic and the schema for parameters. Think of it as a policy class.
- Constraint – an instance of a ConstraintTemplate with specific parameters and scope. Think of it as a policy instance.
Installation#
Install Gatekeeper via Helm:
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
helm install gatekeeper gatekeeper/gatekeeper \
--namespace gatekeeper-system \
--create-namespace \
--set replicas=2 \
--set audit.resources.requests.memory=256Mi \
--set audit.resources.limits.memory=512MiGatekeeper requires roughly 500MB of RAM for the audit controller, more in large clusters. Set resource requests accordingly or the audit pod will get OOMKilled.
Verify the installation:
kubectl get pods -n gatekeeper-system
kubectl get crd | grep gatekeeperYou should see constrainttemplates.templates.gatekeeper.sh and related CRDs.
ConstraintTemplate: Defining Policy Logic#
A ConstraintTemplate defines the Rego code and declares what parameters the policy accepts:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("Missing required labels: %v", [missing])
}The spec.crd section defines a CRD schema. When you apply this template, Gatekeeper creates a new CRD called K8sRequiredLabels in the cluster. The spec.targets[].rego section contains the policy logic. The violation rule is the standard convention – any result from violation means the resource fails the check.
Inside the Rego code, input.review.object is the Kubernetes resource being evaluated, and input.parameters contains the values from the Constraint instance.
Constraint: Instantiating a Policy#
Once the ConstraintTemplate is applied, create a Constraint using the generated CRD:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-team-label
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment"]
- apiGroups: [""]
kinds: ["Service"]
namespaces: ["production", "staging"]
excludedNamespaces: ["kube-system", "gatekeeper-system"]
parameters:
labels:
- "team"
- "environment"Key fields:
- enforcementAction –
denyblocks non-compliant resources,dryrunaudits only (allows but records violations),warnallows but returns a warning to the user. - spec.match.kinds – which resource types to evaluate. Be specific to avoid unnecessary overhead.
- spec.match.namespaces / excludedNamespaces – scope the constraint. Always exclude
kube-systemandgatekeeper-system. - spec.parameters – values passed to the Rego code via
input.parameters.
Practical Example: Require Resource Limits#
Here is a complete example that requires all pods to have CPU and memory limits set, deployed first in dryrun mode:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sresourcelimits
spec:
crd:
spec:
names:
kind: K8sResourceLimits
validation:
openAPIV3Schema:
type: object
properties:
containerExemptions:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sresourcelimits
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not is_exempt(container.name)
not container.resources.limits.cpu
msg := sprintf("Container %q has no CPU limit", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not is_exempt(container.name)
not container.resources.limits.memory
msg := sprintf("Container %q has no memory limit", [container.name])
}
is_exempt(name) {
exemptions := input.parameters.containerExemptions
exemptions[_] == name
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sResourceLimits
metadata:
name: require-resource-limits
spec:
enforcementAction: dryrun
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces: ["kube-system", "gatekeeper-system"]
parameters:
containerExemptions:
- "istio-proxy"Start with enforcementAction: dryrun, then check for violations:
kubectl get k8sresourcelimits require-resource-limits -o yamlLook at the status.violations section. It lists every existing resource that violates the constraint:
kubectl get k8sresourcelimits require-resource-limits \
-o jsonpath='{range .status.violations[*]}{.namespace}/{.name}: {.message}{"\n"}{end}'Once you have fixed all violations, switch to enforcement:
kubectl patch k8sresourcelimits require-resource-limits \
--type=merge -p '{"spec":{"enforcementAction":"deny"}}'Common Policies from the Gatekeeper Library#
The gatekeeper-library repository provides pre-built ConstraintTemplates for common policies:
- K8sBlockNodePort – block Services of type NodePort
- K8sContainerLimits – require resource limits on containers
- K8sDisallowedTags – block the
latesttag on container images - K8sPSPPrivilegedContainer – block privileged containers
- K8sAllowedRepos – restrict which container registries are allowed
- K8sPSPRunAsNonRoot – require containers to run as non-root
- K8sRequiredProbes – require readiness/liveness probes
Use these as starting points rather than writing Rego from scratch.
Mutation Support#
Gatekeeper also supports mutating resources via dedicated CRDs:
- Assign – set a field value on matching resources
- AssignMetadata – add labels or annotations
- ModifySet – add or remove items from a list (e.g., add a toleration)
apiVersion: mutations.gatekeeper.sh/v1
kind: AssignMetadata
metadata:
name: add-environment-label
spec:
match:
scope: Namespaced
kinds:
- apiGroups: ["*"]
kinds: ["Pod"]
namespaces: ["production"]
location: "metadata.labels.environment"
parameters:
assign:
value: "production"Gatekeeper vs Kyverno#
Both tools solve the same problem. The key differences:
- Gatekeeper uses Rego for policy logic. Rego is powerful and expressive but has a learning curve. Gatekeeper has a mature audit system and a large library of pre-built policies.
- Kyverno defines policies entirely in YAML – no separate policy language. This is simpler to learn and fits naturally into GitOps workflows. However, complex logic is harder to express in YAML than in Rego.
For teams already using OPA elsewhere (API gateways, CI pipelines), Gatekeeper is the natural choice. For teams that want to get started quickly with simple policies, Kyverno has a lower barrier to entry.
Common Gotchas#
Exempt system namespaces. Always exclude kube-system and gatekeeper-system from constraints. If Gatekeeper blocks its own pods from starting, you have a deadlock that requires editing the webhook configuration directly.
No constraint ordering. All constraints are evaluated independently. You cannot say “run policy A before policy B.” If two constraints conflict, both violations are reported. Design constraints to be independent of each other.
Audit lag. The audit controller runs on an interval (default 60 seconds). After deploying a new constraint in dryrun mode, wait at least one audit cycle before checking status.violations.
Rego debugging. When Rego rules do not work as expected, use kubectl describe constrainttemplate <name> to check for compilation errors. The status section will show Rego syntax errors. Test Rego logic locally with opa eval before deploying to the cluster.