# Kubernetes NetworkPolicy Explained: Ingress, Egress, Default Deny, Selectors, and IP-Based Rules

> A practical guide to **standard Kubernetes NetworkPolicy** using only the Kubernetes NetworkPolicy API.
> 
> This article intentionally does **not** cover Cilium, Calico-specific policy features, or other extended policy engines. Those are separate topics.

* * *

## Why NetworkPolicy feels confusing

NetworkPolicy looks straightforward:

```yaml
kind: NetworkPolicy
spec:
  podSelector:
  policyTypes:
  ingress:
  egress:
```

But several concepts interact:

*   Is the Pod isolated for ingress?
    
*   Is it isolated for egress?
    
*   Does an empty rule mean allow everything or deny everything?
    
*   What happens when multiple policies select the same Pod?
    
*   Does `from` mean the source or destination?
    
*   Does `to` mean the source or destination?
    
*   Can I select a Pod?
    
*   Can I select a namespace?
    
*   Can I select an external IP?
    
*   What happens when I specify a port?
    
*   Can standard NetworkPolicy explicitly deny one IP/port?
    
*   What happens to return traffic?
    

The easiest way to understand all of this is to build the mental model first.

* * *

# 1\. The two directions: Ingress and Egress

Think of every Pod as having two doors:

```text
                         POD
                  ┌──────────────┐
                  │              │
       INGRESS ──>│              │<── EGRESS
                  │              │
                  └──────────────┘
```

### Ingress

Ingress is traffic **coming into** the Pod.

```text
frontend ───────────────> backend
                         INGRESS
```

From the backend's point of view, this is ingress traffic.

### Egress

Egress is traffic **leaving** the Pod.

```text
frontend ───────────────> backend
   EGRESS
```

From the frontend's point of view, this is egress traffic.

The same connection can therefore be:

```text
frontend                         backend
   │                               │
   │ ─────── TCP connection ─────> │
   │                               │
 EGRESS                           INGRESS
```

This distinction is fundamental.

* * *

# 2\. The default behavior: everything is open

If there is **no NetworkPolicy selecting a Pod**, the Pod is non-isolated.

In the normal Kubernetes NetworkPolicy model:

```text
Ingress = allowed
Egress  = allowed
```

So without applicable policies:

```text
Pod A ───────────────> Pod B       ✓

Pod B ───────────────> Pod A       ✓

Pod A ───────────────> Internet    ✓
```

NetworkPolicy is not a firewall that automatically blocks traffic just because the API object exists somewhere in the cluster.

The important question is:

> **Does a NetworkPolicy select this Pod for this direction?**

* * *

# 3\. A NetworkPolicy has a target: `podSelector`

Every NetworkPolicy has a `podSelector`.

Example:

```yaml
spec:
  podSelector:
    matchLabels:
      app: backend
```

This means:

> Apply this policy to Pods with the label `app=backend` in the policy's namespace.

For example:

```yaml
metadata:
  labels:
    app: backend
```

matches:

```text
Pod A: app=backend       ← selected
Pod B: app=frontend      ← not selected
Pod C: app=database      ← not selected
```

## Empty `podSelector`

This:

```yaml
podSelector: {}
```

means:

> Select **all Pods in the policy's namespace**.

That is why default-deny policies commonly use:

```yaml
podSelector: {}
```

* * *

# 4\. `policyTypes`: which direction are we controlling?

`policyTypes` tells Kubernetes whether the policy applies to:

*   `Ingress`
    
*   `Egress`
    
*   or both
    

Example:

```yaml
policyTypes:
- Ingress
```

means:

> This policy isolates the selected Pods for incoming traffic.

Example:

```yaml
policyTypes:
- Egress
```

means:

> This policy isolates the selected Pods for outgoing traffic.

Example:

```yaml
policyTypes:
- Ingress
- Egress
```

means:

> Isolate the selected Pods in both directions.

A useful mental model is:

```text
policyTypes
     │
     ├── Ingress → control incoming traffic
     │
     └── Egress  → control outgoing traffic
```

For clarity, it is a good practice to explicitly specify `policyTypes`, especially for an egress-only policy.

* * *

