etcd — API Surface#
API types#
etcd exposes four distinct API surfaces:
- gRPC — primary protocol for all client–server communication
- REST/HTTP (grpc-gateway) — JSON shim over the gRPC services, available on the same port
- CLI (etcdctl) — administrative and operational CLI wrapping the gRPC client
- Go library (client/v3) — a first-class Go client library with interface-per-concern design
- Embedding API (embed.Config) — allows Go programs to run etcd in-process
Additionally, the server exposes a minimal HTTP-only surface for health checks, Prometheus metrics, and peer-to-peer Raft traffic.
gRPC API#
Proto files#
All proto definitions live in api/etcdserverpb/:
| File | Purpose |
|---|---|
api/etcdserverpb/rpc.proto | Core client-facing services: KV, Watch, Lease, Cluster, Maintenance, Auth |
api/etcdserverpb/raft_internal.proto | Internal Raft request/response types (not client-facing) |
api/etcdserverpb/etcdserver.proto | Internal EtcdServer snapshot request type |
api/mvccpb/kv.proto | Shared KeyValue and Event types |
api/authpb/auth.proto | Auth types (User, Role, Permission) |
api/versionpb/version.proto | Version information type |
server/etcdserver/api/v3election/v3electionpb/v3election.proto | Leader election service |
server/etcdserver/api/v3lock/v3lockpb/v3lock.proto | Distributed lock service |
server/lease/leasepb/lease.proto | Internal lease persistence type |
server/storage/wal/walpb/record.proto | Internal WAL record type |
Services and RPCs#
KV service — api/etcdserverpb/rpc.proto#
The core key-value store service. Every RPC has a corresponding grpc-gateway REST binding at /v3/kv/....
| RPC | Request | Response | Description |
|---|---|---|---|
Range | RangeRequest | RangeResponse | Get one or more keys; supports prefix ranges, revision pinning, limit, sorting |
Put | PutRequest | PutResponse | Write a key-value pair; optional lease attach, prev_kv return |
DeleteRange | DeleteRangeRequest | DeleteRangeResponse | Delete a key or range; optional prev_kv return |
Txn | TxnRequest | TxnResponse | Multi-predicate, multi-op transaction (compare-and-swap generalization) |
Compact | CompactionRequest | CompactionResponse | Trim event history up to a given revision |
Watch service — api/etcdserverpb/rpc.proto#
Bidirectional streaming service for change notifications.
| RPC | Type | Description |
|---|---|---|
Watch | stream WatchRequest → stream WatchResponse | Multiplexed watch stream: clients send create/cancel watch requests, server pushes events. Supports watching from arbitrary past revisions (up to compaction point). |
Lease service — api/etcdserverpb/rpc.proto#
TTL-based key expiry.
| RPC | Type | Description |
|---|---|---|
LeaseGrant | Unary | Create a lease with a TTL; returns a LeaseID |
LeaseRevoke | Unary | Immediately revoke; all attached keys deleted |
LeaseKeepAlive | stream → stream | Bidirectional heartbeat stream to renew TTL |
LeaseTimeToLive | Unary | Query remaining TTL and optionally list attached keys |
LeaseLeases | Unary | List all active leases |
Cluster service — api/etcdserverpb/rpc.proto#
Cluster membership management.
| RPC | Description |
|---|---|
MemberAdd | Add a member (or learner) to the cluster |
MemberRemove | Remove an existing member |
MemberUpdate | Update a member’s peer URLs |
MemberList | List all members with their status |
MemberPromote | Promote a non-voting learner to a voting member |
Maintenance service — api/etcdserverpb/rpc.proto#
Operational and observability operations.
| RPC | Type | Description |
|---|---|---|
Alarm | Unary | Activate, deactivate, or list cluster health alarms |
Status | Unary | Returns member status: version, db size, leader, raft index |
Defragment | Unary | Reclaim bbolt free pages (reduces on-disk file size) |
Hash | Unary | Compute hash of the entire backend (consistency check) |
HashKV | Unary | Compute hash of the KV store up to a revision (corruption detection) |
Snapshot | stream SnapshotResponse | Stream a point-in-time backup of the entire database |
MoveLeader | Unary | Transfer Raft leadership to another member |
Downgrade | Unary | Initiate or confirm a cluster version downgrade |
Auth service — api/etcdserverpb/rpc.proto#
RBAC access control.
| RPC | Description |
|---|---|
AuthEnable / AuthDisable | Toggle auth globally |
AuthStatus | Check if auth is enabled |
Authenticate | Exchange username+password for a token |
UserAdd / UserGet / UserList / UserDelete | User management |
UserChangePassword / UserGrantRole / UserRevokeRole | User configuration |
RoleAdd / RoleGet / RoleList / RoleDelete | Role management |
RoleGrantPermission / RoleRevokePermission | Key-range RBAC permissions |
Election service — server/etcdserver/api/v3election/v3electionpb/v3election.proto#
Leader election built on top of leases and the KV transactional model.
| RPC | Type | Description |
|---|---|---|
Campaign | Unary (blocks) | Compete for leadership; blocks until elected, returns a LeaderKey |
Proclaim | Unary | Update the leader’s value while holding LeaderKey |
Leader | Unary | Query current leader’s value |
Observe | stream LeaderResponse | Stream leadership changes |
Resign | Unary | Release leadership voluntarily |
Lock service — server/etcdserver/api/v3lock/v3lockpb/v3lock.proto#
Distributed mutual exclusion built on leases and the KV store.
| RPC | Description |
|---|---|
Lock | Acquire a named distributed lock; blocks until held; returns an ephemeral key |
Unlock | Release the lock by deleting the ephemeral key |
gRPC server setup#
server/etcdserver/api/v3rpc/grpc.go is the single wire-up point. The Server() function creates a grpc.Server and registers all service implementations:
pb.RegisterKVServer(grpcServer, NewQuotaKVServer(s))
pb.RegisterWatchServer(grpcServer, NewWatchServer(s))
pb.RegisterLeaseServer(grpcServer, NewQuotaLeaseServer(s))
pb.RegisterClusterServer(grpcServer, NewClusterServer(s))
pb.RegisterAuthServer(grpcServer, NewAuthServer(s))
pb.RegisterMaintenanceServer(grpcServer, NewMaintenanceServer(s, healthNotifier))
healthpb.RegisterHealthServer(grpcServer, health.NewServer())Election and Lock services are registered separately in server/embed/serve.go during the HTTP/gRPC mux setup, because they are optional extensions built on client/v3 (they run client-side logic against the embedded server).
Interceptors#
Three interceptor layers are applied in order for all unary calls:
| Order | Interceptor | Responsibility |
|---|---|---|
| 1 | newLogUnaryInterceptor | Logs slow requests (latency > WarningUnaryRequestDuration), always logs at DEBUG |
| 2 | serverMetrics.UnaryServerInterceptor() | Prometheus counters/histograms via grpc-ecosystem/go-grpc-middleware |
| 3 | newUnaryInterceptor | Checks capability flags, rejects learner-unsupported RPCs, enforces RequireLeader metadata, tracks client API version |
| 4 | (optional) | User-supplied grpc.UnaryServerInterceptor injected via embed.Config.ServiceRegister |
For streaming RPCs, layers 2 and 3 equivalents exist (newStreamInterceptor). OpenTelemetry distributed tracing is added via a gRPC stats handler when EnableDistributedTracing is set.
Quota enforcement is not in the interceptor chain — it is handled at the service object level: NewQuotaKVServer wraps the KV server; NewQuotaLeaseServer wraps the Lease server. Both check the NOSPACE alarm before dispatching.
REST/HTTP API (grpc-gateway)#
The grpc-gateway (grpc-ecosystem/grpc-gateway/v2) is an optional layer that translates HTTP/JSON requests to gRPC calls internally. It is enabled by default (EnableGRPCGateway: true in embed.Config).
Router: gw.ServeMux (grpc-gateway runtime), wrapped in an accessController (auth enforcement for the HTTP path).
Marshaler: Custom protojson marshaler with snake_case field names and emitted default values.
Route registration (server/embed/serve.go):
etcdservergw.RegisterKVHandler(...)
etcdservergw.RegisterWatchHandler(...)
etcdservergw.RegisterLeaseHandler(...)
etcdservergw.RegisterClusterHandler(...)
etcdservergw.RegisterMaintenanceHandler(...)
etcdservergw.RegisterAuthHandler(...)
v3lockgw.RegisterLockHandler(...)
v3electiongw.RegisterElectionHandler(...)Key REST endpoints (from proto google.api.http annotations):
| Method | Path | gRPC equivalent |
|---|---|---|
| POST | /v3/kv/range | KV.Range |
| POST | /v3/kv/put | KV.Put |
| POST | /v3/kv/deleterange | KV.DeleteRange |
| POST | /v3/kv/txn | KV.Txn |
| POST | /v3/kv/compaction | KV.Compact |
| POST | /v3/watch | Watch.Watch |
| POST | /v3/lease/grant | Lease.LeaseGrant |
| POST | /v3/lease/revoke | Lease.LeaseRevoke |
| POST | /v3/lease/keepalive | Lease.LeaseKeepAlive |
| POST | /v3/lease/timetolive | Lease.LeaseTimeToLive |
| POST | /v3/lease/leases | Lease.LeaseLeases |
| POST | /v3/cluster/member/add | Cluster.MemberAdd |
| POST | /v3/cluster/member/remove | Cluster.MemberRemove |
| POST | /v3/cluster/member/list | Cluster.MemberList |
| POST | /v3/cluster/member/promote | Cluster.MemberPromote |
| POST | /v3/lock/lock | Lock.Lock |
| POST | /v3/lock/unlock | Lock.Unlock |
| POST | /v3/election/campaign | Election.Campaign |
| POST | /v3/election/leader | Election.Leader |
| POST | /v3/election/resign | Election.Resign |
Notable design choice: All REST endpoints use HTTP POST, even for read operations. This is a deliberate deviation from REST conventions — it avoids URL-length limitations for complex queries and keeps the HTTP layer as a thin transport shim rather than a designed API.
Plain HTTP endpoints (registered separately via etcdhttp):
| Path | Purpose |
|---|---|
/health | Legacy health check returning {"health": "true"/"false"} |
/livez | Kubernetes liveness probe (pluggable checks) |
/readyz | Kubernetes readiness probe (pluggable checks) |
/metrics | Prometheus metrics scrape endpoint |
/debug/pprof/* | Go pprof profiling endpoints |
/version | Returns etcd server and cluster version |
WebSocket: The tmc/grpc-websocket-proxy middleware wraps the grpc-gateway mux to allow WebSocket connections to streaming endpoints (notably Watch.Watch), enabling browser-based clients.
CLI (etcdctl)#
Framework#
cobra (github.com/spf13/cobra) + pflag (github.com/spf13/pflag). Entry point: etcdctl/ctlv3/ctl.go. Commands added in init() to a global rootCmd.
Command structure#
Commands are organized into 5 groups:
KV Group#
| Command | Description |
|---|---|
get | Retrieve keys, supports range, prefix, revision, sort, limit |
put | Write a key-value pair, optional lease and prev-kv flags |
del | Delete keys or ranges |
txn | Interactive multi-predicate transaction builder |
compaction | Trim event history to a revision |
Cluster Maintenance Group#
| Command | Description |
|---|---|
alarm disarm/list | Manage cluster health alarms |
defrag | Defragment bbolt database |
endpoint status/health/hashkv | Check per-endpoint health and consistency |
move-leader | Transfer Raft leadership |
snapshot save/restore/status | Backup and restore |
member add/remove/update/list/promote | Cluster membership |
downgrade enable/cancel/validate | Downgrade workflow |
Concurrency Group#
| Command | Description |
|---|---|
lock | Acquire a distributed lock, run a command while holding it |
elect | Participate in leader election, run a command if elected |
make-mirror | Mirror a keyspace from another etcd cluster |
Authentication Group#
| Command | Description |
|---|---|
auth enable/disable/status | Global auth toggle |
user add/delete/get/list/passwd/grant-role/revoke-role | User management |
role add/delete/get/list/grant-permission/revoke-permission | Role management |
Utility Group#
| Command | Description |
|---|---|
watch | Stream watch events for keys or prefixes |
lease grant/revoke/timetolive/list/keep-alive | Lease management |
version | Print etcdctl and API version |
check datascale/perf | Performance and data-scale benchmarks |
diagnosis | Run diagnostic checks (linearizability check) |
completion | Generate shell completion scripts |
options | Print all global flags |
Flag patterns#
Global persistent flags (hidden, shown via etcdctl options):
| Flag | Default | Purpose |
|---|---|---|
--endpoints | 127.0.0.1:2379 | Comma-separated list of etcd endpoints |
--write-out / -w | simple | Output format: simple, json, fields, table, protobuf |
--dial-timeout | 2s | gRPC dial timeout |
--command-timeout | 5s | Per-command execution timeout |
--keepalive-time | 2s | gRPC keepalive ping interval |
--cacert | "" | CA certificate for TLS verification |
--cert | "" | Client TLS certificate |
--key | "" | Client TLS key |
--user | "" | username[:password] for auth |
--auth-jwt-token | "" | JWT token for auth |
--insecure-transport | true | Disable TLS (TODO comment: should default false) |
--hex | false | Display byte strings as hex |
Notable pattern: Global flags are deliberately hidden from default help output and shown only via etcdctl options. Per-command flags appear in each command’s --help. This two-tier help system reduces noise for common operations.
Environment variable binding: Every global flag has an ETCD_* env var equivalent (handled at the server level; etcdctl reads flags only).
Go Library API (client/v3)#
Public packages#
The client library is a separate Go module (go.etcd.io/etcd/client/v3 with its own go.mod). This is architecturally significant: applications can depend on the client without pulling in the full server.
| Package | Purpose |
|---|---|
client/v3 | Core client: Client struct, all service interfaces |
client/v3/concurrency | STM (software transactional memory), distributed mutex, leader election primitives |
client/v3/mirror | Key-space replication between clusters |
client/v3/naming/endpoints | gRPC name resolution with etcd as the service registry |
client/v3/credentials | TLS credential helpers |
client/v3/mock/mockserver | In-process mock etcd for testing |
client/v3/kubernetes | Kubernetes-specific optimized interface |
client/v3/experimental | Experimental features |
Client construction#
client, err := clientv3.New(clientv3.Config{
Endpoints: []string{"localhost:2379"},
DialTimeout: 5 * time.Second,
})
// or
client, err := clientv3.NewFromURL("localhost:2379")Client is a struct (not an interface) that embeds all service clients. It manages connection pooling and auto-sync of endpoints from the cluster.
Interface-per-concern design#
The client exposes seven interfaces — one per logical capability:
| Interface | Methods | Description |
|---|---|---|
KV | Put, Get, Delete, Compact, Do, Txn | Key-value operations |
Watcher | Watch, RequestProgress, Close | Watch stream management |
Lease | Grant, Revoke, TimeToLive, Leases, KeepAlive, KeepAliveOnce, Close | Lease lifecycle |
Cluster | MemberAdd, MemberAddAsLearner, MemberRemove, MemberUpdate, MemberList, MemberPromote | Cluster management |
Maintenance | AlarmList, AlarmDisarm, Defragment, Status, HashKV, Snapshot, MoveLeader, Downgrade | Ops |
Auth | AuthEnable, AuthDisable, AuthStatus, Authenticate, UserAdd/Delete/Get/List/etc. | RBAC |
Txn | If, Then, Else, Commit | Transaction builder (fluent interface) |
Functional options (OpOption pattern)#
All KV operations accept variadic OpOption functions, enabling a clean composable API without option-struct proliferation:
// Simple get
resp, err := client.Get(ctx, "key")
// Get with options
resp, err := client.Get(ctx, "prefix/",
clientv3.WithPrefix(),
clientv3.WithRev(42),
clientv3.WithLimit(100),
clientv3.WithSort(clientv3.SortByKey, clientv3.SortAscend),
clientv3.WithKeysOnly(),
)
// Put with lease and previous value
resp, err := client.Put(ctx, "key", "value",
clientv3.WithLease(leaseID),
clientv3.WithPrevKV(),
)Full set of options includes: WithLease, WithLimit, WithRev, WithSort, WithPrefix, WithRange, WithFromKey, WithSerializable, WithKeysOnly, WithCountOnly, WithMinModRev, WithMaxModRev, WithFirstCreate, WithLastKey, WithProgressNotify, WithCreatedNotify, WithFilterPut, WithFilterDelete, WithPrevKV, WithFragment, WithIgnoreValue, WithIgnoreLease.
STM (Software Transactional Memory) — client/v3/concurrency#
The STM interface provides optimistic concurrency control:
type STM interface {
Get(key string) string
Put(key, val string, opts ...OpOption)
Rev(key string) int64
Del(key string)
}concurrency.NewSTM(client, func(stm STM) error {...}) wraps a user function in a Raft-serializable transaction with automatic retry on conflict. Four isolation levels are supported: SerializableSnapshot, Serializable, RepeatableReads, ReadCommitted.
Kubernetes-specific interface — client/v3/kubernetes#
A separate Interface added in 2024 that models the exact contract Kubernetes needs, hiding etcd complexity:
type Interface interface {
Get(ctx context.Context, key string, opts GetOptions) (GetResponse, error)
List(ctx context.Context, prefix string, opts ListOptions) (ListResponse, error)
Count(ctx context.Context, prefix string, opts CountOptions) (int64, error)
OptimisticPut(ctx context.Context, key string, value []byte, expectedRevision int64, opts PutOptions) (PutResponse, error)
OptimisticDelete(ctx context.Context, key string, expectedRevision int64, opts DeleteOptions) (DeleteResponse, error)
}This interface deliberately omits Watch (handled elsewhere in Kubernetes), leases (except as a PutOptions.LeaseID field), and admin operations. It is a rare example of explicitly modeling a consumer’s minimal API surface.
Embedding API (server/embed)#
For Go programs that want to run etcd in-process (e.g., testing, single-binary deployment):
cfg := embed.NewConfig()
cfg.Dir = "/tmp/etcd"
cfg.ListenClientUrls = []url.URL{{Scheme: "http", Host: "localhost:2379"}}
e, err := embed.StartEtcd(cfg)
defer e.Close()
<-e.Server.ReadyNotify() // wait for readyExtension points in embed.Config:
| Field | Type | Purpose |
|---|---|---|
UserHandlers | map[string]http.Handler | Register custom HTTP handlers on client port |
ServiceRegister | func(*grpc.Server) | Register custom gRPC services on the client gRPC server |
These two hooks allow embedding users to co-locate custom gRPC/HTTP services alongside etcd without running a separate server.
Peer-to-peer Raft HTTP#
etcd peers communicate via an internal HTTP-based protocol (server/etcdserver/api/rafthttp). This is not a user-facing API but is part of the overall API surface:
| Path prefix | Purpose |
|---|---|
/raft | Raft message delivery (pipeline and streaming) |
/raft/probing | Peer health probing |
/raft/snapshot | Snapshot transfer between peers |
/members | Peer membership information |
/members/promote/ | Learner promotion (peer-initiated) |
/lease/leases | Lease expiry checkpointing between leader and followers |
/downgrade | Cluster version downgrade coordination |
Peer traffic is multiplexed with client gRPC traffic on the same TCP port using soheilhy/cmux, with HTTP/1.x identified by connection sniffing.
Key design observations#
1. Proto-first API design#
The entire client API is derived from the proto definitions in api/etcdserverpb/rpc.proto. The proto file is the single source of truth for gRPC stubs, grpc-gateway HTTP bindings, OpenAPI documentation, and Go type aliases in client/v3. This eliminates drift between protocol and implementation.
2. All writes via POST (REST)#
The grpc-gateway REST API uses POST for all endpoints, including reads (/v3/kv/range). This is intentional: it avoids GET query-string length limits for complex range queries, keeps the REST layer as a pure serialization shim, and signals to clients that this is not a “RESTful” API — it is a gRPC API with an HTTP convenience wrapper.
3. Interface segregation in the client#
The client library defines seven narrow interfaces (KV, Watcher, Lease, etc.) rather than one fat Client interface. This means test doubles can be minimal, and library authors building on top of etcd can declare exactly which capabilities they need. The kubernetes.Interface is the extreme example: five methods instead of fifty.
4. Functional options for rich queries#
The OpOption pattern for KV operations handles the combinatorial complexity of etcd’s query model (revision pinning, range queries, sorting, filtering, pagination) without proliferating function overloads or option structs. The same Op type is used internally for Do, Txn, and direct Get/Put/Delete calls — the options are composable.
5. Service extensibility via two hooks#
Embedding users can extend the API surface via embed.Config.ServiceRegister (gRPC) and embed.Config.UserHandlers (HTTP), without forking etcd. This is used by Kubernetes’ embedded etcd tests and by projects like k3s that embed etcd.
6. gRPC health protocol#
etcd registers google.golang.org/grpc/health/grpc_health_v1.HealthServer, enabling standard gRPC health checking alongside the custom /health, /livez, /readyz endpoints. This dual health surface supports both Kubernetes-style probes and gRPC-native service mesh health checks.