(十七)ConfigMap 和 Secret
应用通常需要数据库地址、日志级别、功能开关、账号密码和证书等配置,如果把这些内容直接写进镜像,每次修改配置都需要重新构建镜像,也容易把密码提交到代码仓库中
因此 Kubernetes 提供 ConfigMap 和 Secret,把配置与容器镜像分离
- ConfigMap 保存普通配置,比如 LOGLEVEL=info、MYSQLHOST=mysql
- Secret 保存密码、令牌、证书等敏感数据,比如 MYSQLPASSWORD=123456、JWTSECRET=xxxx # ConfigMap ConfigMap 用来保存普通配置,本质上保存的是键值对,其中 Key 是配置名称,Value 可以是一行文本,也可以是一整个配置文件 ## 使用参数进行创建 --from-literal 参数用于直接声明键值对 ```bash kubectl create configmap app-config --from-literal=APPNAME=demo --from-literal=LOGLEVEL=info
kubectl get configmap app-config -o yaml

这里有一个小细节就是 ConfigMap 的 Value 最终按照字符串处理,所以容易被 YAML 识别成数字、布尔值的内容最好加引号
## 使用文件创建
如果配置的参数会比较多,那么使用 --from-literal 进行创建就显得笨重了,这时候我们可以使用 文件 形式进行创建
### 使用 env 文件创建
我们创建一个配置文件,然后使用 --from-env-file,这样就会把文件中的每一行拆成独立的 Key 和 Value
bash cat > app.env <<'EOF' APPNAME=demo LOGLEVEL=info FEATURE_X=true EOF
kubectl create configmap app-env --from-env-file=app.env
kubectl get configmap app-env -o yaml

当前 kubectl 版本允许重复指定多个 env 文件进行创建,但需要注意,如果多个来源包含相同的 Key,需要避免互相覆盖或直接创建失败带来的混乱
bash kubectl create configmap app-env --from-env-file=app.env --from-env-file=other.env
### 使用 .conf 文件创建
我们还可以使用 --from-file=nginx.conf 进行创建,这样文件名 nginx.conf 就是 Key,整个文件内容就是 Value,此时它不会再自动把文件内部的每一行再次拆成键值对
bash cat > nginx.conf <<'EOF' server {
listen 8080;
location / {
return 200 "configmap demo\n";
}} EOF
kubectl create configmap nginx-config --from-file=nginx.conf
kubectl get configmap nginx-config -o yaml

虽然我们可以用上面三种方式创建出 ConfigMap ,但现在存在一个问题,那就是配置已经存进了 Kubernetes 中了,但是程序还没拿到,所以我们需要把 ConfigMap 交给 Pod
# Pod 怎么使用 ConfigMap
ConfigMap 创建之后,只是存在 Kubernetes 中,Pod 需要主动去引用它,主要使用两种方式
- 环境变量:把配置以环境变量的形态注入容器。应用通过读环境变量来获取配置(如 os.Getenv()、System.getenv()、process.env ),适合本来就依赖环境变量读取配置的应用
- Volume 文件:把配置以文件形态挂载进容器。应用通过读配置文件来获取配置,适合本来就依赖配置文件读取配置的应用
## 环境变量
前面我们使用 --from-literal 创建了一个 ConfigMap,现在我们创建一个 Pod ,让 ConfigMap 把配置注入 Pod 容器环境变量里
bash cat > config-env-demo.yaml <<'EOF' apiVersion: v1 kind: Pod metadata: name: config-env-demo spec: containers:
name: app image: busybox:1.36 command: ["sleep", "3600"]
env:
- name: APPNAME # 创建一个环境变量名字是 APPNAME valueFrom: configMapKeyRef: name: app-config # 去 app-config 这个 ConfigMap 拿值 key: APP_NAME # 读取 ConfigMap 的 key 拿到 demo 这个名字 EOF
kubectl apply -f config-env-demo.yaml
kubectl exec config-env-demo -- printenv APP_NAME
这个 Pod 配置文件的意思就是 **去 app-config 这个 ConfigMap,取 APP_NAME 这个 key 的值 demo ,把它作为容器里的环境变量 APP_NAME**

