Estructurar Cloud Deployment Manager para su uso a gran escala

Cuando el sistema “Infraestructura como código” sobrepasa el ejemplo de “Hello World” sin planificación, el código tiende a desestructurarse. Las configuraciones no planificadas están codificadas. La capacidad de mantenimiento se reduce de manera drástica.

Usa este documento, junto con el ejemplo de código adjunto, para estructurar Cloud Deployment Manager de manera más eficaz y a gran escala.

Además, aplica la convención de nombres y las recomendaciones internas en todos tus equipos. Este documento está destinado a un público con conocimientos técnicos avanzados y se supone que conoces los fundamentos de Python, la infraestructura de Google Cloud, Deployment Manager y, en general, la infraestructura como código.

Antes de comenzar

Varios entornos con una sola base de código

Para implementaciones grandes con más de una docena de recursos, las recomendaciones estándares requieren que utilices una cantidad significativa de propiedades externas (parámetros de configuración), a fin de que puedas evitar la codificación de las strings y la lógica para crear plantillas genéricas. Muchas de estas propiedades están parcialmente duplicadas debido a entornos similares como, por ejemplo, un entorno de desarrollo, prueba o producción, y servicios similares. Por ejemplo, todos los servicios estándares se ejecutan en una pila LAMP similar. Seguir estas recomendaciones da como resultado un gran conjunto de propiedades de configuración con una gran cantidad de duplicaciones que pueden ser difíciles de mantener, lo que aumenta la posibilidad de que ocurra un error humano.

La siguiente tabla es una muestra de código para ilustrar las diferencias entre una configuración jerárquica y una configuración única por implementación. En la tabla, se destaca una duplicación común en una configuración única. Mediante la configuración jerárquica, la tabla muestra cómo mover las secciones repetidas a un nivel más alto en la jerarquía para evitar la repetición y disminuir las posibilidades de que tenga lugar un error humano.

Plantilla Configuración jerárquica sin redundancia Configuración única con redundancia

project_config.py

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

N/A

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' }

Para manejar mejor una base de código grande, utiliza un diseño jerárquico estructurado con una combinación en cascada de propiedades de configuración. Para hacerlo, utiliza varios archivos para la configuración, en lugar de solo uno. Además, trabaja con funciones auxiliares y comparte parte de la base de código con los integrantes de toda la organización.

El ejemplo de código que acompaña a este documento Organization_with_departments contiene una base de código con una estructura predefinida pero personalizable, una secuencia de comandos auxiliar a fin de combinar configuraciones, funciones auxiliares para nombres y un conjunto completo de configuraciones de ejemplo. Puedes encontrar este ejemplo funcional en el repositorio de GitHub de muestras de Deployment Manager.

La estructuración y la organización en cascada del código de manera jerárquica ofrecen varios beneficios:

  • Cuando divides la configuración en varios archivos, mejoras la estructura y la legibilidad de las propiedades. También puedes evitar su duplicación.
  • Diseñas la combinación jerárquica para que los valores se coloquen en cascada de una manera lógica, lo que permite crear archivos de configuración de nivel superior que son reutilizables en todos los proyectos o los componentes.
  • Defines cada propiedad solo una vez (aparte de reemplazar), lo que evita la necesidad de tratar con espacios de nombres en los nombres de propiedades.
  • Las plantillas no necesitan conocer el entorno real porque la configuración adecuada se carga según las variables correspondientes.

Estructura la base de código de manera jerárquica

Una implementación de Deployment Manager contiene una configuración YAML o un archivo de esquema, junto con varios archivos de Python. De manera conjunta, estos archivos forman la base de código de una implementación. Los archivos de Python pueden servir para propósitos diferentes. Puedes usar los archivos de Python como plantillas de implementación, archivos de código general (clases auxiliares), o archivos de código que almacenan propiedades de configuración.

Para estructurar la base de código de manera jerárquica, utiliza algunos archivos de Python como archivos de configuración, en lugar del archivo de configuración estándar. Este enfoque te brinda mayor flexibilidad que vincular la implementación a un solo archivo yaml.

