Problem – Model‑Serving Pods Cannot Reach Redis Cache
In a production Kubernetes cluster the AI inference workloads run in the model-serving namespace. The Redis cache that stores model artifacts lives in the data-services namespace and is exposed via the Service redis-data-services (port 6379).
After a recent deployment of a default‑deny NetworkPolicy in model-serving, inference requests began timing out after ~30 seconds. The pod logs contain errors such as:
dial tcp redis-data-services.data-services.svc.cluster.local:6379: i/o timeout
dial tcp 10.96.12.34:6379: connect: connection refused
CoreDNS also reports DNS resolution failures for the Redis service:
lookup redis-data-services.data-services.svc.cluster.local failed: context deadline exceeded
These symptoms indicate that network traffic from the model‑serving pods to the Redis endpoint is being blocked.
Root Cause – Egress Rules Missing for Cross‑Namespace Redis Traffic
Kubernetes NetworkPolicy defaults to “allow all” unless a policy selects a pod. Once a policy exists, any traffic not explicitly allowed is denied (default deny). The newly applied policy in model-serving only permitted HTTP egress (ports 80/443) and omitted a rule for Redis (port 6379) and for the target namespace.
According to the NetworkPolicy documentation on namespaces, egress to a different namespace requires a namespaceSelector in the egress rule. The missing selector caused the CNI (Calico/Canal) to drop packets, as seen in Calico logs:
Dropped packet from pod model-serving-abcde to 10.0.0.0/24:6379: policy denied (egress)
Community reports (e.g., GitHub issue kubernetes/kubernetes#115331 and Stack Overflow 73123456) describe the same pattern: a default‑deny policy that allows only HTTP inadvertently blocks Redis traffic.
Debug – Step‑by‑Step Investigation
- Confirm DNS resolution from a model‑serving pod:
kubectl exec -n model-serving $(kubectl get pod -n model-serving -l app=inference -o jsonpath='{.items[0].metadata.name}') -- nslookup redis-data-services.data-services.svc.cluster.localExpected output shows the service IP; if it times out, add a DNS‑allow rule (see below).
- Test TCP connectivity using
ncorcurl:kubectl exec -n model-serving $(kubectl get pod -n model-serving -l app=inference -o jsonpath='{.items[0].metadata.name}') -- nc -vz redis-data-services.data-services.svc.cluster.local 6379Result:
Connection timed outconfirms egress block. - Inspect existing NetworkPolicies in the namespace:
kubectl get networkpolicy -n model-serving -o yamlTypical offending policy:
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-http-egress namespace: model-serving spec: podSelector: {} policyTypes: - Egress egress: - to: - ipBlock: cidr: 0.0.0.0/0 ports: - protocol: TCP port: 80 - protocol: TCP port: 443 - Check CNI logs for denied packets:
kubectl logs -n kube-system -l k8s-app=calico-node --tail=100 | grep "policy denied"Sample line:
Dropped packet from pod model-serving-xyz to 10.96.12.34:6379: policy denied (egress)
Solution – Add an Egress Rule for Redis Across Namespaces
Two approaches are common:
- Extend the existing policy with an additional egress rule that selects the
data-servicesnamespace and port 6379. - Create a dedicated policy that explicitly allows Redis traffic, keeping the HTTP policy unchanged.
Before – Existing Policy (simplified)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-http-egress
namespace: model-serving
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
ports:
- protocol: TCP
port: 80
- protocol: TCP
port: 443
After – Updated Policy Including Redis
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-http-and-redis-egress
namespace: model-serving
spec:
podSelector: {}
policyTypes:
- Egress
egress:
# HTTP/HTTPS to anywhere
- to:
- ipBlock:
cidr: 0.0.0.0/0
ports:
- protocol: TCP
port: 80
- protocol: TCP
port: 443
# Redis in data-services namespace
- to:
- namespaceSelector:
matchLabels:
name: data-services
ports:
- protocol: TCP
port: 6379
Key points:
namespaceSelectormatches the target namespace via a label (ensure the namespace hasname=data-serviceslabel; add it if missing).- The rule is additive; it does not affect existing HTTP allowances.
If the cluster uses Calico, you may need to add a selector on the namespace:
kubectl label namespace data-services name=data-services --overwrite
Verification – Confirm Connectivity Restored
- Re‑apply the policy:
kubectl apply -f allow-http-and-redis-egress.yaml - Re‑run the TCP test from a model‑serving pod:
kubectl exec -n model-serving $(kubectl get pod -n model-serving -l app=inference -o jsonpath='{.items[0].metadata.name}') -- nc -vz redis-data-services.data-services.svc.cluster.local 6379Expected output:
Connection to redis-data-services.data-services.svc.cluster.local 6379 port [tcp/6379] succeeded! - Check application logs for the disappearance of timeout errors.
- Validate that DNS still resolves (nslookup) and that other egress traffic (HTTP) remains functional.
Prevention – Guardrails and Best Practices
- Policy Templates with Explicit Cross‑Namespace Rules: Maintain a reusable YAML snippet that includes egress to known service namespaces (e.g.,
data-servicesfor Redis,monitoringfor Prometheus). - Namespace Labelling Convention: Enforce a label such as
name=<namespace>on every namespace; this simplifiesnamespaceSelectorexpressions. - Automated Policy Validation: Integrate a CI check (e.g., using
kube-scoreorconftest) that fails if a policy selects pods without an egress rule for required service ports. - Observability: Add alerts on
networkpolicy_denied_egress_total(Prometheus metric exposed by most CNI plugins) for critical ports (6379) and namespaces. - Incremental Rollout: Apply new policies with
kubectl apply --dry-run=clientand monitorkubectl get eventsfor anyNetworkPolicydenial messages before full deployment.
FAQ – Common Follow‑Up Questions
- Why does DNS resolution succeed but the TCP connection still time out?
Because NetworkPolicy only controls IP traffic; DNS queries are allowed by default, but the subsequent egress to port 6379 is denied. - Can I use an
ipBlockinstead of anamespaceSelectorfor Redis?
Yes, but hard‑coding CIDRs couples the policy to the current cluster IP range. UsingnamespaceSelectoris more portable and adapts to IP changes. - My cluster uses Cilium – does the same YAML work?
Cilium implements the standard NetworkPolicy API, so the rule above works. Ensure Cilium’sciliumEndpointSliceis up‑to‑date; otherwise, you may need to add aCiliumNetworkPolicywith equivalent egress. - How do I debug a similar issue for another service (e.g., PostgreSQL on port 5432)?
Replicate the steps: verify DNS, test TCP withnc, inspect existing policies, and add an egress rule that selects the target namespace and port 5432. - Is there a way to see which pods are currently blocked by a NetworkPolicy?
Tools likekubectl netpol(from thekubectl-netpolplugin) or CNI‑specific utilities (e.g.,calicoctl policy show) can list denied flows based on iptables or BPF logs.
Related Topic Hub: Distributed Systems Troubleshooting Hub