前言

在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有何作用呢?

这里主要有两个原因:

  1. 如果使用mountPath进行配置下发的话,下发后mountPath下所有的文件会被覆盖,只剩下下发的配置文件,这在很多场景下是很不合适和合理的。

  2. 由于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的会有些滞后,想追更的朋友欢迎大家关注,谢谢大家支持。 

 

 

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