Situation
While setting up Crossplane on a local kind Kubernetes cluster for the GCP GitOps project, the initial Helm installation failed:
Error: INSTALLATION FAILED: context deadline exceeded
The Crossplane Helm repository was working correctly and the Crossplane image was successfully pulled, so the failure appeared to be occurring after Kubernetes created the Crossplane pods.
Task
Determine why Crossplane could not become ready and restore the cluster to a healthy state without unnecessarily recreating the Kubernetes cluster or blindly reinstalling Crossplane.
Action
1. Inspected Crossplane pods
kubectl get pods -n crossplane-system
Both Crossplane components were stuck in their init containers:
crossplane 0/1 Init:Error
crossplane-rbac-manager 0/1 Init:Error
2. Inspected Kubernetes events
kubectl get events -n crossplane-system --sort-by=.lastTimestamp
The Crossplane image had been pulled successfully, but both init containers repeatedly crashed with:
Back-off restarting failed container crossplane-init
This ruled out an image-pull problem.
3. Inspected init-container logs
kubectl logs -n crossplane-system \
crossplane-... \
-c crossplane-init
The critical error was:
failed to get server groups:
Get "https://10.96.0.1:443/api":
dial tcp 10.96.0.1:443: i/o timeout
The RBAC manager showed the same failure.
This indicated that Crossplane itself was unable to communicate with the Kubernetes API through the cluster's Kubernetes Service.
4. Investigated Kubernetes Service networking
Checked the Kubernetes Service:
kubectl get svc kubernetes
Result:
kubernetes ClusterIP 10.96.0.1 443/TCP
Checked its endpoint:
kubectl get endpoints kubernetes
Result:
kubernetes 172.18.0.4:6443
The API server was running and had a valid endpoint.
5. Investigated kube-proxy
kubectl get pods -n kube-system
Found:
kube-proxy-hdxsw 0/1 Error
Further inspection showed:
kubectl logs -n kube-system -l k8s-app=kube-proxy --tail=100
Critical error:
command failed
err="failed complete: too many open files"
This explained why Kubernetes Service networking was failing.
6. Independently verified the networking failure
A pod was used to access the Kubernetes API:
kubectl run api-test \
--image=curlimages/curl \
--restart=Never \
--rm -it \
-- curl -k --connect-timeout 5 \
https://kubernetes.default.svc/api
Before fixing kube-proxy:
curl: (28) Resolving timed out after 5005 milliseconds
This confirmed that the problem was not specific to Crossplane.
7. Investigated host resource limits
Checked the host's inotify limits:
cat /proc/sys/fs/inotify/max_user_watches
cat /proc/sys/fs/inotify/max_user_instances
Initial values:
65536
128
Kind documents "too many open files" as potentially being caused by exhausted inotify resources and recommends increasing these limits.
The system-wide file descriptor table was not exhausted:
cat /proc/sys/fs/file-nr
showed:
12726 0 9223372036854775807
Therefore, the issue was investigated as an inotify/file-watch resource-limit problem rather than general system-wide file descriptor exhaustion.
8. Increased inotify limits
sudo sysctl fs.inotify.max_user_watches=524288
sudo sysctl fs.inotify.max_user_instances=512
Verified:
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 512
9. Restarted kube-proxy
kubectl delete pod -n kube-system -l k8s-app=kube-proxy
The replacement pod started successfully:
kube-proxy-z4dkd 1/1 Running
Its logs no longer contained the previous too many open files failure.
10. Re-tested Kubernetes API connectivity
Ran the same API test again:
kubectl run api-test \
--image=curlimages/curl \
--restart=Never \
--rm -it \
-- curl -k --connect-timeout 5 \
https://kubernetes.default.svc/api
This time the request reached the Kubernetes API and returned:
403 Forbidden
User "system:anonymous" cannot get path "/api"
The 403 was expected because the test pod was making an unauthenticated request. The important change was:
Before: i/o timeout
After: HTTP 403
This proved that Kubernetes Service networking had been restored.
11. Restarted Crossplane
The original Crossplane pods had been created while kube-proxy was broken, so they were recreated:
kubectl delete pod -n crossplane-system \
crossplane-595f7997d8-rjfxn \
crossplane-rbac-manager-584bbb6c67-pcdrr
The replacement pods became healthy:
crossplane 1/1 Running
crossplane-rbac-manager 1/1 Running
12. Verified Crossplane CRDs
kubectl get crds | grep crossplane
Multiple Crossplane CRDs were successfully installed, including:
compositeresourcedefinitions.apiextensions.crossplane.io
compositions.apiextensions.crossplane.io
managedresourcedefinitions.apiextensions.crossplane.io
providerrevisions.pkg.crossplane.io
providers.pkg.crossplane.io
13. Reconciled Helm's release state
The original Helm installation had timed out while waiting for Crossplane, so Helm recorded the release as:
STATUS: failed
Even though Kubernetes resources had subsequently recovered.
The existing release was upgraded rather than reinstalled:
helm upgrade crossplane \
crossplane-stable/crossplane \
--namespace crossplane-system \
--version 2.3.4 \
--wait \
--timeout 5m
Helm then reported:
STATUS: deployed
REVISION: 2
Final Crossplane state:
crossplane 1/1 Running
crossplane-rbac-manager 1/1 Running
Result
Crossplane was successfully installed and healthy.
Final verification:
Helm release: deployed
Crossplane: 1/1 Running
RBAC manager: 1/1 Running
Crossplane CRDs: installed
Kubernetes API: reachable from pods
kube-proxy: 1/1 Running
Root Cause
The immediate failure was kube-proxy crashing with too many open files, which broke Kubernetes Service networking.
That prevented Crossplane's init containers from reaching the Kubernetes API through:
https://10.96.0.1:443
The issue was resolved by increasing the host's inotify resource limits and restarting kube-proxy.
Key Debugging Lesson
The initial error was:
Helm: context deadline exceeded
but that was only the symptom.
The actual debugging chain was:
Helm timeout
↓
Crossplane pods not ready
↓
Crossplane init container failing
↓
Kubernetes API request timing out
↓
Kubernetes Service networking broken
↓
kube-proxy CrashLoopBackOff
↓
"too many open files"
↓
inotify resource limits
↓
increase limits + restart kube-proxy
↓
Service networking restored
↓
Crossplane becomes healthy
This reinforced the importance of troubleshooting Kubernetes failures from the bottom up:
Pod status → Events → Container logs → Cluster networking → Node/system resources → Fix → Independent verification.

Top comments (0)