Estructurar Deployment Manager para utilizarlo a gran escala

Cuando tu sistema de infraestructura como código va más allá del ejemplo "Hola, mundo" sin planificación, el código tiende a desestructurarse. Las configuraciones no planificadas están codificadas. La mantenibilidad se reduce drásticamente.

Usa este documento para estructurar tus implementaciones de forma más eficiente y a gran escala.

Además, aplica tu convención de nomenclatura y tus prácticas recomendadas internas en todos tus equipos. Este documento está dirigido a un público con conocimientos técnicos avanzados y presupone que tiene conocimientos básicos de Python, Google Cloud infraestructura, Deployment Manager y, en general, infraestructura como código.

Antes de empezar

Varios entornos con una sola base de código

En las implementaciones grandes con más de una docena de recursos, las prácticas recomendadas estándar requieren que uses una cantidad significativa de propiedades externas (parámetros de configuración) para evitar codificar cadenas y lógica en plantillas genéricas. Muchas de estas propiedades están duplicadas parcialmente debido a entornos similares, como el entorno de desarrollo, de prueba o de producción, y a servicios similares. Por ejemplo, todos los servicios estándar se ejecutan en una pila LAMP similar. Si sigues estas prácticas recomendadas, tendrás 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 probabilidad de que se produzcan errores humanos.

En la siguiente tabla se muestra un fragmento de código para ilustrar las diferencias entre una configuración jerárquica y una única configuración por implementación. En la tabla se destaca una duplicación habitual en una sola configuración. Mediante la configuración jerárquica, la tabla muestra cómo mover las secciones repetidas a un nivel superior de la jerarquía para evitar repeticiones y reducir las probabilidades de que se produzcan errores humanos.

Template 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 gestionar mejor una base de código grande, usa un diseño jerárquico estructurado con una fusión en cascada de propiedades de configuración. Para ello, se usan varios archivos de configuración en lugar de uno solo. Además, trabajas con funciones auxiliares y compartes parte de la base de código en toda tu organización.

Estructurar y aplicar en cascada el código de forma jerárquica ofrece varias ventajas:

  • Si divides la configuración en varios archivos, mejorarás la estructura y la legibilidad de las propiedades. También evitas duplicarlos.
  • Diseña la combinación jerárquica para que los valores se apliquen en cascada de forma lógica. Para ello, crea archivos de configuración de nivel superior que se puedan reutilizar en proyectos o componentes.
  • Solo se define cada propiedad una vez (excepto las anulaciones), por lo que no es necesario usar espacios de nombres en los nombres de las propiedades.
  • Tus plantillas no necesitan conocer el entorno real, ya que la configuración adecuada se carga en función de las variables correspondientes.

Estructurar el código base de forma jerárquica

Un despliegue de Deployment Manager contiene un archivo de configuración YAML o un archivo de esquema, junto con varios archivos Python. En conjunto, estos archivos forman la base de código de un despliegue. Los archivos de Python pueden tener diferentes finalidades. Puedes usar los archivos Python como plantillas de despliegue, archivos de código general (clases auxiliares) o archivos de código que almacenan propiedades de configuración.

Para estructurar tu base de código de forma jerárquica, puedes usar algunos archivos de Python como archivos de configuración en lugar del archivo de configuración estándar. Este enfoque te ofrece más flexibilidad que vincular la implementación a un solo archivo YAML.

Tratar la infraestructura como código real

Un principio importante para escribir código limpio es No te repitas (DRY). Definir todo solo una vez. Con este enfoque, la base de código es más limpia, fácil de revisar y validar, y más sencilla de mantener. Cuando una propiedad solo se tiene que modificar en un lugar, se reduce el riesgo de error humano.

Para tener una base de código más ligera con archivos de configuración más pequeños y una duplicación mínima, sigue estas directrices para estructurar tus configuraciones de acuerdo con el principio DRY.

Organizaciones, departamentos, entornos y módulos

