etcd 深度剖析

概述

etcd 是一个高可用的分布式键值存储系统,是 Kubernetes 集群的核心数据存储层。所有集群状态(Pod、Service、ConfigMap、Deployment 等资源)都存储在 etcd 中。etcd 使用 Raft 共识算法保证数据一致性和高可用性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
┌─────────────────────────────────────────────────────────────────────────┐
│ etcd 架构图 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Kubernetes 集群 │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │kube- │ │kube- │ │kube- │ │ kubelet │ │ │
│ │ │apiserver│ │scheduler│ │controller│ │ │ │ │
│ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ │
│ │ │ │ │ │ │ │
│ │ └────────────┼────────────┼────────────┘ │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ kube-apiserver │ │ │
│ │ └───────────┬─────────────┘ │ │
│ └───────────────────────┼───────────────────────────────────────────┘ │
│ │ │
│ │ gRPC │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ etcd 集群 │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ etcd-0 │◄──│ etcd-1 │──►│ etcd-2 │ │ │
│ │ │ (Leader) │ │ (Follower) │ │ (Follower) │ │ │
│ │ └──────┬─────┘ └──────┬─────┘ └──────┬─────┘ │ │
│ │ │ │ │ │ │
│ │ └────────────────┼────────────────┘ │ │
│ │ ▼ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ Raft 共识层 │ │ │
│ │ │ (日志复制/选举) │ │ │
│ │ └────────┬────────┘ │ │
│ │ ▼ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ MVCC 存储引擎 │ │ │
│ │ │ (BoltDB) │ │ │
│ │ └─────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

1. etcd 核心概念

1.1 Raft 一致性算法

etcd 使用 Raft 算法实现分布式一致性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
┌─────────────────────────────────────────────────────────────────────────┐
│ Raft 角色转换图 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ │
│ │ Leader │ │
│ │ (处理所有请求) │ │
│ └────────┬─────────┘ │
│ │ │
│ ┌────────────────┼────────────────┐ │
│ │ 心跳超时 │ │ 心跳超时 │
│ │ 转为 Candidate │ │ 转为 Candidate │
│ ▼ │ ▼ │
│ ┌──────────────────┐ │ ┌──────────────────┐ │
│ │ Candidate │ │ │ Candidate │ │
│ │ (发起选举) │ │ │ (发起选举) │ │
│ └────────┬─────────┘ │ └────────┬─────────┘ │
│ │ │ │ │
│ │ 获得多数票 │ │ 获得多数票 │
│ │ 成为 Leader │ │ 成为 Leader │
│ ▼ │ ▼ │
│ ┌──────────────────┐ │ ┌──────────────────┐ │
│ │ Leader │ │ │ Leader │ │
│ │ │ │ │ │ │
│ └──────────────────┘ │ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Follower │ │
│ │ (同步日志) │ │
│ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

1.2 etcd 术语

术语 说明
Node etcd 集群中的一个实例
Member etcd 集群中的一个成员
Peer 集群中的其他节点
Leader 负责处理所有写请求的节点
Follower 跟随 Leader 的节点
Candidate 参与选举的节点
Term 选举任期,每个任期最多一个 Leader
Log Entry 日志条目,包含命令和任期号
WAL Write-Ahead Log,预写日志
Snapshot 快照,用于压缩日志

2. 数据存储结构

2.1 Kubernetes 数据目录

1
2
3
4
5
6
7
8
/var/lib/etcd/
├── member/
│ ├── wal/
│ │ └── 0000000000000000-0000000000000000.wal
│ ├── snap/
│ │ └── 0000000000000000-0000000000000000.snap
│ └── kv/
│ └── db

2.2 Kubernetes 资源存储路径

etcd 使用分层键空间存储 Kubernetes 资源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/
├── registry/
│ ├── pods/
│ │ └── default/
│ │ └── nginx-pod/
│ │ └── <uid>/
│ │ └── ...
│ ├── services/
│ │ └── default/
│ │ └── myapp/
│ │ └── ...
│ ├── deployments/
│ │ └── apps/
│ │ └── default/
│ │ └── nginx/
│ │ └── ...
│ ├── configmaps/
│ │ └── default/
│ │ └── app-config/
│ ├── secrets/
│ ├── namespaces/
│ │ ├── default/
│ │ ├── kube-system/
│ │ └── ...
│ └── persistentvolumeclaims/
│ └── ...

