etcd — API Surface#

API types#

etcd exposes four distinct API surfaces:

  1. gRPC — primary protocol for all client–server communication
  2. REST/HTTP (grpc-gateway) — JSON shim over the gRPC services, available on the same port
  3. CLI (etcdctl) — administrative and operational CLI wrapping the gRPC client
  4. Go library (client/v3) — a first-class Go client library with interface-per-concern design
  5. 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/:

FilePurpose
api/etcdserverpb/rpc.protoCore client-facing services: KV, Watch, Lease, Cluster, Maintenance, Auth
api/etcdserverpb/raft_internal.protoInternal Raft request/response types (not client-facing)
api/etcdserverpb/etcdserver.protoInternal EtcdServer snapshot request type
api/mvccpb/kv.protoShared KeyValue and Event types
api/authpb/auth.protoAuth types (User, Role, Permission)
api/versionpb/version.protoVersion information type
server/etcdserver/api/v3election/v3electionpb/v3election.protoLeader election service
server/etcdserver/api/v3lock/v3lockpb/v3lock.protoDistributed lock service
server/lease/leasepb/lease.protoInternal lease persistence type
server/storage/wal/walpb/record.protoInternal 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/....

RPCRequestResponseDescription
RangeRangeRequestRangeResponseGet one or more keys; supports prefix ranges, revision pinning, limit, sorting
PutPutRequestPutResponseWrite a key-value pair; optional lease attach, prev_kv return
DeleteRangeDeleteRangeRequestDeleteRangeResponseDelete a key or range; optional prev_kv return
TxnTxnRequestTxnResponseMulti-predicate, multi-op transaction (compare-and-swap generalization)
CompactCompactionRequestCompactionResponseTrim event history up to a given revision

Watch service — api/etcdserverpb/rpc.proto#

Bidirectional streaming service for change notifications.

RPCTypeDescription
Watchstream WatchRequeststream WatchResponseMultiplexed 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.

RPCTypeDescription
LeaseGrantUnaryCreate a lease with a TTL; returns a LeaseID
LeaseRevokeUnaryImmediately revoke; all attached keys deleted
LeaseKeepAlivestreamstreamBidirectional heartbeat stream to renew TTL
LeaseTimeToLiveUnaryQuery remaining TTL and optionally list attached keys
LeaseLeasesUnaryList all active leases

Cluster service — api/etcdserverpb/rpc.proto#

Cluster membership management.

RPCDescription
MemberAddAdd a member (or learner) to the cluster
MemberRemoveRemove an existing member
MemberUpdateUpdate a member’s peer URLs
MemberListList all members with their status
MemberPromotePromote a non-voting learner to a voting member

Maintenance service — api/etcdserverpb/rpc.proto#

Operational and observability operations.

RPCTypeDescription
AlarmUnaryActivate, deactivate, or list cluster health alarms
StatusUnaryReturns member status: version, db size, leader, raft index
DefragmentUnaryReclaim bbolt free pages (reduces on-disk file size)
HashUnaryCompute hash of the entire backend (consistency check)
HashKVUnaryCompute hash of the KV store up to a revision (corruption detection)
Snapshotstream SnapshotResponseStream a point-in-time backup of the entire database
MoveLeaderUnaryTransfer Raft leadership to another member
DowngradeUnaryInitiate or confirm a cluster version downgrade

Auth service — api/etcdserverpb/rpc.proto#

RBAC access control.

RPCDescription
AuthEnable / AuthDisableToggle auth globally
AuthStatusCheck if auth is enabled
AuthenticateExchange username+password for a token
UserAdd / UserGet / UserList / UserDeleteUser management
UserChangePassword / UserGrantRole / UserRevokeRoleUser configuration
RoleAdd / RoleGet / RoleList / RoleDeleteRole management
RoleGrantPermission / RoleRevokePermissionKey-range RBAC permissions

Election service — server/etcdserver/api/v3election/v3electionpb/v3election.proto#

Leader election built on top of leases and the KV transactional model.

RPCTypeDescription
CampaignUnary (blocks)Compete for leadership; blocks until elected, returns a LeaderKey
ProclaimUnaryUpdate the leader’s value while holding LeaderKey
LeaderUnaryQuery current leader’s value
Observestream LeaderResponseStream leadership changes
ResignUnaryRelease leadership voluntarily

Lock service — server/etcdserver/api/v3lock/v3lockpb/v3lock.proto#

Distributed mutual exclusion built on leases and the KV store.

RPCDescription
LockAcquire a named distributed lock; blocks until held; returns an ephemeral key
UnlockRelease 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:

OrderInterceptorResponsibility
1newLogUnaryInterceptorLogs slow requests (latency > WarningUnaryRequestDuration), always logs at DEBUG
2serverMetrics.UnaryServerInterceptor()Prometheus counters/histograms via grpc-ecosystem/go-grpc-middleware
3newUnaryInterceptorChecks 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):

