Kubernetes 配置管理实战:ConfigMap 与 Secret 完全指南
Kubernetes 配置管理实战:ConfigMap 与 Secret 完全指南
应用跑起来之后,配置怎么管理?把配置写死在镜像里,改一次配置就要重新打包一次镜像,显然不可取。Kubernetes 提供了两个专门管理配置的 API 对象——ConfigMap(配置) 和 Secret(机密),让配置与应用代码彻底分离。本文从创建、引用到综合实战,一次讲透。
一、环境准备
先创建一个独立的命名空间,避免实验污染集群环境:
kubectl create ns configure
kubectl config set-context --current --namespace configure
后续所有操作都默认在 configure 命名空间下进行,实验结束后直接删除整个命名空间即可清理干净。
二、ConfigMap 介绍
ConfigMap 是一个存储其他对象所需配置的 API 对象,使用 data 和 binaryData 字段保存键值对数据:
data:保存 UTF-8 字符串,例如配置文件内容、环境变量值;binaryData:保存二进制数据(base64 编码后的字符串)。
几个必须知道的规则:
- ConfigMap 的名称必须是合法的 DNS 子域名;
data/binaryData下每个键的名称只能由字母、数字、-、_、.组成;data与binaryData中的键名不能重叠;- 从 Kubernetes v1.19 开始,可以给 ConfigMap 加
immutable: true,创建后不可修改,适合真正不会变动的配置。
使用建议:
- ConfigMap 只用来存配置数据,不要存数据文件,上限 1 MiB;
- 需要保存大量数据时,请使用存储卷、数据库或独立的文件服务,ConfigMap 在设计上就不是为大数据准备的。
三、ConfigMap 创建
创建命令的通用格式:
kubectl create configmap NAME [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run=...]
1. 键值对类型
kubectl create configmap mysql --from-literal=password=redhat
# 查看结果
kubectl get configmaps mysql -o yaml | grep ^data -A1
data:
password: redhat
2. 文件类型
echo Hello World > index.html
kubectl create configmap web1 --from-file=./index.html
kubectl get configmaps web1 -o yaml | grep ^data -A2
data:
index.html: |
Hello World
此时文件名成为键名,文件内容成为值。
3. 目录类型
echo error > error.html
mkdir web2
mv index.html error.html web2
kubectl create configmap web2 --from-file=./web2
kubectl get configmaps web2 -o yaml | grep ^data -A4
data:
error.html: |
error
index.html: |
Hello World
目录下的每个文件都会变成一个键值对,非常适合把一组静态页面或配置文件整体放入 ConfigMap。
四、ConfigMap 引用
1. 环境变量方式
注意:环境变量属于容器级别,只在指定的容器内生效。
准备 Pod 清单 pod-with-env-from-configmap.yaml:
apiVersion: v1
kind: Pod
metadata:
name: mysql
labels:
name: mysql
spec:
containers:
- image: mysql:latest
imagePullPolicy: IfNotPresent
name: mysql
ports:
- containerPort: 3306
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
# 不再写死 value,而是从 ConfigMap 取值
valueFrom:
configMapKeyRef:
name: mysql
key: password
应用并验证:
kubectl apply -f pod-with-env-from-configmap.yaml
kubectl get pods -o wide
# 进入容器查看环境变量
kubectl exec -it mysql -- bash -c 'echo $MYSQL_ROOT_PASSWORD'
redhat
MySQL 的 root 密码完全来自 ConfigMap,Pod 清单里不出现任何明文密码。
2. Volume 方式(整体挂载)
volume 属于 Pod 级别,通过 volumeMounts 挂载到容器。这种方式会把 ConfigMap 中所有键值对挂载为目录下的文件。
准备 pod-with-volume-from-configmap.yaml:
apiVersion: v1
kind: Pod
metadata:
labels:
run: web
name: web
spec:
containers:
- image: nginx:latest
imagePullPolicy: IfNotPresent
name: web
volumeMounts:
- name: webcontent
# mountPath 是挂载点,ConfigMap 中所有键值对都会出现在这里
mountPath: "/usr/share/nginx/html"
volumes:
- name: webcontent
configMap:
name: web2
验证:
kubectl apply -f pod-with-volume-from-configmap.yaml
kubectl exec web -- ls /usr/share/nginx/html
error.html
index.html
curl http://<POD_IP>
Hello World
curl http://<POD_IP>/error.html
error
Nginx 的网页目录被 ConfigMap 里的两个文件“填满”了。
3. Volume 方式(引用特定 key)
如果只想要部分键,用 items 指定:
volumes:
- name: webcontent
configMap:
name: web2
items:
- key: index.html
path: index.html
也可以使用 subPath,效果类似(注意此时 mountPath 是文件路径):
volumeMounts:
- name: webcontent
mountPath: "/usr/share/nginx/html/index.html"
subPath: index.html
volumes:
- name: webcontent
configMap:
name: web2
一个重要的特性: 通过 Volume 挂载的 ConfigMap 内容会被自动更新。kubelet 每次周期性同步时会检查挂载的 ConfigMap 是否最新,更新的键最终会被投射到文件中(环境变量方式不会自动更新)。
五、集群中的 ConfigMap:以 kube-proxy 为例
Kubernetes 系统组件本身就大量使用 ConfigMap。比如 kube-system 命名空间中常见的 ConfigMap:
kubectl get configmaps -n kube-system
NAME DATA AGE
calico-config 4 37h
coredns 1 37h
kube-proxy 2 37h
kubeadm-config 1 37h
kubelet-config 1 37h
...
kube-proxy 的 ConfigMap 里保存了 config.conf 和 kubeconfig.conf 两个配置文件,其中 config.conf 就是 kube-proxy 的完整配置。kube-proxy 有两种工作模式:ipvs 和 iptables。默认 mode 为空,从日志可以看出实际使用的是 iptables:
kubectl logs -n kube-system kube-proxy-8kp8w
I1019 02:26:00.975333 1 server_others.go:69] "Using iptables proxy"
想切换成 ipvs,直接编辑 ConfigMap:
kubectl edit cm -n kube-system kube-proxy
# 把 mode 改为 "ipvs"
mode: "ipvs"
然后删除 kube-proxy Pod,等待 DaemonSet 控制器重新创建:
kubectl delete pod -n kube-system kube-proxy-{8kp8w,ghgxw,p6qnk}
再次查看日志,确认已经使用 ipvs:
kubectl logs -n kube-system kube-proxy-8swmx
... "Using ipvs Proxier"
这就是“改配置不动镜像”的典型场景:修改 ConfigMap → 触发 Pod 重建 → 新配置生效。
六、综合案例:haproxy + web 负载均衡
需求:
- 创建两个 Nginx Pod(
webapp-1、webapp-2),各自的index.html与error.html由 ConfigMap 提供; - 创建 HAProxy Pod,配置文件
haproxy.cfg保存在 ConfigMap 中,通过 Volume 挂载到容器,把流量转发到两个 webapp。
第一步:创建 webapp-1 及其 ConfigMap
kubectl create cm webapp-1 \
--from-literal=index.html="hello webapp-1" \
--from-literal=error.html="sorry, error."
pod-webapp-1.yaml:
apiVersion: v1
kind: Pod
metadata:
labels:
run: webapp-1
name: webapp-1
spec:
containers:
- name: nginx
image: nginx:latest
imagePullPolicy: IfNotPresent
volumeMounts:
- name: config
mountPath: "/usr/share/nginx/html"
readOnly: true
volumes:
- name: config
configMap:
name: webapp-1
kubectl apply -f pod-webapp-1.yaml
第二步:创建 webapp-2 及其 ConfigMap
操作完全相同,只是内容不同:
kubectl create cm webapp-2 \
--from-literal=index.html="hello webapp-2" \
--from-literal=error.html="sorry, error."
pod-webapp-2.yaml 与上面几乎一样(名称和 ConfigMap 名改为 webapp-2),然后 kubectl apply -f pod-webapp-2.yaml。
第三步:创建 HAProxy
查看两个 Pod 的 IP:
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP
webapp-1 1/1 Running 0 63m 10.224.84.80
webapp-2 1/1 Running 0 63m 10.224.149.25
编写 haproxy.cfg,把流量转发到两个后端:
global
daemon
maxconn 256
defaults
mode http
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http-in
bind *:8080
default_backend servers
backend servers
server app1 10.224.84.80:80 check
server app2 10.224.149.25:80 check
把配置文件放入 ConfigMap,并编写 HAProxy Pod:
kubectl create cm haproxy.cfg --from-file=haproxy.cfg=./haproxy.cfg
haproxy.yaml:
apiVersion: v1
kind: Pod
metadata:
name: haproxy
spec:
containers:
- name: haproxy
image: haproxy
imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: true
volumeMounts:
- name: config
mountPath: "/usr/local/etc/haproxy"
readOnly: true
volumes:
- name: config
configMap:
name: haproxy.cfg
验证负载均衡效果:
kubectl apply -f haproxy.yaml
kubectl get pods haproxy -o wide
curl http://<HAPROXY_IP>:8080
hello webapp-1
curl http://<HAPROXY_IP>:8080
hello webapp-2
两次请求分别打到了两个后端,HAProxy 的轮询生效了。整个过程中,HAProxy 的配置完全由 ConfigMap 提供,没有重新构建镜像。
七、Secret 介绍
Secret 与 ConfigMap 类似,但专门用来保存敏感信息:密码、令牌、密钥等。使用 Secret 意味着机密数据不需要出现在 Pod 清单或镜像中,也可以独立于使用它们的 Pod 创建,降低敏感数据暴露的风险。
两者最核心的区别一句话就能说清:
Secret 对数据编码(base64),ConfigMap 不对数据编码。
注意:base64 只是编码不是加密,Secret 不适合存放真正需要加密保护的数据,生产环境建议配合 KMS 等方案加密。
Secret 的三种常见类型
| 类型 | 说明 | Kubernetes 类型 |
|---|---|---|
| generic | 键值对形式保存任意敏感数据 | Opaque |
| docker-registry | 访问镜像仓库的凭据 | kubernetes.io/dockerconfigjson |
| tls | TLS 公钥/私钥对 | kubernetes.io/tls |
八、Secret 创建
1. generic:基于键值对
一般用于传递变量值:
kubectl create secret generic mysecret1 \
--from-literal=user=tom \
--from-literal=password1=redhat \
--from-literal=password2=redhat
kubectl get secrets mysecret1 -o yaml
apiVersion: v1
data:
password1: cmVkaGF0
password2: cmVkaGF0
user: dG9t
kind: Secret
type: Opaque
可以看到数据已经被 base64 编码:
echo -n tom | base64
dG9t
echo -n redhat | base64
cmVkaGF0
2. generic:基于普通文件
一般用于传递配置文件。文件名作为 key,文件内容作为 value,也可以自定义 key:
echo -n tom > user
echo -n redhat > password1
echo -n redhat > password2
# 文件名即 key
kubectl create secret generic mysecret2 --from-file=./user --from-file=./password1 --from-file=./password2
# 自定义 key:username
kubectl create secret generic mysecret3 --from-file=username=./user --from-file=./password1 --from-file=./password2
3. generic:基于键值对内容的文件
如果文件里本身就是 key=value 格式,用 --from-env-file 一步导入:
cat > env.txt <<EOF
user=tom
password1=redhat
password2=redhat
EOF
kubectl create secret generic mysecret4 --from-env-file=./env.txt
4. generic:基于目录
mkdir config
mv user password1 password2 config
kubectl create secret generic mysecret5 --from-file=./config
5. generic:基于 YAML 文件
YAML 中必须使用 base64 转换后的值:
apiVersion: v1
kind: Secret
metadata:
name: mysecret6
type: Opaque
data:
user: dG9t
password1: cmVkaGF0
password2: cmVkaGF0
6. docker-registry 类型
回顾 Docker 登录:
docker login DOCKER_REGISTRY_SERVER -u=DOCKER_USER -p=DOCKER_PASSWORD
登录凭据默认保存在 ~/.dockercfg 中,后续 pull/push 都靠它。Kubernetes 中用 docker-registry 类型的 Secret 保存同样的信息:
kubectl create secret docker-registry registry \
--docker-username=ningcode \
--docker-password=redhat \
--docker-email=admin@example.com \
--docker-server=registry.example.com
kubectl get secrets registry -o yaml
...
type: kubernetes.io/dockerconfigjson
创建后,Pod 可以通过 imagePullSecrets 引用它,从而从私有仓库拉取镜像。
7. TLS 类型
保存公钥/私钥对,公钥证书必须是 PEM 编码:
# 1. 生成私钥
mkdir certs && cd certs
openssl genrsa -out www.key 2048
# 2. 生成证书请求文件(CN 必须是网站域名)
openssl req -new -key www.key -out www.csr \
-subj "/C=CN/ST=JS/L=NJ/O=LM/OU=DEVOPS/CN=www.example.com/emailAddress=admin@example.com"
# 3. 使用私钥自签名生成证书
openssl x509 -req -days 3650 -in www.csr -signkey www.key -out www.crt
# 4. 创建 TLS Secret
kubectl create secret tls www-tls --cert=./www.crt --key=./www.key
九、Secret 引用
Secret 同样支持环境变量和 Volume 两种方式。
1. 环境变量方式
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysecret1
key: password1
验证:
kubectl exec -it mysql -- bash -c 'echo $MYSQL_ROOT_PASSWORD'
redhat
2. Volume 方式(整体挂载)
volumes:
- name: webcontent
secret:
secretName: web
3. Volume 方式(引用特定 key)
volumes:
- name: webcontent
secret:
secretName: web
items:
- key: index.html
path: index.html
也可以配合 subPath 只挂载单个文件。与 ConfigMap 一样,以卷方式引用的 Secret 支持动态更新:修改 Secret 后等待 kubelet 周期同步(约 30 秒左右),挂载的文件内容就会更新。
4. 文件权限控制
映射过去的文件默认权限是 644(八进制),对应十进制 420。JSON 格式不支持八进制,所以权限值要用十进制写:
volumes:
- name: foo
secret:
secretName: mysecret1
# 256(十进制) = 400(八进制):所有文件只读
defaultMode: 256
只设置单个文件权限:
volumes:
- name: foo
secret:
secretName: mysecret1
items:
- key: user
path: user
mode: 511 # 511(十进制) = 777(八进制)
十、综合实验(练习题)
使用 mysql 和 wordpress 镜像部署一个 blog 应用:
- MySQL 密码和 WordPress 站点证书用 Secret 存储;
- 为 blog 站点配置 vhost,配置文件由 ConfigMap 提供。
这个练习把本文的知识点全部串了起来:Secret 管敏感信息、ConfigMap 管配置文件、Volume 负责挂载。建议独立完成后再对照官方文档查漏补缺。
十一、环境清理
kubectl delete ns configure
删除命名空间会连同其中所有 Pod、ConfigMap、Secret 一起清理,非常方便。
总结
- ConfigMap 存配置、Secret 存机密,两者都可以通过环境变量或 Volume 方式注入 Pod;
- 配置文件类数据推荐 Volume 挂载,还能享受自动更新特性;
- Secret 的 base64 只是编码,不是安全加密;
- ConfigMap / Secret 上限 1 MiB,大数据请用存储卷或数据库;
- 修改 kube-proxy 等系统组件的配置,本质就是编辑它的 ConfigMap 并触发 Pod 重建。
配置与代码分离,是 Kubernetes 应用管理的基本功。掌握 ConfigMap 与 Secret,你就已经拿到了“改配置不重建镜像”的钥匙。
更多推荐



所有评论(0)