关于django:优雅地处理svn / hg / git / etc中特定于站点的设置/配置?

Elegantly handle site-specific settings/configuration in svn/hg/git/etc?

我一直在寻找更好的方法来处理特定于站点的设置(在本例中是django settings.py文件)。

settings.py结构和字段相当一致,但开发人员的框、集成、QA、测试和生产环境之间的值不同。

什么是一种优雅的方式来控制源代码,同时允许在不同的框之间进行更改?

我还担心在源代码管理中使用敏感数据(如数据库密码),但我确实需要自动部署。

我们使用的示例:

  • settings.py设置公共值,然后根据主机名或用户名加载辅助设置文件。

  • 使用部署脚本将值注入settings.py文件。但这只会将问题转移到管理部署脚本,而不是settings.py脚本。

有人有特别优雅的方法吗?


创建一个main settings.py文件,其中应包括:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Pull in hostname-based changes.
import socket
HOSTNAME = socket.gethostname().lower().split('.')[0].replace('-','')

try:
    exec"from myproject.settings.host_%s import *" % HOSTNAME
except ImportError:
    pass

# Pull in the local changes.
try:
    from myproject.settings.local import *
except ImportError:
    pass

现在,为您关心的每个主机名创建一个新的设置文件。但这些真的很小。生产服务器的每个文件只包含:

1
from myproject.settings.production import *

您的登台服务器有:

1
from myproject.settings.staging import *

现在,您可以创建一个production.py文件,其中包含设置、staging.py等的生产覆盖。您可以为服务器扮演的每个角色创建新文件。

最后,您可以在任何具有本地重写的计算机(包括开发人员的计算机)上创建local.py文件,并将此文件标记为被源代码管理忽略,这样更改就不会被签入。

我们用这个结构已经很多年了,它工作得很好。


+1对于内德的回答,但要提及细微的变化。

我认为Django设置分为两个区域:项目和实例。

项目设置对所有实例都是通用的(开发、测试、生产、生产中的多个站点),实例设置仅对该特定服务器实例是本地的。

项目设置类似于INSTALLED_APPS(尽管本地设置也可能包括此内容以添加开发人员的django调试工具栏)、MIDDLEWARE_CLASSESTEMPLATE_LOADERS。实例设置包括数据库设置、MEDIA_URL设置等。

项目设置进入settings.py,实例设置进入local_settings.py,导入settings.pylocal_settings.py列在.gitignore中,项目设置存储在git中。

我试过其他几种不同的方法,但都是在这里结束的,因为它简单得多。

唯一一次我不喜欢这种设置是针对多个站点(使用django站点框架),它们最终会扩散到像sitename_settings.py这样的东西,这些东西导入sitename_local_settings.py等。

最后,我在Git中保留了一个local_settings_template.py,作为新实例的起点,并让devs跟踪他们可能需要对自己的本地设置进行的更改。


让我们将这两个截然不同的问题分开:1)管理特定于站点的设置;2)管理机密。

1)站点特定设置

所有版本(机密除外),甚至是特定于开发人员的设置。

使用django和许多其他软件,配置文件是一段可执行代码,这使得加载公共配置设置和覆盖需要覆盖的内容变得容易。这样你就可以保持干燥。

1
2
3
# settings_prod.py
from settings_base import *
... # override whatever needs to be overridden for production environment

现在你有了settings_base.pysettings_prod.pysettings_dev.pysettings_developper_john.py等。你怎么告诉django使用哪一个?

我相信,将适当的设置文件部署到服务器是部署脚本的一项任务。部署脚本将知道您要部署到生产服务器的主机prod17,因此它将动态生成一个settings.py文件,其外观如下:

1
2
# settings.py (generated by deployment script)
from settings_prod import *

另一种解决方案是在通用settings.py中使用该逻辑:它可以读取环境变量或获取主机名(或应用任何其他逻辑)并加载适当的设置模块:

1
2
3
4
5
# settings.py
import os
if os.environ["MY_APP_ENV"] =="prod":
    from settings_prod import *
elif ...

这里描述了我最喜欢的django设置解决方案。

对于任何其他没有配置文件那么灵活的软件,最好的选择可能是让部署脚本生成配置文件,可能使用模板(像chef或puppet这样的工具可以使这变得容易)。这样可以让您保持干燥:例如,假设一个软件需要一个扁平的config.ini文件,那么部署脚本可以读取common.iniproduction.ini文件,将它们适当地混合在一起,并生成一个config.ini准备部署到生产中。

管理秘密

首先,不要将密码存储在版本控制系统中。-)

管理秘密的一个解决方案是让部署脚本传输秘密。例如,Bob负责部署Web应用程序,他知道数据库的密码,所以当他启动部署脚本时,会提示他输入数据库密码,脚本会将其传输到服务器。或者部署脚本只是读取Bob计算机上文件中的密码并将其传输。这可能是最常见的解决方案。大多数情况下都可以。

1
2
              secrets
deployer ================> server

如果您需要自动创建虚拟机,并且不希望自动部署程序知道任何秘密,那么您可以在虚拟机映像中包含这些秘密。当然,首先必须有人将秘密包含在虚拟机映像中。

1
2
3
4
5
6
                  VM image including secrets
human deployer -------------------------------+
                                              |
                                              |
                    image_name                v
automated deployer ==============> Cloud Service ========> VM including secrets

这个解决方案的问题是,每次秘密更改时都需要生成一个新的VM映像。如果你想避免这种情况,那么你可能需要一个"秘密服务器":一个管理其他服务器秘密的服务器。那么,您需要在VM映像中包含的唯一秘密就是连接到"秘密服务器"所需的引导程序秘密。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
step 1:

               VM image including bootstrap secret
human deployer -----------------------------------+
                                                  |
                                                  |
                    image_name                    v
automated deployer ==================> Cloud Service ========> VM including secrets


step 2:

    bootstrap secret
   ==================>
VM                     Secret Server
   <==================
         secrets

例如,秘密服务器可以是Chef服务器,秘密可以存储在加密的数据包中,引导程序秘密将是解密这些包的密钥。


我处理这个问题的方法是为每个环境创建一个base settings.py文件,然后创建一个设置文件(例如dev_settings.py、live_settings.py)

在每个特定于环境的文件的顶部

1
from settings import *

然后,我可以简单地覆盖任何需要根据特定环境更改的设置。

为了确保每个环境使用正确的设置,我只需修改django_settings_模块环境变量。如何做到这一点取决于您如何部署django(mod_wsgi、mod_python等等…)