浅谈配置文件

很少有人关注配置文件,但它绝对是一个值得讨论的问题。

一个常见问题是:如何处理不同环境下不同的配置?传统的解决方案是为每个环境都单独设置一个配置文件,比如 rails 项目里一般会有 development、production、test 等几个配置文件,不过此方法不易扩展:更多部署意味着更多新的环境,随着项目的不断深入,开发人员可能还会添加他们自己的环境,这将导致各种配置组合的激增,从而给管理部署增加了很多不确定因素,此外,直接在文件中保存配置的话,如果有用户名密码等敏感信息,往往意味着它们会一并被保存到版本库中,这可能会诱发安全隐患,类似的案例在 github 上已经数不胜数了。关于此类问题,12factor 给出的解决方案是在环境变量中保存配置,如此一来,代码层面上就不用再关注不同环境下配置的差异了,版本库里也不用保存敏感信息了(都保存到环境变量里面了)。

说明:很多配置库都支持环境变量,比如 PHP 的 dotenv,亦或者 Golang 的 viper。

不过在环境变量中保存配置并不全是优点,也有缺点。首先:它只能保存字符串,如果要保存复杂结构的数据,那么只能想办法编码后再保存,设想一下数据库服务器多个节点,那么用环境变量保存的话可能需要用逗号分割后再保存成一个大字符串:

DB_HOSTS=10.0.0.1,10.0.0.2,10.0.0.3

实际使用时,程序里获取环境变量后需要先解码后再使用,不得不说不够优雅。

此外,还有一个缺点是:如果应用程序是常驻进程的话,那么往往会在启动的时候就读取环境变量,这意味着假设我们想修改配置的话,那么单纯修改环境变量无法生效,还需要重启应用进程才可以,一旦需要频繁修改配置的话,着实让人恼火。

正因为在环境变量中保存配置有诸多缺点,所以我并没有选择它,最初我的解决方案是把配置都保存到 ETCD 里:不同的环境运行不同的 ETCD 实例,如此就不用再关注不同环境下配置的差异了,此外,如果配置发生了变化的话,那么我们无需重启应用进程,因为通过 viper 的 WatchRemoteConfigOnChannel 方法,我们可以随时感知到变化。

etcdkeeper

etcdkeeper

ETCD 可以说已经接近完美了,不过如果你的应用是部署在 k8s 上的,那么 ETCD 也许并不是最佳选择,因为 k8s 为配置提供了专门的解决方案 ConfigMap:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: service
spec:
  replicas: 1
  template:
    spec:
      containers:
      - name: service
        volumeMounts:
        - name: config-volume
          mountPath: /etc/service
      volumes:
        - name: config-volume
          configMap:
            name: special-config
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: special-config
data:
  config.toml: |
    [service]

    host = ...
    port = ...
    password = ...

那么 ConfigMap 能解决我们上面提到的问题么?

首先看看如何处理不同环境下不同的配置。这很简单, 我们只要在不同的环境上部署不同的 k8s 集群,然后把配置保存在对应的 ConfigMap 上就可以了,不过有人可能会问如果是本地环境的话怎么办?难不成要在本地部署一个 k8s 集群?当然不需要,别忘了即便是使用 ConfigMap,也是把 ConfigMap 映射成为一个本地文件,如果是本地环境的话,直接使用本地文件即可,不用部署 k8s 集群。

接着看看修改配置后如何生效的问题。通用的解决方案是我们可以设置 ConfigMap 更新后滚动更新 Pod,虽然还是需要重启应用,但是至少是自动重启的:

Kubernetes Pod 中的 ConfigMap 配置更新 Kubernetes 中的 ConfigMap 配置更新(续)

有没有不重启应用的方法呢?答案是肯定的!当我们将 ConfigMap 数据添加到数据卷中的特定路径的时候,一旦数据发生变化,挂载的 ConfigMap 将自动更新。如果使用 viper 的话,只需要通过 WatchConfig 来监听变化即可,从而避免了重启应用。

最后还有一个问题是如果在 ConfigMap 里保存配置,那么对应的 yml 是否保存在项目版本库里,如果是的话那么敏感信息问题如何解决?一个权宜的解决方案是把所有和 ConfigMap 相关的 yml 都保存到一个独立的版本库里,而在项目本身的版本库里,只保存一个名字类似 config.toml.dist 或者 config.toml.example 之类的模版文件作为引子。

以上就是我对配置文件的一些粗浅的想法,欢迎讨论。

文章来源:

Author:老王
link:https://blog.huoding.com/2020/12/28/877