#Lease 锁机制(用于 leader 选举)
├── lease/
│ └── <lease-uid>/

# Kubernetes 事件
├── events/

2.3 存储格式

1
2
3
4
5
6
7
8
9
10
// etcd 存储的 key-value 格式
// Key: /registry/<resource>/<namespace>/<name>/<uid>
// Value: 序列化的 protobuf 对象

// 示例:Pod 存储
Key: /registry/pods/default/nginx-7fb96c846b-abc12
Value: {
"typeUrl": "type.googleapis.com/kubernetes.Pod",
"value": <protobuf encoded Pod object>
}

3. 写请求流程

3.1 完整写入流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌─────────────────────────────────────────────────────────────────────────┐
│ etcd 写入流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Client Leader Follower 1 Follower 2 │
│ │ │ │ │ │
│ │ 1. 写请求 │ │ │ │
│ │ ─────────────► │ │ │ │
│ │ │ │ │ │
│ │ │ 2. 写入 WAL │ │ │
│ │ │ ─────────────────────│ │ │
│ │ │ │ │ │
│ │ │ 3. 复制日志条目 │ │ │
│ │ │ ─────────────────────│────────────────│ │
│ │ │ │ │ │
│ │ │ 4. 等待多数节点确认 │ │ │
│ │ │ ◄───────────────────│────────────────│ │
│ │ │ │ │ │
│ │ │ 5. 提交到状态机 │ │ │
│ │ │ ◄───────────────────┤ │ │
│ │ │ │ │ │
│ │ 6. 返回结果 │ │ │ │
│ │ ◄──────────── │ │ │ │
│ │ │ │ │ │
└─────────────────────────────────────────────────────────────────────────┘

3.2 核心代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// etcd/server/etcdserver/api/v2v3/server.go
func (s *EtcdServer) Process(ctx context.Context, r etcdserverpb.Request) (rr etcdserverpb.Response, err error) {
// 1. 验证请求
if err := s.validateRequest(r); err != nil {
return etcdserverpb.Response{}, err
}

// 2. 创建日志条目
raftReq := &etcdsnap.Message{
Type: raft.MsgApp,
Entries: []raftpb.Entry{
{
Term: s.reqIDGen.Tick(),
Index: s.be.NextIndex(),
Data: mustMarshal(&r),
},
},
}

// 3. 提交到 Raft
s.node.Step(ctx, raftReq)

// 4. 等待应用
ch := s.w.Register(raftReq)

select {
case <-ch:
return s.applyRequest(r)
case <-ctx.Done():
return etcdserverpb.Response{}, ctx.Err()
}
}

// 应用到状态机
func (s *EtcdServer) applyRequest(r etcdserverpb.Request) etcdserverpb.Response {
switch r.Type {
case etcdserverpb.Range:
return s.applyRange(r)
case etcdserverpb.Put:
return s.applyPut(r)
case etcdserverpb.Delete:
return s.applyDelete(r)
case etcdserverpb.Txn:
return s.applyTxn(r)
}
}

4. 读请求流程

4.1 线性读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
┌─────────────────────────────────────────────────────────────────────────┐
│ etcd 线性读流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 方法一:Leader 读(默认) │
│ │
│ Client Leader Follower 1 │
│ │ │ │ │
│ │ 1. 读请求 │ │ │
│ │ ─────────────► │ │ │
│ │ │ │ │
│ │ │ 2. 检查 lease │ │
│ │ │ │ │
│ │ │ 3. 直接读取状态机 │ │
│ │ │ │ │
│ │ 4. 返回结果 │ │ │
│ │ ◄──────────── │ │ │
│ │
│ 方法二:Follower 读(Serializable) │
│ │
│ Client Leader Follower 1 │
│ │ │ │ │
│ │ │ │ 1. 读请求 │
│ │ │ │ ◄──────────── │
│ │ │ │ │
│ │ │ ◄───── 2. 获取 commit index │
│ │ │ │ │
│ │ │ ────── 3. 返回 read index │
│ │ │ │ │
│ │ │ │ 4. 等待应用到 read index │
│ │ │ │ │
│ │ │ │ 5. 读取本地数据 │
│ │ │ │ ─────────────► │
│ │ │ │ │
└─────────────────────────────────────────────────────────────────────────┘

