A NetworkPolicy exists in the API, but your connection still succeeds. Or you allow ingress and the connection still fails. Both are understandable once you separate selection, direction, and enforcement.

We’ll build one allowed path between two test Pods and change one permission at a time.

Start with two decisions

Egress is traffic leaving a Pod. Ingress is traffic entering a Pod. If both sides are isolated in the relevant direction, both must allow a new connection.

Policies are additive: their allowed traffic combines. A default-deny policy does not override a separate broad allow. Creating the API object also does not prove that the cluster’s network implementation enforces it. These rules are described in the NetworkPolicy concepts.

Read the diagram as two checks on a connection, not one global firewall switch.

A policy changes the reachable setFor a pod-to-pod connection, applicable source egress and destination ingress policies must allow the traffic. Enforcement requires a supporting network implementation. A policy changes the reachable set Source podAttempts a connectionPolicy checksEgress and ingressAllowed peerBoth sides permit itBlocked peerRequired permission absent
For a pod-to-pod connection, applicable source egress and destination ingress policies must allow the traffic. Enforcement requires a supporting network implementation.
View full-size diagram (opens in a new tab)

Establish a working baseline

Confirm your current context points to the disposable cluster. Create a new namespace and two fixtures:

kubectl create namespace policy-lesson
kubectl run api -n policy-lesson --image=busybox:1.37.0 \
  --labels=app=api --restart=Never --command -- sh -c \
  'mkdir -p /tmp/www && printf "policy-ok\n" > /tmp/www/index.html && httpd -f -p 8080 -h /tmp/www'
kubectl run client -n policy-lesson --image=busybox:1.37.0 \
  --labels=role=frontend --restart=Never --command -- sleep 3600
kubectl wait -n policy-lesson --for=condition=Ready pod/api pod/client --timeout=120s
API_IP=$(kubectl get pod api -n policy-lesson -o jsonpath='{.status.podIP}')
kubectl exec -n policy-lesson client -- wget -T 3 -qO- "http://${API_IP}:8080/"

Expect policy-ok. Resolve any image, admission, or listener failure before continuing. Using the Pod IP keeps DNS out of this first experiment. The official policy exercise provides another small verification pattern.

Remove permission, then restore half of it

Save and apply this as deny.yaml using kubectl apply -f deny.yaml:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: isolate
  namespace: policy-lesson
spec:
  podSelector: {}
  policyTypes: [Ingress, Egress]

Repeat the same request; it should no longer succeed. Policy propagation can take time. If new requests continue to work, investigate enforcement and other policies before assuming isolation exists.

Now save and apply ingress.yaml:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-from-frontend
  namespace: policy-lesson
spec:
  podSelector:
    matchLabels: {app: api}
  policyTypes: [Ingress]
  ingress:
    - from:
        - podSelector:
            matchLabels: {role: frontend}
      ports:
        - {protocol: TCP, port: 8080}

Pause and predict

Will the original client request succeed after adding only this ingress policy?

Need a hint?

The source Pod still has a separate egress decision.

Show the reasoning
No. The API now permits the incoming connection, but the client’s egress remains isolated without an allowance. Fixing one side doesn’t grant the other side permission.

Complete the path

Save and apply egress.yaml:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: frontend-to-api
  namespace: policy-lesson
spec:
  podSelector:
    matchLabels: {role: frontend}
  policyTypes: [Egress]
  egress:
    - to:
        - podSelector:
            matchLabels: {app: api}
      ports:
        - {protocol: TCP, port: 8080}

Repeat the request and expect policy-ok. These peer selectors refer to Pods in this policy’s namespace. Reply traffic for an allowed connection doesn’t require a separate reverse grant.

If you later test service names, add the DNS path deliberately. Don’t interpret a blocked DNS lookup as proof that the application policy is wrong.

Try a different identity

Change only the client label:

kubectl label pod client -n policy-lesson role=other --overwrite

Repeat the request. It should fail again once policy updates are enforced. Restore role=frontend and verify recovery. The backend remains running, so this is stronger evidence than testing a port where nothing listens.

Apply the idea

Someone adds an allow-all egress policy for the client. Does the default-deny object cancel that permission?

Need a hint?

Allowed traffic combines across policies.

Show the reasoning
No. The extra policy broadens the allowed set. Review all policies selecting the Pod, and still check destination ingress. The name “default deny” isn’t a rule-priority mechanism.

When finished, delete only the dedicated namespace with kubectl delete namespace policy-lesson. You should now be able to predict all four stages: baseline success, denial, ingress-only failure, and success after both allowances.