设计 Deployment Manager 结构供大规模使用

当您的“基础架构即代码”系统在没有规划的情况下超过“Hello World”示例规模时,代码往往会变得结构混乱。没有计划的配置采用硬编码。可维护性会急剧下降。

使用本文档及其随附的代码示例,更高效地对部署进行大规模结构设计。

此外,请在您的团队中实施命名惯例和内部最佳做法。本文档适用于技术水平较高的读者,并假设您基本了解 Python、Google Cloud 基础架构、Deployment Manager 以及基础架构即代码。

准备工作

具有单个代码库的多个环境

对于有十几个以上资源的大型部署,标准的最佳做法要求您使用大量外部属性(配置参数),以便您可以避免将字符串和逻辑硬编码到通用模板。由于类似的环境(例如开发、测试或生产环境)和类似的服务,许多属性有部分重复。例如,所有标准服务都在相似的 LAMP 堆栈上运行。遵循这些最佳做法会导致具有大量重复项的大量配置属性变得难以维护,从而增加了出现人为错误的可能性。

下表是一个代码示例,说明了每个部署的分层配置与单个配置之间的不同之处。该表提供了单个配置中的常见重复项。通过使用分层配置,该表显示了如何将重复部分移动到层次结构中更高的层级,以避免重复并减少人为错误的可能性。

模板 没有冗余的分层配置 有冗余的单个配置

project_config.py

config = { 'ProjectId': 'qwerty123456', 'ProjectOwner': 'Bob', 'ProjectAbbrevation': 'SNP' }

不适用

frontend_config.py

config = {'ServiceName': 'frontend'}

config = { 'ProjectId': 'qwerty123456', 'ProjectOwner': 'Bob', 'ProjectAbbrevation': 'SNP', 'ServiceName': 'frontend' }

backend_config.py

config = {'ServiceName': 'backend'}

config = { 'ProjectId': 'qwerty123456', 'ProjectOwner': 'Bob', 'ProjectAbbrevation': 'SNP', 'ServiceName': 'backend' }

db_config.py

config = {'ServiceName': 'db'}

config = { 'ProjectId': 'qwerty123456', 'ProjectOwner': 'Bob', 'ProjectAbbrevation': 'SNP', 'ServiceName': 'db' }

为了更好地处理大型代码库,请使用具有配置属性级联合并的结构化分层布局。为此,您需要为配置使用多个文件,而不是仅使用一个文件。此外,您还可以使用辅助函数并在整个组织中共享部分代码库。

本文档随附的代码示例 Organization_with_departments 包含具有预定义但可自定义结构的代码库、用于合并配置的辅助脚本、用于命名的辅助函数以及一整套示例配置。您可以在 Deployment Manager 示例 GitHub 代码库中找到此有效示例。

以分层方式设计代码结构和进行级联可提供多种优势:

  • 将配置拆分为多个文件可以提高属性的结构和可读性,还可避免重复项。
  • 您可以将分层合并设计为以逻辑方式级联值,创建可跨项目或组件重复使用的顶级配置文件。
  • 您只需定义每个属性一次(除了改写),无需在属性名称中处理命名空间。
  • 您的模板不需要了解实际环境,因为系统将根据相应的变量加载相应的配置。

以分层方式设计代码库结构

Deployment Manager 部署包含一个 YAML 配置或架构文件,以及多个 Python 文件,它们一起构成了部署的代码库。这些 Python 文件有多种用途,可以用作部署模板、一般代码文件(辅助类)或者存储配置属性的代码文件。

如需以分层方式设计代码库结构,请使用一些 Python 文件作为配置文件,而不是标准配置文件。与将部署关联到单个 YAML 文件相比,此方法可提供更高的灵活性。

将您的基础架构视为真实的代码

简洁代码的一个重要原则是避免重复代码 (DRY)。 所有内容都只定义一次。这种方法使代码库更加简洁、更易于查看和验证,并且更易于维护。只需在一个地方修改属性,降低了出现人为错误的风险。