Los principios fundamentales para estructurar tu base de código de forma limpia y jerárquica son usar organizaciones, departamentos, entornos y módulos. Estos principios son opcionales y se pueden ampliar. Para ver 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, se implementa un módulo en un entorno. El combinador de configuraciones selecciona los archivos de configuración adecuados en cada nivel en función del contexto en el que se utilice. También define automáticamente el sistema y el departamento.

Un módulo desplegado en un entorno

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

  1. Propiedades de la organización

    Este es el nivel más alto de tu estructura. En este nivel, puedes almacenar propiedades de configuración como organization_name y organization_abbreviation, que se usan en tu convención de nomenclatura, y funciones auxiliares que quieras 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 departamento, comparte las propiedades que no utilicen otros departamentos. Por ejemplo, department_name o cost_center.

  3. Propiedades del sistema (proyecto)

    Cada departamento contiene sistemas. Un sistema es una pila de software bien definida, como tu plataforma de comercio electrónico. No es un proyecto, sino un ecosistema de servicios que funciona.Google Cloud

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

  4. Propiedades del entorno

    Es probable que tu sistema tenga varios entornos, como Dev, Test o Prod (y, opcionalmente, QA y Staging), que sean bastante similares entre sí. Lo ideal es que usen la misma base de código y solo se diferencien en el nivel de configuración. A nivel de entorno, puede sobrescribir propiedades como default_instance_size para las configuraciones Prod y QA.

  5. Propiedades de los módulos

    Si tu sistema es grande, divídelo en varios módulos en lugar de mantenerlo como un bloque monolítico grande. Por ejemplo, puedes mover las funciones principales de redes y seguridad a bloques independientes. También puedes separar las capas de backend, frontend y base de datos en módulos independientes. Los módulos son plantillas desarrolladas por terceros en las que solo se añade la configuración adecuada. A nivel de módulo, puedes definir propiedades que solo sean relevantes para módulos concretos, incluidas las propiedades diseñadas para sobrescribir las propiedades heredadas a nivel de sistema. Los niveles de entorno y de módulo son divisiones paralelas de un sistema, pero los módulos siguen a los entornos en el proceso de combinación.

  6. Propiedades de módulos específicos del entorno

    Algunas de las propiedades de tus módulos también pueden depender del entorno, como los tamaños de las instancias, las imágenes o los endpoints. Las propiedades de los módulos específicos del entorno son el nivel más específico y el último punto de la combinación en cascada para sobrescribir los valores definidos anteriormente.

Clase auxiliar para combinar configuraciones

La clase config_merger es una clase auxiliar que carga automáticamente los archivos de configuración adecuados y combina su contenido en un único diccionario.

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

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

Al llamar a la función estática ConfigContext, se devuelve el diccionario de configuración combinado.

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

  • El module = "frontend" especifica el contexto en el que se cargan los archivos de propiedades.
  • El entorno se elige automáticamente 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 debe alinearse con tus estructuras de configuración, cargar todos los niveles en el orden correcto y sobrescribir los valores de configuración adecuados. Para cambiar los niveles o el orden de sobrescritura, modifica la clase de combinación de la configuración.

En el uso diario y rutinario, normalmente no tendrás que tocar esta clase. Normalmente, se editan las plantillas y los archivos de configuración correspondientes, y luego se usa el diccionario de salida con todas las configuraciones.

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

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

Puedes crear los archivos de configuración de la organización y del departamento como enlaces simbólicos durante la iniciación del repositorio. Estos archivos pueden estar en un repositorio de código independiente, ya que no forman parte lógicamente de la base de código de un equipo de proyecto, sino que se comparten en toda la organización y el departamento.

El combinador de configuraciones también busca archivos que coincidan con los niveles restantes de tu 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 un despliegue específico. No se puede compartir entre implementaciones.

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

Cualquier archivo Python puede contener variables, lo que te permite almacenar tu configuración de forma estructurada, pero distribuida. Lo mejor es usar diccionarios con una estructura acordada. La herramienta de combinación de configuraciones busca un diccionario llamado configs en todos los archivos de la cadena de combinación. Esos elementos separados configs se combinan en uno.

