NetworkPolicies

How NetworkPolicies control which Pods may talk, how peers and ports combine, why egress breaks DNS, and how to test a policy.

45 min 2 hands-on tasksNetworking · 20% of the exam

Key ideas in this lesson · tap one for a simple explanation

What you will be able to do

  • Write default-deny policies for incoming and outgoing traffic, then open exactly the connections a question asks for.
  • Tell an AND peer from an OR peer at a glance, and select Pods in other namespaces by namespace label.
  • Test a policy from an allowed Pod and a denied Pod, with timeouts, in under a minute.

By default, every can connect to every other Pod. A is a firewall rule for Pods: it says who may connect to them, or where they may connect to. For example: only the frontend Pods may reach the backend Pods, and only on port 8080. The exam topic is Define and enforce Network Policies.

It is a common task, and easy to get almost right. The API accepts any well-formed policy. Whether it does what the question meant depends on one dash in the , on remembering DNS, and on testing it. Also, a policy only works if the plugin enforces it. in this lab does; Flannel on its own ignores policies without any warning.

The rules of isolation

Five rules explain how every NetworkPolicy behaves:

  • A policy picks Pods in its own . Its podSelector chooses them, and podSelector: {} means every Pod in the namespace.
  • No policy means open. A Pod that no policy selects allows all traffic. Once a policy selects it for Ingress (or Egress), the Pod is isolated in that direction. Then only traffic that some policy allows gets through.
  • Policies only add. There are no deny rules. A Pod allows everything that any of its policies allows, added together.
  • Set policyTypes yourself. It says which directions the policy controls. If you leave it out, Ingress is always included, but Egress only when the policy has an egress section.
  • Replies are always allowed. A policy only decides who may open a connection. For Pod A to reach Pod B, A's egress and B's ingress must both allow it.
Both ends must say yes

Pod A

opens the connection

A's egress rules

may A connect to B?

B's ingress rules

may B accept A?

Pod B

receives it

Each end checks only if a policy selects it for that direction. A Pod with no policy says yes to everything.

Most tasks start from a default deny: a policy that selects every Pod and allows nothing. Here are the common starting points:

The three baseline policies
# Deny all incoming traffic to every Pod in the namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: nw-netpol
spec:
  podSelector: {}
  policyTypes: ["Ingress"]
---
# Deny all traffic in both directions (remember DNS, see below)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: nw-netpol
spec:
  podSelector: {}
  policyTypes: ["Ingress", "Egress"]
---
# Allow all incoming traffic (overrides an isolation for the Pods it selects)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all-ingress
  namespace: nw-netpol
spec:
  podSelector: {}
  policyTypes: ["Ingress"]
  ingress:
    - {}

Peers: who is allowed

A peer is an allowed source (under from) or destination (under to). You can pick peers in four ways:

Peer in from / toSelects
podSelector aloneMatching Pods in the policy's own namespace
namespaceSelector aloneEvery Pod in the namespaces whose match
namespaceSelector and podSelector in one entryMatching Pods in matching namespaces: both must match (AND)
ipBlock (cidr, except)A range of IPs, for traffic to or from outside the cluster. Do not use it for Pods, because their IPs change.

The classic trap is the third row. The two rules below differ by one dash, and they mean different things:

One dash changes the meaning
ingress:
  - from:
      - namespaceSelector:          # ONE peer: Pods labeled role=monitor
          matchLabels:              # AND in namespace "observability"
            kubernetes.io/metadata.name: observability
        podSelector:
          matchLabels:
            role: monitor
---
ingress:
  - from:
      - namespaceSelector:          # TWO peers: every Pod in "observability"
          matchLabels:
            kubernetes.io/metadata.name: observability
      - podSelector:                # OR Pods labeled role=monitor in this namespace
          matchLabels:
            role: monitor

Every namespace gets the label kubernetes.io/metadata.name=<name> automatically, and it cannot be changed. Use it to pick a namespace by name, without labeling anything. If the question names its own label, such as team=payments, check it with kubectl get ns --show-labels. Add it with kubectl label ns only if the question says to.

Ports

ports sits next to from (or to) in the same rule. Four things to know:

  • It matches the Pod's port. has already turned the address into a Pod address. So a Service on port 80 with targetPort: 8080 needs a policy that allows 8080.
  • protocol is TCP when you leave it out.
  • port can also be the name of a port, such as http.
  • A rule with from but no ports allows every port. A rule with ports but no from allows every source on those ports.