对于配置文件较小并且只有极少量重复的小型代码库,请遵循这些准则来设计配置结构以遵循 DRY 原则。

组织、部门、环境和模块

要以分层的方式设计简洁的代码库结构,基本原则是使用组织、部门、环境和模块。 这些原则是可选且可扩展的。如需遵循这些原则的示例代码库层次结构图,请参阅配置层次结构

在下图中,一个环境中部署了一个模块。配置合并程序根据配置文件将用到的上下文,在每个层级上选择适当的配置文件。此外,配置合并程序还自动定义了系统和部门。

一个部署在环境中的模块

在下表中,数字代表改写顺序:

  1. 组织属性

    这是结构中的最高层级。在此层级,您可以存储配置属性(例如您在命名惯例中使用的 organization_nameorganization_abbreviation),以及您希望在所有团队中共享和强制执行的辅助函数。

  2. 部门属性

    如果您的结构中有部门,组织中会包含部门。在每个部门的配置文件中,共享其他部门不使用的属性,例如 department_namecost_center

  3. 系统(项目)属性

    每个部门都包含系统。系统是定义完善的软件堆栈,例如您的电子商务平台。系统是 Google Cloud 项目,而是功能性服务生态系统。

    在系统层级,您的团队拥有更高的自主权,远超其上层结构。在这里,您可以为团队和系统范围的参数(例如 system_namedefault_instance_sizenaming_prefix)定义辅助函数(例如 project_name_generator()instance_name_generator()instance_label_generator())。

  4. 环境属性

    您的系统可能有多个环境,例如 DevTestProd 以及可能存在的 QAStaging,这些环境彼此非常相似。理想情况下,它们使用同一个代码库,仅在配置层级上有所不同。在环境层级,您可以为 ProdQA 配置改写 default_instance_size 等属性。

  5. 模块属性

    如果您的系统很大,可将其拆分为多个模块,而不是维持一个大的单体块。例如,您可以将核心网络和安全模块移至单独的块中。您还可以将后端、前端和数据库层拆分为单独的模块。模块是由第三方开发的模板,您只需在其中添加适当的配置。在模块层级,您可以定义仅与特定模块相关的属性,包括旨在改写继承的系统级属性的属性。环境和模块级别是系统中的并行部分,但在合并过程中先合并环境再合并模块。

  6. 特定于环境的模块属性

    某些模块属性可能还取决于环境,例如实例大小、映像、端点。特定于环境的模块属性是最具体的层级,也是用于改写先前定义的值级联合并过程中的最后一点。

用于合并配置的辅助类

config_merger 类属于辅助类,可自动加载适当的配置文件并将文件内容合并为一个字典。

要使用 config_merger 类,您必须提供以下信息:

  • 模块名称。
  • 全局环境,包含环境名称。

调用 ConfigContext 静态函数将返回合并的配置字典。

以下代码显示如何使用此类:

  • module = "frontend" 指定要加载属性文件的上下文环境。
  • 环境自动从 context.properties["envName"] 提取。
  • 全局配置。

    cc = config_merger.ConfigContext(context.properties, module)
    
    print cc.configs['ServiceName']
    

在后台,此辅助类必须与您的配置结构保持一致,以正确的顺序加载所有层级,并改写相应的配置值。要更改层级或改写顺序,请修改配置合并程序类。

在日常和常规使用中,您一般不需要更改此类。通常情况下,您只需修改模板和相应的配置文件,然后使用包含所有配置的输出字典。

示例代码库包含以下三个硬编码的配置文件:

  • org_config.py
  • department_config.py
  • system_config.py

您可以在代码库启动时将组织和部门配置文件创建为符号链接。这些文件可以存在于单独的代码库中,因为从逻辑角度来看这不是项目团队代码库的一部分,而是在整个组织和部门之间共享。

