## Intro

Kubernetes Network Policies are essential for controlling pod-to-pod traffic and enforcing zero-trust networking inside a cluster. However, defining policies is only half the battle. Without effective monitoring and alerting, you cannot know whether policies are actually enforced, misconfigured, or silently dropping legitimate traffic. This guide provides a practical, step-by-step approach to monitoring Kubernetes Network Policies using native tools, Prometheus, and alerting systems. You will learn how to collect metrics, build dashboards, set up alerts, and respond to incidents with real examples and commands.

We will focus on common scenarios such as detecting denied traffic, auditing policy changes, and troubleshooting connectivity issues caused by overly restrictive rules. The article is aimed at platform engineers, SREs, and DevOps teams running Kubernetes in production.

Before making any changes, always establish observability. The goal is to make network policy behavior visible, enable rapid diagnosis, and ensure that any configuration change is safe and reversible.

## Version and Environment Inventory

Before implementing monitoring, document your environment. This inventory will help you choose the right tools and understand compatibility.

### Cluster and CNI Details

Network Policy enforcement depends on the CNI plugin. Common CNIs that support Network Policies include Calico, Cilium, Weave Net, and Antrea. Verify your CNI and its version:

kubectl get nodes -o wide
# Check CNI pods
kubectl get pods -n kube-system | grep -E 'calico|cilium|weave|antrea' 
 Example output (Calico):

calico-node-abcde 1/1 Running 0 5d
calico-kube-controllers-67890 1/1 Running 0 5d 
 Check the CNI configuration in the node if needed, but the pod image tag often reveals the version:

kubectl get ds -n kube-system calico-node -o jsonpath='{.spec.template.spec.containers[0].image}' 

### Kubernetes Version and API Availability

 NetworkPolicy is a stable API (networking.k8s.io/v1) since Kubernetes 1.7. Ensure your cluster is recent enough:

kubectl version --short 
 Example:

Client Version: v1.28.2
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Server Version: v1.28.3 

### Existing Network Policies

 List all Network Policies across namespaces to understand your current state:

kubectl get networkpolicies -A 
 Example output:

NAMESPACE NAME POD-SELECTOR AGE
default deny-all <none> 10d
kube-system allow-dns app=kube-dns 10d
app-ns allow-frontend-tiers tier=frontend 2d 
 Note any default-deny policies and their selectors. This is your baseline.

### Monitoring Tools Available

Check if Prometheus is installed:

kubectl get pods -n monitoring | grep prometheus 
 If not, you may need to install Prometheus and configure it to scrape metrics from your CNI or use a ServiceMonitor.

