ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Docker安装Nacos实战:单机/集群部署、外置MySQL与端口排查

Docker安装Nacos实战:单机/集群部署、外置MySQL与端口排查

1. 先想清楚:为什么我不用压缩包方式装 Nacos

"Docker 安装 Nacos"这个事,看着像一条命令的事,但我这些年带过的人里,十个有八个第一次都会卡在同一个地方——容器起来了,控制台打不开。原因往往不在 Nacos 本身,而在于我们习惯了"解压、改配置、敲启动脚本"这套思路,没有把容器里的端口、数据、配置这三件事重新捋一遍。这篇就按我自己的实操路径来写:从环境盘点、镜像选择、单机落地、集群扩展,一直到踩坑排查,尽量把每一步"为什么要这么干"讲透,让刚接触注册中心的新同学能照着跑通,也让已经用了几年的人能捡到几个细节。

先给结论:用容器跑 Nacos,本质是把"一个需要 JDK 环境、需要外置数据库、需要固定端口组"的 Java 服务,封装成一个可复制、可回滚、可批量下发的单元。它解决的不是"能不能装"的问题,而是"装完之后半年,你还能不能一键把它恢复成今天这个样子"的问题。适合谁看:正在给微服务项目搭注册与配置中心的后端同学、需要给测试环境快速复刻一套中间件的运维同学,以及手里只有一台 4 核 8G 小机器、想让多个中间件共存的独立开发者。

1.1 三种常见部署方式的取舍

很多人纠结"到底用不用 Docker",我的看法是先把三种做法摆到桌面上对比,答案自然就出来了。

维度压缩包直装容器 + 内嵌数据库容器 + 外置数据库
环境依赖需要匹配 JDK 版本,需要自己写 systemd只需要 Docker只需要 Docker,另加一个 MySQL
数据可靠性靠人工备份容器删了配置就没了数据落在 MySQL,容器可随意重建
版本切换改软链、改端口、重启换镜像标签即可换镜像标签即可
集群扩展手工同步配置文件不适合改环境变量即可
上手成本中低中

我自己的习惯是:学习、写 Demo 用"容器 + 内嵌数据库",任何要交给别人用的环境一律"容器 + 外置数据库"。原因很直白——内嵌数据库是单文件存储,只允许一个进程写,天生不支持集群;而配置中心里的东西,往往比代码还金贵,丢一次要几天才能回忆回来。

1.2 需求先定,再动手

动手之前先回答三个问题,能省掉后面一半的返工:

  1. 这套环境给谁用?只有自己用,单机模式足够了;要给多人共用一个测试环境,就要考虑鉴权开关和账号隔离。
  2. 配置数据要不要长期保留?要保留就必须外置 MySQL,别图省事用内嵌库。
  3. 将来会不会扩成多节点?会的话,从第一天起就把端口规划、集群配置项、反向代理位置想好,不要等到三个节点跑起来才发现端口偏移规则没算对。

这三个问题对应的就是后文的单机篇和集群篇,你可以按需跳读,但我建议至少把端口规划那一段看完。

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=200

3.4 启动之后的四个验证动作

容器显示 running 不代表服务可用。我会按下面的顺序验一遍,任何一步不过就不往下走:

  1. 日志里出现启动完成标志。日志末尾应该能看到类似"Started in xx seconds"的字样,前面不能有数据库连接异常。
  2. 健康检查接口返回正常。curl -i http://127.0.0.1:8848/nacos/v1/console/health/readiness,返回 200 说明服务就绪。
  3. 控制台能打开。浏览器访问http://宿主机IP:8848/nacos,能出登录页。
  4. 端到端注册验证。用一个最小客户端注册上去,或者直接在控制台新建一个配置、再读回来,确认写库正常。

第 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 allowedMySQL 8 认证方式连接串加allowPublicKeyRetrieval=true
token secret key密钥长度不足用 32 字节随机数做 Base64
Client not connectedgRPC 端口不通映射并放行 9848
No DataSource set未指定数据库平台补SPRING_DATASOURCE_PLATFORM=mysql
Nacos Server did not start内存不足或配置错误降堆内存、看完整日志
启动后立刻退出且无异常日志端口被占用ss -lntp查占用进程

排查时我的原则是:先看完整日志的末尾 200 行,再看容器状态,最后才怀疑网络。顺序反了会浪费大量时间在验证"网络没问题"这件事上。

7. 我在实际部署中的几点体会

装过几十次之后,我形成了几个改不掉的习惯,分享出来供参考。第一,任何环境都不裸跑docker run,一律用编排文件,因为人脑记不住二十个环境变量,而文件可以进版本库。第二,第一次部署就把外置库配上,哪怕只是自己用,因为从内嵌库迁到外置库这个过程,比一开始就配好要麻烦得多。第三,健康检查探针一定要配,它是容器编排里最便宜的一道保险,能挡住大部分"顺序不对导致启动失败"的问题。

再补一个很实用的小技巧:把部署需要的所有东西——编排文件、建库脚本、密钥生成命令、端口清单——放在同一个目录里,配一个README写清楚三条命令:怎么起、怎么看日志、怎么彻底删干净。半年后接手的人(很可能就是你自己)会感谢现在的你。删干净这条尤其重要,docker compose down之后残留的数据卷和网络,是"重装一次还是不行"的常见元凶。

最后说个版本升级的注意点:跨小版本升级一般直接换镜像标签重启即可,跨大版本升级务必先看官方的升级说明,尤其是数据库表结构变更和配置项改名这两类。我的做法是升级前把库整库备份一份,升级后先验证配置读写和实例注册两条链路,都正常了再把旧镜像删掉。留一份旧镜像在那,出问题时回滚只需要改一行标签,这个成本比重新排查低太多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进