Helm Chart 中通过 configMap 管理应用程序配置文件

背景

各种成熟软件的官方container image中,大多包含了默认的配置文件,在真正的生产部署中,一定需要灵活的方法管理这些配置文件,以满足下列需求:

  1. 能修改默认配置文件
  2. 对配置文件进行版本管理
  3. 修改配置文件能自动触发 rolling update

helm 作为 container 的编排工具,可以满足以上的需求,虽然有些有些并不那么直接。我们以 helm 官方的 cassandra chart (incubator repo)为基础,看看如何实现以上三个需求。


修改配置文件

配置文件可简单看做一堆配置项(Key: value) 的集合,在安装 chart 时作为参数传入并设置到 deployment 中。这是典型的给 helm chart 传参的过程,因此自然想到可以使用 chart values file -- values.yaml 来存储你自定义的配置文件,通过下面的传递途径把值传入到container 中:

Helm Chart 中通过 configMap 管理应用程序配置文件_第1张图片
Untitled Diagram.png

cassandra chart 使用的 cassandra image 默认包含了下面一系列必备配置文件。其中,像 cassandra.yaml 这样的核心配置文件是我们在部署过程中常需要修改的,而且,修改后需要重启 cassandra instance 才能生效。

> kubectl exec po/cassandra-01-1 -- ls /etc/cassandra/
cassandra-env.ps1               hotspot_compiler
cassandra-env.sh                jvm.options
cassandra-jaas.config           logback-tools.xml
cassandra-rackdc.properties     logback.xml
cassandra-topology.properties   metrics-reporter-config-sample.yaml
cassandra.yaml                  README.txt
commitlog_archiving.properties  triggers
cqlshrc.sample

默认实现

官方 cassandra chart 中也确实提供了这种实现方式,你可以通过修改 values.yaml 中的这个参数,来覆盖container中的默认配置文件。

parameter Description Default
configOverrides Overrides config files in /etc/cassandra dir {}

如果你想覆盖哪个 config file,你必须在 configOverrides 这个参数下面,以该 config file 的名字为 key 粘贴上整个 config file 的内容,例如,以 cassandra.yaml 为例,你需要在 values.yaml 中添加如下内容:

# values.yaml
...
## Cassandra config files overrides
configOverrides:
  cassandra.yaml: |
    # Cassandra storage config YAML

    # NOTE:
    #   See http://wiki.apache.org/cassandra/StorageConfiguration for
    #   full explanations of configuration directives
    # /NOTE

    # The name of the cluster. This is mainly used to prevent machines in
    # one logical cluster from joining another.
    cluster_name: cassandra
    ...
    (整个config file的内容比如全部出现在这里)
...

只是这种修改的方法比较笨拙,由于 default helm chart 默认覆盖了整个 /etc/cassandra path,因此,你必须把所有 cassandra 启动必须的 config file 的所有内容都填入 values.yaml/configOverride 下,否则的话,该文件将不会出现在 container 中。首先,对于绝大多数我们不需要修改的配置文件,把默认内容贴在这完全是无意义的工作。其次,这会产生一个非常庞大的 values.yaml file,非常不便于维护。

更优雅的实现

针对上面的实现,我希望对现有 chart 做两点改进

  1. 只传入自定义的文件。无需修改的,自动使用 container 自带的默认配置文件。
  2. 自定义的配置文件存放在一个单独目录下,而不是堆放在 values.yaml 中。

实现方法,把你自定义的配置文件存放在 chart 根目录的 ./configs 文件夹下。在 values.yaml/configOverride 下以数组形式列出所有你希望覆盖的配置文件。整个数据传递过程变为:

Helm Chart 中通过 configMap 管理应用程序配置文件_第2张图片
Untitled Diagram (1).png
values.yaml
## Cassandra config files overrides
configOverrides:
  - cassandra.yaml
  - cassandra-rackdc.properties

values.yaml 中只记录我们要覆盖的config file的索引。

templates/configmap.yaml
{{- if .Values.configOverrides }}
kind: ConfigMap
metadata:
  name: cassandra
...
data:
{{- range .Values.configOverrides }}
  {{ . }}: |
{{ $.Files.Get (printf "%s%s" "configs/" .) | indent 4 }}
{{- end }}
{{- end }}

自动生成一个包含所有自定义 config files 内容的 configMap manifest。每个自定义 config 的内容作为一个 data 的 sub-object 出现,key 是文件名,value 是 config file 的内容。

templates/statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
...
spec:
  ...
  template:
  ...
    spec:
    ...
{{- if or .Values.configOverrides (not .Values.persistence.enabled) }}
      volumes:
{{- end }}
{{- if .Values.configOverrides }}
      - configMap:
          name: cassandra
        name: cassandra-config-overrides
      - name: cassandra-configs
        emptyDir: {}
{{- end }}
...

首先,显然意见,我们需要把包含自定义配置文件的 configMap resource mount成一个volume -- cassandra-config-overrides。同时,我们还创建了一个 cassandra-configs 的临时 volume,它的作用下面介绍。

