记录我们在ZooKeeper上的资源管理实践

并不是专业运维,如果有更好的想法欢迎讨论。

是的,最近我们对ZooKeeper做了一些调整,而这一切都源自一次业务花里胡哨的操作。

当我们以为稳了的时候,往往就有业务从中作妖。

这不就来了吗。。

中间还涉及到ZK的升级、兼容性测试、压测等,之后会通过一篇新的文章来介绍。

背景

有一天运维突然收到告警,某个业务服务实例疯狂请求ZK,同时ZK节点总数飞快增长。

可以看到节点数已经快到80万了,立刻联系业务进行确认,最终定位到和业务定时任务有关。

  • 根本原因

业务有个定时任务需要对海量用户进行推送,于是将用户分批拆成小任务交由不同的节点来处理,实现多个节点同时处理,其中拆分出来的小任务就是存在ZK里的。

而这个过程存在两个设计问题:

1、拆分的小任务在ZK中创建的是永久节点,并且小任务处理完后未删除节点;
2、当用户量快速膨胀,拆分出的小任务就会非常多,导致ZK中节点数快速增长。

日积月累,节点数快速增长,而业务实例也要不断的读取ZK拉取节点来获取小任务,容器Read流量突增。

  • 引发的思考

从结果来看,对ZK的性能没有实际的影响。但是,这个过程也引发了我们的思考,过往我们对于基础组件的管理是粗放的,没有规则限制,也没有资源隔离,出了问题无法快速屏蔽影响,并且所有的业务都在使用同一个ZK集群,鸡蛋都在同一个篮子里。

因此是需要一定的控制、规则来对基础组件进行管理的,ZK本身也提供了一些默认的资源管理能力。

ZK资源管理能力

从ZK官方给出的配置及功能来看,对于资源的管理主要通过ACLquota,其中ACL实现基础的访问控制,quota实现配额控制。

ACL

ZK通过ACL机制(访问控制列表)来实现对ZNode的访问控制,包含身份认证及权限两个部分。

  • 身份认证

ZK提供了多种内置认证模式,包含worldauthdigestipx509,同时也提供自定义可插拔的鉴权插件入口。

认证模式 描述
world 新节点默认认证模式,代表任何人均可操作
auth 使用当前会话中的认证用户
digest 使用用户名:密码字符串生成MD5哈希,然后将其用作ACL标识
ip 使用客户端主机IP作为ACL标识
x509 使用客户端X500主体作为ACL标识

看上去模棱两可,我们一会儿通过例子来理解会更简单。

  • 访问控制权限

权限现类似Unix文件访问权限,分为一下几种类型:

Permission Description
CREATE 创建子节点
READ 可以从节点获取数据并列出其子节点。
WRITE 可以设置节点的数据
DELETE 可以删除子节点
ADMIN 可以设置权限
  • ACL配置

ACL的配置通过get/setAcl完成ZNode权限配置,当需要对具有权限的ZNode进行操作时,可以通过addauth进行身份认证。

以比较常见前三种的认证模式为例,看看实际的例子。

  1. world模式

创建的新ZNode默认是这个认证模式,即所有人均可操作,同时权限包含全部crwda权限。

1
2
3
4
5
[zk: zk-fat-biz.sr.cvte.cn:2181(CONNECTED) 5] create /test     # 创建新的节点/test
Created /test
[zk: zk-fat-biz.sr.cvte.cn:2181(CONNECTED) 6] getAcl /test     # 新节点所有人可操作,具有cdrwa权限
'world,'anyone
: cdrwa
  1. auth模式

该模式下,会自动以当前会话认证的用户进行权限配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[zk: 127.0.0.1:53412(CONNECTED) 2] addauth digest for_test:12345  #添加会话身份认证
[zk: 127.0.0.1:53412(CONNECTED) 3] whoami  # 查询当前会话认证方式及用户信息
Auth scheme: User
ip: 10.244.0.1
digest: for_test
[zk: 127.0.0.1:53412(CONNECTED) 4] create /test  # 创建新的节点/test
Created /test
[zk: 127.0.0.1:53412(CONNECTED) 5] getAcl /test  # 新节点所有人可操作
'world,'anyone
: cdrwa
[zk: 127.0.0.1:53412(CONNECTED) 6] setAcl /test auth::cwrd # 设置/test节点权限,自动配置为当前会话用户可操作
[zk: 127.0.0.1:53412(CONNECTED) 7] getAcl /test # 查看新节点ACL配置,已经更新
'digest,'for_test:x
: cdrw

