Right, so you’ve got your pods running, but letting them talk to anything on the internet is like giving a toddler your credit card and telling them to go wild on Amazon. It’s a bad idea, and you’re going to get a nasty surprise. We need to lock down what they can call out to. This is where egress rules in Network Policies come in, and they’re your first, best line of defense against data exfiltration, crypto-mining pods, or just plain old “oops, that pod called the wrong API 50,000 times a second.”

Let’s be clear: by default, if you don’t have any Network Policies, your pods can make egress connections to any IP address inside or outside the cluster. That’s insane. Our job is to replace that default-allow chaos with a default-deny posture, then poke very specific, very necessary holes.

The Absolute Prerequisite: Default-Deny All Egress

Before you even think about allowing specific egress, you must slam the door shut. This policy is non-negotiable. It creates a “default-deny” state for egress traffic, meaning every outbound connection is blocked unless explicitly whitelisted by another policy. Apply this to a namespace first.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all-egress
  namespace: your-app-namespace
spec:
  podSelector: {} # This selects ALL pods in the namespace
  policyTypes:
  - Egress
  egress: [] # No rules means no traffic is allowed. The door is shut.

Now your pods are effectively trapped. They can’t talk to the API server, can’t resolve DNS, can’t call out to the internet. It might feel cruel, but it’s the necessary foundation. Now we can start being reasonable and letting them do their jobs.

Allowing Egress to an External CIDR Block

The most common need: your pod needs to call a specific external service, like api.stripe.com or us-east-1.aws.com. You don’t control those IPs, but you can allow traffic to their published CIDR ranges. Let’s say our payment service pod needs to talk to Stripe’s API, which lives in the 18.159.0.0/18 range, on port 443.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-egress-to-stripe
  namespace: your-app-namespace
spec:
  podSelector:
    matchLabels:
      app: payment-service # This policy only applies to pods with this label
  policyTypes:
  - Egress
  egress:
  - to:
    - ipBlock:
        cidr: 18.159.0.0/18
    ports:
    - protocol: TCP
      port: 443

See what we did there? We didn’t just open a hole to the whole internet on port 443. We said, “Only the pods labeled app: payment-service can talk only to IPs in this one specific block, and only on port 443.” This is infinitely more secure.

The DNS Catastrophe (And How to Avoid It)

Here’s the pitfall that gets everyone. You apply the policy above, and… nothing. Your pod can’t connect to api.stripe.com. Why? Because it can’t resolve the DNS name to an IP! We blocked all egress, which includes traffic to your cluster’s DNS resolver (usually CoreDNS running in kube-system).

You must explicitly allow egress to the kube-dns service. This is non-optional for any policy that allows external communication by hostname.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-egress-to-stripe
  namespace: your-app-namespace
spec:
  podSelector:
    matchLabels:
      app: payment-service
  policyTypes:
  - Egress
  egress:
  - to: # First rule: Allow DNS to the kube-dns service
    - namespaceSelector:
          matchLabels:
            kubernetes.io/metadata.name: kube-system
      podSelector:
        matchLabels:
          k8s-app: kube-dns
    ports:
    - protocol: UDP
      port: 53 # CoreDNS uses port 53 UDP (and sometimes TCP for large responses)
  - to: # Second rule: Allow traffic to the external CIDR
    - ipBlock:
        cidr: 18.159.0.0/18
    ports:
    - protocol: TCP
      port: 443

This is the correct, robust pattern. Rule number one: always allow DNS. The namespaceSelector and podSelector are combined with a logical AND, meaning it targets pods in kube-system with the label k8s-app: kube-dns.

Best Practices and Rough Edges

  1. IP Stability is a Myth: The biggest headache? Cloud provider IP ranges change. AWS, Google Cloud, Stripe—they all publish their IP ranges via APIs. You must automate the updating of your Network Policies. A static policy will break eventually. This is the roughest edge, and it sucks. Look into tools like the AWS VPC CNI plugin or other controllers that can sync these for you.

  2. Ports Are Not Suggestions: The port in an egress rule is the destination port. Your pod’s outbound ephemeral port (like 45782) is irrelevant. You’re specifying the port on the remote server you want to connect to.

  3. Protocol Matters: Notice we specified protocol: TCP for HTTPS and protocol: UDP for DNS. You can’t mix them. If you need both, you need two entries in the ports list.

  4. You Can’t Deny Hard-Coded IPs (Well…): A common ask is to block a specific IP within a allowed CIDR, say a known-bad IP in AWS’s range. You can do this with the except clause in the ipBlock, but it’s clunky and, again, subject to the stability problem. It’s better to handle this with a dedicated firewall layer upstream of the cluster.

The goal isn’t to make life difficult; it’s to make your cluster secure by default. It feels like a pain at first, but once you’ve defined these policies, you’ve created a explicit, auditable record of exactly what your application is allowed to do. And that is priceless.