Problem
During the execution of integration tests on a Jenkins build agent, the CI job fails with a connection timeout when trying to reach the Haystack API server. In some runs the logs also show DNS resolution failures. The failure aborts the test suite and marks the build as unstable.
[2026-10-13 02:14:37] INFO - Starting integration tests...
[2026-10-13 02:14:38] ERROR - Request to http://haystack-api.internal/v1/search timed out after 10000ms
[2026-10-13 02:14:38] ERROR - java.net.UnknownHostException: haystack-api.internal
Impact:
- Pipeline stalls for ~10 seconds per test before timing out.
- All downstream stages that depend on the API (e.g., data validation, contract tests) are skipped.
- Developers receive false‑negative test results, eroding confidence in the CI pipeline.
Root Cause
The timeout and DNS errors stem from a combination of network isolation and stale DNS cache on the Jenkins agents:
- Agent network policy – The Kubernetes
NetworkPolicyapplied to thejenkins-agentnamespace only allowed egress to ports 443 and 80 on thedefaultnamespace. The Haystack API runs in thehaystacknamespace on port 8080, which was not whitelisted. - Service name mismatch – The integration tests reference
haystack-api.internal, but the actual Service DNS entry ishaystack-api.haystack.svc.cluster.local. The short name resolves only inside thehaystacknamespace, causingUnknownHostExceptionwhen the agent runs in a different namespace. - DNS cache poisoning – The
systemd-resolvedcache on the agent node retained an old negative entry forhaystack-api.internal. Subsequent test runs hit the cached failure until the cache TTL expired.
Debug
Step‑by‑step investigation performed on a failing build agent:
1. Verify DNS resolution from the agent container
$ kubectl exec -ti jenkins-agent-7f9c9d9c9b-ktz9q -- nslookup haystack-api.internal
Server: 10.96.0.10
Address: 10.96.0.10#53
** server can't find haystack-api.internal: NXDOMAIN
2. Check service FQDN
$ kubectl get svc -n haystack haystack-api -o jsonpath='{.spec.clusterIP}'
10.103.45.22
$ kubectl exec -ti jenkins-agent-7f9c9d9c9b-ktz9q -- nslookup haystack-api.haystack.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: haystack-api.haystack.svc.cluster.local
Address: 10.103.45.22
3. Inspect NetworkPolicy
$ kubectl get netpol -n jenkins-agent -o yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: jenkins-agent-egress
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
name: default
ports:
- protocol: TCP
port: 80
- protocol: TCP
port: 443
4. Examine DNS cache on the node
$ sudo systemd-resolve --statistics
Cache: 1024 entries (hits: 3123, misses: 874, evictions: 57)
5. Review Jenkins job configuration
pipeline {
agent { label 'linux' }
stages {
stage('Integration Tests') {
steps {
sh '''
export HAYSTACK_API_URL=http://haystack-api.internal/v1
./run_integration_tests.sh
'''
}
}
}
}
Solution
Three corrective actions were applied:
1. Update NetworkPolicy to allow egress to Haystack namespace on port 8080
Before (restricted egress):
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: jenkins-agent-egress
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
name: default
ports:
- protocol: TCP
port: 80
- protocol: TCP
port: 443
After (added Haystack egress):
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: jenkins-agent-egress
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
name: default
ports:
- protocol: TCP
port: 80
- protocol: TCP
port: 443
- to:
- namespaceSelector:
matchLabels:
name: haystack
ports:
- protocol: TCP
port: 8080
2. Use the fully‑qualified Service DNS name in the test configuration
Modified run_integration_tests.sh to reference the FQDN:
- export HAYSTACK_API_URL=http://haystack-api.internal/v1
+ export HAYSTACK_API_URL=http://haystack-api.haystack.svc.cluster.local:8080/v1
3. Flush stale DNS cache on agent nodes
# On each Jenkins agent node
sudo systemd-resolve --flush-caches
# Verify cache is empty
sudo systemd-resolve --statistics
Optionally, add a postStart hook to the agent pod spec to clear the cache on every start:
apiVersion: v1
kind: Pod
metadata:
name: jenkins-agent
spec:
containers:
- name: agent
image: jenkins/inbound-agent:latest
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "systemd-resolve --flush-caches"]
Verify
Run the pipeline again and confirm successful connectivity:
[2026-10-13 02:14:37] INFO - Starting integration tests...
[2026-10-13 02:14:38] INFO - Connecting to http://haystack-api.haystack.svc.cluster.local:8080/v1/search
[2026-10-13 02:14:38] INFO - Received 200 OK (duration: 124ms)
[2026-10-13 02:14:45] INFO - All integration tests passed
Additional validation steps:
- Check egress flow logs (e.g., Calico or Cilium) for allowed traffic to
10.103.45.22:8080. - Run a one‑off curl from the agent container:
$ kubectl exec -ti jenkins-agent-7f9c9d9c9b-ktz9q -- curl -s -o /dev/null -w "%{http_code}" http://haystack-api.haystack.svc.cluster.local:8080/healthz
200
Prevent
Implement guardrails to avoid recurrence:
- NetworkPolicy review: Include a CI/CD egress rule template that lists required service namespaces and ports. Enforce via CI linting.
- Configuration lint: Add a static analysis step that checks environment variables for fully‑qualified DNS names (e.g., regex
.*\.svc\.cluster\.local). - DNS health check: Deploy a DaemonSet that periodically resolves critical service names and reports failures to Prometheus.
- Agent image hardening: Base the agent image on a minimal OS without persistent DNS cache, or configure
systemd-resolvedto use a very short negative TTL (e.g., 30 s). - Alerting: Create an alert on
kube_pod_container_status_waiting_reason{reason="CrashLoopBackOff",container="agent"}combined withincrease(http_request_duration_seconds_count{code="5xx"}[5m]) > 0to surface connectivity issues early.
Related Topic Hub: RAG Systems Troubleshooting Hub
FAQ
- Why does the DNS name work locally but not in the Jenkins job?
Local development pods run in the samehaystacknamespace, so the short name resolves via the cluster DNS suffix. Jenkins agents run in a separate namespace where the short name is not searchable, requiring the full service FQDN. - Can I keep using the short name by adding a DNS search suffix?
Yes. Addingsearch haystack.svc.cluster.localto/etc/resolv.confof the agent pods would make the short name resolve, but using the FQDN is more explicit and avoids accidental cross‑namespace collisions. - Do I need to modify the Haystack API Service definition?
No. The Service was correctly defined. The issue was only the client side DNS name and egress policy. - What if the API moves to a different port?
Update the NetworkPolicy egress rule to include the new port and adjust the environment variable accordingly. Consider parameterizing the port in a ConfigMap shared between CI and production. - How can I detect similar DNS‑related failures in other pipelines?
Enable a globaldns_error_totalPrometheus counter in the agent image that increments onUnknownHostException. Alert when the counter spikes for any pipeline.