NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

配置管理的五项最佳实践

  • 2010-08-06
  • 本文字数:4857 字

    阅读完需:约 16 分钟

最近有很多关于应用程序的配置以及如何对其进行管理的讨论。 我在 ThoughtWorks 的一名同事 Tom Sulston 和我一起启动了名为 ESCAPE 的项目,它是从应用程序空间之外说明配置管理的一种方式。 它所做的是为多个处于各种环境中的应用程序以 REST 服务的方式提供配置选项。 尽管还没有真正应用 ESCAPE,但它并没有被放弃或者遗忘——只是我们(又一次)需要处理当前手头的工作。

现在我想带你一起看一下我们可以在代码中做些什么,使得他们以及所有需要管理和维护应用程序的人的工作变得更简单。 有些模式已经被我(和其他人)在 ThoughtWorks 的项目中应用过很多次,其价值已经得到了充分的验证。

单一配置源

在很多应用程序中,我都发现可以从所有的代码中以特定的实现方式来访问配置信息。 这不仅导致人们对于在哪里能够对应用程序特定的方面进行配置感到疑惑,并且这个疑惑经常会由于同样的配置参数名称(比方说database.host)根据它的位置而代表不同的意义而变的更加严重。 额外的副作用还包括:

  • 难于识别已用的或者不必要的配置选项
  • 不同部分的代码使用不同的机制来访问相同的配置源
  • 对相同的值使用不同的配置源

从运维的角度来看,这样的系统的最大副作用就在于,配置的源代码经常会拥有不同的格式,比方说,在一些文件中以 XML 形式存在,而在另一些文件中以 key/value 对的形式存在。 所有这些偶然的复杂性使得我们很难将应用程序部署到新环境中。

在这样的系统中,我们经常会发现对于这些配置代码没有进行测试,或者尽管存在测试,但它很脆弱且 / 或不切实际。

因此:

我们应该封装实际的将存储的配置信息放到提供程序中的机制,并将这个提供程序注入(inject)到需要值的地方。 这也让我们可以使用针对配置提供程序的特定实现的测试。 它让我们可以使用这样的方式:存储配置信息,使其可以很容易地根据系统的情况来改变。 例如,你可以在开始的时候使用硬编码的字符串,然后变为文件的形式,并最终变为某种容器中的值。

例如,看下这个简单的 Python 类,它是硬编码值的字典:

复制代码
class ConfigProvider(dict):
def __init__(self):
self['name'] = 'Chris'

class ConfigProvider(dict):

然后这个简单的类可以这样使用:

复制代码
from ConfigProvider import ConfigProvider
class ConfigProviderUser:
def __init__(self, cfg):
self.cfg = cfg
print "Hello, my name is %s" % self.cfg["name"]
if __name__ == "__main__":
ConfigProviderUser(ConfigProvider())

但是,之后我们决定必须停止使用硬编码,变为从.properties 读取配置信息。 这样,我们唯一需要改变的代码就是 ConfigProvider,最终它会像下面这样:

复制代码
class ConfigProvider(dict):
src = None
prop = re.compile(r"([\w. ]+)\s*=\s*(.*)")
def __init__(self, source = "config.properties"):
self.src = source
self.loadConfig()
def loadFileData(self):
data = ""
try:
input = open(self.src, 'r')
data = input.read()
input.close()
except IOError:
pass
return data
def loadConfig(self):
for (key, val) in self.prop.findall(self.loadFileData()):
self[key.strip()] = val

考虑一下,如果现在我们觉得.properties 不够好,想要切换到.yam 文件,那么需要做些什么呢? 再一次,你需要改变的代码就只有 ConfigProvider。 在这里它处于事务状态,在其中可以很好地处理这两种格式(基于文件的扩展名):

