Problem – Canary Elasticsearch Pods Time Out After Calico NetworkPolicy Change
During a canary rollout of a new Elasticsearch version, pods labeled app=es-canary were unable to reach the primary elasticsearch-master service. All HTTP requests to port 9200 and transport requests to port 9300 resulted in connection‑timeout errors, causing indexing and query failures and halting the rollout.
java.net.ConnectException: Connection timed out (connection timeout=30s)
org.elasticsearch.transport.TransportException: [es-canary-0] failed to connect to node [es-master-0]
no alive nodes found in your cluster
The failure manifested as repeated health‑check failures in the canary deployment pipeline and an alert from the SRE team indicating “Elasticsearch canary pods unreachable”.
Root Cause – Calico Policy Over‑Restricts Egress for Canary Pods
Calico NetworkPolicy was updated to enforce a stricter egress rule that only allowed traffic to pods with label role=backend. The policy was written as:
apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
name: backend-egress
namespace: production
spec:
selector: all()
egress:
- action: Allow
destination:
selector: role == 'backend'
Because the primary Elasticsearch pods carry the label role=master (or no role label at all), the egress rule did not match, and Calico’s default‑deny behavior blocked all outbound traffic from the canary pods. The Calico NetworkPolicy API reference states that an empty selector matches no pods, which explains why the policy effectively isolated the canary pods.
Additionally, the Elasticsearch network settings documentation requires both HTTP (9200) and transport (9300) ports to be reachable from any node that participates in the cluster. The policy change violated this requirement, leading to the observed timeouts.
Debug – Investigation Steps
1. Verify Symptom with Logs and Metrics
kubectl logs -n production es-canary-0
2023-11-12T08:45:12.345Z WARN [es-canary-0] failed to connect to node [es-master-0] (org.elasticsearch.transport.TransportException)
java.net.ConnectException: Connection timed out (connection timeout=30s)
Metrics from Prometheus showed a spike in es_http_client_requests_total{status="timeout"} for the canary deployment.
2. Confirm NetworkPolicy Effect
kubectl get networkpolicy -n production -o yaml backend-egress
Calico logs (visible via journalctl -u calico-node) contained entries such as:
DENIED egress from pod production/es-canary-0 to 10.96.0.10:9200
3. Test Connectivity from Canary Pod
kubectl exec -n production es-canary-0 -- curl -s -o /dev/null -w "%{http_code}" http://elasticsearch-master:9200
Result: 000 (no response). Using nc -vz elasticsearch-master 9200 also timed out.
4. Inspect Service & Endpoints
kubectl get svc -n production elasticsearch-master -o yaml
kubectl get endpoints -n production elasticsearch-master -o yaml
Endpoints resolved correctly (IP 10.96.0.10), confirming DNS and service configuration were not at fault.
5. Review Policy Scope and Namespace Selectors
GitHub issue projectcalico/calico#4932 describes a similar failure when a namespaceSelector unintentionally excluded the monitoring namespace, leading to DNS resolution failures. In our case, the policy lacked a namespaceSelector but the selector: all() applied cluster‑wide, affecting the canary pods.
Solution – Adjust Calico NetworkPolicy to Permit Elasticsearch Traffic
Before (Blocking Policy)
apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
name: backend-egress
namespace: production
spec:
selector: all()
egress:
- action: Allow
destination:
selector: role == 'backend'
After (Allow Elasticsearch HTTP & Transport)
apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
name: es-canary-egress
namespace: production
spec:
selector: app == 'es-canary'
egress:
# Allow traffic to any pod that is part of the Elasticsearch cluster
- action: Allow
destination:
selector: app == 'elasticsearch' # matches master, data, ingest pods
ports:
- 9200
- 9300
# Preserve existing backend allowance
- action: Allow
destination:
selector: role == 'backend'
The updated policy explicitly matches the canary pods (app=es-canary) and permits egress to any pod labeled app=elasticsearch on the required ports. This aligns with the ECK security guide, which recommends allowing both HTTP and transport ports for intra‑cluster communication.
Apply the Fixed Policy
kubectl apply -f es-canary-egress.yaml
After applying, Calico logs no longer show DENIED entries for the canary pod.
Verify – Confirm Connectivity and Functional Health
1. Re‑run Connectivity Checks
kubectl exec -n production es-canary-0 -- curl -s -o /dev/null -w "%{http_code}" http://elasticsearch-master:9200
Expected output: 200
kubectl exec -n production es-canary-0 -- nc -vz elasticsearch-master 9300
Connection to elasticsearch-master 9300 port [tcp/*] succeeded!
2. Observe Elasticsearch Client Logs
2023-11-12T08:52:01.112Z INFO [es-canary-0] successfully connected to node [es-master-0] (org.elasticsearch.transport.TransportService)
3. Check Health‑Check Metrics
Prometheus query es_cluster_health_status{cluster="canary"} returns green for the canary deployment.
4. Verify Rollout Completion
The CI/CD pipeline proceeds past the canary stage, and the new Elasticsearch version is promoted to production.
Prevent – Best Practices for NetworkPolicy Management in Elasticsearch Deployments
- Label Consistency: Ensure all Elasticsearch pods share a common label (e.g.,
app=elasticsearch) that can be referenced by policies. - Policy Scope: Use
selectorthat targets only the intended pods (e.g.,app == 'es-canary') instead ofall()which applies globally. - Port Whitelisting: Explicitly list both HTTP (9200) and transport (9300) ports in egress rules; omit them and you’ll encounter silent timeouts.
- Policy Ordering: When multiple policies exist, remember that Calico evaluates them cumulatively; a default‑deny policy can override a later allow rule if selectors don’t match.
- Testing Before Promotion: Deploy a temporary pod that mimics the canary label and run connectivity checks against the Elasticsearch service before applying new policies.
- Observability: Enable Calico flow logs (or audit logs) and monitor for
DENIED egressmessages; correlate them with application timeouts.
FAQ – Common Follow‑Up Questions
- Why did the canary pods still resolve the
elasticsearch-masterDNS name but fail to connect?
Because DNS resolution succeeds (service exists) but the egress traffic is blocked by Calico, resulting in a “no alive nodes found” error after the client times out. - Do I need to allow traffic to the Elasticsearch service IP or to individual pod IPs?
Both are covered by the same rule; Calico evaluates the destination selector against pod labels, which matches the pod IPs behind the service. Allowing the service IP alone would not bypass the pod‑level deny. - Can I use a
namespaceSelectorto simplify the policy?
Yes, but ensure the selector includes the namespace where the Elasticsearch pods run. A missing namespace (as seen in the fintech incident) will inadvertently block traffic. - What ports must be opened for an Elasticsearch cluster in Kubernetes?
At minimum, HTTP (9200) for REST calls and transport (9300) for inter‑node communication. If TLS is enabled, also allow the corresponding TLS ports (default same numbers). - How do I debug a similar issue if the policy uses
podSelector: {}?
An empty selector matches no pods, effectively isolating the source. Replace it with a concrete label selector or remove the rule if you intend “allow all”.
Related Topic Hub: Data Infrastructure Troubleshooting Hub