K8s详细学习笔记 第四章:应用配置管理:ConfigMap与Secret
第四章:应用配置管理:ConfigMap与Secret
在构建容器化应用时,一个核心的最佳实践原则是将配置与应用镜像分离。如果将数据库地址、API密钥、功能开关等配置信息硬编码到容器镜像中,会带来一系列严重问题:
- 可移植性差:同一个镜像无法不经修改就部署到开发、测试、生产等不同环境中,因为每个环境的配置(如数据库连接)都不同。
- 更新流程繁琐:仅仅为了修改一个配置项,就需要重新构建、推送和部署整个镜像,效率低下。
- 安全风险高:将敏感信息(如密码、Token)打包进镜像是极不安全的做法,任何能够访问镜像的人都能轻易获取这些信息。
为了解决这些问题,Kubernetes提供了两种专门的API资源来管理配置数据:ConfigMap用于存储非敏感的、通用的配置信息;Secret则用于存储和管理敏感数据。本章将深入探讨如何使用这两种资源,实现配置与代码的彻底解耦。
4.1 ConfigMap:将配置信息与应用镜像解耦
ConfigMap是一个API对象,用于将非机密的配置数据以键值对的形式存储在Kubernetes集群中。Pod可以像消费存储卷(Volume)或环境变量一样消费这些数据。
通过使用ConfigMap,你可以将环境相关的配置从容器镜像中抽离出来,使得你的应用程序镜像变得“环境无关”,更具可移植性。
创建ConfigMap
创建ConfigMap有多种灵活的方式,包括命令式和声明式。
1. 使用 kubectl create configmap 命令(命令式)
这是最快捷的创建方式,适合临时或简单的配置。
-
从字面值创建 (
--from-literal):# 创建一个名为 my-app-config 的ConfigMap,包含两个键值对 kubectl create configmap my-app-config \ --from-literal=app.properties.greeting=Hello \ --from-literal=app.properties.farewell=Bye -
从单个文件创建 (
--from-file): 文件名会成为key,文件内容会成为value。# 1. 创建一个配置文件 game.properties echo "enemy.types=aliens,monsters" > game.properties echo "player.lives=3" >> game.properties # 2. 从文件创建ConfigMap kubectl create configmap game-config --from-file=game.properties创建后,这个ConfigMap会有一个名为
game.properties的key,其value是整个文件的内容。 -
从多个文件创建 (
--from-file): 可以指定一个目录,目录下的每个文件名都会成为一个key。# 1. 创建一个配置目录 mkdir -p config-dir echo "ui.color=blue" > config-dir/ui.properties echo "log_level=info" > config-dir/app.conf # 2. 从目录创建ConfigMap kubectl create configmap app-config-dir --from-file=config-dir这将创建一个ConfigMap,包含
ui.properties和app.conf两个key。
2. 使用YAML文件(声明式)- 推荐方式
在生产环境中,强烈建议使用YAML文件来定义ConfigMap,这样可以将其纳入版本控制系统(如Git),实现基础设施即代码(IaC)。
# my-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: my-app-config-yaml
data:
# key-value 格式,value是纯字符串
database.url: "jdbc:mysql://db.example.com:3306/mydb"
api.endpoint: "https://api.example.com/v1"
# 也可以嵌入整个文件内容作为value,使用 | 保留换行符
nginx.conf: |
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
应用该文件:kubectl apply -f my-configmap.yaml
你可以使用 kubectl get configmap 查看已创建的ConfigMap,使用 kubectl describe configmap <name> 查看其详细内容。
如何通过环境变量和Volume挂载使用ConfigMap
一旦ConfigMap被创建,Pod中的容器就可以通过两种主要方式来使用它。
方式一:作为环境变量注入容器
这是最简单直接的使用方式,适合将少量的、简单的配置项注入应用。
-
注入所有键值对 (
envFrom): 将ConfigMap中的所有key都作为环境变量注入容器。# pod-with-env-from-cm.yaml apiVersion: v1 kind: Pod metadata: name: test-pod-envfrom spec: containers: - name: test-container image: busybox command: [ "/bin/sh", "-c", "env && sleep 3600" ] envFrom: - configMapRef: name: my-app-config-yaml # 引用ConfigMap的名称创建这个Pod后,进入容器内部执行
env命令,你会看到database.url和api.endpoint等环境变量。注意:nginx.conf这个key不符合环境变量的命名规范(不能包含.),所以它不会被注入。 -
注入特定的键 (
valueFrom): 只选择ConfigMap中的某一个key,并将其值赋给一个指定的环境变量。# pod-with-env-valuefrom-cm.yaml apiVersion: v1 kind: Pod metadata: name: test-pod-valuefrom spec: containers: - name: test-container image: busybox command: [ "/bin/sh", "-c", "env && sleep 3600" ] env: - name: DB_HOST_URL # 定义我们自己的环境变量名 valueFrom: configMapKeyRef: name: my-app-config-yaml # 引用ConfigMap名称 key: database.url # 引用具体的key这个例子中,容器内会有一个名为
DB_HOST_URL的环境变量,其值为jdbc:mysql://db.example.com:3306/mydb。
方式二:作为文件挂载到容器中 (volumes & volumeMounts)
当配置项是完整的配置文件(如nginx.conf, settings.xml)或者配置项很多时,将它们作为文件挂天在到容器中是更好的选择。
# pod-with-volume-cm.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-pod-volume
spec:
containers:
- name: test-container
image: busybox
command: [ "/bin/sh", "-c", "ls -l /etc/config && sleep 3600" ]
volumeMounts:
- name: config-volume # 挂载点的名称,需与下面的volumes.name对应
mountPath: /etc/config # 挂载到容器内的这个目录
volumes:
- name: config-volume # 定义一个Volume
configMap:
name: my-app-config-yaml # Volume的数据来源是这个ConfigMap
# 可选: 使用items字段精确控制挂载哪些key以及文件名
items:
- key: database.url
path: db_url.txt # database.url的值会写入db_url.txt文件
- key: nginx.conf
path: my_nginx.conf # nginx.conf的值会写入my_nginx.conf文件
创建这个Pod后,进入容器,你会在/etc/config目录下看到db_url.txt和my_nginx.conf这两个文件,它们的内容分别对应ConfigMap中database.url和nginx.conf的值。如果省略items字段,ConfigMap中的每个key都会成为一个文件名,其value成为文件内容。
4.2 Secret:管理敏感数据
Secret的设计理念和使用方式与ConfigMap几乎完全相同,但其目的是为了存储和管理敏感信息,如:
- 数据库密码
- API密钥或Token
- TLS/SSL证书和私钥
- Docker仓库的认证信息
虽然用法相似,但Kubernetes对Secret提供了额外的保护措施:
- Secret只会被分发到需要它的Pod所在的Node节点上。
- Kubelet会将Secret存储在内存(tmpfs)中,而不是写入磁盘。
- 可以配置RBAC策略,严格控制哪些用户或ServiceAccount有权限读取某个Secret。
- 在更高安全性的集群中,可以启用Etcd的静态加密功能,使得存储在etcd中的Secret数据本身也是加密的。
【重难点】 Secret的编码方式(Base64)并非加密
这是一个极其重要的、初学者最容易误解的概念。当你查看一个Secret的YAML描述时,会发现它的data字段下的值都是经过Base64编码的字符串。
apiVersion: v1
kind: Secret
metadata:
name: my-secret
type: Opaque # Opaque是默认的通用Secret类型
data:
# 'admin' 的 Base64 编码是 'YWRtaW4='
username: YWRtaW4=
# 'S!B3rP@ssw0rd' 的 Base64 编码是 'UyFCM3JQYXNzdzByZA=='
password: UyFCM3JQYXNzdzByZA==
必须明确:Base64是一种编码(Encoding),而不是加密(Encryption)!
- 编码 (Encoding): 是为了方便数据在特定媒介上传输或存储而改变其表现形式,这个过程是可逆的,并且不需要密钥。任何人拿到
YWRtaW4=这个字符串,都可以通过echo 'YWRtaW4=' | base64 --decode命令轻松地解码出原始值admin。 - 加密 (Encryption): 是为了保护数据不被未授权访问而将其转换成密文,这个过程需要密钥,没有正确的密钥,就无法解密出原文。
那为什么Kubernetes要使用Base64?
主要原因是为了处理二进制数据。Secret不仅可以存字符串,还可以存储证书、密钥文件等二进制内容。YAML和JSON这类文本格式无法直接嵌入原始的二进制数据,而Base64可以将任意二进制数据转换成一个只包含ASCII字符的文本字符串,从而可以安全地嵌入到YAML或JSON文件中。
结论:不要把Base64编码当作一种安全措施。 Secret的安全性依赖于Kubernetes的访问控制(RBAC)、静态加密等机制,而不是这种可轻易逆转的编码。
Secret的创建和使用
创建和使用Secret的方式与ConfigMap惊人地相似。
创建Secret:
-
命令式:
# 创建一个包含用户名和密码的通用Secret kubectl create secret generic db-user-pass \ --from-literal=username=myuser \ --from-literal=password='My$tRoNgP@ssw0rd'kubectl会自动对value进行Base64编码。 -
声明式 (YAML):
你需要手动对数据进行Base64编码。# 手动编码 echo -n 'myuser' | base64 # 输出: bXl1c2Vy echo -n 'My$tRoNgP@ssw0rd' | base64 # 输出: TXkkdFJvTmdQQHNzdzByZA==# db-secret.yaml apiVersion: v1 kind: Secret metadata: name: db-user-pass-yaml type: Opaque data: username: bXl1c2Vy password: TXkkdFJvTmdQQHNzdzByZA==应用该文件:
kubectl apply -f db-secret.yaml
在Pod中使用Secret:
使用方法与ConfigMap完全相同,只是引用的资源类型从configMapKeyRef或configMapRef变成了secretKeyRef或secretRef。
-
作为环境变量:
# pod-with-secret-env.yaml apiVersion: v1 kind: Pod metadata: name: test-pod-secret-env spec: containers: - name: test-container image: busybox command: [ "/bin/sh", "-c", "env && sleep 3600" ] env: - name: DB_USERNAME valueFrom: secretKeyRef: name: db-user-pass # 引用Secret的名称 key: username - name: DB_PASSWORD valueFrom: secretKeyRef: name: db-user-pass key: password -
作为文件挂载:
# pod-with-secret-volume.yaml apiVersion: v1 kind: Pod metadata: name: test-pod-secret-volume spec: containers: - name: test-container image: busybox command: ["/bin/sh", "-c", "ls -l /etc/db-credentials && cat /etc/db-credentials/password && sleep 3600"] volumeMounts: - name: db-secret-volume mountPath: "/etc/db-credentials" readOnly: true # 挂载Secret时,建议设置为只读 volumes: - name: db-secret-volume secret: secretName: db-user-pass当Secret作为Volume挂载时,Kubernetes会自动进行Base64解码,所以在容器内的文件中看到的是原始数据。
4.3 热更新: 在不重启应用的情况下更新配置
这是一个非常实用的高级功能。想象一下,你只想调整一下应用的日志级别,或者修改一个功能开关,难道非要触发一次完整的滚动更新、重启所有Pod吗?这不仅效率低,还可能导致服务瞬间抖动。
ConfigMap和Secret都支持配置的热更新。 其工作机制如下:
- 当你使用
kubectl edit configmap <name>或kubectl apply -f更新了一个ConfigMap或Secret后,其在etcd中的数据会立即改变。 kubelet组件会周期性地(默认大约1分钟)检查其所在节点上Pod所挂载的ConfigMap和Secret是否发生了变化。- 如果
kubelet检测到变化,它会自动更新挂载在容器内的文件内容。
关键点来了: Kubernetes只负责更新文件,它不会自动重启或通知你的应用程序。你的应用程序需要自己有能力检测到配置文件的变化,并重新加载配置。
如何让应用感知配置变化?
- 应用原生支持: 很多现代框架和库(如Spring Cloud Config, Nginx, Prometheus)都支持在不重启进程的情况下重新加载配置文件。例如,Nginx可以通过发送
nginx -s reload信号来重新加载其nginx.conf。 - 文件监控:你的应用可以内置一个文件监控的逻辑(watch),当发现配置文件被修改时,自动触发内部的重载逻辑。
- Sidecar模式: 对于不具备原生重载能力的应用,可以部署一个轻量的“边车”容器。这个Sidecar容器的唯一任务就是监控配置文件(例如使用
inotify工具),一旦文件变化,它就通过进程信号(如SIGHUP)或调用应用的API端点来通知主应用容器重新加载配置。
实战示例:Nginx配置热更新
让我们来实践一下。我们将使用ConfigMap来管理Nginx的配置文件,并演示如何在不重启Nginx Pod的情况下修改其配置。
步骤1:创建包含nginx.conf的ConfigMap
# nginx-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-conf
data:
default.conf: |
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
# 返回一个自定义的头部信息
add_header X-App-Version "v1.0";
}
}
kubectl apply -f nginx-config.yaml
步骤2:创建Nginx Deployment,并挂载ConfigMap
# nginx-hot-reload-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-hot-reload
spec:
replicas: 1
selector:
matchLabels:
app: nginx-hot
template:
metadata:
labels:
app: nginx-hot
spec:
containers:
- name: nginx
image: nginx:1.21
ports:
- containerPort: 80
volumeMounts:
- name: nginx-conf-volume
# 挂载到Nginx默认读取配置的目录
mountPath: /etc/nginx/conf.d
volumes:
- name: nginx-conf-volume
configMap:
name: nginx-conf
kubectl apply -f nginx-hot-reload-deployment.yaml
步骤3:暴露服务并验证
为了方便访问,我们创建一个NodePort服务。kubectl expose deployment nginx-hot-reload --type=NodePort --port=80
获取服务的NodePort和Node IP,然后使用curl进行访问:
# -I 选项只看头部信息
curl -I http://<Node-IP>:<Node-Port>
你应该能在返回的头部信息中看到 X-App-Version: v1.0。
步骤4:更新ConfigMap
现在,我们来修改ConfigMap,将版本号改为v2.0。kubectl edit configmap nginx-conf
在打开的编辑器中,将add_header X-App-Version "v1.0";改为add_header X-App-Version "v2.0";,然后保存退出。
步骤5:等待并触发重载
等待大约一分钟,让kubelet将更新后的ConfigMap同步到Pod内的挂载点。
然后,进入Nginx Pod内部,手动触发配置重载。
# 获取Pod名称
POD_NAME=$(kubectl get pods -l app=nginx-hot -o jsonpath='{.items[0].metadata.name}')
# 进入Pod
kubectl exec -it $POD_NAME -- /bin/bash
# 在Pod内部,检查配置文件是否已更新
cat /etc/nginx/conf.d/default.conf # 你会看到 v2.0
# 发送重载信号
nginx -s reload
# 退出Pod
exit
步骤6:再次验证
再次执行curl命令:
curl -I http://<Node-IP>:<Node-Port>
这一次,你应该能看到返回的头部信息变成了 X-App-Version: v2.0。我们成功地在没有重启Pod的情况下,更新了应用的配置!在实际生产中,触发nginx -s reload的步骤可以通过Sidecar等方式自动化。
【重难点总结】
-
配置与代码分离的重要性: 这是云原生应用设计的基石,遵循了“十二要素应用”中的“配置”原则。它带来了无与伦比的灵活性(同一镜像部署多环境)、敏捷性(快速修改配置无需重建镜像)和安全性(敏感信息不进入版本库和镜像仓库)。将配置作为独立的K8s资源进行管理,使得运维和开发职责分离,配置管理更加清晰和规范。
-
Secret的Base64编码并非加密: 必须牢记这一点以避免产生错误的安全感。Base64仅仅是为了数据传输的兼容性。真正的安全来自于最小权限原则(通过RBAC限制对Secret的访问)、静态加密(加密etcd中的数据)、网络策略(限制Pod间的网络访问)以及运行时安全(保护Node和容器环境)等一系列纵深防御措施。
通过熟练掌握ConfigMap和Secret,你就能构建出更加健壮、安全和易于管理的Kubernetes应用,这是从入门到专业的必经之路。
K8s 详细学习笔记 目录
以下是整个系列的10章目录,点击章节标题即可跳转阅读:
- K8s详细学习笔记 第一章:K8s入门与核心概念
- K8s详细学习笔记 第二章:部署你的第一个应用:Pod与Deployment
- K8s详细学习笔记 第三章:应用访问与服务发现:Service与Ingress
- K8s详细学习笔记 第四章:应用配置管理:ConfigMap与Secret
- K8s详细学习笔记 第五章:数据持久化:Volume与Persistent Storage
- K8s详细学习笔记 第六章:应用的健康检查与自愈能力
- K8s详细学习笔记 第七章:资源的限制与调度
- K8s详细学习笔记 第八章:应用的扩缩容与更新策略
- K8s详细学习笔记 第九章:K8s安全机制:认证、授权与准入控制
- K8s详细学习笔记 第十章:集群运维与故障排查
更多推荐
所有评论(0)