# 5\. Standard NetworkPolicy is an ALLOW-list

This is the single most important concept.

Standard Kubernetes NetworkPolicy does **not** provide an explicit `deny:` rule.

You cannot write:

```yaml
egress:
- deny:
  - ipBlock:
      cidr: 192.168.121.17/32
```

There is no standard `deny` field in the Kubernetes NetworkPolicy API.

Instead, NetworkPolicy answers:

> **What traffic should be allowed?**

Everything else is implicitly denied **once the Pod is isolated for that direction**.

Think:

```text
No policy selecting Pod
        ↓
     ALLOW ALL

Policy selects Pod for Egress
        ↓
     ALLOW only
     what matches rules
```

* * *

# 6\. An Egress rule automatically creates egress isolation

This is a very common point of confusion.

Suppose there is no default-deny policy.

You create:

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-database
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: backend

  policyTypes:
  - Egress

  egress:
  - to:
    - ipBlock:
        cidr: 192.168.121.17/32
    ports:
    - protocol: TCP
      port: 9999
```

You do **not** need another empty egress policy first.

The selected Pods become egress-isolated.

The result is:

```text
backend Pod
    │
    ├──> 192.168.121.17:9999/TCP     ✓ ALLOW
    │
    ├──> 192.168.121.17:80           ✗ DENY
    │
    ├──> 8.8.8.8:443                 ✗ DENY
    │
    └──> another Pod                 ✗ DENY
```

Why?

Because the Pod is now egress-isolated and the policy only allows traffic matching its egress rule.

* * *

# 7\. Compare that with having no Egress policy

### Scenario A: no egress policy

```text
Pod
 │
 ├──> 192.168.121.17:9999     ✓
 ├──> 192.168.121.17:80       ✓
 ├──> 8.8.8.8:443             ✓
 └──> anywhere else           ✓
```

Egress is open.

### Scenario B: an Egress policy allows one destination

```yaml
policyTypes:
- Egress

egress:
- to:
  - ipBlock:
      cidr: 192.168.121.17/32
  ports:
  - protocol: TCP
    port: 9999
```

Now:

```text
192.168.121.17:9999     ✓
Everything else         ✗
```

This distinction is critical:

> **The existence of an applicable Egress policy changes the selected Pod from non-isolated to egress-isolated.**

* * *

# 8\. Default-deny Ingress

A classic default-deny ingress policy is:

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: default
spec:
  podSelector: {}
  policyTypes:
  - Ingress
```

Notice there is no `ingress:` rule.

This means:

```text
All Pods in namespace
        │
        ↓
Ingress isolation enabled
        │
        ↓
No ingress rules
        │
        ↓
DENY ALL INGRESS
```

So:

```text
frontend ───────X──────> backend
random   ───────X──────> backend
external ───────X──────> backend
```

The empty rule set is not "allow everything."

It means:

> The selected Pods are isolated, and this policy allows no ingress traffic.

* * *

# 9\. Default-deny Egress

Same concept:

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
  namespace: default
spec:
  podSelector: {}
  policyTypes:
  - Egress
```

Result:

```text
All Pods
    │
    ↓
Egress isolation
    │
    ↓
No egress rules
    │
    ↓
DENY ALL EGRESS
```

So:

```text
Pod ─────X────> Internet
Pod ─────X────> database
Pod ─────X────> another Pod
```

All are blocked unless another applicable NetworkPolicy allows them.

### Important: DNS

A default-deny egress policy also blocks DNS traffic unless DNS is explicitly allowed by another applicable policy.

That is a common reason applications suddenly stop resolving service names after egress lockdown.

* * *

# 10\. Default-deny both directions

You can isolate both directions with one policy:

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: default
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
```

No ingress rules.

No egress rules.

Therefore:

```text
                  POD
                   │
          ┌────────┴────────┐
          │                 │
       INGRESS           EGRESS
          │                 │
        DENY              DENY
```

This is the strongest baseline:

```text
DEFAULT DENY
      +
EXPLICIT ALLOW
```

* * *

# 11\. `ingress.from`: Who can connect to me?

Suppose the backend has:

```yaml
podSelector:
  matchLabels:
    app: backend

policyTypes:
- Ingress

ingress:
- from:
  - podSelector:
      matchLabels:
        app: frontend
```

Read this as:

> Allow traffic **from frontend Pods** into backend Pods.

So:

```text
frontend ─────────> backend
          ✓
```

but:

```text
random-pod ───────> backend
          ✗
```

Remember:

```text
INGRESS
   │
   └── from = SOURCE
```

* * *

# 12\. `egress.to`: Where can I connect?

Now reverse the direction.

```yaml
podSelector:
  matchLabels:
    app: frontend

policyTypes:
- Egress

egress:
- to:
  - podSelector:
      matchLabels:
        app: backend
```

Read this as:

> Allow frontend Pods to connect **to backend Pods**.

So:

```text
frontend ─────────> backend
          ✓
```

Remember:

```text
EGRESS
   │
   └── to = DESTINATION
```

A simple memory trick:

```text
Ingress  → from = who is coming TO me?
Egress   → to   = where am I going?
```

* * *

# 13\. What can `from` and `to` target?

Standard Kubernetes NetworkPolicy provides three main peer selector mechanisms:

1.  `podSelector`
    
2.  `namespaceSelector`
    
3.  `ipBlock`
    

These can be used under:

```yaml
ingress:
- from:
```

and:

```yaml
egress:
- to:
```

Let's look at each.

* * *

# 14\. Target type #1: `podSelector`

Example:

```yaml
ingress:
- from:
  - podSelector:
      matchLabels:
        app: frontend
```

This selects Pods with:

```text
app=frontend
```

in the **same namespace as the NetworkPolicy** when `podSelector` is used by itself.

Example:

```text
Namespace: default

frontend Pod
  app=frontend

backend Pod
  app=backend
```

Policy attached to backend:

```text
frontend ─────────> backend
          ✓
```

A frontend Pod in another namespace is not selected by this standalone `podSelector`.

* * *

# 15\. Target type #2: `namespaceSelector`

You can select namespaces by their labels.

Example:

```yaml
ingress:
- from:
  - namespaceSelector:
      matchLabels:
        team: platform
```

Read this as:

> Allow traffic from Pods in namespaces labeled `team=platform`.

This can allow Pods from multiple namespaces.

Think:

```text
namespace-a   team=platform
namespace-b   team=platform
namespace-c   team=payments
```

Then:

```text
namespace-a Pods ──┐
                   ├──> backend ✓
namespace-b Pods ──┘

namespace-c Pods ─────> backend ✗
```

* * *

# 16\. Target type #3: `ipBlock`

`ipBlock` lets you select an IP range using CIDR.

Example:

```yaml
egress:
- to:
  - ipBlock:
      cidr: 192.168.121.17/32
```

`/32` represents exactly one IPv4 address:

```text
192.168.121.17
```

You can also specify a range:

```yaml
ipBlock:
  cidr: 192.168.121.0/24
```

which represents the CIDR range.

You can exclude a smaller CIDR from the range:

```yaml
ipBlock:
  cidr: 192.168.121.0/24
  except:
  - 192.168.121.17/32
```

Conceptually:

```text
192.168.121.0/24
        │
        ├── most addresses   ✓
        │
        └── .17              ✗ excluded
```

### Important limitation

`ipBlock.except` excludes an IP/CIDR range. It is not a port-specific deny mechanism.

So it can express:

```text
ALLOW 192.168.121.0/24
EXCEPT 192.168.121.17
```

but standard NetworkPolicy cannot express:

```text
ALLOW 192.168.121.17:80
DENY  192.168.121.17:9999
ALLOW 192.168.121.17:443
```

using an explicit deny rule.

* * *

# 17\. Can `ipBlock` target external IPs?

Yes. `ipBlock` is intended for IP/CIDR-based policy, including traffic between Pods and the outside world.

For example:

```yaml
egress:
- to:
  - ipBlock:
      cidr: 8.8.8.8/32
```

can express an allow rule for traffic to that IP.

But there is an important Kubernetes caveat:

> The exact source/destination IP seen by NetworkPolicy can depend on address rewriting performed by the network implementation, cloud provider, Services, and other networking components.

