应用程序配置管理及ConfigMap资源

官方文档:https://kubernetes.io/zh/docs/concepts/configuration/configmap/ 分布式环境中,基于负载,容错等需求的考虑,几乎所有的服务都需要在不同机器上部署不止一个实例。随着程序功能的日益复杂,程序的配置日益增多,而且配置文件的修改频率通常远远大于代码本身,这种情况下,有时仅仅是一个配置内容的改动,就不得不重新进行代码提交到svn/Git、编译、打包、分发上线的流程。部署规则较大的场景中,分发上线工作即繁杂又沉重 究其根本,所有的这些麻烦都是由于配置和代码在管理和发布过程中不加区分所致。配置本身源于代码,是为了提高代码的灵活性而提取出来的一些经常变化的或需要定制的内容,而正是配置的这种天生的变化特征为部署过程带来了不小的麻烦,也最终催生了分布式系统配置管理系统,将配置内容从代码中完全分离出来,及时可靠高效的提供配置访问和更新服务 国内分布式配置中心相关的开源项目有Diamond、Apollo、Qconf、disconf

作为分布式系统的K8S也提供了统一配置管理方案——configmap。K8S基于configmap对象实现了将配置文件从容器镜像中解耦,从而增强了容器应用的可移植性。简单来说,一个ConfigMap对象就是一系列配置数据的集合,这些数据可”注入”到Pod对象中,并被Pod对象里面的容器所使用,注入方式有挂载为存储卷和传递为环境变量两种方式

ConfigMap对象将配置数据以键值对的形式进行存储,这些数据可以在Pod对象中使用或者为系统组件提供配置,例如控制器对象等。不过,无论应用程序如何使用ConfigMap对象中的数据,用户都完全可以通过在不同的环境中创建名称相同但内容不同的ConfigMap对象,从而为不同环境中 同一功能的Pod资源提供不同的配置信息,实现应用与配置的灵活勾兑。

Docker为我们提供的配置管理方式,第一种是通过环境变量来传递变量值,这种方式应对一些比较复杂的场景就显得比较弱了。另外一种是常用的也是docker volume将容器外的配置文件映射到容器内,这两种方式都有优缺点,后种方式更适合我们的系统,因为大多数应用通常从一个或多个配置文件种读取参数。但这种方式也有明显的缺陷,我们必须在目标主机上先创建好对应的配置文件,然后才能映射到容器里。如果是分布式的环境这种情况就变得更为严重,因为无论采用哪种方式,写入或者修改多台服务器上的某个指定文件,并确保这些文件保持一直,都非常难完成。并且,还需要维护一大堆配置文件。所以需要一个几种管理系统来统一管理这些配置,K8S的configMap巧妙的解决了这个问题

首先,把所有的配置项都当作key=value字符串,当然value可以来自于某个文件,比如配置password=123456、user=root、host=172.18.15.114用于表示连接FTP服务器的配置参数,这些选项可以作为Map表中的一个项,然后提供API以方便K8S相关组件或应用CRUD操作这些数据,上述专门用来保存配置参数的Map就是K8S ConfigMap资源对象 解下来,K8S提供了一种内建机制,将存储在ETCD中的ConfigMap通过Volume映射的方式变成目标Pod内的配置文件,不管目标Pod被调度到哪台服务器上,都会完成自动映射。进一步的,如果ConfigMap中的key-value数据被修改,则映射到Pod中的”配置文件”也会被实时反映到目标Pod当中,并且其数据也会自动更新。于是,K8S ConfigMap就成为了分布式系统中使用方法最为简单且对应用吴侵入的配置中心

ConfigMap是一个API对象,让你可以存储其他对象所需要使用的配置。和其他 Kubernetes 对象都有一个 spec 不同的是,ConfigMap 使用 data 块来存储元素(键名)和它们的值。并且,ConfigMap的名字必须是一个合法的DNS子域名

创建ConfigMap对象

K8S的不少资源可以使用kubectl create命令创建,也可以使用清单创建,例如前面讲到的namespace。ConfigMap是另外一个两种创建方式都比较常用的资源。而且,通过使用kubectl create configmap命令,用户可以根据目录,文件或直接值创建ConfigMap对象。命令的语法格式如下

  1. kubectl create configmap <map-name> <data-source>

其中map-name即为configmap对象的名称,而data-source是数据源,它可以通过直接值,文件或目录来获取。无论是哪一种数据源供给方式,它都要转换为ConfigMap对象中的Key-Value数据,其中Key由用户在命令行给出或是文件数据源的文件名,它仅能由字母、数字、连接号和点号组成,而value则是直接值或文件数据源的内容