### 导入多个配置
一个一个写 configMapKeyRef 的配置,太复杂了,所以我们可以将 spec.env 替换成 spec.envFrom,这样 ConfigMap 中的配置会整体作为环境变量交给容器
两种配置的区别是 configMapKeyRef 只拿一个配置,而 envFrom 一次拿多个配置
### 修改 ConfigMap 后会怎样
如果 Pod 启动时拿到 LOG_LEVEL=info,后面又将 ConfigMap 改成 LOG_LEVEL=debug 。这时候 Pod 是不会一起被修改成 debug 模式,因为环境变量是 Pod 在创建时被注入的
如果使用了 Deployment 创建的 Pod ,则可以通过 回滚 的方式进行重新配置
bash kubectl rollout restart deployment {deployment-name}
如果不是,则只能重新创建一个 Pod
## Volume 文件
并非所有的应用都是使用环境变量进行配置的,比如 Nginx,依赖的是 `xx/xx/nginx.conf 。同样的,在前面我们使用 --from-file 创建了一个保存 nginx.conf 的 ConfigMap,现在我们创建一个 Pod ,让 ConfigMap 把这个配置文件挂载到 Pod 容器里
bash cat > config-volume-demo.yaml <<'EOF' apiVersion: v1 kind: Pod metadata: name: config-volume-demo spec: containers:
name: nginx image: nginx:latest
volumeMounts:
- name: config mountPath: /etc/nginx/conf.d # 把 config 这个 Volume 挂载到这个目录 readOnly: true
volumes:
name: config configMap: name: nginx-config # 使用 nginx-config 这个 ConfigMap EOF
kubectl apply -f config-volume-demo.yaml
kubectl exec config-volume-demo -- cat /etc/nginx/conf.d/nginx.conf
这个 Pod 配置文件的意思就是 **去 nginx-config 这个 ConfigMap 拿到 nginx.conf,然后把 ConfigMap 作为一个 Volume 挂载到容器的 `/etc/nginx/conf.d` 目录中**

因为 ConfigMap 中的 Key 是 nginx.conf ,所以挂载到 `/etc/nginx/conf.d` 后,容器中就会出现 `/etc/nginx/conf.d/nginx.conf`

这里和前面学习的 Volume 是一样的,只是 Volume 的数据来源换成了 ConfigMap:
text ConfigMap # ConfigMap 提供数据(配置文件)
↓Volume # Volume 中间载体,把这份数据包装成一块可挂载的存储
↓容器目录 # 容器目录 最终的挂载点,容器里的程序去这个目录读配置
### 修改文件名
默认情况下,ConfigMap 的 Key 会直接作为容器中的文件名,比如:
text ConfigMap Key:nginx.conf
↓/etc/nginx/conf.d/nginx.conf
如果我们想把它改成 default.conf ,可以给 configMap 增加 items
yaml volumes:
- name: config
configMap:
name: nginx-config
items:
- key: nginx.conf # 读取 ConfigMap 中的 nginx.conf
path: default.conf # 挂载后文件名改成 default.conf
这时候容器中的文件就会变成:text /etc/nginx/conf.d/default.conf ``` 所以 items 可以理解为 指定要挂载 ConfigMap 中的哪个 Key,同时通过 path 指定这个 Key 在 Volume 中生成的文件名
- key: nginx.conf # 读取 ConfigMap 中的 nginx.conf
path: default.conf # 挂载后文件名改成 default.conf
修改 ConfigMap 后会怎样
如果我们后面修改了 nginx-config 中 nginx.conf 的内容,普通的 ConfigMap Volume 会在一段时间后把新的内容同步到容器中的文件,也就是
修改 ConfigMap
↓
Volume 中的文件更新
↓
容器中的 nginx.conf 更新
但是容器中的文件更新了,不代表 Nginx 一定马上使用新的配置,因为 Nginx 是否重新读取配置,还要看 Nginx 自己有没有重新加载配置文件,所以这里需要区分
- Kubernetes 更新配置文件
- 应用重新读取配置文件
### subPath
上面的写法是把整个 Volume 挂载到:
text /etc/nginx/conf.d如果只想把其中一个文件挂载到指定位置,可以使用 subPath ,意思就是 从 config 这个 Volume 中只拿 nginx.conf ,然后挂载成容器中的/etc/nginx/conf.d/default.conf```yaml volumeMounts: - name: config mountPath: /etc/nginx/conf.d/default.conf subPath: nginx.conf ``` 需要注意,使用 subPath 挂载以后,ConfigMap 后续发生修改时,这个文件不会自动同步更新,所以两种方式的区别就是
- 普通 ConfigMap Volume ,ConfigMap 修改后,文件会逐步同步更新
- subPath ,ConfigMap 修改后,文件不会自动更新
Secret
Secret 用来保存密码、令牌、证书这类敏感配置,使用方式和 ConfigMap 基本一致,同样可以通过环境变量或者 Volume 文件交给 Pod 使用
使用参数进行创建
kubectl create secret generic db-secret --from-literal=username=admin --from-literal=password='t0p-Secret'
kubectl get secret db-secret -o yaml
echo 'YWRtaW4=' | base64 -d
echo 'dDBwLVNlY3JldA==' | base64 -d
查看以后会发现,Secret 中的数据长得奇形怪状的,因为这里的内容使用了 Base64 编码,通过解码后就能看到原始的值。所以需要注意的是 Base64 只是一种编码方式,并不等于加密方式,只要拿到了 Secret 中的 Base64 内容,就可以很容易还原出原始数据

