Intro
Kubernetes Lease architecture is a foundational but often overlooked mechanism that coordinates distributed components without tight coupling. A Lease is a lightweight object in the coordination.k8s.io/v1 API group that grants a holder exclusive or time-bound rights over a shared resource. In practice, leases power critical functions such as controller leader election, node heartbeats, and custom distributed locks.
This guide is for developers, DevOps consultants, and technical startup teams who need to operate Kubernetes reliably. We will move beyond theory to show you exactly how leases are structured, how they flow through the system, and how to troubleshoot them with concrete commands and expected outputs. The goal is operational safety: observe before changing, limit blast radius, avoid secrets in commands, verify every result, and document recovery paths.
We will cover Version and Environment Inventory, Safe Configuration Path, Verification and Diagnostics, Failure Modes and Recovery, and an Operations Checklist. Each section includes practical kubectl commands, examples, and signals to watch for.
Version and Environment Inventory
Before touching any lease-related configuration, you must know your cluster version, the lease implementation in use, and the components that depend on it. Leases are enabled by default in Kubernetes 1.14+ via the coordination.k8s.io API group. However, leader election behavior can change between versions, so always check.
Quick inventory commands:
kubectl version --short
kubectl api-versions | grep coordination
kubectl get --raw /apis/coordination.k8s.io/v1 | jq .
Expected output includes coordination.k8s.io/v1 and a list of resources like leases. If you see only coordination.k8s.io/v1beta1, you are on an older version (pre-1.19) and should plan an upgrade.
Inspect existing leases across namespaces:
kubectl get leases --all-namespaces
Example output:
NAMESPACE NAME HOLDER AGE
kube-system kube-controller-manager ip-10-0-0-10.ec2.internal_12345678-1234-... 10d
kube-system kube-scheduler ip-10-0-0-11.ec2.internal_87654321-4321-... 10d
kube-node-lease ip-10-0-0-20 ip-10-0-0-20 5m
This tells you which components use leases and who currently holds them. Note the lease names: kube-controller-manager, kube-scheduler, and per-node leases in the kube-node-lease namespace. The holder identity is typically a pod name or node name with a unique UUID suffix for leader election.
Check prerequisites for your intended change. For example, if you want to add a custom lease for your application, you need RBAC permissions to create leases in your namespace. Verify with:
kubectl auth can-i create leases -n your-namespace
If the result is no, you need to create a Role and RoleBinding. Keep the local test small: apply one scoped manifest and inspect its effect.
Safe Configuration Path
A lease object is simple, but misconfiguring its duration, renew time, or identity can cause split-brain or liveness issues. The spec has four fields:
holderIdentity: string that identifies the current holder (e.g., pod name).leaseDurationSeconds: how long a holder can keep the lease without renewing.acquireTime: timestamp when the lease was acquired.renewTime: timestamp when the holder last renewed.leaseTransitions: number of times the lease changed holders.
Here is a minimal manifest for a custom leader election lease:
apiVersion: coordination.k8s.io/v1
kind: Lease
metadata:
name: my-app-leader
namespace: default
spec:
holderIdentity: "my-app-pod-0"
leaseDurationSeconds: 15
acquireTime: "2025-03-01T10:00:00Z"
renewTime: "2025-03-01T10:00:00Z"
leaseTransitions: 0
Apply it with kubectl apply -f lease.yaml and verify creation:
kubectl get lease my-app-leader -o yaml
The holderIdentity should match your pod name. In production, you would not set these manually; your application code would use a leader election library (e.g., client-go's leaderelection package) to create and renew the lease.
Key configuration best practices:
leaseDurationSecondsshould be 2-3 times the renew interval. If renew interval is 5 seconds, set duration to 15 seconds.- Use unique identities: pod names are good, but add a UUID if pods can be rescheduled with the same name.
- Never hardcode secrets in lease annotations or holderIdentity.
Safe change approach:
Start read-only:
kubectl get lease my-app-leader -o json | jq .spec
Capture the current state and timestamps. Then make one small change, such as adjusting leaseDurationSeconds from 15 to 20, and apply. Monitor the renew time to ensure the holder keeps renewing.
For local testing, you can use kubectl port-forward to access a service that exposes leader status. But first, verify the lease object itself is being updated.
Verification and Diagnostics
Verifying lease behavior requires observing both the object state and the application logs. Start with the lease:
kubectl get lease my-app-leader -o jsonpath='{.spec.holderIdentity}{"\n"}'
Expected output: my-app-pod-0. If empty, no leader is elected.
Check renewal timestamps:
kubectl get lease my-app-leader -o jsonpath='{.spec.renewTime}{"\n"}'
Compare with current time. If renewTime is older than leaseDurationSeconds, the lease has expired, and the leader should step down.
Diagnosing controller leader election:
For built-in controllers like kube-controller-manager, you can check logs for election events:
kubectl logs -n kube-system kube-controller-manager-ip-10-0-0-10 | grep -i "leader"
Look for lines like:
I0301 10:00:00.123456 1 leaderelection.go:258] successfully acquired lease kube-system/kube-controller-manager
If you see repeated failures to acquire, inspect the lease object:
kubectl get lease -n kube-system kube-controller-manager -o yaml
Check if the holder identity is a pod that no longer exists. If so, the new pod may be unable to take over if the old lease hasn't expired. You can delete the lease to force re-election (with caution, as it briefly disrupts the controller).
Use kubectl describe for event context:
kubectl describe lease my-app-leader
Although leases have limited events, the describe output shows the current spec and status. For pods using leases, kubectl describe pod may show related events, such as LeaderElection or FailedToUpdateLease.
Check logs of your application for lease updates:
If you are using client-go leader election, enable verbose logging to see renewal attempts. For example, with klog level 4:
I0301 10:00:05.000000 1 leaderelection.go:268] successfully renewed lease default/my-app-leader
If you see failed to renew lease messages, check network connectivity to the API server and clock skew between nodes.
Failure Modes and Recovery
Leases can fail in several ways, leading to no leader, multiple leaders, or stale holders. We cover the most common scenarios and how to recover.
1. Lease expired and not renewed
Cause: The leader pod crashed or lost connectivity, and the lease duration passed.
Symptom: renewTime is older than leaseDurationSeconds; a new pod fails to acquire because it waits for expiry.
Recovery:
# Check if leader pod is running
kubectl get pods -l app=my-app
# If not, delete the stale lease to allow immediate acquisition
kubectl delete lease my-app-leader
Then verify the new pod becomes holder:
kubectl get lease my-app-leader -o jsonpath='{.spec.holderIdentity}{"\n"}'
2. Split-brain due to clock skew
Cause: Nodes have significant time differences, causing one node to think the lease expired while another thinks it is still valid.
Symptom: Two pods both believe they are leader, leading to conflicting actions.
Recovery:
- Synchronize clocks with NTP on all nodes.
- Increase
leaseDurationSecondsto tolerate skew, but not too much. - Use a fencing mechanism if your application requires strict exclusivity.
3. Lease object deleted accidentally
Cause: kubectl delete lease without a backup, or a namespace cleanup removed it.
Symptom: All contenders fail to find the lease and may error out.
Recovery: Recreate the lease. If your application uses client-go, it will create the lease automatically if it doesn't exist, though it may waste an election cycle. To speed up, create an empty lease with the correct name and namespace:
apiVersion: coordination.k8s.io/v1
kind: Lease
metadata:
name: my-app-leader
namespace: default
spec: {}
Then observe that the leader updates it.
4. Permission denied errors when updating lease
Cause: RBAC policies changed, or the service account lacks update permission on leases.
Symptom: Logs show 403 Forbidden when trying to update the lease.
Recovery: Check and fix RBAC:
kubectl auth can-i update leases -n default --as=system:serviceaccount:default:my-app-sa
# If no, create a Role with update permission and bind it
kubectl create role lease-updater --verb=update,get,list --resource=leases -n default
kubectl create rolebinding lease-updater-binding --role=lease-updater --serviceaccount=default:my-app-sa
5. Lease transitions spike
Cause: Frequent leader changes due to flapping or premature expiration.
Symptom: leaseTransitions counter increases rapidly.
Diagnosis:
kubectl get lease my-app-leader -o jsonpath='{.spec.leaseTransitions}{"\n"}'
Check logs for acquisition and loss events. Consider increasing leaseDurationSeconds or investigating network issues.
Recovery verification: after any intervention, confirm the lease is stable:
watch -n 5 "kubectl get lease my-app-leader -o jsonpath='{.spec.holderIdentity}{"\n"} {@.spec.renewTime}{"\n"}'"
Ensure the same holder persists and renewTime updates regularly.
Operations Checklist
Use this checklist for routine lease operations and incident response.
| Step | Action | Expected Output |
|---|---|---|
| 1. Environment check | kubectl version --short and kubectl api-versions | grep coordination | coordination.k8s.io/v1 present |
| 2. List leases | kubectl get leases -A | Names, holders, ages |
| 3. Inspect a specific lease | kubectl get lease <name> -o yaml | Spec with holder, durations, times |
| 4. Verify holder is alive | kubectl get pods -l <selector> | Pod status Running |
| 5. Check renewal freshness | kubectl get lease <name> -o jsonpath='{.spec.renewTime}' | Timestamp within lease duration |
| 6. Test permissions | kubectl auth can-i update leases -n <ns> --as=<sa> | yes |
| 7. Simulate failure | kubectl delete lease <name> (in test env) | New holder acquires after deletion |
| 8. Monitor transitions | watch "kubectl get lease <name> -o jsonpath='{.spec.leaseTransitions}'" | Stable number 0-1 |
| 9. Document recovery | For each failure mode, record exact commands run and their outputs | Runbook updated |
| 10. Review RBAC | kubectl get role,rolebinding -n <ns> | grep lease | Minimum required permissions |
Real-world example: A startup's deployment had a leader election bug causing frequent pod restarts. By checking kubectl get lease -n app and noticing leaseTransitions: 42 in one hour, they identified misconfigured leaseDurationSeconds (5 seconds) and increased it to 15 seconds. The transitions stabilized to 1 per day.
Checklist for changes:
- Capture current lease spec before modification.
- Make one change at a time.
- Use
kubectl applywith a versioned manifest, notkubectl editfor production. - Verify the change with the commands above.
- Have a rollback plan: either reapply previous manifest or delete the lease to force re-election.
Conclusion
Kubernetes Lease architecture is a silent workhorse. It enables leader election for controllers, node heartbeats for the node lifecycle controller, and custom coordination for your applications. By understanding its fields, observing its behavior, and following safe operational practices, you can prevent split-brain scenarios and ensure high availability.
Start with one low-risk verification: list all leases in your cluster, identify the holders, and confirm they are renewing. Then, if you run custom leader election, review the lease duration relative to your renew interval. Use the commands from this guide to document your current state and test changes in a non-production namespace first.
A reliable workflow makes failure visible: capture timestamps, protect identities, limit changes, and define recovery verification before an incident forces your hand. With leases, a few minutes of proactive inspection can save hours of debugging distributed coordination issues.