How an allow rule is built

Protect these Pods

the top-level podSelector

app: backend

Allow from

a podSelector or namespaceSelector

app: frontend

On these ports

the Pod's own port

8080/​TCP

Start from a default deny, then open one door at a time. This is the policy below.

frontend may reach backend on 8080, nothing else may
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend
  namespace: nw-netpol
spec:
  podSelector:
    matchLabels:
      app: backend               # the policy protects the backend Pods
  policyTypes: ["Ingress"]
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend
      ports:
        - protocol: TCP
          port: 8080

Egress and the DNS exception

Once a Pod is isolated for egress, it cannot reach either. Every connection by name then fails with bad address before it even starts. So almost every egress policy needs a DNS rule: port 53, over UDP and TCP. Allow it to the CoreDNS Pods, as below. Allowing port 53 to any destination is simpler, and also accepted unless the question says otherwise.

Egress: DNS plus one destination
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: client-egress
  namespace: nw-egress
spec:
  podSelector:
    matchLabels:
      role: client
  policyTypes: ["Egress"]
  egress:
    - to:                                   # DNS, to the CoreDNS Pods only
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
    - to:                                   # the api Pods, on their container port
        - podSelector:
            matchLabels:
              app: api
      ports:
        - protocol: TCP
          port: 8080

Prove it works

Never trust a policy you have not tested. Test from one Pod that should get in, and from one that should not. Use wget -T 3, so a blocked connection fails after 3 seconds instead of hanging.

Prove it: one allowed client, one denied client
kubectl -n nw-netpol get netpol
kubectl -n nw-netpol describe netpol allow-frontend      # reads the selectors back in plain English

# -T 3 makes a blocked connection fail in 3 seconds instead of hanging
kubectl -n nw-netpol exec frontend -- wget -qO- -T 3 http://backend:8080/hostname   # expect a name
kubectl -n nw-netpol exec intruder -- wget -qO- -T 3 http://backend:8080/hostname   # expect "download timed out"

# Blocked looks like a timeout; "connection refused" means the policy allowed it and nothing listens
kubectl -n nw-egress exec client -- nslookup api.nw-egress.svc.cluster.local         # DNS still works?
What you seeWhat it means
A replyThe connection is allowed
download timed outA policy blocks it
connection refusedThe traffic got through, but nothing listens on that port, or the Service has no Pods
bad addressThe name did not resolve. After an egress policy, the DNS rule is missing or wrong.

In the official docs· open on exam day too

Hands-on task 1 of 2nw-np-1
~7 min6% weightNetworking

Default deny, then allow one client

In namespace nw-netpol, create a NetworkPolicy named default-deny-ingress that blocks all incoming traffic to every Pod in the namespace. Then create a NetworkPolicy named allow-frontend that lets Pods labeled app=frontend connect to Pods labeled app=backend on TCP port 8080, and nothing else. Afterwards frontend must reach http://backend:8080/hostname and intruder must not.

Cluster:kubectl config use-context kind-cka

  1. 1

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-np-1/setup | bash
  2. 2

    Solve it in your terminal

    Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under /tmp/cka/.

  3. 3

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-np-1/verify | bash
    Got FAIL? Read which check failed, fix it, run it again.
  4. 4

    Clean up (optional)

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-np-1/cleanup | bash
Hands-on task 2 of 2nw-np-2
~7 min6% weightNetworking

Lock down egress except DNS and one Service

In namespace nw-egress, create a NetworkPolicy named client-egress for Pods labeled role=client. Those Pods may make DNS lookups (UDP and TCP port 53) and may connect to the Pods behind Service api. All other outgoing traffic from them must be blocked. Afterwards Pod client must get an answer from http://api/hostname and must not reach http://other/hostname.

Cluster:kubectl config use-context kind-cka

  1. 1

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-np-2/setup | bash
  2. 2

    Solve it in your terminal

    Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under /tmp/cka/.

  3. 3

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-np-2/verify | bash
    Got FAIL? Read which check failed, fix it, run it again.
  4. 4

    Clean up (optional)

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-np-2/cleanup | bash

Finished this lesson?

0 of 2 tasks passed. Your readiness counts tasks, so run them before you move on.