为什么避免使用环境变量作为配置?
“环境变量作为配置”的使用是“12 因子”推荐的。虽然这是一种常见的做法,并且通常会导致很少或没有问题,但将其称为最佳实践有点过分。
12 因子网站中提到的使用它们的原因是
十二因子应用程序将配置存储在环境变量中(通常缩写为 env 变量或 env)。env 变量很容易在部署之间更改,而无需更改任何代码;与配置文件不同,它们不太可能意外地签入代码库;与自定义配置文件或其他配置机制(如 Java 系统属性)不同,它们是与语言和操作系统无关的标准。
其中两个理由是合理的。的确,这些都是很好的理由
- 易于在部署之间更改。
- 与语言和操作系统无关。
但是,这些都没有要求将配置存储在环境变量中。创建与语言和操作系统无关的配置文件(INI、YAML 等)很容易,并且通常也容易使文件在部署之间易于更改 - 例如,如果部署是容器化的,则通过挂载文件。
环境变量本质上“更容易”在部署之间更改的说法不那么正确 - 编写文件本身并不困难,除非它被设计得很难(例如,文件被烘焙到容器镜像中而不是被挂载),更改并不难。
此外,使用环境变量还存在一些缺点,这些缺点往往会在配置大小超过某个特定点时表现得很糟糕。
环境变量是全局状态
环境变量是一种全局状态的形式。每个变量仅与环境相关联。这些变量将与许多具有多种不同用途的其他变量共享
- 变量交叉污染的可能性很高 - 意外地将一个变量命名为与另一个变量相同(不知不觉地用于不同的目的,例如 PATH)的可能性很高,这会导致奇怪、难以调试和可怕的后果。
- 如果你需要检查环境变量,例如找到你认为存在的但实际上不存在的变量,追踪起来很麻烦。
全局状态本身并不一定是“坏事”,但过多的全局状态是一件非常糟糕的事情。少量配置(例如少于 10 个变量)通常可以放置到环境变量中,几乎不会造成任何损害,但一旦数量增加,全局状态的危险性就会增加。
环境变量值无法处理比字符串更复杂的结构
环境变量是一组键值对,其中键几乎总是大写字符串,值始终是字符串。
虽然这对于许多目的已经足够了,但许多情况下需要存储的配置数据需要比字符串稍微复杂一些的东西。
当开发人员遇到这种限制时,他们往往会在字符串中创建具有神秘代码的丑陋子结构。
LS_COLORS 的使用方法就是一个很好的例子
rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
一团糟,对吧?
(这实际上不是严格遵循 12 因子的例子,但我见过很多类似的例子,它们是按照这种方式做的)。
在 StrictYAML 中,以下可以表示为
# Special
di: 01;34 # directory is blue
# Extensions
*.tz: 01;31 # red
*.flv: 01;35 # purple
虽然代码很神秘,可能需要更改(例如,目录:蓝色),但至少可以通过注释轻松解释其神秘性。
创建命名约定以处理无法处理的情况
一个常见的例子
PERSONNEL_DATABASE_HOST, PERSONNEL_DATABASE_PORT, PERSONNEL_DATABASE_NAME, PERSONNEL_DATABASE_PASSWORD, FACTORY_DATABASE_HOST, FACTORY_DATABASE_PORT, FACTORY_DATABASE_NAME, FACTORY_DATABASE_PASSWORD, HOTEL_BACKUP_DATABASE_HOST, HOTEL_BACKUP_DATABASE_USERNAME, HOTEL_BACKUP_DATABASE_PASSWORD, HOTEL_DATABASE_HOST, HOTEL_DATABASE_PORT, HOTEL_DATABASE_NAME, HOTEL_DATABASE_PASSWORD
你发现上面列表中意外遗漏的变量了吗?它会导致严重错误。
StrictYAML 版本
database:
personnel:
host: xxx
port: xxx
name: xxx
password: xxx
factory:
host: xxx
port: xxx
name: xxx
password: xxx
hotel backup:
host: xxx
name: xxx
password: xxx
hotel:
host: xxx
name: xxx
port: xxx
password: xxx
现在呢?