Troubleshooting¶
Start with the exact resource status, conditions, Events, Pod logs, and Admin endpoint reachability. Avoid deleting Pods or PVCs as a first response: an identity-bearing workload may be in a durable handoff.
Fast triage¶
kubectl describe garagecluster garage -n storage
kubectl get garagecluster garage -n storage -o yaml
kubectl get garagenode -n storage -o wide
kubectl get pods,pvc,events -n storage --sort-by=.metadata.creationTimestamp
kubectl logs -n garage-operator-system \
deployment/garage-operator-controller-manager --tail=200
Cluster is not Ready¶
Check the most specific condition:
| Condition / signal | Likely cause | Action |
|---|---|---|
StorageTopologyReady=False |
Member joining, draining, or waiting for an older layout | Wait for the named member/layout version; do not start another topology edit |
StorageScaleDownBlocked=True |
Scale-down would violate replication.factor |
Add capacity or lower factor through the documented migration, not a forced delete |
StorageRolloutReady=False |
One identity-bearing Pod is in an OnDelete handoff | Inspect status.storageRollout and exact Pod/PVC UIDs |
QuorumAtRisk=True |
One or more partitions lack write quorum | Restore reachable storage nodes or follow Garage recovery; do not edit layout casually |
PublicEndpointReady=False |
RPC endpoint cannot be derived or is invalid | Configure per-node routable endpoint or correct the Service |
ManagementHandleReady=False |
External Admin API is unreachable | Check URL, token Secret, NetworkPolicy, DNS, and TLS proxy |
GatewayConnected=False |
Edge/gateway RPC connection is not established | Check both directions and every gateway endpoint |
Bucket or key is stuck¶
kubectl get garagebucket,garagekey -n storage -o yaml
kubectl describe garagebucket app-data -n storage
kubectl describe garagekey app-key -n storage
Confirm the referenced cluster is Ready, the Admin token works, and any cross-namespace GarageReferenceGrant exists in the destination namespace. A lifecycle rule on Garage <2.3.0 can be accepted but remains LifecycleConfigured=False when the Admin API cannot persist it.
GarageBucket is stuck deleting¶
Inspect the phase, condition message, retry counter, and bucket contents:
kubectl get garagebucket app-data -n storage -o yaml
kubectl describe garagebucket app-data -n storage
With the default deletionPolicy: Delete, Garage requires completed S3 objects
and live K2V entries to be removed before it accepts DeleteBucket. Lifecycle
rules may eventually empty the bucket, so a retrying finalizer can complete
later. If the data must be preserved, change the resource to
deletionPolicy: Retain while it is terminating; the operator will release its
finalizer without contacting Garage. Do not remove the finalizer manually
unless abandoning remote cleanup is intentional.
For a retained bucket, save status.bucketId before deletion so the bucket can
later be re-adopted with spec.bucketId. Retain does not protect Garage's
underlying workloads or PVCs.
Bucket/key/token stuck Pending with a ClusterNotReady condition mentioning DNS¶
The GarageCluster itself can be Running (its pods are reachable directly by
IP) while GarageBucket/GarageKey/GarageAdminToken resources stay
Pending forever, because those controllers reach the cluster through its
<name>.<namespace>.svc.<cluster-domain> Service DNS name, not a pod IP. On a
cluster with a non-default Kubernetes DNS domain (e.g. a Talos cluster with
cluster.network.dnsDomain set), the operator's internal admin-API calls use
the wrong suffix and every lookup fails with no such host.
kubectl get garagekey app-key -n storage \
-o jsonpath='{.status.conditions[?(@.type=="Ready")].message}{"\n"}'
If the message contains no such host naming a .svc.cluster.local address
that doesn't match your cluster's real DNS domain, set the operator's cluster
domain to match:
- Helm:
--set clusterDomain=<your-domain>(see the Helm reference). - Raw manifest (
install.yaml/kustomize): uncomment and set theCLUSTER_DOMAINenv var on themanagercontainer inconfig/manager/manager.yaml, or patch the running Deployment directly.
The operator's admin-API flag/env var is --cluster-domain / CLUSTER_DOMAIN
(default cluster.local); a restart is required after changing it since it is
read once at startup.
403 No such key from S3¶
This often means a gateway identity lost its capacity-less layout role, not that the S3 key Secret is wrong.
The garage.rajsingh.info/tier selector below targets an operator-managed
label emitted on generated workloads. Treat it as a read-only diagnostic
selector; do not edit the label to change gateway behavior.
kubectl get garagecluster garage -n storage \
-o jsonpath='{.status.gatewayNodesNotInLayout}{"\n"}'
kubectl get garagenode -n storage \
-l garage.rajsingh.info/tier=gateway -o yaml
Resolve the named gateway role/layout condition first. Forcing arbitrary layout changes can make the problem worse.
Federation is unhealthy¶
Check that:
- every site uses the same RPC Secret bytes;
- every storage and gateway identity has its own routable address;
- remote Admin endpoints resolve and accept the configured tokens;
storageRpcEndpointTemplateandgatewayRpcEndpointTemplatematch actual ordinals;- no network policy or firewall blocks reverse RPC;
- only one layout writer is changing topology.
RemoteClustersHealthy ignores short blips; PeerUnreachable indicates sustained unreachability and needs direct endpoint testing.
Node-local pool is blocked¶
Inspect the parent condition and generated nodes:
The garage.rajsingh.info/cluster selector below uses an operator-managed
label on generated resources. It is a read-only diagnostic selector, not a
supported way to assign a node to a cluster.
kubectl get garagecluster garage-mixed -n garage -o yaml
kubectl get garagenode -n garage \
-l garage.rajsingh.info/cluster=garage-mixed -o wide
kubectl get daemonset,pods -n garage
Common reasons are an API server below 1.27, scheduling-gate capability proof not yet complete, a namespace Pod Security policy that rejects HostPath, overlapping selectors, a HostPath marker missing, a Node selected by two pools, or the shared Garage 256-role limit. Correct the prerequisite and let the operator resume; do not manually activate the DaemonSet Pod.
A rollout is stuck¶
Read the exact actor in status.storageRollout, including workload UID, prior Pod UID, PVC UIDs, and Garage node ID. Correct only workload conditions such as scheduling, image pull, or volume attachment. Then request a retry with a new nonce:
kubectl annotate garagecluster garage -n storage \
garage.rajsingh.info/recover-storage-rollout="$(date +%s)"
If the identity or claim evidence is inconsistent, stop and preserve the resources. Manual Pod/PVC deletion can turn a recoverable rollout into an identity replacement.
Use dangerous recovery annotations carefully¶
skip-dead-nodes and allow-missing-data can unblock a layout only when an identity is permanently gone and the data-loss consequences are understood. purge-blocks is irreversible. purge-cluster-layout is a disruptive factor migration. Read the operations reference and record an incident decision before using them.