在为一个客户开发持续集成/持续交付 (CI/CD) 解决方案时,我的首要任务之一是自动化在 OpenShift 中引导 CI/CD Jenkins 服务器。 遵循 DevOps 的最佳实践,我迅速创建了一个配置文件来驱动脚本完成这项工作。 当我意识到我需要一个单独的 Jenkins 服务器用于生产环境时,很快就变成了两个配置文件。 之后,客户又提出要求,不同的组需要多个工程和生产 CI/CD 服务器对,并且每个服务器的配置相似但略有不同。
当需要对两个或多个服务器共有的值进行不可避免的更改时,在两个或四个文件中传播这些更改变得非常困难且容易出错。 随着为更复杂的测试和部署添加了 CI/CD 环境,每个组和环境的共享和特定值的数量也在增加。
随着更改变得越来越频繁,数据变得越来越复杂,在配置文件中进行更改变得越来越难以管理。 我需要一个更好的解决方案来解决这个由来已久的问题,并更快、更可靠地管理更改。 更重要的是,我需要一种解决方案,让我的客户在完成工作后也能做到同样的事情。
定义问题
从表面上看,这听起来像是一个非常简单的问题。 给定 `my-config-file.conf` (或一个 `*.ini` 或 `*.properties`) 文件
KEY_1=value-1
KEY_2=value-2
您只需在脚本顶部执行以下行
#!/usr/bin/bash
set -o allexport
source my-config-file.conf
set +o allexport
这段代码实现了配置文件中的所有变量到环境中,并且 `set -o allexport` 会自动导出它们。 原始文件,作为一个典型的键/值属性文件,也非常标准且易于解析到另一个系统中。 更复杂的情况如下:
- **某些值是从变量复制并粘贴到变量,并且是相关的。** 除了违反 DRY(“不要重复自己”)原则外,它还容易出错,尤其是在需要更改值时。 如何重用文件中的值?
- **配置文件的某些部分可在原始脚本的多次运行中重用,而其他部分仅对特定运行有用。** 如何超越复制和粘贴并模块化数据,以便某些部分可以在其他地方重用?
- **文件模块化后,如何处理冲突并定义优先级?** 如果同一个文件中定义了两次同一个键,您采用哪个值? 如果两个配置文件定义了同一个键,哪个具有优先级? 特定安装如何覆盖共享值?
- **配置文件最初旨在供 shell 脚本使用,并且是为 shell 脚本处理而编写的。 如果需要在另一个环境中加载或重用配置文件,是否有办法使它们易于供其他系统使用,而无需进一步处理?** 我想将一些键/值对移动到 Kubernetes 中的单个 ConfigMap 中。 使处理后的数据可用于使导入过程简单易行的最佳方法是什么,以便其他系统不必了解配置文件的结构方式?
本文将带您了解一些简单的代码片段,并展示实现起来有多么容易。
定义配置文件内容
执行文件意味着它将执行变量以及其他 shell 语句,例如命令。 因此,配置文件应该只包含键/值对,而不应该定义函数或执行代码。 因此,我将以类似于属性和 .ini 文件的方式定义这些文件
KEY_1=${KEY_2}
KEY_2=value-2
...
KEY_N=value-n
从这个文件中,您应该期望以下行为
$ source my-config-file.conf
$ echo $KEY_1
value-2
我故意让它有点违反直觉,因为它引用了我尚未定义的值。 在本文的后面,我将向您展示处理这种情况的代码。
定义模块化和优先级
为了保持代码的简单性并使定义文件直观,我为文件和变量分别实施了从左到右、从上到下的优先级策略。 更具体地说,给定一个配置文件列表
- 列表中的每个文件将按照从第一个到最后一个(从左到右)的顺序进行处理
- 键的第一个定义将定义该值,后续的值将被忽略
有很多方法可以做到这一点,但我发现这种策略简单明了,易于编码,也易于向他人解释。 换句话说,我并不是声称这是最佳设计决策,但它有效,并且简化了调试。
给定这个以冒号分隔的两个配置文件列表
first.conf:second.conf
及其内容
# first.conf
KEY_1=value-1
KEY_1=ignored-value
# first.conf
KEY_1=ignored-value
您应该期望
$ echo $KEY_1
value-1
解决方案
此函数将实现已定义的要求
_create_final_configuration_file() {
# convert the list of files into an array
local CONFIG_FILE_LIST=($(echo ${1} | tr ':' ' '))
local WORKING_DIR=${2}
# removes any trailing whitespace from each file, if any
# this is absolutely required when importing into ConfigMaps
# put quotes around values if extra spaces are necessary
sed -i -e 's/\s*$//' -e '/^$/d' -e '/^#.*$/d' ${CONFIG_FILE_LIST[@]}
# iterates over each file and prints (default awk behavior)
# each unique line; only takes first value and ignores duplicates
awk -F= '!line[$1]++' ${CONFIG_FILE_LIST[@]} > ${COMBINED_CONFIG_FILE}
# have to export everything, and source it twice:
# 1) first source is to realize variables
# 2) second time is to realize references
set -o allexport
source ${COMBINED_CONFIG_FILE}
source ${COMBINED_CONFIG_FILE}
set +o allexport
# use envsubst command to realize value references
cat ${COMBINED_CONFIG_FILE} | envsubst > ${FINAL_CONFIG_FILE}
它执行以下步骤
- 它删除每行中多余的空格。
- 它遍历每个文件,并将每一行写入具有唯一键(即,由于 `awk` 的魔力,它会跳过重复的键)的中间配置文件。
- 它执行中间文件两次,以实现在内存中的所有引用。
- 中间文件中引用的值从现在内存中的值实现,并写入最终配置文件,该文件可用于进一步处理。
如上面的说明所示,当执行组合配置中间文件时,必须执行两次。 这是为了使在被引用之后定义的引用值可以在内存中正确实现。 `envsubst` 替换环境变量的值,并且输出被重定向到最终配置文件以进行可能的后处理。 根据前面示例的要求,这可以采用在 ConfigMap 中实现数据的形式
kubectl create cm my-config-map --from-env-file=${FINAL_CONFIG_FILE} \
-n my-namespace
示例代码
您可以在我的 GitHub 存储库 modular-config-file-sample 中找到示例代码,其中包含 `specific.conf` 和 `shared.conf` 文件,演示了如何组合表示特定配置文件和通用共享配置文件的文件。 配置文件由以下内容组成:
# specific.conf
KEY_1=${KEY_2}
KEY_2='some value'
KEY_1='this value will be ignored'
# shared.conf
SHARED_KEY_1='some shared value'
SHARED_KEY_2=${SHARED_KEY_1}
SHARED_KEY_1='this value will never see the light of day'
KEY_1='this was overridden'
请注意值周围的单引号。 我故意选择带有空格的示例值,以使事情更有趣,并且这些值必须用引号引起来; 否则,当执行文件时,每个单词将被解释为一个单独的命令。 但是,一旦设置了值,变量引用就不需要用引号引起来。
该存储库包含一个小型的 shell 脚本实用程序 `pconfs.sh`。 这是从示例代码目录中运行以下命令时发生的情况
# NOTE: see the sample code for the full set of command line options
$ ./pconfs.sh -f specific.conf:shared.conf
================== COMBINED CONFIGS BEFORE =================
KEY_1=${KEY_2}
KEY_2='some value'
SHARED_KEY_1='some shared value'
SHARED_KEY_2=${SHARED_KEY_1}
================ COMBINED CONFIGS BEFORE END ===============
================= PROOF OF SUBST IN MEMORY =================
KEY_1: some value
SHARED_KEY_2: some shared value
=============== PROOF OF SUBST IN MEMORY END ===============
================== PROOF OF SUBST IN FILE ==================
KEY_1=some value
KEY_2='some value'
SHARED_KEY_1='some shared value'
SHARED_KEY_2=some shared value
================ PROOF OF SUBST IN FILE END ================
这证明了即使是复杂的值也可以在值定义之前和之后引用。 它还表明,无论是在文件内部还是跨文件,只有该值的第一个定义会被保留,并且在文件列表中从左到右给出优先级。 这就是为什么我在运行此命令时首先指定解析 specific.conf; 这允许特定配置覆盖示例中的任何更通用的共享值。
现在,您应该拥有一个易于实现的解决方案,用于在 shell 中创建和使用模块化配置文件。 此外,处理文件的结果应该足够容易使用或导入,而无需其他系统了解数据的原始格式或组织。
评论已关闭。