复制代码
class ConfigProvider(dict):
src = None
prop = re.compile(r"([\w. ]+)\s*=\s*(.*)")
def __init__(self, source = "config.properties"):
self.src = source
self.loadConfig()
def loadConfig(self):
if self.src.endswith(".properties"):
self.loadPropertiesConfig()
elif self.src.endswith(".yaml"):
self.loadYamlConfig()
def loadFileData(self):
data = ""
try:
input = open(self.src, 'r')
data = input.read()
input.close()
except IOError:
pass
return data
def loadPropertiesConfig(self):
for (key, val) in self.prop.findall(self.loadFileData()):
self[key.strip()] = val
def loadYamlConfig(self):
entries = yaml.load(self.loadFileData())
if entries:
self.update(entries)

单一配置规则集

太多的应用程序,甚至是很小很简单、不需要外部配置文件而将所有配置信息都包含在命令行中程序,都无法将配置规则充分地告诉用户。 这些规则可能包括(不仅限于):

  • 所有配置属性能够被设置为什么?
  • 需要哪个配置属性,哪些是可选的?
  • 是否可以检查提供给一个属性的值的有效性?
  • 是否有默认值,如果有的话,它们位于哪里?

通常这是因为这些规则只是代码行为的隐式副作用(implicit side effects)。 通常情况下,应用程序会正常启动并运行,但是如果用户试图执行某些功能,而该功能需要未设定或者无效的配置值,那么此时你就会得到不希望看到的结果。 这会让我们耗费大量时间来验证这样的应用程序部署是否成功,并且很容易产生错误。

因此:

定义单独的规则集,在其中定义所有上面所提到的问题。 然后我们可以使用这个单独的正确的源文件来生成配置模板,只要它适合你的应用程序。 这对于支持模式验证的格式(像 XML)会非常有效,但是仍然可以应用给像属性文件一样简单的系统,其中你只是生成形式化的示例文件。

单独的配置规则集可以被用作部署配置冒烟测试的一部分。 如果在应用程序初始化的时候缺少需要的配置元素,那么它就会立即崩溃,并且效果明显。 不要等到应用程序试图读取那些值的时候才发现问题。 如果我们知道更多关于如何检查所提供的值是否有效的信息(如果值是整型的,或者是已经存在的文件,或者是我们选择打开 socket 端口的主机名和端口,那么就易于测试),那么也要在此测试。

这些提供程序必须经过单元测试。 这些测试要针对所有模板执行,这些模板是被外部的配置应用程序的人员和 / 或系统所使用的。 部署配置冒烟测试应该在开发单元测试的时候就执行。 如果添加了新的配置选项,那么就应该添加针对该选项的单元测试。 当有人更新代码库的时候,如果他们没有在工作站上定义该值,那么测试就会失败,你就要大声地告诉他们“我需要你定义配置值sheep,但是没有!”

尽管单独的配置规则集必须大量使用单独的配置源文件,但是要记住它们的关注点是不同的。 我们需要注意避免二者之间的泄漏(leakage)。

在上个模式中我们开始使用的 Python 例子中执行,我们现在就会得到配置规则集,如下:

复制代码
class ConfigRuleset(dict):
defaults = {
'name': 'no name',
}
required = [
'name',
]
def __init__(self):
self.update(self.defaults)
def validate(self):
missing = []
for key in self.required:
if not self.has_key(key):
missing.append(key)
if len(missing):
raise KeyError("The following required config keys are missing: %s" % missing)

再一次,我们只需要改变配置提供程序。 它可能会像下面这样:

复制代码
class ConfigProvider(ConfigRuleset):
src = None
prop = re.compile(r"([\w. ]+)\s*=\s*(.*)")
def __init__(self, source = "config.properties"):
ConfigRuleset.__init__(self)
self.src = source
self.loadConfig()
self.validate()
….

然后,ConfigRuleset中的结构体defaultsrequired会成为我们代码中的正确值的唯一来源,它是针对我们的默认值以及所需要的那个键值的。

配置视图

当你试图诊断运行的应用程序中所发生的问题时,通常就需要查看当前运行的配置值是怎样的。 只查看当前的配置源文件无法得到精确的信息,因为应用程序早最后一次载入之后,配置信息可能会发生改变。

