704 lines
22 KiB
Markdown
704 lines
22 KiB
Markdown
---
|
||
tags: [k8s, configmap, secret, devops]
|
||
create time: 2026-07-07 15:48
|
||
---
|
||
|
||
# ConfigMap 与 Secret — K8s 配置管理
|
||
|
||
## 概述
|
||
|
||
> [!info] 十二要素应用原则(12-Factor App)
|
||
> 配置应当与代码严格分离。镜像是不可变的构建产物,而运行时环境(数据库地址、API Key、特性开关)应当通过外部注入,而非硬编码在镜像中。Kubernetes 的 **ConfigMap** 和 **Secret** 正是实现这一原则的核心机制——它们将配置数据从 Pod 定义中解耦,使得同一镜像可以部署到不同环境而无需重建。
|
||
|
||
简而言之:
|
||
|
||
- **ConfigMap**:存放非敏感的配置数据(如 Config 文件、命令行参数、环境变量)。
|
||
- **Secret**:存放敏感数据(如密码、Token、TLS 证书),并提供额外的安全保障。
|
||
|
||
下图展示了配置数据如何从集群外部流入 Pod 内部:
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph Cluster["K8s Cluster"]
|
||
CM[ConfigMap]
|
||
SEC[Secret]
|
||
ETCD[(etcd)]
|
||
CM --> ETCD
|
||
SEC --> ETCD
|
||
end
|
||
|
||
subgraph Pod["Pod"]
|
||
ENV["环境变量注入"]
|
||
VOL["Volume 挂载\n(/etc/config, /etc/secret)"]
|
||
end
|
||
|
||
ETCD -->|kubelet sync| VOL
|
||
ETCD -->|Pod 启动时注入| ENV
|
||
|
||
subgraph External["外部管理"]
|
||
VAULT["HashiCorp Vault"]
|
||
AWS_SM["AWS Secrets Manager"]
|
||
end
|
||
|
||
External -->|CSI Driver / Sync| SEC
|
||
```
|
||
|
||
---
|
||
|
||
## ConfigMap 详解
|
||
|
||
### 创建方式
|
||
|
||
#### 1. 命令式创建(kubectl create configmap)
|
||
|
||
```bash
|
||
# 方式一:从字面值创建
|
||
kubectl create configmap app-config \
|
||
--from-literal=APP_ENV=production \
|
||
--from-literal=LOG_LEVEL=info \
|
||
--from-literal=DB_HOST=mysql.default.svc.cluster.local
|
||
|
||
# 方式二:从单个文件创建
|
||
kubectl create configmap nginx-conf \
|
||
--from-file=nginx.conf
|
||
|
||
# 方式三:从目录创建(目录下所有文件各成为一个 key)
|
||
kubectl create configmap app-config-files \
|
||
--from-file=config/
|
||
|
||
# 方式四:指定 key 名称(而非使用文件名作为 key)
|
||
kubectl create configmap app-conf \
|
||
--from-file=my-config=./app.properties
|
||
|
||
# 方式五:从 .env 文件创建(key=value 格式)
|
||
kubectl create configmap env-config \
|
||
--from-env-file=.env
|
||
```
|
||
|
||
> [!tip] `--from-env-file` vs `--from-file`
|
||
> `--from-env-file` 解析 `KEY=VALUE` 格式,每个键值对成为一个独立的 key;而 `--from-file` 将整个文件内容作为单个 key 的 value。二者用途不同,不要混淆。
|
||
|
||
#### 2. 声明式创建(YAML)
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: ConfigMap
|
||
metadata:
|
||
name: app-config
|
||
namespace: default
|
||
labels:
|
||
app: my-app
|
||
data:
|
||
# 简单键值对
|
||
APP_ENV: "production"
|
||
LOG_LEVEL: "info"
|
||
DB_PORT: "3306"
|
||
|
||
# 多行配置文件作为 value
|
||
application.properties: |
|
||
server.port=8080
|
||
spring.datasource.url=jdbc:mysql://mysql:3306/app
|
||
spring.datasource.username=root
|
||
logging.level.root=INFO
|
||
|
||
# JSON 格式的配置
|
||
features.json: |
|
||
{
|
||
"enableDarkMode": true,
|
||
"maxRetries": 3,
|
||
"timeout": 30
|
||
}
|
||
```
|
||
|
||
查看创建结果:
|
||
|
||
```bash
|
||
kubectl describe configmap app-config
|
||
kubectl get configmap app-config -o yaml
|
||
```
|
||
|
||
### 挂载方式
|
||
|
||
ConfigMap 中的数据注入到 Pod 有两种主要方式:**环境变量注入** 和 **Volume 挂载**。二者在行为上有显著差异。
|
||
|
||
| 特性 | 环境变量注入 | Volume 挂载 |
|
||
|------|-------------|------------|
|
||
| 注入时机 | Pod 启动时 | Pod 启动时 + 运行时可更新 |
|
||
| 热更新支持 | 不支持,需重启 Pod | 支持(kubelet 定期同步) |
|
||
| 适用场景 | 简单键值对、启动参数 | 配置文件(nginx.conf 等) |
|
||
| 引用方式 | `configMapKeyRef` / `envFrom` | `volumes.configMap` |
|
||
| 文件权限 | 不涉及 | 可设置 `defaultMode` |
|
||
| 多 key 批量注入 | `envFrom` 批量导入 | 整个目录挂载 |
|
||
|
||
#### 环境变量注入
|
||
|
||
**方式一:逐个 key 引用(`configMapKeyRef`)**
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: Pod
|
||
metadata:
|
||
name: app-pod
|
||
spec:
|
||
containers:
|
||
- name: app
|
||
image: my-app:1.0
|
||
env:
|
||
# 引用 ConfigMap 中的单个 key
|
||
- name: APP_ENV # 容器内的环境变量名
|
||
valueFrom:
|
||
configMapKeyRef:
|
||
name: app-config # ConfigMap 名称
|
||
key: APP_ENV # ConfigMap 中的 key 名
|
||
- name: LOG_LEVEL
|
||
valueFrom:
|
||
configMapKeyRef:
|
||
name: app-config
|
||
key: LOG_LEVEL
|
||
optional: true # ConfigMap 或 key 不存在时不报错
|
||
```
|
||
|
||
**方式二:批量导入(`envFrom`)**
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: Pod
|
||
metadata:
|
||
name: app-pod
|
||
spec:
|
||
containers:
|
||
- name: app
|
||
image: my-app:1.0
|
||
envFrom:
|
||
- configMapRef:
|
||
name: app-config
|
||
prefix: "CFG_" # 可选前缀,避免命名冲突
|
||
```
|
||
|
||
> [!warning] `envFrom` 会跳过无效 key
|
||
> ConfigMap 中如果存在不符合环境变量命名规范的 key(如包含 `-` 或以数字开头),这些 key 会被静默跳过,不会报错。使用时需注意 key 的命名规范。
|
||
|
||
#### Volume 挂载
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: Pod
|
||
metadata:
|
||
name: app-pod
|
||
spec:
|
||
containers:
|
||
- name: app
|
||
image: nginx:1.25
|
||
volumeMounts:
|
||
- name: config-volume
|
||
mountPath: /etc/nginx/conf.d # 挂载到容器内目录
|
||
readOnly: true
|
||
- name: app-properties
|
||
mountPath: /app/config # 挂载整个目录
|
||
volumes:
|
||
# 挂载 ConfigMap 中指定的 key
|
||
- name: config-volume
|
||
configMap:
|
||
name: nginx-conf
|
||
items:
|
||
- key: default.conf
|
||
path: default.conf # 文件名(挂载后的文件名)
|
||
mode: 0644 # 文件权限
|
||
# 挂载 ConfigMap 全部 key(每个 key 成为一个文件)
|
||
- name: app-properties
|
||
configMap:
|
||
name: app-config
|
||
defaultMode: 0644 # 默认文件权限
|
||
```
|
||
|
||
### 热更新机制
|
||
|
||
ConfigMap 通过 Volume 挂载后,当 ConfigMap 的内容在集群中被更新时,kubelet 会自动将新内容同步到 Pod 的挂载目录中。这个过程的延迟取决于:
|
||
|
||
1. **kubelet sync 周期**:默认每 **1 分钟**(`--sync-frequency` 参数控制)。
|
||
2. **传播延迟**:从 API Server 到 kubelet 的 watch 通知延迟。
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant User as 用户/kubectl
|
||
participant API as API Server
|
||
participant Kubelet as Kubelet (Node)
|
||
participant Pod as Pod Container
|
||
|
||
User->>API: kubectl apply -f configmap.yaml
|
||
API->>Kubelet: watch 通知 ConfigMap 变更
|
||
Note over Kubelet: 等待 sync 周期(最多 1 分钟)
|
||
Kubelet->>Pod: 更新 Volume 挂载文件(原子替换)
|
||
Note over Pod: 应用需 reload 配置(如 nginx -s reload)
|
||
```
|
||
|
||
> [!warning] 热更新 ≠ 自动生效
|
||
> ConfigMap 文件更新后,**应用本身必须能够感知文件变化并重新加载配置**。例如 Nginx 需要 `nginx -s reload`,Spring Boot 需要 `@RefreshScope`。否则虽然文件内容已更新,但应用仍使用旧配置。
|
||
|
||
**环境变量方式不可热更新**:环境变量在 Pod 启动时由 kubelet 注入到容器的进程环境中,运行期间不会再更新。要更新环境变量,只能删除并重建 Pod。
|
||
|
||
### 不可变 ConfigMap
|
||
|
||
从 Kubernetes 1.21 开始,可以将 ConfigMap 标记为不可变(immutable),这是一个重要的性能优化手段。
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: ConfigMap
|
||
metadata:
|
||
name: app-config-v1
|
||
immutable: true # 标记为不可变
|
||
data:
|
||
APP_ENV: "production"
|
||
LOG_LEVEL: "info"
|
||
```
|
||
|
||
**为什么需要不可变 ConfigMap?**
|
||
|
||
- **减轻 API Server 压力**:普通 ConfigMap 会被 kubelet watch,任何变更都会触发全集群的通知广播。对于大规模集群(数千节点),大量 watch 事件会显著增加 API Server 的负担。
|
||
- **防止意外修改**:标记为 immutable 后,该 ConfigMap 的内容不可更改(只能删除重建)。
|
||
- **推荐场景**:发布后不再变更的配置(如已上线的版本配置)。
|
||
|
||
> [!tip] 不可变 ConfigMap 的更新策略
|
||
> 由于 immutable ConfigMap 无法修改,更新配置时需要创建一个新的 ConfigMap(如 `app-config-v2`),然后更新 Deployment 引用新的 ConfigMap 名称。可以结合 Deployment 的滚动更新实现零停机配置切换。
|
||
|
||
---
|
||
|
||
## Secret 详解
|
||
|
||
### 类型
|
||
|
||
Kubernetes 内置了多种 Secret 类型,用于不同场景。以下是常用类型的对比:
|
||
|
||
| 类型 | 用途 | data 字段说明 | 典型场景 |
|
||
|------|------|--------------|---------|
|
||
| `Opaque` | 通用型(默认) | 用户自定义的任意 key-value | 数据库密码、API Key 等 |
|
||
| `kubernetes.io/tls` | TLS 证书 | 必须含 `tls.crt` 和 `tls.key` | Ingress TLS 终止 |
|
||
| `kubernetes.io/dockerconfigjson` | 镜像仓库凭证 | 必须含 `.dockerconfigjson` | 私有镜像仓库拉取 |
|
||
| `kubernetes.io/basic-auth` | 基本认证 | `username` + `password` | Git/HTTP Basic Auth |
|
||
| `kubernetes.io/ssh-auth` | SSH 认证 | 必须含 `ssh-privatekey` | Git SSH 拉取 |
|
||
| `kubernetes.io/service-account-token` | SA Token | 自动创建 | Pod 身份认证(已逐步被 Bound SA Token 替代) |
|
||
|
||
#### Opaque Secret 示例
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: Secret
|
||
metadata:
|
||
name: db-credentials
|
||
type: Opaque
|
||
data:
|
||
# 值必须是 Base64 编码
|
||
username: cm9vdA== # echo -n 'root' | base64
|
||
password: czNjcjN0UEBzc3cwcmQ= # echo -n 's3cr3tP@ssw0rd' | base64
|
||
# 也可以用 stringData(自动编码,方便调试,生产慎用)
|
||
stringData:
|
||
connection-string: "mysql://root:s3cr3tP@ssw0rd@mysql:3306/app"
|
||
```
|
||
|
||
#### TLS Secret 示例
|
||
|
||
```bash
|
||
# 通过 kubectl 创建 TLS Secret
|
||
kubectl create secret tls my-tls-secret \
|
||
--cert=tls.crt \
|
||
--key=tls.key
|
||
```
|
||
|
||
```yaml
|
||
# 等价的 YAML 声明
|
||
apiVersion: v1
|
||
kind: Secret
|
||
metadata:
|
||
name: my-tls-secret
|
||
type: kubernetes.io/tls
|
||
data:
|
||
tls.crt: <base64-encoded-cert>
|
||
tls.key: <base64-encoded-key>
|
||
```
|
||
|
||
#### Docker Registry Secret
|
||
|
||
```bash
|
||
# 创建 Docker Registry 凭证
|
||
kubectl create secret docker-registry regcred \
|
||
--docker-server=registry.example.com \
|
||
--docker-username=admin \
|
||
--docker-password=P@ssw0rd \
|
||
--docker-email=admin@example.com
|
||
```
|
||
|
||
```yaml
|
||
# 在 Pod 中使用
|
||
apiVersion: v1
|
||
kind: Pod
|
||
metadata:
|
||
name: private-app
|
||
spec:
|
||
imagePullSecrets:
|
||
- name: regcred # 引用 Docker Registry Secret
|
||
containers:
|
||
- name: app
|
||
image: registry.example.com/my-app:1.0
|
||
```
|
||
|
||
### 编码 vs 加密
|
||
|
||
> [!danger] Base64 编码不等于加密
|
||
> Secret 的 `data` 字段使用 Base64 编码,但这只是编码格式,**不是加密**。任何拥有集群读权限的人都可以轻松解码。`kubectl get secret db-credentials -o jsonpath='{.data.password}' | base64 -d` 即可还原明文。
|
||
|
||
**etcd 加密(EncryptionConfiguration)**
|
||
|
||
Kubernetes 支持在 etcd 层面对 Secret 进行真正的加密存储:
|
||
|
||
```yaml
|
||
# /etc/kubernetes/encryption-config.yaml
|
||
apiVersion: apiserver.config.k8s.io/v1
|
||
kind: EncryptionConfiguration
|
||
resources:
|
||
- resources:
|
||
- secrets
|
||
- configmaps # 1.25+ 也支持加密 ConfigMap
|
||
providers:
|
||
# 第一个 provider 用于解密(兼容)
|
||
- aescbc:
|
||
keys:
|
||
- name: key1
|
||
secret: <32-byte-base64-encoded-key> # head -c 32 /dev/urandom | base64
|
||
# fallback(不加密,仅读取旧数据)
|
||
- identity: {}
|
||
```
|
||
|
||
> [!tip] 加密流程
|
||
> 启用 EncryptionConfiguration 后,新写入的 Secret 会使用配置的 provider 加密存储。已有的旧 Secret 在下次写入(update)时才会重新加密。读取时 API Server 自动解密,对客户端透明。
|
||
|
||
#### 更安全的加密方案对比
|
||
|
||
| 方案 | 加密层次 | 密钥管理 | 适用场景 |
|
||
|------|---------|---------|---------|
|
||
| Base64(默认) | 无加密 | 无 | 仅编码格式 |
|
||
| aescbc(EncryptionConfiguration) | etcd 存储层 | 静态密钥(文件) | 基础加密需求 |
|
||
| KMS Plugin(AWS KMS、GCP KMS) | etcd 存储层 | 外部 KMS 托管 | 生产环境推荐 |
|
||
| 外部密钥管理(Vault) | 应用层 | 专业密钥管理系统 | 高安全要求 |
|
||
|
||
### 挂载方式
|
||
|
||
Secret 的挂载方式与 ConfigMap 基本一致,但有以下关键差异:
|
||
|
||
| 特性 | ConfigMap | Secret |
|
||
|------|-----------|--------|
|
||
| 存储介质 | 普通目录(tmpfs 或磁盘) | **tmpfs(内存文件系统)**,不落盘 |
|
||
| 默认文件权限 | `0644` | **`0400`**(仅 owner 可读) |
|
||
| 适用内容 | 非敏感配置 | 密码、Token、证书等敏感数据 |
|
||
| etcd 存储 | 明文 | 可加密(EncryptionConfiguration) |
|
||
|
||
#### 环境变量注入
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: Pod
|
||
metadata:
|
||
name: app-with-secret
|
||
spec:
|
||
containers:
|
||
- name: app
|
||
image: my-app:1.0
|
||
env:
|
||
- name: DB_PASSWORD
|
||
valueFrom:
|
||
secretKeyRef:
|
||
name: db-credentials
|
||
key: password
|
||
- name: DB_USERNAME
|
||
valueFrom:
|
||
secretKeyRef:
|
||
name: db-credentials
|
||
key: username
|
||
# 也可以批量注入所有 Secret key
|
||
# envFrom:
|
||
# - secretRef:
|
||
# name: db-credentials
|
||
# prefix: "SECRET_"
|
||
```
|
||
|
||
#### Volume 挂载
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: Pod
|
||
metadata:
|
||
name: app-with-tls
|
||
spec:
|
||
containers:
|
||
- name: app
|
||
image: nginx:1.25
|
||
volumeMounts:
|
||
- name: tls-certs
|
||
mountPath: /etc/tls
|
||
readOnly: true
|
||
- name: db-secret
|
||
mountPath: /etc/secrets
|
||
readOnly: true
|
||
volumes:
|
||
- name: tls-certs
|
||
secret:
|
||
secretName: my-tls-secret
|
||
defaultMode: 0400 # 仅 owner 可读
|
||
items:
|
||
- key: tls.crt
|
||
path: server.crt
|
||
- key: tls.key
|
||
path: server.key
|
||
- name: db-secret
|
||
secret:
|
||
secretName: db-credentials
|
||
defaultMode: 0440 # owner + group 可读
|
||
```
|
||
|
||
> [!info] tmpfs 存储
|
||
> Secret 通过 Volume 挂载时使用 tmpfs(内存文件系统),数据不会写入节点的磁盘。这避免了敏感数据在磁盘上残留的风险,但会占用少量内存。
|
||
|
||
### 安全最佳实践
|
||
|
||
#### 1. 使用外部密钥管理系统
|
||
|
||
在生产环境中,推荐使用专业的密钥管理系统而非原生 Secret:
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
subgraph External["外部密钥管理"]
|
||
VAULT["HashiCorp Vault"]
|
||
AWS["AWS Secrets Manager"]
|
||
GCP["GCP Secret Manager"]
|
||
end
|
||
|
||
subgraph K8s["Kubernetes"]
|
||
CSI["Secrets Store CSI Driver"]
|
||
SYNC["External Secrets Operator"]
|
||
POD["Pod"]
|
||
end
|
||
|
||
VAULT --> CSI
|
||
AWS --> CSI
|
||
GCP --> CSI
|
||
CSI -->|挂载为 Volume| POD
|
||
|
||
VAULT --> SYNC
|
||
AWS --> SYNC
|
||
SYNC -->|同步为 K8s Secret| POD
|
||
```
|
||
|
||
- **Secrets Store CSI Driver**:将外部密钥直接挂载为 Volume,不在 etcd 中存储。
|
||
- **External Secrets Operator**:从外部系统同步密钥为 K8s Secret,适合需要原生 Secret 兼容的场景。
|
||
|
||
#### 2. RBAC 权限控制
|
||
|
||
```yaml
|
||
# 限制特定 ServiceAccount 只能读取特定 Secret
|
||
apiVersion: rbac.authorization.k8s.io/v1
|
||
kind: Role
|
||
metadata:
|
||
name: secret-reader
|
||
namespace: production
|
||
rules:
|
||
- apiGroups: [""]
|
||
resources: ["secrets"]
|
||
resourceNames: ["db-credentials", "api-keys"] # 仅限特定 Secret
|
||
verbs: ["get"] # 仅允许读取
|
||
---
|
||
apiVersion: rbac.authorization.k8s.io/v1
|
||
kind: RoleBinding
|
||
metadata:
|
||
name: app-secret-reader
|
||
namespace: production
|
||
subjects:
|
||
- kind: ServiceAccount
|
||
name: my-app-sa
|
||
namespace: production
|
||
roleRef:
|
||
kind: Role
|
||
name: secret-reader
|
||
apiGroup: rbac.authorization.k8s.io
|
||
```
|
||
|
||
#### 3. 审计日志
|
||
|
||
启用 API Server 的审计日志,追踪 Secret 的读写操作:
|
||
|
||
```yaml
|
||
# /etc/kubernetes/audit-policy.yaml
|
||
apiVersion: audit.k8s.io/v1
|
||
kind: Policy
|
||
rules:
|
||
# 记录所有对 Secret 的读写操作
|
||
- level: Metadata
|
||
resources:
|
||
- group: ""
|
||
resources: ["secrets"]
|
||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||
```
|
||
|
||
#### 4. 其他安全建议
|
||
|
||
- **避免在 Pod Spec 中直接写 `stringData`**:生产环境应使用 `data` + Base64,或通过 CI/CD 管道注入。
|
||
- **定期轮换密钥**:结合 External Secrets Operator 实现自动轮换。
|
||
- **避免将 Secret 推送到 Git**:使用 `.gitignore` 排除 Secret YAML,或使用 Sealed Secrets / SOPS 加密后再提交。
|
||
- **最小权限原则**:每个 ServiceAccount 只应拥有它所需的最小 Secret 访问权限。
|
||
|
||
---
|
||
|
||
## 配置热更新实践
|
||
|
||
在生产环境中,单纯的 ConfigMap Volume 挂载虽然支持热更新,但应用本身还需要 reload 机制。以下是几种常见的无重启配置刷新方案。
|
||
|
||
### 方案一:ConfigMap Reloader(Sidecar)
|
||
|
||
[stakater/ConfigMapReloader](https://github.com/stakater/Reloader) 是一个 Kubernetes Controller,当它检测到关联的 ConfigMap/Secret 发生变更时,会自动触发 Deployment 的滚动更新(rollout restart),从而实现配置刷新。
|
||
|
||
```yaml
|
||
# 部署 Reloader 后,在 Deployment 中添加注解
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: my-app
|
||
annotations:
|
||
# Reloader 监听这些 ConfigMap/Secret 的变更
|
||
reloader.stakater.com/reload: "app-config,db-credentials"
|
||
spec:
|
||
template:
|
||
spec:
|
||
containers:
|
||
- name: app
|
||
image: my-app:1.0
|
||
```
|
||
|
||
> [!tip] 原理
|
||
> Reloader 并非真正实现"热更新",而是通过修改 Pod Template 的 annotation 触发 Deployment 的滚动更新。本质上是重启 Pod,但对用户无感知(滚动更新保证零停机)。
|
||
|
||
### 方案二:应用内 Watch 机制
|
||
|
||
一些应用框架原生支持配置文件监听:
|
||
|
||
```go
|
||
// Go 应用使用 fsnotify 监听配置文件变更
|
||
import "github.com/fsnotify/fsnotify"
|
||
|
||
func watchConfig(path string, reload func()) {
|
||
watcher, _ := fsnotify.NewWatcher()
|
||
defer watcher.Close()
|
||
watcher.Add(path)
|
||
|
||
for {
|
||
select {
|
||
case event := <-watcher.Events:
|
||
if event.Op&fsnotify.Write == fsnotify.Write {
|
||
log.Println("Config file modified, reloading...")
|
||
reload()
|
||
}
|
||
case err := <-watcher.Errors:
|
||
log.Println("Watch error:", err)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 方案三:SubPath 挂载 + 特殊处理
|
||
|
||
当使用 `subPath` 挂载时,ConfigMap 的更新不会自动传播到容器。如果必须使用 `subPath`,需要配合其他方案(如 Reloader 触发重启):
|
||
|
||
```yaml
|
||
volumeMounts:
|
||
- name: config-volume
|
||
mountPath: /etc/nginx/nginx.conf
|
||
subPath: nginx.conf # 只挂载单个文件到指定路径
|
||
```
|
||
|
||
> [!warning] SubPath 无法热更新
|
||
> 使用 `subPath` 挂载的文件不会随 ConfigMap 更新而更新。这是因为 kubelet 为 subPath 创建的是符号链接或独立 bind mount,不在正常的 sync 范围内。如果需要热更新,请避免使用 `subPath`。
|
||
|
||
---
|
||
|
||
## 常见陷阱与最佳实践
|
||
|
||
### 陷阱一:ConfigMap 大小限制(1MB)
|
||
|
||
Kubernetes 对 ConfigMap 和 Secret 有 **1MB** 的大小限制(这是 etcd 的 kv 对大小限制决定的)。
|
||
|
||
```
|
||
# 错误:ConfigMap 超过 1MB 会创建失败
|
||
The ConfigMap "xxx" is invalid: data: Too long: must have at most 1048576 bytes
|
||
```
|
||
|
||
**解决方案**:
|
||
|
||
- 拆分为多个 ConfigMap。
|
||
- 使用外部配置存储(如 Consul、Nacos)。
|
||
- 对于大型配置文件,考虑挂载到 PV。
|
||
|
||
### 陷阱二:环境变量命名冲突
|
||
|
||
当使用 `envFrom` 批量导入时,多个 ConfigMap/Secret 中可能存在同名 key,后导入的会覆盖先前的。
|
||
|
||
```yaml
|
||
envFrom:
|
||
- configMapRef:
|
||
name: base-config # 含 APP_ENV=staging
|
||
- configMapRef:
|
||
name: override-config # 含 APP_ENV=production(覆盖)
|
||
```
|
||
|
||
> [!tip] 使用 prefix 避免冲突
|
||
> 为每个 `envFrom` 来源设置不同的 `prefix`,如 `base-config` 用 `BASE_`,`override-config` 用 `OVERRIDE_`。
|
||
|
||
### 陷阱三:Secret 的 watch 机制开销
|
||
|
||
Pod 通过 Volume 引用 Secret 时,kubelet 会 watch 该 Secret 的变更。在大规模集群中,大量 Secret 的 watch 可能导致 API Server 压力过大。
|
||
|
||
**缓解措施**:
|
||
|
||
- 使用 `immutable: true` 标记不会变更的 Secret。
|
||
- 合并小 Secret,减少 watch 对象数量。
|
||
- 使用 KMS Provider 加密时,注意解密操作的性能开销。
|
||
|
||
### 陷阱四:文件权限与安全上下文
|
||
|
||
Secret 默认权限为 `0400`,如果容器进程以非 root 用户运行,可能无法读取。
|
||
|
||
```yaml
|
||
# 解决方案:调整 Secret 文件权限
|
||
volumes:
|
||
- name: secret-volume
|
||
secret:
|
||
secretName: app-secret
|
||
defaultMode: 0444 # 所有用户可读
|
||
# 或者精确设置
|
||
items:
|
||
- key: config.yaml
|
||
path: config.yaml
|
||
mode: 0644
|
||
```
|
||
|
||
### 最佳实践总结
|
||
|
||
| 实践 | 说明 |
|
||
|------|------|
|
||
| ConfigMap 与 Config 文件分离 | 不同环境的配置使用不同的 ConfigMap |
|
||
| 使用 `immutable: true` | 已发布配置标记为不可变,减轻 API Server 压力 |
|
||
| Secret 使用外部管理系统 | 生产环境使用 Vault/KMS,而非原生 Secret |
|
||
| 避免 `envFrom` 无差别导入 | 明确列出需要的 key,避免意外覆盖 |
|
||
| 避免 `subPath` 挂载需要热更新的配置 | `subPath` 不支持热更新,优先使用目录挂载 |
|
||
| 设置合理的 RBAC | 每个 ServiceAccount 只授予最小必要权限 |
|
||
| 配置版本管理 | ConfigMap 名称带版本号(如 `app-config-v3`),便于回滚 |
|
||
| CI/CD 中管理 Secret | 使用 Sealed Secrets、SOPS 或 External Secrets Operator |
|
||
|
||
---
|
||
|
||
## 延伸阅读
|
||
|
||
- [[k8s-03-pod]] — Pod 生命周期与资源管理
|
||
- [[k8s-04-deployment-and-replicaset]] — Deployment 滚动更新与回滚
|
||
- [Kubernetes 官方文档 - ConfigMap](https://kubernetes.io/docs/concepts/configuration/configmap/)
|
||
- [Kubernetes 官方文档 - Secret](https://kubernetes.io/docs/concepts/configuration/secret/)
|
||
- [stakater/Reloader - ConfigMap/Secret 自动触发重启](https://github.com/stakater/Reloader)
|
||
- [Secrets Store CSI Driver](https://secrets-store-csi-driver.sigs.k8s.io/)
|
||
- [External Secrets Operator](https://external-secrets.io/)
|