单节点 OpenSearch 建索引后,
_cluster/health返回status: yellow且unassigned_shards不为 0,等多久都不会转绿。根因是index.number_of_replicas默认 1,而副本分片不允许与主分片放在同一节点——单节点只有一个节点,副本永远无处可去。解法是把副本数设为 0:建索引时显式指定,或对已建索引动态更新_settings,不用重建。本地体验里 yellow 本身无害。适用 OpenSearch 全版本,本文实测 3.9.0。
Issue & Env(案发现场与环境)
- 业务场景:跟着 从零跑通 OpenSearch 用 Docker 起了单节点容器,建
movies索引时没指定副本数,之后查集群健康一直是 yellow。 - 环境:OpenSearch 3.9.0,Docker 单节点(
discovery.type=single-node),macOS / Linux 均可复现。 - 关键区分:yellow 不是故障。主分片都在,数据照常读写;red 才是主分片缺失。但单节点的 yellow 也不会自己消失——它是确定性状态,不是过渡状态。
StackTrace(堆栈日志)
建索引时不带 settings,副本数走默认值:
curl -sk -u admin:Devfix@2026 -X PUT "https://localhost:9200/movies?pretty" \
-H "Content-Type: application/json" \
-d '{ "mappings": { "properties": { "title": { "type": "text" } } } }'
查集群健康,status 变成 yellow,unassigned_shards 是 1:
{
"cluster_name" : "docker-cluster",
"status" : "yellow",
"timed_out" : false,
"number_of_nodes" : 1,
"number_of_data_nodes" : 1,
"active_primary_shards" : 1,
"active_shards" : 1,
"relocating_shards" : 0,
"initializing_shards" : 0,
"unassigned_shards" : 1,
"delayed_unassigned_shards" : 0,
"number_of_pending_tasks" : 0
}
看分片明细,主分片 STARTED,副本分片挂在 UNASSIGNED:
curl -sk -u admin:Devfix@2026 "https://localhost:9200/_cat/shards/movies?v"
index shard prirep state docs store ip node
movies 0 p STARTED 0 220b 172.17.0.2 a937e018cee5
movies 0 r UNASSIGNED
官方 Cluster Health API 文档的示例响应就是这个场景,原文注明:
The response shows a yellow status because this is a single-node
cluster with replica shards that cannot be allocated.
Reproduce(复现代码)
最小复现,从起容器到变黄:
docker run -d -p 9200:9200 -p 9600:9600 \
-e "discovery.type=single-node" \
-e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=Devfix@2026" \
opensearchproject/opensearch:3.9.0
# 等 JVM 启动几十秒,先确认此时还是 green(没有索引就没有分片)
curl -sk -u admin:Devfix@2026 "https://localhost:9200/_cluster/health?pretty"
# 建索引,不带 number_of_replicas
curl -sk -u admin:Devfix@2026 -X PUT "https://localhost:9200/movies?pretty" \
-H "Content-Type: application/json" \
-d '{}'
# 再查健康:yellow,unassigned_shards 为 1
curl -sk -u admin:Devfix@2026 "https://localhost:9200/_cluster/health?pretty"
等 30 秒再查,状态纹丝不动。这不是分配慢,是永远分配不了。
Root Cause(根因分析)
副本的备份语义决定分配规则
副本分片的作用是给主分片做备份:主分片所在的节点挂了,副本顶上。如果主副两份存同一个节点,节点一挂两份一起丢,备份失去意义。所以 OpenSearch 的分片分配决策器 same_shard 会直接拒绝这种放置。
官方 Cluster Allocation Explain API 文档的单节点示例里,same_shard 决策器的原文输出:
{
"decider" : "same_shard",
"decision" : "NO",
"explanation" : "a copy of this shard is already allocated to this node [[research_papers][0], node[KfEEGG7_SsKZVFqI4ko2FA], [P], s[STARTED], a[id=SfxnuSLESQSI0Htcv0N3vA]]"
}
文档对这个结果的定性说明,原样引用:
This is a typical situation in single-node clusters where replica
shards cannot be allocated because OpenSearch does not allow a
primary and its replica to coexist on the same node.
你可以让集群亲口解释这个分片为什么分配不出去:
curl -sk -u admin:Devfix@2026 -X GET "https://localhost:9200/_cluster/allocation/explain?pretty"
不带请求体时,它自动挑第一个未分配分片给出解释,返回里就是上面那段 same_shard 拒绝记录。
默认副本数是 1,单节点永远满足不了
官方 Index settings 文档写明:index.number_of_replicas 是动态设置,不指定时跟随集群默认 cluster.default_number_of_replicas,而后者默认值是 1。
把两条规则连起来看:建索引不设副本数 → 每个主分片配 1 个副本 → 副本必须去「别的节点」→ 单节点集群没有别的节点 → 副本永远 unassigned → unassigned_shards 永远不为 0 → 状态永远 yellow。链条上没有一步会超时重试成功,所以等多久都没用。
yellow 在本地无害,red 才要慌
官方博客对 unassigned shards 的解释:yellow 表示所有主分片活跃、个别副本未分配,数据是安全且可搜索的,只是暂时失去副本冗余;red 才是主分片缺失,对应数据不可读写。
所以本地学习阶段,yellow 不影响任何功能,不处理也行。真正要当真的是生产多节点集群:yellow 意味着部分数据此刻没有备份,如果承载主分片的节点恰好在这时故障,那份数据就失去访问,要等恢复才能回来。
进阶:内部索引也会把集群拖黄
论坛帖子 Number of replicas in Opensearch single node setup 实测:即便在 opensearch.yml 设了 cluster.default_number_of_replicas: 0,security-auditlog、.opendistro-ism-config 这些内部索引仍按 1 副本创建,真实输出:
index shard prirep state docs store ip node
security-auditlog-2023.02.15 0 p STARTED 4 60.1kb 10.65.177.46 test-01
security-auditlog-2023.02.15 0 r UNASSIGNED
另一帖 Single node yellow cluster ISM index 是 .opendistro-job-scheduler-lock(5 分片 1 副本)拖黄单节点,wazuh-indexer 的 Issue #87 标题就叫 “Yellow cluster status on single-node fresh install”——同一个问题在不同发行版反复出现。社区给出的解法不是逐个改副本,而是 auto_expand_replicas,见下面方案三。
The Fix(解决方案)
方案一:建索引时就把副本设为 0
本地单节点最干净的做法,建索引时显式指定:
curl -sk -u admin:Devfix@2026 -X PUT "https://localhost:9200/movies?pretty" \
-H "Content-Type: application/json" \
-d '{
"settings": {
"index.number_of_shards": 1,
"index.number_of_replicas": 0
}
}'
方案二:已建索引动态改副本数
索引已经建好也不用删。number_of_replicas 是动态设置,_settings 直接改,立即生效:
curl -sk -u admin:Devfix@2026 -X PUT "https://localhost:9200/movies/_settings?pretty" \
-H "Content-Type: application/json" \
-d '{ "index": { "number_of_replicas": 0 } }'
返回 "acknowledged" : true 即改成功。对比记忆:number_of_shards 是静态(final)设置,只能在创建时指定,要改分片数必须重建索引。
方案三:auto_expand_replicas 让副本随节点数伸缩
以后可能扩成多节点时,用这个设置替代写死副本数:
curl -sk -u admin:Devfix@2026 -X PUT "https://localhost:9200/movies/_settings?pretty" \
-H "Content-Type: application/json" \
-d '{ "index": { "auto_expand_replicas": "0-2" } }'
副本数在 0 到 2 之间自动跟随节点数:单节点 0 副本、转绿;扩到 3 节点自动补到每分片 2 副本。论坛帖 26435 里对内部索引的修法就是给它们套一个含 auto_expand_replicas: "0-all" 的 index template,0-all 表示不设上限、始终等于节点数减 1。
验证转绿
curl -sk -u admin:Devfix@2026 "https://localhost:9200/_cluster/health?pretty"
{
"cluster_name" : "docker-cluster",
"status" : "green",
"active_primary_shards" : 1,
"active_shards" : 1,
"unassigned_shards" : 0
}
再看分片明细,r 那行 UNASSIGNED 消失:
curl -sk -u admin:Devfix@2026 "https://localhost:9200/_cat/shards/movies?v"
注意事项
- 本地体验可以干脆不管 yellow:它不影响写入、搜索和任何 API,纯粹是健康条的颜色。
- 生产多节点别习惯性把副本设 0,那是主动放弃冗余;单节点只是「放不下」,多节点是「没必要」。
- 集群里有多个索引都拖黄时,逐个
_settings改太笨,用 index template 统一管副本策略更省心。
References(参考来源)
- Cluster Health API — OpenSearch 官方文档opensearch-docs
- Cluster Allocation Explain API — OpenSearch 官方文档opensearch-docs
- Index settings — OpenSearch 官方文档opensearch-docs
- Error log: Unassigned Shards – Why Your Cluster is Showing Yellow or Red — OpenSearch 官方博客opensearch-blog
- Number of replicas in Opensearch single node setup — OpenSearch 论坛opensearch-forum
- Single node yellow cluster ISM index — OpenSearch 论坛opensearch-forum
- Yellow cluster status on single-node fresh install · Issue #87 · wazuh/wazuh-indexergithub-issue
