Network policies
Default-deny NetworkPolicies, selecting peers by Pod labels, namespace labels and CIDRs, the AND vs OR trap in from and to, and keeping DNS working when you lock down egress.
Exam tasks: 3.2 (define and enforce Network Policies), with 5.5 when a policy blocks too much
The decision: which Pods does this policy select, which direction does it isolate, and exactly which peers and ports should still get through?
How enforcement works
- Pods start non-isolated: everything is allowed. A Pod becomes isolated for a direction only when a policy
selects it and lists that direction in
policyTypes. - Policies are allow-lists and additive. The result is the union of all rules that apply. There's no deny rule and no ordering, so one permissive policy can't be overridden by a stricter one.
- Policies are namespaced and select Pods in their own namespace only (
podSelector). - Replies to an allowed connection are allowed automatically. You don't write a return rule.
- The CNI plugin enforces them. On a plugin without support (plain Flannel), policies are accepted and do nothing.
Policy applied, nothing changed
If a correct-looking policy has no effect at all, check that the cluster's CNI plugin enforces NetworkPolicy and
that podSelector really matches the target Pods' labels (k get pods --show-labels). An empty
podSelector: {} selects every Pod in the namespace, which is often what a default deny needs.
The baseline: default deny
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all
namespace: vault-svc
spec:
podSelector: {} # every Pod in vault-svc
policyTypes:
- Ingress
- Egress # no ingress or egress rules listed, so nothing is allowed- Ingress only: list just
Ingress. Egress only: justEgress. - If you omit
policyTypes, it defaults toIngress, plusEgressonly if the policy hasegressrules. - "Allow all ingress" is
ingress: [{}]: one empty rule matches every source.
Selecting peers: AND vs OR
The single most tested detail. Each item in from (or to) is a separate peer, and items are ORed. Selectors
inside the same item are ANDed.
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: storefront
podSelector: # no dash: same item as namespaceSelector
matchLabels:
tier: apiOnly Pods labelled tier=api in namespace storefront.
| Peer | Matches |
|---|---|
podSelector alone | Pods in the policy's namespace with those labels |
namespaceSelector alone | All Pods in namespaces with those labels |
| Both in one item | Pods with those labels in namespaces with those labels |
ipBlock (with optional except) | CIDRs, for traffic leaving or entering the cluster. Pod IPs are ephemeral, so use selectors for Pods |
Exam signal
Every namespace carries the immutable label kubernetes.io/metadata.name: <name>. Use it to select a namespace by
name instead of adding your own labels, unless the task tells you which label to use.
Ports
ports:
- protocol: TCP
port: 5432
- protocol: TCP
port: 30000
endPort: 30100 # a range
- port: metrics # a named containerPort on the target Pod- A rule with
fromandportsneeds both to match. A rule with onlyportsallows those ports from anywhere. - Ports are the Pod's port (the
targetPort), not the Service port. Policies act after the Service IP is translated.
Egress and DNS
Once you isolate egress, Pods can't resolve names, because CoreDNS is just another set of Pods. Allow it explicitly:
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53Egress allowed to the database, app still broken
A policy that allows egress only to db on 5432 breaks an app that connects to db.data.svc.cluster.local,
because the DNS lookup is dropped first. Symptoms look like a database outage. Add the DNS rule (UDP and TCP
53) whenever you write egress rules.
Testing a policy
k -n vault-svc get netpol
k -n vault-svc describe netpol allow-api # shows the parsed selectors, check AND vs OR here
k -n storefront run t --rm -it --restart=Never --image=busybox:1.36 -l tier=api -- \
wget -qO- -T 2 http://keeper.vault-svc:8200 # allowed peer: should answer
k -n storefront run t2 --rm -it --restart=Never --image=busybox:1.36 -- \
wget -qO- -T 2 http://keeper.vault-svc:8200 # no label: should time outScenarios
Both selectors in one peer item are ANDed: reporter Pods inside analytics. Two items are ORed, which would admit every Pod in analytics plus reporter Pods in ledger. A podSelector alone looks in the policy's own namespace (ledger). A namespaceSelector alone admits all of analytics.
'Bad address' is a name-resolution failure: DNS traffic to CoreDNS isn't port 443, so it's dropped. Narrowing the ipBlock doesn't help DNS. Return traffic is allowed automatically. Removing Egress from policyTypes would undo the restriction the team wanted.
NetworkPolicies are a union of allow rules with no precedence. The monitoring policy matches the source and doesn't restrict ports, so the connection is allowed regardless of the first policy.
Drill
kubectl config use-context lab-net
Namespace vault-svc runs Pods labelled app=keeper listening on TCP 8200. Make sure that:
- All ingress to every Pod in
vault-svcis denied by default (policydeny-ingress). - Pods labelled
tier=apiin namespacestorefrontcan reachapp=keeperPods on TCP 8200 only (policyallow-api).
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-ingress
namespace: vault-svc
spec:
podSelector: {}
policyTypes:
- Ingress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api
namespace: vault-svc
spec:
podSelector:
matchLabels:
app: keeper
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: storefront
podSelector:
matchLabels:
tier: api
ports:
- protocol: TCP
port: 8200k apply -f vault-policies.yaml
k -n vault-svc describe netpol allow-api
KEEPER=$(k -n vault-svc get pod -l app=keeper -o jsonpath='{.items[0].status.podIP}')
k -n storefront run ok --rm -it --restart=Never --image=busybox:1.36 -l tier=api -- nc -zv -w 2 $KEEPER 8200 # open
k -n storefront run no --rm -it --restart=Never --image=busybox:1.36 -- nc -zv -w 2 $KEEPER 8200 # timeoutFurther reading
Pod networking
The Kubernetes networking model, how containers in a Pod share one network namespace, how the CNI plugin gives Pods routable IPs across nodes, and what kube-proxy adds on top.
Services
ClusterIP, NodePort, LoadBalancer, headless and ExternalName Services, the port, targetPort and nodePort mapping, EndpointSlices, traffic policies, and fixing a Service that has no endpoints.