这里有一点需要特别注意,单个会话可以添加多个身份认证,如果此时使用auth模式进行ACL配置,那么会为已认证的多个用户身份都添加上权限

所以,在使用auth模式时,建议先通过whoami确认当前会话认证的用户身份,再进行ACL配置。

  1. digest模式

这种模式就比较简单了,直接为指定的用户设置节点权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[zk: 127.0.0.1:53412(CONNECTED) 5] whoami   #当前无用户身份认证
Auth scheme: User
ip: 10.244.0.1
[zk: 127.0.0.1:53412(CONNECTED) 6] create /test  #创建新的节点/test
Created /test
[zk: 127.0.0.1:53412(CONNECTED) 7] getAcl /test  #新节点所有人可操作
'world,'anyone
: cdrwa
[zk: 127.0.0.1:53412(CONNECTED) 8] setAcl /test  digest:for_test:bSFrX+8cMlx0KSVxu5vJhdp61wg=:crwd  #digest模式为for_test用户设置节点权限
[zk: 127.0.0.1:53412(CONNECTED) 9] getAcl /test  #由于当前会话无身份认证,所以无权限
Insufficient permission : /test
[zk: 127.0.0.1:53412(CONNECTED) 10] addauth digest for_test:12345  #增加用户身份认证
[zk: 127.0.0.1:53412(CONNECTED) 11] getAcl /test  #再次尝试获取节点ACL配置
'digest,'for_test:x
: cdrw

其中,digest模式配置ACL时,需要配置编码的认证信息,方式如下:

1
2
3
#for_test:12345,即对{用户名:密码}整个内容进行编码
echo -n for_test:12345 | openssl dgst -binary -sha1 | openssl base64
bSFrX+8cMlx0KSVxu5vJhdp61wg=

注意事项!!!,子节点不会继承ACL配置,意味着新创建的子节点为所有人可操作

Quota

Quota机制是ZK对ZNode及其子节点的数量、大小进行控制的一种方法,从3.5.0版本就开始支持,但是在旧版本中仅支持软规则Quota,即当ZNode及其子节点的数量、大小超过限制后,仅会在日志中打印异常,不会强行限制令操作失败,直到3.7.0版本才支持硬规则Quota

1
2
3
[zk: 127.0.0.1:53412(CONNECTED) 13] setquota
org.apache.commons.cli.MissingOptionException: Missing required option:
[-n num soft quota, -b bytes soft quota, -N num hard quota, -B bytes hard quota]
quota参数取值 参数描述
“n” 节点下能够创建的最大子节点数量,软规则,超过阈值仅日志告警
“b” 节点下能够创建的数据总大小的上限,单位字节,软规则
“N” 节点下能够创建的最大子节点数量,硬规则,超过阈值则操作失败
“B” 节点下能够创建的数据总大小的上限,单位字节,硬规则

其中,只有设置了Quota后,才可以通过listquota查看节点下的子节点数量、大小。

注意事项!!!,在新版本ZK中(3.8.1已测试)需要在ZK配置文件中,加入enforceQuota=true,才能使用硬规则Quota

当前节点也会占用Quota配额。

[zk: 127.0.0.1:53412(CONNECTED) 18] set /test test_content # 无子节点,仅设置值 [zk: 127.0.0.1:53412(CONNECTED) 15] setquota -n 100 /test [zk: 127.0.0.1:53412(CONNECTED) 19] listquota /test absolute path is /zookeeper/quota/test/zookeeper_limits Output quota for /test count=100,bytes=-1=;byteHardLimit=-1;countHardLimit=-1 Output stat for /test count=1,bytes=12 # 节点数量占用1,节点大小占用12字节

SuperDigest

