Kibana 是咱们日常摆弄 Elasticsearch 最常见的那块“仪表盘”,不管是看报表、搭监控,还是调试日志,最后都得靠它来展示。可它有个容易让人栽跟头的地方:所有配置,比如你做好的 Dashboard、Visualization、Index Pattern,甚至保存的搜索条件,其实都存放在 Elasticsearch 里的一堆内部索引中。也就是说,Kibana 自己本身不“记住”这些东西,它只是绘图的“前台”,真正的“仓库”在 ES 那边。一旦有人误操作把这些索引删了,或者升级 Kibana 时搞坏了版本,那辛辛苦苦攒出来的看板可能说没就没,比丢了钱包还难受。今天咱们就来聊聊怎么给 Kibana 做一套稳妥的配置备份和恢复方案,把这种意外风险压到最低。

一、Kibana 配置到底藏在哪儿

先别急着备份,得先搞清楚东西放在哪。Kibana 的配置项,包括你创建的图表、搜索、索引模式,全都保存在 Elasticsearch 的 .kibana 开头的索引里。类比一下,这就像你虽然用电脑上的 Excel 画表格,但文件实际存在 U 盘里,U 盘要是坏了,Excel 再漂亮也没用。

打开终端,执行下面这条命令,就能看到这些索引长什么样。

# 查看所有 .kibana 开头的索引,顺便看看占了多少空间
curl -s -X GET "http://localhost:9200/_cat/indices/.kibana*?v&s=index" \
  -H "Content-Type: application/json"

输出结果里会出现类似 .kibana_1、.kibana_security_1 这样的名字,这些索引就是 Kibana 的全部家当。知道这一点后,备份思路就清晰了:要么把这些索引里的数据“导出来”,要么直接给整个索引做“快照”。

二、最偷懒的备份方法:导出 Saved Objects

Kibana 自带一个“保存对象”功能,英文叫 Saved Objects。所有你手动做出来的仪表盘、可视化图表、索引模式,都算“保存对象”。这个功能允许你一键导出成一个文件,恢复的时候再导回去。虽然它不能覆盖全部配置,但对于大多数用户来说,这已经是最常用的“救命稻草”。

2.1 通过界面导出

在 Kibana 左侧菜单找到“Stack Management”,进入后点“Saved Objects”,能看到所有已保存对象。点“Export”按钮,就能下载一个 .ndjson 文件。这个操作当然很好用,但问题是没法自动化。如果你有几十套 Kibana 环境,手动一个个点,手指都得抽筋。

2.2 用 API 自动导出

好在 Kibana 提供了 API 接口,用一条命令就能把全部保存对象导出来。咱们用 curl 来搞定。先写上技术栈,免得后面乱套。

#!/bin/bash
# 技术栈:Bash Shell + curl
# 用途:导出Kibana所有Saved Objects

KIBANA_URL="http://localhost:5601"
KIBANA_USER="elastic"
KIBANA_PASS="changeme"
OUTPUT_FILE="kibana_saved_objects.ndjson"

# 发送导出请求
# 请求体里的 type 是数组,空数组表示导出所有类型
# includeReferencesDeep 设为true,表示把被引用的对象也一起带上,避免恢复后缺依赖
curl -s -X POST "${KIBANA_URL}/api/saved_objects/_export" \
  -u "${KIBANA_USER}:${KIBANA_PASS}" \
  -H "kbn-xsrf: true" \
  -H "Content-Type: application/json" \
  -d '{
    "type": [],
    "includeReferencesDeep": true
  }' \
  -o "${OUTPUT_FILE}"

# 检查导出文件是否为空
if [ -s "${OUTPUT_FILE}" ]; then
  echo "导出成功,文件名为 ${OUTPUT_FILE}"
else
  echo "导出失败,请检查Kibana地址或账号权限"
fi

这段脚本会把所有 Dashboard、Visualization 等打包到一个文件里。注意 kbn-xsrf 这个请求头是 Kibana 防止跨站请求的机制,不加它 API 会拒绝服务。