<style>
.eno-quiz-widget{margin:2rem 0;padding:1.5rem;border-radius:12px;background:var(--eno-surface-lowest,#f7f7f8);border:1px solid var(--eno-border-soft,#e2e2e6);font-family:var(--eno-font-body,Inter,sans-serif)}
.eno-quiz-widget .eno-quiz-kicker{font-family:var(--eno-font-label,"JetBrains Mono",monospace);font-size:.75rem;letter-spacing:.05em;text-transform:uppercase;color:var(--eno-text-muted,#5f5f68);margin:0 0 .5rem}
.eno-quiz-widget .eno-quiz-question{font-family:var(--eno-font-heading,"Hanken Grotesk",sans-serif);font-size:1.0625rem;font-weight:600;margin:0 0 1rem;color:var(--eno-text-strong,#1a1a1f)}
.eno-quiz-widget .eno-quiz-options{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.5rem}
.eno-quiz-widget .eno-quiz-option{display:block;width:100%;min-height:44px;text-align:left;padding:.625rem .875rem;border-radius:8px;border:1.5px solid var(--eno-border-soft,#e2e2e6);background:#fff;font-size:.9375rem;cursor:pointer;transition:border-color var(--eno-motion-base,180ms ease-out),background var(--eno-motion-base,180ms ease-out)}
.eno-quiz-widget .eno-quiz-option:hover{border-color:var(--eno-primary,#0059bb)}
.eno-quiz-widget .eno-quiz-option:focus-visible{outline:none;box-shadow:0 0 0 3px rgb(0 89 187 / 0.15)}
.eno-quiz-widget .eno-quiz-option[aria-pressed="true"]{border-color:var(--eno-primary,#0059bb);background:rgb(0 89 187 / 0.06)}
.eno-quiz-widget .eno-quiz-option[data-correct="true"].eno-quiz-revealed{border-color:var(--eno-success,#17803a);background:rgb(23 128 58 / 0.08)}
.eno-quiz-widget .eno-quiz-option[data-correct="false"].eno-quiz-revealed.eno-quiz-was-selected{border-color:var(--eno-error,#ba1a1a);background:var(--eno-error-soft,#ffdad6)}
.eno-quiz-widget .eno-quiz-option-icon{display:inline-block;width:1.1em;margin-right:.4em;font-weight:700}
.eno-quiz-widget .eno-quiz-submit{margin-top:1rem;min-height:44px;padding:.5rem 1.25rem;border-radius:8px;border:none;background:var(--eno-primary,#0059bb);color:#fff;font-weight:600;font-size:.9375rem;cursor:pointer;transition:opacity var(--eno-motion-base,180ms ease-out)}
.eno-quiz-widget .eno-quiz-submit:disabled{opacity:.5;cursor:not-allowed}
.eno-quiz-widget .eno-quiz-submit:focus-visible{outline:none;box-shadow:0 0 0 3px rgb(0 89 187 / 0.15)}
.eno-quiz-widget .eno-quiz-explanation{margin-top:1rem;padding:.875rem 1rem;border-radius:8px;font-size:.9375rem;line-height:1.5;display:none}
.eno-quiz-widget .eno-quiz-explanation.eno-quiz-visible{display:block}
.eno-quiz-widget .eno-quiz-explanation.eno-quiz-correct{background:rgb(23 128 58 / 0.08);color:var(--eno-success,#17803a)}
.eno-quiz-widget .eno-quiz-explanation.eno-quiz-incorrect{background:var(--eno-error-soft,#ffdad6);color:var(--eno-error,#ba1a1a)}
@media (prefers-reduced-motion: reduce){.eno-quiz-widget *{transition:none!important}}
</style><div class="eno-quiz-widget" role="group" aria-label="Quick check question">
Quick check 1 of 2

What is the default behavior of pod-to-pod communication in a Kubernetes cluster?

- All pods are allowed to communicate with each other
- All pods are denied communication unless explicitly allowed
- Communication is only allowed within the same namespace
- Communication is only allowed if encrypted

<button type="button" class="eno-quiz-submit" disabled>Submit</button>
<div class="eno-quiz-explanation">Reference passage [1] states: &#39;By default, all pods in a Kubernetes cluster are allowed to communicate with each other, and all network traffic is unencrypted.&#39;</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
  document.querySelectorAll('.eno-quiz-widget:not([data-eno-quiz-bound])').forEach(function (widget) {
    widget.setAttribute('data-eno-quiz-bound', '1');
    var options = Array.prototype.slice.call(widget.querySelectorAll('.eno-quiz-option'));
    var submitBtn = widget.querySelector('.eno-quiz-submit');
    var explanation = widget.querySelector('.eno-quiz-explanation');
    var selected = null;
    options.forEach(function (opt) {
      opt.addEventListener('click', function () {
        if (widget.hasAttribute('data-eno-quiz-answered')) return;
        options.forEach(function (o) { o.setAttribute('aria-pressed', 'false'); });
        opt.setAttribute('aria-pressed', 'true');
        selected = opt;
        submitBtn.disabled = false;
      });
    });
    submitBtn.addEventListener('click', function () {
      if (!selected || widget.hasAttribute('data-eno-quiz-answered')) return;
      widget.setAttribute('data-eno-quiz-answered', '1');
      submitBtn.disabled = true;
      var correct = selected.getAttribute('data-correct') === 'true';
      options.forEach(function (o) {
        o.classList.add('eno-quiz-revealed');
        if (o === selected) o.classList.add('eno-quiz-was-selected');
        var icon = o.querySelector('.eno-quiz-option-icon');
        if (o.getAttribute('data-correct') === 'true') icon.textContent = '\u2713';
        else if (o === selected) icon.textContent = '\u2717';
      });
      explanation.classList.add('eno-quiz-visible', correct ? 'eno-quiz-correct' : 'eno-quiz-incorrect');
    });
  });
});
</script>

## Safe Configuration Path

Monitoring Network Policies should be done incrementally to avoid disrupting production traffic. Follow a safe path: observe, enable metrics, test, and then alert.

### Step 1: Enable Metrics Collection from CNI

Most CNIs expose Prometheus metrics. For Calico, the Felix agent (running on each node) provides detailed policy metrics. Enable metrics reporting by applying the following configuration if not already enabled.

For Calico, you can use the built-in FelixConfiguration:

apiVersion: crd.projectcalico.org/v1
kind: FelixConfiguration
metadata:
 name: default
spec:
 prometheusMetricsEnabled: true
 prometheusMetricsPort: 9091 
 Apply with kubectl apply -f felix-config.yaml . Verify by checking a node's Felix port:

kubectl get felixconfigurations default -o yaml | grep prometheus 
 For Cilium, metrics are enabled by default, but verify:

kubectl -n kube-system exec ds/cilium -- cilium status | grep Metrics 

### Step 2: Create a ServiceMonitor for Prometheus

 If you use the Prometheus Operator, create a ServiceMonitor to scrape Felix metrics:

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
 name: calico-felix
 namespace: monitoring
spec:
 selector:
 matchLabels:
 k8s-app: calico-node
 endpoints:
 - port: metrics
 interval: 30s 
 Ensure the calico-node service exposes the metrics port. If not, create a service:

apiVersion: v1
kind: Service
metadata:
 name: calico-node-metrics
 namespace: kube-system
 labels:
 k8s-app: calico-node
spec:
 selector:
 k8s-app: calico-node
 ports:
 - name: metrics
 port: 9091
 targetPort: 9091 
 Apply and verify Prometheus targets:

kubectl -n monitoring port-forward svc/prometheus-operated 9090:9090
# Then open http://localhost:9090/targets and confirm calico-felix is up. 

### Step 3: Define Minimal Test Traffic

 Before relying on alerts, generate known traffic that should be allowed or denied by a test policy. Create a test namespace and pods:

kubectl create ns policy-test
kubectl run allowed-client --image=busybox --restart=Never -- sleep 3600
kubectl run denied-client --image=busybox --restart=Never -- sleep 3600
kubectl run server --image=nginx --restart=Never 
 Apply a NetworkPolicy that allows only allowed-client to access server:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
 name: test-allow-specific
 namespace: policy-test
spec:
 podSelector:
 matchLabels:
 run: server
 ingress:
 - from:
 - podSelector:
 matchLabels:
 run: allowed-client
 ports:
 - port: 80 
 Now test connectivity:

kubectl exec -it allowed-client -- wget -qO- --timeout=2 http://server
# Should return HTML
kubectl exec -it denied-client -- wget -qO- --timeout=2 http://server
# Should fail or time out 
 This baseline helps validate that alerts correspond to actual traffic.

## Verification and Diagnostics

Once metrics are scraped and test traffic is flowing, you need to verify that policy enforcement is visible and diagnose any issues.

### Key Metrics to Monitor

Different CNIs expose different metric names. For Calico Felix, important metrics include:

- felix_iptables_restore_errors : errors applying iptables rules.

- felix_denied_packets_total : count of packets denied by policy (if iptables action is set to LOG).

- felix_ipset_errors : errors manipulating IP sets.

- felix_active_local_endpoints : number of local endpoints.

For Cilium:

- cilium_drop_count_total : dropped packets due to policy.

- cilium_policy_endpoint_enforcement_status : enforcement status per endpoint.

Query examples in Prometheus:

# Total denied packets in last 5 minutes
sum(increase(felix_denied_packets_total[5m]))

# Rate of drops in Cilium
sum(rate(cilium_drop_count_total[5m])) by (reason) 

### PromQL for Policy Auditing

 To see which pods are generating drops, use labels. Calico metrics often include labels like source_namespace , source_pod , dest_namespace , dest_pod . Example:

topk(10, sum by (source_namespace, source_pod) (rate(felix_denied_packets_total[5m]))) 
 This gives you the top sources of denied traffic.

### Using kubectl to Inspect Policy Status

While CNI metrics show enforcement, you can also inspect policy objects and endpoints:

kubectl describe networkpolicy test-allow-specific -n policy-test 
 Output includes the pod selector and ingress rules. To see if a policy is applied to a pod, check the pod's annotations or use CNI CLI tools:

For Calico:

kubectl exec -n kube-system calicoctl -- calicoctl get profile -A 
 For Cilium, use cilium endpoint list to see policy enforcement status:

kubectl -n kube-system exec ds/cilium -- cilium endpoint list 
 Look for Policy enforcement column: Ingress, Egress, or Both.

### Diagnosing Connectivity Issues

If traffic is unexpectedly blocked, follow this sequence:

- Check if NetworkPolicy exists and selects the pod:

kubectl get networkpolicy -n <namespace> 

- Describe the policy and verify rules:

 kubectl describe networkpolicy <name> -n <namespace> 
 For Calico Felix:

- Test with a known client pod (as earlier).

- Check metrics for drops: query Prometheus for denied packets in that namespace.

- Check CNI logs:

kubectl logs -n kube-system ds/calico-node -c calico-node --tail=50 
 For Cilium:

kubectl -n kube-system logs ds/cilium --tail=50 

 Quick check 2 of 2

What is recommended to start with when strict network isolation between tenants is required?

- A policy that allows all traffic within a namespace
- A default policy that denies communication between pods with another rule allowing DNS queries
- A policy that allows all traffic between namespaces
- A policy that only allows HTTPS traffic

<button type="button" class="eno-quiz-submit" disabled>Submit</button>
<div class="eno-quiz-explanation">Reference passage [1] says: &#39;In a multi-tenant environment where strict network isolation between tenants is required, starting with a default policy that denies communication between pods is recommended with another rule that allows all pods to query the DNS server for name resolution.&#39;</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
  document.querySelectorAll('.eno-quiz-widget:not([data-eno-quiz-bound])').forEach(function (widget) {
    widget.setAttribute('data-eno-quiz-bound', '1');
    var options = Array.prototype.slice.call(widget.querySelectorAll('.eno-quiz-option'));
    var submitBtn = widget.querySelector('.eno-quiz-submit');
    var explanation = widget.querySelector('.eno-quiz-explanation');
    var selected = null;
    options.forEach(function (opt) {
      opt.addEventListener('click', function () {
        if (widget.hasAttribute('data-eno-quiz-answered')) return;
        options.forEach(function (o) { o.setAttribute('aria-pressed', 'false'); });
        opt.setAttribute('aria-pressed', 'true');
        selected = opt;
        submitBtn.disabled = false;
      });
    });
    submitBtn.addEventListener('click', function () {
      if (!selected || widget.hasAttribute('data-eno-quiz-answered')) return;
      widget.setAttribute('data-eno-quiz-answered', '1');
      submitBtn.disabled = true;
      var correct = selected.getAttribute('data-correct') === 'true';
      options.forEach(function (o) {
        o.classList.add('eno-quiz-revealed');
        if (o === selected) o.classList.add('eno-quiz-was-selected');
        var icon = o.querySelector('.eno-quiz-option-icon');
        if (o.getAttribute('data-correct') === 'true') icon.textContent = '\u2713';
        else if (o === selected) icon.textContent = '\u2717';
      });
      explanation.classList.add('eno-quiz-visible', correct ? 'eno-quiz-correct' : 'eno-quiz-incorrect');
    });
  });
});
</script>

## Failure Modes and Recovery

Monitoring itself can fail, and policies can cause unintended outages. This section covers common failure modes and how to recover safely.

### Typical Failure Scenarios

### 1. Overly Restrictive Network Policy

A new policy may block DNS or health checks, causing pods to fail readiness probes. Symptom: pods stuck in ContainerCreating or not ready.

Check pod events:

kubectl describe pod <pod-name> -n <namespace> 
 If events show connection refused to kube-dns or similar, the policy is likely blocking DNS. Quick recovery: delete or modify the policy to allow DNS (port 53) to kube-dns.

Example fix: apply an egress rule for DNS:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
 name: allow-dns
 namespace: myapp
spec:
 podSelector: {}
 egress:
 - to:
 - namespaceSelector:
 matchLabels:
 kubernetes.io/metadata.name: kube-system
 ports:
 - protocol: UDP
 port: 53
 - protocol: TCP
 port: 53
 policyTypes:
 - Egress 

### 2. Metrics Endpoint Not Scraped

 If Prometheus cannot scrape Felix metrics, alerts won't fire. Check Prometheus targets for errors:

https://prometheus.example.com/targets 
 Common issues: service labels mismatch, network policy blocking Prometheus, or missing ServiceMonitor. Fix by aligning labels in ServiceMonitor and Service, and ensure Prometheus can reach the metrics port.

### 3. Log Noise from Denied Packets

If you enable iptables logging for denied packets (via FelixConfiguration logSeverityScreen or similar), logs may flood. Set appropriate log levels and rotate logs. Monitor your log pipeline.

### Recovery Procedures

### Rolling Back a Network Policy

Always back up policies before changing. Use kubectl get networkpolicy <name> -n <ns> -o yaml > policy-backup.yaml . To rollback, apply the backup.

### Temporarily Disabling Enforcement

If a policy is causing an outage, you can quickly delete it:

kubectl delete networkpolicy <name> -n <namespace> 
 But to be safer, you can annotate the policy to disable enforcement if supported by CNI. For Calico, you can set the policy order or use applyOnForward ? Not all CNIs support this. Deletion is often the quickest.

## Operations Checklist

Use this checklist to ensure ongoing monitoring and quick response.

### Daily Checks

- [ ] Prometheus target calico-felix is UP (or your CNI equivalent).

- [ ] No critical alerts firing for policy drops or errors.

- [ ] CNI pods are Running and not restarting frequently.

Command:

kubectl get pods -n kube-system | grep -E 'calico|cilium|antrea|weave' 

### Weekly Checks

- [ ] Review top denied traffic sources using PromQL and investigate if legitimate.

- [ ] Audit NetworkPolicy changes: kubectl get events --all-namespaces | grep NetworkPolicy .

- [ ] Check Prometheus storage and scrape duration to avoid performance issues.

### When a New NetworkPolicy is Applied

- Before applying, simulate expected traffic with a test pod.

- Apply policy in a staging namespace first if possible.

- Monitor metrics for unexpected drops in the first hour.

- Have a rollback plan: backup YAML.

### Alert Runbook: High Denied Traffic

 Alert definition example (Prometheus rule):

groups:
- name: network-policy
 rules:
 - alert: HighDeniedTraffic
 expr: sum(rate(felix_denied_packets_total[5m])) > 100
 for: 5m
 labels:
 severity: warning
 annotations:
 summary: "High rate of denied packets"
 description: "Denied packets rate is {{ $value }} per second. Investigate potential misconfigurations." 
 Runbook steps:

- Check Prometheus to identify top namespaces and pods causing denies.

- If legitimate traffic, adjust NetworkPolicy to allow it.

- If malicious, investigate source and consider additional security measures.

- If noise, adjust logging or alert threshold.

## Conclusion

Kubernetes Network Policy monitoring is crucial for maintaining a secure and reliable cluster. By leveraging CNI metrics, Prometheus, and well-defined alerts, you can detect misconfigurations early, troubleshoot connectivity issues quickly, and ensure policies are enforced as intended. This guide provided concrete steps for environment inventory, safe configuration, verification, failure recovery, and operational checklists. Start with the basics: enable metrics, create a dashboard, and set up simple alerts. Then iterate to refine your monitoring as your policies evolve. Remember, the goal is not just to enforce policies but to understand their impact on your applications and respond effectively when things go wrong.