k8s系列-存储-ConfigMap和Secret

K8S-ConfigMap和Secret

ConfigMap和Secret性质上比较类似,K8S中都通常用来存储一些配置信息,Secret一般用来保存密码、token、密钥等敏感数据的配置问题,定义声明式文件的时候可以用volume或者环境变量方式引用,ConfigMap通常用来存一些没那么敏感的数据。

ConfigMap

简介

ConfigMap API资源用来保存key-value pair配置数据,这个数据可以在pods里使用,或者被用来为像controller一样的系统组件存储配置数据。ConfigMap不是属性配置文件的替代品,ConfigMaps只是作为多个properties文件的引用,比如下面这个例子中

data一栏包括很多配置数据,可以用来保存单个属性,也可以用来保存一个配置文件,配置的数据可以在pod中来使用,ConfigMaps可以被用来:

  • 设置环境变量的值
  • 在容器里设置命令行参数
  • 在数据卷里创建config文件

一个简单的ConfigMap

config-example.yml

1
2
3
4
5
6
7
8
9
10
11
12
kind: ConfigMap
apiVersion: v1
metadata:
name: example-config
namespace: default
data:
example.property.1: hello
example.property.2: world
example.property.file: |-
property.1=value-1
property.2=value-2
property.3=value-3

我们使用如下命令就可以创建这个ConfigMap了

1
kubectl apply -f config-example.yml

创建ConfigMap的几种方式

同时我们可以使用如下命令查看如何创建ConfigMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@localhost home]# kubectl create configmap -h

Examples:
# Create a new configmap named my-config based on folder bar
kubectl create configmap my-config --from-file=path/to/bar

# Create a new configmap named my-config with specified keys instead of file basenames on disk
kubectl create configmap my-config --from-file=key1=/path/to/bar/file1.txt --from-file=key2=/path/to/bar/file2.txt

# Create a new configmap named my-config with key1=config1 and key2=config2
kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2

# Create a new configmap named my-config from the key=value pairs in the file
kubectl create configmap my-config --from-file=path/to/bar

# Create a new configmap named my-config from an env file
kubectl create configmap my-config --from-env-file=path/to/bar.env

使用文件夹创建ConfigMap

比如我们现在有如下两个properties文件:

game.properties

1
2
3
4
5
6
7
enemies=aliens
lives=3
enemies.cheat=true
enemies.cheat.level=noGoodRotten
secret.code.passphrase=UUDDLRLRBABAS
secret.code.allowed=true
secret.code.lives=30

ui.properties

1
2
3
4
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice

现在我们把这两个文件都放在/home/properties目录下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@localhost properties]# cat /home/properties/game.properties
enemies=aliens
lives=3
enemies.cheat=true
enemies.cheat.level=noGoodRotten
secret.code.passphrase=UUDDLRLRBABAS
secret.code.allowed=true
secret.code.lives=30

[root@localhost properties]# cat /home/properties/ui.properties
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice

我们使用如下命令创建出这个ConfigMap

1
[root@localhost properties]# kubectl create configmap game-config --from-file=/home/properties/

--from-file指定的是只要在目录下面的所有文件都会被用在ConfigMap里面创建一个键值对,键的名字就是文件名字,值就是文件内容

可以使用如下命令查看这个刚创建的ConfigMap,并且使用-o yaml指定以yaml的格式输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@localhost properties]# kubectl get configmap game-config -o yaml
apiVersion: v1
data:
game.properties: |
enemies=aliens
lives=3
enemies.cheat=true
enemies.cheat.level=noGoodRotten
secret.code.passphrase=UUDDLRLRBABAS
secret.code.allowed=true
secret.code.lives=30
ui.properties: |
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice
kind: ConfigMap
metadata:
creationTimestamp: "2019-04-17T15:57:21Z"
name: game-config
namespace: default
resourceVersion: "381696"
selfLink: /api/v1/namespaces/default/configmaps/game-config
uid: 7c106bf9-6129-11e9-b679-080027eea012

使用单个文件创建ConfigMap

也可以使用之前的单个properties文件进行创建ConfigMap,比如使用如下命令创建game.properties的ConfigMap,并且使用yaml格式进行查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@localhost properties]# kubectl create configmap game-config-222 --from-file=/home/properties/game.properties
configmap/game-config-222 created

