Tip
  • Etcd是CoreOS基于Raft开发的 分布式key-value存储 可用于服务发现、共享配置以及一致性保障(如数据库选主、分布式锁等)
  • 在分布式系统中,如何管理节点间的状态一直是一个难题,etcd像是专门为集群环境的服务发现 和注册而设计,它提供了数据TTL失效、数据改变监视、多值、目录监听、分布式锁原子操作等功能,可以方便的跟踪并管理集群节点的状态

1 raft 协议

2 安装

ETCD_VER=v3.4.17
# DOWNLOAD_URL=https://github.com/etcd-io/etcd/releases/download
DOWNLOAD_URL=https://repo.huaweicloud.com/etcd/
rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
rm -rf /tmp/etcd-download-test && mkdir -p /tmp/etcd-download-test
curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp/etcd-download-test --strip-components=1

#rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
#rm -rf /tmp/etcd-download-test


# client 相关的 是给客户端 用的url
# peer 是给集群节点之间通信用的
#我们可以 启动, 使用别的端口, 因为这里我们 k8s已经启动了etcd 了.
# 会在当前目录下创建 一个 default.etcd , 来存储数据
etcd --listen-client-urls 'http://localhost:12379' \
 --advertise-client-urls 'http://localhost:12379' \
 --listen-peer-urls 'http://localhost:12380' \
 --initial-advertise-peer-urls 'http://localhost:12380' \
 --initial-cluster 'default=http://localhost:12380'

# 其他参数
# 节点名称, 不指定的话,默认是default
--name 'default'
--data-dir #不指定的话,默认会在当前目录下 的 default.etcd 目录

2.1 集群

3 etcdctl使用

别名配置
# etcdctl --endpoints=http://localhost:12379 put a b
# pod 安装的etcd ,我们的宿主机上 连接
export ETCDCTL_API=3
etcdctl member list -w table --endpoints=https://localhost:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt  --cert=/etc/kubernetes/pki/etcd/peer.crt --key=/etc/kubernetes/pki/etcd/peer.key

cat >> /etc/profile <<EOF
export ETCDCTL_API=3
alias e="etcdctl --endpoints=https://localhost:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt  --cert=/etc/kubernetes/pki/etcd/peer.crt --key=/etc/kubernetes/pki/etcd/peer.key"
EOF

# 启动一个测试用etcd, 使用这个别名来测试
alias ee="etcdctl --endpoints=http://localhost:12379"
# 节点成员 table 格式
ee member list -w table
# json 格式显示
ee member list -w json
# 状态
ee endpoint status -w table
ee put name tom #设置key value
ee get name # 显示key 和value
    name
    tom
ee put name jack # 有则修改,无则增加
ee get name -w json
# etcd 的key 采用层次化的空间结构, 像 linux的目录一样
ee put /a a1 --debug # debug信息
ee put /a/b ab1
# 带上前缀的 查询
ee get --prefix /a # /a 和/a/b 都会查询到
# 查看key  --keys-only只显示key
ee get / --prefix  --keys-only
# 这个才会显示所有的key,  put name tom 这种的key 也会显示出来
ee get "" --prefix  --keys-only
ee del name # 结果显示 删除的key的个数
ee put /person1/age 11
ee del /person1/age
# 会删除以/a 为前缀 的key 包括/a
ee del /a --prefix

4 版本管理

4.1 各个版本号的含义

# 全新,没有添加过任何数据的etcd
# 我们删除 启动etcd的目录下的 default.etcd 然后重新启动etcd 即可
# 随便 get 一个key, 下面的 key a 是不存在的
ee get a -w json
{"header":
    {
        "cluster_id":17478742799590499669,
        "member_id":14532165781622267127,
        "revision":1,  #作用域为集群,逻辑时间戳,全局单调递增,任何 key 的增删改都会使其自增
        "raft_term":2
    }
}

# 添加
ee put a b
ee get a -w json
{
  "header": {
    "cluster_id": 17478742799590499669,
    "member_id": 14532165781622267127,
    "revision": 2, #全局 版本+1, 任何添加修改的操作都会自增, 即使值没有修改,比如2次 ee put a bb
    "raft_term": 2
  },
  "kvs": [
    {
      "key": "YQ==", # echo YQ== |base64 -d  ==> a
      #作用域为 key, 值为: 创建这个key时集群的全局Revision, 直到删除前都保持不变
      "create_revision": 2,
      #作用域为 key, 值为: 最后修改这个key时 集群的全局Revision
      "mod_revision": 2,
      # 作用域为 key, 这个key刚创建时Version为1,之后每次更新都会自增,即这个key从创建以来更新的总次数
      "version": 1,
      "value": "Yg=="  # echo Yg== |base64 -d  ==> b
    }
  ],
  "count": 1
}

