Skip to content
master
Switch branches/tags
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
 
 
pkg
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

zk2etcd

zk2etcd 是一款同步 zookeeper 数据到 etcd 的工具

项目背景

在云原生大浪潮下,业务都逐渐上 k8s,许多业务以前使用 zookeeper 作为注册中心,现都逐渐倾向更加贴近云原生的 etcd。

在业务向云原生迁移改造的过程中,可能需要将 zookeeper 中注册的数据同步到 etcd,且可能需要两者长期共存,实时同步增量数据。

本项目就是为了解决 zookeeper 数据同步到 etcd 而生。

部署

examples 下提供部署到 k8s 的 yaml 示例。

变更历史

参考 CHANGELOG

核心原理与方案

与其它同步工具共存

当其它同步工具 (比如etcd同步工具) 也在 etcd 的相同目录下进行同步时,如何保证不干扰 (不勿删其它工具写入的 key) ?

zk2etcd 引入 redis 存储,记录本工具向 etcd 写入过的 key,在同步时如果要删除 etcd 中的数据,先判断一下该 key 是否为本工具写入,如果不是就忽略,避免勿删其它工具写入的 key。

增量同步

zk2etcd sync 运行期间会 watch zookeeper 的数据变更,并将其同步到 etcd 中,原理图:

一些技术难点与实现细节:

  • 关于 watch: zookeeper 存储是树状结构,所以需要通过递归的 list watch children 遍历所有节点才能实现 watch 所有数据的变化。
  • 关于新增: 在没有提前知道完整 key 的情况下,zookeeper 无法感知新增事件,只能收到 ChildrenChanged 事件,zk2etcd 在收到 ChildrenChanged 事件后拿到所有 children 并与 etcd 中数据进行计算对比,得出哪个 key 是新增的,并将其 put 到 etcd 中。
  • 关于删除: zookeeper 的 list children watch 可以收到本节点被删除的事件,由于 zk2etcd 通过递归对所有节点进行了 list children watch,可以感知到所有节点的删除的事件,当收到后会同步删除 etcd 中的数据。
  • 每次对 etcd 进行增删时也会同步增删 redis 中的数据,以便全量同步发生删除时判断 key 是否为本工具写入。
  • 由于 zookeeper 的 watch 是一次性的,所以每次收到事件后又必须重新 watch,两次 watch 之间理论上是可能丢失件的,这也是 zookeeper 本身 watch 机制的缺陷。
  • 由于 zookeeper 是树状结构,而 etcd v3 是扁平结构,etcd 无法像 zookeeper 一样 list children,也就无法取到某个目录下的子节点,只能按照 prefix 来 list 所有 key,所以无法对比出某个目录下,zk 中没有,etcd 中有的情况。当 zookeeper 的某个 delete 事件丢失后,只能依赖全量同步来 fix。

diff 与全量同步

zk2etcd diff 可以对比出 zookeeper 与 etcd 中数据中的数据差异,以便观察同步一致性的情况,原理图:

  • 通过拉取 zookeeper 与 etcd 中的全量数据进行对比,查出 etcd 中哪些 key 是多余的(应该删除),哪些是缺失的(应该补齐)。
  • 判断多余 key 会查 redis,判断多余的 key 是否为本工具写入,如果不是则将其移出 (忽略该 key),认为它不是多余的。

zk2etcd diff 带上 --fix 则表示进行手动全量同步一次,即拿到 diff 结果,删除 etcd 中多余的 key,补齐缺失的 key。

zk2etcd sync 在启动时会全量同步一次,然后会周期性全量同步一次来 fix 掉 zookeeper 与 etcd 中数据存在的一些差异。

用法

zk2etcd 主要提供 syncdiff 两个核心的子命令:

  • sync 用于同步 zookeeper 中数据到 etcd,启动时全量同步一次,然后增量实时同步,期间还会定期全量同步进行 fix,避免 watch zookeeper 时遗漏某些 event 导致数据不一致。
  • diff 用于检测 zookeeper 与 etcd 中数据的差异,也提供 fix 可选项来手动 fix 数据差异。