这个方法虽然省事,但有一个大坑:它不包含你修改过的“高级设置”,比如日期的显示格式、默认查询时间范围等。这些设置也存在 .kibana 索引里,只不过不属于 Saved Objects。所以想要更稳妥,还得靠下一招。

三、最踏实的备份:给 Elasticsearch 索引做快照

Elasticsearch 自带一个叫“快照”的功能,简单说就是把某个时刻的索引状态整体“拍张照片”存起来,以后想恢复就能一键还原。这比导 Saved Objects 要全面得多,因为快照是存储层级的备份,不管配置文件藏多深,统统给你照下来,就像把整个档案柜都搬走,而不是只挑几个文件夹。

3.1 先准备一个“仓库”

快照不能随便放,得先指定一个地方来存,这个地方叫仓库。仓库可以挂在本机磁盘、共享存储,甚至云对象存储上。下面演示用本地文件系统做仓库。

#!/bin/bash
# 技术栈:Bash Shell + curl
# 用途:创建用于存储Kibana快照的仓库

ES_URL="http://localhost:9200"
ES_USER="elastic"
ES_PASS="changeme"

# 创建仓库。location 是ES节点上的绝对路径,需要确保该路径存在且ES有写权限
curl -s -X PUT "${ES_URL}/_snapshot/kibana_backup_repo" \
  -u "${ES_USER}:${ES_PASS}" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "fs",
    "settings": {
      "location": "/mnt/backups/es_snapshots",
      "compress": true
    }
  }'

有一点要记牢:如果 Elasticsearch 是集群模式,这个路径必须能被所有数据节点访问到。否则有的节点写不进去,快照就会失败。

3.2 给 .kibana 索引拍快照

仓库建好之后,就可以给带有 .kibana 前缀的索引做快照了。做快照时最好带上时间戳,这样以后能看到每一天的版本。

# 技术栈:Bash Shell + curl
# 用途:对 .kibana 索引执行快照

ES_URL="http://localhost:9200"
ES_USER="elastic"
ES_PASS="changeme"
SNAPSHOT_NAME="kibana_$(date +%Y%m%d_%H%M%S)"

# 执行快照。indices 指定为 .kibana*,代表所有相关索引
# ignore_unavailable 设为true,避免某些索引不存在时报错
curl -s -X PUT "${ES_URL}/_snapshot/kibana_backup_repo/${SNAPSHOT_NAME}" \
  -u "${ES_USER}:${ES_PASS}" \
  -H "Content-Type: application/json" \
  -d '{
    "indices": ".kibana*",
    "ignore_unavailable": true,
    "include_global_state": false
  }'

执行完之后,可以用下面这条命令查看快照状态,直到它变成 SUCCESS 才算是真的完成。注意,快照是异步的,发出请求不代表马上完成。

# 查看某个快照的执行状态
curl -s -X GET "http://localhost:9200/_snapshot/kibana_backup_repo/${SNAPSHOT_NAME}" \
  -u "elastic:changeme" | jq '.snapshots[0].state'

快照的完整程度比 Saved Objects 高不少,因为连 Kibana 内部存储的“高级设置”也都一并备份了。但它的缺点是比较重,而且和 Elasticsearch 版本强相关。等到你需要恢复的时候,得保证版本差不多,否则可能起不来。

四、把备份自动化:自己写一个一键脚本

手动执行命令还是不够安稳,万一人忘了呢?最好写成一个脚本,让服务器定时跑。下面这个脚本把“导出 Saved Objects”和“做快照”合并到一起,一条命令全搞定。

#!/bin/bash
# 技术栈:Bash Shell + curl + jq
# 用途:一键备份Kibana配置(Saved Objects + 快照)

set -euo pipefail