Durante la combinación, cuando una propiedad con la misma ruta y el mismo nombre aparece varias veces en los diccionarios, el combinador de configuraciones sobrescribe esa propiedad. En algunos casos, este comportamiento es útil, por ejemplo, cuando un valor predeterminado se sobrescribe con un valor específico del contexto. Sin embargo, hay muchos otros casos en los que no querrá sobrescribir la propiedad. Para evitar que se sobrescriba una propiedad, añádele un espacio de nombres independiente para que sea único. En el siguiente ejemplo, se añade un espacio de nombres creando un nivel adicional en el diccionario de configuración, lo que crea un subdiccionario.

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 nomenclatura

Las convenciones de nomenclatura son la mejor forma de mantener bajo control la infraestructura de Deployment Manager. No utilices nombres genéricos o vagos, como my project o test instance.

El siguiente ejemplo muestra una convención de nomenclatura para instancias de toda la organización:

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

Si proporcionas una función auxiliar, será fácil asignar un nombre a cada instancia según la convención acordada. También facilita la revisión del código, ya que ningún nombre de instancia procede de otro lugar que no sea esta función. La función recoge automáticamente los nombres de las configuraciones de nivel superior. Este enfoque ayuda a evitar entradas innecesarias.

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

Estructura de carpetas del código base de ejemplo

La estructura de carpetas del código base de ejemplo es flexible y personalizable. Sin embargo, está codificado parcialmente en el combinador de configuraciones y en el archivo de esquema de Deployment Manager, lo que significa que, si haces alguna modificación, debes reflejar esos cambios en el combinador de configuraciones y en 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 entre diferentes equipos de proyectos. Para simplificar, 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 una clase auxiliar independiente para los departamentos. Puedes añadir cualquier clase auxiliar a nivel de organización o de sistema.

La carpeta global puede estar en un repositorio de Git independiente. Puedes hacer referencia a sus archivos desde los sistemas individuales. También puedes usar enlaces simbólicos, pero pueden generar confusión o errores 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 varios 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 únicos de este sistema. También hace referencia a las configuraciones globales mediante enlaces 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 de asistencia, puedes añadir más clases de asistencia y hacer referencia a 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 hacer referencia a las plantillas de Deployment Manager. Los enlaces simbólicos también funcionan aquí.

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

Prácticas recomendadas

Las siguientes prácticas recomendadas pueden ayudarte a estructurar tu código de forma jerárquica.

Archivos de esquema

En el archivo de esquema, es obligatorio que Deployment Manager incluya cada archivo que utilices de alguna forma durante la implementación. Si añades una carpeta completa, el código será más corto y genérico.

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

Varias implementaciones

Es una práctica recomendada que un sistema contenga varias implementaciones, lo que significa que usan los mismos conjuntos de configuraciones, aunque sean módulos diferentes (por ejemplo, redes, cortafuegos, backend o frontend). Es posible que tengas que acceder a la salida de estas implementaciones desde otra implementación. Puedes consultar el resultado de la implementación cuando esté listo y guardarlo en la carpeta de configuraciones. Puedes añadir estos archivos de configuración durante el proceso de combinación.

Los enlaces simbólicos son compatibles con los comandos gcloud deployment-manager y los archivos vinculados se cargan correctamente. Sin embargo, no todos los sistemas operativos admiten enlaces simbólicos.

Jerarquía de configuración

El siguiente diagrama muestra un resumen de los diferentes niveles y sus relaciones. Cada rectángulo representa un archivo de propiedades, como se indica en el nombre de archivo en rojo.

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

Orden de combinación contextual

El combinador de configuraciones selecciona los archivos de configuración adecuados en cada nivel en función del contexto en el que se utilice cada archivo. El contexto es un módulo que vas a implementar en un entorno. Este contexto define el sistema y el departamento automáticamente.

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

Diagrama del orden de sobrescritura

Siguientes pasos