Trata la infraestructura como código real

Un principio importante para el código limpio es No te repitas (DRY). Define todo solo una vez. Este enfoque hace que la base de código sea más limpia, más fácil de revisar y validar y más fácil de mantener porque, cuando una propiedad debe modificarse en solo un lugar, el riesgo de error humano disminuye.

Para una base de código más ligera con archivos de configuración más pequeños y una duplicación mínima, utiliza estos lineamientos para estructurar las configuraciones a fin de seguir el principio DRY.

Organizaciones, departamentos, entornos y módulos

El principio fundamental para estructurar la base de código de forma limpia y jerárquica es el uso de organizaciones, departamentos, entornos y módulos. Estos principios son opcionales y prolongables. Para obtener un diagrama de la jerarquía de la base de código de ejemplo, que sigue estos principios, consulta la jerarquía de configuración.

En el siguiente diagrama, un módulo se implementa en un entorno. La combinación de configuración selecciona los archivos de configuración apropiados en cada nivel según el contexto en el que se utiliza. También define de manera automática el sistema y el departamento.

Un módulo implementado en un entorno

En la siguiente lista, los números representan el orden de reemplazo:

  1. Propiedades organizativas

    Este es el nivel más alto en la estructura. En este nivel, puedes almacenar propiedades de configuración como organization_name, organization_abbreviation, que usas en la convención de nombres y funciones auxiliares que deseas compartir y aplicar en todos los equipos.

  2. Propiedades de los departamentos

    Las organizaciones contienen departamentos, si los tienes en tu estructura. En el archivo de configuración de cada uno, comparte las propiedades que no usan los otros, por ejemplo, department_name, cost_center.

  3. Propiedades de los sistemas (proyectos)

    En cada departamento existen sistemas. Un sistema es una pila de software bien definida, por ejemplo, la plataforma de comercio electrónico. No es un proyecto de Google Cloud, sino un ecosistema de servicios que funciona.

    En el nivel de sistema, el equipo tiene mucha más autonomía que en los niveles superiores. En ese nivel, puedes definir funciones auxiliares (por ejemplo, project_name_generator(), instance_name_generator(), instance_label_generator()) para los parámetros de todo el equipo y el sistema (por ejemplo, system_name, default_instance_size, naming_prefix).

  4. Propiedades de los entornos

    Es probable que tu sistema tenga varios entornos, como Dev, Test o Prod. De manera opcional, también puede tener QA y Staging, que son bastante similares entre sí. Lo ideal sería que usen la misma base de código y se diferencien solo en el nivel de configuración. En el nivel de entorno, puedes reemplazar propiedades como default_instance_size para las configuraciones Prod y QA.

  5. Propiedades de los módulos

    Si el sistema es grande, divídelo en varios módulos, en lugar de mantener un bloque monolítico grande. Por ejemplo, puedes pasar los módulos principales de las herramientas de redes y de la seguridad a bloques separados. También puedes separar las capas de backend, frontend y base de datos en módulos independientes. Los módulos están constituidos por plantillas que desarrollan terceros en las que puedes agregar solamente la configuración apropiada. En el nivel de módulo, puedes definir propiedades que sean relevantes únicamente para módulos particulares, lo que incluye propiedades diseñadas para reemplazar propiedades heredadas en el nivel de sistema. Los niveles de entorno y módulo son divisiones paralelas en un sistema, pero los módulos siguen los entornos en el proceso de combinación.

  6. Propiedades de los módulos específicas de los entornos

    Algunas de las propiedades de los módulos también pueden depender del entorno, como los tamaños de instancias, imágenes y extremos. Las propiedades de los módulos específicas de los entornos son el nivel más específico y el último punto de la combinación en cascada para reemplazar el valor definido previamente.

Clase auxiliar para combinar configuraciones

La clase config_merger es una clase auxiliar que carga de manera automática los archivos de configuración apropiados y combina su contenido en un solo diccionario.

