You can't review every K8s manifest manually. Policy-as-code automatically enforces rules: no latest tags, resource limits required, no root containers. This lesson teaches OPA Gatekeeper — the Kubernetes policy engine.

What Policy-as-Code Does

Without: Tribal knowledge "Hey, remember to set resource limits" "Don't use :latest in production" "Make sure you don't run as root" → Humans forget. Drift happens. With: Automated enforcement Pod without limits → REJECTED at admission :latest tag → REJECTED Root container → REJECTED → Impossible to violate. Enforced by the cluster itself.

Essential Production Policies

Install Gatekeeper

helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm install gatekeeper gatekeeper/gatekeeper \
  -n gatekeeper-system --create-namespace

Policy 1: No latest Tag

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sdisallowedtags
spec:
  crd:
    spec:
      names:
        kind: K8sDisallowedTags
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sdisallowedtags
        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          endswith(container.image, ":latest")
          msg := sprintf("Container '%v' uses :latest tag", [container.name])
        }
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sDisallowedTags
metadata:
  name: no-latest
spec:
  match:
    kinds: [{ apiGroups: [""], kinds: ["Pod"] }]
    namespaces: ["production"]

Policy 2: Resource Limits Required

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequiredresources
spec:
  crd:
    spec:
      names:
        kind: K8sRequiredResources
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequiredresources
        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not container.resources.limits
          msg := sprintf("Container '%v' must have resource limits", [container.name])
        }
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredResources
metadata:
  name: require-limits
spec:
  match:
    kinds: [{ apiGroups: [""], kinds: ["Pod"] }]
    namespaces: ["staging", "production"]

Policy 3: No Root Containers

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8snoroot
spec:
  crd:
    spec:
      names:
        kind: K8sNoRoot
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8snoroot
        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not input.review.object.spec.securityContext.runAsNonRoot
          msg := "Pods must set securityContext.runAsNonRoot: true"
        }

What Happens When a Policy Blocks

$ kubectl apply -f deployment.yaml Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [no-latest] Container 'api' uses :latest tag
Gatekeeper intercepts the API request and rejects it BEFORE the resource is created. Works with ArgoCD too — sync will fail and show the violation.
Policies are GitOps-managed too. Store your ConstraintTemplates and Constraints in the GitOps repo. ArgoCD syncs them. The policies themselves are version-controlled, reviewed, and deployed via the same process as everything else.

🧠 Recall Check

  1. At what point does Gatekeeper enforce policies — build time or deploy time?
  2. What's a ConstraintTemplate vs a Constraint?
  3. If ArgoCD tries to sync a Deployment that violates a policy, what happens?
  4. Name three policies every production cluster should have.
Reveal answers
  1. Deploy time (admission). Gatekeeper is a Kubernetes admission webhook — it intercepts API requests BEFORE resources are created/updated.
  2. ConstraintTemplate = the rule logic (Rego code). Constraint = where/when to apply it (which namespaces, which resource kinds).
  3. The sync fails. ArgoCD shows the app as "Degraded" with the admission error. The violating resource is NOT created.
  4. No :latest tags, resource limits required, no root containers. Also valuable: no LoadBalancer services (use Ingress), require labels, max replica limits.

Next lesson: Pipeline Optimization & Cost — making everything fast and cheap.