# ===== 以下配置请根据实际环境修改 =====
ES_URL="http://localhost:9200"
KIBANA_URL="http://localhost:5601"
ES_USER="elastic"
ES_PASS="changeme"
BACKUP_DIR="/opt/kibana_backups"
SNAPSHOT_REPO="kibana_backup_repo"
# 每次运行用时间戳生成唯一快照名
SNAPSHOT_NAME="kibana_$(date +%Y%m%d_%H%M%S)"
# =====================================

mkdir -p "${BACKUP_DIR}"
LOG_FILE="${BACKUP_DIR}/backup.log"

# 简单的日志函数,顺便把内容输出到终端
log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "${LOG_FILE}"
}

# 第一步:导出Saved Objects
export_saved_objects() {
  local output="${BACKUP_DIR}/saved_objects_${SNAPSHOT_NAME}.ndjson"
  log "开始导出Saved Objects..."
  
  curl -s -X POST "${KIBANA_URL}/api/saved_objects/_export" \
    -u "${ES_USER}:${ES_PASS}" \
    -H "kbn-xsrf: true" \
    -H "Content-Type: application/json" \
    -d '{"type": [], "includeReferencesDeep": true}' \
    -o "${output}"
  
  # 如果文件不是空的,就说明导出成功
  if [ -s "${output}" ]; then
    log "Saved Objects导出成功:${output}"
  else
    log "警告:导出文件为空,请检查Kibana服务或权限!"
  fi
}

# 第二步:确保快照仓库存在
ensure_repo() {
  log "检查快照仓库 ${SNAPSHOT_REPO} ..."
  # 简单判断仓库是否已存在
  local exists
  exists=$(curl -s -u "${ES_USER}:${ES_PASS}" \
    "${ES_URL}/_snapshot/${SNAPSHOT_REPO}" | grep -c '"type"')
  
  if [ "${exists}" -eq 0 ]; then
    log "仓库不存在,现在创建..."
    curl -s -X PUT "${ES_URL}/_snapshot/${SNAPSHOT_REPO}" \
      -u "${ES_USER}:${ES_PASS}" \
      -H "Content-Type: application/json" \
      -d "{
        \"type\": \"fs\",
        \"settings\": {
          \"location\": \"/mnt/backups/es_snapshots\",
          \"compress\": true
        }
      }"
    log "仓库创建成功。"
  else
    log "仓库已存在,直接复用。"
  fi
}

# 第三步:对.kibana索引做快照
create_snapshot() {
  log "正在创建快照 ${SNAPSHOT_NAME} ..."
  curl -s -X PUT "${ES_URL}/_snapshot/${SNAPSHOT_REPO}/${SNAPSHOT_NAME}" \
    -u "${ES_USER}:${ES_PASS}" \
    -H "Content-Type: application/json" \
    -d '{
      "indices": ".kibana*",
      "ignore_unavailable": true,
      "include_global_state": false
    }'
  log "快照请求已发出,等待完成..."
  
  # 轮询快照状态,直到变成SUCCESS
  for i in $(seq 1 30); do
    local state
    state=$(curl -s -X GET "${ES_URL}/_snapshot/${SNAPSHOT_REPO}/${SNAPSHOT_NAME}" \
      -u "${ES_USER}:${ES_PASS}" | jq -r '.snapshots[0].state' 2>/dev/null || echo "")
    if [ "$state" = "SUCCESS" ]; then
      log "快照完成,状态为 SUCCESS"
      return 0
    fi
    log "快照状态为 ${state},10秒后再检查..."
    sleep 10
  done
  log "快照等待超时,请检查ES日志。"
}

# 主流程
log "===== Kibana配置备份开始 ====="
export_saved_objects
ensure_repo
create_snapshot
log "===== 备份完成,文件位于 ${BACKUP_DIR} ====="

把这段脚本保存成 kibana_backup.sh,先加上执行权限,再手动试跑一次。

# 给脚本加上可执行权限
chmod +x /opt/kibana_backups/kibana_backup.sh

# 手动执行一次
/opt/kibana_backups/kibana_backup.sh

