K8s by Example: Network Policies

NetworkPolicies are firewall rules for Pods. By default, all Pods can communicate freely. Policies restrict traffic to explicit allow rules. Requires a CNI that supports NetworkPolicy (Calico, Cilium, etc.).

network-policy.yaml

NetworkPolicies use the networking.k8s.io/v1 API. podSelector targets which Pods the policy applies to. Once selected, Pods become isolated for the specified policyTypes.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-policy
  namespace: my-app
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
    - Ingress
    - Egress
network-policy-ingress.yaml

Ingress rules control incoming traffic. This example allows traffic from frontend Pods on port 8080. Empty selector {} selects all Pods in the namespace.

spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend
      ports:
        - protocol: TCP
          port: 8080
network-policy-logic.yaml

Multiple selectors in one from block use AND logic (first example: requires BOTH namespace AND pod selector). Multiple from blocks use OR logic (second example: matches EITHER). Be careful with this distinction since it’s a common source of policy errors.

ingress:
  - from:
      - namespaceSelector:
          matchLabels:
            env: production
        podSelector:
          matchLabels:
            role: frontend
---
ingress:
  - from:
      - namespaceSelector:
          matchLabels:
            env: production
      - podSelector:
          matchLabels:
            role: frontend
network-policy-namespace.yaml

Allow traffic from other namespaces using namespaceSelector. The kubernetes.io/metadata.name label is auto-applied to all namespaces. Empty selector {} matches any namespace.

ingress:
  - from:
      - namespaceSelector:
          matchLabels:
            kubernetes.io/metadata.name: monitoring
    ports:
      - protocol: TCP
        port: 9090
  - from:
      - namespaceSelector: {}
        podSelector:
          matchLabels:
            role: ingress-controller
network-policy-egress.yaml

Control egress (outbound) traffic. First rule allows DNS (port 53), which is critical or Pods can’t resolve names. Second rule allows external HTTPS except private IPs. Use ipBlock to allow/deny specific CIDR ranges.

egress:
  - to:
      - namespaceSelector:
          matchLabels:
            kubernetes.io/metadata.name: kube-system
    ports:
      - protocol: UDP
        port: 53
  - to:
      - ipBlock:
          cidr: 0.0.0.0/0
          except:
            - 10.0.0.0/8
            - 172.16.0.0/12
            - 192.168.0.0/16
    ports:
      - protocol: TCP
        port: 443
default-deny.yaml

Default deny policies isolate a namespace. Apply these first, then add specific allow rules. Best practice for multi-tenant clusters and zero-trust networking.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
spec:
  podSelector: {}
  policyTypes:
    - Ingress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
spec:
  podSelector: {}
  policyTypes:
    - Egress
network-policy-ports.yaml

Named ports reference container port names instead of numbers. More maintainable when port numbers change. Use endPort to match port ranges.

ingress:
  - from:
      - podSelector:
          matchLabels:
            app: frontend
    ports:
      - protocol: TCP
        port: http
      - protocol: TCP
        port: 8000
        endPort: 8100
terminal

Debug NetworkPolicies by checking which policies apply to a Pod and verifying CNI support. Policies are additive, so if any policy allows traffic, it’s allowed.

$ kubectl get networkpolicies -n my-app
NAME                   POD-SELECTOR   AGE
api-policy             app=api        5m
default-deny-ingress   <none>         10m

$ kubectl describe networkpolicy api-policy
Spec:
  PodSelector: app=api
  Allowing ingress traffic:
    To Port: 8080/TCP
    From:
      PodSelector: app=frontend

$ kubectl run test --rm -it --image=busybox -- wget -qO- http://api:8080
Hello from API!

$ kubectl get pods -n kube-system -l k8s-app=calico-node
NAME                READY   STATUS    AGE
calico-node-abc12   1/1     Running   30d

Index | GitHub | Use arrow keys to navigate |