利用直接值创建ConfigMap对象

kubectl create configmap命令使用--from-literal选项可以命令行直接给出键值对来创建ConfigMap对象,重复使用此选项则可以传递多个键值对。命令格式如下

  1. [root@k8s-master01 pv]# kubectl create configmap configmap-name --from-literal=key-name-1=value-1
  2. configmap/configmap-name created
  3. [root@k8s-master01 pv]#

例如,下面的命令创建special-config时传递了两个键值对

  1. [root@k8s-master01 pv]# kubectl create configmap special-config --from-literal=special.how=very --from-literal=special.type=charm
  2. configmap/special-config created
  3. [root@k8s-master01 pv]#

kubectl get configmap 命令可用于查看创建的configmap对象。如下命令

  1. [root@k8s-master01 pv]# kubectl get cm
  2. NAME DATA AGE
  3. configmap-name 1 3m54s
  4. special-config 2 44s
  5. [root@k8s-master01 pv]#
  6. [root@k8s-master01 pv]# kubectl get cm special-config
  7. NAME DATA AGE
  8. special-config 2 119s
  9. [root@k8s-master01 pv]# kubectl get cm special-config -o yaml
  10. apiVersion: v1
  11. data:
  12. special.how: very
  13. special.type: charm
  14. kind: ConfigMap
  15. metadata:
  16. creationTimestamp: "2020-07-10T09:39:18Z"
  17. name: special-config
  18. namespace: default
  19. resourceVersion: "5488398"
  20. selfLink: /api/v1/namespaces/default/configmaps/special-config
  21. uid: ebfc4f82-897e-4f3f-8f52-297d77c51899
  22. [root@k8s-master01 pv]#

此类方式提供的数据量有限,一般是在仅通过有限的几个数据项即可为Pod资源提供足够的配置信息时使用

基于文件创建

kubectl create configmap命令使用--from-file选项即可基于文件内容来创建ConfigMap对象,它的命令格式如下。可以重复多次使用--from-file选项以传递多个文件内容

  1. kubectl create configmap <configmap_name> --from-file=<path-to-file>

例如,下面的命令可以把事先准备好的Nginx配置文件模板保存于ConfigMap对象www.ilinux.io中

  1. [root@k8s-master01 configmap]# kubectl create configmap www.ilinux.io --from-file=/etc/kubernetes/nginx/configmap/www.ilinux.io.conf
  2. configmap/www.ilinux.io created
  3. [root@k8s-master01 configmap]#

这种方式创建的ConfigMap对象,其数据存储的键为文件名,值为文件内容,例如下面命令显示的www.ilinux.io对象的信息

  1. [root@k8s-master01 configmap]# kubectl get cm
  2. NAME DATA AGE
  3. configmap-name 1 18m
  4. special-config 2 15m
  5. www.ilinux.io 1 70s
  6. [root@k8s-master01 configmap]# kubectl get cm www.ilinux.io -o yaml
  7. apiVersion: v1
  8. data:
  9. www.ilinux.io.conf: |
  10. server {
  11. listen 80
  12. server_name www.ilinux.io;
  13. location / {
  14. root /usr/share/nginx/html;
  15. }
  16. }
  17. kind: ConfigMap
  18. metadata:
  19. creationTimestamp: "2020-07-10T09:53:14Z"
  20. name: www.ilinux.io
  21. namespace: default
  22. resourceVersion: "5489766"
  23. selfLink: /api/v1/namespaces/default/configmaps/www.ilinux.io
  24. uid: 345a7beb-0059-4c27-a956-391337dd78e2
  25. [root@k8s-master01 configmap]#

如果需要自行指定键名,则可以在--from-file选项中直接指定自定义的键,命令各位如下

  1. kubectl create configmap <configmap-name> --from-file=<keyname>=<path-to-file>

通过这种方式创建的configmap资源可以直接以键值形式收纳应用程序的完整配置信息,多个文件可以分别存储于不同的键值当作。另外需要说明的是,基于直接值和基于文件创建的方式也可以混编使用。

基于目录创建

如果配置文件数量较多且存储于有限的目录中时,kubectl还提供了基于目录直接将多个文件分别收纳为键值数据的configmap资源创建方式。将--from-file选项后面所跟的路径指向一个目录路径就能将目录下的所有文件一同创建于同一ConfigMap资源中,命令格式如下

  1. kubectl create configmap <configmap_name> --from-file=<path-to-directory>