For example, traffic involving a Service or load balancer may undergo source/destination NAT, so the IP you expect may not be the IP the NetworkPolicy implementation evaluates.

This is one reason `ipBlock` behavior should be tested in the actual cluster/network implementation.

* * *

# 18\. `podSelector` + `namespaceSelector`: the AND case

You can combine them in the **same peer entry**:

```yaml
ingress:
- from:
  - namespaceSelector:
      matchLabels:
        team: platform
    podSelector:
      matchLabels:
        app: frontend
```

This means:

> Allow Pods labeled `app=frontend` **AND** located in namespaces labeled `team=platform`.

Conceptually:

```text
namespace must match
        AND
Pod must match
```

Example:

```text
namespace-a
team=platform
  └── frontend
       ✓

namespace-b
team=payments
  └── frontend
       ✗

namespace-c
team=platform
  └── database
       ✗
```

* * *

# 19\. A very common YAML mistake: AND vs OR

Compare these two.

## Case A: same peer entry

```yaml
from:
- namespaceSelector:
    matchLabels:
      team: platform
  podSelector:
    matchLabels:
      app: frontend
```

This means:

```text
namespace = platform
AND
pod = frontend
```

## Case B: two peer entries

```yaml
from:
- namespaceSelector:
    matchLabels:
      team: platform

- podSelector:
    matchLabels:
      app: frontend
```

This means:

```text
namespace = platform
OR
Pod = frontend in the policy's namespace
```

The indentation changes the meaning.

This is a very important NetworkPolicy YAML detail.

* * *

# 20\. Ports: another important concept

Suppose you have:

```yaml
egress:
- to:
  - ipBlock:
      cidr: 192.168.121.17/32
  ports:
  - protocol: TCP
    port: 9999
```

The destination and port both need to match.

Think:

```text
destination MATCH
        AND
port MATCH
        ↓
      ALLOW
```

Therefore:

```text
192.168.121.17:9999/TCP     ✓
192.168.121.17:80/TCP       ✗
8.8.8.8:9999/TCP             ✗
```

The rule is not saying "allow the IP OR the port."

It is saying:

> Allow traffic that matches the destination **and** the port.

* * *

# 21\. Multiple ports in one rule are OR

Example:

```yaml
ports:
- protocol: TCP
  port: 80
- protocol: TCP
  port: 443
```

means:

```text
TCP/80   ✓
TCP/443  ✓
```

The ports inside that list are logically ORed.

* * *

# 22\. Multiple destinations in one rule are OR

Example:

```yaml
to:
- ipBlock:
    cidr: 10.0.0.0/24
- ipBlock:
    cidr: 192.168.1.0/24
```

means:

```text
10.0.0.0/24      ✓
192.168.1.0/24   ✓
```

The destination peers are logically ORed.

* * *

# 23\. Multiple rules are also additive

Suppose:

```yaml
egress:
- to:
  - podSelector:
      matchLabels:
        app: database

- to:
  - podSelector:
      matchLabels:
        app: redis
```

Then:

```text
backend ─────> database     ✓
backend ─────> redis        ✓
backend ─────> frontend     ✗
```

Think:

```text
Rule 1 OR Rule 2 OR Rule 3 ...
```

not:

```text
Rule 1 AND Rule 2
```

* * *

# 24\. Multiple NetworkPolicies are additive

This is one of the most important concepts.

Suppose Policy A allows:

```text
frontend → backend:8080
```

and Policy B allows:

```text
monitoring → backend:9090
```

If both policies select the same backend Pods:

```text
frontend   ───> backend:8080     ✓
monitoring ───> backend:9090     ✓
```

The policies do not override each other.

Their allowed traffic is combined.

Think:

```text
Policy A
   │
   ├── allow X
   │
Policy B
   │
   ├── allow Y
   │
Policy C
   │
   ├── allow Z
   │
   ↓
X + Y + Z are allowed
```

This also means there is no standard policy ordering such as:

```text
Policy 1
   ↓
Policy 2
   ↓
Policy 3
```

The policies are combined additively.

* * *

# 25\. There is no "deny wins" in standard NetworkPolicy

Because standard NetworkPolicy does not have explicit deny rules, you cannot normally create:

