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_configmetadata.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 中已经加载的配置不会随之自动更新。
题目中的操作顺序是:
- 启动 Elasticsearch。
- 向 keystore 写入 GCS 凭据。
- 安装
repository-gcs。 - 立即注册 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 启动前完成以下工作:
- 确认 distribution 已包含
repository-gcs,或者在自定义镜像中预先安装。 - 通过 Docker secret 或只读挂载提供 Service Account JSON。
- 由启动脚本在 Elasticsearch 进程启动前执行
elasticsearch-keystore add-file。 - 妥善持久化
/usr/share/elasticsearch/config或其中的 keystore。 - 避免将原始凭据文件写入镜像层、日志、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 仍未取得指定客户端的凭据。可以依次检查:
- keystore setting 名称是否准确。
- repository 的
client是否与 setting 中的客户端名一致。 - 是否已在所有相关节点上添加凭据。
- 执行
add-file后是否调用了/_nodes/reload_secure_settings。 - reload 响应是否包含
reload_exception。 - Elasticsearch 实际使用的配置目录,是否与修改 keystore 时使用的目录相同。
- 是否已显式配置 project ID。
不要为 .config/gcloud/active_config 添加 Java policy,也不要用 chmod 1777 绕过错误。该文件只是默认发现流程中的一个候选来源。正确加载 keystore 凭据并配置 project ID 后,Elasticsearch 无需依赖容器中的本地 gcloud 配置。
备注:内容仅供参考。
