本位主要用以记录一些etcd的基本操作和常识。

1. 概述

etcd是一个分布式的、可靠的键值对存储服务。

1.1 主要功能

  • 基本的 key-value 存储
  • 监听机制
  • key 的过期及续约机制,用于监控和服务发现
  • 原子 CAS 和 CAD,用于分布式锁和 leader 选举

1.2 分布式特点

  • 分布式一致性
  • 分布式锁
  • leader选举
  • 写屏障(write barriers)

1.3 用途

  • 配置管理

    • kubernetes存储配置到etcd,通过etcd的watchAPI及时发布配置更改
  • 服务注册、发现

    1. 同一 service 的所有节点注册到相同目录下,节点启动后将自己的信息注册到所属服务的目录中。
    2. 服务节点定时发送心跳,注册到服务目录中的信息设置一个较短的 TTL,运行正常的服务节点每隔一段时间会去更新信息的 TTL
    3. 通过名称能查询到服务提供外部访问的 IP 和端口号。比如网关代理服务时能够及时的发现服务中新增节点、丢弃不可用的服务节点,同时各个服务间也能感知对方的存在
  • 协调分布式工作

    • 分布式锁
    • 分布式信号量

2. 工作方式

2.1 选举

etcd各节点有三种角色,分别是:

  • leader
    处理所有客户端交互,日志复制等,一个任期只有一个。
  • follower
    完全被动的选民,是只读的。
  • candidate
    候选人,可以被选举为新领导。

etcd使用了Raft一致性协议来保证数据的强一致性,选举是Raft协议的重要组成。etcd集群如果没有leader将不允许任何数据更新操作。选举完成以后,集群会通过心跳的方式维持 leader 的地位,一旦 leader 失效,会有新的 follower 起来竞选 leader。

2.1.1 选举流程

  1. 在集群刚启动时,所有节点的状态都为 follower,如果在一段时间周期内(election timeout)没有收到来自 leader 的 heartbeat,触发 leader election,该节点将自己切换为candidate,同时向其他节点发起选举请求,follower会响应这个请求。

    如果多个candidate同时发起了选举,导致都没有获得大多数选票时,每一个candidate会随机等待一段时间后重新发起新一轮投票(一般是随机等待150-300ms),可避免多个节点同时竞选。
  2. 当选举请求在集群中有超半数节点同意后,该candidate就切换为leader,如果没有达成一致,则 candidate 随机选择一个等待间隔(150ms ~ 300ms)再次发起投票。
  3. leader 节点依靠定时向 follower 发送 heartbeat 来保持其地位.

2.1.2 异常场景

  1. 任何时候如果其它 follower 在 election timeout 期间都没有收到来自 leader 的 heartbeat,同样会将自己的状态切换为 candidate 并发起选举。每成功选举一次,新 leader 的任期(Term)都会比之前 leader 的任期大 1。
  2. 集群中的leader故障后,会产生新的leader,之后如果旧的leader恢复,新旧leader之间会比较日志,缺少消息的一方会变为follower。

2.2 存储

etcd的存储机制在v2和v3版本之间有差异,以下只讲v3版本的实现。

在v3版本中,etcd存储分为两部分:

  • 内存中的索引:kvindex
  • 后端存储,可对接多种存储媒介,当前使用boltdb

2.2.1 kvindex

kvindex基于Google的btree实现,保存在内存中,当client通过key查询value时,会现在kvindex中查询这个key的所有revision,然后通过revision去后端存储(boltdb)查询数据。

2.2.2 boltdb

boltdb中存储的key是reversion,value则是client查询的key在这个reversion下的value,这样在boltdb中存储了这个key的所有版本的值,从而实现多版本机制。

2.2.3 reversion

每一个revision 都由( main ID, sub ID)唯一标识,它也是实现 etcd v3的基础。

同一事务共享maiID,但事务中的每次操作(PUT、DELETE)subID会递增(从0开始)。