```text
Policy A:
ALLOW 10.0.0.0/24

Policy B:
DENY 10.0.0.5
```

and expect the second policy to override the first.

There is no standard `deny` rule.

Instead, the design is:

```text
DEFAULT DENY
       +
EXPLICIT ALLOW
```

This is why NetworkPolicy is best understood as an allow-list.

* * *

# 26\. Pod-to-Pod traffic: both directions matter

Suppose:

```text
frontend Pod ───────────────> backend Pod
```

There are potentially two policy checks:

```text
frontend                       backend
   │                              │
   │ EGRESS                       │ INGRESS
   │                              │
   └──────── connection ─────────>│
```

For a Pod-to-Pod connection to work when both Pods are isolated:

```text
SOURCE EGRESS = ALLOW
        AND
DESTINATION INGRESS = ALLOW
        ↓
CONNECTION = ALLOW
```

If either side blocks it:

```text
SOURCE EGRESS = DENY
        OR
DESTINATION INGRESS = DENY
        ↓
CONNECTION = BLOCKED
```

* * *

# 27\. Scenario: both sides are default-deny

Suppose we start with:

```text
Frontend:
  Egress = DENY ALL

Backend:
  Ingress = DENY ALL
```

Then:

```text
frontend ─────────X────────> backend
```

Blocked.

* * *

# 28\. Scenario: only frontend Egress is allowed

Now we add:

```text
Frontend:
  Egress → ALLOW backend:8080

Backend:
  Ingress → still DENY ALL
```

Result:

```text
frontend ─────────X────────> backend
```

Still blocked.

Why?

Because:

```text
Frontend Egress = ✓
Backend Ingress = ✗
```

Both sides are not allowed.

* * *

# 29\. Scenario: only backend Ingress is allowed

Now instead:

```text
Frontend:
  Egress → still DENY ALL

Backend:
  Ingress → ALLOW frontend:8080
```

Result:

```text
frontend ─────────X────────> backend
```

Still blocked.

Why?

```text
Frontend Egress = ✗
Backend Ingress = ✓
```

Again, both sides are not allowed.

* * *

# 30\. Scenario: both sides are allowed

Now:

```text
Frontend:
  Egress → ALLOW backend:8080

Backend:
  Ingress → ALLOW frontend:8080
```

Result:

```text
frontend ──────────────────> backend
             ALLOWED ✓
```

Now:

```text
Frontend Egress = ✓
Backend Ingress = ✓
```

The connection works.

* * *

# 31\. But do you always need both policies?

No.

This is an important nuance.

Suppose:

```text
Frontend:
  Egress = NOT isolated

Backend:
  Ingress = ALLOW frontend:8080
```

Then the connection can work.

Why?

Because the frontend has no applicable Egress isolation, so its egress is allowed by default.

Likewise:

```text
Frontend:
  Egress = ALLOW backend:8080

Backend:
  Ingress = NOT isolated
```

can also work.

The actual rule is:

> If the source is egress-isolated, its egress must allow the connection.
> 
> If the destination is ingress-isolated, its ingress must allow the connection.

If neither side is isolated, traffic is allowed by default.

* * *

# 32\. Return traffic

Suppose:

```text
frontend ─────────> backend
       request
```

is allowed.

The backend's response traffic for that connection is permitted as reply traffic.

You do not normally need to create a separate NetworkPolicy rule just to permit the response to an already allowed connection.

Think:

```text
frontend                    backend

   ─────── request ────────>
   <────── response ────────

        ONE CONNECTION
```

NetworkPolicy is concerned with whether the connection is allowed; it is not normally necessary to model the return packet as an independent application connection.

* * *

# 33\. A complete example

Suppose we have:

```text
frontend Pod
app=frontend

backend Pod
app=backend

database Pod
app=database
```

We want:

```text
frontend → backend:8080
backend  → database:5432
```

and we want everything else denied.

A common design is:

```text
Default deny
      +
Allow frontend → backend
      +
Allow backend → database
```

### Default deny

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: default
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
```

### Allow frontend to backend

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-backend
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 8080
```

### Allow backend to database

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-backend-to-database
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: database
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: backend
    ports:
    - protocol: TCP
      port: 5432