4.2 读模式对比

模式 说明 一致性 性能
Linearizable 线性读,保证全局顺序 强一致 较低
Serializable 串行化读,本地数据读取 中等一致 较高
Snapshot 快照读 读取创建时的状态

5. Watch 机制

5.1 Watch 流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
┌─────────────────────────────────────────────────────────────────────────┐
│ etcd Watch 流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Client Leader WatchHub Follower │
│ │ │ │ │ │
│ │ 1. 创建 Watch │ │ │ │
│ │ ─────────────► │ │ │ │
│ │ │ 2. 注册 Watch │ │ │
│ │ │ ──────────────────► │ │ │
│ │ │ │ │ │
│ │ │ 3. 返回 Watch ID │ │ │
│ │ ◄──────────── │ │ │ │
│ │ │ │ │ │
│ │ 4. 等待事件 │ │ │ │
│ │ ◄──────────── │ │ │ │
│ │ │ │ │ │
│ │ │ 5. 数据变更 │ │ │
│ │ │ ────────────────────│──────────────►│ │
│ │ │ │ │ │
│ │ │ │ 6. 推送事件 │ │
│ │ │ │ ─────────────►│ │
│ │ │ │ │ │
│ │ 7. 收到变更 │ │ │ │
│ │ ◄──────────── │ │ │ │
│ │ │ │ │ │
└─────────────────────────────────────────────────────────────────────────┘

5.2 Watch 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// etcd/server/etcdserver/api/v3client/watch.go
type Watcher struct {
streams map[int64]*watchStream
}

func (w *watcher) Watch(ctx context.Context, r *pb.WatchCreateRequest) (WatchID, error) {
// 1. 创建 Watch
watch := &Watch{
Key: r.Key,
RangeEnd: r.RangeEnd,
StartRev: r.StartRevision,
Filters: r.Filters,
}

// 2. 找到 Watch 创建的节点
if isLocal(r.Key) {
// 本地节点
return w.watchLocal(ctx, watch)
}

// 3. 发送到对应节点
return w.watchRemote(ctx, watch)
}

// 接收变更事件
func (ws *watchStream) run() {
for {
select {
case rev := <-ws.revChan:
// 检查是否有新数据
events, err := ws.getEvents(rev)
if err != nil {
continue
}
// 发送事件
ws.send(WatchResponse{Events: events})
}
}
}

5.3 Kubernetes 中的 Watch 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// kube-apiserver 中的 Watch 使用
// 位置:staging/src/k8s.io/apiserver/pkg/storage/cacher.go

func (c *Cacher) Watch(ctx context.Context, key string, opts storage.ListOptions) (watch.Interface, error) {
// 1. 获取指定版本的缓存
watchable, ok := c.watchCache[*opts.ResourceVersion]
if !ok {
// 等待数据就绪
watchable = c.watchCache.WaitUntilFreshResourceVersion(opts.ResourceVersion)
}

// 2. 创建 watcher
return watchable.Watch(ctx, key, opts)
}

6. 事务与租约

6.1 事务 (Txn)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// etcd 事务保证原子性
// txn 比较多个 key 的值,然后根据结果执行不同的操作