使用 YAML 创建
如果自己编写 Secret YAML,可以使用 stringData,这样就不需要自己手动进行 Base64 编码
cat > db-secret.yaml <<'EOF'
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
stringData:
username: admin
password: t0p-Secret
EOF
kubectl apply -f db-secret.yaml
kubectl get secret db-secret -o yaml

stringData.username: admin 保存以后大致会变成 data.username: YWRtaW4= ,所以 stringData 主要就是为了方便我们编写 Secret
需要注意,如果里面是真实密码,那么这个 YAML 文件同样不能直接提交到 Git 仓库,因为 stringData 并没有提供额外的加密能力
和 ConfigMap 一样,现在 Secret 虽然已经存在 Kubernetes 中了,但是 Pod 还没有拿到,所以接下来同样需要让 Pod 去引用它
Pod 怎么使用 Secret
Secret 和 ConfigMap 一样,也是主要有两种使用方式
- 环境变量:把 Secret 中的数据注入到容器环境变量中
Volume 文件:把 Secret 中的数据以文件形式挂载到容器中
环境变量
前面我们创建了一个 db-secret ,其中有
username=admin password=t0p-Secret现在创建一个 Pod,把 Secret 中的用户名和密码注入到容器环境变量
cat > secret-env-demo.yaml <<'EOF' apiVersion: v1 kind: Pod metadata: name: secret-env-demo spec: containers: - name: app image: busybox:1.36 command: ["sleep", "3600"] env: - name: DB_USERNAME valueFrom: secretKeyRef: name: db-secret # 去 db-secret 这个 Secret 拿值 key: username # 读取 username 这个 Key - name: DB_PASSWORD valueFrom: secretKeyRef: name: db-secret key: password EOF
kubectl apply -f secret-env-demo.yaml
kubectl exec secret-env-demo -- printenv DB_USERNAME
这个 Pod 配置文件的意思就是 **去 db-secret 这个 Secret,读取 username 和 password,然后分别作为容器中的 DB_USERNAME 和 DB_PASSWORD 环境变量**

