PAYATHON 2026

Elasticsearch Docker 无法将 Snapshot 保存到 GCS

排障赵师傅

结论

这个错误通常与 Linux 文件权限无关。Service Account JSON 已写入 elasticsearch.keystore,但正在运行的 Elasticsearch 进程尚未重新加载这项 secure setting。

由于 repository-gcs 没有取得 gcs.client.default.credentials_file,它会转而执行 Google Cloud 的默认凭据和项目发现流程,依次尝试读取:

  • ~/.config/gcloud/active_config
  • metadata.google.internal
  • Application Default Credentials

Elasticsearch 的 Java 安全策略不允许插件读取 /usr/share/elasticsearch/.config/gcloud/active_config,因此抛出 java.security.AccessControlException。创建该文件、修改 chmod 或放宽容器目录权限都解决不了凭据未加载的问题。

在 Elasticsearch 8.8.2 中,gcs.client.<name>.credentials_file 是可重新加载的 secure setting。执行 add-file 后,调用 POST /_nodes/reload_secure_settings 即可加载新凭据,无需重启容器。安装或升级插件后仍须重启 Elasticsearch,因为插件无法动态加载。

为什么 keystore 中有配置,插件却没有使用

bin/elasticsearch-keystore add-file 修改的是磁盘上的文件:

/usr/share/elasticsearch/config/elasticsearch.keystore

当前 JVM 中已经加载的配置不会随之自动更新。

题目中的操作顺序是:

  1. 启动 Elasticsearch。
  2. 向 keystore 写入 GCS 凭据。
  3. 安装 repository-gcs。
  4. 立即注册 repository。

这套流程缺少 secure settings reload。运行中的节点仍认为没有配置 credentials_file,所以进入了默认凭据发现流程。

keystore 保存的是 JSON 文件的内容,不是原文件路径。写入成功后,原始 JSON 文件无需继续保留在容器中。

正确的配置步骤

以下示例使用这些参数:

  • 容器名为 es01
  • GCS bucket 名为 my-snapshot-bucket
  • Service Account 文件为当前目录下的 gcs-service-account.json
  • 使用默认客户端 default
  • Elasticsearch 已关闭安全认证,与问题中的启动参数一致

1. 确认 GCS repository 功能可用

Elasticsearch 8.8.2 的官方 Docker distribution 通常已经包含 GCS repository 功能,不需要在节点运行期间重复安装。

如果当前使用的 distribution 确实没有 repository-gcs,应在构建镜像时安装,再启动 Elasticsearch。例如:

FROM docker.elastic.co/elasticsearch/elasticsearch:8.8.2

RUN bin/elasticsearch-plugin install --batch repository-gcs

安装插件后必须重启 Elasticsearch,已经运行的 JVM 不会动态加载新安装的插件。

2. 将凭据写入 Elasticsearch keystore

先临时复制凭据文件:

docker cp ./gcs-service-account.json \
  es01:/tmp/gcs-service-account.json

然后写入 keystore:

docker exec es01 \
  bin/elasticsearch-keystore add-file --force \
  gcs.client.default.credentials_file \
  /tmp/gcs-service-account.json

检查 setting 名称是否存在:

docker exec es01 \
  bin/elasticsearch-keystore list

输出中应包含:

gcs.client.default.credentials_file

list 只显示名称,不会输出凭据内容。

写入完成后,可以删除临时文件:

docker exec es01 rm /tmp/gcs-service-account.json

3. 让运行中的节点重新加载 secure settings

如果 Elasticsearch keystore 没有设置密码,调用:

curl -X POST \
  "http://localhost:9200/_nodes/reload_secure_settings?pretty" \
  -H "Content-Type: application/json" \
  -d '{}'

如果 keystore 有密码,则提交:

curl -X POST \
  "http://localhost:9200/_nodes/reload_secure_settings?pretty" \
  -H "Content-Type: application/json" \
  -d '{
    "secure_settings_password": "KEYSTORE_PASSWORD"
  }'

检查响应中各节点是否出现 reload_exception。单节点 Docker 环境中应当只有一个节点。

也可以通过重启容器加载配置。不过,如果容器使用 --rm 启动且配置目录未持久化,删除容器时,其中的 keystore 也会丢失。除非通过 volume、Docker secret 或其他初始化流程重新生成 keystore,否则不要直接移除旧容器并创建新容器。

4. 显式配置 GCP project ID