因此:

我们需要为所有人提供一种简单的并且是众所周知的方法,用来找出运行的系统从何处载入配置信息,以及载入的值是什么。 这在启动时可能和打印配置树(以及源文件的位置)一样简单,尽管这在长期运行的系统中很快就会产生变化。 更健壮的方法是要具备某种网页 / 关于页面 / 远程过程调用,它会返回当前运行时的配置信息,其中带有这些值是从何处载入的信息(特别是在有多个可能的配置源的情况下)。

对于这个视图来说,通常为系统提供版本 / 构建 / 发布的信息也是很有用的。 关于它的价值的更多信息,可以参见我之前在Self Identifying Software 发布的文章

在我们一直使用的Python 示例中,实现这一点只需要返回代表 ConfigProvider的字符串。

DNS 服务名称

当前人们普遍认为,当配置服务端点时,使用原始的 IP 地址是很不好的实践。 几乎全球(尽管很遗憾不是所有)都在使用 DNS 名称。 尽管如此,当这些 DNS 条目指向特定的服务器主机名的时候,人们还是有些问题需要处理。 这在初始部署应用程序的时候会很容易解决,但是如果你需要对其中一个服务进行硬件上的升级,那么会发生什么情况呢? 让我么考虑一下这些非常单纯的情况:

你拥有一个非常忙碌的站点,它使用中心数据库来存储客户信息。 这个数据库还被市场团队使用,为某些应用程序和其他的报表工具存储数据。 业务进展得很好,但是这台服务器(让我们称之为db02)现在有一些性能问题,并且需要用一台更好的,崭新的服务器(让我们称之为db04)来升级。 这会是一个漫长而痛苦的过程,因为你需要找出所有使用这台服务器的应用程序,并且明确当开始切换的时候,要如何重新对它们进行配置以使用新的服务器。

因此:

我们应该对所有的服务使用DNS 服务名称。 最简单的解决方案是为你的服务端点使用 DNS CNAME 记录。 在上述的示例中,我们会创建叫做clientdb的 CNAME 记录,它会指向db02,而所有使用该服务器的应用程序都会被配置为使用clientdb作为服务端点的主机名。 当我们需要将数据库迁移到新的服务器上时,在切换计划中的最终步骤只是更新 CNAME 值,使其指向db04。 这使得我们不仅不需要改变各个应用程序的配置,并且它还提供了非常便利的取消策略,如果由于某些原因,新的db04出现了问题,那么我们只需要将 CNAME 改回去,指向db02,直到问题解决。

基于 DNS 的环境设定

使用上述的DNS 服务名称会有少许副作用。 如果你拥有大量不同的针对开发和测试的客户端服务器,那么就会有很多 CNAME 值,它们看起来只有细微的差别。 比方说下面的情况:

  • 生产环境使用clientdb.example.com
  • 性能测试服务器使用clientdb-perf.example.com
  • 质量保证服务器使用clientdb-qa.example.com
  • 开发环境使用clientdb-dev.example.com

这种混乱会导致我们需要为每个环境都设置配置文件。

因此:

我们应该为你的服务器使用基于 DNS 的环境设定。 想要达到这个目的,首先你需要将顶级域切分成大量依赖于功能的子域,并且在每个子域中创建DNS 服务名称,指向与该服务相关的服务器。 基于上面的列表,我们可能会这样设置:

  • 生产环境使用clientdb.prod.example.com
  • 性能测试环境使用clientdb.perf.example.com
  • 质量保证环境使用clientdb.qa.example.com
  • 开发环境使用clientdb.dev.example.com

然后服务器会在子域之内解析,而这些子域是根据功能划分的。 也就是说,所有 QA 服务器首先会解析qa.example.com中的项目,如果检索失败,才会尝试在example.com中解析。 这让你可以针对客户端服务器主机名 (clientdb_)_ 拥有单独的配置值,它会在所有环境中正确地解析。 这种技术还会带来额外的好处,它仍然拥有在一般的顶级域中所定义的全局服务。