```

If backend egress is still denied by the default-deny policy, you would also need an egress allow for backend → database.

For example:

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-backend-egress-to-database
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
  - Egress
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: database
    ports:
    - protocol: TCP
      port: 5432
```

Now the flow is:

```text
frontend
   │
   │ Egress must be allowed
   ↓
backend
   │
   │ Egress must be allowed
   ↓
database
```

And the database must allow the corresponding ingress.

* * *

# 34\. A practical way to read any NetworkPolicy

Whenever you see a NetworkPolicy, read it in this order:

## Step 1 — Who is selected?

Look at:

```yaml
podSelector:
```

Ask:

> Which Pods does this policy apply to?

* * *

## Step 2 — Which direction?

Look at:

```yaml
policyTypes:
```

Ask:

> Is this controlling ingress, egress, or both?

* * *

## Step 3 — Who/where is allowed?

For ingress:

```yaml
from:
```

Ask:

> Who can connect to these Pods?

For egress:

```yaml
to:
```

Ask:

> Where can these Pods connect?

* * *

## Step 4 — Which ports?

Look at:

```yaml
ports:
```

Ask:

> Which protocol and destination port are allowed?

* * *

## Step 5 — Remember the implicit deny

If the Pod is isolated for that direction:

```text
matches a rule → ALLOW
doesn't match   → DENY
```

* * *

# 35\. Your original policy, translated

Your original policy was:

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-metadata
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: app
  policyTypes:
  - Egress
  egress:
  - to:
    - ipBlock:
        cidr: 192.168.121.17/32
    ports:
    - protocol: TCP
      port: 9999
```

Read it in plain English:

> Select Pods with `app=app`.

Then:

> Isolate them for Egress.

Then:

> Allow TCP traffic to `192.168.121.17` on port `9999`.

Therefore:

```text
app=app Pod

192.168.121.17:9999/TCP     ✓ ALLOW
192.168.121.17:80           ✗ DENY
192.168.121.17:443          ✗ DENY
8.8.8.8:443                 ✗ DENY
another Pod                 ✗ DENY
```

So despite its name:

```yaml
name: deny-metadata
```

the policy is actually **allowing** the metadata destination.

The name does not change policy behavior.

* * *

# 36\. If I want to block one specific IP/port, what should I remember?

Suppose your requirement is:

```text
ALLOW everything
EXCEPT
192.168.121.17:9999
```

Standard Kubernetes NetworkPolicy does not provide an explicit deny rule for this.

You cannot simply write:

```yaml
deny:
- ipBlock:
    cidr: 192.168.121.17/32
  ports:
  - port: 9999
```

That field does not exist in the standard NetworkPolicy API.

Instead, standard NetworkPolicy works by defining the traffic that **is allowed**.

This is why a precise "deny one IP + port while allowing everything else" requirement can require careful allow-list design, or a policy engine with capabilities beyond the standard Kubernetes NetworkPolicy API.

We will cover those extended policy engines separately.

* * *

# 37\. What NetworkPolicy can target

For standard Kubernetes NetworkPolicy, keep these three peer mechanisms in your mental model:

| Selector | What it targets | Typical use |
| --- | --- | --- |
| `podSelector` | Pods | frontend → backend |
| `namespaceSelector` | Namespaces, and therefore Pods in them | allow traffic from another team/namespace |
| `ipBlock` | IP/CIDR ranges | external networks, specific IPs, CIDRs |

You can also combine `namespaceSelector` and `podSelector` in the same peer entry to select specific Pods in selected namespaces.

* * *

# 38\. What about Services?

NetworkPolicy fundamentally operates at the network traffic level, not at the Kubernetes Service object level.

For example, an application may connect to:

```text
my-backend.default.svc.cluster.local:8080
```

The traffic ultimately reaches a Pod endpoint.

How the Service IP, endpoint IP, NAT, routing, and NetworkPolicy implementation interact can depend on the cluster's networking implementation.

Therefore, don't assume that writing a policy around a Service's virtual IP is always equivalent to writing a policy around the actual Pod endpoints.

For exact behavior involving Services and IP rewriting, test with your actual network plugin.

* * *

# 39\. NetworkPolicy requires enforcement support

Creating a NetworkPolicy object does not magically enforce traffic filtering.

Your cluster must use a network plugin that supports NetworkPolicy enforcement.

For example, the Kubernetes API can accept:

```bash
kubectl apply -f network-policy.yaml
```

but actual traffic enforcement depends on the networking implementation.

This is why NetworkPolicy behavior can sometimes differ between clusters.

* * *

# 40\. The mental model to remember

If you remember only these rules, you can understand most standard NetworkPolicies.

### Rule 1 — No applicable policy means open

```text
No policy selecting Pod
        ↓