[root@localhost properties]# kubectl get configmap game-config-222 -o yaml
apiVersion: v1
data:
game.properties: |
enemies=aliens
lives=3
enemies.cheat=true
enemies.cheat.level=noGoodRotten
secret.code.passphrase=UUDDLRLRBABAS
secret.code.allowed=true
secret.code.lives=30
kind: ConfigMap
metadata:
creationTimestamp: "2019-04-17T16:07:27Z"
name: game-config-222
namespace: default
resourceVersion: "382481"
selfLink: /api/v1/namespaces/default/configmaps/game-config-222
uid: e516cec2-612a-11e9-b679-080027eea012

命令行指定key-value创建ConfigMap

我们可以直接在create的时候指定创建的ConfigMap的key-value,比如下面的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@localhost properties]# kubectl create configmap special-config2 --from-literal=test=good --from-literal=test2=good2
configmap/special-config2 created

[root@localhost properties]# kubectl get configmap special-config2 -o yaml
apiVersion: v1
data:
test: good
test2: good2
kind: ConfigMap
metadata:
creationTimestamp: "2019-04-17T16:10:12Z"
name: special-config2
namespace: default
resourceVersion: "382697"
selfLink: /api/v1/namespaces/default/configmaps/special-config2
uid: 47ddf47d-612b-11e9-b679-080027eea012

Pod中使用ConfigMap

使用ConfigMap来替代环境变量

定义如下文件pod-config-env.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
apiVersion: v1
kind: ConfigMap
metadata:
name: special-config
namespace: default
data:
special.how: very
special.type: charm
---
apiVersion: v1
kind: ConfigMap
metadata:
name: env-config
namespace: default
data:
log_level: INFO
---
apiVersion: v1
kind: Pod
metadata:
name: pod-config-env
spec:
containers:
- name: test-container
image: busybox:1.28.3
command: ["/bin/sh", "-c", "env"]
env:
- name: SPECIAL_LEVEL_KEY
valueFrom:
configMapKeyRef:
name: special-config
key: special.how
- name: SPECIAL_TYPE_KEY
valueFrom:
configMapKeyRef:
name: special-config
key: special.type
envFrom:
- configMapRef:
name: env-config
restartPolicy: Never

上文中我们定义了两个ConfigMap,一个是special-config,一个是env-config,分别以不同的方式引入,一种是直接envFrom将env-config整体引入pod作为环境变量,一种是通过env的定义按需引入,指定name为ConfigMap的名字,key为ConfigMap中的key。

使用如下命令进行创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@localhost properties]# kubectl apply -f /home/pod-config-env.yml

#再使用如下命令查看这个pod的日志输出,因为这个container在运行的时候会打印出所有的环境变量,可以看到包含 SPECIAL_TYPE_KEY=charm SPECIAL_LEVEL_KEY=very log_level=INFO,证明环境变量引入成功
[root@localhost properties]# kubectl logs pod-config-env
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.96.0.1:443
HOSTNAME=pod-config-env
SHLVL=1
HOME=/root
SPECIAL_TYPE_KEY=charm
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
SPECIAL_LEVEL_KEY=very
log_level=INFO
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_SERVICE_HOST=10.96.0.1
PWD=/

ConfigMap 的更新

ConfgiMap 更新后,如果是以文件夹方式挂载的,会自动将挂载的 Volume 更新。如果是以文件形式挂载的,则不会自动更新。
但是对多数情况的应用来说,配置文件更新后,最简单的办法就是重启 Pod(杀掉再重新拉起)。如果是以文件夹形式挂载的,可以通过在容器内重启应用的方式实现配置文件更新生效。即便是重启容器内的应用,也要注意 ConfigMap 的更新和容器内挂载文件的更新不是同步的,可能会有延时,因此一定要确保容器内的配置也已经更新为最新版本后再重新加载应用。

Secret

Secret 对象类型用来保存敏感信息,例如密码、OAuth 令牌和 ssh key。将这些信息放在 Secret 中比放在 Pod 的定义或者 docker 镜像中来说更加安全和灵活。

创建一个简单的 Secret

1
2
3
# Create files needed for rest of example.
$ echo -n "admin" > ./username.txt
$ echo -n "1f2d1e2e67df" > ./password.txt

使用 kubectl 将这个文件打包为一个 Secret 中并在 APIServer 中创建了一个对象。generic 指的是从本地 file, directory 或者 literal value 创建一个 secret

1
kubectl create secret generic db-user-pass --from-file=./username.txt --from-file=./password.txt

