Network Policy

A network policy is a Kubernetes object that says which connections to and from a group of pods are allowed, at the IP address and port level — “OSI layer 3 or 4,” in the documentation’s terms, “within your cluster, and also between Pods and the outside world.” Pods are selected by label, and the peers a policy can name are other pods, namespaces and IP ranges. Quotations here were read in September 2026 against the v1.37 documentation, which is worth noting because one part of this page is explicitly version-scoped.

It exists because the default is wide open: “by default, all pods in a Kubernetes cluster are allowed to communicate with each other, and all network traffic is unencrypted.” Nothing about creating a namespace changes that.

The prerequisite that decides whether any of it is real

Before the semantics, the sentence that matters most in practice: “network policies are implemented by the network plugin,” and “creating a NetworkPolicy resource without a controller that implements it will have no effect.” The multi-tenancy guidance states the same thing as a warning — without a CNI plugin that supports them, “NetworkPolicy resources will be ignored.”

Consider what that leaves you with. The API server accepts the object. It shows up in listings and in your manifests repository. It passes review. And nothing in the API distinguishes an enforced policy from a decorative one. Nor can you check after the fact, because “the ability to log network security events (for example connections that are blocked or accepted)” is on the documented list of things this API cannot do.

So the only way to find out is to try it, on the cluster itself rather than on a lab copy, and again whenever the networking layer is replaced or upgraded. A single refused connection proves less than it appears to, though: a connection also fails when nothing is listening, when DNS does not resolve, when routing is broken, or when some other firewall intervened. A failed attempt is an observation about that attempt, not a guarantee about the policy.

A test worth trusting has four parts: a control connection that should succeed and does, so you know the path works at all; a target chosen so that something is genuinely listening on the other end; the attempt matched to the policy you are testing in direction, protocol, port, selector and namespace — and, where it matters, from a Pod on a different node, since traffic from a Pod’s own node is always allowed; and a look at the plugin’s own state or logs, which is a different thing from the API having no standard event logging. Absent that, you have a belief.

Isolation is per-direction and conditional

The model is narrower than the word suggests. The documentation qualifies it immediately: “‘isolation’ here is not absolute, rather it means ‘some restrictions apply’.”

A pod starts non-isolated in both directions — “by default, a pod is non-isolated for egress; all outbound connections are allowed” — and becomes isolated for a direction only when a policy names it — “a pod is isolated for egress if there is any NetworkPolicy that both selects the pod and has ‘Egress’ in its policyTypes,” and the same sentence holds for ingress with ‘Ingress’. Once isolated in a direction, only what some applicable policy allows in that direction gets through. Ingress and egress are declared independently, and both matter for a single connection.

Three consequences follow, and together they are most of what you need to debug a blocked or unexpectedly open connection.

  • Policies add up and never conflict. “Network policies do not conflict; they are additive.” Where several apply, “the connections allowed in that direction from that pod is the union of what the applicable policies allow. Thus, order of evaluation does not affect the policy result.” There is no precedence to learn — and no way to carve an exception out of something already allowed. To take access away you must find the policy that granted it.
  • Both ends must agree. “For a connection from a source pod to a destination pod to be allowed, both the egress policy on the source pod and the ingress policy on the destination pod need to allow the connection. If either side does not allow the connection, it will not happen.” When two teams own the two namespaces, debugging requires both.
  • Leaving out policyTypes is a decision. “If no policyTypes are specified on a NetworkPolicy then by default Ingress will always be set and Egress will be set if the NetworkPolicy has any egress rules.” A policy written to lock down inbound traffic leaves outbound completely unrestricted, and it looks restrictive in review.

The last one is the common self-inflicted gap. Someone adds an ingress policy, the cluster looks segmented, and every pod can still reach anything it likes on the internet.

Default-deny, and the DNS trap

Because there are no deny rules, a default-deny posture is built by selecting all pods in a namespace and allowing nothing, then adding allowances back. The multi-tenancy guidance recommends exactly that: start “with a default policy that denies communication between pods… with another rule that allows all pods to query the DNS server for name resolution.”

That second clause is not optional, and the NetworkPolicy page says so in plain terms: “a default deny-all egress policy also blocks DNS traffic. If your workloads need DNS resolution, you must add a separate NetworkPolicy that allows egress to your cluster’s DNS.” Skipping it produces an outage that looks nothing like a network problem — name resolution fails, so every client reports a connection error to a host it cannot even resolve.

One related footgun from the same guidance: do not use an empty label selector for namespaceSelector, which quietly means every namespace rather than none.

Even a complete deny-all has documented holes, and they are the right kind of boring to memorize.

  • “A pod cannot block access to itself.”
  • “Traffic to and from the node where a Pod is running is always allowed, regardless of the IP address of the Pod or the node.”
  • A deny-all is “only guaranteed to deny TCP, UDP and SCTP connections. For other protocols, such as ARP or ICMP, the behaviour is undefined.”
  • Behaviour for pods on the host network is “undefined,” and the most common implementation “ignores hostNetwork pods when matching podSelector and namespaceSelector.”