配置合并程序还会查找与结构的其余级层匹配的文件:

  • envs/[environment name].py
  • [environment name]/[module name].py
  • modules/[module name].py

配置文件

Deployment Manager 使用一个配置文件,该文件是特定部署的单个文件。它无法在部署之间共享。

使用 config-merger 类时,配置属性与此配置文件完全分离,因为您没有使用它。您使用的是一组 Python 文件,让您可以更加灵活地进行部署。这些文件也可以跨部署共享。

所有 Python 文件都可以包含变量,让您能够以结构化但分布式的方式存储配置。最佳方法是使用具有约定结构的字典。配置合并程序会在合并链中的每个文件中查找名为 configs 的字典。这些单独的 configs 会合并为一个。

在合并期间,当字典中多次出现具有相同路径和名称的属性时,配置合并程序将改写该属性。 在某些情况下,此行为很有用,例如当特定于上下文的值改写默认值时。但是,在很多其他情况下,您需要避免改写属性。要防止改写属性,请为其添加单独的命名空间以使其唯一。在以下示例中,您将通过在配置字典中创建一个额外的层级来添加命名空间,从而创建一个子字典。

config = {
    'Zip_code': '1234'
    'Count': '3'
    'project_module': {
        'admin': 'Joe',
    }
}

config = {
    'Zip_code': '5555'
    'Count': '5'
    'project_module_prod': {
        'admin': 'Steve',
    }
}

辅助类和命名惯例

命名惯例是控制 Deployment Manager 基础架构的最佳方式。您不想使用任何模糊或通用名称,例如 my projecttest instance

以下示例是实例的组织范围命名惯例:

def getInstanceName(self, name):
  return '-'.join(self.configs['Org_level_configs']['Org_Short_Name'],
                  self.configs['Department_level_configs']['Department_Short_Name'],
                  self.configs['System_short_name'],
                  name,
                  self.configs["envName"])

提供辅助函数可实现根据约定的惯例轻松命名每个实例,还便于您轻松检查代码,因为所有实例名称均来自于此函数。该函数会自动从更高层级的配置中获取名称。此方法有助于避免不必要的输入。

您可以将这些命名惯例应用于大多数 Google Cloud 资源和标签。更为复杂的函数甚至可以生成一组默认标签。

示例代码库的文件夹结构

此示例代码库的文件夹结构非常灵活且可自定义。 但是,由于该文件夹结构部分硬编码到配置合并程序和 Deployment Manager 架构文件中,因此,如果进行修改,您需要在配置合并程序和架构文件中反映这些更改。

├── global
│   ├── configs
│   └── helper
└── systems
    └── my_ecom_system
        ├── configs
        │   ├── dev
        │   ├── envs
        │   ├── modules
        │   ├── prod
        │   └── test
        ├── helper
        └── templates
    

全局文件夹包含不同项目团队共享的文件。 为简单起见,配置文件夹包含组织配置和所有部门的配置文件。在本示例中,部门没有单独的辅助类。您可以在组织或系统层级添加任何辅助类。

全局文件夹可以位于单独的 Git 代码库中。您可以从各个系统引用其文件。您也可以使用符号链接,但它们可能会在某些操作系统中造成混乱或中断。

├── configs
│   ├── Department_Data_config.py
│   ├── Department_Finance_config.py
│   ├── Department_RandD_config.py
│   └── org_config.py
└── helper
    ├── config_merger.py
    └── naming_helper.py

此系统文件夹包含一个或多个不同的系统。这些系统彼此独立,并且不共享配置。

├── configs
│   ├── dev
│   ├── envs
│   ├── modules
│   ├── prod
│   └── test
├── helper
└── templates

此配置文件夹包含此系统特有的所有配置文件,还通过符号链接引用全局配置。

