当 Nacos 说"配置不存在"时,它可能在撒谎

Nacos 说配置不存在,其实是鉴权失败。NacosConfigDataLoader 把 403 和超时统一包装成了 not found。本篇给出区分这几种故障的判据。

一、症状:一个把人引向错误方向的报错

部署时四个服务里的三个启动失败,日志给出:

Config data resource 'nacos:user-service.yml?group=DEFAULT_GROUP' via location
'nacos:user-service.yml?group=DEFAULT_GROUP' does not exist

"配置不存在"。 于是我去 Nacos 控制台确认——配置明明在

用户贴出控制台截图:配置列表共 3 条 —— user-service.yml / business-service.yml / order-service.yml,group = DEFAULT_GROUP,格式 YAML,命名空间 dev。

配置在,dataId 对,group 对,namespace 对,但应用说"不存在"。

这就是那种会让你开始怀疑自己眼睛的报错。

二、根因:一个 catch (Exception) 吞掉了所有真相

翻 SCA 2025 的源码,NacosConfigDataLoader#doLoad()

try {
    // 真正去 Nacos 拉配置
    configService.getConfig(...);
} catch (Exception e) {
    log.error("Error getting properties from nacos: " + resource, e);   // ← 真实原因在这里
    if (!resource.isOptional()) {
        throw new ConfigDataResourceNotFoundException(resource, e);      // ← 抛出去变成了"不存在"
    }
}

catch (Exception e) —— 注意是 Exception,不是 NacosException 或更具体的类型。

于是任何异常都被包装成同一句 "does not exist":

真实原因 报错文案
403 鉴权失败 does not exist
连接超时 does not exist
DNS 解析不了 nacos 这个主机名 does not exist
配置真的没有 does not exist

四种完全不同的故障,同一个错误信息。

判据:真异常在上一行

docker logs api-cloud-user 2>&1 | grep -B2 -A12 "Error getting properties from nacos"

必须 grep 那一行 log.error("Error getting properties from nacos: ..."),它打印的堆栈里才有 NacosException 的真实错误码。

⚠️ 不要用 --tail=N 直接看。

那一行 Error getting properties from nacos 总是出现在 APPLICATION FAILED TO START 之前,而 --tail 是从末尾往前截——刚好会把它截掉,只留下误导性的 does not exist。本项目在这个细节上多花了一轮。

三、真正的原因:Nacos 开了鉴权,客户端却匿名访问

grep 到那一行之后,真相就清楚了:403

Nacos 侧配置了:

NACOS_AUTH_ENABLE: "true"
NACOS_AUTH_TOKEN: ${NACOS_AUTH_TOKEN}
NACOS_AUTH_IDENTITY_KEY: ${NACOS_AUTH_IDENTITY_KEY}
NACOS_AUTH_IDENTITY_VALUE: ${NACOS_AUTH_IDENTITY_VALUE}

而 Spring Cloud Alibaba 客户端在没有显式配置凭据时,默认值是空串

// 源码行为等价于
properties.put(USERNAME, env.getProperty("...username", ""));
properties.put(PASSWORD, env.getProperty("...password", ""));

空串 = 匿名访问 = 被服务端拒绝。

最反直觉的一点:两个客户端的回退前缀不同

实测 SCA 2025.0.0.0 的属性读取路径,这是本节的核心知识点

客户端 绑定属性 回退读取
config spring.cloud.nacos.config.username / .password ${spring.nacos.username:}
discovery spring.cloud.nacos.discovery.username / .password ${spring.cloud.nacos.username:}

注意 config 侧的回退前缀是 spring.nacos,不是 spring.cloud.nacos

这个差异来自 NacosPropertiesPrefixer 的默认值。也就是说,如果你只写了全局的:

spring:
  cloud:
    nacos:
      username: nacos
      password: xxx

config 客户端读不到,因为它回退时找的是 spring.nacos.username

稳妥的写法:三处都写

本项目的做法是"全写一遍",不依赖任何回退逻辑:

