Problem – MLflow Tracking API Unreachable During Batch Job Execution
Batch jobs that run model training or data preprocessing attempt to log parameters, metrics, and artifacts to an MLflow tracking server. In production the jobs fail with errors such as:
2026-10-30 14:12:03,721 ERROR mlflow.tracking.client: Failed to log metric:
Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/mlflow/tracking/client.py", line 1123, in log_metric
resp = self._tracking_client.log_metric(run_id, key, value, timestamp, step)
File "/usr/local/lib/python3.11/site-packages/mlflow/tracking/_tracking_service/client.py", line 215, in log_metric
raise MlflowException("Failed to log metric: %s" % e)
mlflow.exceptions.MlflowException: Failed to log metric:
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='mlflow-tracking.mycorp.com', port=443): Max retries exceeded with url: /api/2.0/mlflow/runs/log-metric (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 111] Connection refused'))
The failure halts the batch pipeline, causing downstream steps to be skipped and SLA violations.
Root Cause – Network Path Disruption and Inconsistent TLS Configuration
The underlying issue is a combination of:
- Network segmentation: The batch job runs in a Kubernetes namespace that is isolated by NetworkPolicy. Outbound traffic to the MLflow tracking server’s IP (10.2.45.12) on port 443 is blocked for pods that do not have the
mlflow-clientlabel. - TLS certificate mismatch: The tracking server presents a certificate signed by an internal CA that is not present in the
ca-bundle.crtof the batch job container image. The OpenSSL handshake succeeds only whencurlis invoked with--insecure, but the MLflow Python client aborts on verification failure. - Service discovery drift: The DNS entry
mlflow-tracking.mycorp.comwas recently updated to point to a new load balancer IP. The batch job’s DNS cache (glibc) retained the stale IP for 5 minutes, causing initial connection attempts to hit a non‑listening endpoint.
Debug – Systematic Investigation Steps
1. Verify DNS Resolution
$ nslookup mlflow-tracking.mycorp.com
Server: 10.96.0.10
Address: 10.96.0.10#53
Non-authoritative answer:
Name: mlflow-tracking.mycorp.com
Address: 10.2.45.12
Compare with the expected load balancer IP from the service manifest:
apiVersion: v1
kind: Service
metadata:
name: mlflow-tracking
spec:
type: LoadBalancer
loadBalancerIP: 10.2.45.12
2. Test TCP Connectivity from a Job Pod
$ kubectl exec -it batch-job-7f9c9c9c9c-abcde -- /bin/sh
/ # ss -tn src :0 dst 10.2.45.12:443
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 10.244.2.15:54231 10.2.45.12:443
If the state is SYN‑SENT or no entry appears, the network path is blocked.
3. Capture TLS Handshake with OpenSSL
$ openssl s_client -connect mlflow-tracking.mycorp.com:443 -servername mlflow-tracking.mycorp.com
CONNECTED(00000003)
depth=0 OU = MyCorp, CN = mlflow-tracking.mycorp.com
verify error:num=20:unable to get local issuer certificate
verify return:1
...
The error confirms the client cannot verify the server’s certificate chain.
4. Inspect NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-mlflow
spec:
podSelector:
matchLabels:
app: batch-job
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 10.2.45.12/32
ports:
- protocol: TCP
port: 443
The policy is missing the required mlflow-client label, causing egress denial.
5. Review MLflow Client Configuration
import mlflow
print(mlflow.get_tracking_uri())
# Output: https://mlflow-tracking.mycorp.com
The URI is correct, but the client environment variable MLFLOW_TRACKING_INSECURE_TLS is not set, so verification is enforced.
Solution – Restoring Reliable Connectivity
1. Update NetworkPolicy to Allow Egress
Add the mlflow-client label to batch job pods and adjust the policy to match that label.
# Before
spec:
podSelector:
matchLabels:
app: batch-job
# After
spec:
podSelector:
matchLabels:
app: batch-job
role: mlflow-client
Apply the revised policy:
kubectl apply -f networkpolicy-mlflow.yaml
2. Propagate Internal CA Certificate to Job Image
Copy the corporate CA bundle into the container and update the trust store.
# Dockerfile snippet (before)
FROM python:3.11-slim
# ... other layers ...
# Dockerfile snippet (after)
FROM python:3.11-slim
COPY ca-bundle.crt /usr/local/share/ca-certificates/internal-ca.crt
RUN update-ca-certificates
# ... other layers ...
3. Enable TLS Verification Bypass as a Temporary Measure (not recommended for long term)
export MLFLOW_TRACKING_INSECURE_TLS=true
Only use this while rolling out the CA update to avoid downtime.
4. Flush DNS Cache in Long‑Running Pods
Set ndots and options timeout:1 attempts:3 in the kube-dns ConfigMap, then restart pods, or use kubectl exec to clear the glibc cache:
# Inside pod
$ echo 2 > /proc/sys/vm/drop_caches
5. Redeploy Batch Jobs with Updated Image
kubectl set image deployment/batch-job batch-job=myrepo/batch-job:2.1.0
Verify – Confirming the Fix
Successful Log Write
import mlflow
mlflow.set_tracking_uri("https://mlflow-tracking.mycorp.com")
with mlflow.start_run() as run:
mlflow.log_param("alpha", 0.01)
mlflow.log_metric("accuracy", 0.93)
print("Run ID:", run.info.run_id)
Expected console output:
Run ID: 3f2c9d8e5a1e4b7b9c6d2a1e7f4b8c9d
Check the tracking server UI for the new run.
NetworkPolicy Effectiveness
$ kubectl exec -it batch-job-7f9c9c9c9c-abcde -- ss -tn dst :443
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 10.244.2.15:54231 10.2.45.12:443
TLS Verification Pass
$ openssl s_client -connect mlflow-tracking.mycorp.com:443 -servername mlflow-tracking.mycorp.com
CONNECTED(00000003)
depth=0 OU = MyCorp, CN = mlflow-tracking.mycorp.com
Verify return code: 0 (ok)
Prevent – Operational Guardrails
- Automated Certificate Distribution: Use a sidecar init container that injects the latest
ca-bundle.crtfrom a ConfigMap mounted as a secret. - NetworkPolicy Auditing: Run
kubectl netpol lintin CI to ensure egress rules for external services are declared. - Health Checks: Add a liveness probe that performs a lightweight
curl -f https://mlflow-tracking.mycorp.com/api/2.0/mlflow/experiments/listcall. - DNS TTL Management: Set a low TTL (30 s) for the MLflow service DNS record to reduce stale cache impact during IP changes.
- Observability: Emit a custom metric
mlflow_tracking_api_success_totalfrom the batch job; alert on a drop below a threshold.
Related Topic Hub: Model Serving Troubleshooting Hub
FAQ
- Why does the batch job see “Connection refused” while an interactive pod can reach the MLflow server?
The interactive pod likely has the
mlflow-clientlabel that satisfies the NetworkPolicy, whereas the batch job pod does not. Adjust the pod selector or add the label. - Can I use HTTP instead of HTTPS to avoid TLS issues?
MLflow tracking server can be configured for plain HTTP, but this disables encryption and is not recommended for production. Proper CA distribution is the safer fix.
- How do I know if the DNS cache is stale inside a long‑running pod?
Run
dig +short mlflow-tracking.mycorp.cominside the pod and compare the result with the current Service IP. If they differ, restart the pod or clear the glibc cache. - Is setting
MLFLOW_TRACKING_INSECURE_TLS=truea permanent solution?No. It disables certificate verification and exposes traffic to man‑in‑the‑middle attacks. Use it only as a temporary bridge while fixing the trust store.
- What monitoring alerts should I create for this failure mode?
Alert on any of the following:
- Increase in
mlflow_tracking_api_error_totalmetric. - Pod restarts of batch jobs due to uncaught
MlflowException. - NetworkPolicy audit failures for egress to
mlflow-tracking.mycorp.com:443.
- Increase in