如下面的命令,将/etc/kubernetes/nginx/configmap/目录下的所有文件都保存于nginx-config-files对象中

  1. [root@k8s-master01 configmap]# kubectl create configmap nginx-config-files --from-file=/etc/kubernetes/nginx/configmap/
  2. configmap/nginx-config-files created
  3. [root@k8s-master01 configmap]#

此目录中包含有两个配置文件,创建ConfigMap资源时,它们会被分别存储为两个键值数据

  1. [root@k8s-master01 configmap]# kubectl get cm nginx-config-files -o yaml
  2. apiVersion: v1
  3. data:
  4. tomcat.ilinux.io: |
  5. server {
  6. listen 80
  7. server_name tomcat.ilinux.io;
  8. location / {
  9. root /usr/share/nginx/html;
  10. }
  11. }
  12. www.ilinux.io.conf: |
  13. server {
  14. listen 80
  15. server_name www.ilinux.io;
  16. location / {
  17. root /usr/share/nginx/html;
  18. }
  19. }
  20. kind: ConfigMap
  21. metadata:
  22. creationTimestamp: "2020-07-10T10:05:49Z"
  23. name: nginx-config-files
  24. namespace: default
  25. resourceVersion: "5490878"
  26. selfLink: /api/v1/namespaces/default/configmaps/nginx-config-files
  27. uid: 0c176bd0-8dcd-403f-8496-20bb15115799
  28. [root@k8s-master01 configmap]#

注意:describe命令和get -o yaml命令都可以显示由文件创建而成的键及其值瞒不过两者使用的键和值之间的分隔符不同

使用清单创建

基于清单文件创建ConfigMap资源时,它所使用的字段包括通常的apiVersion、kind和metadata字段,以及用于存储数据的关键字段data

  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4. name: configmap-demo
  5. namespace: default
  6. data:
  7. log_level: info
  8. log_file: /var/log/test.log

如果其值来自于文件内容时,则使用配置文件创建ConfigMap资源的便捷性还不如直接使用命令的方式创建,因此建议直接使用命令行加载文件或者目录的方式进行创建。为了便于配置留存,可以在创建完成后使用get -o yaml命令获取到相关信息后再进行编辑流程

向Pod环境变量传递ConfigMap对象键值数据

如前面所示,Pod资源的环境变量值的获得方式之一包括引用ConfigMap对象中的数据,这一点通过在.spec.containers.env.valueFrom字段中内嵌configMapKeyRef对象即可实现,其格式如下

  1. valueFrom:
  2. configMapKeyRef:
  3. key: <string>
  4. name: <string>
  5. optional: <boolean>

其中,字段name的值为要引用的ConfigMap对象的名称,字段key可用于指定要引用configMap对象中某键的键名,而字段optional则用于为当前Pod资源指明此引用是否为可选。此类环境变量的使用方式与直接定义的环境变量并无区别,它们可被用于容器的启动脚本或直接传递给容器应用等 下面是保存于配置文件configmap-env.yaml的资源定义示例,它包含了两个资源,彼此之间使用”—-“相分隔。第一个资源是名为busybox-httpd-config的ConfigMap对象,它包含了两个键值数据;第二个资源是名为configmap-env-demo的Pod资源对象,它通过环境变量引用了busybox-httpd=config对象中的键值数据,并将其直接传递给了自定义运行的容器应用httpd

  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4. name: busybox-httpd-config
  5. namespace: default
  6. data:
  7. httpd_port: "8080"
  8. verbose_level: "-vv"
  9. ---
  10. apiVersion: apps/v1
  11. kind: Deployment
  12. metadata:
  13. name: configmap-env-demo
  14. spec:
  15. replicas: 1
  16. selector:
  17. matchLabels:
  18. app: configmap-env-demo
  19. template:
  20. metadata:
  21. labels:
  22. app: configmap-env-demo
  23. spec:
  24. containers:
  25. - name: busybox-httpd
  26. image: docker.io/busybox:1.28.3
  27. command: ["/bin/httpd"]
  28. args: ["-f","-p","$(HTTPD_PORT)","$(HTTPD_LOG_LEVEL)"]
  29. env:
  30. - name: HTTPD_PORT
  31. valueFrom:
  32. configMapKeyRef:
  33. name: busybox-httpd-config
  34. key: httpd_port
  35. - name: HTTPD_LOG_LEVEL
  36. valueFrom:
  37. configMapKeyRef:
  38. name: busybox-httpd-config
  39. key: verbose_level

