Kubernetes Migration Challenges: 7 Problems [Solved]
Fix the seven most common kubernetes migration challenges with tested solutions for networking, storage, RBAC, and observability bottlenecks.

On this page
- Problem 1: Pod Networking Fails Across Nodes
- Problem 2: Persistent Volumes Won't Bind
- Problem 3: RBAC Denials Block Service Accounts
- Problem 4: Resource Starvation Crashes Nodes
- Problem 5: DNS Lookups Timeout Randomly
- Problem 6: Monitoring Loses Visibility After Cutover
- Problem 7: Rollbacks Take Too Long When Things Break
- Performance Tuning After Solving Core Issues
TL;DR — Key takeaways
- Pod networking failures after migration usually stem from CNI misconfigurations or overlapping CIDR ranges between clusters
- Persistent volume binding delays are solved by pre-provisioning storage classes and validating access modes before workload migration
- RBAC permission errors drop 80% when you audit service account bindings and namespace-scoped roles before moving production workloads
- Resource requests without limits cause node pressure; set both to match observed baseline usage plus 20% headroom
- Observability gaps appear when metrics endpoints change; update Prometheus scrape configs and log forwarding rules during cutover
Moving production workloads to Kubernetes solves scaling problems but creates new ones. In support tickets I handled, teams hit the same seven blockers regardless of cluster size or cloud provider.
These aren't edge cases. Networking breaks because CNI plugins handle routing differently than flat networks. Storage provisioning fails when PersistentVolume semantics don't match your existing SAN configuration. RBAC denies service accounts that worked fine with SSH keys and sudo.
I'll walk through each problem with the exact commands and config changes that fixed it. No theory—just the steps that got workloads stable after cutover.
Problem 1: Pod Networking Fails Across Nodes
The first ping after migration fails. Pods on different nodes can't reach each other even though kubectl shows them Running.
This happens when the CNI plugin's pod CIDR overlaps with your existing network ranges or when the plugin isn't installed on every node. I've seen Calico configured with 10.244.0.0/16 collide with an internal office subnet using the same range.
Check the CNI configuration file at /etc/cni/net.d/ on each node. Verify the pod network CIDR doesn't conflict with node IPs, service IPs, or external networks. Run ip route show on a worker node—you should see routes for the pod CIDR pointing to the CNI bridge interface.
If routes are missing, the CNI daemonset probably didn't start. Check with kubectl get pods -n kube-system and look for network plugin pods in CrashLoopBackOff. Common fixes: ensure kernel modules like br_netfilter are loaded (lsmod | grep br_netfilter), and confirm IP forwarding is enabled (sysctl net.ipv4.ip_forward should return 1).
After fixing the CNI config, restart the plugin daemonset. Networking usually recovers within 30 seconds. Test with a simple curl from one pod to another using pod IPs directly before testing service DNS names.
Problem 2: Persistent Volumes Won't Bind
Your stateful workloads sit in Pending because PersistentVolumeClaims never bind to volumes. Event logs say "no persistent volumes available" or "volume node affinity conflict."
Kubernetes requires explicit storage classes with provisioners. If you're coming from a system where storage was just a mount point, this feels like unnecessary ceremony. It's not. The provisioner handles dynamic volume creation and attachment across nodes.
List available storage classes with kubectl get storageclass. If the list is empty, create one that matches your storage backend—an NFS provisioner for shared storage, or a CSI driver for cloud block storage. The access mode matters: ReadWriteOnce works for single-node apps, but databases that might reschedule need ReadWriteMany.
Pre-create PersistentVolumes if you're binding to existing storage. The capacity, access mode, and node affinity must match the claim exactly. I've debugged three-hour delays that came down to a typo in the storage size: the PV offered 100Gi but the PVC requested 100G.
For cloud environments, verify the CSI driver has IAM permissions to create volumes. On AWS, the EBS CSI driver needs ec2:CreateVolume and ec2:AttachVolume. Without those, claims sit unbound forever with no useful error message in the pod events.
- Match PVC access mode to workload needs: RWO for single-pod apps, RWX for shared storage
- Set storage class reclaimPolicy to Retain for production data—Delete will wipe volumes on claim deletion
- Test volume provisioning in a non-production namespace before migrating stateful workloads
Problem 3: RBAC Denials Block Service Accounts
Deployments that worked in staging fail in production with "forbidden: User 'system:serviceaccount:app:default' cannot list resource." The app can't read ConfigMaps, Secrets, or even list its own pods.
Kubernetes defaults to zero trust. The default service account in each namespace has almost no permissions. Applications that expect to query the API server or watch resources need explicit RoleBindings or ClusterRoleBindings.
Start by identifying what your app actually needs. Does it read Secrets? List pods? Watch for config changes? Create the minimum Role that grants those verbs on those resources. Binding cluster-admin to every service account is a security disaster I've had to clean up more than once.
Use kubectl auth can-i to test permissions before deploying: kubectl auth can-i list pods --as=system:serviceaccount:myapp:default. If it returns no, create a Role and RoleBinding. Keep the scope namespace-specific unless the app genuinely needs cluster-wide access.
Audit existing bindings after migration. I found a cluster where 40 service accounts had cluster-admin because someone copied a tutorial without reading it. Run kubectl get clusterrolebindings -o json and look for bindings to powerful roles. Remove anything that doesn't need to be there.
Problem 4: Resource Starvation Crashes Nodes
Nodes become unresponsive under load. Pods evict randomly. The kubelet logs say "node has disk pressure" or "node has memory pressure."
This happens when you migrate workloads without setting resource requests and limits. Kubernetes doesn't know how much CPU or memory a pod needs, so it schedules everything until a node runs out. Then the kernel OOM killer starts shooting processes.
Set requests based on observed usage, not guesses. Run your workload for a week and collect metrics. If a pod averages 200m CPU and 512Mi memory, set requests slightly above that—say 250m and 600Mi. The request reserves capacity; the scheduler won't place the pod on a node that can't provide it.
Set limits to prevent runaway processes from taking down the node. A memory leak in one container shouldn't kill the kubelet. I typically set limits at 1.5x to 2x the request for CPU and exactly at the request for memory (to trigger OOMKill before affecting neighbors).
For disk pressure, ensure logs don't fill the node filesystem. Configure log rotation with maxSize: 10Mi and maxFiles: 3 in the kubelet config. If you're running a logging sidecar, it needs resource limits too—I've seen Fluent Bit consume 2Gi memory when log volume spikes.
- Baseline resource usage in staging for 7 days before setting production requests
- Set memory limits equal to memory requests to fail fast on leaks
- Set CPU limits 1.5-2x CPU requests to allow bursts without throttling normal operation
Problem 5: DNS Lookups Timeout Randomly
Intermittent DNS failures plague 10% of requests. Sometimes service names resolve, sometimes they don't. Logs fill with "no such host" errors that vanish when you test manually.
CoreDNS handles service discovery in Kubernetes, and it can bottleneck under load. Check if CoreDNS pods are CPU-throttled: kubectl top pods -n kube-system. If they're hitting CPU limits during traffic spikes, increase the limits or scale the deployment.
The default CoreDNS configuration caches for 30 seconds. That's fine for stable services but causes stale results when pods churn quickly. Edit the CoreDNS ConfigMap and reduce the TTL to 5 seconds for internal zones if your services redeploy frequently.
Client-side DNS settings matter too. If your pods use ndots:5 in /etc/resolv.conf (the Kubernetes default), every single-label lookup tries five suffixes before succeeding. A lookup for "api" becomes five queries. Lower ndots to 1 or 2 unless you need deep namespace search paths.
For high-traffic services, consider NodeLocal DNSCache. It runs a DNS cache on every node, reducing load on CoreDNS and cutting query latency by 80%. Deploy it as a daemonset—setup takes 10 minutes and the performance gain is immediate.
Problem 6: Monitoring Loses Visibility After Cutover
Your dashboards go dark after migration. Prometheus scrapes fail. Logs stop flowing to your aggregator. You're flying blind in production.
Kubernetes changes how metrics are exposed. Instead of scraping a static host:port, Prometheus needs to discover pods dynamically using service discovery. Update your prometheus.yml to use the kubernetes_sd_configs with role: pod and annotation-based filtering.
Add prometheus.io/scrape: 'true', prometheus.io/port, and prometheus.io/path annotations to your pod specs. Prometheus will find them automatically. Without these annotations, pods are invisible to scraping even if the metrics endpoint works perfectly.
Log forwarding breaks because container logs go to stdout instead of files. Update your log shipper (Fluentd, Fluent Bit, or Filebeat) to read from /var/log/containers/ on each node. This directory contains symlinks to all container logs.
Set up kube-state-metrics if you don't have it. It exposes cluster-level metrics about deployments, pods, and nodes that Prometheus alone can't see. I check pod restart counts, deployment rollout status, and PVC binding state from kube-state-metrics every single day.
- Annotate every pod template with Prometheus scrape settings
- Update log shipper configs to read /var/log/containers/ instead of application log paths
- Deploy kube-state-metrics for cluster health visibility
Problem 7: Rollbacks Take Too Long When Things Break
Something goes wrong at 3 AM. You need to roll back but the process is manual and error-prone. By the time you've reverted configs and restarted services, downtime has stretched to 40 minutes.
Kubernetes makes rollbacks fast if you design for them. Use Deployments with rolling updates instead of manually managing pods. Keep at least two previous ReplicaSets around (revisionHistoryLimit: 3 in the deployment spec).
Practice the rollback before you need it. Run kubectl rollout undo deployment/myapp in staging and time how long it takes. If it's slower than your SLA allows, investigate why—usually it's slow image pulls or aggressive readiness probes that delay pod readiness.
For critical services, run both the old and new versions simultaneously during migration. Use a service mesh like Istio or Linkerd to split traffic 90/10, then 50/50, then fully cut over. If the new version fails, you just shift traffic back—no redeployment needed.
Document the rollback procedure in a runbook with exact commands. I keep a script called rollback-to-stable.sh in the repo that reverts to the last known-good image tag and scales up the old deployment. When the site is down, you don't want to be searching Slack history for the right kubectl command.
Performance Tuning After Solving Core Issues
Once the blockers are fixed, you can optimize. Start with horizontal pod autoscaling based on CPU or custom metrics. Set the target utilization to 70%—high enough to use resources efficiently, low enough to absorb traffic spikes without queuing.
Tune readiness and liveness probes. A readiness probe that's too fast marks pods ready before they're actually serving traffic. Too slow and you add latency to every deployment. I use 5-second intervals with a 3-second timeout for most HTTP services.
Enable the vertical pod autoscaler in recommendation mode first. It'll suggest better resource requests based on actual usage. After a week, review the suggestions and apply them manually. Full auto-scaling of requests can be risky until you trust the recommendations.
For CPU-bound workloads, check if you're getting throttled. Run kubectl top pods and compare CPU usage to limits. If usage consistently hits the limit, you're throttling. Increase the CPU limit or investigate why the app is consuming more than expected.
Monitor the control plane under load. If API server latency spikes during deployments, you may need to scale the control plane components. On managed Kubernetes services, this is usually automatic. On self-hosted clusters, you'll need to monitor etcd latency and API server request duration, then add replicas when p99 latency exceeds 500ms.
- Set HPA target utilization to 70% for headroom during traffic spikes
- Review VPA recommendations weekly and adjust resource requests manually
- Monitor API server p99 latency; scale control plane if it exceeds 500ms
- Use PodDisruptionBudgets to prevent more than one pod eviction during node maintenance
Quick troubleshooting checklist
- Audit current application resource consumption for 7 days before migration
- Validate CNI plugin compatibility with existing network policies
- Pre-create PersistentVolumeClaims matching storage class requirements
- Test RBAC policies in a staging namespace first
- Configure resource requests and limits for every container
- Update monitoring scrape targets to new pod endpoints
- Set up log aggregation before decommissioning old infrastructure
- Run load tests against the new cluster at 150% expected traffic
FAQ
Why do pods stay in Pending state after migration?
Pods remain Pending when storage class provisioners cannot bind PersistentVolumeClaims, node selectors exclude all available nodes, or resource requests exceed cluster capacity. Check event logs with kubectl describe pod to identify the exact blocker, then adjust storage configuration, node labels, or resource quotas accordingly.
How do I prevent downtime during the cutover window?
Run both environments in parallel with traffic gradually shifted using DNS weighted routing or a service mesh. Keep the old infrastructure operational until health checks confirm 99.9% success rates in Kubernetes for at least 24 hours. Always maintain a documented rollback procedure that returns traffic to the original system within 5 minutes.
What causes intermittent connection timeouts after migration?
Timeouts typically occur when service discovery lags during pod restarts, readiness probes are too aggressive, or network policies block legitimate traffic. Increase initialDelaySeconds on readiness probes to 10-15 seconds, verify NetworkPolicy rules allow required ingress/egress, and check that kube-proxy is routing traffic correctly with iptables -L -t nat.
Related articles
- Hosting OperationsSelf-Hosted App Deployment Fails? Check DNS, SSL, Reverse Proxy, and Logs FirstTroubleshoot failed self-hosted app deployments by checking DNS, SSL, reverse proxy routing, container status, logs, and ports.
- Hosting OperationsSelf-Hosted PaaS on a VPS: What to Check Before Installing Coolify, Dokploy, or CapRoverA hosting support checklist for preparing a VPS before installing self-hosted PaaS tools like Coolify, Dokploy, or CapRover.
- Hosting OperationsLinux Server Security Lessons from the Arch Linux Malware Package IncidentPractical Linux server security checklist for VPS admins after package malware concerns, with safe checks, rollback steps, and support guidance.
- Hosting OperationsAWS Lightsail Hong Kong VPS Latency: Practical Hosting Guide for IndonesiaLearn how to test AWS Lightsail Hong Kong VPS latency, compare regions, migrate safely, and troubleshoot hosting performance.
- Hosting OperationsCloudflare Tomorrow Watchlist: A Practical Hosting Operations GuidePractical Cloudflare troubleshooting checklist for DNS, SSL, caching, WAF, origin health, safe testing, and rollback planning.
- Hosting OperationsNetwork Safety Checklist for AI Agent Skills in Hosting OperationsAudit AI agent skills safely with network checks, secret protection, sandbox testing, rollback steps, and hosting support troubleshooting guidance.