// 示例:Kubernetes 中的 atomic update
func AtomicUpdate(cli *clientv3.Client, key string, updateFn func(old []byte) ([]byte, error)) error {
for {
// 1. 读取当前值
resp, err := cli.Get(ctx, key)
if err != nil {
return err
}

// 2. 计算新值
newValue, err := updateFn(resp.Kvs[0].Value)
if err != nil {
return err
}

// 3. 原子更新(如果值没变)
txn := cli.Txn(ctx)
txn.If(clientv3.Compare(clientv3.Value(key), "=", string(resp.Kvs[0].Value)))
txn.Then(clientv3.Put(key, string(newValue)))
_, err = txn.Commit()

if err == nil {
return nil
}
// 重试
}
}

6.2 租约 (Lease)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Lease 用于实现带 TTL 的 key
// Kubernetes 用它实现 leader 选举、endpoint 刷新等

// 创建带租约的 key
func putWithLease(cli *clientv3.Client, key, value string) error {
// 1. 创建租约
lease, err := cli.Grant(ctx, 10) // 10 秒 TTL
if err != nil {
return err
}

// 2. 使用租约写入 key
_, err = cli.Put(ctx, key, value, clientv3.WithLease(lease.ID))
return err
}

// Kubernetes 中的 Lease 示例
// kube-controllermanager 的 leader election
type LeaderElectionRecord struct {
Holder LeaderElectionOwner `json:"holder"`
LeaseDurationSeconds int `json:"leaseDurationSeconds"`
AcquireTime metav1.Time `json:"acquireTime"`
RenewTime metav1.Time `json:"renewTime"`
LeaderTransitions int `json:"leaderTransitions"`
}

7. 快照与压缩

7.1 快照机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
┌─────────────────────────────────────────────────────────────────────────┐
│ etcd 快照与压缩 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ WAL 日志文件序列: │
│ │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ 0001 │ │ 0002 │ │ 0003 │ │ 0004 │ │ 0005 │ │ 0006 │ ... │
│ │ WAL │ │ WAL │ │ WAL │ │ WAL │ │ WAL │ │ WAL │ │
│ └──┬───┘ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ │
│ │ │
│ │ 定期创建快照 │
│ ▼ │
│ ┌──────┐ │
│ │Snap- │ ◄── 0001-0004 的状态快照 │
│ │ 01 │ │
│ └──┬───┘ │
│ │ │
│ │ 删除旧 WAL │
│ ▼ │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ 0005 │ │ 0006 │ │ ... │ <- 保留 0005 之后的日志 │
│ │ WAL │ │ WAL │ │ │ │
│ └──────┘ └──────┘ └──────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

7.2 压缩配置

1
2
3
4
5
6
7
8
9
# etcd 压缩配置
# 方法一:自动压缩(基于时间)
etcd --auto-compaction-mode=revision --auto-compaction-retention=1000

# 方法二:手动压缩
etcdctl compaction 1000

# 方法三:删除历史版本
etcdctl del --prefix "" --from-key 1000

8. 高可用与故障恢复

8.1 集群部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 启动 etcd 集群
# 节点 1
etcd --name=etcd-0 \
--initial-advertise-peer-urls=http://192.168.1.1:2380 \
--listen-peer-urls=http://192.168.1.1:2380 \
--listen-client-urls=http://192.168.1.1:2379 \
--advertise-client-urls=http://192.168.1.1:2379 \
--initial-cluster=etcd-0=http://192.168.1.1:2380,etcd-1=http://192.168.1.2:2380,etcd-2=http://192.168.1.3:2380

# 节点 2
etcd --name=etcd-1 \
--initial-advertise-peer-urls=http://192.168.1.2:2380 \
...

# 节点 3
etcd --name=etcd-2 \
--initial-advertise-peer-urls=http://192.168.1.3:2380 \
...

8.2 故障恢复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 检查集群健康状态
etcdctl --endpoints=https://192.168.1.1:2379 endpoint health

# 2. 查看成员列表
etcdctl --endpoints=https://192.168.1.1:2379 member list

# 3. 移除故障节点
etcdctl --endpoints=https://192.168.1.1:2379 member remove <member-id>

# 4. 添加新节点
etcdctl --endpoints=https://192.168.1.1:2379 member add etcd-new --peer-urls=http://192.168.1.4:2380