├── department_config.py -> ../../../global/configs/Department_Data_config.py
├── org_config.py -> ../../../global/configs/org_config.py
├── system_config.py
├── dev
│   ├── frontend.py
│   └── project.py
├── prod
│   ├── frontend.py
│   └── project.py
├── test
│   ├── frontend.py
│   └── project.py
├── envs
│   ├── dev.py
│   ├── prod.py
│   └── test.py
└── modules
    ├── frontend.py
    └── project.py

Org_config.py:

config = {
  'Org_level_configs': {
    'Org_Name': 'Sample Inc.',
    'Org_Short_Name': 'sampl',
    'HQ_Address': {
      'City': 'London',
      'Country': 'UK'
    }
  }
}

在此辅助文件夹中,您可以添加更多辅助类和引用全局类。

├── config_merger.py -> ../../../global/helper/config_merger.py
└── naming_helper.py -> ../../../global/helper/naming_helper.py

在此模板文件夹中,您可以存储或引用 Deployment Manager 模板。符号链接也适用于文件夹。

├── project_creation -> ../../../../../../examples/v2/project_creation
└── simple_frontend.py

使用示例代码库

要开始将此分层做法应用为基础架构即代码的基础,最佳方法是克隆示例代码库并复制 hierarchical_configuration 文件夹的内容。

  1. 查看示例代码库。

    git clone https://github.com/GoogleCloudPlatform/deploymentmanager-samples.git
    cd deploymentmanager-samples/community/hierarchical_configuration/Organization_with_departments/systems/my_ecom_system
    gcloud config set deployment_manager/glob_imports True
    
  2. 要设置系统,请使用以下符号链接将全局文件引用到本地上下文。

    ln -sf ../../../global/helper/config_merger.py helper/config_merger.py
    ln -sf ../../../global/helper/naming_helper.py helper/naming_helper.py
    ln -sf ../../../global/configs/org_config.py configs/org_config.py
    
  3. 从全局列表中选择适当的部门。

    ln -sf ../../../global/configs/Department_Data_config.py configs/department_config.py
    
  4. 要设置正确的环境上下文,请使用 --properties 标志指定 envName 属性。您可以利用此属性使用相同的命令运行针对不同环境的相同代码。[MY-PROJECT-ID] 代表您自己的 Google Cloud 项目的 ID。

    [MY-PROJECT-ID]
    gcloud deployment-manager deployments create hierarchy-org-example-dev
    --template env_demo_project.py --properties=envName:dev
    gcloud deployment-manager deployments create hierarchy-org-example-test
    --template env_demo_project.py --properties=envName:test
    gcloud deployment-manager deployments create hierarchy-org-example-prod
    --template env_demo_project.py --properties=envName:prod
    

最佳实践

以下最佳做法可帮助您以分层方式设计代码结构。

架构文件

在架构文件中,Deployment Manager 要求列出在部署期间以任何方式使用的每个文件。添加整个文件夹可使您的代码更短、更通用。

  • 辅助类:
- path: helper/*.py
  • 配置文件:
- path: configs/*.py
- path: configs/*/*.py
  • 批量(glob 样式)导入
gcloud config set deployment_manager/glob_imports True

多个部署

最佳做法是让系统包含多个部署,即部署使用相同的配置集(即使它们属于不同的模块),例如网络、防火墙、后端、前端。您可能需要从其他部署访问这些部署的输出。您可以在部署就绪后查询此部署的输出,并将其保存在配置文件夹下。您可以在合并过程中添加这些配置文件。

gcloud deployment-manager 命令支持符号链接,并且链接文件会正确加载。但是,并不是所有操作系统都支持符号链接。

配置层次结构

下图是不同层级及其关系的概览。每个矩形代表一个属性文件,如红色文件名所示。

具有不同层级并突出其关系的配置层次结构。

上下文感知合并顺序

配置合并文件根据每个文件使用时的上下文选择每个层级上的相应配置文件。上下文指您在环境中部署的模块。此上下文自动定义系统和部门。

在下图中,数字代表层次结构中的改写顺序:

改写顺序图

后续步骤