1. 先想清楚:为什么我不用压缩包方式装 Nacos
"Docker 安装 Nacos"这个事,看着像一条命令的事,但我这些年带过的人里,十个有八个第一次都会卡在同一个地方——容器起来了,控制台打不开。原因往往不在 Nacos 本身,而在于我们习惯了"解压、改配置、敲启动脚本"这套思路,没有把容器里的端口、数据、配置这三件事重新捋一遍。这篇就按我自己的实操路径来写:从环境盘点、镜像选择、单机落地、集群扩展,一直到踩坑排查,尽量把每一步"为什么要这么干"讲透,让刚接触注册中心的新同学能照着跑通,也让已经用了几年的人能捡到几个细节。
先给结论:用容器跑 Nacos,本质是把"一个需要 JDK 环境、需要外置数据库、需要固定端口组"的 Java 服务,封装成一个可复制、可回滚、可批量下发的单元。它解决的不是"能不能装"的问题,而是"装完之后半年,你还能不能一键把它恢复成今天这个样子"的问题。适合谁看:正在给微服务项目搭注册与配置中心的后端同学、需要给测试环境快速复刻一套中间件的运维同学,以及手里只有一台 4 核 8G 小机器、想让多个中间件共存的独立开发者。
1.1 三种常见部署方式的取舍
很多人纠结"到底用不用 Docker",我的看法是先把三种做法摆到桌面上对比,答案自然就出来了。
| 维度 | 压缩包直装 | 容器 + 内嵌数据库 | 容器 + 外置数据库 |
|---|---|---|---|
| 环境依赖 | 需要匹配 JDK 版本,需要自己写 systemd | 只需要 Docker | 只需要 Docker,另加一个 MySQL |
| 数据可靠性 | 靠人工备份 | 容器删了配置就没了 | 数据落在 MySQL,容器可随意重建 |
| 版本切换 | 改软链、改端口、重启 | 换镜像标签即可 | 换镜像标签即可 |
| 集群扩展 | 手工同步配置文件 | 不适合 | 改环境变量即可 |
| 上手成本 | 中 | 低 | 中 |
我自己的习惯是:学习、写 Demo 用"容器 + 内嵌数据库",任何要交给别人用的环境一律"容器 + 外置数据库"。原因很直白——内嵌数据库是单文件存储,只允许一个进程写,天生不支持集群;而配置中心里的东西,往往比代码还金贵,丢一次要几天才能回忆回来。
1.2 需求先定,再动手
动手之前先回答三个问题,能省掉后面一半的返工:
- 这套环境给谁用?只有自己用,单机模式足够了;要给多人共用一个测试环境,就要考虑鉴权开关和账号隔离。
- 配置数据要不要长期保留?要保留就必须外置 MySQL,别图省事用内嵌库。
- 将来会不会扩成多节点?会的话,从第一天起就把端口规划、集群配置项、反向代理位置想好,不要等到三个节点跑起来才发现端口偏移规则没算对。
这三个问题对应的就是后文的单机篇和集群篇,你可以按需跳读,但我建议至少把端口规划那一段看完。
2. 环境盘点与镜像选择:别在第一步就埋雷
装中间件最容易出问题的地方,往往不是中间件本身,而是宿主机。我见过太多"启动正常但访问不了"的案例,最后查出来是宿主机防火墙、SELinux、内存不足或者磁盘满了。这一段先把地基检查一遍。
2.1 宿主机的前置检查清单
按顺序过一遍,基本能排除八成环境问题:
- Docker 是否可用:
docker version能同时看到 Client 和 Server 两段信息,说明守护进程正常;只有 Client 说明当前用户没权限或者服务没起来。 - 当前用户是否在 docker 组:每次都要
sudo说明没加组,加完要重新登录才生效。 - 磁盘余量:镜像本身不大,但日志会随着服务数量线性增长,建议单独给
/var/lib/docker留出不小于 20G。 - 内存余量:Nacos 默认的堆内存配置对小机器并不友好,2G 内存的机器不做限制直接跑,很容易被系统 OOM 干掉。
- 时间同步:容器时间和宿主机偏差过大,后面做鉴权校验时会出现莫名其妙的失败,先确认时间源正常。
命令层面我会先跑这几条确认:
docker version docker info | grep -E "Server Version|Storage Driver|Docker Root Dir" free -h df -h /var/lib/docker提示:如果
df -h显示根分区已经用了 80% 以上,先清理再装。容器日志默认写在宿主机上,磁盘打满之后容器会表现为"进程还在但完全不响应",非常难查。
2.2 镜像标签怎么选
镜像标签这件事,我踩过的坑是:图省事用了latest,半年后同事重建环境,拉下来的版本已经跨了两个大版本,配置文件格式全变了,排查了半天才发现是版本不一致。
我的建议是永远锁小版本号,比如nacos/nacos-server:v2.4.3这种写法。选版本时看三点:
- 团队里其他服务用的客户端版本。注册中心的客户端和服务端有兼容区间,跨大版本混用会出现接口不匹配。
- 是否需要 ARM 架构支持。现在不少开发机是 ARM 芯片,拉镜像前先确认该标签是否提供多架构清单。
- 是否有已知的高危漏洞修复。这个查官方发布说明即可,不要凭印象。
确认镜像支持的架构,可以这样看:
docker manifest inspect nacos/nacos-server:v2.4.3 | grep -A3 architecture如果输出里同时出现amd64和arm64,说明是多架构镜像,直接docker pull会自动匹配当前平台。如果没有,ARM 机器上要么找对应标签,要么用模拟层跑,性能会明显下降。
2.3 端口规划:这是最容易翻车的一节
从 2.x 开始,Nacos 的通信方式变了,除了我们都熟悉的 HTTP 端口,还多了一组基于长连接的端口。这一段必须记牢,因为漏映射一个端口,症状是"控制台能打开,但服务注册不上",非常反直觉。
| 端口 | 用途 | 是否必须映射到宿主机 |
|---|---|---|
| 8848 | 主端口,控制台与 OpenAPI | 必须 |
| 9848 | 客户端 gRPC 通道,等于主端口 + 1000 | 必须 |
| 9849 | 服务端之间 gRPC 通道,等于主端口 + 1001 | 集群必须,单机可映射 |
| 7848 | 集群内部选举通信 | 仅集群需要 |
偏移规则是固定的:9848 = 8848 + 1000,9849 = 8848 + 1001。如果你把主端口改成 8858,那 gRPC 端口就会自动变成 9858 和 9859,映射时必须同步改,别只改前面不改后面。这一点我建议写进部署文档的第一行。
注意:单机模式下 9849 用不到,但映射上不会有副作用,我一般顺手一起映射,省得将来扩集群时忘了。
3. 单机模式完整落地:从空目录到能登录
这一段是整篇的核心操作部分。我会按"数据库准备 → 启动命令 → 组合编排 → 验证"的顺序来写,每一步都给出可以直接抄的命令,同时也说明这条命令里每个参数为什么这么写。
3.1 数据库侧:建库、建用户、导脚本
先把 MySQL 侧准备好。字符集必须是 utf8mb4,这一点没有商量余地——配置内容里出现特殊字符或表情符号时,utf8 会直接报错。
CREATE DATABASE nacos_config DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER 'nacos'@'%' IDENTIFIED BY '换成你自己的强密码'; GRANT ALL PRIVILEGES ON nacos_config.* TO 'nacos'@'%'; FLUSH PRIVILEGES;接下来要导入表结构。这里有个很多人不知道的细节:官方镜像里自带建表脚本,不需要去别处找。把容器先临时起一下,把脚本拷出来就行:
docker create --name nacos-tmp nacos/nacos-server:v2.4.3 docker cp nacos-tmp:/home/nacos/conf/mysql-schema.sql ./mysql-schema.sql docker rm nacos-tmp拿到脚本后导入:
mysql -h 数据库地址 -u nacos -p nacos_config < mysql-schema.sql导入完成后核对一下表数量,正常应该有十几张,关键的几张是:
config_info:配置主表,所有正式配置都在这his_config_info:历史版本,发布回滚靠它config_info_beta、config_info_tag:灰度与标签配置tenant_info:命名空间users、roles、permissions:开启鉴权后才有意义
如果users表没导进去又开了鉴权,症状是登录时提示用户不存在,而不是密码错误。这个坑我后面还会提。
提示:不要用 root 账号连 Nacos。除了安全层面,还有一个现实原因——某些托管数据库会对 root 的连接来源做限制,出问题时排查成本很高。
3.2 一条 docker run 命令的逐段拆解
先给完整命令,再逐段解释:
docker run -d \ --name nacos-server \ --restart always \ -e MODE=standalone \ -e SPRING_DATASOURCE_PLATFORM=mysql \ -e MYSQL_SERVICE_HOST=192.168.10.20 \ -e MYSQL_SERVICE_PORT=3306 \ -e MYSQL_SERVICE_DB_NAME=nacos_config \ -e MYSQL_SERVICE_USER=nacos \ -e MYSQL_SERVICE_PASSWORD=你的密码 \ -e MYSQL_SERVICE_DB_PARAM="useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true" \ -e JVM_XMS=512m \ -e JVM_XMX=512m \ -e JVM_XMN=256m \ -p 8848:8848 \ -p 9848:9848 \ -p 9849:9849 \ -v /data/nacos/logs:/home/nacos/logs \ nacos/nacos-server:v2.4.3逐段说:
--restart always让容器跟随守护进程自动拉起,机器重启后不用人工干预。这个对测试环境和生产环境都适用,唯一要注意的是如果配置本身有问题,它会反复重启,看日志时注意时间戳间隔。
MODE=standalone是单机模式开关。这个参数不写,容器会按集群模式启动,然后因为找不到集群配置而反复报错。这是新手最容易忽略的一个环境变量。
SPRING_DATASOURCE_PLATFORM=mysql告诉它别用内嵌数据库。不写这一行,它依然会静静地在容器内部用内嵌库跑起来,数据落在容器里,删容器即消失。它不会报错,只会悄悄丢数据,这个特性非常坑。
MYSQL_SERVICE_DB_PARAM这一长串,每一项都有用。serverTimezone不设会导致时间字段出现 8 小时偏差;MySQL 8 的默认认证方式需要allowPublicKeyRetrieval=true,不设就报公钥检索失败;useSSL=false是内网场景的常规做法,如果数据库强制要求加密连接,这项要改。
JVM_XMS/XMX/XMN三个值的关系要理清:XMN是新生代大小,一般取堆的 1/3 到 1/2。这里 512m 堆配 256m 新生代,属于偏大的比例,适合配置读取这种短生命周期对象多的场景。如果内存紧张,把 XMS/XMX 一起降到 256m,XMN 给 128m,也能跑起来。
-v只挂了日志目录。因为配置数据在 MySQL 里,容器内的data目录不重要,挂不挂都行;但日志一定要挂出来,不然出问题时你连日志都看不全。
3.3 用编排文件管理,比裸命令强在哪
裸命令能跑通,但过两周你就会忘记当初写了哪些参数。我更推荐用编排文件,把"这个服务长什么样"变成一份可提交、可对比、可回滚的声明。
version: "3.8" services: nacos: image: nacos/nacos-server:v2.4.3 container_name: nacos-server restart: always environment: MODE: standalone SPRING_DATASOURCE_PLATFORM: mysql MYSQL_SERVICE_HOST: 192.168.10.20 MYSQL_SERVICE_PORT: "3306" MYSQL_SERVICE_DB_NAME: nacos_config MYSQL_SERVICE_USER: nacos MYSQL_SERVICE_PASSWORD: 你的密码 MYSQL_SERVICE_DB_PARAM: "useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true" JVM_XMS: 512m JVM_XMX: 512m JVM_XMN: 256m NACOS_AUTH_ENABLE: "true" NACOS_AUTH_TOKEN: 你的Base64密钥 NACOS_AUTH_IDENTITY_KEY: serverIdentity NACOS_AUTH_IDENTITY_VALUE: security ports: - "8848:8848" - "9848:9848" - "9849:9849" volumes: - /data/nacos/logs:/home/nacos/logs healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8848/nacos/v1/console/health/readiness"] interval: 10s timeout: 5s retries: 6 start_period: 60s这里加的healthcheck值得单独说一下。Nacos 启动是个慢过程,从容器状态变成 running 到真正能对外服务,中间可能有几十秒。编排文件里如果有个依赖它的服务,用健康检查做门禁,比单纯depends_on靠谱得多。start_period给到 60 秒,是为了避免启动期间被误判为不健康而重启,反而拖慢启动。
启动和查看:
docker compose up -d docker compose ps docker compose logs -f --tail=2003.4 启动之后的四个验证动作
容器显示 running 不代表服务可用。我会按下面的顺序验一遍,任何一步不过就不往下走:
- 日志里出现启动完成标志。日志末尾应该能看到类似"Started in xx seconds"的字样,前面不能有数据库连接异常。
- 健康检查接口返回正常。
curl -i http://127.0.0.1:8848/nacos/v1/console/health/readiness,返回 200 说明服务就绪。 - 控制台能打开。浏览器访问
http://宿主机IP:8848/nacos,能出登录页。 - 端到端注册验证。用一个最小客户端注册上去,或者直接在控制台新建一个配置、再读回来,确认写库正常。
第 4 步很多人跳过,结果等到业务方接入时才发现问题。我的习惯是新建一个test.yaml配置,内容随便写一行,发布后去数据库config_info表里select一下,能查到就说明写库链路通了。
4. 参数与配置:几个容易拍脑袋决定的细节
装起来只是开始,跑得稳不稳,取决于几个参数选得对不对。这一段挑三个最容易出问题的点展开。
4.1 JVM 内存到底给多少
我的算法是这样的:先看容器内存上限,堆内存取上限的 50%~60%,剩下留给堆外内存、元空间、线程栈和系统开销。因为 Nacos 除了 Java 堆,还要用直接内存做网络通信缓冲,堆给太满会导致容器整体超限被杀。
举个具体例子:一台 4 核 8G 的机器,同时跑数据库和两个中间件,给 Nacos 的容器限制 1G,那么XMS=XMX=512m、XMN=256m是合适的;如果机器很宽裕,容器限 2G,堆可以给到 1G,XMN给 384m。
小机器上还有个隐性问题:没设-Xmx时,JVM 会按宿主机总内存去算默认堆,容器里看到的可能是宿主机的数字,于是它心安理得地申请一个大堆,然后被容器限制直接干掉。所以JVM_XMS/XMX不是"优化项",是"必填项"。
观察是否够用,看两处:容器docker stats里的内存峰值,以及日志里有没有频繁的垃圾回收记录。前者长期贴近上限就说明该加,后者频繁则说明堆偏小或者新生代比例不合适。
4.2 鉴权开关与密钥这两个参数
默认镜像的鉴权开关并不一定处于你需要状态,生产环境必须显式打开,并且同时配好下面三个参数,缺一个都起不来:
NACOS_AUTH_ENABLE=true:总开关。NACOS_AUTH_TOKEN:用于签名,要求是Base64 编码后的字符串,且解码后的原始字节长度不少于 32 字节。太短会直接启动失败。NACOS_AUTH_IDENTITY_KEY与NACOS_AUTH_IDENTITY_VALUE:服务端之间互相识别的标识,任意字符串即可,但要保证集群内所有节点一致。
生成密钥最省事的办法:
openssl rand -base64 32复制输出直接贴进去。不要自己手敲一串字符再去做编码,很容易长度不够。我见过有人直接把明文密码填进去,结果长度不足 32 字节,容器起来就退出,日志里一行密钥长度异常的提示,找了一晚上。
还有一个细节:密钥一旦确定,集群里所有节点必须完全一致,否则节点之间通信会认证失败,表现为集群列表里节点状态异常。轮换密钥时要所有节点一起换并重启,不能只换一台。
4.3 数据卷怎么挂才不白挂
单机外置库的场景下,容器内真正值得持久化的只有日志。但要提醒一句:如果你暂时还在用内嵌库,那/home/nacos/data必须挂出来,否则容器一删,所有配置全没。这个目录里存的是内嵌库的数据文件。
日志目录建议按天切割并限制总大小,否则一个跑了大半年的环境,日志能吃掉几十个 G。容器日志本身也要在编排文件里加限制:
logging: driver: json-file options: max-size: "50m" max-file: "3"这几行看着不起眼,但它是防止"某天磁盘突然满了导致全线告警"的最便宜的手段。
5. 集群模式:三个节点的正确姿势
单机扛不住的时候就要上集群。Nacos 集群的最小可用规模是 3 节点,因为内部选举需要多数派,2 节点反而比 1 节点更脆弱——挂一台就失去多数派,整个集群不可写。
5.1 集群配置项怎么写
集群模式下,MODE不再写standalone,改为通过NACOS_SERVERS或CLUSTER_CONF声明全部节点地址。写法是IP:主端口,多个用逗号分隔,所有节点的这份列表必须完全一致:
environment: MODE: cluster NACOS_SERVERS: "192.168.10.31:8848 192.168.10.32:8848 192.168.10.33:8848"注意分隔符,不同版本对空格和逗号的处理略有差异,我第一次配的时候就因为混用了空格和逗号,导致只识别出一个节点。稳妥做法是全部用空格,并且用引号把整串括起来。
除了节点列表,集群还必须满足两个前提:所有节点指向同一个 MySQL 库,以及所有节点的鉴权密钥完全一致。数据库不一致会导致配置读写出错乱,密钥不一致会导致节点间认证失败。
5.2 端口偏移在集群下的连带影响
单机时漏映射 9848 只是客户端连不上,集群时问题会被放大。因为节点之间也要通过 gRPC 通信,如果 9849 没映射或者被防火墙挡住,表现是集群列表里能看到对方,但健康状态一直是异常,日志里刷连接超时。
所以集群部署时的端口清单是四个:8848、9848、9849、7848。其中 7848 只在集群内部用,可以不暴露到公网,但节点之间必须互通。我的做法是:8848 和 9848 对外,9849 和 7848 只在内网网段开放。
注意:如果节点跨机房部署,把 7848 的选举通信放到高延迟链路上,会出现选主频繁抖动。跨机房场景建议同机房内做集群,跨机房用同步工具做配置分发,不要硬凑成一个集群。
5.3 前面要不要挂反向代理
挂不挂取决于客户端怎么配。如果不挂,客户端就要写全部节点地址,好处是少一层依赖,坏处是扩缩容时每处配置都得改。
如果挂,需要注意两点:一是必须支持长连接,因为 2.x 的通信是 gRPC,普通七层代理默认可能不支持;二是健康检查要探到就绪接口,不能只探端口存活。我一般用四层转发做入口,配置简单,出问题也好定位。
还有个小经验:入口地址不要和控制台地址混用。我习惯用一个专门的内网域名给客户端用,控制台另走一个入口,这样将来做访问控制时不会互相牵制。
6. 常见问题排查实录
这一段是踩坑记录。我按症状分类整理,方便你对照着查。
6.1 容器起不来的几种典型情况
症状:容器反复重启,状态在 Restarting 和 Up 之间跳。先看日志末尾,八成是数据库连不上或者密钥长度不合法。数据库问题看报错里的连接串,重点核对主机、端口、库名、密码四项;密钥问题日志会明确提示长度不足。
症状:启动到一半退出,日志里有内存相关字样。说明堆设置超过了容器限制,或者宿主机本身内存不够。先docker stats看一眼,再把JVM_XMS/XMX往下调。
症状:日志里一切正常,但容器状态是 unhealthy。这不是服务挂了,是健康检查的判定条件太严。把start_period加长,或者把探针超时放宽,通常就好了。
6.2 控制台能开但服务注册不上
这是最高频的一类问题,几乎全部指向 gRPC 端口。
| 现象 | 最可能原因 | 排查动作 |
|---|---|---|
| 客户端报连接超时 | 9848 未映射或被防火墙拦截 | telnet 宿主机IP 9848验证连通性 |
| 注册后实例列表为空 | 客户端与服务端版本跨度过大 | 核对客户端与服务端的版本兼容区间 |
| 偶发注册成功、偶发失败 | 长连接被中间网络设备断开 | 检查是否有连接空闲回收策略 |
| 控制台显示服务数为 0 | 命名空间或分组不一致 | 核对客户端配置的命名空间 ID |
其中"命名空间不一致"这个坑我要多说一句:控制台默认在public命名空间,客户端如果配了别的命名空间 ID 却又没在控制台建过,数据会写进一个看不见的地方,表现出来就是"明明注册了却找不到"。命名空间 ID 一定要用控制台生成的字符串,不要自己随手起个名字当 ID。
6.3 数据库与鉴权相关的问题
导入脚本后表数量不对。有些建表语句带if not exists,重复执行不会报错,但如果第一次执行中途断了,后面再执行会静默跳过。我的做法是删库重建,重导一次,别在残缺的表结构上排查。
开启鉴权后登录提示用户不存在。说明users表是空的。鉴权相关的三张表必须随 schema 一起导进去,且首次登录后要立刻改掉默认密码。
访问接口返回 403。除了权限配置,还要检查密钥是否所有节点一致、宿主机时间是否准确。签名校验对时间敏感,偏差过大会直接拒绝。
配置发布成功但读取到旧值。先确认是不是读到了缓存,客户端有本地快照机制;再确认是不是发布到了错误的命名空间。数据库里config_info和his_config_info对比一下,能快速定位是没写进去还是读错了地方。
6.4 一份可以直接贴在文档里的速查表
| 报错关键词 | 根因方向 | 处理动作 |
|---|---|---|
Communications link failure | 数据库不可达 | 检查网络、账号、库名 |
Public Key Retrieval is not allowed | MySQL 8 认证方式 | 连接串加allowPublicKeyRetrieval=true |
token secret key | 密钥长度不足 | 用 32 字节随机数做 Base64 |
Client not connected | gRPC 端口不通 | 映射并放行 9848 |
No DataSource set | 未指定数据库平台 | 补SPRING_DATASOURCE_PLATFORM=mysql |
Nacos Server did not start | 内存不足或配置错误 | 降堆内存、看完整日志 |
| 启动后立刻退出且无异常日志 | 端口被占用 | ss -lntp查占用进程 |
排查时我的原则是:先看完整日志的末尾 200 行,再看容器状态,最后才怀疑网络。顺序反了会浪费大量时间在验证"网络没问题"这件事上。
7. 我在实际部署中的几点体会
装过几十次之后,我形成了几个改不掉的习惯,分享出来供参考。第一,任何环境都不裸跑docker run,一律用编排文件,因为人脑记不住二十个环境变量,而文件可以进版本库。第二,第一次部署就把外置库配上,哪怕只是自己用,因为从内嵌库迁到外置库这个过程,比一开始就配好要麻烦得多。第三,健康检查探针一定要配,它是容器编排里最便宜的一道保险,能挡住大部分"顺序不对导致启动失败"的问题。
再补一个很实用的小技巧:把部署需要的所有东西——编排文件、建库脚本、密钥生成命令、端口清单——放在同一个目录里,配一个README写清楚三条命令:怎么起、怎么看日志、怎么彻底删干净。半年后接手的人(很可能就是你自己)会感谢现在的你。删干净这条尤其重要,docker compose down之后残留的数据卷和网络,是"重装一次还是不行"的常见元凶。
最后说个版本升级的注意点:跨小版本升级一般直接换镜像标签重启即可,跨大版本升级务必先看官方的升级说明,尤其是数据库表结构变更和配置项改名这两类。我的做法是升级前把库整库备份一份,升级后先验证配置读写和实例注册两条链路,都正常了再把旧镜像删掉。留一份旧镜像在那,出问题时回滚只需要改一行标签,这个成本比重新排查低太多。