Skip to main content

Kubernetes Discovery

DDS discovery in Kubernetes using DNS-based service resolution.

Overview

Standard DDS uses UDP multicast for discovery, which is typically not available in Kubernetes environments. HDDS provides K8s Discovery - a zero-dependency solution using Kubernetes Headless Services and DNS resolution.

How It Works

┌─────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Pod A │ │ Pod B │ │ Pod C │ │
│ │ HDDS │ │ HDDS │ │ HDDS │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ └──────────────┼──────────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Headless Service│ │
│ │ hdds-discovery │ │
│ │ clusterIP: None │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ CoreDNS │ │
│ │ A/AAAA records │ │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
  1. Each pod queries DNS: hdds-discovery.default.svc.cluster.local
  2. CoreDNS returns A/AAAA records for all pods matching the service selector
  3. HDDS registers each pod IP as a static peer
  4. Standard RTPS discovery (SPDP/SEDP) proceeds over unicast UDP

Kubernetes Setup

Headless Service

Create a Headless Service (clusterIP: None) for your DDS pods:

apiVersion: v1
kind: Service
metadata:
name: hdds-discovery
namespace: default
spec:
clusterIP: None # Required: makes it a headless service
selector:
app: my-dds-app
ports:
- name: dds-spdp
port: 7400
protocol: UDP
- name: dds-user
port: 7411
protocol: UDP

Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
name: my-dds-app
spec:
replicas: 3
selector:
matchLabels:
app: my-dds-app
template:
metadata:
labels:
app: my-dds-app
spec:
containers:
- name: app
image: my-dds-app:latest
env:
# Optional: expose pod info to application
- name: HDDS_POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: HDDS_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: HDDS_K8S_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- containerPort: 7400
protocol: UDP
- containerPort: 7411
protocol: UDP

Rust API

Basic Usage

use hdds::{Participant, TransportMode};

let participant = Participant::builder("my-app")
.with_transport(TransportMode::UdpMulticast)
.with_k8s_discovery("hdds-discovery", "default")
.build()?;

Custom Configuration

use hdds::{Participant, TransportMode};
use hdds::discovery::K8sDiscoveryConfig;
use std::time::Duration;

let k8s_config = K8sDiscoveryConfig::new("hdds-discovery", "production")
.with_port(7411)
.with_poll_interval(Duration::from_secs(10));

let participant = Participant::builder("my-app")
.with_transport(TransportMode::UdpMulticast)
.with_k8s_discovery_config(k8s_config)
.build()?;

Manual Discovery

For advanced use cases:

use hdds::discovery::{K8sDiscovery, K8sDiscoveryConfig};

let discovery = K8sDiscovery::with_service("hdds-discovery", "default");

// One-shot resolution
let peers = discovery.resolve_peers();
for peer in peers {
println!("Found peer: {}", peer);
}

// Background polling with callback
let discovery = K8sDiscovery::with_service("hdds-discovery", "default")
.on_peer_discovered(|addr| {
println!("New peer discovered: {}", addr);
});

let handle = discovery.start();

// ... later
handle.stop();

Helper Functions

use hdds::discovery::k8s::{resolve_k8s_service, get_pod_ip, get_pod_name};

// One-shot DNS resolution
let peers = resolve_k8s_service("hdds-discovery", "default", 7411);

// Get pod info from downward API
if let Some(ip) = get_pod_ip() {
println!("My pod IP: {}", ip);
}
if let Some(name) = get_pod_name() {
println!("My pod name: {}", name);
}

Environment Variables

VariableDefaultDescription
HDDS_K8S_SERVICEhdds-discoveryKubernetes service name
HDDS_K8S_NAMESPACEdefaultKubernetes namespace
HDDS_K8S_PORT7411DDS port for discovered peers
HDDS_K8S_POLL_INTERVAL_MS5000DNS poll interval (ms)
HDDS_POD_IP-Pod IP (from downward API)
HDDS_POD_NAME-Pod name (from downward API)

Feature Flag

K8s Discovery requires the k8s feature:

[dependencies]
hdds = { path = "../hdds/crates/hdds", features = ["k8s"] }

Architecture Notes

Zero Dependencies

K8s Discovery uses only std::net::ToSocketAddrs for DNS resolution - no external crates required.

Background Polling

  • Dedicated thread: hdds-k8s-discovery
  • Polls DNS at configurable interval (default: 5s)
  • New peers trigger register_static_peer() on DiscoveryFsm
  • Peer removal handled by DDS lease mechanism (not DNS)

DNS Name Format

{service}.{namespace}.svc.{cluster_domain}

Example: hdds-discovery.default.svc.cluster.local

Troubleshooting

No Peers Discovered

  1. Check Headless Service exists:

    kubectl get svc hdds-discovery -o yaml
    # Verify: clusterIP: None
  2. Check pods are selected:

    kubectl get endpoints hdds-discovery
    # Should list pod IPs
  3. Test DNS resolution from a pod:

    kubectl exec -it <pod> -- nslookup hdds-discovery.default.svc.cluster.local
  4. Enable debug logging:

    RUST_LOG=hdds::discovery=debug ./my-app

Peers Found But No Communication

  1. Check UDP ports are open:

    kubectl exec -it <pod> -- netstat -ulnp | grep 74
  2. Check Network Policy allows UDP:

    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
    name: allow-dds
    spec:
    podSelector:
    matchLabels:
    app: my-dds-app
    ingress:
    - ports:
    - protocol: UDP
    port: 7400
    - protocol: UDP
    port: 7411

Slow Discovery

  • Reduce poll interval: HDDS_K8S_POLL_INTERVAL_MS=1000
  • Check CoreDNS performance
  • Consider using StatefulSet for stable pod names

Best Practices

  1. Use dedicated namespace for DDS workloads
  2. Set resource limits on DDS pods
  3. Use StatefulSet for predictable pod identities
  4. Monitor DNS resolution latency
  5. Configure appropriate poll interval based on pod churn rate

Example: Complete K8s Deployment

---
apiVersion: v1
kind: Namespace
metadata:
name: dds-system
---
apiVersion: v1
kind: Service
metadata:
name: hdds-discovery
namespace: dds-system
spec:
clusterIP: None
selector:
app: sensor-network
ports:
- name: spdp
port: 7400
protocol: UDP
- name: user
port: 7411
protocol: UDP
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: sensor-node
namespace: dds-system
spec:
serviceName: hdds-discovery
replicas: 5
selector:
matchLabels:
app: sensor-network
template:
metadata:
labels:
app: sensor-network
spec:
containers:
- name: sensor
image: hdds-sensor:1.0
env:
- name: HDDS_K8S_SERVICE
value: hdds-discovery
- name: HDDS_K8S_NAMESPACE
value: dds-system
- name: HDDS_POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
ports:
- containerPort: 7400
protocol: UDP
- containerPort: 7411
protocol: UDP