Policy as code
Two places a policy can live, and they answer different questions. One stops packets, the other stops applies. Teams that pick one and call it done have decided, without saying so, which question they do not care about.
The mistake is treating network policy and admission policy as the same category of thing. They're both "policy," they're both YAML, and they both live in Git. But one runs in the datapath, continuously, per packet. The other runs at admission, once, per request. The failure modes are not the same.
The network layer, Cilium
Cilium network policies answer the traffic question. Can a pod in namespace A talk to a pod in namespace B? What leaves the namespace, what comes in, what is east west and what is north south? That's the datapath, enforced by eBPF programs attached at the packet hooks, every packet, all the time.
Its failure mode is per packet. A packet that violates the policy is dropped at the point of enforcement. There's no log line by default unless something is already watching, which is why "Cilium dropped it and told nobody" is a real incident pattern. The policy doesn't care about intents, it cares about packets.
The admission layer, Kyverno and OPA Gatekeeper
Admission policies answer the request question. Can this pod exist at all? Where is the image allowed to come from, what capabilities are forbidden, what labels are required. Both tools are admission controllers, they intercept API requests before the object touches etcd, and they can accept or reject.
They are not the same tool under the hood, and the choice matters.
Kyverno is Kubernetes native. Policies are ClusterPolicy resources, plain YAML, no new language. It validates, it mutates, and it generates. A team that already speaks YAML reads a Kyverno policy the same day.
OPA Gatekeeper is Rego. You write ConstraintTemplates in a real policy language, then instantiate Constraints from them. More expressive, more powerful, and a real learning curve. Teams with compliance muscle and audit requirements tend to pick it, because Rego can express things YAML structures can't.
Violation and audit are not the same
Both tools have a mode worth knowing before you deploy it. In violation mode the request is rejected, the object never exists, and the dev gets an error back. In audit mode the object is allowed and the violation is logged. Audit first, enforce later, is the safe adoption path, and it's also how you discover what your org actually does before you break it.
Where the bad apply actually gets stopped
The mechanism question everyone gets half right. A dev runs
kubectl apply with a Deployment that violates policy. The validating
webhook runs during the API request, before etcd persists anything. The apply fails
synchronously. The dev sees the error in the same command output. No stuck state, no
half created pod, the object never existed.
The "stuck" version only appears in one case. If the Deployment itself passes but the pod template violates, the Deployment exists and the ReplicaSet's pod creation gets denied. Then you see pods with FailedCreate events quoting the webhook message. That's the traceable event, and it's the difference between "the request was rejected" and "the rollout is failing."
Order matters too. Mutating webhooks run first, they can rewrite the object, that is Kyverno's mutate. Validating webhooks run second, they can reject. Validation mutating, in that order.
They don't overlap, they stack
Admission can't stop a packet. The datapath can't stop an apply. A malicious or misconfigured workload that got through admission can still do whatever the network policy allows, and a perfect network policy does nothing about a bad manifest that was never applied. You need both layers, and you need to know which one is answering which question when something goes wrong.
The boring conclusion
Network policy for traffic, admission policy for objects, both declared in Git, both reconciled by Flux. And one honest caveat. Flux only prunes what it previously applied, so a policy hand created out of thin air is drift it won't see. That's the gap admission time detection and audits exist to cover. Pick Kyverno or Gatekeeper by your team's language, not by vibes, and run audit before you enforce.
Which policy caught a workload that should never have existed?