apiVersion: apps/v1
kind: StatefulSet
...
spec:
  ...
  template:
  ...
    spec:
{{- if .Values.configOverrides }}
      initContainers:
      - name: config-chown
        image: busybox
        command: [ 'sh', '-c', 'cp /cassandra-config-overrides/* /cassandra-configs/ && chown 999:999 /cassandra-configs/*']
        volumeMounts:
        - name: cassandra-config-overrides
          mountPath: /cassandra-config-overrides
        - name: cassandra-configs
          mountPath: /cassandra-configs/
{{- end }} 

最直接的方法,我们应该把包含 config files 的 cassandra-config-overrides volume 直接 mount 到 container 的 /etc/cassandra 目录中,但是我们并没有这么做,而是创建了一个 initContainer 来把 cassandra-config-overrides 中的 config files 拷贝到刚才创建的 emptyDir cassandra-configs volume 中,并修改文件的 owner 和 group。

之所以多此一举,是因为 kubernetes 的 configMap volume 现在还不支持在 mount 时自定义 user 和 group (参考 https://github.com/kubernetes/kubernetes/issues/81089),直接 mount configMap volume,不满足 cassandra 对配置文件 permission 的要求,container 启动时会遇到如下错误:

chown: changing ownership of '/etc/cassandra/cassandra.yaml': Read-only file system

暂时的解决方案就是增加如上的 initContainer,把配置文件从 Ready-only 的 configMap volume 拷贝到权限更大的 emptyDir volume,并修改文件的 owner:group 为当前 session 的 user:group。然后把 emptyDir volume mount 到 container 中。如下:

apiVersion: apps/v1
kind: StatefulSet
...
spec:
  ...
  template:
  ...
    spec:
      containers:
      - name: {{ template "cassandra.fullname" . }}
        image: "{{ .Values.image.repo }}:{{ .Values.image.tag }}"
        ...
        volumeMounts:
        - name: data
          mountPath: /var/lib/cassandra
{{- if .Values.configOverrides }}
{{- range .Values.configOverrides }}
        - name: cassandra-configs
          mountPath: /etc/cassandra/{{ . }}
          subPath: {{ . }}
{{- end }}
{{- end }}
...

经过以上操作,用户自定义的配置文件的 user:group 被修改为 cassandra instance用户,statefulset 中的 database replica pod 可以正常启动。

> kubectl exec po/cassandra-01-2 -- ls -l /etc/cassandra/cassandra.yaml
-rw-r--r-- 1 cassandra cassandra 58588 May 30 11:49 /etc/cassandra/cassandra.yaml

对配置文件进行版本管理

通常,helm chart 都是存放在 VCS 系统中,例如 github,这种配置管理代码化,可以方便的实现软件部署的版本管理。另外, helm chart 本身也有 release revision的概念,例如,在部署了以上 chart 后,如果运维过程中需要修改软件配置文件,可以通过 helm upgrade 来启用新的配置,或者,如果升级出现问题,可通过 helm rollback 回滚到之前的配置。


修改配置文件能自动触发 rolling update

如果我们用 configMap 导入配置文件,当修改了配置文件后,helm upgrade 会自动更新 kubernetes 中的 configMap resource。但是,即便是 configMap 被 mount 为 volume,它本身的修改并不会触发它所属的 deployment / statefulset 等上层资源的 rolling update。

对于像 cassandra 这样的应用程序来说,配置文件修改后,必须通过 restart 来使新的配置生效。也就是说,在你修改了 ./configs/ 下的某个配置文件后,即便是通过 helm upgrade 发布了修改,它也不会立刻生效,必须手动 rolling update (restart) 所有的 replica pod 之后,新的配置才能真正生效。 如何能自动的触发 rolling update 呢?

参考 charts_tips_and_tricks/#automatically-roll-deployments,我们知道,只有当一个 deployment / statefulset 的 spec 发生修改时,rolling update 才会自动发生。因此,在 helm chart 中,一个巧妙的做法是将 configMap 的 hash 值作为 annotation 写入 deployment / statefulset 的 template.metadata.annotations 中,这样,当 configMap 的内容修改的时候,annotation 会响应发生变化,rolling update 被自动 trigger。

在 cassandra 的例子中,我们希望将范围扩大到:任何 ./configs 下的配置文件的内容发生变化,都会触发 rolling update。实现方法为:

apiVersion: apps/v1
kind: StatefulSet
...
spec:
  ...
  template:
    metadata:
{{- if .Values.configOverrides }}
      annotations:
{{- $SUM := "" }}
{{- range .Values.configOverrides }}
{{- $SUM = $.Files.Get (print "configs/" .) | sha256sum | print $SUM }}
{{- end }}
       checksum/config: {{ $SUM | sha256sum }}
{{- end }}
...

思路非常简单,先对每个配置文件的内容进行 hash,把所有 hash 值拼接成一个字符串,再对字符串进行二次 hash,生成的最终 hash 值作为 annotation 写入 statefulset.spec.template.metadata.annotations。这样,你可以任意修改 config file,在 helm upgrade 后,rolling update 会自动进行,你修改也会随之生效。

你可能感兴趣的:(Helm Chart 中通过 configMap 管理应用程序配置文件)