具体启动参数及其解释详见 help:

$ zk2etcd sync --help
sync data from zookeeper to etcd

Usage:
  zk2etcd sync [flags]

Flags:
      --concurrent uint                   the concurreny of syncing worker (default 50)
      --enable-event-log                  enable event log
      --etcd-cacert string                verify certificates of TLS-enabled secure servers using this CA bundle
      --etcd-cert string                  identify secure client using this TLS certificate file
      --etcd-key string                   identify secure client using this TLS key file
      --etcd-servers string               comma-separated list of etcd servers address
      --fullsync-interval duration        the interval of full sync, set to 0s to disable it (default 5m0s)
  -h, --help                              help for sync
      --log-level string                  log output level,possible values: 'debug', 'info', 'warn', 'error', 'panic', 'fatal' (default "info")
      --redis-password string             redis password
      --redis-server string               redis server address
      --redis-username string             redis username
      --zookeeper-exclude-prefix string   comma-separated list of zookeeper path prefix to be excluded (default "/dubbo/config")
      --zookeeper-prefix string           comma-separated list of zookeeper path prefix to be synced (default "/dubbo")
      --zookeeper-servers string          comma-separated list of zookeeper servers address
compare keys between zk and etcd

Usage:
  zk2etcd diff [flags]

Flags:
      --concurrent uint                   the concurreny of syncing worker (default 50)
      --enable-event-log                  enable event log
      --etcd-cacert string                verify certificates of TLS-enabled secure servers using this CA bundle
      --etcd-cert string                  identify secure client using this TLS certificate file
      --etcd-key string                   identify secure client using this TLS key file
      --etcd-servers string               comma-separated list of etcd servers address
      --fix                               set to true will fix the data diff between zk and etcd
  -h, --help                              help for diff
      --log-level string                  log output level,possible values: 'debug', 'info', 'warn', 'error', 'panic', 'fatal' (default "info")
      --max-round int                     max diff round, must >= 1 (default 1)
      --redis-password string             redis password
      --redis-server string               redis server address
      --redis-username string             redis username
      --round-interval duration           the interval of every diff round (default 3s)
      --zookeeper-exclude-prefix string   comma-separated list of zookeeper path prefix to be excluded (default "/dubbo/config")
      --zookeeper-prefix string           comma-separated list of zookeeper path prefix to be synced (default "/dubbo")
      --zookeeper-servers string          comma-separated list of zookeeper servers address

sync 启动示例:

$ zk2etcd sync
  --redis-server=redis.test.svc.cluster.local:6379 \
  --redis-password=hello:world \
  --etcd-servers=etcd.test.svc.cluster.local:2379 \
  --zookeeper-servers=zookeeper.test.svc.cluster.local:2181 \
  --zookeeper-exclude-prefix=/dubbo/config,/roc/test \
  --zookeeper-prefix=/dubbo,/roc \
  --log-level=info \
  --enable-event-log \
  --fullsync-interval=5m \
  --concurrent=50

若 etcd 需要证书,可以配置下证书相关参数:

  --etcd-cacert=/certs/ca.crt \
  --etcd-cert=/certs/cert.pem \
  --etcd-key=/certs/key.pem

使用 diff 检测数据差异示例:

$ zk2etcd diff
  --redis-server=redis.test.svc.cluster.local:6379 \
  --redis-password=hello:world \
  --etcd-servers=etcd.test.svc.cluster.local:2379 \
  --zookeeper-servers=zookeeper.test.svc.cluster.local:2181 \
  --zookeeper-exclude-prefix=/dubbo/config,/roc/test \
  --zookeeper-prefix=/dubbo,/roc \
  --log-level=info \
  --concurrent=50

若需要 etcd 证书,同 sync 加上证书相关参数即可。

