想让业务更顺利地运行,有一个问题不容忽视,那就是应用的配置管理。Kubernetes里专门用来管理配置信息的两种对象:ConfigMap和Secret,使用它们来灵活地配置、定制我们的应用。
ConfigMap/Secret
从数据安全的角度来看可以分成两类
- 一类是明文配置,也就是不保密,可以任意查询修改,比如服务端口、运行参数、文件路径等等。
- 另一类则是机密配置,由于涉及敏感信息需要保密,不能随便查看,比如密码、秘钥、证书等等。
这两类配置信息本质上都是字符串,只是由于安全性的原因,在存放和使用方面有些差异,所以Kubernetes也就定义了两个API对象,ConfigMap用来保存明文配置,Secret用来保存密码配置。
ConfigMap
创建样板
export out="--dry-run=client -o yaml"
kubectl create cm info $out
得到的样板文件大概是这个样子
apiVersion: v1
kind: ConfigMap
metadata:
name: info
这里只有apiVersion、kind、metadata三个字段,既然ConfigMap要存储数据,我们就需要用另一个含义更明确的字段“data”
要生成带有“data”字段的YAML样板,你需要在kubectl create后面多加一个参数--from-literal,表示从字面值生成一些数据:
kubectl create cm info --from-literal=k=v $out
注意,因为在ConfigMap里的数据都是Key-Value结构,所以--from-literal参数需要使用k=v的形式。
修改YAML样板,得到了一个比较完整的ConfigMap对象
apiVersion: v1
kind: ConfigMap
metadata:
name: info
data:
count: '10'
debug: 'on'
path: '/etc/systemd'
greeting: |
say hello to kubernetes.
现在就可以使用kubectl apply把这个YAML交给Kubernetes,让它创建ConfigMap对象,查看等
kubectl apply -f cm.yml
kubectl get cm
kubectl describe cm info
现在ConfigMap的Key-Value信息就已经存入了etcd数据库,后续就可以被其他API对象使用。
Secret
Secret对象又细分出很多类
- 访问私有镜像仓库的认证信息
- 身份识别的凭证信息
- HTTPS通信的证书和私钥
- 一般的机密信息(格式由用户自行解释)
前几种我们现在暂时用不到,所以就只使用最后一种,创建YAML样板的命令是kubectl create secret generic
kubectl create secret generic user --from-literal=name=root $out
得到的Secret对象大概是这个样子
apiVersion: v1
kind: Secret
metadata:
name: user
data:
name: cm9vdA==
“name”值是一串“乱码”,而不是刚才在命令行里写的明文“root”。只是做了Base64编码,根本算不上真正的加密,所以我们完全可以绕开kubectl,自己用Linux小工具“base64”来对数据编码,然后写入YAML文件
echo -n "123456" | base64
MTIzNDU2
必须要加参数-n去掉字符串里隐含的换行符,否则Base64编码出来的字符串就是错误的。
新增两个数据
apiVersion: v1
kind: Secret
metadata:
name: user
data:
name: cm9vdA== # root
pwd: MTIzNDU2 # 123456
db: bXlzcWw= # mysql
操作Secret对象
kubectl apply -f secret.yml
kubectl get secret
kubectl describe secret user
使用kubectl describe不能直接看到Secret内容,只能看到数据的大小。
如何使用ConfigMap和Secret
以某种方式“注入”到Pod里,让应用去读取。两种途径:环境变量和加载文件。
以环境变量的方式使用ConfigMap/Secret
Pod定义的时候,描述容器的字段“containers”里有一个“env”,它定义了Pod里容器能够看到的环境变量。
当时我们只使用了简单的“value”,把环境变量的值写“死”在了YAML里,实际上它还可以使用另一个“valueFrom”字段,从ConfigMap或者Secret对象里获取值,这样就实现了把配置信息以环境变量的形式注入进Pod,也就是配置与应用的解耦。
使用kubectl explain对它的说明
kubectl explain pod.spec.containers.env.valueFrom
>>>
KIND: Pod
VERSION: v1
RESOURCE: valueFrom <Object>
DESCRIPTION:
Source for the environment variable's value. Cannot be used if value is not
empty.
EnvVarSource represents a source for the value of an EnvVar.
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”,然后你要再进一步指定应用的ConfigMap/Secret的“name”和它里面的“key”,要当心的是这个“name”字段是API对象的名字,而不是Key-Value的名字。
看一下例子
apiVersion: v1
kind: Pod
metadata:
name: env-pod
spec:
container:
- 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: name
image: busybox
name: busy
imagePullPolicy: IfNotPresent
command: ["/bin/sleep", "300"]
Pod与ConfigMap、Secret的“松耦合”关系,它们不是直接嵌套包含,而是使用“KeyRef”字段间接引用对象,这样,同一段配置信息就可以在不同的对象之间共享。
我们使用kubectl apply创建Pod,再用kubectl exec进入Pod,验证环境变量是否生效。
kubeclt apply -f env-pod.yml
kubeclt exec -it env-pod -- sh
echo $COUNT
echo $GREETING
echo $USERNAME $PASSWORD
以Volume的方式使用ConfigMap/Secret
为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的名字。
containers:
- volumeMounts:
- mountPath: /tmp/cm-items
name: cm-vol
- mountPath: /tmp/sec-items
name: sec-vol
现在看Pod的完整YAML描述列出来,然后使用kubectl apply创建它
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"]
看看配置信息被加载成了什么形式:
kubectl apply -f vol-pod.yml
kubectl get pod
kubectl exec -it vol-pod -- sh
ConfigMap和Secret都变成了目录的形式,而它们里面的Key-Value变成了一个个的文件,而文件名就是Key。
环境变量用法简单,更适合存放简短的字符串,而Volume更适合存放大数据量的配置文件,在Pod里加载成文件后让应用直接读取使用。
其他
-
Linux里对环境变量的命名有限制,不能使用“-”“.”等特殊字符,所以在创建ConfigMap/Secret的时候要留意一下,否则会无法以环境变量的形式注入Pod。
-
可以在“volumes.configMap.items”字段里用“key”“path”为ConfigMap里的每个Key-Value精确地指定加载的路径名,也就是给文件改名。