13. Kubernetes - ConfigMap / Secret
ConfigMap
前面的资源对象并不能满足日常工作中的所有需求,一个最重要的需求就是应用的配置管理,特别是可变配置。
比如,在开发过程中程序需要配置 MySQL 或者 Redis 的连接地址。如果是以前的部署方式,此时想要修改这些信息,就需要修改代码的配置,然后重新打包部署。如果使用 ConfigMap,它能够向容器中注入配置信息,不仅可以是单个配置,也可以是整个配置文件。后面只需要修改 ConfigMap 的配置就能实现应用的配置更新。
ConfigMap 资源清单
ConfigMap 资源清单示例:
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-demo
data:
serviceName: "demo-service"
listenPort: "8080"
config: |
mysql.ip="192.168.2.1"
mysql.port="3306"
redis.ip="192.168.2.2"
redis.port="6379"
在这个配置清单中,前两项是单个属性配置,config 字段中的可以看成是一个配置文件,其中的 |
的作用在于,保留下面属性的换行符和每行相对于第一行的缩进,多余的缩进和行尾的空白都会被删除。
使用示例:
config: |
第一行
第二行
第三行
第四行
转换成 JSON 格式就是:
{"config": "第一行\n 第二行\n 第三行\n第四行"}
如果将 |
换成 >
,表示折叠的意思,只有空白和才会被识别成换行,原来的换行符则会被识别成空格:
config: >
第一行
第二行
第三行
第五行
转换成 JSON 格式就是:
{"config": "第一行 第二行 第三行\n第五行"}
还可以使用竖线和加号或者减号进行配合使用,+
表示保留文字块末尾的换行,-
表示删除字符串末尾的换行。
config: |
"hello"
# Json 格式:{"config": "hello\n"}
config: |-
"hello"
# Json 格式:{"config": "hello"}
config: |+
"hello"
# Json 格式:{"config": "hello\n\n"}
# 有几个换行则加几个
创建 ConfigMap
创建 ConfigMap 的命令:
# 通过指定目录创建 ConfigMap
kubectl create configmap cm-demo --from-file=/path/dir
# 通过指定文件创建 ConfigMap,可以是多个文件
kubectl create configmap cm-demo --from-file=key1=/path/dir/file1.txt --from-file=key2=/path/dir/file2.txt
# 直接指定键值创建 ConfigMap
kubectl create configmap cm-demo --from-literal=key1=value1 --from-literal=key2=value2
准备一个目录 /cm/config/
,下面准备两个文件 mysql.conf
和 redis.conf
:
# mysql.conf 内容
host="192.168.2.1"
port="3306"
# redis.conf 内容
host="192.168.2.2"
port="6379"
测试创建:
# 直接通过整个目录创建,默认的 Key 就是文件名
kubectl create configmap cm-demo1 --from-file=/cm/config
# 指定文件创建,可以指定对于的 Key
kubectl create configmap cm-demo2 --from-file=mysqlConfig=/cm/config/mysql.conf --from-file=redisConfig=/cm/config/redis.conf
# 指定 K/V 直接创建
kubectl create configmap cm-demo3 --from-literal=k1=v1 --from-literal=k2=v2
使用 ConfigMap(环境变量)
在 ConfigMap 创建成功之后,有以下方法在 Pod 中使用它:
- 设置环境变量的值
- 在容器中设置命令行参数
- 在数据卷中挂载配置文件
使用环境变量示例:
apiVersion: v1
kind: Pod
metadata:
name: pod-cm-demo1
spec:
containers:
- name: c-cm-demo1
image: busybox:latest
command: [/bin/sh, -c, "env"]
# 将每个配置文件单独给一个环境变量
env:
- name: MYSQL_CONFIG
valueFrom:
configMapKeyRef:
name: cm-demo1
key: mysql.conf
- name: REDIS_CONFIG
valueFrom:
configMapKeyRef:
name: cm-demo1
key: redis.conf
# 直接导入整个 ConfigMap 到环境变量中
envFrom:
- configMapRef:
name: cm-demo
通过查看创建 Pod 之后输出的日志,可以看到设置的环境变量为:
# cm-demo 的 mysql.conf Key
MYSQL_CONFIG=host="192.168.2.1"
port="3306"
# cm-demo 的 redis.conf Key
REDIS_CONFIG=host="192.168.2.2"
port="6379"
# cm-demo 的配置生成了三个环境变量
config=mysql.ip="192.168.2.1"
mysql.port="3306"
redis.ip="192.168.2.2"
redis.port="6379"
serviceName=demo-service
listenPort=8080
使用 ConfigMap(容器运行参数)
作为容器的运行参数示例:
apiVersion: v1
kind: Pod
metadata:
name: pod-cm-demo2
spec:
containers:
- name: c-cm-demo2
image: busybox:latest
command: [/bin/sh, -c, "echo ${ENV_V1}-${ENV_V2}"]
env:
- name: ENV_V1
valueFrom:
configMapKeyRef:
name: cm-demo3
key: k1
- name: ENV_V2
valueFrom:
configMapKeyRef:
name: cm-demo3
key: k2
使用 ConfigMap(数据卷挂载)
以数据卷的方式挂载到 Pod 中使用:
apiVersion: v1
kind: Pod
metadata:
name: pod-cm-demo3
spec:
volumes:
- name: v-cm-demo
configMap:
name: cm-demo1
items:
- key: mysql.conf
path: path/to/msyql.conf
containers:
- name: c-cm-demo3
image: busybox:latest
command: [/bin/sh, -c, "ls -lh /opt/config/ && cat /opt/config/mysql.conf"]
volumeMounts:
- name: v-cm-demo
mountPath: /opt/config/
此时 ConfigMap 中的 Key 就能够变成文件名,然后挂载到指定的目录下。当然,也可以自己指定挂载的文件:
...
configMap:
name: cm-demo1
items:
- key: mysql.conf
path: /opt/config/mysql.conf
...
当使用数据卷的方式挂载到 Pod 中,此时更新 ConfigMap,挂载的数据也是会跟着热更新的。这意味着以配置文件名称作为 Key 名称,然后使用 volume 挂载的方式是最适合开发场景的。
只有通过 Kubernetes API 创建的 Pod 才能使用 ConfigMap,其他方式创建的(比如静态 Pod)不能使用,同时 ConfigMap 文件大小限制为
1MB
(ETCD 的要求)。
Secret
ConfigMap 一般用于存储非安全的配置信息,原因在于 ConfigMap 使用的是明文的方式存储。这用来保存密码等对象显然是不合理的。此时就需要另外一个对象帮忙完成,那就是 Secret。
Secret 主要包含了以下几种类型:
Opaque
:base64 编码的 Secret,主要用来存储密码,密钥等。但数据可以通过base64 -d
解码得到,加密性很弱。kubernetes.io/dockercfg
:~/dockercfg
文件的序列化形式。kubernetes.io/dockerconfigjson
:用来存储私有 docker registry 的认证信息,~/.docker/config.json
文件的序列号形式。kubernetes.io/service-account-token
:ServiceAccount 在创建时,Kubernetes 会默认创建一个对应的 Secret 对象,Pod 如果使用 ServiceAccount,对应的 Secret 会自动的挂载到 Pod 的/run/secret/kubernetes.io/serviceaccount
中。kubernetes.io/ssh-auth
:用于 SSH 身份认证的凭据。kubernetes.io/basic-auth
:用于基本身份认证的凭据。bootstrap.kubernetes.io/token
:用于节点接入集群校验的 Secret。
上面是 Secret 对象内置的几种类型,通过 Secret 的 type 字段设置,也可以定义自己的 Secret 类型。如果 type 字段为空,则使用默认的 Opaque
类型。
Opaque
Secret 资源包含 2 个键值对:
-
data
:用于存储 base64 编码的任意数。 -
stringData
:为了方便 secret 使用未编码的字符串。
比如,新建一个 username 为 admin,password 为 admin123 的 Secret 对象。
先对 username 和 password 进行 base64 编码:
echo -n admin | base64
echo -n admin123 | base64
如图所示:
创建 Secret 资源清单:
apiVersion: v1
kind: Secret
metadata:
name: secret-demo
type: Opaque
data:
username: YWRtaW4=
password: YWRtaW4xMjM=
应用之后查看:
kubectl describe secrets secret-demo
如图所示:
可以看到 data 中的数据没有被显示出来,如果想要获取到数据可以以 yaml 当时显示:
kubectl get secrets secret-demo -o yaml
如图所示:
在某些时候,也可能不想手动编码,此时就可以将数据放到 stringData 中,那么在创建或者更新的 Secret 的时候就能自动对其进行编码,比如一个复杂点的场景:
apiVersion: v1
kind: Secret
metadata:
name: secret-demo1
type: Opaque
stringData:
mysql.yaml: |
host: 192.168.2.1
port: 3306
username: root
password: root123
此时看到整个数据的配置就是加密的状态:
解密查看:
创建好 Secret 对象后,有两种方式来使用它:
- 环境变量的方式
- Volume 挂载的方式
使用 Secret(环境变量)
通过环境变量的方式使用 Secret:
apiVersion: v1
kind: Pod
metadata:
name: pod-secret-demo1
spec:
containers:
- name: c-secret-demo
image: busybox:latest
command: ["/bin/sh", "-c", "env"]
env:
- name: USERNAME
valueFrom:
secretKeyRef:
name: secret-demo
key: username
- name: PASSWORD
valueFrom:
secretKeyRef:
name: secret-demo
key: password
运行之后通过日志可以看到:
USERNAME=admin
PASSWORD=admin123
变量是以明文的方式注入到环境变量中的。
使用 Secret(数据卷挂载)
通过数据卷挂载的方式使用 Secret:
apiVersion: v1
kind: Pod
metadata:
name: pod-secret-demo2
spec:
volumes:
- name: v-secret-demo
secret:
secretName: secret-demo1
containers:
- name: c-secret-demo2
image: busybox:latest
command: ["/bin/sh", "-c", "cat /opt/secret/mysql.yaml"]
volumeMounts:
- name: v-secret-demo
mountPath: /opt/secret/
使用方法和 ConfigMap 是类似的,也可以通过 items
指定挂载到指定的文件。
kubernetes.io/dockerconfigjson
直接创建 docker registry 认证的 secret:
kubectl create secret docker-registry secret-docker-registry --docker-server="hub.docker.com" --docker-username="dylan" --docker-password="123456" --docker-email="1214966109@qq.com"
除此之外,也可以通过指定文件的方式创建镜像仓库认证信息:
kubectl create secret generic secret-docker-registry --from-file=.dockerconfigjson=/root/.docker/config.json --type=kubernetes.io/dockerconfigjson
如图所示:
可以通过 base64 解码:
此时如果需要拉取私有仓库的镜像要用到认证的,就可以直接使用这个Secret:
apiVersion: v1
kind: Pod
metadata:
name: pod-secret-demo2
spec:
containers:
- name: c-secret-demo2
image: hub.docker.com/imageDemo:v1.0
imagePullSecrets:
- name: secret-docker-registry
特别注意:
ImagePullSecrets
与Secrets
不同,因为Secrets
可以挂载到 Pod 中,但是ImagePullSecrets
只能由 Kubelet 访问。
除了该方法设置 ImagePullSecrets 的方式访问私有仓库获取镜像以外,还可以通过 ServiceAccount 中设置 ImagePullSecrets 然后自动为使用了该 SA 的 Pod 注入配置信息。
kubernetes.io/basic-auth
该类型用来存放用于基本身份认证所需的凭据信息,使用这种 Secret 类型时,Secret 的 data(或 stringData)字段中一般会包含以下两个键:
- username:用于身份认证的用户名。
- password:用于身份认证的密码或令牌。
可以和创建 Opaque 一样通过 data 字段或者 stringData 字段定义。
apiVersion: v1
kind: Secret
metadata:
name: secret-demo3
type: kubernetes.io/basic-auth
stringData:
username: admin
password: admin123
提供基本身份认证类型的 Secret 仅仅是出于用户方便性考虑,也可以使用 Opaque 类型来保存用于基本身份认证的凭据,不过使用内置的 Secret 类型的有助于对凭据格式进行统一处理。
kubernetes.io/ssh-auth
该类型用来存放 SSH 身份认证中所需要的凭据,使用这种 Secret 类型时,Secret 的 data(或 stringData)字段中一般会提供一个 ssh-privatekey
键值对,作为要使用的 SSH 凭据。
apiVersion: v1
kind: Secret
metadata:
name: secret-demo4
type: kubernetes.io/ssh-auth
data:
ssh-privatekey: |
MIIEpQIBAAKCAQEAulqb...
同样提供 SSH 身份认证类型的 Secret 也仅仅是出于用户方便性考虑,也可以使用 Opaque 类型来保存用于 SSH 身份认证的凭据,只是使用内置的 Secret 类型的有助于对凭据格式进行统一处理。
kubernetes.io/tls
该类型用来存放证书及其相关密钥。该类型主要提供给 Ingress 资源,用以校验 TLS 链接,使用此类型的 Secret 时,Secret 的 data (或 stringData)字段必须包含 tls.key
和 tls.crt
主键。
apiVersion: v1
kind: Secret
metadata:
name: secret-demo4
type: kubernetes.io/tls
data:
tls.crt: |
MIIC2DCCAcCgAwIBAgIBATANBgkqh ...
tls.key: |
MIIEpgIBAAKCAQEA7yn3bRHQ5FHMQ ...
提供 TLS 类型的 Secret 仅仅是出于用户方便性考虑,也可以使用 Opaque 类型来保存用于 TLS 服务器与/或客户端的凭据。只是使用内置的 Secret 类型的有助于对凭据格式进行统一化处理。
当使用 kubectl 来创建 TLS Secret 时,可以通过命令指定证书来直接创建更为方便:
kubectl create secret tls secret-demo4 --cert=/path/cert --key=/path/key
需要注意的是用于 --cert
的公钥证书必须是 .PEM
编码的 (Base64 编码的 DER 格式),且与 --key
所给定的私钥匹配,私钥必须是通常所说的 PEM 私钥格式,且未加密。
对这两个文件而言,PEM 格式数据的第一行和最后一行(--------BEGIN CERTIFICATE-----
和 -------END CERTIFICATE----
)都不会包含在其中。
kubernetes.io/service-account-token
ServiceAccount
是 Pod 和集群 apiserver 通讯的访问凭证。kubernetes.io/service-account-token
这种类型就是主要给 ServiceAccount 使用。ServiceAccount 在创建时会默认创建对应的 Secret。
查看任意一个 Pod:
kubectl get pod pod-secret-demo1 -o yaml
可以看到:
- 当创建 Pod 的时候,如果没有指定 ServiceAccount,Pod 则会使用默认名称空间中名称为 default 的 ServiceAccount。
- volumes 字段中有一个
projected
类型的 volume 被挂载到了/var/run/secrets/kubernetes.io/serviceaccount
,该类型的 volume 可以同时挂载多个来源的数据。默认挂载了 configMap,downwardAPI,serviceAccountToken。
更多信息由于需要等到学习了 ServiceAccount 的时候才能搞得清除。
补充说明
如果某个容器已经在通过环境变量使用某 Secret,对该 Secret 的更新不会被容器马上看见,除非容器被重启,当然也可以使用一些第三方的解决方案在 Secret 发生变化时触发容器重启。
在 Kubernetes v1.21 版本提供了不可变的 Secret 和 ConfigMap 的可选配置 stable
,对于大量使用 Secret 或 ConfigMap 的集群时,禁止变更具有以下好处:
- 可以防止意外更新导致应用程序中断。
- 通过将 Secret 标记为不可变来关闭 kube-apiserver 对其的 watch 操作,从而显著降低 kube-apiserver 的负载,提升集群性能。
配置方法:
apiVersion: v1
kind: Secret
metadata:
...
data:
...
immutable: true # 标记为不可变
一旦 Secret 或 ConfigMap 被标记为不可更改,撤销此操作或更改 data 字段的内容都是不允许的,只能删除并重新创建这个 Secret。现有的 Pod 将继续使用已删除 Secret 的挂载点,所以也需要重新引用的 Pod。
Secret vs ConfigMap
相同点:
- k/v 形式
- 属于特定的命名空间
- 可以导出到环境变量
- 可以通过目录/文件形式挂载
- 通过 volume 挂载的配置信息均可热更新
不同点:
- Secret 可以被 ServerAccount 关联。
- Secret 可以存储 docker register 的鉴权信息,用在 ImagePullSecret 参数中,用于拉取私有仓库的镜像。
- Secret 支持 Base64 加密。
- Secret 有多种分类,而 ConfigMap 不区分类型。
同样 Secret 文件大小限制为 1MB(ETCD 的要求),Secret 虽然采用 Base64 编码,但还是可以很方便解码获取到原始信息,所以对于非常重要的数据还是需要慎重考虑,可以考虑使用 Vault 来进行加密管理。