若检测完需要立即 fix,可以再加上 --fix 参数。

部署

zk2etcd 每个版本都有对应的容器镜像 (imroc/zk2etcd),tag 与 版本号一致。

通过 docker 启动示例:

$ docker run -d imroc/zk2etcd zk2etcd sync \
  --redis-server=redis.test.svc.cluster.local:6379 \
  --redis-password=hello:world \
  --etcd-servers=etcd.test.svc.cluster.local:2379 \
  --zookeeper-servers=zookeeper.test.svc.cluster.local:2181 \
  --zookeeper-exclude-prefix=/dubbo/config,/roc/test \
  --zookeeper-prefix=/dubbo,/roc \
  --log-level=info \
  --fullsync-interval=5m \
  --concurrent=50
$ docker run -it imroc/zk2etcd zk2etcd diff \
  --redis-server=redis.test.svc.cluster.local:6379 \
  --redis-password=hello:world \
  --etcd-servers=etcd.test.svc.cluster.local:2379 \
  --zookeeper-servers=zookeeper.test.svc.cluster.local:2181 \
  --zookeeper-exclude-prefix=/dubbo/config,/roc/test \
  --zookeeper-prefix=/dubbo,/roc \
  --log-level=info \
  --fullsync-interval=5m \
  --concurrent=50

部署到 Kubernetes 中的 YAML 示例:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: debug
spec:
  replicas: 1
  selector:
    matchLabels:
      app: debug
  template:
    metadata:
      labels:
        app: debug
    spec:
      serviceAccountName: zk2etcd
      containers:
      - name: debug
        image: imroc/zk2etcd:1.3.0
        volumeMounts:
        - mountPath: /certs
          name: etcd-certs
        command:
        - zk2etcd
        - sync
        - '--redis-server=redis.test.svc.cluster.local:6379'
        - '--redis-password=hello:world'
        - '--enable-event-log'
        - '--etcd-servers=etcd.test.svc.cluster.local:2379'
        - '--zookeeper-servers=zookeeper.test.svc.cluster.local:2181'
        - '--zookeeper-exclude-prefix=/dubbo/config,/roc/test'
        - '--zookeeper-prefix=/dubbo,/roc'
        - '--log-level=info'
        - '--fullsync-interval=5m'
        - '--concurrent=50'
        - '--etcd-cacert=/certs/ca.crt'
        - '--etcd-cert=/certs/cert.pem'
        - '--etcd-key=/certs/key.pem'
     volumes:
     - name: certs
       secret:
         secretName: etcd-certs
         
---
apiVersion: v1
kind: Service
metadata:
  name: zk2etcd
  labels:
    app: zk2etcd
spec:
  type: ClusterIP
  ports:
  - port: 80
    protocol: TCP
    name: http
  selector:
    app: zk2etcd
    
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: zk2etcd

---

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: zk2etcd
rules:
- apiGroups:
  - coordination.k8s.io
  resources:
  - leases
  verbs:
  - '*'

---

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: zk2etcd
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: zk2etcd
subjects:
- kind: ServiceAccount
  name: zk2etcd

关于高可用

zk2etcd sync 支持高可用部署,--leader-elect 默认为 true,表示启用 leader 选举,原理是利用 k8s api 注册租约,各个 zk2etcd 副本竞争租约,只能有一个副本在运行,当运行的副本因某种原因无法正常工作时,其它的副本将竞争租约选举出新的 leader 来继续运行。

目前高可用部署需要将 zk2etcd 部署在 k8s 中,且使用 RBAC 授予 lease 的读写权限(见上面的 yaml 示例)。

API 说明

zk2etcd 提供了几个 HTTP 接口,端口为 80 (暂不支持自定义):

  • /log: 用于动态调整日志级别,POST 方法,只接收一个 level 参数,自带的 log 子命令封装了此调用,使用示例: zk2etcd log --level=debug --zk2etcd-addr=http://zk2etcd:80
  • /debug: golang 的 pprof 接口。使用示例: go tool pprof -http=:9090 http://zk2etcd.test.svc.cluster.local:80/debug/pprof/heap
  • /metrics: 提供 prometheus 指标。

