
本文档完整记录了在中国区 EKS 集群上部署 AWS Workload Credentials Provider 的全过程。通过 Sidecar 模式,应用可以无感知地获取 AWS Secrets Manager 中的敏感配置,无需在代码中处理复杂的 AWS 认证逻辑。
在 Kubernetes 中管理 Secret 始终是个让人头疼的问题。传统的工作流中,每个开发团队都要自己实现一套从 AWS 获取 Secret 的逻辑------有的用 SDK,有的用 init container,有的干脆写死在配置文件里。这不仅重复造轮子,还带来了严重的安全隐患。
AWS Workload Credentials Provider(WCP)的出现改变了这一切。作为 AWS Secrets Manager Agent 的演进版本,它采用了 Sidecar 代理模式------你在 Pod 中描述想要的 Secret,WCP 负责获取、缓存和提供。
在我们的实践中,整个系统体现了优雅的分层思想。这种分层设计让每个组件职责单一,便于独立演进和故障隔离。具体来说,我们将整个架构划分为四个层次:
系统分层架构
| 层级 | 组件 | 职责 |
|---|---|---|
| 基础设施层 | EKS 集群、ECR、VPC | 提供计算和网络资源 |
| 身份层 | EKS Pod Identity、IAM | 托管 AWS 凭证 |
| 代理层 | WCP Sidecar | Secret 获取、缓存、刷新 |
| 应用层 | Agent 应用 | 业务逻辑,无感知使用 Secret |
一个典型的 Pod 包含两个容器协同工作:
- Agent 容器(业务应用):运行 LLM 代理服务,通过 localhost 访问 Secret
- WCP 容器(Sidecar):提供本地 HTTP 服务,缓存来自 Secrets Manager 的 API Key
WCP 网络架构与通信模型
在深入部署之前,理解 WCP 的通信模型至关重要。WCP 的设计哲学是将 Secret 管理下沉到基础设施层,让应用容器无感知地使用。下图展示了 Pod 内部的网络拓扑
#mermaid-svg-5BDfbgMCFpF5DsZJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5BDfbgMCFpF5DsZJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5BDfbgMCFpF5DsZJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5BDfbgMCFpF5DsZJ .error-icon{fill:#552222;}#mermaid-svg-5BDfbgMCFpF5DsZJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5BDfbgMCFpF5DsZJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5BDfbgMCFpF5DsZJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5BDfbgMCFpF5DsZJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5BDfbgMCFpF5DsZJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5BDfbgMCFpF5DsZJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5BDfbgMCFpF5DsZJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5BDfbgMCFpF5DsZJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5BDfbgMCFpF5DsZJ .marker.cross{stroke:#333333;}#mermaid-svg-5BDfbgMCFpF5DsZJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5BDfbgMCFpF5DsZJ p{margin:0;}#mermaid-svg-5BDfbgMCFpF5DsZJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5BDfbgMCFpF5DsZJ .cluster-label text{fill:#333;}#mermaid-svg-5BDfbgMCFpF5DsZJ .cluster-label span{color:#333;}#mermaid-svg-5BDfbgMCFpF5DsZJ .cluster-label span p{background-color:transparent;}#mermaid-svg-5BDfbgMCFpF5DsZJ .label text,#mermaid-svg-5BDfbgMCFpF5DsZJ span{fill:#333;color:#333;}#mermaid-svg-5BDfbgMCFpF5DsZJ .node rect,#mermaid-svg-5BDfbgMCFpF5DsZJ .node circle,#mermaid-svg-5BDfbgMCFpF5DsZJ .node ellipse,#mermaid-svg-5BDfbgMCFpF5DsZJ .node polygon,#mermaid-svg-5BDfbgMCFpF5DsZJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5BDfbgMCFpF5DsZJ .rough-node .label text,#mermaid-svg-5BDfbgMCFpF5DsZJ .node .label text,#mermaid-svg-5BDfbgMCFpF5DsZJ .image-shape .label,#mermaid-svg-5BDfbgMCFpF5DsZJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-5BDfbgMCFpF5DsZJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5BDfbgMCFpF5DsZJ .rough-node .label,#mermaid-svg-5BDfbgMCFpF5DsZJ .node .label,#mermaid-svg-5BDfbgMCFpF5DsZJ .image-shape .label,#mermaid-svg-5BDfbgMCFpF5DsZJ .icon-shape .label{text-align:center;}#mermaid-svg-5BDfbgMCFpF5DsZJ .node.clickable{cursor:pointer;}#mermaid-svg-5BDfbgMCFpF5DsZJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5BDfbgMCFpF5DsZJ .arrowheadPath{fill:#333333;}#mermaid-svg-5BDfbgMCFpF5DsZJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5BDfbgMCFpF5DsZJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5BDfbgMCFpF5DsZJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5BDfbgMCFpF5DsZJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5BDfbgMCFpF5DsZJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5BDfbgMCFpF5DsZJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5BDfbgMCFpF5DsZJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5BDfbgMCFpF5DsZJ .cluster text{fill:#333;}#mermaid-svg-5BDfbgMCFpF5DsZJ .cluster span{color:#333;}#mermaid-svg-5BDfbgMCFpF5DsZJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5BDfbgMCFpF5DsZJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5BDfbgMCFpF5DsZJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-5BDfbgMCFpF5DsZJ .icon-shape,#mermaid-svg-5BDfbgMCFpF5DsZJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5BDfbgMCFpF5DsZJ .icon-shape p,#mermaid-svg-5BDfbgMCFpF5DsZJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5BDfbgMCFpF5DsZJ .icon-shape .label rect,#mermaid-svg-5BDfbgMCFpF5DsZJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5BDfbgMCFpF5DsZJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5BDfbgMCFpF5DsZJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5BDfbgMCFpF5DsZJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 外部访问
Pod网络命名空间
localhost:2773
HTTPS
AssumeRole
注入凭证
HTTP
Agent App
:8080
WCP Sidecar
HTTP Server
AWS Secrets Manager
EKS Pod Identity
用户
通信时序(Agent 调用 LLM 的完整流程)
GLM-5.2 API Secrets Manager WCP Sidecar Agent App 用户 GLM-5.2 API Secrets Manager WCP Sidecar Agent App 用户 #mermaid-svg-fHalPDfKBf69eSij{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fHalPDfKBf69eSij .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fHalPDfKBf69eSij .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fHalPDfKBf69eSij .error-icon{fill:#552222;}#mermaid-svg-fHalPDfKBf69eSij .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fHalPDfKBf69eSij .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fHalPDfKBf69eSij .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fHalPDfKBf69eSij .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fHalPDfKBf69eSij .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fHalPDfKBf69eSij .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fHalPDfKBf69eSij .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fHalPDfKBf69eSij .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fHalPDfKBf69eSij .marker.cross{stroke:#333333;}#mermaid-svg-fHalPDfKBf69eSij svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fHalPDfKBf69eSij p{margin:0;}#mermaid-svg-fHalPDfKBf69eSij .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-fHalPDfKBf69eSij text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-fHalPDfKBf69eSij .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-fHalPDfKBf69eSij .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-fHalPDfKBf69eSij .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-fHalPDfKBf69eSij .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-fHalPDfKBf69eSij #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-fHalPDfKBf69eSij .sequenceNumber{fill:white;}#mermaid-svg-fHalPDfKBf69eSij #sequencenumber{fill:#333;}#mermaid-svg-fHalPDfKBf69eSij #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-fHalPDfKBf69eSij .messageText{fill:#333;stroke:none;}#mermaid-svg-fHalPDfKBf69eSij .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-fHalPDfKBf69eSij .labelText,#mermaid-svg-fHalPDfKBf69eSij .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-fHalPDfKBf69eSij .loopText,#mermaid-svg-fHalPDfKBf69eSij .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-fHalPDfKBf69eSij .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-fHalPDfKBf69eSij .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-fHalPDfKBf69eSij .noteText,#mermaid-svg-fHalPDfKBf69eSij .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-fHalPDfKBf69eSij .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-fHalPDfKBf69eSij .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-fHalPDfKBf69eSij .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-fHalPDfKBf69eSij .actorPopupMenu{position:absolute;}#mermaid-svg-fHalPDfKBf69eSij .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-fHalPDfKBf69eSij .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-fHalPDfKBf69eSij .actor-man circle,#mermaid-svg-fHalPDfKBf69eSij line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-fHalPDfKBf69eSij :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1. Pod 启动阶段 TTL 300秒,预取完成 2. 请求处理阶段 Header: X-Aws-Parameters-Secrets-Token 无需调用 AWS API GetSecretValue(llm/api/config)返回 Secret + 缓存到内存POST /chat {"message":"你好"}GET localhost:2773/secretsmanager/get返回 Secret (缓存命中 <1ms)POST open.bigmodel.cn/...Authorization: Bearer <API Key>返回 LLM 响应{"response":"我是GLM..."}
在整个通信过程中,WCP 的性能表现是我们最关心的指标。基于实际测试,我们收集了以下关键性能数据:
| 指标 | 数值 | 说明 |
|---|---|---|
| 缓存命中延迟 | ~1ms | 内存读取,无网络 IO |
| 缓存未命中 | ~50-100ms | 需调用 AWS API |
| 默认 TTL | 300s | 可配置 |
| 预取数量 | 2 个 | llm/api/config, test/wcp/demo |
在决定使用 Sidecar 模式之前,我们对比了三种不同的部署方案。每种方案都有其特定的适用场景和权衡。建议生产环境使用 Sidecar 模式。WCP 的设计初衷就是 per-Pod 代理,每个应用有自己的缓存实例,故障域隔离。
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Sidecar | 隔离性好,故障不扩散,缓存独立 | 额外容器开销 | 推荐,生产环境 |
| DaemonSet | 资源共享,节点级缓存 | 单点故障,权限共享 | 测试环境 |
| SDK 直连 | 简单直接 | 重复造轮子,无缓存 | 不推荐 |
配置详解
以下是完整的 Dockerfile,包含了 Rust 依赖的中国区镜像加速配置:
dockerfile
#==============================================================================
# Stage 1: Builder
#==============================================================================
FROM public.ecr.aws/docker/library/rust:1.92-bookworm AS builder
# 配置中国镜像加速 (Rust crates)
RUN mkdir -p /usr/local/cargo && \
echo '[source.crates-io]' > /usr/local/cargo/config.toml && \
echo 'replace-with = "rsproxy-sparse"' >> /usr/local/cargo/config.toml && \
echo '[source.rsproxy-sparse]' >> /usr/local/cargo/config.toml && \
echo 'registry = "sparse+https://rsproxy.cn/index/"' >> /usr/local/cargo/config.toml && \
echo '[net]' >> /usr/local/cargo/config.toml && \
echo 'git-fetch-with-cli = true' >> /usr/local/cargo/config.toml
WORKDIR /src
COPY . .
RUN CARGO_HOME=/usr/local/cargo cargo build --release && \
cp /src/target/release/aws-workload-credentials-provider /aws-workload-credentials-provider
#==============================================================================
# Stage 2: Runtime
#==============================================================================
FROM public.ecr.aws/docker/library/debian:bookworm-slim
RUN apt-get update && \
apt-get install -y --no-install-recommends ca-certificates && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /aws-workload-credentials-provider /app/aws-workload-credentials-provider
RUN chmod 0755 /app/aws-workload-credentials-provider
ENTRYPOINT ["/app/aws-workload-credentials-provider"]
CMD ["sm", "start", "--config", "/etc/wcp/config.toml"]
在这个 Dockerfile 中,有几个关键配置需要特别注意:
| 配置 | 说明 |
|---|---|
rsproxy-sparse |
中国区 Rust crates 镜像,加速依赖下载 |
bookworm |
Debian 12,glibc 2.36 |
readOnlyRootFilesystem: true |
运行时根文件系统只读(安全最佳实践) |
ConfigMap
WCP 采用 TOML 格式配置。核心参数说明:
| 配置项 | 默认值 | 说明 |
|---|---|---|
ttl_seconds |
300 | Secret 缓存过期时间(秒) |
cache_size |
1000 | 最大缓存 Secret 数量 |
http_port |
2773 | WCP 监听端口 |
ssrf_headers |
X-Aws-Parameters-Secrets-Token | 请求必须携带的 SSRF 防护头部 |
prefetch.secrets |
- | 启动时预取的 Secret 列表 |
yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: wcp-config
namespace: <namespace>
data:
config.toml: |
[logging]
log_level = "INFO"
log_to_file = false
[capabilities.secrets_manager]
enabled = true
http_port = 2773
region = "<region>"
max_conn = 800
[capabilities.secrets_manager.cache]
ttl_seconds = 300
cache_size = 1000
[capabilities.secrets_manager.security]
ssrf_headers = ["X-Aws-Parameters-Secrets-Token"]
ssrf_env_variables = ["AWS_TOKEN"]
[capabilities.secrets_manager.prefetch]
cache_buffer_ratio = 0.8
max_jitter_seconds = 5
[[capabilities.secrets_manager.prefetch.secrets]]
secret_id = "llm/api/config"
Deployment
Sidecar 模式 Deployment 关键配置:
| 配置项 | 说明 |
|---|---|
localhost:2773 |
WCP 只监听 127.0.0.1,仅本 Pod 可访问 |
emptyDir (Memory) |
WCP 使用内存临时存储 |
serviceAccountName |
绑定 EKS Pod Identity 的 ServiceAccount |
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: agent-app
namespace: <namespace>
spec:
replicas: 1
template:
spec:
serviceAccountName: wcp-app-sa
containers:
- name: agent
image: <account-id>.dkr.ecr.<region>.amazonaws.com.cn/agent-app:latest
ports:
- containerPort: 8080
env:
- name: WCP_HOST
value: "localhost"
- name: WCP_PORT
value: "2773"
- name: WCP_TOKEN
value: "<namespace>-token-12345"
- name: SECRET_NAME
value: "llm/api/config"
readinessProbe:
httpGet:
path: /health
port: 8080
- name: wcp-provider
image: <account-id>.dkr.ecr.<region>.amazonaws.com.cn/wcp:latest
args:
- sm
- start
- --config
- /etc/wcp/config.toml
env:
- name: AWS_REGION
value: <region>
- name: AWS_TOKEN
value: "<namespace>-token-12345"
volumeMounts:
- name: wcp-config
mountPath: /etc/wcp
readOnly: true
- name: wcp-tmp
mountPath: /tmp
volumes:
- name: wcp-config
configMap:
name: wcp-config
- name: wcp-tmp
emptyDir:
medium: Memory
Agent 应用代码
Agent 应用通过 localhost:2773 访问 WCP 获取 Secret,必须携带 SSRF Token:
go
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
func getSecretFromWCP() (*LLMConfig, error) {
url := fmt.Sprintf("http://%s:%s/secretsmanager/get?secretId=%s",
getEnv("WCP_HOST", "localhost"),
getEnv("WCP_PORT", "2773"),
getEnv("SECRET_NAME", "llm/api/config"))
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("X-Aws-Parameters-Secrets-Token",
getEnv("WCP_TOKEN", ""))
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result struct {
SecretString string `json:"SecretString"`
}
json.NewDecoder(resp.Body).Decode(&result)
var llmConfig LLMConfig
json.Unmarshal([]byte(result.SecretString), &llmConfig)
return &llmConfig, nil
}
测试验证
Pod 状态检查:
bash
$ kubectl get pods -n <namespace> -o wide
NAME READY STATUS RESTARTS AGE
agent-app-748ddb77d5-mwbms 2/2 Running 0 5m
WCP 日志确认预取成功:
2026-06-28T11:18:40.818Z INFO - listening on http://127.0.0.1:2773
2026-06-28T11:18:45.728Z INFO - Pre-fetch complete: success=2, failed=0
Health 检查:
bash
$ kubectl exec -n <namespace> deployment/agent-app -c agent -- \
curl -s http://localhost:8080/health
{"status":"healthy"}
Chat 对话测试:
bash
$ kubectl exec -n <namespace> deployment/agent-app -c agent -- \
curl -s -X POST http://localhost:8080/chat \
-H "Content-Type: application/json" \
-d '{"message":"你好"}'
响应示例:
json
{
"model": "GLM-5.2",
"response": "我是GLM,由Z.ai开发的大语言模型..."
}
整个方案的核心价值在于:让应用专注于业务逻辑,将 Secret 管理交给基础设施。WCP 的本地缓存机制将 Secret 获取延迟从 50-100ms 降至 1ms,同时 SSRF 防护确保了即使在高风险场景下也能安全地提供 Secret 服务。
最后需要说明的是,本文档只展示了 WCP 的 Secrets Manager 能力。实际上 WCP 还支持 ACM 证书导出 功能------它可以自动从 AWS Certificate Manager 导出 SSL/TLS 证书到本地文件系统,并支持证书到期自动刷新和自定义刷新命令。如果你的应用需要管理 HTTPS 证书,WCP 同样可以通过 Sidecar 或系统服务的方式提供自动化的证书管理。