Kubernetes 配置映射中的自动子目录



(大约 2 年前被问到一个非常相似的问题,虽然它是专门关于秘密的,但我怀疑这个故事对于 configmaps 有什么不同......但至少,我可以介绍用例以及为什么现有的解决方法对我们来说不可行。

给定一个简单的精简deployment.yaml

apiVersion: apps/v1beta1
kind: Deployment
metadata: 
name: example
spec:
template: 
spec:
containers:
- name: example
volumeMounts:
- name: vol
mountPath: /app/Configuration
volumes:
- name: vol
configMap:
name: configs

和匹配configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
name: configs
labels:
k8s-app: example
data:
example1.json: |-
{
"key1": "value1"
}
example2.json: |-
{
"key2": "value2"
}

configmap.yaml中的键 ,无论它们是什么,都只是简单地创建为文件,无需修改deployment.yaml或具有除 mountPath 以外的任何细节。

问题在于实际结构具有子文件夹来处理覆盖根值的区域特定值:

Configuration  example1.json
Configuration  example2.json
Configuration  us  example1.json
Configuration  us  ca  example2.json

对于可以想象到的许多不同国家和地区以及每个单独配置的模块,这些模块的数量和性质显然会有所不同。 其目的是为最终用户提供一个工具,允许他们设置和管理这些配置,这将在幕后自动生成configmap.yaml并在 kubernetes 中更新它。

然而,除非我还没有找到一个技巧,否则这似乎超出了 kubernetes 的能力范围,在几个方面。

首先,没有语法允许指定作为目录的 configmap 键,也不允许在键中包含子目录路径:

data:
# one possible approach (currently complains that it doesn't validate '[-._a-zA-Z0-9]+')
/us/example1.json: |-
{
"key1": "value1"
}
# another idea; this obviously results in 'invalid type for io.k8s.api.core.v1.ConfigMap.data: got "map", expected "string"'
us:
example2.json: |-
{
"key2": "value2"
}

那么,我们有哪些选择来实现这一点呢?

好吧,我们可以在 deployment.yaml 的volumes: -configMap:节点中使用items: -key: path:方法将密钥映射到特定位置,

和/或在 deployment.yaml 的volumeMounts:节点中生成多个节点,

使用任一subPath:(与在volumes: configMap:中使用items: -key: -path:基本相同),

或为每个子目录单独单独的配置映射,并将它们全部挂载为不同的volumesdeployment.yaml。

所有这些方法都需要对 deployment.yaml 进行大量且令人难以置信的冗长更改,泄露它不应该有任何理由知道的知识,使其可变并不断重新生成而不是静态,使将设置更新推出到已部署的 pod 等复杂化。 只是不好。 所有这些只是为了映射一个目录,只是因为它包含子目录......

当然,这不可能是它应该的工作方式吗? 我错过了什么? 我应该怎么做?

使用 Helm 通过从.Files.Glob构建{ filename_sha: file_contents }的配置图,然后挂载每个这样的文件来解决 OP 的问题非常容易。它适用于二进制数据、有趣的文件名和深度嵌套的目录,至少到目前为止是这样。

如果你有很多文件,这是没有效率的,但如果它不是太多,这里的简单性可能会证明罪恶是合理的。

这是我的代码摘录,它复制了 Helm 图表中files/**的所有内容,并将其挂载在容器中的/mnt/**下:

apiVersion: v1
kind: ConfigMap
metadata:
name: my-files
data:
# map all files under the files/ to a shasummed key pointing at the contents
{{ range $path, $bytes := .Files.Glob "files/**" }}
{{ sha256sum $path | nindent 2 }}: {{ $.Files.Get $path | quote }}
{{ end }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-container
spec:
# ... (omitted for brevity)
template:
spec:
containers:
- name: amp-server
# ... (omitted for brevity)
volumeMounts:
# now mount each such file, trimming the 'files/' prefix here
{{ range $path, $bytes := .Files.Glob "files/**" }}
- name: config-files-multipart
mountPath: {{ printf "/mnt/%s" (trimPrefix "files/" $path) | quote }}
subPath: {{ sha256sum $path | quote }}
{{ end }}
volumes:
- name: config-files-multipart
configMap:
name: my-files

从"容器本机"的角度来看,具有应用程序在启动时处理以达到其规范配置的大型配置文件文件系统树是一种反模式。最好有一个生成单个文件的工作流程,该文件可以存储在 ConfigMap 中,并以其最终形式轻松检查。例如,参见nginx入口。

但显然并不是每个人都在重写他们的应用程序,以更好地与 kubernetes 方法保持一致。在部署时将配置文件的完整目录树获取到容器中的最简单方法是使用 initContainers 和 emptyDir 挂载。

将配置文件树打包到容器(有时称为"仅数据"容器)中,并让容器启动脚本将配置树复制到 emptyDir 挂载中。然后,应用程序可以按预期使用树。

根据配置树的规模,另一个可行的选择可能是在配置映射中的文件"路径"中模拟带有下划线而不是斜杠的子树。这将使您失去一般文件系统性能(如果您只是要读取配置,这应该永远不会成为问题),并迫使您重写一点应用程序代码(访问配置时文件模式遍历而不是目录遍历),但应该以相当便宜的价格解决您的用例。

一些解决方法:

  1. 有一个仅数据容器,上面有数据...
FROM scratch
... # copy data here

然后将其添加为将卷安装到另一个容器上的挎斗...

    从配置
  1. 创建一个焦油球,将其转换为配置映射,挂载在容器中并更改容器命令以在启动之前解压缩配置...

  2. 用一些特殊的字符重命名文件,而不是/,如us@example.json并使用脚本像开始时一样mv它们。

所有这些都是非常黑客...最好的方案是重构它们以在平面文件夹中使用,并使用类似 kustomize 的东西创建它们:

kustomize edit add configmap my-configmap --from-file='./*.json'

最新更新