Ingress = allowed
Egress  = allowed
```

### Rule 2 — An applicable policy isolates that direction

```text
Ingress policy selects Pod
        ↓
Ingress becomes allow-list
```

```text
Egress policy selects Pod
        ↓
Egress becomes allow-list
```

### Rule 3 — Empty rules mean deny everything for that isolated direction

```text
Ingress isolation
+
no ingress rules
        ↓
DENY ALL INGRESS
```

```text
Egress isolation
+
no egress rules
        ↓
DENY ALL EGRESS
```

### Rule 4 — NetworkPolicy is allow-list based

```text
MATCHES an allowed rule → ALLOW
DOES NOT MATCH          → DENY
```

for an isolated direction.

### Rule 5 — Multiple policies are additive

```text
Policy A allows X
Policy B allows Y

Result:
X + Y are allowed
```

### Rule 6 — Pod-to-Pod traffic can involve both sides

```text
SOURCE EGRESS ✓
       +
DESTINATION INGRESS ✓
       =
CONNECTION ✓
```

when both directions are isolated.

* * *

# 41\. The simplest possible picture

Keep this picture in your head:

```text
                         Kubernetes Pod
                              │
                    ┌─────────┴─────────┐
                    │                   │
                 INGRESS              EGRESS
                    │                   │
                 "from"               "to"
                    │                   │
                    ↓                   ↓
             Who can reach me?   Where can I reach?
                    │                   │
                    └─────────┬─────────┘
                              │
                        ALLOW-LIST
                              │
                     Everything else
                         is denied
                    when isolated
```

And for peer selection:

```text
             from / to
                 │
       ┌─────────┼─────────┐
       │         │         │
       ↓         ↓         ↓
      Pod     Namespace    IP/CIDR
 selector    selector     ipBlock
```

That's the standard Kubernetes NetworkPolicy model.

* * *

# 42\. Final cheat sheet

```text
podSelector
    ↓
Which Pods does this policy apply to?

policyTypes
    ↓
Which direction becomes isolated?

Ingress
    ↓
Traffic coming INTO selected Pods

Egress
    ↓
Traffic going OUT OF selected Pods

from
    ↓
Ingress source

to
    ↓
Egress destination

podSelector
    ↓
Select Pods

namespaceSelector
    ↓
Select namespaces

ipBlock
    ↓
Select IP/CIDR ranges

ports
    ↓
Restrict protocol + destination port

No applicable policy
    ↓
Traffic allowed by default

Applicable policy + no matching rule
    ↓
Traffic denied for that isolated direction

Multiple policies
    ↓
Allowed traffic is combined additively

Explicit deny
    ↓
Not part of the standard Kubernetes NetworkPolicy API
```

* * *

## Official Kubernetes sources

This article intentionally uses the standard Kubernetes NetworkPolicy model and official Kubernetes documentation as the source of truth.

*   Kubernetes Network Policies: https://kubernetes.io/docs/concepts/services-networking/network-policies/
    
*   Kubernetes NetworkPolicy API reference: https://kubernetes.io/docs/reference/kubernetes-api/networking/network-policy-v1/
    

The official API reference defines `podSelector`, `policyTypes`, `ingress`, `egress`, `podSelector`, `namespaceSelector`, `ipBlock`, ports, and the additive behavior of applicable policies.

> **Note:** NetworkPolicy enforcement requires a network plugin that supports NetworkPolicy. Exact behavior around source/destination IP rewriting, Services, load balancers, and other networking features can depend on the cluster's networking implementation.