SuperDigest是一种特殊的身份认证方式,以”超级用户”的身份访问任何ZNode节点,对于该用户是不会进行ACL检查的,在3.2.0版本后,该认证方式默认关闭。

若需要开启,则需要在ZK配置文件中进行设置:

1
2
# admin:12345,类似digest模式的配置方式
DigestAuthenticationProvider.superDigest=admin:RIcluWliVzL12y0nV2O1rx6dKLg=

配置后,在ZK中就可以通过该用户操作任意节点。

1
2
3
4
5
6
7
8
9
10
11
[zk: 127.0.0.1:53412(CONNECTED) 0] getAcl /test  #新会话无身份认证,所以无权限
Insufficient permission : /test
[zk: 127.0.0.1:53412(CONNECTED) 1] addauth digest admin:12345  #超级用户身份认证
[zk: 127.0.0.1:53412(CONNECTED) 2] getAcl /test  #获取目录权限成功
'digest,'for_test:bSFrX+8cMlx0KSVxu5vJhdp61wg=   #ACL配置只有for_test用户有权限
: cdrw
[zk: 127.0.0.1:53412(CONNECTED) 3] whoami  #当前用户为super用户
Auth scheme: User
ip: 10.244.0.1
super:
digest: admin

ZK资源管理规则

基于以上能力,我们整理了业务申请、使用ZK资源的管理规则。

业务侧

  1. 限制根目录访问,仅超管账号可操作,限制业务自行创建一级节点;
  2. 各业务需使用ZK时,申请一级节点作为业务独立节点,隔离不同业务,所有业务新建节点均在申请的一级节点下,例如用户中心服务,则申请/usercenter,业务新建节点为/usercenter/abc
  3. 为业务分配身份认证信息,并为业务一级节点配置ACL,仅授权业务身份可访问(仅有cwrd权限);
  4. 为业务一级节点配置Quota配额,限制业务节点资源使用,包含子节点数量及大小限制。

通过上面的方式,可以实现业务资源的隔离和限制。

运维侧

  • 业务节点管理

当业务节点有了ACL配置,那就面临一个问题,运维在必要时如何操作业务节点,例如之前提到业务新建大量节点,需要运维介入进行删除。

在通过一级节点资源隔离后,业务节点对应不同的身份认证,运维操作需要根据不同的业务使用不同的身份认证,这对运维来说并不友好。

这就使用上了SuperDigest,我们为运维配置了”超级用户”,可操作所有节点,在必要时可以通过该用户进行运维行为。

  • 监控和告警

同时还有一个运维需要面对的问题,就是监控和告警。Quota配额决定的业务使用资源的上限,但是运维侧需要对业务资源的使用情况有感知,这就涉及到Quota的监控。

我们的组件监控主要通过组件exporter+Prometheus+Grafana来完成监控数据的采集、存储和展示。

ZooKeeper中,有对应的Quota Metrics来反映当前的Quota配额使用情况,从3.8.0版本开始提供。

对,没错,3.8.0才有。。。 3.5.0 quota软规则 3.7.0 quota硬规则 3.8.0 quota metrics to prometheus

看了一下源码,暴露出来的指标包含一级节点的Quota配额限制、使用量及异常数。

其中NAMESPACE可以理解为一级子节点,有了Quota Metrics就可以进行监控,当某个一级节点的配额使用超过一定比例时,就可以进行告警。

自动化资源申请

有了ZK资源管理规则,业务需要使用时就会找到运维进行申请,整个配置的过程还是比较繁琐且容易出错的,所以为了减轻运维同学的负担,同时避免人工误操作带来的错误或故障,我们提供了自动化的资源申请流程,业务可自行进行申请。

业务提供流水线申请ZK资源,技术经理审核完后自动执行申请操作,返回给业务对应的一级目录信息、身份认证信息。

总结

通过这次实践,我们整理了一些基础的ZooKeeper资源管理规则,以此来规范我们的资源申请和使用。

从基础资源和设施的管理角度上看,我们其实还有很多部分是空白的,更多都是提供资源,但是并不做管理,当没有规范约束时,就容易带来问题。

对于组件的管理和建设包含很多方面,未来的路还很长,需要一点点去弥补。