gRPC Reliability on Linkerd: Retry Budgets, Circuit Breaking, and Canary Releases
Oct 2026
TL;DR
- Linkerd puts gRPC reliability controls in Kubernetes resources and annotations, so you can configure them without changing service code.
- Retry budgets cap the extra traffic that failed RPCs generate during an outage. Use them for calls that are safe to retry.
- Circuit breaking ejects failing endpoints, while timeouts limit how long an RPC can wait.
- Weighted gRPC routes let you send a fraction of new calls to a canary and roll back if errors rise.
- Connection draining gives active streams time to finish during a rollout. This guide covers failure handling and rollout safety, not load-balancing performance.
Why gRPC breaks reliability patterns built for HTTP/1.1
gRPC carries individual calls over HTTP/2 connections that can stay open across many RPCs. A connection can look healthy while individual calls fail. Many failed RPCs still return HTTP 200, with the gRPC error reported in the response trailers, so a load balancer watching only connection health or HTTP status can miss the problem.
Independent client retries can pile more work onto a service during a partial outage. Say 100 RPCs arrive and 50 fail. If each failed call gets 2 more attempts and those attempts fail too, the service handles 200 attempts for 100 original calls. Clients written in different languages may each apply their own retry policy, with no common limit on the added load.
Linkerd can enforce a retry budget in its proxies, giving you one place to control the retries the mesh makes without changing application code. You still need to account for retries made by application clients, since those sit outside the mesh budget. And you must choose retryable methods carefully. Repeating an RPC that already changed data can repeat the change, while a stream that has already delivered messages can’t simply start over without application support.
Retry budgets (ServiceProfile) and per-route retry limits (GRPCRoute)
A retry budget caps how many extra requests Linkerd can send after failures. Instead of letting each gRPC client retry independently during a partial outage, you mark safe RPCs as retryable and give the client-side proxy a budget.
This ServiceProfile permits retries for a read-only GetBook RPC. Its budget allows retries worth 20% of the original request volume, plus a minimum allowance of 10 retries per second. Unused allowance expires after 10 seconds.
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
name: books.default.svc.cluster.local
namespace: default
spec:
routes:
- name: POST /books.BookService/GetBook
condition:
method: POST
pathRegex: /books.BookService/GetBook
isRetryable: true
retryBudget:
retryRatio: 0.2
minRetriesPerSecond: 10
ttl: 10sLinkerd tracks this budget at the client-side proxy, so the 10-retry minimum isn't a cluster-wide cap. Set that floor with your number of client proxies in mind. Keep write RPCs out of the retryable routes unless repeating them is safe. You don't need to change application code to apply the ServiceProfile.
GRPCRoute per-route limit
A GRPCRoute can use retry.linkerd.io/grpc to select retryable gRPC status codes and retry.linkerd.io/limit to cap attempts on that route. For example, retry.linkerd.io/grpc=unavailable with retry.linkerd.io/limit=2 sets a fixed per-route limit, not a volume-based budget. If a ServiceProfile exists for the Service, Linkerd uses it and ignores the GRPCRoute, so choose one resource per service before configuring retries.
Best for: Partial outages where independent client retries would pile more requests onto an already struggling gRPC service.
Circuit breaking and timeouts as Kubernetes-native config
For gRPC, circuit breaking means Linkerd temporarily stops sending new RPCs to a pod after that endpoint fails repeatedly. Linkerd configures this failure accrual through annotations on a Kubernetes Service. You don't need a separate CircuitBreaker resource.
apiVersion: v1
kind: Service
metadata:
name: greeter
namespace: demo
annotations:
balancer.linkerd.io/failure-accrual: "consecutive"
balancer.linkerd.io/failure-accrual-consecutive-max-failures: "5"
spec:
selector:
app: greeter
ports:
- name: grpc
port: 50051
targetPort: 50051After 5 consecutive failures from an endpoint, Linkerd temporarily ejects it from the choices for new requests. That gives an unhealthy pod time to recover instead of sending it more RPCs that may fail or wait until their deadlines expire. Existing streams stay on their established connections.
Timeouts set a separate boundary on how long an RPC can run. For unary calls, you can attach Linkerd's request-timeout annotation to a Gateway API GRPCRoute that matches one gRPC method.
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
name: greeter-say-hello
namespace: demo
annotations:
timeout.linkerd.io/request: "5s"
spec:
parentRefs:
- group: ""
kind: Service
name: greeter
port: 50051
rules:
- matches:
- method:
service: demo.Greeter
method: SayHello
backendRefs:
- name: greeter
port: 50051The route applies the 5-second limit to demo.Greeter/SayHello. Set timeouts for streaming methods separately, since a request-wide limit can end a healthy long-running stream.
Best for: Use failure accrual when individual pods repeatedly fail, and use timeouts when callers need an upper bound on RPC duration. Pair them with retry budgets if you retry failed calls. The circuit breaker removes unhealthy endpoints from selection, while the budget limits the extra traffic retries create.
Canary releases and progressive delivery for gRPC
Weighted-route canarying sends a small share of new gRPC calls to a candidate release while the stable release handles the rest. In Linkerd, a GRPCRoute can attach to the Service your clients already call and split traffic between 2 backend Services. You don’t need to change client code.
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
name: payments-canary
namespace: payments
spec:
parentRefs:
- group: ""
kind: Service
name: payments
port: 8080
rules:
- backendRefs:
- name: payments-stable
port: 8080
weight: 95
- name: payments-canary
port: 8080
weight: 5Clients call payments on port 8080. Linkerd routes roughly 5% of new matching RPCs to payments-canary. The weights describe RPC routing, so you shouldn’t read them as a guarantee that exactly 5% of users or active streams will reach the canary. Use GRPCRoute for new traffic splits. SMI TrafficSplit is deprecated.
Automatic rollback needs a rollout controller and a failure rule. Linkerd supplies per-route success-rate and latency measurements, but the GRPCRoute above won’t change its own weights when errors rise. A controller must watch a defined signal, such as the canary’s gRPC error rate crossing a threshold, and set the canary weight back to 0. Flagger’s Gateway API support currently targets HTTPRoute, so don’t assume its automated analysis can drive this GRPCRoute. Argo Rollouts' Gateway API plugin supports GRPCRoute (and HTTPRoute, TCPRoute); Flagger's does not.
Blue/green uses the same routing idea with a different release decision. You prepare a complete green deployment, then switch new RPCs from blue to green, commonly by changing route weights. A canary gives you an observation period at 5% before you raise that share. Blue/green gives you a faster cutover when you’ve already validated green through other means.
Long-lived server-streaming and bidirectional RPCs make either rollback less immediate than a route edit might suggest. Setting the canary weight to 0 steers new RPCs away, but an active stream stays on the backend that accepted it. Keep the old release available long enough for streams to finish, and set a drain policy that fits your stream duration.
Best for: Choose a canary when you want to measure errors and latency on a limited share of new gRPC calls before widening a release. Choose blue/green when you can validate the new release before a full cutover and can account for streams still running on the old one.
Keeping streams alive through retries, circuit breaks, and rollouts
A server-streaming or bidirectional gRPC call can stay open well past a rollout’s usual shutdown window. If Kubernetes kills the old pod before the call finishes, the client loses the stream. A retry may start a new call, but it can’t restore messages already exchanged or resume the old call’s state.
Linkerd can drain connections during pod shutdown. Once Kubernetes removes a terminating pod from service endpoints, new streams can go to other ready pods while existing streams finish on the old one. Linkerd can’t move an active stream between pods. You still need enough terminationGracePeriodSeconds for the application and proxy to shut down, and your application must let active calls finish within that window.
Apply retry limits, circuit breakers, and timeouts with the stream’s lifetime in mind. A circuit breaker can keep new calls away from an unhealthy endpoint, but it can’t repair a stream already running there. A deadline shorter than a valid stream will end that call even when the server works correctly. For streams that can outlast any practical shutdown window, give clients a way to reconnect and resume from a known position.
Best for: Token-generation responses and event feeds, where an interrupted stream can lose output or leave the client unsure which messages it received.
How Linkerd compares to Istio and Cilium on gRPC reliability
Istio’s sidecar mode gives you substantial control over retries and unhealthy backends. Its configuration spans VirtualService and DestinationRule, so check both when you’re tracing why a gRPC call retried or an endpoint stopped receiving traffic. In ambient mode without a waypoint, the L4 proxy balances connections rather than individual RPCs. A long-lived gRPC connection can therefore stay on one backend.
Linkerd keeps retry budgets in mesh configuration and reports per-route latency and success rates. Cilium can inspect gRPC traffic at L7, but its HTTP-rule model and weaker per-method observability leave fewer controls for a rollout judged on individual RPC methods. If you rely on those signals for canary decisions, verify them in your Cilium version before choosing it.
Conclusion
Platform teams running gRPC at scale should own reliability policy in Kubernetes configuration. Linkerd lets you apply shared controls across services without asking each service owner to build the same failure handling in application code.
Configured together, those controls help contain partial failures and give you a safer way to release changes. Your platform team can set and test the policies once, then apply them consistently across gRPC services.
FAQs
Do Linkerd retry budgets require app code changes?
No. You configure a ServiceProfile in Kubernetes and mark eligible gRPC methods as retryable. Check that those methods are safe to repeat, since a retry can run the same operation twice.
What’s the difference between a retry budget and a circuit breaker?
A retry budget caps the extra requests Linkerd sends when RPCs fail. Circuit breaking temporarily removes an endpoint that keeps failing, so Linkerd can direct new requests elsewhere.
Can you canary gRPC streaming endpoints?
Yes. A weighted GRPCRoute can send new streams to a canary backend, but a route change won’t move an active stream. Give existing streams time to finish before shutting down the old pods.
Does Gateway API GRPCRoute replace ServiceProfile?
GRPCRoute handles routing and can set a per-route retry limit, but that limit isn’t a retry budget. When both exist for the same service, the ServiceProfile takes precedence and the GRPCRoute is ignored, so choose the resource that fits the policy you need.
