Docker容器内Java应用无法访问IMDS但curl正常的问题排查
问题背景
我在EC2实例中使用Docker Compose运行基于eclipse-temurin:21-jdk的Java容器。我清楚默认情况下Docker容器无法访问主机网络,除非启用network_mode: "host"。
但遇到了异常情况:
- 容器内执行
curl命令能正常从实例元数据服务(IMDS)获取元数据:
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
该命令可成功返回响应。
- 但我的Java应用(使用AWS SDK)尝试从IMDS获取凭证时失败,抛出异常:
software.amazon.awssdk.core.exception.SdkClientException: Unable to load credentials from any of the providers in the chain AwsCredentialsProviderChain(credentialsProviders=[SystemPropertyCredentialsProvider(), EnvironmentVariableCredentialsProvider(), WebIdentityTokenCredentialsProvider(), ProfileCredentialsProvider(profileName=default), ContainerCredentialsProvider(), InstanceProfileCredentialsProvider()]) : [SystemPropertyCredentialsProvider(): Unable to load credentials from system settings. Access key must be specified either via environment variable (AWS_ACCESS_KEY_ID) or system property (aws.accessKeyId)., EnvironmentVariableCredentialsProvider(): Unable to load credentials from system settings. Access key must be specified either via environment variable (AWS_ACCESS_KEY_ID) or system property (aws.accessKeyId)., WebIdentityTokenCredentialsProvider(): Either the environment variable AWS_WEB_IDENTITY_TOKEN_FILE or the javaproperty aws.webIdentityTokenFile must be set., ProfileCredentialsProvider(profileName=default): java.io.IOException: Is a directory, ContainerCredentialsProvider(): Cannot fetch credentials from container - neither AWS_CONTAINER_CREDENTIALS_FULL_URI or AWS_CONTAINER_CREDENTIALS_RELATIVE_URI environment variables are set., InstanceProfileCredentialsProvider(): Failed to load credentials from IMDS.]
我的问题
- 为什么容器内的
curl能成功访问IMDS,但Java应用中的AWS SDK却失败? - 是否可以在不使用
network_mode: "host"或sidecar代理容器的情况下让应用访问IMDS?如果可以,如何实现?
已尝试的操作
- 搜索多个线程并咨询工具,但大多数解决方案需要:
- 设置
network_mode: "host"(我希望避免此操作) - 使用sidecar容器作为代理访问IMDS
- 设置
- 这两种方案都不符合我的使用需求。
额外上下文
169.254.169.254是EC2上实例元数据服务(IMDS)的虚拟IP地址。- 我的应用需要访问IMDS以获取IAM角色凭证,用于与AWS服务进行身份验证。
Docker Compose文件
version: "3" services: test-container: image: myContainerImage restart: unless-stopped environment: AWS_REGION: ${REGION} AWS_EC2_METADATA_DISABLED: "false" AWS_EC2_METADATA_SERVICE_ENDPOINT: "http://169.254.169.254" #network_mode: "host" volumes: - $HOME/.aws/credentials:/root/.aws/credentials:ro ports: - "8081:8082" volumes: datalake-data: driver: local
解答
问题1:curl能访问但AWS SDK失败的原因
- IMDS版本差异:EC2的IMDS有v1和v2两个版本。
curl默认使用无需会话令牌的IMDSv1即可访问,但AWS SDK v2默认尝试使用IMDSv2——该版本要求先获取会话令牌才能请求凭证。容器内curl直接访问IMDSv1路径能成功,但SDK的InstanceProfileCredentialsProvider默认走IMDSv2流程,容器网络环境下获取会话令牌的请求可能因NAT规则限制被拦截或失败。 - SDK配置与凭证链干扰:你设置的
AWS_EC2_METADATA_SERVICE_ENDPOINT可能未被SDK正确识别;同时挂载的~/.aws/credentials是目录而非文件,导致ProfileCredentialsProvider报错,打乱了凭证链的正常遍历流程,影响了IMDS提供者的执行。 - 重定向处理差异:IMDSv2请求存在302重定向,
curl会自动处理该重定向,但SDK在容器网络下处理重定向时可能出现网络问题,导致请求失败。
问题2:不使用host网络或sidecar的解决方案
方案1:强制AWS SDK使用IMDSv1
在容器环境变量中添加:
AWS_EC2_METADATA_V1_DISABLED="false" AWS_SDK_JAVA_IMDS_V1_ENABLED="true"
或者在Java代码中配置InstanceProfileCredentialsProvider强制启用IMDSv1:
InstanceProfileCredentialsProvider credentialsProvider = InstanceProfileCredentialsProvider.builder() .imdsClient(ImdsClient.builder() .endpointOverride(URI.create("http://169.254.169.254")) .useImdsV1(true) .build()) .build();
注意:IMDSv1安全性低于v2,仅在无法使用v2的场景下临时使用。
方案2:修复IMDSv2的容器网络访问
IMDSv2要求客户端发送PUT请求获取会话令牌,Docker默认桥接网络中,容器发出的PUT请求可能被EC2网络规则拦截。可通过修改EC2实例的IMDS配置允许来自容器网络的请求:
- 在EC2实例上执行以下AWS CLI命令:
aws ec2 modify-instance-metadata-options --instance-id <你的实例ID> --http-put-response-hop-limit 2
该命令将IMDS的响应跳数限制从默认的1改为2,允许处于NAT后的容器(第二跳)接收IMDSv2的会话令牌响应。
2. 保持SDK默认的IMDSv2配置即可,无需额外修改。
方案3:修复凭证挂载问题
Docker Compose中挂载的$HOME/.aws/credentials是目录而非文件,导致ProfileCredentialsProvider报错。如果不需要本地凭证,直接删除该挂载项;如果需要,确保宿主机的$HOME/.aws/credentials是文件,挂载配置保持不变即可。
内容的提问来源于stack exchange,提问作者k.elgohary
相关产品推荐
相关产品推荐