# 5. 备份恢复
etcdctl snapshot save backup.db
etcdctl snapshot restore backup.db --data-dir=/var/lib/etcd

9. 关键源码路径

功能 源码路径
Raft 实现 etcd/raft/
Server 实现 etcd/server/etcdserver/
存储引擎 etcd/server/etcdserver/api/v2/
MVCC etcd/server/etcdserver/api/mvcc/
Watch etcd/server/etcdserver/api/v3rpc/
gRPC etcd/server/etcdserver/api/v3client/
WAL etcd/wal/
Snap etcd/snap/

面试题

基础题

1. etcd 在 Kubernetes 中的作用是什么?

etcd 是 Kubernetes 的后端存储,保存所有集群状态数据:

  • 资源对象(Pod、Service、Deployment、ConfigMap 等)
  • 集群元数据(节点信息、命名空间)
  • 认证信息(Token、证书)
  • Leader 选举状态

当 kube-apiserver 需要读写资源时,实际上是与 etcd 交互。

2. etcd 如何保证数据一致性?

etcd 使用 Raft 共识算法保证一致性:

  • Leader 选举:集群通过选举产生 Leader,所有写请求必须经过 Leader
  • 日志复制:写请求先写入 WAL,然后复制到多数节点才算提交
  • Term 机制:每个选举周期有唯一 Term 号,防止脑裂
  • 多数派原则:只有获得多数节点确认的操作才是有效的

3. 什么是 MVCC?

MVCC(Multi-Version Concurrency Control)多版本并发控制:

  • etcd 每次修改都会创建新版本,不覆盖旧数据
  • 每个 key-value 都有 revision(版本号)
  • 支持读取历史版本
  • 通过压缩机制清理旧版本

4. etcd 的 Watch 机制有什么用?

Watch 用于监听 key 的变化:

  • kube-apiserver 用 Watch 监听资源变化
  • kubelet 监听 Pod 分配变化
  • 控制器监听相关资源变化并触发 reconcile
  • 实现近实时的状态同步

5. etcd 的 Lease 是什么?

Lease 是带 TTL 的租约:

  • 创建时指定过期时间
  • 定期续约可延长 TTL
  • 过期后关联的 key 自动删除
  • Kubernetes 用它实现 leader election、endpoint 刷新

中级题

6. etcd 读请求是如何处理的?

etcd 支持多种读模式:

  1. **线性读 (Linearizable)**:从 Leader 读取,保证全局一致
  2. **串行化读 (Serializable)**:可从 Follower 读取,本地数据
  3. 快照读:读取创建时的数据版本

kube-apiserver 默认使用线性读确保数据一致。

7. etcd 如何进行数据备份和恢复?

1
2
3
4
5
6
7
8
9
10
# 备份
etcdctl snapshot save backup.db

# 查看快照状态
etcdctl snapshot status backup.db

# 恢复
etcdctl snapshot restore backup.db --data-dir=/var/lib/etcd/new

# 从备份恢复后重启 etcd

生产环境建议定期备份并验证备份可恢复性。

8. etcd 的 WAL 和快照是什么?

  • **WAL (Write-Ahead Log)**:预写日志,记录所有操作,用于故障恢复
  • Snapshot:状态快照,保存某一时刻的完整数据

日志先写入 WAL,然后定期创建快照压缩日志文件。

9. 如何优化 etcd 性能?

  1. 使用 SSD:etcd 需要快速磁盘 IO
  2. 合理部署:控制平面节点单独部署
  3. 网络优化:低延迟网络
  4. 参数调优:增加 --snapshot-count
  5. 压缩配置:合理设置压缩周期
  6. 监控告警:监控磁盘 IO、延迟、可用空间

10. etcd 集群如何处理节点故障?

  1. Follower 故障:Leader 无法与其通信,移除该节点后重新添加
  2. Leader 故障:触发新的选举,选举成功后继续服务
  3. 多数节点故障:集群不可用,无法接受写请求

etcd 需要 (n+1)/2 个节点正常才能工作。

高级题

