应用程序启动过程中通常需要传递参数,当参数较多时会将参数保存到配置文件中,并在配置文件中设置默认参数,当程序启动时再从配置文件中读取配置。
k8s中的应用当然也有这种需求,一种方式是将基础的配置文件直接打包到镜像中,然后在启动命令中设置某些需要更改的配置项,但是,既然将配置项放到配置文件中,当然是希望能够方便地修改配置,如果每次修改配置都需要修改命令行参数,那配置文件有何意义?如果修改配置文件的默认配置还需要重新打包镜像,那配置就没作用了,就跟直接将配置项写死在程序中一样。因此,镜像中不应该包含配置文件,而是只有应用程序。
为了能够让应用程序从统一的地方加载配置文件,同时,能够一次性修改Deployment或者DaemonSet的所有Pod的配置项,k8s提供了两种保存配置的方式,ConfigMap和Secret(ConfigMap是直接以键值对的方式保存配置项,Secret则是会对配置项的值进行加密存储),Pod可以将它们作为卷挂载到容器中。
ConfigMap的使用包含ConfigMap的创建和挂载。
ConfigMap的创建可以通过命令行和yaml文件。
通过命令行创建ConfigMap:
kubectl create configmap app-config --from-literal=log_level=debug --from-literal=user=admin
上面的命令会创建名为app-config的ConfigMap,里面有两个配置项:user和log_level,值分别是admin和debug。最终在ConfigMap中的配置就是:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
log_level: debug
user: admin
两个配置项以kv的方式保存在configmap.data。
一般来说,很多程序的配置项比较多,特别是生产环境的配置,因此,实际工作中一般不太可能直接用上面的命令创建ConfigMap,通常都是使用文件或者目录的方式创建:
kubectl create configmap app-config --from-file=a.conf --from-file=config_item=b.conf --from-file=config_dir
上面演示了三种从文件或者文件夹读取配置的方式:
可以发现,用这种从文件中读取配置创建ConfigMap的方式默认情况下会使用文件名作为键,文件内容作为值,当然,也可以自定义键名,这种方式创建的ConfigMap的配置如下:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
a.conf: |
a=b
c=d
e=f
config_item: |
m=n
p=q
m.conf: |
l=m
n.conf: |
d=e
在k8s中,创建ConfigMap最常用的肯定还是yaml的方式,根据上面的ConfigMap的yaml结果也可以知道ConfigMap中的配置方式。
唯一需要说明的就是多行文本的表示:上例中,由于配置文件有多行,如果不加后面的|
,最终的ConfigMap中的配置项的值会将多行变成一行,这当然不是我们预期的,因此,为了让配置文件能够以原本的样子保存到ConfigMap中,一般都可以带上|
,如果真的要让配置文件内容与ConfigMap一模一样,则可以使用|+
,前面的|
在多个空行的情况下会合并成一个空行。
创建完ConfigMap,下一步就是将ConfigMap挂载给Pod使用。
最简单的方式就是直接将ConfigMap挂载到容器中,其中的键变成文件名,值就成为文件的内容:
apiVersion: v1
kind: Pod
metadata:
name: test
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: my-config
mountPath: /opt
volumes:
- name: my-config
configMap:
name: app-config
Pod将app-config这个ConfigMap命名为my-config,然后将my-config这个卷挂载到nginx容器的/opt目录,创建该Pod后,Pod的nginx容器的/opt目录下就有4个配置文件:a.conf、config-item、m.conf、n.conf,容器的进程在启动后就可以读取这些配置文件。
当然,有时候事情并不是如此美好,k8s中也提供了一些选项用以应对一些特殊场景。
场景一:不允许修改ConfigMap
有时候ConfigMap的值是固定的,需要防止ConfigMap中的值被意外修改,这时候可以设置configmap.immutable为true,该选项只在1.21及以上的版本可用。
将ConfigMap设置为不能修改的好处除了防止被意外修改,还能够降低apiserver的压力,因为系统会关闭对无法修改的ConfigMap的监视操作。
场景二:可选的ConfigMap
默认情况下,当挂载某个不存在的ConfigMap时,Pod会长时间处于ContainerCreating状态,然后启动失败。有时候Pod会弱依赖其他的ConfigMap,也就是说,如果ConfigMap存在则进行挂载,如果不存在,则忽略,此时可以设置pod.spec.volumes.configMap.optional选项为true,那么在ConfigMap不存在的情况下,Pod会启动成功。
场景三:只挂载ConfigMap中的单个配置项
常规场景下是将ConfigMap中的键值对以目录中的文件方式挂载到目标目录,但是有时候只需要挂载某个配置项,这时候可以使用pod.spec.containers.volumeMounts.subPath选项,将该配置设置为ConfigMap中的某个键:
apiVersion: v1
kind: Pod
metadata:
name: test
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: my-config
mountPath: /opt/abc.conf
subPath: a.conf
volumes:
- name: my-config
configMap:
name: app-config
这里就将app-config这个ConfigMap中的a.conf挂载到容器中的/opt/abc.conf文件。
但是使用subPath有个问题:如果后续修改了Configmap的内容,容器重启时会失败。因此,一般不建议使用subPath。
ConfigMap中的配置是以明文形式存储,为了增强数据的安全性,k8s提供了Secret。
Secret有三种类型:
跟ConfigMap类似,Secret也有命令行和yaml文件的方式创建。
可以发现,Opaque类型的Secret的创建命令跟ConfigMap很像,就是多了个类型字段,类型设置为generic,同时,通过base64 -d
就可以直接解码成原来的值,因此,实项目中,如果需要高度安全,Opaque类型的Secret肯定还是不合适的。
对于文件类型的创建命令以及挂载到容器中的方式跟ConfigMap没有区别,这里不再过多介绍。
kubernetes.io/service-account-token类型的Secret:
创建ServiceAccount时,会创建对应的Secret,其中就包含证书和token,当pod.spec.serviceAccount设置为某个ServiceAccount时,Pod就会将该ServiceAccount对应的Secret挂载到Pod的/run/secrets/kubernetes.io/serviceaccount
目录。
kubernetes.io/dockerconfigjson类型的Secret:
当容器运行时需要拉去镜像启动容器时,如果需要认证信息,就需要创建这种类型的Secret。
假设用户可以用admin/pwd登陆到内部的registry.oa.com镜像仓库,如果没有指定pod.spec.imagePullSecrets,而直接拉取不到镜像时,pod就会启动失败。
kubectl create secret docker-registry my-registry --docker-server=registry.oa.com --docker-username=admin --docker-password=pwd
此时查看Secret可以发现,k8s将用户名和密码变成了镜像仓库的登陆token,然后就可以将pod.spec.imagePullSecrets设置为my-registry。
ConfigMap和Secret都是用于存储应用的配置,但是Secret用于存储特定场景下的安全性较高的配置。