IP-range rules carry their own uncertainty. They “should be cluster-external IPs, since Pod IPs are ephemeral and unpredictable,” and where the cluster rewrites source or destination addresses, “it is not defined whether this happens before or after NetworkPolicy processing, and the behavior may be different for different combinations of network plugin, cloud provider, Service implementation.” An egress rule expressed as a CIDR may or may not apply to traffic sent through a Service address, depending on your stack. Test it rather than reasoning about it.

Two timing behaviours to design around

Policies are not applied instantaneously, and the documentation is unusually direct about what that requires of your application.

At startup, a pod “may be started unprotected” if it is created before the plugin has finished handling a relevant policy. Once handling is complete, new pods “will be isolated before they are started,” but allow rules land afterwards, so “in the worst case, a newly created pod may have no network connectivity at all when it is first started.” Partial views are normal too: a pod may reach a peer on one node immediately and another “a few seconds later.” Since “there is no way to tell from the Kubernetes API when exactly that happens,” the documentation states a requirement rather than a caveat: “pods must be resilient against being started up with different network connectivity than expected.” An init container that waits for a destination to become reachable is the documented remedy; a process that exits on its first failed connection is the anti-pattern. Policies apply at Pod level, so they “apply equally to init containers, sidecar containers, and regular containers.”

At the other end, revocation is not specified. When the policies affecting an established connection change, “it is implementation defined as to whether the change will take effect for that existing connection or not,” and the advice is “not to modify policies/pods/namespaces in ways that might affect existing connections.” A network policy is admission control for new connections, not a kill switch.

Nor is deleting the Pod one, which is worth stating because it is the usual reflex. Deletion is a request: the kubelet sends the container “a TERM (aka. SIGTERM) signal, with a grace period timeout,” those stop requests are processed “asynchronously. There is no guarantee to the order of processing,” and “the KILL signal is sent to any remaining processes” only “once the grace period has expired.” Forcing it removes the API object rather than guaranteeing the process is gone: with a force deletion “the API server does not wait for confirmation from the kubelet that the Pod has been terminated on the node it was running on. It removes the Pod in the API immediately so a new Pod can be created with the same name,” and the documentation attaches a caution — “immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.” So the API call tells you that the record is gone, not what the process is doing.

Be precise about what termination does achieve, because the opposite over-claim is just as misleading. If the process really does exit, its open sockets go with it — the kernel closes the file descriptors a terminating process held, and when the last reference to a socket is gone that process is no longer using the connection. Termination is a real containment mechanism; the uncertainty is whether it happened. Two cases differ. Where the kubelet is reachable and the container exits, on SIGTERM or on the later KILL, the connections that process owned are finished with it. Where the node is partitioned, wedged or has lost contact with the control plane, nothing ran there — the API object disappeared and the container may still be serving traffic, which is exactly the situation the force-deletion caution describes. A Pod owned by a controller will also be replaced, so the workload returns under a new name.

One further distinction matters when reading a connection list during an incident. A closed socket on one end is not immediately known at the other: a peer holding a connection whose process has exited may not notice until it writes and the reset arrives, or until a keepalive or timeout expires. So “the process is gone” and “the remote side has stopped waiting” are different observations, made in different places, and a dashboard showing an established connection can be describing a peer’s stale view rather than live traffic.

So containment is three separate things, and an incident response that conflates them will report a connection cut that is still open: the delete request, the process actually terminating on the node, and the network path being blocked. Only the second and third actually stop traffic, and the second is verified on the node rather than in the API. Cutting off a compromised workload means stopping the controller from recreating it, confirming on the node that the process is gone, and — for anything still running, or for the case where you cannot confirm — using a control your environment actually enforces at the packet level — which you should have verified in advance rather than during the incident.

What it is not, and what sits next to it

As of the v1.37 documentation, the API cannot express an explicit deny, target Services by name, apply to nodes by their Kubernetes identity, define defaults across all namespaces, or log anything. Nor does it touch TLS or force traffic through a gateway. Those absences are why a real segmentation design usually combines layers.

MechanismControlsBlind to
RBACWho may read or change API objectsPackets between pods
Network policyWhich pod-to-pod and external connections may be established, at layer 3 and 4Identity beyond labels, request content, and its own enforcement status
Service mesh“Layer 7 policies based on workload identity,” commonly with mutual TLSNothing in this list — but it is “significantly more complex to manage”

A last point about what a policy proves. Because the rules are allow-only and additive across every object that happens to select a pod, the reachability of a workload is not stated anywhere — it is the union of everything that was allowed, computed across files different people wrote. Keeping those files reviewed and versioned as policy as code is what keeps the union inspectable, and network segmentation is only one plane of tenant isolation; the API plane is governed by authorization instead. See There Is No Deny Rule for how the two combine on a shared cluster.

References, read September 2026 against the Kubernetes v1.37 documentation: Kubernetes: Network Policies; Multi-tenancy. The list of unsupported capabilities is explicitly scoped to a release, so re-read it against your own version; Kubernetes Documentation, Pod Lifecycle.


Discover more from Insightful Data Lab

Subscribe to get the latest posts sent to your email.

Similar Posts

Questions, corrections, or additional insights?

This site uses Akismet to reduce spam. Learn how your comment data is processed.