一文弄懂ConfigMap在k8s中的各种玩法以及应用场景
前言在K8S的某些场景下,pod需要依赖各种配置以及配置文件,这些配置不能写死在镜像中,否则会影响到镜像的扩展性。此时ConfigMap作为K8S中提供的配置管理组件登场了。ConfigMap可以将环境变量配置信息和容器镜像解耦,便于应用配置的修改。下文就ConfigMap的使用方法以及使用场景进行下总结,帮助大家在不同场景下能正确的使用ConfigMap。正文上图就是整个ConfigMap的生命
前言
在K8S的某些场景下,pod需要依赖各种配置以及配置文件,这些配置不能写死在镜像中,否则会影响到镜像的扩展性。此时ConfigMap作为K8S中提供的配置管理组件登场了。ConfigMap可以将环境变量配置信息和容器镜像解耦,便于应用配置的修改。
下文就ConfigMap的使用方法以及使用场景进行下总结,帮助大家在不同场景下能正确的使用ConfigMap。
正文
上图就是整个ConfigMap的生命周期以及使用方式,下面结合图进行详细讲解。
ConfigMap的生命周期
ConfigMap通过yaml文件实例化成对象,然后存储在K8S的etcd系统中。
etcd和很多人比较熟悉的zookeeper基本上有异曲同工之妙,都是分布式协调系统中的翘楚。而两者最主要的区别就是etcd的分布式一致性算法是基于raft实现,而zookeeper是基于paxos算法实现的;如果是JAVA应用则会选择zookeeper,go应用当仁不让的会选择etcd。
由于K8S是基于go实现的,所以选择etcd作为元数据以及部分配置数据的存储就显得理所应当了。这样K8S就不需要关注数据一致性以及选主相关问题,专心实现业务方面的问题即可。
另外,ConfigMap可以在某些场景下实现热更新,即ConfigMap发生变化后,应用该ConfigMap的Pod可以自动感知并应用新的配置。具体的实现逻辑是在pod创建或者重新部署时通过kubelet创建对该Pod使用的ConfigMap进行监控,定期进行ConfigMap比对,如果ConfigMap发生变更,则拉取最新的ConfigMap给对应的Pod使用。
但是ConfigMap的热更新受到ConfigMap使用方式的限制,并非所有的使用方式都支持ConfigMap的热更新,这个问题下面具体讲解。
当然上面涉及到的ConfigMap的生命周期和使用方法都需要K8S的API Server以及kubelet通力合作完成的,这两个服务也是K8S最重要的两个服务,后续值得好好研究下。
下面提供一个ConfigMap创建的例子:
apiVersion: v1
kind: ConfigMap
metadata:
name: k8s-config
data:
key1: hello
key2: k8s
这是一个最简单的ConfigMap,定义了名称为k8s-config的ConfigMap,里面有两个KV,在后续的pod中可以通过ConfigMap name的引用,通过key得到对应的value。
ConfigMap的使用方式
ConfigMap的使用方式主要分为三种:volume挂载、环境变量注入以及环境变量实例化。这三种方式都是通过应用的yaml文件中配置ConfigMap引用而实现的,下面通过例子来具体讲解。
volume挂载
这种使用方式的场景是当一个镜像需要依赖某些配置文件,而这些配置文件中有某些变量需要根据不同的环境进行变更时,为了提高镜像的复用性,就会将这些配置文件抽象出来作成ConfigMap。在镜像部署时,通过ConfigMap依赖将配置文件挂载到Pod中的固定路径下供业务系统使用。
看下面的例子:
首先是ConfigMap创建的yaml文件:
apiVersion: v1
kind: ConfigMap
metadata:
name: test-conf
namespace: test
data:
test-application-remote.properties: |-
XXXX=xxxx
XXXX=xxxx
此处省略data部分,大家理解即可。
然后在应用的yaml文件中引用ConfigMap:
volumes:
- name: "test-log-config" #创建volume的名称
configMap:
name: "test-conf" #引用configMap卷
items:
- key: "log4j2.xml" #根据key获取configMap指定的配置
path: "log4j2.xml"
- name: "test-init-config"
configMap:
name: "test-conf"
items:
- key: "init-config.json" #根据key获取configMap指定的配置
path: "init-config.json"
- name: "test-application"
configMap:
name: "test-conf"
items:
- key: "test-application-remote.properties" #根据key获取configMap指定的配置
path: "application-remote.properties"
在volumes配置中通过对configMap的name进行匹配,然后根据key字段取出对应的配置,并绑定到对印的path上。
接下来就是使用volumeMounts属性对volume进行mount,当Pod实例化以后会将配置文件生成到具体路径供业务系统使用:
volumeMounts:
- name: "test-application"
mountPath: "/home/test/app/config/application-remote.properties"
subPath: "application-remote.properties"
- name: "test-init-config"
mountPath: "/home/test/app/config/init-config.json"
subPath: "init-config.json"
- name: "test-log-config"
mountPath: "/home/test/app/config/log4j2.xml"
subPath: "log4j2.xml"
此处的name需要和上文volumes的name进行匹配,进行配置与Pod中路径的关联,关联成功即可以将配置下发到Pod中对应的路径上。
此处细心的小伙伴可能发现了subPath这个属性,如果说mountPath是挂载路径的话,那subPath有何作用呢?
这里主要有两个原因:
-
如果使用mountPath进行配置下发的话,下发后mountPath下所有的文件会被覆盖,只剩下下发的配置文件,这在很多场景下是很不合适和合理的。
-
由于K8S不允许同一个目录被mount多次,所以针对很多配置文件在一个目录下的场景,mountPath显然无法满足需求。
鉴于上述两个需求,K8S提供了subPath属性,即在当前路径下提供子路径来规避既有机制。这样既可以避免路径下文件被覆盖的问题,又可以解决单路径多配置文件的问题。
看到这里大家可能有疑问,感觉subPath的使用场景可以覆盖mountPath这种场景,那为什么还会保留mountPath这种方式呢?
原因是上面那个ConfigMap热更新的问题,在ConfigMap所有使用方式中,只有volume mount这种方式可以实现ConfigMap的热更新,所以存在即是合理,总是有需求和应用场景的。
通过ConfigMap的这种使用方式,可以将配置文件灵活的映射到Pod中的一个路径供Pod中的业务系统使用,在Pod扩展或者重启的时候也能快速的进行复制和部署,所以,这种方式比较适合于应用系统等无状态服务的部署。
环境变量注入
这种方式比较简单,其实就是在应用的yaml中将ConfigMap中的配置数据通过ENV的方式注入到Pod中供Pod直接使用。这种比较简单,直接看看如何调用即可。下面给个例子:
env:
- name: KEY1
valueFrom:
configMapKeyRef:
name: k8s-config
key: key1
- name: KEY2
valueFrom:
configMapKeyRef:
name: k8s-config
key: key2
环境变量实例化
现在试想一个场景,如果你需要配置一个elasticsearch的有状态服务,需要进行节点的横向扩容,此时肯定需要一个ConfigMap来进行节点配置文件elasticsearch.yml的实例化。但是由于每个节点配置文件上都有各自定制化如IP等私有化配置,此时如果使用常规的ConfigMap处理方式,可能一个节点就需要一个ConfigMap来管理。这在集群规模较小的时候尚可以接受,但是如果集群规模很大,那么维护ConfigMap也是一个负担很重的工作量。那有没有办法解决这种场景遇到的问题呢?
答案是显然的,针对这种场景,我们可以通过ConfigMap模板的方式来进行支持,将ConfigMap中elasticsearch.yml的变量部分通过环境变量的方式注入进来,每次在通过yaml调用ConfigMap时对ConfigMap进行实例化,然后再进行配置文件volume的挂载。
这样就将变化的部分交给了需要扩展应用的yaml文件来管理,在整个配置文件变更较少的场景下,这种方式的效率比较高,比较适合分布式应用或者分布式数据库这种需要横向扩展的场景。
下面给出一个例子作为参考:
apiVersion: v1
kind: ConfigMap
metadata:
name: es-conf
namespace: test
data:
elasticsearch.yml: |-
cluster:
name: ${CLUSTER_NAME}
node:
master: ${NODE_MASTER}
data: ${NODE_DATA}
name: ${NODE_NAME}
ingest: ${NODE_INGEST}
max_local_storage_nodes: ${MAX_LOCAL_STORAGE_NODES}
network.host: ${NETWORK_HOST}
path:
data: /usr/share/elasticsearch/data
logs: /usr/share/elasticsearch/logs
bootstrap:
memory_lock: ${MEMORY_LOCK}
http:
enabled: ${HTTP_ENABLE}
compression: true
cors:
enabled: ${HTTP_CORS_ENABLE}
allow-origin: ${HTTP_CORS_ALLOW_ORIGIN}
discovery:
zen:
ping.unicast.hosts: ${DISCOVERY_SERVICE}
minimum_master_nodes: ${NUMBER_OF_MASTERS}
这就是ConfigMap的模板文件,里面${}中的数据都需要在应用的yaml中进行实例化。
下面就是应用yaml实例化的例子:
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: CLUSTER_NAME
value: test_es
- name: NUMBER_OF_MASTERS
value: "2"
- name: HTTP_CORS_ALLOW_ORIGIN
value: "*"
- name: HTTP_CORS_ENABLE
value: "false"
- name: NETWORK_HOST
value: "0.0.0.0"
- name: MAX_LOCAL_STORAGE_NODES
value: "1"
- name: NODE_MASTER
value: "true"
- name: NODE_INGEST
value: "false"
- name: NODE_DATA
value: "false"
- name: HTTP_ENABLE
value: "true"
- name: ES_JAVA_OPTS
value: -Xms2048m -Xmx2048m
- name: MEMORY_LOCK
value: "false"
- name: DISCOVERY_SERVICE
value: "elasticsearch-discovery"
- name: TZ
value: "Asia/Shanghai"
通过env的方式将配置映射到ConfigMap中,然后后续再通过第一种volume挂载的方式进行正常的配置文件下发即可。
总结
上面就是ConfigMap相关的内容,在讲解ConfigMap的同时,也将其他相关的K8S机制一并讲解了下,通过这种方式帮助大家理顺K8S的工作流程以及概念体系,帮助大家更好更快捷的入门K8S。
另外,笔者长期关注大数据通用技术,通用原理以及NOSQL数据库的技术架构以及使用。如果大家感觉笔者写的还不错,麻烦大家多多点赞和分享转发,也许你的朋友也喜欢。
最后挂个公众号二维码,公众号的文章是最新的,CSDN的会有些滞后,想追更的朋友欢迎大家关注,谢谢大家支持。
更多推荐
所有评论(0)