emptyDir + initContainer实现ConfigMap的动态更新(K8s相关)

发布于:2024-07-06 ⋅ 阅读:(46) ⋅ 点赞:(0)

1. 絮絮叨叨

  • K8s部署服务时,一般都需要使用ConfigMap定义一些配置文件
  • 例如,部署分布式SQL引擎Presto,会在ConfigMap中定义coordinator、worker所需的配置文件
  • node.properties为例,node.environmentnode.data-dir的值将由Helm的values.yaml进行渲染
    data:
      node.properties: |
        # node.id由系统使用uuid自动赋值
        node.environment={{ .Values.environment }}
        node.data-dir={{ .Values.server.data_dir }}
    
  • 工作过程中,笔者接到了一个新的需求:node.id需要与podName保持一致,而非毫无规律的UUID
  • 如果是毫无规律的UUID,将nodeId与pod映射需要一个复杂的过程,笔者的做法一般是:
    1. 通过Presto的worker lists实现nodeId与pod IP的映射在这里插入图片描述
    2. 通过kubectl -n ${namespace} get pod -owide | grep ${IP}实现pod IP与pod的映射,从而最终实现nodeId与pod的映射(图片可能上下文不一致,忽略即可)在这里插入图片描述
  • 所谓的podName其实就是K8s中的metadata.name,也是kubectl -n ${namespace} get pod展示的NAME在这里插入图片描述

2. 一些前期调研

2.1 初始想法

拿到这个需求后,自己的想法很简单,以StatefulSet方式部署的coordinator为例:

  1. 更新node.properties的定义

    data:
      node.properties: |
        node.id=NODE_ID
        node.environment={{ .Values.environment }}
        node.data-dir={{ .Values.server.data_dir }}
    
  2. 定义 POD_NAME env,其值为metadata.name

    env:
      - name: POD_NAME
        valueFrom:
          fieldRef:
            fieldPath: metadata.name
    
  3. 通过container中的command,获取env、使用sed命令更新node.properties中的NODE_ID关键字

    command: [ 'sh', '-c', 'sed -i "s/NODE_ID/$POD_NAME/g" /opt/presto/etc/node.properties' ]
    
  • 存在的问题: 服务部署失败,提示Read-only file system
    sed: couldn't open temporary file /opt/onequery/etc/sedQau48c: Read-only file system
    
  • 甚至尝试cat、更新后再写入文件,仍然失败
    # command
    (cat /opt/onequery/etc/node.properties | sed "s/NODE_ID/$POD_NAME/" > /opt/onequery/etc/node.properties)
    # 报错信息
    /bin/sh: 1: cannot create /opt/onequery/etc/node.properties: Read-only file system
    

2.2 ConfigMap能否智能地动态更新? —— No

  • 由于当时对Helm values.yaml如何实现ConfigMap的更新机制不了解,笔者期望ConfigMap能智能加载容器的环境变量,自己实现更新
    • 保持上面的env定义不变,将node.properties的定义修改成如下样式
      data:
        node.properties: |
          node.id=$(POD_NAME)
          node.environment={{ .Values.environment }}
          node.data-dir={{ .Values.server.data_dir }}
      
    • 期望ConfigMap像Shell脚本一样,动态加载env
  • 笔者试验后发现,node.properties中node.id=$(POD_NAME)原封不动、未被修改

2.3 学习官方文档 —— 未解

  • 阅读K8s官方文档,发现有关ConfigMap的文档,主要是描述如何定义、使用ConfigMap,并未提及如何动态更新ConfigMap
    • 《ConfigMaps》:如何定义ConfigMap,两种使用方式:
      • ConfigMap映射为文件,一般都是作为etc/config/目录下的配置文件
      • 基于ConfigMap定义环境变量env,ConfigMap中的key-value及时一个环境变量
    • 《Configure a Pod to Use a ConfigMap》中的使用描述更加详细

3. 终极解决方案:emptyDir + initContainer

3.1 具体实现方案

  1. 定义volumes
    volumes:
      # 加载原始的ConfigMap
      - name: config-template-volume
        configMap:
          name: onequery-coordinator
      # 定义一个emtyDir
      - name: config-volume
        emptyDir: {}
    
  2. 创建initContainer,在initContainer中实现nodeId的动态更新
    initContainers:
      - name: config-template
        image: "{{ .Values.Images.onequery }}"
        # 将ConfigMap拷贝到emptyDir映射的路径,并使用sed命令、基于环境变量动态修改NODE_ID为podName
        command: [ 'sh', '-c', 'cp /etc/config-templates/* /opt/onequery/etc && (cat /etc/config-templates/node.properties | sed "s/NODE_ID/$POD_NAME/" > /opt/onequery/etc/node.properties)' ]
        volumeMounts:
          # 定义volumeMounts,分别将ConfigMap、emptyDir映射到container的指定路径
          - name: config-template-volume
            mountPath: /etc/config-templates
          - name: config-volume
            mountPath: /opt/onequery/etc
        env:
          - name: POD_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
    
  3. coordinator对应的container中,定义volumeMount、与initContainer保持一致(也可以不一致)。启动后的coordinator container,可以访问写入emptyDir的、动态更新后的node.properties文件
    volumeMounts:
      - name: config-volume
        mountPath: /opt/onequery/etc
    

4. 后记

4.1 关于emptyDir卷

  • K8s官方文档对emptyDir卷的描述可知:emptyDir的生命周期与pod一致,初始时为一个空目录,特别适合在pod的不同容器中共享文件
  • 上面的实现方案中,即使initContainer、coordinator container中emptyDir卷的挂载路径不同,也可以共同读写emptyDir中的文件

4.2 volumes vs volumeMounts

  • volumes(存储卷):是K8s中的一种资源,用于将持久化存储与Pod绑定,与Pod具有相同的生命周期
  • volumeMounts(挂载点):
    • 在容器描述符中定义volumeMount,可以将 volume 挂载到容器中的指定路径
    • 在容器中读写上述路径下的文件,实际就是读写持久化存储中相应路径下的文件
  • 举个例子,阐述二者之间的关系:
    • volumes定义如下,将log-path与宿主机的/data00/basic_log/svc_logs/presto目录绑定
      volumes:
       - name: log-path
         hostPath:
           path: /data00/basic_log/svc_logs/presto/log
           type: DirectoryOrCreate # 支持动态创建目录
      
    • 在container中定义volumeMount,使得容器中的/opt/data/var/log路径对应
      volumeMounts:
        - name: log-path # volume name
          mountPath: /opt/data/var/log
          # 将基于环境变量POD_NAME、在宿主机磁盘上动态创建子目录,最终coordinator-0在宿主机上的路径为/data00/basic_log/svc_logs/presto/log/coordinator/coordinator-0
          subPathExpr: coordinator/$(POD_NAME) 
      
    • coordinator运行起来后,会在/opt/data/var/log下生成launcher.log。相应地,其所在宿主机的/data00/basic_log/svc_logs/presto/log/coordinator/coordinator-0目录下将出现launcher.log文件
    • 在容器中对launcher.log的读写操作,实际就是在读写宿主机中对应目录的launcher.log

网站公告

今日签到

点亮在社区的每一天
去签到