09ConfigMap和Secret

ConfigMap

在 Kubernetes 中,有几种特殊的 Volume,也就是Projected Volume,你可以把它翻译为“投射数据卷”,它们存在的意义不是为了存放容器里的数据,也不是用来进行容器和宿主机之间的数据交换。这些特殊 Volume 的作用,是为容器提供预先定义好的数据。所以,从容器的角度来看,这些 Volume 里的信息就是仿佛是被 Kubernetes“投射”(Project)进入容器当中的。这正是 Projected Volume 的含义。

ConfigMap顾名思义,就是保存配置项(key=value)的一个Map,如果你只是把它理解为编程语言中的一个Map,那就大错特错了。ConfigMap是分布式系统中“配置中心”的独特实现之一。我们知道,几乎所有应用都需要一个静态的配置文件来提供启动参数,当这个应用是一个分布式应用,有多个副本部署在不同的机器上时,配置文件的分发就成为一个让人头疼的问题,所以很多分布式系统都有一个配置中心组件,来解决这个问题。但配置中心通常会引入新的API,从而导致应用的耦合和侵入。Kubernetes则采用了一种简单的方案来规避这个问题,如图所示:

image.png

具体做法如下:

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

image.png

与 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资源文件
image.png

使用ConfigMap

ConfigMap和secret的创建方式几乎差不多,创建的三种方式省略,直接看使用

  1. 创建一个名为 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>
  1. 在 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:
image.png

image.png

华为商业产品CCE:
image.png

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会
  1. 命令行方式

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
  1. 文件方式

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
  1. 变量方式

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
posted @ 2023-07-19 17:07  风有遗霜  阅读(69)  评论(0)    收藏  举报