Service Account 凭据和 project ID 是两项独立的客户端配置。建议显式设置以下配置,避免 Google Cloud SDK 尝试读取本地 gcloud 配置或访问 Compute Engine metadata:

gcs.client.default.project_id

可以在启动容器时加入:

docker run --rm \
  --name es01 \
  -p 9200:9200 \
  -p 9300:9300 \
  -e "xpack.security.enabled=false" \
  -e "discovery.type=single-node" \
  -e "gcs.client.default.project_id=YOUR_GCP_PROJECT_ID" \
  docker.elastic.co/elasticsearch/elasticsearch:8.8.2

也可以写入 elasticsearch.yml:

gcs.client.default.project_id: YOUR_GCP_PROJECT_ID

project_id 是普通节点配置。不要把 Service Account JSON 内容写入 elasticsearch.yml 或 repository 注册请求。

如果没有显式设置 project ID,具体行为取决于所用凭据,以及 Google Cloud 客户端库能否从环境中推导出该值。对于在本地 laptop 上运行的 Docker container,显式配置通常更可靠。

5. 注册 Snapshot repository

凭据客户端名为 default 时,可以这样注册 repository:

curl -X PUT \
  "http://localhost:9200/_snapshot/cheese7?pretty" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "gcs",
    "settings": {
      "bucket": "my-snapshot-bucket",
      "client": "default",
      "base_path": "elasticsearch/snapshots"
    }
  }'

这里的 client 必须与 keystore setting 中的客户端名一致:

gcs.client.default.credentials_file
           ^^^^^^^

如果凭据使用另一个名称,例如:

gcs.client.backup.credentials_file

repository 中也要使用:

{
  "client": "backup"
}

注册后可以单独验证:

curl -X POST \
  "http://localhost:9200/_snapshot/cheese7/_verify?pretty"

6. 创建 Snapshot

curl -X PUT \
  "http://localhost:9200/_snapshot/cheese7/snapshot_001?wait_for_completion=true&pretty"

查看 Snapshot:

curl \
  "http://localhost:9200/_snapshot/cheese7/snapshot_001?pretty"

适合长期运行的 Docker 配置

手动进入运行中的容器修改 keystore 适合临时排查。容器重新创建后,这些修改会消失,因此不适合长期部署。

生产环境应在 Elasticsearch 启动前完成以下工作:

  1. 确认 distribution 已包含 repository-gcs,或者在自定义镜像中预先安装。
  2. 通过 Docker secret 或只读挂载提供 Service Account JSON。
  3. 由启动脚本在 Elasticsearch 进程启动前执行 elasticsearch-keystore add-file。
  4. 妥善持久化 /usr/share/elasticsearch/config 或其中的 keystore。
  5. 避免将原始凭据文件写入镜像层、日志、repository API 请求或版本库。

不要在 Dockerfile 中用 COPY 将长期有效的 Service Account 私钥直接打包进镜像。即使之后删除该文件,它仍可能留在旧的镜像层中。

单独检查 GCS 权限

凭据成功加载后,如果出现 403 Forbidden、storage.objects.* denied 或 bucket 不存在等错误,再检查 Google Cloud IAM。

Service Account 至少需要具备实际 Snapshot 操作所需的 bucket 权限,包括列出、读取、创建和删除对象。具体权限取决于是否只读恢复、是否创建 Snapshot,以及是否执行清理。其他应用可以访问同一个 bucket,不代表它拥有 Elasticsearch repository 验证和维护所需的全部权限。

排查时关注这些信号

如果日志中仍有以下内容:

failed to load Application Default Credentials
metadata.google.internal
.config/gcloud/active_config

通常表示 Elasticsearch 仍未取得指定客户端的凭据。可以依次检查:

  1. keystore setting 名称是否准确。
  2. repository 的 client 是否与 setting 中的客户端名一致。
  3. 是否已在所有相关节点上添加凭据。
  4. 执行 add-file 后是否调用了 /_nodes/reload_secure_settings。
  5. reload 响应是否包含 reload_exception。
  6. Elasticsearch 实际使用的配置目录,是否与修改 keystore 时使用的目录相同。
  7. 是否已显式配置 project ID。

不要为 .config/gcloud/active_config 添加 Java policy,也不要用 chmod 1777 绕过错误。该文件只是默认发现流程中的一个候选来源。正确加载 keystore 凭据并配置 project ID 后,Elasticsearch 无需依赖容器中的本地 gcloud 配置。

备注:内容仅供参考。