这里和 ConfigMap 中的 `configMapKeyRef` 非常像
- ConfigMap → configMapKeyRef
- Secret → secretKeyRef
### 导入多个配置
如果 Secret 中有很多配置,一个一个写 secretKeyRef 也比较麻烦,可以使用 envFrom
yaml envFrom:
- secretRef: name: db-secret ``` 这样 Secret 中的多个 Key 会整体作为环境变量交给容器
所以两种方式的区别和 ConfigMap 一样
- secretKeyRef:读取某一个 Secret Key
- envFrom:一次导入 Secret 中多个配置 ### 修改 Secret 后会怎样 Secret 通过环境变量注入以后,更新规则和 ConfigMap 一样,中途修改不会改变 Pod 的环境变量
由 Deployment 管理的 Pod ,可以重新启动 Deployment ,让新的 Pod 重新读取 Secret
kubectl rollout restart deployment {deployment-name}
如果是单独创建的 Pod,则需要重新创建 Pod
Volume 文件
有些应用支持从文件中读取密码、证书、Token 等敏感配置,这时候可以把 Secret 挂载成 Volume,前面的 db-secret 中有:
username=admin
password=t0p-Secret
现在创建一个 Pod,把 Secret 挂载到 /etc/credentials 目录
cat > secret-volume-demo.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: secret-volume-demo
spec:
containers:
- name: app
image: busybox:1.36
command: ["sleep", "3600"]
volumeMounts:
- name: credentials
mountPath: /etc/credentials # 把 credentials Volume 挂载到这个目录
readOnly: true
volumes:
- name: credentials
secret:
secretName: db-secret # 使用 db-secret 这个 Secret
EOF
kubectl apply -f secret-volume-demo.yaml
kubectl exec secret-volume-demo -- ls /etc/credentials
kubectl exec secret-volume-demo -- cat /etc/credentials/username

- 文件名 = Secret 的 Key
- 文件内容 = Secret 的 Value
整体过程和 ConfigMap Volume 基本一致:
Secret # Secret 提供敏感配置
↓
Volume # Volume 作为中间载体
↓
容器目录 # 程序从目录中的文件读取配置
设置文件权限
Secret 中保存的通常是密码、私钥等敏感内容,所以还可以通过 defaultMode 设置挂载文件的权限
volumes:
- name: credentials
secret:
secretName: db-secret
defaultMode: 0400
0400 表示文件只有所有者具有读取权限 挂载以后类似:
-r-------- username
-r-------- password
修改 Secret 后会怎样
普通 Secret Volume 的更新方式和 ConfigMap Volume 类似,如果 Secret 内容发生修改
修改 Secret
↓
Volume 中的文件更新
↓
容器中的文件逐步更新
但是容器中的文件发生变化以后,应用是否重新读取新的密码或者证书,还需要看应用本身,所以同样需要区分
- Kubernetes 更新 Secret 文件
应用重新读取 Secret 文件
如果应用只在启动的时候读取一次密码,那么即使文件已经更新,程序也可能继续使用之前读取到内存中的旧密码
subPath
Secret Volume 同样支持 subPath,比如只想把
password这个文件挂载到指定位置volumeMounts:name: credentials mountPath: /etc/app/db-password subPath: password
意思就是 **从 credentials 这个 Volume 中只拿 password 文件,然后挂载成容器中的 `/etc/app/db-password`**
需要注意,Secret 使用 subPath 以后,后续 Secret 发生修改时,这个文件也不会自动同步更新,所以
- 普通 Secret Volume:Secret 修改后,文件会逐步同步更新
- subPath:Secret 修改后,文件不会自动更新
Secret 扩展
这里额外讲解一下 Secret 的一些东西
常见 Secret 类型
我们前面使用的:
type: Opaque
是最普通的一种 Secret 类型,用来保存自定义敏感数据,比如用户名、密码、Token 等。除此之外,Kubernetes 还提供了一些常见类型
kubernetes.io/tls:保存 TLS 证书和私钥kubernetes.io/dockerconfigjson:保存私有镜像仓库认证信息kubernetes.io/basic-auth:保存用户名和密码kubernetes.io/ssh-auth:保存 SSH 私钥
比如,创建一个 TLS Secret
kubectl create secret tls web-tls --cert=server.crt --key=server.key
kubectl get secret tls -o yaml
创建以后里面主要有两个字段:
tls.crt
tls.key
可以提供给 Ingress、Gateway 或应用程序使用
Secret 是否安全
Secret 的名字虽然叫 Secret,但不能简单理解成里面的数据已经被完全加密。首先,Secret 中常见的 Base64 内容可以直接解码:
echo 'YWRtaW4=' | base64 -d
所以 Base64 本身不提供安全保护,Secret 的安全还依赖 Kubernetes 其他机制,比如
- 使用 RBAC 限制谁可以读取 Secret
- 为 etcd 开启静态加密
- 不把真实密码写进 Git、日志和命令历史
限制哪些 Pod 可以使用 Secret
还有一个容易忽略的问题,如果某个用户有权限在一个 Namespace 中随意创建 Pod,那么他可能创建一个 Pod,把这个 Namespace 中的 Secret 挂载进去,然后进入容器读取
所以 Pod 创建权限本身也是一种比较敏感的权限
总结
这篇文章的内容也算是比较多的了,主要是 ConfigMap 和 Secret 两种配置方法操作方式都差不多,导致了大部分篇幅都是以操作为主
当然,看完这篇文章能依稀记得 ConfigMap 用来存普通配置,Secret 用了存敏感类配置就差不多了,后续忘了再回过头查就可以了
愿诸君顺遂