Para usar la clase config_merger, debes proporcionar la siguiente información:

  • El nombre del módulo.
  • El contexto global, que contiene el nombre del entorno.

Una llamada a la función estática ConfigContext muestra el diccionario de configuración combinado.

En el siguiente código, se muestra cómo usar esta clase:

  • module = "frontend" especifica el contexto en el que se cargan los archivos de propiedades
  • El entorno se selecciona de manera automática de context.properties["envName"]
  • La configuración global

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

En segundo plano, esta clase auxiliar tiene que alinearse con las estructuras de configuración, cargar todos los niveles en el orden correcto y reemplazar los valores de configuración adecuados. Para cambiar los niveles o el orden de reemplazo, modifica la clase de combinación de configuración.

En el día a día, no es común que necesites hacer cambios a esta clase. Por lo general, edita las plantillas y los archivos de configuración adecuados y, a continuación, utiliza el diccionario de salida con todas las configuraciones.

La base de código de ejemplo contiene los siguientes tres archivos de configuración codificados:

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

Puedes crear los archivos de configuración de la organización y del departamento como vínculos simbólicos durante el inicio del repositorio. Estos archivos pueden residir en un repositorio de código separado, ya que este no forma lógicamente parte de la base de código del equipo del proyecto, pero se comparte en toda la organización y el departamento.

La combinación de configuración también busca archivos que coincidan con los niveles restantes de la estructura:

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

Archivo de configuración

Deployment Manager usa un archivo de configuración, que es un archivo único para una implementación específica. No se puede compartir en todas las implementaciones.

Cuando usas la clase config-merger, las propiedades de configuración están separadas por completo de este archivo de configuración porque no lo usas. En su lugar, usas una colección de archivos de Python, que te brinda mucha más flexibilidad en una implementación. Estos archivos también pueden compartirse en todas las implementaciones.

Cualquier archivo de Python puede contener variables, lo que te permite almacenar la configuración de una manera estructurada pero distribuida. Lo mejor es usar diccionarios con una estructura acordada. La combinación de configuración busca un diccionario denominado configs en cada archivo de la cadena de combinación. Esas configs distintas se combinan en una sola.

Durante la combinación, cuando aparece una propiedad con la misma ruta y el mismo nombre en los diccionarios varias veces, la combinación de configuración reemplaza esa propiedad. En algunos casos, este comportamiento es útil, como cuando un valor específico del contexto reemplaza un valor predeterminado. Sin embargo, existen muchos otros casos en los que deseas evitar el reemplazo de la propiedad. Para evitar el reemplazo de una propiedad, agrega un espacio de nombres separado para que sea único. En el siguiente ejemplo, agrega un espacio de nombres creando un nivel adicional en el diccionario de configuración, lo que crea un subdirectorio.

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

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

Clases auxiliares y convenciones de nombres

Las convenciones de nombres son la mejor manera de mantener bajo control la infraestructura de Deployment Manager. No quieres ver nombres vagos y genéricos, como my project o test instance.

El siguiente ejemplo es una convención de nombres en toda la organización para las instancias:

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"])

Proporcionar una función auxiliar facilita la asignación de nombre a cada instancia según la convención acordada. También simplifica la revisión del código porque ningún nombre de instancia proviene de otro lugar que no sea esta función. La función recoge de forma automática nombres de configuraciones de nivel superior. Este enfoque ayuda a evitar entradas innecesarias.

Puedes aplicar estas convenciones de nombres a la mayoría de los recursos de Google Cloud y a las etiquetas. Incluso las funciones más complejas pueden generar un conjunto de etiquetas predeterminadas.

Estructura de carpeta de la base de código de ejemplo

La estructura de carpeta de la base de código de ejemplo es flexible y personalizable. Sin embargo, está codificada de manera parcial en la combinación de configuración y en el archivo de esquema de Deployment Manager, por lo que, si haces modificaciones, debes reflejar esos cambios en la combinación de configuración y los archivos de esquema.

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

