# 与 etcd 交互

> etcdctl：与 etcd 服务器交互的命令行工具

---

LLMS 索引： [llms.txt](/zh/llms.txt)

---

用户通常通过设置或获取键的值来与 etcd 交互。本节介绍如何使用 etcdctl（用于与 etcd 服务器交互的命令行工具）实现这一操作。此处描述的概念同样适用于 gRPC API 或客户端库 API。

etcdctl 与 etcd 通信时所使用的 API 版本可通过 `ETCDCTL_API` 环境变量设置为 `2` 或 `3`。默认情况下，主分支（3.4）上的 etcdctl 使用 v3 API，而较早版本（3.3 及更早）默认使用 v2 API。

请注意，使用 v2 API 创建的任何键均无法通过 v3 API 查询。对 v2 键执行 v3 API ```etcdctl get``` 操作时，将返回 0 且不包含键数据，这是预期行为。


```bash
export ETCDCTL_API=3
```

## 查找版本 {#find-versions}

etcdctl 版本与服务器 API 版本可用于确定执行 etcd 各项操作时应使用的正确命令。

以下是查找版本号的命令：

```bash
$ etcdctl version
etcdctl version: 3.1.0-alpha.0+git
API version: 3.1
```

## 写入键 {#write-a-key}

应用程序通过向键写入数据将键存储到 etcd 集群中。每个存储的键都会通过 Raft 协议复制到集群中的所有成员，以实现一致性和可靠性。

以下是将键 `foo` 的值设置为 `bar` 的命令：

```bash
$ etcdctl put foo bar
OK
```

此外，可通过为键附加租约，将其设置为指定时间间隔。

以下是将键 `foo1` 的值设置为 `bar1` 并保留 10 秒的命令。

```bash
$ etcdctl put foo1 bar1 --lease=1234abcd
OK
```

> [!NOTE]
> 上述命令中的租约 ID `1234abcd` 指创建 10 秒租约时返回的 ID。该 ID 后续可附加至键。

## 读取键 {#read-keys}

应用程序可从 etcd 集群读取键的值。查询可读取单个键，或键的范围。

假设 etcd 集群已存储以下键：

```bash
foo = bar
foo1 = bar1
foo2 = bar2
foo3 = bar3
```

以下是读取键 `foo` 值的命令：

```bash
$ etcdctl get foo
foo
bar
```

以下是读取键 `foo` 值的十六进制格式的命令：

```bash
$ etcdctl get foo --hex
\x66\x6f\x6f          # Key
\x62\x61\x72          # Value
```

以下是仅读取键 `foo` 值的命令：

```bash
$ etcdctl get foo --print-value-only
bar
```

以下是遍历从 `foo` 到 `foo3` 范围内键的命令：

```bash
$ etcdctl get foo foo3
foo
bar
foo1
bar1
foo2
bar2
```

> [!NOTE]
> `foo3` 被排除，因为范围位于半开区间 `[foo, foo3)` 内，不包含 `foo3`。

以下是遍历所有以 `foo` 为前缀的键的命令：

```bash
$ etcdctl get --prefix foo
foo
bar
foo1
bar1
foo2
bar2
foo3
bar3
```

以下是遍历所有以 `foo` 为前缀的键、并将结果数量限制为 2 的命令：

```bash
$ etcdctl get --prefix --limit=2 foo
foo
bar
foo1
bar1
```