mainid和sub id组成了全局唯一的reversion。

2.2.4 压缩历史版本

由于boltdb中保存了所有的版本,数据会越来越多,当数据量很大时,可以使用compact来压缩历史版本,也就是删除一部分历史,保留最近的版本。

compact(4)压缩4以前的 即1、2、3会被删除

2.3 Watch

etcd的watch机制支持watch某个固定的key,也支持watch一个范围。client发起watch时,可以指定一个revision(也可以不指定),如果指定了revision,etcd会从指定的revision返回数据。

2.4 租约-Lease

租约可以指定一对Key value的TTL,是Key有一个有效状态。

3. 使用

3.1 安装部署

可使用docker安装,方便快捷

docker-compose.yml文件内容如下:

version: '2'

services:
  etcd:
    image: 'bitnami/etcd:latest'
    environment:
      - "ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379"
      - "ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379"
      - "ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380"
      - "ETCD_INITIAL_ADVERTISE_PEER_URLS=http://0.0.0.0:2380"
      - "ALLOW_NONE_AUTHENTICATION=yes"
      - "ETCD_INITIAL_CLUSTER=node1=http://0.0.0.0:2380"
      - "ETCD_NAME=node1"
      - "ETCD_DATA_DIR=/opt/bitnami/etcd/data"
    ports:
      - 2379:2379
      - 2380:2380

  e3w:
    hostname: e3w
    image: soyking/e3w:latest
    volumes:
      - ./e3w/config.ini:/app/conf/config.default.ini
    ports:
      - "61101:8080"

按上面的内容执行后会启动两个容器,1个是etcd节点,还有1个etcd的web ui:e3w。注意:要提前准备好e3w的配置文件。

[app]
port=8080
auth=false

[etcd]
root_key=/ui
dir_value=
addr=etcd:2379,etcd:22379,etcd:32379
username=
password=
cert_file=
key_file=
ca_file=

web ui操作和查询到的key会自动加个前缀,即配置文件中的root_key。所以别奇怪为什么查不到你写入的数据。如果开启了权限验证,需要配置username和password。

3.2 命令行操作

命令行操作非常简单,帮助信息清晰易懂。

几个常用命令:

  1. 获取

    etcdctl get <key>
  1. 更新

    etcdctl  put <key> <value>
  1. 删除

    etcdctl del <key>

权限控制相关:

  1. root用户存在时才能开启权限控制

    etcdctl user add root  # 默认带root角色
    etcdctl auth enable
  2. 开启权限控制后执行命令需要用--user指定用户

    etcdctl get <key> --user=<用户>
  1. 使用新用户执行命令,提示没有权限,需要添加指定key的读写权限

    (开启权限控制后,命令后面都需要加--user=<用户>,下面的命令已省略)

    # 创建role:rw_test
    etcdctl role add rw_test
    
    # 给role添加权限: 对/test开头的key有readwrite权限
    etcdctl role grant-permission rw_test readwrite /test --prefix=true
    
    # 给用户添加role
    etcdctl user grant-role <用户> rw_test

3.3 Python操作

python操作etcd需要使用第三方库:python-etcd3

相关文档:https://python-etcd3.readthedocs.io/
pip install etcd3  # 注意这里不是python-etcd3

常用操作:

  1. 连接

    client = etcd3.client(host, port, user, password)
  2. 读取

    client.get(key, serializable=False)
    client.get_prefix(key_prefix, revision=None, **kwargs)  # 可以指定revision,limit等参数
  3. watch

    client.watch(key, start_revision=None, **kwargs) # 可以指定revision等参数
    client.watch_prefix(key, start_revision=None, **kwargs) # 可以指定revision等参数
    client.add_watch_prefix_callback(key_prefix, callback, start_revision=None, **kwargs) # 可以指定revision等参数
    # WatchResponse对象中的header属性可以看到revision信息