# 修改
ee put a abc
ee get a -w json
{
  "header": {
    "cluster_id": 17478742799590499669,
    "member_id": 14532165781622267127,
    "revision": 3,
    "raft_term": 2
  },
  "kvs": [
    {
      "key": "YQ==",
      "create_revision": 2,
      "mod_revision": 3,
      "version": 2,  #这个key的第2个版本
      "value": "YWJj"
    }
  ],
  "count": 1
}

ee put a hello
ee get a -w json
{
  "header": {
    "cluster_id": 17478742799590499669,
    "member_id": 14532165781622267127,
    "revision": 4,
    "raft_term": 2
  },
  "kvs": [
    {
      "key": "YQ==",
      "create_revision": 2,
      "mod_revision": 4,
      "version": 3,
      "value": "aGVsbG8="
    }
  ],
  "count": 1
}


ee put hello world
ee get hello -w json
{
  "header": {
    "cluster_id": 17478742799590499669,
    "member_id": 14532165781622267127,
    "revision": 5,
    "raft_term": 2
  },
  "kvs": [
    {
      "key": "aGVsbG8=",
      "create_revision": 5, #等于 当前的全局revision
      "mod_revision": 5, #等于 当前的全局revision
      "version": 1,
      "value": "d29ybGQ="
    }
  ],
  "count": 1
}
ee put b bb

4.2 获取对应版本的数据

# rev=0 表示获取当前版本的k v
ee get a --rev=0
    a
    hello
# rev 指定的是全局版本 , 意思是在那个版本的时候, 这个key a 的值是多少
ee get a --rev=2
    a
    b
# 所以 这个得到的是空. 全局rev=1的时候 a 还没创建. 创建的时候revision=2
ee get a --rev=1

# 等到的是 hello, 因为这个版本下
ee get a --rev=5
    a
    hello
ee get a --rev=6 # 这个也是hello ,显然 全局版本是6的时候, a的值肯定是hello

4.3 从指定版本号起监听

# 可以看到指定版本号以及之后的所有版本数据, 如果版本已经被compact
# 会提示 required revision has been compacted
# 看后面的容量管理
ee watch a --rev=1 -w json

5 容量管理

# 删除 default.etcd
# 启动时我们 设置etcd存储大小 16M
etcd --listen-client-urls 'http://localhost:12379' \
    --advertise-client-urls 'http://localhost:12379' \
    --listen-peer-urls 'http://localhost:12380' \
    --initial-advertise-peer-urls 'http://localhost:12380' \
    --initial-cluster 'default=http://localhost:12380' \
    --quota-backend-bytes=$((16*1024*1024))

ee put a b
# 修改key x ,因为版本的原因,每次修改数据都会保存,这样存储就满了
# 满了后再put ,会报错
while true
do
    dd if=/dev/urandom bs=1M count=1 | ee put x || break
done
# 查看状态 , 可以看到Errors alarm:NOSPACE
ee endpoint status -w table

ee alarm list # 查看报警信息
memberID:14532165781622267127 alarm:NOSPACE

# 压缩版本
# 表示压缩到 全局版本的第10个版本, 也就是, 10版本前的key,只保留里面最新版本的那个值
# 然后版本归为 10
# 如果是compact 当前版本, 那么就是压缩成到最新的版本,key对应旧版本的数据就都删除了,只保留最新版本的值.
ee compact 10
ee get a --rev 10 # 如果指定版本的话, 是指定10 不是2
# 清理碎片
ee defrag

# 清理alarm  解除报警,没有errors
ee alarm disarm

5.1 compact配置

单独的etcd

/etc/kubernetes/manifests/etcd.yaml 添加2个配置可以控制压缩

--auto-compaction-retention=1  # 每一个小时自动压缩
--auto-compaction-mode=periodic # 模式是时间间隔, 另外一个模式是reversion数量
k8s中的etcd

上面那样修改是无效的, 这个压缩的操作,k8s代码里给写了1
staging/src/k8s.io/apiserver/pkg/storage/storagebackend/factory/etcd3.go#startCompactorOnce

cmd/kube-apiserver/app/options/options.go
func NewServerRunOptions() *ServerRunOptions {
  s := ServerRunOptions{
      GenericServerRunOptions: genericoptions.NewServerRunOptions(),
      Etcd:                    genericoptions.NewEtcdOptions(
          // NewDefaultConfig() 里会看到 DefaultCompactInterval = 5 * time.Minute
                    storagebackend.NewDefaultConfig(kubeoptions.DefaultEtcdPathPrefix, nil)
                                ),
      ..
  }
}

6 关于k8s中的相关

Back to top