注意事项

  • 若用 k8s 部署,启动参数中若有双引号或其它特殊字符,最外层用单引号括起来,避免 yaml 格式问题导致启动失败,示例: - '--etcd-key="/certs/key.pem"'
  • 在 k8s 中部署且需要使用 etcd 证书,可以将证书、密钥与根证书存储到 secret 中,然后再 pod 中挂载 secret,启动参数引用相应的文件路径。

监控 metrics

zk2etcd 通过 80 端口 (暂时写死) 暴露 metrics,路径 /metrics,包含以下 metrics:

  • zk2etcd_etcd_op_total: etcd 操作次数统计,以下是示例 promql
    • 统计etcd写入次数: sum(irate(zk2etcd_etcd_op_total{op="put"}[2m]))
    • 统计etcd删除次数: sum(irate(zk2etcd_etcd_op_total{op="delete"}[2m]))
    • etcd 操作失败统计: sum by (status,op)(rate(zk2etcd_etcd_op_total{status!="success"}[1m]))
  • zk2etcd_etcd_op_duration_seconds: etcd操作时延统计,以下是示例 promql
    • etcd p95时延: histogram_quantile(0.95, sum(rate(zk2etcd_etcd_op_duration_seconds_bucket[1m])) by (le))
    • etcd p99时延: histogram_quantile(0.99, sum(rate(zk2etcd_etcd_op_duration_seconds_bucket[1m])) by (le))
  • zk2etcd_zk_op_total: zk 操作次数统计,以下是示例 promql
    • zk 操作失败统计: sum by (status,op)(rate(zk2etcd_zk_op_total{status!="success"}[1m]))
    • zk 各类型操作 rps: sum by (op) (irate(zk2etcd_zk_op_total[2m]))
  • zk2etcd_zk_op_duration_seconds: zk操作时延统计,以下是示例 promql
    • zk p95时延: histogram_quantile(0.95, sum(rate(zk2etcd_zk_op_duration_seconds_bucket[1m])) by (le))
    • zk p99时延: histogram_quantile(0.99, sum(rate(zk2etcd_zk_op_duration_seconds_bucket[1m])) by (le))
  • zk2etcd_fixed_total: 全量同步 fix 时,删除或补齐 etcd 数据次数统计(通常是增量同步时丢失event导致的部分数据不同步)
    • 统计因etcd数据缺失导致的数据补齐操作次数: sum(rate(zk2etcd_fixed_total{type="put"}[1m]))
    • 统计因etcd数据多余导致的数据删除操作次数: sum(rate(zk2etcd_fixed_total{type="delete"}[1m]))
    • 统计全部fix操作: sum(rate(zk2etcd_fixed_total[1m]))
  • zk2etcd_zk_conn_total: zk连接数量
  • zk2etcd_zk_connect_total: zk连接次数

grafana 面板示例:

迭代计划

  • 全量同步
  • 增量同步
  • 版本管理 (version 子命令打印详细信息, 版本号/commit/buiddate 等)
  • 日志增强 (自定义 level + json 输出)
  • 并发度控制
  • 数据一致性 (周期性全量检测+watch delete)
  • 支持配置 etcd 证书
  • 支持配置多个 zk prefix
  • 检测 zk 与 etcd 数据差异的 diff 能力
  • 支持 prometheus 指标监控
  • 容灾与自愈能力
  • 支持与 etcd 同步工具共存(不勿删其它同步工具写入的数据,需引入外部存储记录状态)
  • 日志更丰富的自定义(如文件存储,事件日志)
  • 日志记录事务id,将一系列操作串起来
  • 支持动态加载配置(如日志级别)

About

zk2etcd 是一款同步 zookeeper 数据到 etcd 的工具

Resources

Packages

No packages published