查看英文原文: 5 Configuration Management Best Practices


给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。

2010-08-06 00:008681
用户头像

发布了 340 篇内容, 共 126.2 次阅读, 收获喜欢 13 次。

关注

评论

发布
暂无评论
发现更多内容

MacOS数据库开发工具Navicat Premium 15 for Mac中文激活版

影影绰绰一往直前

Navicat Premium下载 Navicat Premium破解版 Navicat Premium 15

企业制作网站时为何香港云服务器成为首选?

一只扑棱蛾子

香港云服务器

对话在行人|合众思壮:基于用友BIP重塑业务应用,推进业财融合

用友BIP

对话在行人 数智化领先实践

11 月 11日,MatrixOne 社区邀请您来深圳办公室坐坐

MatrixOrigin

分布式数据库 云原生数据库 MatrixOrigin MatrixOne HTAP数据库

DAPP 燃烧铸币质押挖矿系统开发

l8l259l3365

2023云栖大会龙蜥操作系统专场成功举办

开放原子开源基金会

开源 云栖大会

医共体建设进入高峰期 区域医疗平台运营管理如何破局

用友BIP

医疗平台运营

专业fcpx视频剪辑推荐 Final Cut Pro最新激活中文版

mac大玩家j

Mac软件 fcpx插件 视频编辑器

如何为Affinity Publisher或Designer创建条形码?

Rose

Affinity Publisher 条形码设计

火山引擎云原生存储加速实践

字节跳动云原生计算

大数据 云原生 存储

Mac日程管理软件Fantastical 中文破解版 让日程管理更加便捷!

Rose

日程管理App Fantastical Mac中文版 Fantastical下载 Mac日历软件

调用API接口获取淘宝商品评论:方法与实战

Noah

用友全球司库十问(六)|新一代票据能力如何实现赋能企业票据管理?

用友BIP

全球司库 票据管理

架构实战营 - 模块四作业

王朝阳

架构实战营

Mac窗口管理软件合集|告别混乱屏幕,一切井井有条

Rose

mac窗口管理软件 Mac破解软件 苹果电脑分屏软件 Mac软件下载站

信息系统建设和企业税务管理的结合

用友BIP

税务管理

外贸独立站谷歌seo优化的8大技巧

九凌网络

endnote x9怎么和word关联?Word中用EndNote X9教程

Rose

Word 2021 许可证 endnote x9 文献写作管理软件

最新苹果系统 macOS 14 Sonoma 14.1正式版

iMac小白

MacOS 14 macOS Sonoma MacOS14系统

调用API接口获取淘宝店铺所有商品:详细指南与代码实践

Noah

人才驱动:水泥建材企业如何实现智能化人才管理

用友BIP

人才发展

如何成为前1%的程序员

互联网工科生

程序员 提升自我

即时通讯技术文集(第22期):IM安全相关文章(Part1) [共13篇]

JackJiang

网络编程 即时通讯 IM

华为云云容器引擎CCE产品文档带来4个升级,降低使用难度

华为云开发者联盟

云原生 华为云 华为云开发者联盟 华为云CCE容器服

交易所钱包系统开发

西安链酷科技

数字货币 dapp 交易所

吴翰清《计算》重磅来袭,为了可计算的价值,写给所有人!

博文视点Broadview

数电票如何管理?

用友BIP

数电票

llustrator 2024 for Mac最新中文破解版下载

影影绰绰一往直前

Illustrator 2024 Illustrator破解版下载 Illustrator mac Illustrator激活版

腾讯云入选 2023Gartner分布式混合基础设施魔力象限

ToB行业头条

昇腾迁移丨4个TensorFlow模型训练案例解读

华为云开发者联盟

人工智能 华为云 昇腾 华为云开发者联盟

ps2021一键换天空教程

Rose

ps2021破解版 一键换天空 ps2021下载 ps2021天空替换 Photoshop Mac破解版

配置管理的五项最佳实践_技术管理_Chris Read_InfoQ精选文章