以下是使用 [`RangeStream`](/zh/docs/etcd/learning/api/#rangestream) RPC 遍历所有以 `foo` 为前缀的键的命令。结果与单次 `Range` 调用完全相同：

```bash
$ etcdctl get --stream --prefix foo
foo
bar
foo1
bar1
foo2
bar2
foo3
bar3
```

`--stream` 不支持 `--order`、`--sort-by` 或修订版本过滤。

## 读取键的过往版本 {#read-past-version-of-keys}

应用程序可能需要读取已被覆盖的键的旧版本。例如，应用程序可通过访问键的早期版本来回滚至旧配置。或者，应用程序可通过访问键的历史记录，在多次请求中获取多个键的一致视图。

由于对 etcd 集群键值存储的每次修改都会递增 etcd 集群的全局修订版本，因此应用程序可通过提供较早的 etcd 修订版本来读取已被覆盖的键。

假设一个 etcd 集群中已存在以下键：

```bash
foo = bar         # revision = 2
foo1 = bar1       # revision = 3
foo = bar_new     # revision = 4
foo1 = bar1_new   # revision = 5
```

以下是访问键的历史版本的示例：

```bash
$ etcdctl get --prefix foo # access the most recent versions of keys
foo
bar_new
foo1
bar1_new

$ etcdctl get --prefix --rev=4 foo # access the versions of keys at revision 4
foo
bar_new
foo1
bar1

$ etcdctl get --prefix --rev=3 foo # access the versions of keys at revision 3
foo
bar
foo1
bar1

$ etcdctl get --prefix --rev=2 foo # access the versions of keys at revision 2
foo
bar

$ etcdctl get --prefix --rev=1 foo # access the versions of keys at revision 1
```

## 读取大于等于指定键字节值的键 {#read-keys-which-are-greater-than-or-equal-to-the-byte-value-of-the-specified-key}

应用程序可能需要读取字节值大于或等于指定键的键。

假设一个 etcd 集群中已存在以下键：

```bash
a = 123
b = 456
z = 789
```

以下是读取键值大于或等于键 `b` 字节值的命令：

```bash
$ etcdctl get --from-key b
b
456
z
789
```

## 删除键 {#delete-keys}

应用程序可以从 etcd 集群中删除一个键或一组键。

假设一个 etcd 集群中已存在以下键：

```bash
foo = bar
foo1 = bar1
foo3 = bar3
zoo = val
zoo1 = val1
zoo2 = val2
a = 123
b = 456
z = 789
```

以下是删除键 `foo` 的命令：

```bash
$ etcdctl del foo
1 # one key is deleted
```

以下是删除键范围从 `foo` 到 `foo9` 的命令：

```bash
$ etcdctl del foo foo9
2 # two keys are deleted
```

以下是删除键 `zoo` 的命令，删除后将返回被删除的键值对：

```bash
$ etcdctl del --prev-kv zoo
1   # one key is deleted
zoo # deleted key
val # the value of the deleted key
```

以下是用于删除前缀为 `zoo` 的键的命令：

```bash
$ etcdctl del --prefix zoo
2 # two keys are deleted
```

以下是删除键值大于或等于键 `b` 字节值的命令：

```bash
$ etcdctl del --from-key b
2 # two keys are deleted
```

## 监听键变化 {#watch-key-changes}

应用程序可对键或键范围进行监听，以监控任何更新。

以下是监听键 `foo` 的命令：

```bash
$ etcdctl watch foo
# in another terminal: etcdctl put foo bar
PUT
foo
bar
```

以下是监听键 `foo` 的十六进制格式的命令：

```bash
$ etcdctl watch foo --hex
# in another terminal: etcdctl put foo bar
PUT
\x66\x6f\x6f          # Key
\x62\x61\x72          # Value
```

以下是监听从 `foo` 到 `foo9` 范围键的命令：

```bash
$ etcdctl watch foo foo9
# in another terminal: etcdctl put foo bar
PUT
foo
bar
# in another terminal: etcdctl put foo1 bar1
PUT
foo1
bar1
```

以下是监听键前缀为 `foo` 的键的命令：

```bash
$ etcdctl watch --prefix foo
# in another terminal: etcdctl put foo bar
PUT
foo
bar
# in another terminal: etcdctl put fooz1 barz1
PUT
fooz1
barz1
```

以下是监听多个键 `foo` 和 `zoo` 的命令：

```bash
$ etcdctl watch -i
$ watch foo
$ watch zoo
# in another terminal: etcdctl put foo bar
PUT
foo
bar
# in another terminal: etcdctl put zoo val
PUT
zoo
val
```

## 监听键的历史变更 {#watch-historical-changes-of-keys}

应用程序可能需要监听 etcd 中键的历史变更。例如，应用程序可能希望接收某个键的所有修改；如果应用程序保持与 etcd 的连接，则 `watch` 已足够。然而，如果应用程序或 etcd 发生故障，故障期间可能发生变更，应用程序将无法实时接收更新。为确保更新能够送达，应用程序必须能够监听键的历史变更。为此，应用程序可以在监听时指定一个历史修订版本，如同读取键的过去版本一样。

假设已完成以下操作序列：

```bash
$ etcdctl put foo bar         # revision = 2
OK
$ etcdctl put foo1 bar1       # revision = 3
OK
$ etcdctl put foo bar_new     # revision = 4
OK
$ etcdctl put foo1 bar1_new   # revision = 5
OK
```

以下是监听历史变更的示例：

```bash
# watch for changes on key `foo` since revision 2
$ etcdctl watch --rev=2 foo
PUT
foo
bar
PUT
foo
bar_new
```

```bash
# watch for changes on key `foo` since revision 3
$ etcdctl watch --rev=3 foo
PUT
foo
bar_new
```

以下是一个仅从最后一次历史变更开始监听的示例：

```bash
# watch for changes on key `foo` and return last revision value along with modified value
$ etcdctl watch --prev-kv foo
# in another terminal: etcdctl put foo bar_latest
PUT
foo         # key
bar_new     # last value of foo key before modification
foo         # key
bar_latest  # value of foo key after modification
```

## 监听进度 {#watch-progress}

应用程序可能需要检查监听的进度，以判断监听流的更新状态。例如，若监听用于更新缓存，则了解缓存相对于法定人数读取的修订版本是否过时会很有帮助。

可以使用交互式监听会话中的“progress”命令，向 etcd 服务器请求在监听流中发送进度通知更新：

```bash
$ etcdctl watch -i
$ watch a
$ progress
progress notify: 1
# in another terminal: etcdctl put x 0
# in another terminal: etcdctl put y 1
$ progress
progress notify: 3
```

> [!NOTE]
> 进度通知响应中的修订版本号是监听流所连接的本地 etcd 服务器节点的修订版本。如果该节点处于网络分区状态且不属于法定人数，此进度通知的修订版本可能低于对非分区 etcd 服务器节点执行法定人数读取时返回的修订版本。

## 压缩的修订版本 {#compacted-revisions}

如前所述，etcd 会保留修订版本，以便应用程序能够读取键的过往版本。然而，为了避免积累无限量的历史数据，必须对过去的修订版本执行压缩。执行压缩后，etcd 会移除历史修订版本，释放资源以供后续使用。所有修订版本早于已压缩修订版本的过时数据将不可用。

以下是执行压缩修订版本的命令：

```bash
$ etcdctl compact 5
compacted revision 5

# any revisions before the compacted one are not accessible
$ etcdctl get --rev=4 foo
Error:  rpc error: code = 11 desc = etcdserver: mvcc: required revision has been compacted
```

> [!NOTE]
> 可通过在任意键（存在或不存在）上使用 get 命令以 JSON 格式获取当前 etcd 服务器的修订版本。以下示例展示了对 etcd 服务器中不存在的 mykey 执行操作的情况：

```bash
$ etcdctl get mykey -w=json
{"header":{"cluster_id":14841639068965178418,"member_id":10276657743932975437,"revision":15,"raft_term":4}}
```

## 授予租约 {#grant-leases}

应用程序可从 etcd 集群授予键的租约。当键绑定到租约时，其生命周期与租约的生命周期绑定，而租约的生命周期由生存时间（TTL）决定。每个租约在授予时由应用程序指定最小生存时间（TTL）值。租约的实际 TTL 值至少为最小 TTL，且由 etcd 集群选定。一旦租约的 TTL 到期，租约即失效，所有绑定的键将被删除。

以下是授予租约的命令：

```bash
# grant a lease with 60 second TTL
$ etcdctl lease grant 60
lease 32695410dcc0ca06 granted with TTL(60s)

# attach key foo to lease 32695410dcc0ca06
$ etcdctl put --lease=32695410dcc0ca06 foo bar
OK
```

## 撤销租约 {#revoke-leases}

应用程序通过租约 ID 撤销租约。撤销租约将删除其所有关联的键。

假设已完成以下操作序列：

```bash
$ etcdctl lease grant 60
lease 32695410dcc0ca06 granted with TTL(60s)
$ etcdctl put --lease=32695410dcc0ca06 foo bar
OK
```

以下是撤销相同租约的命令：

```bash
$ etcdctl lease revoke 32695410dcc0ca06
lease 32695410dcc0ca06 revoked

$ etcdctl get foo
# empty response since foo is deleted due to lease revocation
```

## 保持租约有效 {#keep-leases-alive}

应用程序可通过刷新租约的 TTL 来维持租约有效，防止其过期。

假设已完成以下操作序列：

```bash
$ etcdctl lease grant 60
lease 32695410dcc0ca06 granted with TTL(60s)
```

以下是保持相同租约持续有效的命令：

```bash
$ etcdctl lease keep-alive 32695410dcc0ca06
lease 32695410dcc0ca06 keepalived with TTL(60)
lease 32695410dcc0ca06 keepalived with TTL(60)
lease 32695410dcc0ca06 keepalived with TTL(60)
...
```

## 获取租约信息 {#get-lease-information}

应用程序可能需要了解租约信息，以便能够续期，或检查租约是否仍然有效或已过期。应用程序也可能需要知道某个特定租约所关联的键。

假设已完成以下操作序列：

```bash
# grant a lease with 500 second TTL
$ etcdctl lease grant 500
lease 694d5765fc71500b granted with TTL(500s)

# attach key zoo1 to lease 694d5765fc71500b
$ etcdctl put zoo1 val1 --lease=694d5765fc71500b
OK

# attach key zoo2 to lease 694d5765fc71500b
$ etcdctl put zoo2 val2 --lease=694d5765fc71500b
OK
```

获取租约信息的命令如下：

```bash
$ etcdctl lease timetolive 694d5765fc71500b
lease 694d5765fc71500b granted with TTL(500s), remaining(258s)
```

以下是获取租约信息及其关联键的命令：

```bash
$ etcdctl lease timetolive --keys 694d5765fc71500b
lease 694d5765fc71500b granted with TTL(500s), remaining(132s), attached keys([zoo2 zoo1])

# if the lease has expired or does not exist it will give the below response:
Error:  etcdserver: requested lease not found
```