11. 分析 etcd 的 Raft 日志复制流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Leader 处理写请求
func (r *raft) Step(ctx context.Context, msg pb.Message) {
switch msg.Type {
case pb.MsgApp:
// Follower 收到日志复制请求
r.handleAppendEntries(ctx, msg)
}
}

func (r *raft) handleAppendEntries(msg pb.Message) {
// 1. 检查 term
if msg.Index < r.raftLog.lastIndex() {
return
}

// 2. 追加日志
r.raftLog.append(msg.Entries...)

// 3. 更新 commit index
if msg.Commit > r.raftLog.commitIndex {
r.raftLog.commitIndex = min(msg.Commit, msg.Index)
}
}

// Leader 发送心跳并检查复制状态
func (r *raft) sendHeartbeat(to uint64) {
// 发送心跳携带 commit index
r.send(pb.Message{
Type: pb.MsgHeartbeat,
Commit: r.raftLog.commitIndex,
From: r.id,
To: to,
})
}

日志复制遵循:写入 WAL → 复制到多数节点 → 提交到状态机。

12. Kubernetes 中 etcd 的数据隔离是如何实现的?

1
2
3
4
5
6
7
8
9
10
11
// etcd key 路径设计
// /registry/<resource-type>/<namespace>/<name>/<uid>

// 示例
/registry/pods/default/nginx-7fb96c846b-abc12
/registry/services/default/myapp
/registry/deployments.apps/default/myapp

// 命名空间隔离
// 不同命名空间的资源存储在不同路径下
// kubectl get pods -n kube-system 只查询 /registry/pods/kube-system/

这种分层设计支持命名空间级别的隔离和访问控制。

13. etcd 的线性一致性是如何实现的?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// etcd 通过以下机制保证线性一致性:

// 1. Leader lease
// Leader 在任期内持有 lease,读请求必须验证 leader 未改变
type lease struct {
ID leaseID
TTL int64
// Leader lease 用于判断读取的是否为最新数据
}

// 2. Read index
// 读取前先确认 Leader 仍为当前 Leader
func (r *raft) ReadIndex(ctx context.Context) error {
// 发送 ReadIndex 消息给 Leader
// Leader 确认自己仍是 leader 后返回 read index
// Follower 等待应用到这个 index
}

// 3. 租约续约保证
// Leader 定期发送心跳续约 lease
// 如果 lease 过期,需要重新验证 leader 身份

场景题

14. etcd 集群中出现”etcd cluster is unavailable”错误如何排查?

排查步骤:

  1. 检查 etcd 进程状态:systemctl status etcd
  2. 检查网络连通性:etcdctl endpoint health
  3. 检查磁盘空间:df -h
  4. 检查日志:journalctl -u etcd
  5. 常见原因:
    • 多数节点不可用
    • 磁盘空间不足
    • 网络分区
    • Leader 选举失败
    • 证书过期

15. 如何调整 etcd 的压缩策略?

1
2
3
4
5
6
7
8
9
10
11
12
13
# 自动压缩 - 基于 revision
etcd --auto-compaction-mode=revision \
--auto-compaction-retention=1000

# 自动压缩 - 基于时间
etcd --auto-compaction-mode=periodic \
--auto-compaction-retention=1h

# 手动压缩
etcdctl compaction 1000

# 删除历史版本
etcdctl del --prefix "" --from-key 1000

建议生产环境使用自动压缩,避免手动操作。

16. 如何实现 etcd 的安全加固?

  1. TLS 加密:启用 mTLS 加密所有通信
  2. 认证:启用客户端认证
  3. 授权:使用 RBAC 限制访问
  4. 防火墙:限制访问端口
  5. 定期备份:快照备份
  6. 监控:监控关键指标
  7. 限流:防止过大请求影响集群
1
2
3
4
5
6
# 启用 TLS
etcd --cert-file=/path/to/cert \
--key-file=/path/to/key \
--peer-cert-file=/path/to/peer-cert \
--peer-key-file=/path/to/peer-key \
--trusted-ca-file=/path/to/ca