使用如下指令可以看到创建好的 Secret,重点关注 data 字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@localhost ~]# kubectl get secret db-user-pass -o yaml
apiVersion: v1
data:
password.txt: MWYyZDFlMmU2N2Rm
username.txt: YWRtaW4=
kind: Secret
metadata:
creationTimestamp: "2019-06-25T15:29:41Z"
name: db-user-pass
namespace: default
resourceVersion: "640854"
selfLink: /api/v1/namespaces/default/secrets/db-user-pass
uid: 0cfacdb1-975e-11e9-91bb-080027eea012
type: Opaque

仔细看你会发现,这个 data 字段并不是一开始我们写入的值,我们写入的 password.txt 是 “1f2d1e2e67df”, 写入 username.txt 的是 “admin”。

默认情况下,get 和 describe 命令都不会显示文件的内容。这是为了防止将 secret 中的内容被意外暴露给从终端日志记录中刻意寻找它们的人,每一项值都会经过 base64 编码。

1
2
3
4
5
6
7
8
[root@localhost ~]# echo -n  "admin" | base64
YWRtaW4=
[root@localhost ~]# echo -n "1f2d1e2e67df" | base64
MWYyZDFlMmU2N2Rm

# base64解码
[root@localhost ~]# echo "YWRtaW4=" | base64 --decode
admin

Secret 的使用

Secret 可以作为数据卷被挂载,或作为环境变量暴露出来以供 pod 中的容器使用。它们也可以被系统的其他部分使用,而不直接暴露在 pod 内。例如,它们可以保存凭据,系统的其他部分应该用它来代表您与外部系统进行交互。

Pod 中使用 Secret

Pod 的 volume 中使用:

  1. 创建一个新的 Secret 或者使用已经有的 secret,多个 pod 可以使用同一个 Secret
  2. volumes数组中 spec.volumes[].secret.secretName 必须等于 Secret 对象的名字
  3. spec.containers[].volumeMounts[] 加到需要用到该 secret 的容器中。指定 spec.containers[].volumeMounts[].readOnly = truespec.containers[].volumeMounts[].mountPath 为您想要该 secret 出现的尚未使用的目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
kind: Secret
apiVersion: v1
metadata:
name: db-secret
data:
# admin
username: YWRtaW4=
# 123456
password: MTIzNDU2
---
apiVersion: v1
kind: Pod
metadata:
name: mypod-secret1
spec:
containers:
- name: mypod
image: nginx
volumeMounts:
- name: test-db
mountPath: "/etc/foo"
readOnly: true
volumes:
- name: test-db
secret:
secretName: db-secret

使用如下命令创建这个 pod-secret.yml 文件生成的 pod 和 secret,容器内已经被解码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kubectl apply -f pod-secret.yml

# 进入生成的pod
kubectl exec -ti mypod-secret1 /bin/bash

# 查看 /etc/foo 目录
root@mypod-secret1:/etc/foo# ls
password username

# 输出 username
root@mypod-secret1:/etc/foo# cat username
admin

# 输出password
adminroot@mypod-secret1:/etc/foo# cat password
123456

Secret 的值作为环境变量

这个 pod 的两个环境变量使用我们之前创建的 Secret 的变量值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: Pod
metadata:
name: secret-env-pod
spec:
containers:
- name: mycontainer
image: redis
env:
- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: db-secret
key: username
- name: SECRET_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
restartPolicy: Never

进入 Pod 容器内部查看

1
2
3
4
5
6
7
kubectl exec -ti secret-env-pod /bin/bash

# 查看 Secret 注入的环境变量
root@secret-env-pod:/data# echo $SECRET_USERNAME
admin
root@secret-env-pod:/data# echo $SECRET_PASSWORD
123456

Secret 的自动更新

当已经在 volume 中被消费的 secret 被更新时,被映射的 key 也将被更新。

Kubelet 在周期性同步时检查被挂载的 secret 是不是最新的。但是,它正在使用其基于本地 ttl 的缓存来获取当前的 secret 值。结果是,当 secret 被更新的时刻到将新的 secret 映射到 pod 的时刻的总延迟可以与 kubelet 中的secret 缓存的 kubelet sync period + ttl 一样长。

经过实验,挂载 Secret 到文件目录的 Pod 中确实能实时更新到,但是使用 Secret 值作为环境变量的 Pod 并未更新。

相关链接

Secret 官方资料