如果一切顺利,就能在 /opt/kibana_backups 里看到导出的 .ndjson 文件,以及在 ES 里创建好的快照。之后想定时跑,只要加一条 crontab 规则,比如每天凌晨 2 点执行:

# 每天凌晨2点执行备份
0 2 * * * /opt/kibana_backups/kibana_backup.sh >> /var/log/kibana_backup_cron.log 2>&1

有了 cnotab,就算你睡得正香,服务器也会默默把备份做好。偶尔想看有没有成功,翻一眼日志就行。

五、恢复流程:把备份拿出来“救火”

备份做得再好,不会恢复等于白做。下面分两种情况说:一种是只导出了 Saved Objects,另一种是做了快照。

5.1 恢复 Saved Objects

如果你手头只有那个 .ndjson 文件,恢复方式也很简单。在 Kibana 界面的 Saved Objects 里点“Import”,选择文件,然后勾选“覆盖已有对象”就能导回去。当然,用 API 也可以,适合那种界面打不开的场景。

# 技术栈:Bash Shell + curl
# 用途:从ndjson文件导入Saved Objects

KIBANA_URL="http://localhost:5601"
KIBANA_USER="elastic"
KIBANA_PASS="changeme"
BACKUP_FILE="kibana_saved_objects.ndjson"

# 导入文件。overwrite=true表示如果已存在同名对象,直接覆盖
curl -s -X POST "${KIBANA_URL}/api/saved_objects/_import" \
  -u "${KIBANA_USER}:${KIBANA_PASS}" \
  -H "kbn-xsrf: true" \
  -H "Content-Type: multipart/form-data" \
  -F "file=@${BACKUP_FILE}" \
  -F "overwrite=true"

导入之后,刷新一下 Kibana 页面,通常就能看到恢复出来的仪表盘了。不过要注意,如果你在导出之后还创建了新的对象,并且同名,覆盖之后新内容会被旧内容顶掉。所以导入前最好确认一下当前环境是不是真的需要“回到过去”。

5.2 从快照恢复整个 Kibana 索引

要恢复“高级设置”这些普通导出没有的东西,就得用快照。但快照恢复是有风险的,因为它会直接把 .kibana 索引整个替换掉。如果当前索引里还有想要的新数据,千万先备份,或者用“重命名恢复”的方式把旧索引搬到旁边,再手动处理。

下面演示一个比较稳妥的做法:把快照里的 .kibana 索引恢复成一个带新名字的索引,不要直接覆盖。

# 技术栈:Bash Shell + curl
# 用途:从快照恢复.kibana索引到另一个索引名

ES_URL="http://localhost:9200"
ES_USER="elastic"
ES_PASS="changeme"
SNAPSHOT_REPO="kibana_backup_repo"
SNAPSHOT_NAME="kibana_20251015_120000"

# 执行恢复。rename_pattern匹配原来的索引名,rename_replacement改成 new_ 前缀
curl -s -X POST "${ES_URL}/_snapshot/${SNAPSHOT_REPO}/${SNAPSHOT_NAME}/_restore" \
  -u "${ES_USER}:${ES_PASS}" \
  -H "Content-Type: application/json" \
  -d '{
    "indices": ".kibana*",
    "ignore_unavailable": true,
    "include_global_state": false,
    "rename_pattern": "(.+)",
    "rename_replacement": "restored_$1"
  }'

执行完以后,你会得到一堆 restored_.kibana_1 之类的索引。这时 Kibana 并不会直接认这些新名字,你需要手动处理索引别名,或者干脆把原索引删掉,再利用 Elasticsearch 的“重新索引”功能把数据搬回去。这个过程比较繁琐,因此更常见的做法是:先关闭 Kibana,删除当前 .kibana* 索引,然后直接恢复成原索引名。这样最简单,但要注意,一旦出错,当前配置就没了,你必须确保快照是好的。

