学习目标
- 理解配置与代码分离的工程意义
- 掌握 ConfigMap 的创建方式与挂载方式
- 理解 Secret 的类型与 Base64 编码(≠加密)
- 掌握配置热更新机制
1. 为什么要分离配置?
将数据库地址、端口号、API Key 等配置硬编码在容器镜像中是一个严重的反模式:
- 修改配置就要重新构建镜像
- 同一个镜像无法在开发/测试/生产环境间复用
- 敏感信息(密码、密钥)泄露在镜像层中
ConfigMap 存储非敏感配置,Secret 存储敏感配置。两者都可以通过环境变量或挂载文件的方式注入到 Pod 中。
2. ConfigMap
创建方式
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===
# 从命令行键值对创建
kubectl create configmap app-config --from-literal=DB_HOST=mysql-service --from-literal=DB_PORT=3306
预期输出:
bashconfigmap/app-config created
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===
# 查看 ConfigMap 列表
kubectl get configmap app-config
预期输出:
bashNAME DATA AGE
app-config 2 0s
输出解读:
DATA 2表示这个 ConfigMap 中有 2 个键值对(DB_HOST 和 DB_PORT)。
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===
# 查看 ConfigMap 的详细内容
kubectl describe configmap app-config
预期输出:
bashName: app-config
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
DB_HOST:
----
mysql-service
DB_PORT:
----
3306
BinaryData
====
Events: <none>
输出解读:
Data部分清晰列出了每个键和对应的值。BinaryData为空,表示没有二进制数据。ConfigMap 支持存储纯文本和二进制数据两种格式。
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===
# 以 YAML 格式查看(观察存储结构)
kubectl get configmap app-config -o yaml
预期输出:
yamlapiVersion: v1
data:
DB_HOST: mysql-service
DB_PORT: "3306"
kind: ConfigMap
metadata:
creationTimestamp: "2026-04-23T09:01:49Z"
name: app-config
namespace: default
resourceVersion: "166379"
uid: f3ebf66d-36b9-48a3-9ad1-305d9183ff5c
输出解读:ConfigMap 的 YAML 结构非常简单——
data字段下就是纯粹的键值对。注意DB_PORT的值"3306"带了引号,因为 YAML 中所有 ConfigMap 的值都是字符串类型。
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===
# 清理
kubectl delete configmap app-config
其他创建方式
bash# 从文件创建(将整个文件内容作为一个键值对)
kubectl create configmap nginx-conf --from-file=nginx.conf
# 从 YAML 声明式创建(推荐用于生产环境,可纳入 Git 管理)
kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
DB_HOST: "mysql-service"
DB_PORT: "3306"
app.properties: |
server.port=8080
logging.level=INFO
EOF
参数解读:
|符号是 YAML 的多行文本语法。app.properties的值是一个完整的多行配置文件内容,Pod 挂载后会生成一个名为app.properties的文件。
使用方式
yaml# 方式 1:作为环境变量注入(所有键值对变成容器的环境变量)
spec:
containers:
- name: app
envFrom:
- configMapRef:
name: app-config # 容器内可直接用 $DB_HOST、$DB_PORT
# 方式 2:作为文件挂载(每个键变成一个文件)
spec:
containers:
- name: app
volumeMounts:
- name: config-volume
mountPath: /etc/config # 容器内 /etc/config/DB_HOST、/etc/config/DB_PORT
volumes:
- name: config-volume
configMap:
name: app-config
设计选择:
- 环境变量方式:简单直接,适合少量配置项。缺点是 ConfigMap 更新后不会自动刷新,需要重启 Pod
- Volume 挂载方式:适合配置文件(如 nginx.conf)。优点是 ConfigMap 更新后约 30-60 秒自动刷新,无需重启 Pod
3. Secret
核心区别
Secret 与 ConfigMap 结构相同,但有以下区别:
- 数据以 Base64 编码存储(注意:Base64 不是加密,只是编码!任何人都能解码)
- 挂载到 Pod 后存放在 tmpfs(内存文件系统)中,不写入磁盘
- 可以通过 RBAC 对 Secret 的访问进行细粒度控制
常见类型
| 类型 | 用途 |
|---|---|
Opaque | 通用密钥(默认类型) |
kubernetes.io/tls | TLS 证书和私钥 |
kubernetes.io/dockerconfigjson | 私有镜像仓库认证 |
创建与查看
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===
# 创建一个包含数据库密码的 Secret
kubectl create secret generic db-secret \
--from-literal=username=admin \
--from-literal=password='S3cur3P@ss!'
预期输出:
bashsecret/db-secret created
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===
kubectl get secret db-secret
预期输出:
bashNAME TYPE DATA AGE
db-secret Opaque 2 0s
输出解读:
TYPE: Opaque是通用类型的 Secret。DATA: 2表示包含 2 个键值对。注意kubectl get不会显示 Secret 的实际值——这是一层基本的安全保护。
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===
# describe 也只显示字节数,不显示实际值
kubectl describe secret db-secret
预期输出:
bashName: db-secret
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
password: 11 bytes
username: 5 bytes
输出解读:只显示了
password: 11 bytes(11 个字节 =S3cur3P@ss!的长度)和username: 5 bytes(5 个字节 =admin的长度),而不是明文值。
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===
# 用 -o yaml 可以看到 Base64 编码后的值
kubectl get secret db-secret -o yaml
预期输出:
yamlapiVersion: v1
data:
password: UzNjdXIzUEBzcyE=
username: YWRtaW4=
kind: Secret
metadata:
creationTimestamp: "2026-04-23T09:01:57Z"
name: db-secret
namespace: default
type: Opaque
安全警告:
UzNjdXIzUEBzcyE=看起来像加密,但其实只是 Base64 编码。任何人都可以轻松解码:
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===
# Base64 解码验证——一行命令就能还原明文!
echo "UzNjdXIzUEBzcyE=" | base64 -d
预期输出:
bashS3cur3P@ss!
重要认知:这就是为什么说 Base64 ≠ 加密。在生产环境中,如果需要真正的加密保护,应该启用 etcd 的静态加密(Encryption at Rest)或使用外部密钥管理系统(如 HashiCorp Vault)。
在 Pod 中使用 Secret
yaml# 作为环境变量引用单个键
spec:
containers:
- name: app
env:
- name: DB_PASSWORD # 环境变量名
valueFrom:
secretKeyRef:
name: db-secret # Secret 的名字
key: password # Secret 中的键名
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===
# 清理
kubectl delete secret db-secret
动手实验
实验 12.1:ConfigMap 热更新验证
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===
# 创建 ConfigMap
kubectl create configmap test-config --from-literal=message="Hello V1"
预期输出:
bashconfigmap/test-config created
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===
# 创建 Pod,通过 Volume 挂载 ConfigMap,每 5 秒读取并打印配置内容
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: config-demo
spec:
containers:
- name: app
image: docker.m.daocloud.io/library/busybox
command: ['sh', '-c', 'while true; do cat /etc/config/message; echo; sleep 5; done']
volumeMounts:
- name: config-vol
mountPath: /etc/config
volumes:
- name: config-vol
configMap:
name: test-config
EOF
kubectl wait --for=condition=Ready pod/config-demo --timeout=60s
预期输出:
bashpod/config-demo created
pod/config-demo condition met
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===
# 查看初始日志——应该输出 "Hello V1"
kubectl logs config-demo --tail=3
预期输出:
bashHello V1
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===
# 修改 ConfigMap 的值为 "Hello V2"
kubectl patch configmap test-config -p '{"data":{"message":"Hello V2"}}'
预期输出:
bashconfigmap/test-config patched
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===
# 等待约 60 秒后再次查看日志
sleep 65
kubectl logs config-demo --tail=3
预期输出:
bashHello V1
Hello V1
Hello V2
输出解读:你可以清晰地看到日志从
Hello V1自动变成了Hello V2——我们没有重启 Pod,ConfigMap 的修改通过 Volume 挂载自动传播到了容器内部。这就是热更新的威力。K8s 的 kubelet 会定期(默认每 60 秒)同步 ConfigMap 的最新值到挂载的 Volume 中。
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===
# 清理
kubectl delete pod config-demo
kubectl delete configmap test-config
预期输出:
bashpod "config-demo" deleted
configmap "test-config" deleted
检查点
- 能用三种方式创建 ConfigMap(命令行、文件、YAML)
- 理解环境变量注入 vs Volume 挂载的热更新差异
- 知道 Secret 的 Base64 编码不等于加密
- 能在 Pod 中引用 Secret 的值
- 能通过修改 ConfigMap 观察到 Pod 内配置自动更新
Comments NOTHING