1. 絮絮叨叨
- K8s部署服务时,一般都需要使用ConfigMap定义一些配置文件
- 例如,部署分布式SQL引擎Presto,会在ConfigMap中定义coordinator、worker所需的配置文件
- 以node.properties为例,
node.environment
和node.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映射需要一个复杂的过程,笔者的做法一般是:
- 通过Presto的worker lists实现nodeId与pod IP的映射
- 通过
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为例:
-
更新node.properties的定义
data: node.properties: | node.id=NODE_ID node.environment={{ .Values.environment }} node.data-dir={{ .Values.server.data_dir }}
-
定义
POD_NAME
env,其值为metadata.name
env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name
-
通过container中的command,获取env、使用sed命令更新
node.properties
中的NODE_ID
关键字command: [ 'sh', '-c', 'sed -i "s/NODE_ID/$POD_NAME/g" /opt/presto/etc/node.properties' ]
- 存在的问题: 服务部署失败,提示ConfigMap所在卷是read-only的,
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
- 保持上面的env定义不变,将node.properties的定义修改成如下样式
- 笔者试验后发现,node.properties中
node.id=$(POD_NAME)
原封不动、未被修改
2.3 学习官方文档 —— 未解
- 阅读K8s官方文档,发现有关ConfigMap的文档,主要是描述如何定义、使用ConfigMap,并未提及如何动态更新ConfigMap
- 《ConfigMaps》:如何定义ConfigMap,两种使用方式:
- ConfigMap映射为文件,一般都是作为
etc/
或config/
目录下的配置文件 - 基于ConfigMap定义环境变量env,ConfigMap中的key-value及时一个环境变量
- ConfigMap映射为文件,一般都是作为
- 《Configure a Pod to Use a ConfigMap》中的使用描述更加详细
- 《ConfigMaps》:如何定义ConfigMap,两种使用方式:
3. 终极解决方案:emptyDir + initContainer
- 通过read-only file system这个错误薪资,笔者找到了类似问题的交流帖:
- Error config Map volume mount read-only file system error
- k8s 使用 statefulset 如何在 pod 内获取序号并设置到环境变量里
- 使用
emptyDir + initContainer
,实现了ConfigMap的动态更新
3.1 具体实现方案
- 定义volumes
volumes: # 加载原始的ConfigMap - name: config-template-volume configMap: name: onequery-coordinator # 定义一个emtyDir - name: config-volume emptyDir: {}
- 创建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
- 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
- volumes定义如下,将log-path与宿主机的