Elasticsearch 问题大全
myluzh 发布于 阅读:5 Kubernetes
0x00 前言
本文整理几个 Elasticsearch 日常运维里比较常见的问题。
主要包含:
- License 过期导致安全功能不可用
- 分片数量达到上限
- 给现有索引添加生命周期规则
- 删除 N 天前的历史索引
- 统计索引占用空间
我这里示例以 Elasticsearch 7.x 为主,8.x 大部分 API 也能参考,但认证和安全默认配置可能会有差异。
0x01 通用变量
下面命令里统一使用这几个变量,后面直接复制比较方便。
ES_URL="http://10.84.38.43:9200"
ES_USER="elastic"
ES_PASS="你的密码"
如果没有开启账号密码认证,把 -u "$ES_USER:$ES_PASS" 去掉即可。
先确认 ES 能访问:
curl -u "$ES_USER:$ES_PASS" "$ES_URL"
查看集群健康状态:
curl -u "$ES_USER:$ES_PASS" "$ES_URL/_cluster/health?pretty"
0x02 License 过期
现象
日志里出现类似关键字:
current license is non-compliant for [security]
常见情况是试用许可证过期,导致部分安全功能不可用。
查看当前 License
curl -u "$ES_USER:$ES_PASS" "$ES_URL/_license?pretty"
也可以在 Kibana 的 Dev Tools 里执行:
GET /_license
切换到 Basic License
如果只是想恢复免费 Basic License,可以执行:
curl -u "$ES_USER:$ES_PASS" \
-X POST "$ES_URL/_license/start_basic?acknowledge=true"
Kibana Dev Tools:
POST /_license/start_basic?acknowledge=true
验证
curl -u "$ES_USER:$ES_PASS" "$ES_URL/_license?pretty"
确认返回里 type 是 basic,status 是 active。
注意:Basic License 是免费许可证,但不包含所有付费能力。如果原来用了付费功能,切换后需要确认业务是否依赖这些功能。
0x03 分片数量达到上限
现象
创建索引或写入数据时报错:
this action would add [10] shards, but this cluster currently has [3000]/[3000] maximum normal shards open
意思是当前集群打开的 normal shard 数已经达到上限。
查看当前分片数量
curl -u "$ES_USER:$ES_PASS" "$ES_URL/_cat/shards?v"
只统计分片数量:
curl -s -u "$ES_USER:$ES_PASS" "$ES_URL/_cat/shards?h=index,shard,prirep,state" | wc -l
查看当前集群配置:
curl -u "$ES_USER:$ES_PASS" \
"$ES_URL/_cluster/settings?include_defaults=true&pretty" | grep -A5 max_shards_per_node
临时提高分片上限
如果确认磁盘、内存、节点负载都还能支撑,可以临时提高 cluster.max_shards_per_node。
Kibana Dev Tools:
PUT _cluster/settings
{
"persistent": {
"cluster.max_shards_per_node": 10000
}
}
curl:
curl -u "$ES_USER:$ES_PASS" \
-X PUT "$ES_URL/_cluster/settings" \
-H 'Content-Type: application/json' \
-d '{
"persistent": {
"cluster.max_shards_per_node": 10000
}
}'
注意
这个只是放大上限,不是根治。
真正要处理的是:
- 删除不需要的历史索引
- 减少新索引默认分片数
- 给日志索引配置 ILM 生命周期
- 合理规划 rollover 策略
如果小索引太多,只提高上限会让集群状态越来越重,后面 master 压力也会变大。
0x04 给索引添加生命周期规则
说明
ILM 可以自动处理索引生命周期,比如日志索引保留 7 天后自动删除。
这里用 k8s-* 这类日志索引做示例。
1、创建生命周期策略
Kibana Dev Tools:
PUT _ilm/policy/7d-delete
{
"policy": {
"phases": {
"delete": {
"min_age": "7d",
"actions": {
"delete": {}
}
}
}
}
}
curl:
curl -u "$ES_USER:$ES_PASS" \
-X PUT "$ES_URL/_ilm/policy/7d-delete" \
-H 'Content-Type: application/json' \
-d '{
"policy": {
"phases": {
"delete": {
"min_age": "7d",
"actions": {
"delete": {}
}
}
}
}
}'
2、给现有索引绑定 ILM
下面脚本会把 7d-delete 策略应用到所有 k8s-* 索引。
#!/bin/bash
ES_URL="http://10.84.38.43:9200"
ES_USER="elastic"
ES_PASS="你的密码"
POLICY_NAME="7d-delete"
INDEX_PATTERN="k8s-*"
indices=$(curl -s -u "$ES_USER:$ES_PASS" "$ES_URL/_cat/indices/$INDEX_PATTERN?h=index")
if [ -z "$indices" ]; then
echo "没有匹配到索引: $INDEX_PATTERN"
exit 0
fi
for index in $indices; do
echo "给索引绑定 ILM: $index -> $POLICY_NAME"
curl -s -u "$ES_USER:$ES_PASS" \
-X PUT "$ES_URL/$index/_settings" \
-H 'Content-Type: application/json' \
-d "{
\"index.lifecycle.name\": \"$POLICY_NAME\"
}"
echo
done
3、给新索引自动绑定 ILM
只给现有索引加 ILM 不够,新建出来的索引还要通过模板自动带上策略。
PUT _index_template/k8s-log-template
{
"index_patterns": ["k8s-*"],
"template": {
"settings": {
"index.lifecycle.name": "7d-delete",
"number_of_shards": 1,
"number_of_replicas": 1
}
}
}
4、验证
查看索引是否绑定生命周期:
curl -u "$ES_USER:$ES_PASS" \
"$ES_URL/k8s-*/_ilm/explain?pretty"
注意:给已有索引绑定删除策略时,如果索引创建时间已经超过 min_age,可能很快被删除。生产环境先找一两个索引测试,不要直接全量跑。
0x05 删除 N 天前的索引
说明
如果没有配置 ILM,也可以临时用脚本删除历史索引。
下面脚本按索引名里的日期判断,比如:
k8s-app-2025.05.01
k8s-nginx-2025.05.02
匹配规则是:
k8s.*-YYYY.MM.DD
1、安装依赖
这里使用 7.x Python 客户端:
pip3 install 'elasticsearch<8'
2、删除脚本
默认是 DRY_RUN = True,只打印不删除。确认没问题后再改成 False。
from datetime import datetime, timedelta
import re
from elasticsearch import Elasticsearch, exceptions
ES_URL = "http://10.84.38.43:9200"
ES_USER = "elastic"
ES_PASS = "你的密码"
DAYS = 30
INDEX_PATTERN = re.compile(r"k8s.*-(\d{4}\.\d{2}\.\d{2})$")
DRY_RUN = True
es = Elasticsearch(
[ES_URL],
http_auth=(ES_USER, ES_PASS),
timeout=30
)
expire_time = datetime.utcnow() - timedelta(days=DAYS)
indices = es.cat.indices(format="json")
old_indices = []
for item in indices:
index_name = item["index"]
match = INDEX_PATTERN.match(index_name)
if not match:
continue
index_date = datetime.strptime(match.group(1), "%Y.%m.%d")
if index_date < expire_time:
old_indices.append(index_name)
print(f"下面索引超过 {DAYS} 天:")
for index_name in old_indices:
print(index_name)
if DRY_RUN:
print("当前是 DRY_RUN 模式,没有执行删除。")
exit(0)
for index_name in old_indices:
try:
response = es.indices.delete(index=index_name, ignore=[400, 404])
if response.get("acknowledged", False):
print(f"删除成功: {index_name}")
else:
print(f"删除失败: {index_name}, response={response}")
except exceptions.ConnectionError as e:
print(f"连接 ES 失败: {e}")
except exceptions.ElasticsearchException as e:
print(f"删除索引异常: {index_name}, error={e}")
print("处理完成。")
3、执行
python3 delete_old_indices.py
确认输出没问题后,把脚本里的:
DRY_RUN = True
改成:
DRY_RUN = False
再执行一次。
0x06 统计索引大小
1、直接用 cat indices 查看
先用 ES 自带接口看,通常已经够用。
curl -u "$ES_USER:$ES_PASS" \
"$ES_URL/_cat/indices?v&s=store.size:desc&h=index,docs.count,pri.store.size,store.size"
只看某个前缀:
curl -u "$ES_USER:$ES_PASS" \
"$ES_URL/_cat/indices/jaeger-*?v&s=store.size:desc&h=index,docs.count,pri.store.size,store.size"
字段说明:
pri.store.size:主分片占用store.size:主分片 + 副本分片总占用docs.count:文档数量
2、用 Python 汇总
如果想算总大小,可以用下面脚本。
pip3 install requests
es_index_size.py:
import requests
from requests.auth import HTTPBasicAuth
ES_URL = "http://10.84.38.43:9200"
ES_USER = "elastic"
ES_PASS = "你的密码"
INDEX_PATTERN = "jaeger-*"
auth = HTTPBasicAuth(ES_USER, ES_PASS)
resp = requests.get(
f"{ES_URL}/{INDEX_PATTERN}/_stats/store",
auth=auth,
timeout=30
)
resp.raise_for_status()
data = resp.json()
indices = data.get("indices", {})
if not indices:
print("没有匹配到索引")
exit(0)
items = []
total_size = 0
for index_name, index_data in indices.items():
size = index_data["total"]["store"]["size_in_bytes"]
total_size += size
items.append((index_name, size))
items.sort(key=lambda x: x[1], reverse=True)
for index_name, size in items:
mb = size / 1024 / 1024
gb = size / 1024 / 1024 / 1024
print(f"Index: {index_name}, Size: {mb:.2f} MB == {gb:.6f} GB")
print("-" * 80)
print(f"索引总数: {len(items)}")
print(f"总大小: {total_size / 1024 / 1024:.2f} MB == {total_size / 1024 / 1024 / 1024:.6f} GB")
执行:
python3 es_index_size.py
0x07 总结
这几个问题本质上都和日志类索引的生命周期有关。
我的建议:
- 日志索引尽量一开始就配置 ILM
- 不要把默认分片数开太大
- 分片达到上限时先清理历史索引,再考虑提高
max_shards_per_node - 删除索引脚本默认先开 dry-run
- License 过期先确认是否真的需要付费能力,不需要就切回 Basic
如果是 Kubernetes 里跑 Elasticsearch,建议优先用 ECK 管理集群,证书、账号、滚动升级都会省很多事。
参考链接
- Elasticsearch License API:https://www.elastic.co/guide/en/elasticsearch/reference/7.17/start-basic.html
- Elasticsearch ILM:https://www.elastic.co/guide/en/elasticsearch/reference/7.17/index-lifecycle-management.html
- Elasticsearch cluster settings:https://www.elastic.co/guide/en/elasticsearch/reference/7.17/modules-cluster.html
- Elasticsearch cat indices API:https://www.elastic.co/guide/en/elasticsearch/reference/7.17/cat-indices.html
k8s kubernetes elastic elasticsearch es 问题 排错 索引 ILM