两种特殊的存储卷:configMap和secret
|
|
这两种存储卷多数情况下,它们的目的不是给Pod提供存储空间来用的,而是给我们的管理员或用户提供了从集群外部向Pod内部的应用程序注入配置信息的方式。
第一点,比如在集群上,有个名称空间,在这个名称空间之上,有个Pod在运行。我们启动Pod是基于镜像来运行容器的。镜像做好后,镜像内的应用程序启动时要读配置文件也是镜像内的配置文件,因此镜像内的应用启动时到底应该运行在什么配置下,在做镜像之前就确定了。做完镜像后就改不了了。除非用entrypoint脚本,去接受用户启动容器时传一些环境变量进来。把环境变量中的数据给他写到或替换到配置文件中去从而使得应用程序在启动之前就获得一个新的配置文件,而后得到新配置。否则会有个问题,做好了的镜像,它可能为了适用不同的部署方式或者不同的环境,需要做很多个镜像。比如开发、测试、线上,三套环境都要部署tomcat,但是使用的内存大小不一样,很显然我们把配置写在配置文件之中,三个环境不可能都适用的,所以不得不做三个镜像,这很麻烦。所以后来想了其他办法来解决这个配置信息的注入。在k8s上也是如此,本来镜像都是做好的,一个镜像应付不了那么多场景,就不得不做多个镜像,这个特别难受,所以能够从镜像外部向镜像内部启动为容器时给它添加配置信息,是很重要的。
第二点,假如现在部署了20个Pod,里面都是tomcat,就算我们能够从外部通过环境变量注入进去,但是运行了一段时间以后,需要改配置参数,怎么改?重新替换环境变量的值。但是entrypoint已经生过效了。你可能还要触发entrypoint脚本重新执行一次,甚至是Pod不用动,把里面容器重启下。这个过程其实是比较麻烦的,而且还得一个一个去操作,一个一个重新改环境变量的值。应用程序运行时,有一种简单地方式来实现这个功能,可以使用配置中心。把配置放在配置中心,tomcat启动时加载的文件,文件看起来存在,可能并没有内容,文件来自于配置中心,动态从配置中心拉过来,然后启动tomcat。将来在配置中心修改配置,并通知给进程,让这些进程重载配置文件。k8s也面临一样的场景,这20个Pod需要更新的时候,改配置只能通过改镜像的话,代价太大了。因此我们不把配置写死在镜像中,而是引入一个新的资源,这个新的资源甚至是整个k8s之上的一等公民,这个资源就叫configMap,它里面放的是配置信息。随后启动的每一个Pod时,都可以共享使用同一个configMap资源。这个资源对象可以当存储卷来使用,也可以从中基于环境变量的方式从里面获取到一些数据传递给环境变量从而注入到容器中使用。
总结下来,有两种较为合适的方式去解决以上出现的两个场景:
- 1、假如现在要启动一个Pod,这个Pod启动时可以把configmap资源关联到当前Pod上来,从中读一个数据,传递给Pod内容器中的一个变量,仍然是变量注入的方式来给容器传递配置信息。
- 2、把每一个configmap当一个存储卷,直接挂载到容器中的某个目录上,这个目录恰好是应用程序读取配置信息的文件路径。而且支持动态修改,意思是说如果configmap中内容修改下,改完后会通知给所有的Pod,让Pod里面的应用重载,当然有些应用可能不能自动重载,你需要手动触发下重载。因此configmap扮演了k8s之上的配置中心的功能。
所以要想传一个配置文件给Pod中的容器,可以把配置文件打包在configmap中,让Pod启动时把configmap挂载到配置文件目录下,就从这下面读配置文件就能ok了。但是configmap是明文存数据的,因此在里面放的各种配置文件,不能包含敏感数据,比如连mysql,不能放root账号。跟configmap有些同样功能的另外一个标准k8s资源叫secret。secret的功能和configmap功能一样,所不同的是其中的内容不是明文存放的,而是base64编码存放的。不是加密,很容易解开,但是至少对很多人来讲,是不可为的任务了。
先做一个总结:配置容器化应用的方式:
- 1、自定义命令行参数;
args: [] - 2、把配置文件直接放进镜像;
不建议,这是在用dockerfile制作镜像时就把配置文件放进去了 - 3、环境变量
(1) Cloud Native的应用程序一般可直接通过环境变量加载配置;
(2) 通过entrypoint脚本来预处理变量为配置文件中的配置信息,通过SET命令之类的替换到配置文件中去; - 4、存储卷
Pod资源获取环境变量的方式:env、envFrom
|
|
- valueFrom: 数据来自引用另外一个变量或另外一个对象
|
|
- configMapKeyRef: 在configMap中的某个key的vlaue,configMap也是键值对。
- fieldRef: 某个字段,这个字段很可能是Pod自身的字段,比如像metadata.labels, metadata.annotations, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP.
- resourceFieldRef: 资源需求和资源算法,后面讲到调度器算法时在解释
- secretKeyRef: 引用secretKey
configMap
简称cm。
configMap作用:让配置信息与镜像文件解耦。从而增强了应用的可移植性以及可复用性。configMap就是一系列配置数据的集合,而这些数据将来可以注入到Pod中的容器中所使用。
在configMap中,所有的配置信息都保存为键值格式。比如name: zhangshan。但是这种格式保存的数据量很小,我们也可以保存为比较复杂的格式。比如键名叫server.conf, 值是曾经在Nginx上配置时传递的这个server的整个内容。可以是整个文件的所有内容。因为value的长度是没有限制的。
configMap是一等公民,属于名称空间的资源,但是它的字段比较独特,和之前所学的Pod、Service等都不一样。没有spec,因为它没有那么复杂的结构,不需要嵌套众多状态描述的属性或字段。只需要给数据就可以了,所以有个data字段,是个map类型的。也可以以二进制形式给数据。
data和binaryData,一般只使用其中一种。
定义configMap时,可以使用yaml文件,也可以在命令行使用kubectl create configmap来定义。如果需要长期使用,可以定义成yaml文件来保存。
configMap示例
|
|
【示例1】:把键值做成configMap。
可以看出,直接就可以看到configMap中的内容了。
【示例2】:把一个文件内容做成configMap。
|
|
configmap两种方式注入容器中:
- 1.在容器中使用env,使用envFrom来获取。
如果通过环境变量来注入的话,必须确保环境变量传递进去能被容器作为配置信息使用,否则传过去没用。而两种方式上面提了,cloudnative,可直接通过环境变量加载配置,要不就是通过entrypoint脚本来处理。所以不是云环境,得在容器里写了entrypoint脚本才能使用传递进来的环境变量。 - 2.存储卷方式
【示例3】:使用env来传递环境变量
- optional: 如果在容器中定义了从configmap引用,万一没有这个configmap,或者configmap中没有这个key,pod就启动不了了,因为引用数据不存在,会出错的。但是我们可以optional这一项启用起来,optional: true。意思是可选的,就算没有也没关系,如果不写为true,就表示must be defined。
|
|
|
|
已经传进来了。
现在修改configmap中的key的value,看Pod中容器中的变量是否会及时生效。
查看Pod容器中变量是否发生变化:
实践证明修改 ConfigMap 无法更新容器中已注入的环境变量信息。
当我们使用环境变量注入时,只在容器启动时有效。因为它是Pod创建时获取的。如果通过存储卷的方式来获取,是可以实时更新的。见下面的实例
【示例4】:使用存储卷来传递环境变量。
configmap无论创建时是不是文件,比如在命令行指了个key,指了个value,我们可以把整个configmap当存储卷挂载到Pod上,然后yaml文件中指定容器把存储卷挂载到自己的某个目录下(挂载点不存在会自动创建)。在这个挂载目录下,每一个key就转换成一个文件名,那个key的value就转换成文件内容。以刚才在命令行创建的configmap nginx-config为例。
|
|
现在去修改下cm中的key的value:
在容器中查看这个变量:
需要稍微等下,因为这个修改要同步到apiserver中,apiserver在同步到Pod上。时间取决于apiserver同步速度。这里实测大概10秒才能同步更新。
其实这两个文件是链接文件,不是同步进来的,这两个文件本来就是访问原来的文件。链接改成指向新的configmap版本。是个独特路径,映射n级后能到configmap存储卷上,所以只要configmap源内容改了,就会同步改掉。
【示例5】:演示真正注入Nginx配置文件。
|
|
如果这个挂载点指定的目录在原来容器中有内容,会被挂载的内容覆盖掉。
接下来配置hosts解析:
访问:
现在改下外部配置,改下监听端口:
还是等了10来秒才生效
访问:
但是刚才挂载的时候,只要configmap有的键,都挂载进去了。如果以存储卷挂载访问configmap时不希望把所有的key都挂载进来,只希望挂载部分key,这也是可以的。
- itmes: 把name字段指定的configMap中的哪些字段挂载进来
|
|
列出键的时候,不光要列出键的名字,还要指明真正的文件名(path),key已经不是默认作为文件名了,是required。文件名和key名一样也可以,但是你需要指定。文件内容就是key所对应的value。挂载的文件可以同时指定文件权限(mode),path不能使用..,因为..有特殊作用,路径映射方式。
Secret
configMap存数据是明文的,Secret是base64编码的,但是使用Secret会麻烦一些。只有敏感数据才建议使用secret存放。私钥和证书最好是放在secret中,还有连接mysql时的密码。
对于secret而言,它有三种类型:
- 1.generic: Create a secret from a local file, directory or literal value
通用的secret,就是里面保存一些密码数据 - 2.tls: Create a TLS secret
私钥和证书。 - 3.docker-registry: Create a secret for use with a Docker registry
保存docker-registry的认证信息。每一个Node在运行Pod之前,它得把Pod所依赖的镜像拉取到本地,需要到某个registry上拉取。如果是去某个私有registry拉取,里面镜像也是私有的,必须要输入账号密码才能访问,账号密码放哪?也就意味着当前的Node要能够自动地认证,并从registry拉取镜像。否则Pod创建肯定是失败。现在是kubelet会认证到私有仓库,然后拉下来进行。12345678[root@spark32 configmap]# kubectl explain pods.specimagePullSecrets <[]Object>ImagePullSecrets is an optional list of references to secrets in the samenamespace to use for pulling any of the images used by this PodSpec. Ifspecified, these secrets will be passed to individual pullerimplementations for them to use. For example, in the case of docker, onlyDockerConfig type secrets are honored. More info:https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod
创建secret命令:
|
|
【示例1】:
- Opaque: 模糊类型,就是generic类型
|
|
这么看是看不到secret内容的。
【示例2】:env方式引用secret中的key
可见,变量的值是解码以后注入进去的。