让我们讨论如何配置Python应用程序,尤其是那些可能存在于多个环境中的应用程序——开发、暂存、生产等……应用程序使用的工具和框架并不是特别重要,因为我采用的方法下面的概述是基于纯Python的。这种方法源于使用Django设置的烦恼,它也是我将要处理的任何Python应用程序的首选。概述:Python模块和包我最喜欢的Python特性之一是构成应用程序的文件和目录与您在代码中导入和使用它们的方式之间存在一对一的对应关系。例如,给定这个import语句:我们可以推断出以下目录结构:许多语言和框架都依赖于这个新概念,包括Clojure和ES6。在我们的示例中,Python将utils目录视为一个包。当你把一个空的__init__.py放在一个目录中时,那个目录就变成了一个包。作为一名Python黑客,您可能遇到过这样一种常见情况:您的utils.py文件最终变得太大,因此您将其拆分到包含许多较小文件的utils//目录中。发生这种情况时,我们可以执行以下操作:现在我们已经看到Python包是由目录中是否存在__init__.py文件决定的……但是如果该文件不为空怎么办?In__init__.py因为它只是一个普通的旧Python文件,所以你可以在其中放入任何你想要的东西,它会在第一次导入包时执行。>>>旁注通常我们不鼓励将代码放在__init__.py中,因为它会对导入产生意想不到的副作用。你可以自己测试一下。创建一个名为foo的目录,并为其提供一个空的__init__.py文件。从同一目录下的PythonREPL运行以下代码:这里没有输出就好了,说明语句运行成功。现在让我们编辑我们的__init__.py文件以包含以下代码:sys.exit()通常用于使进程以特定状态退出。在新的REPL中重新运行相同的实验,您会发现Pythonshell在导入后立即退出。在较大的应用程序中,效果会更明显:整个应用程序将退出。通过这种方式,我们了解了基本原理并了解了如何恶意使用此功能。也许我们可以永远使用它?多个环境和十二要素应用程序您的应用程序可能存在于多个环境中。您的本地开发环境可能是第一个,您可能有一个位于Jenkins或其他CI平台上的测试环境。您的代码已部署到生产或实时环境。某些系统可能具有在实际操作之前使用的模拟环境。即使您只认为自己是一个爱好者,在本地开发代码并将其部署到vps或类似Heroku的平台也意味着您要兼顾多个环境。我在构建应用程序时遵循的规则之一是我应该能够将代码库部署到任何环境(无需修改),前提是我们有办法告诉系统它在哪里运行。与此相比,为每个部署目标构建多个部分需要额外的时间和复杂性来构建和维护。这些部分通常设计为在单一目标环境中运行,因此通常很难或不可能在本地或测试模式下运行它们。著名的十二因素法也认同这个观点,认为所有的配置都应该作为环境变量存在。我在一定程度上同意这一点,但有时会倾向于将所有内容都设置为环境变量,这很快就会变得无法支持。如果系统的每个旋钮和转盘都是一个环境变量,您会发现最终会将变量组合存储在某处以供运行或调试。看到这里有问题了吗?我们正在将配置移出一个区域(代码,通常保存在版本控制中)并将它们移到更容易出现错误和人为错误的区域。我用来划清界线的一般准则是:不经常更改或显着影响系统行为的静态内容应该存在于代码中。经常更改或应保密的动态内容(API密钥/凭据)应该存在于代码之外。我们如何切换环境?为了让应用程序在环境之间改变其行为,我们需要一种方法来告诉它它在哪里运行。依靠环境变量(查看模式?),我倾向于为此目的使用ENV(或变体)。Ruby/Rails生态系统使用RACK_ENV或RAILS_ENVJavascript项目通常会使用NODE_ENV>>>Margin在更改底层框架或工具的运行时行为的标志与指定应用程序操作模式的标志之间画一条线很重要.例如,有时简单的DEBUG=True/False不够好。我最近为一个客户完成了一个项目,约定如下:我本地的开发环境没有设置ENV变量,所以系统默认推断开发环境。AWSCodePipeline上的测试环境使用ENV=test,EC2上的生产环境使用ENV=production注意:重要的是要考虑不设置此变量的后果。这会是灾难性的吗?例如,应用程序能否在生产集群内以DEV模式启动并最终向公众显示回溯?对于某些应用程序,默认值应该是生产。这里没有正确或错误的答案,但需要考虑。最终目标从开发人员的角度来看,我们希望像这样访问我们的配置:上面的导入行不包含任何暗示我们所处环境的内容。我们在任何地方都看不到开发或生产一词。相反,我们只导入我们需要的,并让配置系统决定它来自哪里。我们利用文件系统和语言本身来提供读取配置的API。在幕后,这就是配置目录在磁盘上的样子:common.py包含我们所有的公共或共享配置。这些东西在不同的环境下差别不大。如果愿意,您可以将其称为基本配置或共享配置。environments/development.py包含开发配置。该文件可以从版本控制中排除,以便团队中的每个开发人员都可以实现自己的配置设置。environments/(production|staging).py包含特定于每个环境的配置。让我们看看common.py:这是一个人为的例子,所以请不要太深入细节。请务必注意,这是一个相当静态的配置,不会经常更改。现在让我们看一下environment/development.py:我们首先导入公共配置,以便默认继承所有公共配置。现在我们可以添加、替换或增加参数,而无需从父配置中复制粘贴。为了支持本地开发,我可以自定义在我的环境中使用的AWS资源。系统的其余部分没有改变,但现在我的本地系统使用我自己的Dynamo表和S3存储桶。因为该文件不受版本控制,所以我可以安全地存储机密信息,例如我自己的GOOGLE_CLIENT_凭据。因为公共DEFAULT_NAMESERVERS是可访问的,所以我可以扩展它们而不是将任何公共值复制粘贴到我自己的配置中。在生产环境中,systemd命令用于重启应用程序以响应某些管理操作。由于我的Mac没有systemd,我用一个简单的空操作替换了系统重启命令,完全避免了这个问题。它是如何工作的回到我们的config/__init__.py文件,我们可以在这里实现什么来实现它?这实际上非常简单:我们利用导入时评估来动态获取必要的配置。下面一步步来:1.首先我们导入importlib模块(文档),它为我们提供了一些方便的用代码导入代码的工具。2.使用我们既定的约定——ENV环境变量——我们得到当前运行环境的名称。3.如果没有设置环境,我们默认选择开发,但如前所述,这个决定会因系统而异。我们甚至可以考虑阻止应用程序启动,除非定义了这个变量。下面是一个示例:4.接下来我们使用importlib.import_module函数将包含环境特定代码的模块加载到局部变量模块中。5.最后,我们更新这个模块的全局变量,将development.py文件中的设置合并进去。6.最终,您会看到一些方便的工具(a-laRails),可以更轻松地根据上下文切换特定逻辑。它们作为函数保存,以便实现与该模块隔离,而不是在任何使用它的地方隔离。这种方法很大程度上受到RubyonRails配置的启发,它实现了非常相似的外观和感觉,只是底层实现不同。一个真实世界的例子为了提供另一个真实世界的例子,这里是这个站点的配置:首先,这是我的配置目录的确切目录结构:development.pylocallyuseproduction.pyforHerokutest.pyforThereispytest'slocal包含静态配置的单元测试base.py:项目中其他地方使用的集中式日志记录格式。通用目录和帮助函数,使路径相关的工作更容易。我的服务的时区。当页面不提供自己的标题时使用的默认标题。在development.py中,站点标题被覆盖,因此当我编辑时我知道我正在查看本地副本。我还定义了一些与生产环境截然不同的本地Redis配置。SENTRY_DSN只定义在production.py中,不在base或其他环境中。这是为了防止在开发或测试情况下激活哨兵(集中式错误记录)。在Heroku上,Redis连接详细信息来自URL,因此我们在此处进行配置。最后,为了演示如何在应用程序的其他地方使用此设置,让我们看看如何建立Redis连接:注意最后一行:RedisManager.from_config()用于隔离问题。RedisManager的其余部分不知道配置中的数据是什么样的,也不需要。这是配置层和系统其余部分之间的切换点。结论我在我所有的Python项目中都使用了这种方法,并且还没有发现这种方法(或其变体)不起作用的情况。我们可以灵活地创建无限数量的环境。例如,如果我们想为拉取请求启动一个暂存环境:我们只需要使用“cpenvironments/staging.pyenvironments/PR_402.pyandENV=PR_402”。在本地开发时,我们可以通过在系统前面加上ENV=production以生产模式运行系统,反之亦然,我们可以在其他任何地方以开发或测试模式运行软件。开发人员可以通过查看每个环境被覆盖的配置来快速收集每个环境之间的主要差异。这使得将新团队成员加入您的代码库变得更加容易。同样,团队中的每个开发人员都可以拥有自己独特的配置。这不会对中央配置产生太大影响,因为您的系统有一些与其他系统不同的设置。我们可以通过将environments/test.py中的某些变量显式设置为None来保护我们的测试环境,以避免意外访问生产环境资源。我们消除了在各种CLI工具(如Docker等)和通用Python包之间传递大型键/值配置映射的负担,因此几乎没有学习曲线和与其他Python工具的互操作性问题。我们避免了支持外部库/依赖项的成本。总而言之,这种方法不是很吸引人,这正是我们在构建可靠、可维护和高效的系统时想要的。通过一些普通的旧Python和几行特殊代码,我们在系统配置中释放了很多灵活性和强大功能。
