09ConfigMap和Secret
ConfigMap
在 Kubernetes 中,有几种特殊的 Volume,也就是Projected Volume,你可以把它翻译为“投射数据卷”,它们存在的意义不是为了存放容器里的数据,也不是用来进行容器和宿主机之间的数据交换。这些特殊 Volume 的作用,是为容器提供预先定义好的数据。所以,从容器的角度来看,这些 Volume 里的信息就是仿佛是被 Kubernetes“投射”(Project)进入容器当中的。这正是 Projected Volume 的含义。
ConfigMap顾名思义,就是保存配置项(key=value)的一个Map,如果你只是把它理解为编程语言中的一个Map,那就大错特错了。ConfigMap是分布式系统中“配置中心”的独特实现之一。我们知道,几乎所有应用都需要一个静态的配置文件来提供启动参数,当这个应用是一个分布式应用,有多个副本部署在不同的机器上时,配置文件的分发就成为一个让人头疼的问题,所以很多分布式系统都有一个配置中心组件,来解决这个问题。但配置中心通常会引入新的API,从而导致应用的耦合和侵入。Kubernetes则采用了一种简单的方案来规避这个问题,如图所示:

具体做法如下:
- 用户将配置文件的内容保存到ConfigMap中,文件名可作为key,value就是整个文件的内容,多个配置文件都可被放入同一个ConfigMap。
- 在建模用户应用时,在Pod里将ConfigMap定义为特殊的Volume进行挂载。在Pod被调度到某个具体Node上时,ConfigMap里的配置文件会被自动还原到本地目录下,然后映射到Pod里指定的配置目录下,这样用户的程序就可以无感知地读取配置了。
- 在ConfigMap的内容发生修改后,Kubernetes会自动重新获取ConfigMap的内容,并在目标节点上更新对应的文件。

与 Secret 类似的是 ConfigMap,它与 Secret 的区别在于,ConfigMap 保存的是不需要加密的、应用所需的配置信息。而 ConfigMap 的用法几乎与 Secret 完全相同:你可以使用 kubectl create configmap 从文件或者目录创建 ConfigMap,也可以直接编写 ConfigMap 对象的 YAML 文件。
比如,一个 Java 应用所需的配置文件(.properties 文件),就可以通过下面这样的方式保存在 ConfigMap 里:
# .properties 文件的内容
$ cat example/ui.properties
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice
# 从.properties 文件创建 ConfigMap
$ kubectl create configmap ui-config --from-file=example/ui.properties
# 查看这个 ConfigMap 里保存的信息 (data)
$ kubectl get configmaps ui-config -o yaml
apiVersion: v1
data:
ui.properties: |
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice
kind: ConfigMap
metadata:
name: ui-config
...
例如:Mybatis的jdbc资源文件
使用ConfigMap
ConfigMap和secret的创建方式几乎差不多,创建的三种方式省略,直接看使用
- 创建一个名为 nginx-config 的 ConfigMap,其中包含要用作 index.html 文件的 HTML 代码。可以使用以下命令来创建 ConfigMap:
[root@master 0510]# kubectl create configmap nginx-config --from-literal=index.html='<html><body><h1>Hello, World!</h1></body></html>'
configmap/nginx-config created
[root@master 0510]# kubectl get cm/nginx-config
NAME DATA AGE
nginx-config 1 4s
# 可以看到是以明文方式存储
[root@master 0510]# kubectl describe cm/nginx-config
Name: nginx-config
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
index.html:
----
<html><body><h1>Hello, World!</h1></body></html>
BinaryData
====
Events: <none>
- 在 Pod 的 YAML 文件中添加一个 volume 和容器卷挂载,以将 ConfigMap 挂载到 Pod 中,并将其内容提供给 Nginx 容器。
[root@master 0510]# vim nginx-cm.yaml
[root@master 0510]# kubectl apply -f nginx-cm.yaml
pod/nginx-pod created
[root@master 0510]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-pod 1/1 Running 0 4s
[root@master 0510]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-pod 1/1 Running 0 11s 10.244.140.94 node02 <none> <none>
[root@master 0510]# curl 10.244.140.94
<html><body><h1>Hello, World!</h1></body></html>
Secret
Secret也用于解决应用配置的问题,不过它解决的是对敏感信息的配置问题,比如数据库的用户名和密码、应用的数字证书、Token、SSH密钥及其他需要保密的敏感配置。对于这类敏感信息,我们可以创建一个Secret对象,然后被Pod引用。Secret中的数据要求以BASE64编码格式存放。注意,BASE64编码并不是加密的,Kubernetes1.7版本以后,Secret中的数据才可以以加密的形式进行保存,更加安全。
阿里商业产品ACK:

华为商业产品CCE:
Secret它的作用,是帮你把 Pod 想要访问的加密数据,存放到 Etcd 中。然后,你就可以通过在 Pod 的容器里挂载 Volume 的方式,访问到这些 Secret 里保存的信息了。
Secret 最典型的使用场景,莫过于存放数据库的 Credential 信息,比如下面这个例子:
apiVersion: v1
kind: Pod
metadata:
name: test-projected-volume
spec:
containers:
- name: test-secret-volume
image: busybox
args:
- sleep
- "86400"
volumeMounts:
- name: mysql-cred
mountPath: "/projected-volume"
readOnly: true
volumes:
- name: mysql-cred
projected:
sources:
- secret:
name: user
- secret:
name: pass
在这个 Pod 中,我定义了一个简单的容器。它声明挂载的 Volume,并不是常见的 emptyDir 或者 hostPath 类型,而是 projected 类型。而这个 Volume 的数据来源(sources),则是名为 user 和 pass 的 Secret 对象,分别对应的是数据库的用户名和密码。
这里用到的数据库的用户名、密码,正是以 Secret 对象的方式交给 Kubernetes 保存的。完成这个操作的指令,如下所示:
$ cat ./username.txt
admin
$ cat ./password.txt
c1oudc0w!
$ kubectl create secret generic user --from-file=./username.txt
$ kubectl create secret generic pass --from-file=./password.txt
其中,username.txt 和 password.txt 文件里,存放的就是用户名和密码;而 user 和 pass,则是我为 Secret 对象指定的名字。而我想要查看这些 Secret 对象的话,只要执行一条 kubectl get 命令就可以了:
$ kubectl get secrets
NAME TYPE DATA AGE
user Opaque 1 51s
pass Opaque 1 51s
当然,除了使用 kubectl create secret 指令外,我也可以直接通过编写 YAML 文件的方式来创建这个 Secret 对象,比如:
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
user: YWRtaW4=
pass: MWYyZDFlMmU2N2Rm
可以看到,通过编写 YAML 文件创建出来的 Secret 对象只有一个。但它的 data 字段,却以 Key-Value 的格式保存了两份 Secret 数据。其中,“user”就是第一份数据的 Key,“pass”是第二份数据的 Key。
需要注意的是,Secret 对象要求这些数据必须是经过 Base64 转码的,以免出现明文密码的安全隐患。这个转码操作也很简单,比如:
$ echo -n 'admin' | base64
YWRtaW4=
$ echo -n '1f2d1e2e67df' | base64
MWYyZDFlMmU2N2Rm
这里需要注意的是,像这样创建的 Secret 对象,它里面的内容仅仅是经过了转码,而并没有被加密。在真正的生产环境中,你需要在 Kubernetes 中开启 Secret 的加密插件,增强数据的安全性。
接下来,我们尝试一下创建这个 Pod:
$ kubectl create -f test-projected-volume.yaml
当 Pod 变成 Running 状态之后,我们再验证一下这些 Secret 对象是不是已经在容器里了:
$ kubectl exec -it test-projected-volume -- /bin/sh
$ ls /projected-volume/
user
pass
$ cat /projected-volume/user
root
$ cat /projected-volume/pass
1f2d1e2e67df
从返回结果中,我们可以看到,保存在 Etcd 里的用户名和密码信息,已经以文件的形式出现在了容器的 Volume 目录里。而这个文件的名字,就是 kubectl create secret 指定的 Key,或者说是 Secret 对象的 data 字段指定的 Key。
更重要的是,像这样通过挂载方式进入到容器里的 Secret,一旦其对应的 Etcd 里的数据被更新,这些 Volume 里的文件内容,同样也会被更新。其实,这是 kubelet 组件在定时维护这些 Volume。
需要注意的是,这个更新可能会有一定的延时。所以在编写应用程序时,在发起数据库连接的代码处写好重试和超时的逻辑,绝对是个好习惯。
创建secret
首先,需要了解三种将数据添加到 Secrets 中的不同的方式:
- --from-env-file=[file]:使用此选项可以从文件中读取多个环境变量并将它们添加到 Secret 中。每行一个环境变量,格式为 KEY=VALUE。
- --from-file=[file]:使用此选项可以将整个文件作为一个 Secret 添加到 Kubernetes 集群中。在添加之后,可以通过挂载该 Secret 来访问文件中的内容。
- --from-literal=[key=value]:使用此选项可以直接将键值对添加到 Secret 中。这是一种简单但较少使用的方法,常用于添加单个键值对。
总的来说,这三种方法都可以用于创建 Secrets,但它们针对数据的形式有所不同。--from-env-file 适用于存储多个键值对,而 --from-file 更适合于存储大型文件,而 --from-literal 则更适合存储单个键值对。
secret类型:
- 默认为 Opaque,base64编码格式,用来存储密码和密钥,但是它可以通过base64 -decode解码得到原始数据,安全性较弱。
- dockerconfigjson:存储私有docker registry的认证信息
- service-account-token:用于被serviceaccount引用。sa创建的时候,k8s会创建对应的secret。如果pod使用了sc,对应的secret会
- 命令行方式
kubectl create secret generic mysec1 --from-literal=name1=wang --from-literal=name2=yuan
[root@master 0510]# kubectl create secret generic mysec1 --from-literal=name1=wang --from-literal=name2=yuan
secret/mysec1 created
[root@master 0510]# kubectl get secrets
NAME TYPE DATA AGE
mysec1 Opaque 2 7s
[root@master 0510]# kubectl describe secrets/mysec1
Name: mysec1
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
name1: 4 bytes # 默认只会显示key,不会显示values
name2: 4 bytes
解码base64
# 查看编码后的values
[root@master 0510]# kubectl get secrets mysec1 -o yaml
apiVersion: v1
data:
name1: d2FuZw==
name2: eXVhbg==
kind: Secret
metadata:
creationTimestamp: "2023-05-10T01:40:46Z"
name: mysec1
namespace: default
resourceVersion: "421303"
uid: e29cd69e-96a0-40b3-af2b-8b95f5f8a450
type: Opaque
# 手动编码
[root@master 0510]# echo -n wang | base64
d2FuZw==
[root@master 0510]# echo -n yuan | base64
eXVhbg==
# 解码
[root@master 0510]# echo -n d2FuZw== | base64 -d ;echo
wang
[root@master 0510]# echo -n eXVhbg== | base64 -d ;echo
yuan
- 文件方式
kubectl create secret generic mysec2 --from-file user
[root@master 0510]# echo -n redhat > user
[root@master 0510]# cat user ;echo
redhat
[root@master 0510]# kubectl create secret generic mysec2 --from-file user
secret/mysec2 created
[root@master 0510]# kubectl get secrets mysec2
NAME TYPE DATA AGE
mysec2 Opaque 1 16s
[root@master 0510]# kubectl describe secrets mysec2
Name: mysec2
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
user: 6 bytes
[root@master 0510]# kubectl get secrets mysec2 -o yaml
apiVersion: v1
data:
user: cmVkaGF0
kind: Secret
metadata:
creationTimestamp: "2023-05-10T02:28:57Z"
name: mysec2
namespace: default
resourceVersion: "428538"
uid: 073008bf-1c51-4689-be4d-fbc95b6c7819
type: Opaque
# 解码验证
[root@master 0510]# echo -n cmVkaGF0 | base64 -d ;echo
redhat
- 变量方式
kubectl create secret generic mysec3 --from-env-file=data.txt
[root@master 0510]# vim data.txt
[root@master 0510]# cat data.txt
name=wang
age=18
sex=1
[root@master 0510]# kubectl create secret generic mysec3 --from-env-file=data.txt
secret/mysec3 created
[root@master 0510]# kubectl get secrets mysec3
NAME TYPE DATA AGE
mysec3 Opaque 3 29s
[root@master 0510]# kubectl describe secrets mysec3
Name: mysec3
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
age: 2 bytes
name: 4 bytes
sex: 1 bytes
[root@master 0510]# kubectl get secrets mysec3 -o yaml
apiVersion: v1
data:
age: MTg=
name: d2FuZw==
sex: MQ==
kind: Secret
metadata:
creationTimestamp: "2023-05-10T02:35:38Z"
name: mysec3
namespace: default
resourceVersion: "429539"
uid: 52dd5e0a-05b9-484e-9d9b-6b4ab2a9c268
type: Opaque
[root@master 0510]# echo -n MTg= | base64 -d ;echo
18
[root@master 0510]# echo -n d2FuZw== | base64 -d ;echo
wang
[root@master 0510]# echo -n MQ== | base64 -d ;echo
1
使用secret
[root@master 0510]# cat pod1.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pod1
name: pod1
spec:
volumes:
- name: v1
secret:
secretName: mysec3
optional: true
containers:
- image: nginx
name: pod1
volumeMounts:
- name: v1
mountPath: "/tmp/abc"
readOnly: true
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
[root@master 0510]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-8465d75b7-9lxwf 1/1 Running 3 (3d12h ago) 5d13h
pod1 1/1 Running 0 86s
[root@master 0510]# kubectl exec -it pod/pod1 -- bash
root@pod1:/# cd /tmp/abc/
# 文件就是键,内容就是值。
root@pod1:/tmp/abc# cat age ; echo
18
root@pod1:/tmp/abc# cat sex ; echo
1
root@pod1:/tmp/abc# cat name ; echo
wang
root@pod1:/tmp/abc# exit
exit
但是对于某些镜像,它在运行时,需要这些变量,比如mysql,要加上 MYSQL_ROOT_PASSWORD.那如何把name1的值,设置为 MYSQL_ROOT_PASSWORD 的值呢?
以卷的方式挂载,并不适合用于镜像变量传递密码。那它的应用场景是什么呢?一般用于传递配置文件。
示例:给MySQL传递密码
[root@master 0510]# cat mydb.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: mydb
name: mydb
spec:
containers:
- image: mysql
imagePullPolicy: IfNotPresent
name: mydb
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysec2
key: user
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
[root@master 0510]# kubectl get secrets mysec2 -o yaml
apiVersion: v1
data:
user: cmVkaGF0
kind: Secret
metadata:
creationTimestamp: "2023-05-10T02:28:57Z"
name: mysec2
namespace: default
resourceVersion: "428538"
uid: 073008bf-1c51-4689-be4d-fbc95b6c7819
type: Opaque
[root@master 0510]# echo -n cmVkaGF0|base64 -d;echo
redhat
[root@master 0510]# kubectl exec -it pod/mydb -- bash
bash-4.4# echo $MYSQL_ROOT_PASSWORD
redhat
bash-4.4# exit
exit
command terminated with exit code 127
[root@master 0510]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
mydb 1/1 Running 0 7m48s 10.244.196.163 node01 <none> <none>
nfs-client-provisioner-8465d75b7-9lxwf 1/1 Running 3 (3d12h ago) 5d13h 10.244.196.145 node01 <none> <none>
pod1 1/1 Running 0 24m 10.244.140.88 node02 <none> <none>
[root@master 0510]# mysql -u root -predhat 10.244.196.163
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (2)
[root@master 0510]# mysql -u root -predhat -h 10.244.196.163
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.33 MySQL Community Server - GPL
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.003 sec)
MySQL [(none)]> exit
Bye

浙公网安备 33010602011771号