MethodPathgRPC equivalent
POST/v3/kv/rangeKV.Range
POST/v3/kv/putKV.Put
POST/v3/kv/deleterangeKV.DeleteRange
POST/v3/kv/txnKV.Txn
POST/v3/kv/compactionKV.Compact
POST/v3/watchWatch.Watch
POST/v3/lease/grantLease.LeaseGrant
POST/v3/lease/revokeLease.LeaseRevoke
POST/v3/lease/keepaliveLease.LeaseKeepAlive
POST/v3/lease/timetoliveLease.LeaseTimeToLive
POST/v3/lease/leasesLease.LeaseLeases
POST/v3/cluster/member/addCluster.MemberAdd
POST/v3/cluster/member/removeCluster.MemberRemove
POST/v3/cluster/member/listCluster.MemberList
POST/v3/cluster/member/promoteCluster.MemberPromote
POST/v3/lock/lockLock.Lock
POST/v3/lock/unlockLock.Unlock
POST/v3/election/campaignElection.Campaign
POST/v3/election/leaderElection.Leader
POST/v3/election/resignElection.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):

PathPurpose
/healthLegacy health check returning {"health": "true"/"false"}
/livezKubernetes liveness probe (pluggable checks)
/readyzKubernetes readiness probe (pluggable checks)
/metricsPrometheus metrics scrape endpoint
/debug/pprof/*Go pprof profiling endpoints
/versionReturns 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#

CommandDescription
getRetrieve keys, supports range, prefix, revision, sort, limit
putWrite a key-value pair, optional lease and prev-kv flags
delDelete keys or ranges
txnInteractive multi-predicate transaction builder
compactionTrim event history to a revision

Cluster Maintenance Group#

CommandDescription
alarm disarm/listManage cluster health alarms
defragDefragment bbolt database
endpoint status/health/hashkvCheck per-endpoint health and consistency
move-leaderTransfer Raft leadership
snapshot save/restore/statusBackup and restore
member add/remove/update/list/promoteCluster membership
downgrade enable/cancel/validateDowngrade workflow

Concurrency Group#

CommandDescription
lockAcquire a distributed lock, run a command while holding it
electParticipate in leader election, run a command if elected
make-mirrorMirror a keyspace from another etcd cluster

Authentication Group#

CommandDescription
auth enable/disable/statusGlobal auth toggle
user add/delete/get/list/passwd/grant-role/revoke-roleUser management
role add/delete/get/list/grant-permission/revoke-permissionRole management

Utility Group#

CommandDescription
watchStream watch events for keys or prefixes
lease grant/revoke/timetolive/list/keep-aliveLease management
versionPrint etcdctl and API version
check datascale/perfPerformance and data-scale benchmarks
diagnosisRun diagnostic checks (linearizability check)
completionGenerate shell completion scripts
optionsPrint all global flags

Flag patterns#

Global persistent flags (hidden, shown via etcdctl options):

FlagDefaultPurpose
--endpoints127.0.0.1:2379Comma-separated list of etcd endpoints
--write-out / -wsimpleOutput format: simple, json, fields, table, protobuf
--dial-timeout2sgRPC dial timeout
--command-timeout5sPer-command execution timeout
--keepalive-time2sgRPC 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-transporttrueDisable TLS (TODO comment: should default false)
--hexfalseDisplay 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.

PackagePurpose
client/v3Core client: Client struct, all service interfaces
client/v3/concurrencySTM (software transactional memory), distributed mutex, leader election primitives
client/v3/mirrorKey-space replication between clusters
client/v3/naming/endpointsgRPC name resolution with etcd as the service registry
client/v3/credentialsTLS credential helpers
client/v3/mock/mockserverIn-process mock etcd for testing
client/v3/kubernetesKubernetes-specific optimized interface
client/v3/experimentalExperimental 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:

InterfaceMethodsDescription
KVPut, Get, Delete, Compact, Do, TxnKey-value operations
WatcherWatch, RequestProgress, CloseWatch stream management
LeaseGrant, Revoke, TimeToLive, Leases, KeepAlive, KeepAliveOnce, CloseLease lifecycle
ClusterMemberAdd, MemberAddAsLearner, MemberRemove, MemberUpdate, MemberList, MemberPromoteCluster management
MaintenanceAlarmList, AlarmDisarm, Defragment, Status, HashKV, Snapshot, MoveLeader, DowngradeOps
AuthAuthEnable, AuthDisable, AuthStatus, Authenticate, UserAdd/Delete/Get/List/etc.RBAC
TxnIf, Then, Else, CommitTransaction 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 ready

Extension points in embed.Config:

FieldTypePurpose
UserHandlersmap[string]http.HandlerRegister custom HTTP handlers on client port
ServiceRegisterfunc(*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 prefixPurpose
/raftRaft message delivery (pipeline and streaming)
/raft/probingPeer health probing
/raft/snapshotSnapshot transfer between peers
/membersPeer membership information
/members/promote/Learner promotion (peer-initiated)
/lease/leasesLease expiry checkpointing between leader and followers
/downgradeCluster 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.