| Crates.io | tonic-lb-k8s |
| lib.rs | tonic-lb-k8s |
| version | 0.1.0 |
| created_at | 2026-01-14 00:37:57.963972+00 |
| updated_at | 2026-01-14 00:37:57.963972+00 |
| description | Kubernetes endpoint discovery for tonic gRPC load balancing |
| homepage | |
| repository | https://github.com/ecliptical/tonic-lb-k8s |
| max_upload_size | |
| id | 2041833 |
| size | 139,720 |
Kubernetes endpoint discovery for Tonic gRPC load balancing.
When using gRPC (HTTP/2) with Kubernetes, standard ClusterIP services don't load balance effectively because HTTP/2 multiplexes all requests over a single long-lived TCP connection. Headless services expose individual pod IPs, but the client needs to:
This crate watches Kubernetes EndpointSlice resources and feeds endpoint changes to a
user-provided Tonic balance channel.
EndpointSlice watchAdd tonic and tonic-lb-k8s to your Cargo.toml:
[dependencies]
tonic = "0.14"
tonic-lb-k8s = "0.1"
use std::net::SocketAddr;
use std::time::Duration;
use tonic::transport::{Channel, Endpoint};
use tonic_lb_k8s::{discover, DiscoveryConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create your own balance channel
let (channel, tx) = Channel::balance_channel::<SocketAddr>(1024);
// Start discovery - build function returns Endpoint for each address
let config = DiscoveryConfig::new("my-grpc-service", 50051);
discover(config, tx, |addr| {
Endpoint::from_shared(format!("http://{addr}"))
.unwrap()
.connect_timeout(Duration::from_secs(5))
});
// Use with your generated gRPC client
let client = MyServiceClient::new(channel);
let response = client.some_method(request).await?;
Ok(())
}
use std::net::SocketAddr;
use std::time::Duration;
use tonic::transport::{Channel, ClientTlsConfig, Endpoint};
use tonic_lb_k8s::{discover, DiscoveryConfig};
let (channel, tx) = Channel::balance_channel::<SocketAddr>(1024);
let config = DiscoveryConfig::new("my-grpc-service", 50051);
let tls = ClientTlsConfig::new();
discover(config, tx, move |addr| {
Endpoint::from_shared(format!("https://{addr}"))
.unwrap()
.tls_config(tls.clone())
.unwrap()
.connect_timeout(Duration::from_secs(5))
});
Applications using this crate require Kubernetes RBAC permissions to watch EndpointSlice resources.
| API Group | Resource | Verbs |
|---|---|---|
discovery.k8s.io |
endpointslices |
list, watch |
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: endpointslice-reader
namespace: <your-namespace>
rules:
- apiGroups: ["discovery.k8s.io"]
resources: ["endpointslices"]
verbs: ["list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: <your-app>-endpointslice-reader
namespace: <your-namespace>
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: endpointslice-reader
subjects:
- kind: ServiceAccount
name: <your-service-account>
namespace: <your-namespace>
For cross-namespace discovery, use a ClusterRole and ClusterRoleBinding instead.
See the examples directory for a complete demonstration including:
Licensed under the MIT license.