注意,在command或args字段种引用环境变量要使用”$(VAR_NAME)”的格式。待上面配置文件中的资源创建完成后,可以通过如下命令验证Pod资源监听的端口等配置信息是否为busybox-httpd-config中定义的内容

  1. [root@k8s-master01 configmap]# kubectl get pod -o wide -l "app=configmap-env-demo"
  2. NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
  3. configmap-env-demo-74986787b8-nhx4m 1/1 Running 0 3m7s 10.244.59.10 172.18.15.113 <none> <none>
  4. [root@k8s-master01 configmap]#
  5. [root@k8s-master01 configmap]# kubectl exec configmap-env-demo-74986787b8-nhx4m -- ps aux
  6. PID USER TIME COMMAND
  7. 1 root 0:00 /bin/httpd -f -p 8080 -vv
  8. 7 root 0:00 ps aux
  9. [root@k8s-master01 configmap]#

注意: 创建引用了configmap资源的Pod对象时,被引用的资源必须事先存在,否则将无法启动相应的容器,直到被依赖的资源创建完成为止。不过,那些未引用不存在的configmap资源的容器将不受影响。另外,ConfigMap是名称空间级别的资源,它必须与引用它的Pod资源在同一空间中

假设存在这么一种情形,某ConfigMap资源中存在比较多的键值数据,而全部或大部分的这些键值数据都需要由容器来引用。此时,为容器逐一配置相应的环境变量将是一件非常麻烦的事情,而且极易出错。对此,Pod资源支持在容器中使用envFrom字段直接将ConfigMap资源中的所有键值一次性的完成导入。它的使用格式如下:

  1. spec:
  2. containers:
  3. - image: some-image
  4. envFrom:
  5. - prefix: <string>
  6. configMapRef:
  7. name: <string>

envFrom字段值是对象列表,可用于同时从多个ConfigMap对象导入键值数据。为了避免从多个ConfigMap引用键值数据时产生键名冲突,可以在每个引用中将被导入的键使用prefix字段指定一个特定的前缀,如”HTCFG”,一类的字符串,于是ConfigMap对象中的httpport将成为Pod资源中名为HTCFG_httpd_port的变量 如果键名中使用了连接线”-“,那么在转换变量名时,连接线将被自动替换为下划线”

例如,吧上面示例中的Pod资源转为如下形式的定义(configmap-envfrom.yaml)配置为呢就后,其引用ConfigMap进行配置的效果并无不同

  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4. name: busybox-envfrom-config
  5. namespace: default
  6. data:
  7. httpd_port: "8080"
  8. verbose_level: "-vv"
  9. ---
  10. apiVersion: apps/v1
  11. kind: Deployment
  12. metadata:
  13. name: configmap-envfrom-demo
  14. spec:
  15. replicas: 1
  16. selector:
  17. matchLabels:
  18. app: configmap-envfrom-demo
  19. template:
  20. metadata:
  21. labels:
  22. app: configmap-envfrom-demo
  23. spec:
  24. containers:
  25. - name: busybox-httpd-envfrom
  26. image: docker.io/busybox:1.28.3
  27. command: ["/bin/httpd"]
  28. args: ["-f","-p","$(HTCFG_httpd_port)","$(HTCFG_verbose_level)"]
  29. envFrom:
  30. - prefix: HTCFG_
  31. configMapRef:
  32. name: busybox-envfrom-config

资源创建完成之后,可以通过其变量验证其导入的结果

  1. [root@k8s-master01 configmap]# kubectl get pods -l app=configmap-envfrom-demo
  2. NAME READY STATUS RESTARTS AGE
  3. configmap-envfrom-demo-75dd76fb84-2z2vn 1/1 Running 0 16s
  4. [root@k8s-master01 configmap]#
  5. [root@k8s-master01 configmap]# kubectl exec configmap-envfrom-demo-75dd76fb84-2z2vn printenv |grep "^HTCFG"
  6. HTCFG_httpd_port=8080
  7. HTCFG_verbose_level=-vv
  8. [root@k8s-master01 configmap]#

值得提醒的是,从ConfigMap对象导入资源时,prefix为可选字段,省略时,所有变量名同ConfigMap中的键名。如果不存在键名冲突的可能性,例如从单个ConfigMap对象导入变量或在ConfigMap对象中定义键名时已然添加了特定的前缀,那么省略前缀的定义既不会导致键名冲突,又能保持变量的简洁