如何进行配置管理
Nginx 有 nginx.conf、Redis 有 redis.conf、MySQL 有 my.cnf … 很多应用都通过配置文件进行管理。在使用 docker 时,我们可以使用如下几种管理配置文件的方式:
- 编写 Dockerfile 时使用
COPY
命令将配置文件打包进镜像中 - 在运行时使用
docker cp
将配置文件拷贝进正在运行的容器 - 使用
docker run -v
将配置目录映射到本机的文件系统中
当然,这几种方法在大规模的集群中的缺点显而易见,不适合自动化运维管理。Kubernetes 还是使用 YAML 语言来定义 API 对象,再组合起来实现动态配置——它们是 ConfigMap 和 Secret,专门用来管理配置信息的两种对象。
提示:以下是本篇文章正文内容,下面案例可供参考
一、ConfigMap 和 Secret 简介
应用程序有很多类别的配置信息,但从数据安全的角度来看可以分成两类:
- 一类是明文配置,也就是不保密,可以任意查询修改,比如服务端口、运行参数、文件路径等。
- 另一类则是机密配置,由于涉及敏感信息需要保密,不能随便查看,比如密码、密钥、证书等。
这两类配置信息本质上都是字符串,只是由于安全性的原因,在存放和使用方面有些差异,所以 Kubernetes 也就定义了两个 API 对象,ConfigMap
用来保存明文配置,Secret
用来保存秘密配置。
二、ConfigMap
首先先从 kubectl create 生成一个 ConfigMap
的模板:
$ kubectl create cm info --dry-run=client -o yaml
ConfigMap
存储的是配置数据,是静态的字符串,并不是容器,所以它们就不需要用 spec
字段来说明运行时的“规格”。
apiVersion: v1
kind: ConfigMap
metadata:
name: info
其实 ConfigMap 存储的数据放在字段 data 中。要生成带有 data 字段的 YAML 模板,我们需要多加一个参数 --from-literal 表示从字面值生成一些数据:
$ kubectl create cm info --from-literal=key=val --dry-run=client -o yaml
注意:因为在 ConfigMap 里的数据都是 Key-Value 结构,所以 --from-literal 参数需要使用 k=v 的形式
然后我们整理一下生成的 YAML 文件,再添加几个键值对:
apiVersion: v1
kind: ConfigMap
metadata:
creationTimestamp: null
name: info
data:
count: '10'
debug: 'on'
path: '/usr/bin'
greeting: |
say hello to kubernetes.
然后使用下面的命令创建 ConfigMap 对象:
$ kubectl apply -f config-map.yml
创建成功后,我们可以用 kubectl get 、kubectl describe 来查看 ConfigMap 的状态:
$ kubectl get cm
NAME DATA AGE
info 4 7s
kube-root-ca.crt 1 7m39s
$ kubectl describe cm info
Name: info
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
count:
----
10
debug:
----
on
greeting:
----
say hello to kubernetes.
path:
----
/usr/bin
BinaryData
====
Events: <none>
现在 ConfigMap 的 Key-Value 信息就已经存入了 etcd 数据库,后续就可以被其他 API 对象使用。
三、Secret
在 Kubernetes 里 Secret 对象又细分出很多类,比如:
- 访问私有镜像仓库的认证信息
- 身份识别的凭证信息
- HTTPS 通信的证书
- 私钥一般的机密信息(格式由用户自行解释)
我们就拿一般机密信息做例子,使用命令行生成一个 YAML 文件:
$ kubectl create secret generic user --from-literal=name=root --from-literal=pwd=12345678 --from-literal=db=postgresql --dry-run=client -o yaml
apiVersion: v1
kind: Secret
metadata:
creationTimestamp: null
name: user
data:
db: cG9zdGdyZXNxbA== # postgresql
name: cm9vdA== # root
pwd: MTIzNDU2Nzg= # 12345678
Secret
与 ConfigMap
的不同之处就在于,Secret 不让用户直接看到原始数据,起到一定的保密作用。不过它的手法非常简单,只是做了Base64
编码,根本算不上真正的加密,所以我们完全可以绕开 kubectl,自己用 Linux 命令 base64
来对数据编码,然后写入 YAML 文件。
当然我们也可以使用 base64 命令自己编码一串字符,然后写入 YAML 文件:
$ echo -n "12345678" | base64
MTIzNDU2Nzg=
注意:echo -n 表示去除换行符
然后我们创建和查看 Secret 对象:
$ kubectl apply -f secret.yaml
secret/user created
$ kubectl get secret
NAME TYPE DATA AGE
default-token-kshx4 kubernetes.io/service-account-token 3 2m3s
user Opaque 3 12s
$ kubectl describe secret user
Name: user
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
db: 10 bytes
name: 4 bytes
pwd: 8 bytes
这样一个存储敏感信息的 Secret 对象也就创建好了,而且因为它是保密的,使用 kubectl describe 不能直接看到内容,只能看到数据的大小。
四、使用配置
ConfigMap
和 Secret
只是一些存储在 etcd
里的字符串,所以如果想要在运行时产生效果,就必须要以某种方式注入
到 Pod 里,让应用去读取。Kubernetes 有两种途径来注入配置:环境变量和加载文件。
1.以环境变量的方式使用配置
前面在创建Pod
时,我们使用了container
字段中的env
来定义Pod
容器中可见的环境变量:
spec:
containers:
- image: busybox:latest
name: busy
imagePullPolicy: IfNotPresent
env:
- name: os
value: "ubuntu"
- name: debug
value: "on"
command:
- /bin/echo
args:
- "$(os), $(debug)"
我们使用 value
字段为 name
提供了值;其实我们还可以使用valueFrom
字段来从 ConfigMap
或者 Secret
对象里面获取值,实现把配置信息以环境变量的形式注入 Pod
,完成配置与应用的解耦。
我们使用 kubectrl explain 查看一下字段的说明:
$ kubectl explain pod.spec.containers.env.valueFrom
FIELDS:
configMapKeyRef <Object>
Selects a key of a ConfigMap.
fieldRef <Object>
Selects a field of the pod: supports metadata.name, metadata.namespace,
`metadata.labels['<KEY>']`, `metadata.annotations['<KEY>']`, spec.nodeName,
spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.
resourceFieldRef <Object>
Selects a resource of the container: only resources limits and requests
(limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu,
requests.memory and requests.ephemeral-storage) are currently supported.
secretKeyRef <Object>
Selects a key of a secret in the pod's namespace
可以看到 valueFrom
环境变量值的来源可以是 configMapKeyRef
或者 secretKeyRef
(中间的 fieldRef
和 resourceFieldRef
可以先不管)。然后我们需要进一步指定 ConfigMap/Secret
的 name
和它的 key
,这里的 name
是指 API 对象的名称。
下面是一个Pod YAML 的示例:
apiVersion: v1
kind: Pod
metadata:
name: env-pod
spec:
containers:
- env:
- name: COUNT
valueFrom:
configMapKeyRef:
name: info
key: count
- name: GREETING
valueFrom:
configMapKeyRef:
name: info
key: greeting
- name: USERNAME
valueFrom:
secretKeyRef:
name: user
key: name
- name: PASSWORD
valueFrom:
secretKeyRef:
name: user
key: pwd
image: busybox
name: busy
imagePullPolicy: IfNotPresent
command: ["/bin/sleep", "300"]
我们先启动这个 Pod:
$ kubectl apply -f env-pod.yaml
pod/env-pod created
然后我们使用 kubectl exec 命令查看环境变量:
$ kubectl exec env-pod -- /bin/ash -c "env"
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.96.0.1:443
HOSTNAME=env-pod
SHLVL=1
HOME=/root
GREETING=say hello to kubernetes.
USERNAME=root
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
COUNT=10
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_SERVICE_HOST=10.96.0.1
PWD=/
PASSWORD=12345678
可以看到环境变量都注入到 Pod 中了。
我们也可以使用交互式命令行查看环境变量:
$ kubectl apply -f env-pod.yml
$ kubectl exec -it env-pod -- sh
echo $COUNT
echo $GREETING
echo $USERNAME $PASSWORD
2. 以 Volume 的方式使用配置
Kubernetes 为 Pod 定义了一个 Volume
的概念,可以翻译成是“存储卷”。如果把 Pod 理解成是一个虚拟机,那么 Volume
就相当于是虚拟机里的磁盘。我们可以为 Pod 挂载(mount)多个 Volume
,里面存放供 Pod 访问的数据,这种方式有点类似 docker run -v
,虽然用法复杂了一些,但功能也相应强大一些。
在 Pod 里挂载 Volume
很容易,只需要在 spec
里增加一个 volumes
字段,然后再定义卷的名字和引用的 ConfigMap/Secret
就可以了。要注意的是 Volume
属于 Pod
,不属于容器,所以它和字段containers
是同级的,都属于 spec
。
下面定义了两个 Volume,分别引用 ConfigMap 和 Secret,名字是 cm-vol 和 sec-vol :
spec:
volumes:
- name: cm-vol
configMap:
name: info
- name: sec-vol
secret:
secretName: user
有了 Volume
的定义之后,就可以在容器里挂载了,这要用到 volumeMounts
字段,正如它的字面含义,可以把定义好的 Volume
挂载到容器里的某个路径下,所以需要在里面用 mountPath name
明确地指定挂载路径和 Volume
的名字。
你可以看到,挂载 Volume 的方式和环境变量又不太相同。环境变量是直接引用了 ConfigMap/Secret,而 Volume 又多加了一个环节,需要先用 Volume 引用 ConfigMap/Secret,然后在容器里挂载 Volume。
这种方式的好处在于:以 Volume 的概念统一抽象了所有的存储,不仅现在支持 ConfigMap/Secret
,以后还能够支持临时卷、持久卷、动态卷、快照卷等许多形式的存储。
这里是完整的 YAML 描述文件:
apiVersion: v1
kind: Pod
metadata:
name: vol-pod
spec:
volumes:
- name: cm-vol
configMap:
name: info
- name: sec-vol
secret:
secretName: user
containers:
- volumeMounts:
- mountPath: /tmp/cm-items
name: cm-vol
- mountPath: /tmp/sec-items
name: sec-vol
image: busybox
name: busy
imagePullPolicy: IfNotPresent
command: ["/bin/sleep", "300"]
我们创建这个 Pod 然后进入 Pod 查看文件:
$ kubectl apply -f vol-pod.yml
pod/vol-pod created
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
vol-pod 1/1 Running 0 48s
$ kubectl exec -it vol-pod -- /bin/ash
ConfigMap
和 Secret
都变成了目录的形式,而它们里面的 Key-Value
变成了一个个的文件,而文件名就是 Key。
环境变量用法简单,更适合存放简短的字符串,而 Volume 更适合存放大数据量的配置文件,在 Pod 里加载成文件后让应用直接读取使用。
总结
- ConfigMap 记录了一些 Key-Value 格式的字符串数据,描述字段是“data”,不是“spec”。
- Secret 与 ConfigMap 很类似,也使用“data”保存字符串数据,但它要求数据必须是 Base64 编码,起到一定的保密效果。
- 在 Pod 的“env.valueFrom”字段中可以引用 ConfigMap 和 Secret,把它们变成应用可以访问的环境变量。
- 在 Pod 的“spec.volumes”字段中可以引用 ConfigMap 和 Secret,把它们变成存储卷,然后在“spec.containers.volumeMounts”字段中加载成文件的形式。
- ConfigMap 和 Secret 对存储数据的大小没有限制,但小数据用环境变量比较适合,大数据应该用存储卷,可根据具体场景灵活应用。