自己在生产环境用ClickHouse时,最常接到的一类报障就是:账号明明建好了,登录却失败,客户端直接抛一串Code: 516。很多同学第一反应怀疑网络不通、端口没开,折腾半天才发现是认证层的问题。
516这个错误码,在ClickHouse里对应的全称是UNKNOWN_USER,含义非常直接:服务端在验证登录时,找不到一个“与本次连接匹配”的用户。注意我的措辞,不是简单的“用户不存在”,而是“匹配不上”。这里面的差别极大,很多诡异问题就是因为场景不匹配造成的。
这篇文章我会把创建用户、登录、排查516的完整链路拆开讲,包含SQL写法、三种验证方式、隐藏坑点和常见案例,照着流程走,基本上十分钟之内能把问题定位到具体原因上。
1. 先把code 516的逻辑搞清楚
1.1 516在认证链路里到底处于哪一环
ClickHouse的登录认证,服务端是分步骤检查的。第一步先看“用户是否存在”,第二步看“该用户是否允许从当前IP/主机登录”,第三步才校验密码。516出现的位置在前两步。
再叠加另一个高频错误码198(AUTHENTICATION_FAILED)来看,逻辑就非常清晰了:
| 错误码 | 语义 | 产生条件 | 排查方向 |
|---|---|---|---|
| 516 | UNKNOWN_USER | 用户名在系统里找不到,或客户端来源地址不在该用户允许的HOST范围内 | system.users表、HOST配置 |
| 198 | AUTHENTICATION_FAILED | 用户存在,但密码校验不通过 | 密码明文、认证方式、特殊字符转义 |
很多刚接触ClickHouse的人以为516就是“密码错”,其实密码错了通常是198。如果你看到的报错确实是516,先把“用户匹配”作为第一怀疑对象。
这里有个比较误导人的地方:如果你用clickhouse-client连接,很可能因为users.xml里残留了同名用户、或者本机配置文件指向了别的用户目录,导致服务端实际看到的用户名和你以为的用户名不一致,此时报的也是516。所以第一步绝不是盯着密码猜,而是确认服务端视角里的用户到底是什么状态。
1.2 为什么“用户明明存在”还会516
我把实际排查中遇到的情况归类了一下,最常见的是这四类:
第一,用户名拼写或大小写不一致。ClickHouse的用户名是区分大小写的,AppReader和appreader是两个完全不同的用户。手滑把P大写写成小写,登录就会516。
第二,HOST范围没放进去。这个特别典型:很多教程里创建用户时不写HOST子句,或者只写了HOST IP '127.0.0.1',本地测试没问题。一旦从局域网的另外一台机器、或者Docker容器里连接,来源IP不在白名单里,服务端视同“这个用户在当前连接上不存在”,直接516。
第三,连接到了错误的实例或者错误的端口。ClickHouse默认的Native协议端口是9000,HTTP端口是8123。如果机器上装了多个ClickHouse,而客户端实际连接的是另一个实例,服务端里自然没有你创建的那个用户。
第四,users.xml配置目录和SQL管理系统冲突。新版ClickHouse支持CREATE USER创建用户,但如果users.xml里存在同名用户,很多版本会以配置文件为准,SQL里创建的用户虽然写在system.users里,实际登录时却走了配置文件那一套,性质上等于“没这个用户”。
2. 创建用户的正确姿势,从语法到权限
2.1 先确认你的ClickHouse支持SQL管理用户
老版本ClickHouse管理用户只能改users.xml,新版本才支持SQL方式。第一步先看一眼版本:
clickhouse-client --query "SELECT version()"如果你的版本低于20.4,建议直接升级,否则后续所有基于SQL的用户管理都无从谈起。另外还要确认配置里开启了access_management,在config.xml中对应:
<user_directories> <users_xml> <path>users.xml</path> <access_management>1</access_management> </users_xml> </user_directories>如何确认当前生效的用户目录类型?可以查系统表:
SELECT * FROM system.user_directories;通常能看到至少一个local directory或users_xml条目。如果你启用了多个目录,注意它们的优先级,这直接影响后面同名用户冲突的排查。
2.2 创建用户时把HOST写对,能省一半的排查时间
很多人习惯写最简单的:
CREATE USER app_rw IDENTIFIED BY 'password';这句话本身没问题,默认HOST是全部允许。但如果你加了一个HOST限制,就要非常小心。看这个例子:
CREATE USER IF NOT EXISTS app_rw IDENTIFIED WITH sha256_password BY 'P@ssw0rd!' HOST IP '127.0.0.1', '192.168.10.15';这个用户只能在本地或者192.168.10.15这台机器上登录。其他地址全部516。HOST子句支持多种写法:
HOST IP '192.168.%'使用IP网段通配HOST LIKE '%example.com'使用域名后缀匹配HOST REGEXP '^.*\.internal$'使用正则匹配HOST ANY表示允许所有来源HOST NONE表示不允许任何来源
我的建议是:开发环境直接不写HOST,或者写HOST ANY;生产环境宁可写严一点,比如内网网段,但一定要把运维机器、监控机器、ETL机器的IP都列进去,否则上线后第一个接到516报障的就是你自己。
2.3 密码认证方式怎么选
IDENTIFIED BY和IDENTIFIED WITH xx BY的差别,很多人不清楚。直接记结论:
-- 方式1:不指定密码算法,默认使用sha256_password CREATE USER user1 IDENTIFIED BY 'secret'; -- 方式2:显式指定明文密码 CREATE USER user1 IDENTIFIED WITH plaintext_password BY 'secret'; -- 方式3:显式指定SHA256 CREATE USER user1 IDENTIFIED WITH sha256_password BY 'secret'; -- 方式4:旧协议兼容 CREATE USER user1 IDENTIFIED WITH double_sha1_password BY 'secret';生产环境不推荐plaintext_password,因为密码会以明文形式存在元数据里,虽然ClickHouse内部存储也不是纯明文,但风险比SHA256高。double_sha1_password是给老的MySQL客户端协议用的,日常不需要。
如果你用HTTP接口登录,密码走Basic Auth,服务端同样按对应算法验证。这里经常出的坑是:建用户时用了sha256_password,但某些老工具默认用double_sha1_password发密码,两边算法对不上,结果就是“密码明明是对的,却登不进去”。
3. 一次完整创建到登录验证的实操闭环
3.1 从建用户到给权限的完整命令
直接给一套可复制的流程。假设我要建一个只读账号report_ro,用于报表查询:
clickhouse-client进入命令行后,依次执行:
CREATE DATABASE IF NOT EXISTS app_data; CREATE USER IF NOT EXISTS report_ro IDENTIFIED WITH sha256_password BY 'Report#2024!' HOST IP '127.0.0.1', '192.168.10.%'; GRANT SELECT ON app_data.* TO report_ro;这里有个很多人问的点:只给SELECT,用户登录后能执行SELECT 1吗?能。SELECT 1不涉及任何库表,属于不依赖细粒度权限的表达式查询。但如果执行SELECT * FROM app_data.orders,没权限就会报Code: 497之类的“Not enough privileges”错误。
如果你想给他查看当前库表的能力,但不允许改数据,可以再加:
GRANT SHOW TABLES ON app_data.* TO report_ro; GRANT SHOW DATABASES ON *.* TO report_ro;这些权限用完即止,不要顺手把ALTER、DROP也给了。
3.2 本地用Native客户端验证登录
退出当前客户端,用新用户登录:
clickhouse-client --host 127.0.0.1 --port 9000 --user report_ro --password 'Report#2024!'登录成功后执行:
SELECT currentUser();输出应该是report_ro。这里顺带提一下,密码尽量用单引号包住,尤其是包含!、#、$这类符号时,双引号在部分shell里还会做变量展开,单引号最稳。
如果你在命令行里不写--password,客户端会交互式询问,这是最安全的方式,也不容易把密码留在shell历史里。
3.3 HTTP接口的验证方式
除了原生客户端,ClickHouse的HTTP接口也是一种很常用的登录验证手段,而且排查问题时更快:
curl -u report_ro:'Report#2024!' 'http://127.0.0.1:8123/?query=SELECT%201'响应里会直接返回1。如果返回Code: 516,说明HTTP端口上的认证同样失败。这里的技巧是,-u传用户名密码时,客户端会自动做Basic Auth编码,比在URL query里手动写user=xxx&password=yyy安全得多——后者一旦密码里有&或者%,极易被解析错。
顺便说一句,如果返回的是Code: 198,那么用户是匹配到的,只需要改密码或者重新确认密码本身就够了;返回Code: 516,则继续回上面查system.users。
3.4 登录后立刻检查实际权限
登录不是终点,权限对不对才是重点。执行:
SHOW GRANTS FOR report_ro;输出里应该包含你之前的授权。再尝试:
SELECT * FROM app_data.orders LIMIT 1;如果这个查询返回Not enough privileges,检查授权粒度;如果返回Table doesn't exist,则是库表不存在,方向和516已经无关。
4. 登录排查五连问和定位手段
4.1 把516的排查流程固化成五个问题
我每次处理类似报障,都按固定顺序问自己:
- 用户真的在服务端存在吗?去
system.users查一下,用SELECT name, auth_type FROM system.users;一条命令就能看到全部用户。 - 用户允许的来源包含当前连接地址吗?重点看HOST信息。
- 密码一字不差吗?特别注意隐藏字符、大小写、中文输入法混入的全角字符。
- 端口和协议对吗?9000是Native,8123是HTTP,9004是MySQL协议,9005是PostgreSQL协议,别串了。
- 有没有同名用户被
users.xml或其他目录抢先匹配?
列维一下,这五个问题覆盖了我在生产中遇到过的90%的516。尤其是第二条,是最容易被忽略的。现在容器化部署特别多,你在宿主机上测试用的是127.0.0.1,等应用从容器里连过来,源IP变成了Docker网段172.x.x.x,如果HOST没放开,516就在所难免。
4.2 用服务端日志直接看认证结果
有时我们很难从客户端完整看到具体原因。最有效的是去服务端看日志:
sudo tail -n 200 /var/log/clickhouse-server/clickhouse-server.log或者:
journalctl -u clickhouse-server --since "10 minutes ago" --no-pager抓关键信息:
Unknown user xxx基本可以定位到“用户名不存在”或“用户被HOST过滤”Authentication failed: password incorrect是密码错误,对应198User xxx is not allowed to connect from host ...非常明确地告诉你HOST不匹配
日志里信息越明确,越没必要猜。拿不准的时候,先看日志再动手改配置,能少走很多弯路。
4.3 两个高频场景复现
场景一:开发同学在测试机创建了用户,本机能登录,但跑批服务器报516。查system.users,发现用户存在;查HOST定义,发现自己只写了HOST IP '127.0.0.1'。跑批机的IP是10.0.0.88,显然不在白名单。解决方式:
ALTER USER report_ro HOST IP '127.0.0.1', '10.0.0.%';改完再登录就正常了。这里用到的是ALTER USER,不用重建用户。
场景二:用户在SQL里创建了app_rw,测试也能登上。隔天重启后,登录报516。查system.users,用户还在,但system.user_directories里显示有两个目录,users.xml里也定义了一个同名但密码不同的app_rw。此时配置文件优先级盖过了SQL用户。解决方式:从users.xml中移除同名用户配置,重启服务端即可。
5. 高频踩坑清单和一些额外提醒
5.1 速查表
| 故障场景 | 报错样例 | 核心原因 | 处理建议 |
|---|---|---|---|
| 用户不存在 | Code: 516, Unknown user | 用户名写错/未创建 | system.users确认 |
| 来源IP被限制 | Code: 516,日志里有not allowed | HOST范围不含当前IP | ALTER USER ... HOST IP |
| 密码错 | Code: 198 | 密码明文不一致 | 重置密码 |
| 密码含特殊字符 | Code: 198或连接直接失败 | shell转义、URL编码 | 单引号包裹、用-u |
| 连错端口 | 连接超时或516 | 9000/8123混淆 | 确认端口协议 |
| 同名配置覆盖 | 516但system.users有记录 | users.xml同名 | 清理配置文件 |
| 算法不一致 | 198 | 客户端与用户认证方式不匹配 | 统一用sha256_password |
| 忘记授权 | Code: 497 | 用户存在密码对但无权限 | GRANT相应权限 |
我建议把这个表存下来,报一个516过一个,效率提升非常明显。
5.2 两个容易被忽视的运维细节
第一个,ClickHouse的SQL用户元数据默认存放在/var/lib/clickhouse/access/目录下,做备份的时候一定要把这个目录也带上。很多备份策略只备份data目录,恢复到一个新实例后发现所有SQL用户全部丢失,应用又回退到匿名登录,风险很大。
第二个,改造密码时用ALTER USER而不是先DROP USER再重建。DROP会同时级联清理授权,重建后如果忘记重新GRANT,你还会遇到另一个看似“权限丢失”的问题。能原地改就原地改:
ALTER USER report_ro IDENTIFIED WITH sha256_password BY 'NewPass9!';5.3 还有一个顺带提一下的重启坑
如果你之前手工动过system库下的表,比如在default库里建过一个同名的query_log表,或者多次手动创建系统表副本,重启ClickHouse时可能碰到failed to flush system log ... already exists这种报错。这个问题跟516没有直接关系,但两者经常一起出现在同一批维护操作里。原因多半是系统表元数据与内部附加表发生了命名冲突,处理时需要把多余的同名表清理或重命名后再启动。操作前强烈建议先备份/var/lib/clickhouse下的相关目录,尤其是不应随意改动系统库表结构。
6. 从实际案例中提炼出的最后建议
处理516这类认证问题,最忌讳的就是“觉得密码没问题”然后反复重试。每次重试都会在服务端产生一条认证日志,但不会有任何实质性帮助。正确做法是:先确认用户匹配,确认来源地址,再谈密码。
我个人的操作习惯是三层验证:先SELECT * FROM system.users确认用户存在,然后SHOW GRANTS FOR 用户名确认权限,再分别用Native客户端和HTTP接口各登一次。三层都过,基本可以判定问题不在认证层;哪一层不过,就对应处理哪一层。这个方法在几次线上故障中帮我快速规避了“猜密码”的弯路,也推荐给你。
如果你现在正好被516卡住,按照上面的顺序查一遍,大概率能直接找到原因。如果你是刚部署ClickHouse,前面提到的创建语法和users.xml冲突问题,建议提前读完再开工,能省下不少折腾时间。