spring:
  cloud:
    nacos:
      # 全局(discovery 的回退会读这里)
      username: ${NACOS_USER:nacos}
      password: ${NACOS_PASSWORD:nacos}
      config:
        server-addr: ${NACOS_ADDR:127.0.0.1:8848}
        username: ${NACOS_USER:nacos}
        password: ${NACOS_PASSWORD:nacos}
      discovery:
        server-addr: ${NACOS_ADDR:127.0.0.1:8848}
        username: ${NACOS_USER:nacos}
        password: ${NACOS_PASSWORD:nacos}

compose 侧统一注入:

x-common-env: &common-env
  NACOS_USER: ${NACOS_USER:-nacos}
  NACOS_PASSWORD: ${NACOS_PASSWORD:-nacos}

凭据与推送脚本 setup-nacos-config.sh 读的是 .env同一组变量,所以改密码只需动一处。

四、顺带纠正一个错误结论

修这个坑的过程中,还有一个发现值得单独说。

我此前记录过一条结论:「Nacos 3.x 的 /v3/client/* 客户端接口免鉴权,可用于排查」

当时 status.shsetup-nacos-config.sh 的回读逻辑都基于这个假设写的,结果:

setup-nacos-config.sh    三条配置全部 [WARN] 回读内容与预期有差异
make status              注册检查依旧全 [MISS]
                         [WARN] Nacos 就绪探针返回 HTTP 404

三个现象,同一个根因。 直接从 nacos-server.jar 里反查 controller 的注解(方法见 A2 篇),确认:

// InstanceOpenApiController
@Secured(action = READ, apiType = OPEN_API)      // ← 名字叫 OpenApi,但要鉴权
@GetMapping("/list")
public Object list(...)

"OpenAPI" 指的是"对外提供的 API",不等于"匿名可访问"。 只要 NACOS_AUTH_ENABLE=true,不带 accessToken 一律 403。

那条"免鉴权"结论的来源是把 Nacos 3.0 的文档口径当成了 3.2.4 的实测结果。已修正。

五、修复清单

文件 改动
四个 application.yml 全局 + config + discovery 三处都写 username/password
docker-compose.yml x-common-env 注入 NACOS_USER / NACOS_PASSWORD
status.sh 注册检查带 accessToken;失败时打印服务端 message,区分"鉴权失败"与"真没注册"
setup-nacos-config.sh 回读带 token;异常时打印前 5 行原文(不再只有"有差异"三个字)
deploy/README.md 示例命令补 accessToken

最后一条很重要:让脚本自己说清楚是哪种失败

# ❌ 只报结论
echo "[MISS] user-service"

# ✅ 区分失败类型
if [ -z "$token" ]; then
  echo "[WARN] 无法获取 accessToken,注册检查结果不可信"
elif echo "$resp" | grep -q 'user not found'; then
  echo "[ERROR] 鉴权失败:$resp"
else
  echo "[MISS] user-service"
fi

"鉴权失败"和"服务没注册"必须能被区分。 否则运维脚本本身就成了新的误导源——这正是本系列 E 组那篇要讲的主题。

六、复盘

技术层面

  • NacosConfigDataLoadercatch (Exception) 把一切异常统一成"配置不存在",这是设计缺陷,不是你的配置有问题
  • SCA 客户端 config / discovery 两个客户端的凭据回退前缀不同(spring.nacos vs spring.cloud.nacos),是最容易踩的一点
  • 排查命令必须 grep 上游的 Error getting properties from nacos,且不能用 --tail 截断

方法层面

当报错文案与客观事实冲突时("不存在" vs 控制台里明明有),不要怀疑事实,要去找那句文案的产生位置。

本项目里这类"撒谎的报错"至少有三个(另见 C2 的 Whitelabel Error Page、D2 的 Started xxx in N seconds),它们的共同特征是:错误信息由某个兜底分支产出,不携带真实原因。

修完这个坑之后,make status 里那行 token 长度: 108 成了最有价值的输出——它用最简单的方式证明了"鉴权这一环已经通了"。

原创文章,作者:ECHO陈文,如若转载,请注明出处:https://www.luweipai.cn/ops/1789976420/

  • 0