La carpeta global contiene archivos que se comparten en diferentes equipos de proyectos. Por cuestiones de simplicidad, la carpeta de configuración contiene la configuración de la organización y todos los archivos de configuración de los departamentos. En este ejemplo, no hay ninguna clase auxiliar independiente para los departamentos. Puedes agregar cualquier clase auxiliar a nivel de la organización o del sistema.

La carpeta global puede residir en un repositorio de Git separado. Puedes consultar los archivos en los sistemas individuales. También puedes utilizar vínculos simbólicos, pero estos podrían causar confusión o interrupciones en determinados sistemas operativos.

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

La carpeta de sistemas contiene uno o más sistemas diferentes. Los sistemas están separados entre sí y no comparten configuraciones.

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

La carpeta de configuración contiene todos los archivos de configuración que son exclusivos de este sistema, y también hace referencia a las configuraciones globales mediante vínculos simbólicos.

├── 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'
    }
  }
}

En la carpeta auxiliar, puedes agregar más clases auxiliares y consultar las clases globales.

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

En la carpeta de plantillas, puedes almacenar o consultar las plantillas de Deployment Manager. Los vínculos simbólicos también funcionan en ese entorno.

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

Utilizar la base de código de ejemplo

La mejor manera de comenzar a aplicar esta práctica jerárquica como base de tu infraestructura como código es clonar la base de código de ejemplo y copiar el contenido de la carpeta hierarchical_configuration.

  1. Verifica el repositorio de ejemplo.

    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. Para configurar el sistema, utiliza los siguientes vínculos simbólicos para hacer referencia a los archivos globales en el contexto local.

    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. Selecciona el departamento apropiado en la lista global.

    ln -sf ../../../global/configs/Department_Data_config.py configs/department_config.py
    
  4. A fin de establecer el contexto de entorno correcto, usa la marca --properties para especificar la propiedad envName. Esta propiedad te permite ejecutar el mismo código orientado a entornos diferentes con el mismo comando. [MY-PROJECT-ID] representa el ID de tu proyecto de Google Cloud.

    [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
    

Recomendaciones

Aquí se recopilan algunas prácticas recomendadas adicionales para ayudarte a estructurar el código de manera jerárquica.

Archivos de esquema

En el archivo de esquema, es un requisito de Cloud Deployment Manager enumerar cada archivo que uses de cualquier manera durante la implementación. Agregar una carpeta completa hace que el código sea más corto y más genérico.

  • Clases auxiliares:
- path: helper/*.py
  • Archivos de configuración:
- path: configs/*.py
- path: configs/*/*.py
  • Importaciones masivas (estilo glob):
gcloud config set deployment_manager/glob_imports True

Varias implementaciones

Se recomienda que el sistema contenga varias implementaciones, lo que significa que van a usar los mismos conjuntos de configuraciones, incluso si son módulos diferentes, por ejemplo, herramientas de redes, firewalls, backend, frontend. Es posible que necesites acceder al resultado de estas implementaciones desde otra implementación. Puedes consultar el resultado de la implementación una vez que esté listo y guardarlo en la carpeta de configuraciones. Puedes agregar estos archivos de configuración durante el proceso de combinación.

Los vínculos simbólicos son compatibles con los comandos gcloud deployment-manager, y los archivos vinculados se cargan de manera correcta. Sin embargo, los vínculos simbólicos no se admiten en todos los SO.

Jerarquía de configuración

En el siguiente diagrama, se incluye una descripción general de los diferentes niveles y sus relaciones. Cada rectángulo representa un archivo de propiedad, como lo indica el nombre del archivo en rojo.

Jerarquía de configuración con niveles diferentes y sus relaciones destacadas.

Orden de combinación adaptado al contexto

La combinación de configuración selecciona los archivos de configuración apropiados en cada nivel según el contexto dentro del cual se usa cada archivo. El contexto es un módulo que implementas en un entorno. Este contexto define el sistema y el departamento de forma automática.

En el siguiente diagrama, los números representan el orden de reemplazo en la jerarquía:

Diagrama del orden de reemplazo

Qué sigue