# 技术栈:Bash Shell + curl
# 用途:删掉当前.kibana索引,再从快照恢复(高风险操作,慎用!)

# 先关闭Kibana应用(在Kibana机器上执行)
# systemctl stop kibana

# 删除当前.kibana索引
curl -s -X DELETE "http://localhost:9200/.kibana*" \
  -u "elastic:changeme"

# 从快照恢复到原索引名
curl -s -X POST "http://localhost:9200/_snapshot/kibana_backup_repo/kibana_20251015_120000/_restore" \
  -u "elastic:changeme" \
  -H "Content-Type: application/json" \
  -d '{
    "indices": ".kibana*",
    "ignore_unavailable": true
  }'

# 启动Kibana
# systemctl start kibana

做完这些,Kibana 就会回到快照时的样子。这里强烈建议先在测试环境演练一遍,别等真出事时临时抱佛脚。

六、应用场景、技术优缺点和注意事项

6.1 适合哪些场景?

  • 升级前必做:Kibana 和 Elasticsearch 升级都有一定风险,尤其跨大版本时,内部数据结构可能会变。升级前做一次完整快照,万一失败还能倒回去。
  • 多团队协作:一堆人都在往 Kibana 里加图表,难免有人手抖删错。有了备份,可以迅速恢复到出事前的时间点。
  • 环境迁移:从测试环境搬到生产环境,或者换集群,导出一份 Saved Objects 再导入,省时省力。

6.2 两种备份方式的优缺点

Saved Objects 导出,优点是文件小、人眼可读、跨版本移植性好,用文本编辑器就能看到里面有什么。缺点也明显,它不管“高级设置”和部分内部元数据,而且如果对象之间有复杂的引用关系,导出时漏掉某个引用,恢复完可能有些图表显示不出来。

快照备份,优点是极其完整,连索引的底层结构都原样保存,恢复后和当时一模一样。缺点是重,而且强依赖 Elasticsearch 版本,不能拿到别的集群里乱用。另外,恢复快照的权限要求高,如果连集群管理员都搞不定,那就只能靠 Saved Objects 续命了。

6.3 必须留意的坑

  • 版本兼容性:Kibana 升级之后,不要用旧版本的导出文件往新版本里硬导,很可能因为字段格式变化而导入失败。最好是同版本或小版本之间导入。
  • 索引名称别写错:快照时用的是 .kibana* 通配,但 Elasticsearch 里也可能有 .kibana_task_manager 等其他索引,恢复时也要一并考虑,否则任务记录丢失。
  • 权限要锁死:备份脚本里存了明文密码,这本身就是安全隐患。建议把脚本文件权限改成 700,或者用环境变量注入密码。
  • 备份不等于万事大吉:定期演练恢复流程,确认备份文件能被成功导入。否则等到真要用时发现文件损坏,那才叫绝望。
  • 快照仓库的位置:别把快照仓库放在 Elasticsearch 的数据目录里,否则磁盘满了,整个集群都可能挂。放独立磁盘,并且定期清理旧快照。

6.4 锦上添花的小工具

除了上面这些,你还可以用 elasticsearch-crone(自带工具)配合对象存储做跨机房备份,或者用 Curator 定期清理过期快照。不过这些属于进阶玩法,先把基础备份做好,已经能避开百分之九十的坑了。

七、总结

Kibana 的配置备份,本质上就是备份它身后的 Elasticsearch 索引。有人喜欢用 Saved Objects 导出,方便灵活;有人喜欢用快照,完整可靠。对我来说,最科学的方法是两条腿走路:日常用脚本把 Saved Objects 导出一份,隔一段时间再做一次快照。这样既有了快速恢复的轻量方案,也有了一旦崩塌就能整体还原的救心丸。关键不在于用哪个,而在于你真正把备份做了,并且验证过恢复是通的。人总有手滑的时候,但合理的备份策略,能让手滑多一次“后悔药”的机会。希望今天这套方法能帮你在意外来临时,不那么慌。