Como estruturar o Cloud Deployment Manager para uso em escala

Quando seu sistema de "Infraestrutura como código" cresce além do exemplo "Hello World" sem planejamento, o código tende a ficar desestruturado. Configurações não planejadas são fixadas no código. A capacidade de manutenção cai drasticamente.

Use este documento e o exemplo de código que acompanha esse documento para estruturar suas implantações com mais eficiência e em escala.

Além disso, faça com que todas as equipes apliquem a convenção de nomenclatura e as práticas recomendadas internas. Este documento é destinado a um público tecnicamente avançado e pressupõe que você tenha um conhecimento básico de Python, infraestrutura do Google Cloud, Deployment Manager e, geralmente, infraestrutura como código.

Antes de começar

Vários ambientes com uma única base de código

Em grandes implantações com mais de uma dúzia de recursos, as práticas recomendadas padrão exigem o uso de uma quantidade significativa de propriedades externas (parâmetros de configuração). Assim você evita a fixação de strings e lógica no código de modelos genéricos. Muitas dessas propriedades são parcialmente duplicadas em serviços e ambientes semelhantes (como os de desenvolvimento, testes ou produção). Por exemplo, todos os serviços padrão estão sendo executados em uma pilha LAMP semelhante. Seguir estas práticas recomendadas gera um grande conjunto de propriedades de configuração, muitas delas duplicadas, que pode ser difícil de manter e aumenta a chance de erro humano.

A tabela a seguir apresenta um exemplo de código para demonstrar as diferenças entre uma configuração hierárquica e uma configuração única por implantação. A tabela destaca uma duplicação comum na configuração única. Ao usar a configuração hierárquica, a tabela mostra como mover seções repetidas para um nível mais alto na hierarquia para evitar repetição e diminuir as chances de erro humano.

Modelo Configuração hierárquica sem redundância Configuração única com redundância

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 lidar melhor com uma base de código grande, use um layout hierárquico estruturado e combine as propriedades de configuração em cascata. Para fazer isso, use vários arquivos de configuração, em vez de apenas um. Além disso, você trabalhará com funções auxiliares e compartilhará parte da base de código com sua organização.

O exemplo de código que acompanha este documento, Organization_with_departments, contém uma base de código com estrutura predefinida que pode ser personalizada, um script auxiliar para combinar configurações, funções auxiliares para nomenclatura e um conjunto completo de configurações de exemplo. É possível encontrar este exemplo de trabalho no repositório do GitHub de amostras do Deployment Manager.

Estruturar e colocar seu código em cascata de forma hierárquica oferece vários benefícios:

  • Ao dividir a configuração em vários arquivos, você melhora a estrutura e a legibilidade das propriedades, além de evitar duplicá-las.
  • A combinação hierárquica é feita de modo a colocar os valores em cascata de forma lógica, criando arquivos de configuração de nível superior que podem ser reutilizados em outros projetos ou componentes.
  • Cada propriedade é definida apenas uma vez (exceto as substituições), evitando a necessidade de lidar com a atribuição de namespace em nomes de propriedades.
  • Seus modelos não precisam distinguir o ambiente, já que a configuração adequada é carregada com base nas variáveis apropriadas.

Como estruturar sua base de código hierarquicamente

Uma implantação do Deployment Manager contém um arquivo de configuração YAML ou de esquema e vários arquivos Python. Juntos, eles formam a base de código de uma implantação. Os arquivos Python podem ter várias finalidades. É possível usá-los como modelos de implantação, arquivos de código geral (classes auxiliares) ou como arquivos de código que armazenam propriedades de configuração.

Para estruturar a base de código hierarquicamente, alguns arquivos Python são usados como arquivos de configuração no lugar do arquivo de configuração padrão. Essa abordagem é mais flexível do que vincular a implantação a um único arquivo YAML.

Como tratar a infraestrutura como código real

É importante seguir o princípio Não se repita (DRY, na sigla em inglês) para manter o código limpo. Defina tudo apenas uma vez. Essa abordagem torna a base de código mais limpa, mais fácil de revisar, de validar e de manter. Quando uma propriedade precisa ser modificada em apenas um lugar, o risco de erro humano diminui.

Use estas diretrizes para estruturar suas configurações seguindo o princípio DRY e manter uma base de código mais leve com arquivos de configuração menores e duplicação mínima.

Organizações, departamentos, ambientes e módulos

Organizações, departamentos, ambientes e módulos são os princípios fundamentais para estruturar sua base de código de forma limpa e hierárquica. Eles são opcionais e extensíveis. Veja em hierarquia de configuração um diagrama da hierarquia da base de código de exemplo, que segue esses princípios.

No diagrama a seguir, um módulo é implantado em um ambiente. A combinação de configurações seleciona os arquivos de configuração adequados em cada nível de acordo com o contexto de uso. Ela também define automaticamente o sistema e o departamento.

Módulo implantado em um ambiente

Na lista a seguir, os números representam a ordem de substituição:

  1. Propriedades de organização

    O nível mais alto da sua estrutura. Nesse nível, é possível armazenar propriedades de configuração, como organization_name, organization_abbreviation, que você usa na convenção de nomenclatura e funções auxiliares que quer compartilhar e aplicar em todas as equipes.

  2. Propriedades de departamento

    Organizações contêm departamentos, caso você tenha departamentos na estrutura. No arquivo de configuração de cada departamento, compartilhe propriedades que não são usadas por outros departamentos, como department_name ou cost_center.

  3. Propriedades de sistema (projeto)

    Cada departamento contém sistemas. Um sistema representa uma pilha de software bem definida, como uma plataforma de comércio eletrônico. Não é um projeto do Google Cloud, e sim um ecossistema de serviços em funcionamento.

    No nível do sistema, sua equipe tem muito mais autonomia do que nos níveis acima. Aqui, você pode definir funções auxiliares (como project_name_generator(), instance_name_generator() ou instance_label_generator()) para os parâmetros de toda a equipe e de todo o sistema (como system_name, default_instance_size ou naming_prefix).

  4. Propriedades de ambiente

    Seu sistema provavelmente tem vários ambientes, como Dev, Test ou Prod e, opcionalmente, QA e Staging, todos bem semelhantes entre si. O ideal é que eles usem a mesma base de código e apenas as configurações sejam diferentes. No nível do ambiente, é possível substituir propriedades como default_instance_size nas configurações de Prod e QA.

  5. Propriedades de módulo

    Caso seu sistema seja grande, divida-o em vários módulos em vez de mantê-lo como um grande bloco monolítico. É possível, por exemplo, transferir a base de rede e de segurança para blocos separados, assim como as camadas de back-end, front-end e banco de dados. Os módulos são modelos desenvolvidos por terceiros em que você adiciona apenas a configuração adequada. Defina no nível do módulo as propriedades relevantes apenas para módulos específicos, incluindo as propriedades que devem substituir aquelas herdadas do nível do sistema. Os níveis de ambiente e módulo são divisões paralelas de um sistema, mas os módulos vêm depois dos ambientes no processo de combinação.

  6. Propriedades de módulo específicas para um ambiente

    Algumas das suas propriedades de módulo também podem variar de acordo com o ambiente, como o tamanho das instâncias, imagens, endpoints. Propriedades de módulo específicas para um ambiente são o nível mais específico e representam o último ponto na combinação em cascata para substituir valores definidos anteriormente.

Classe auxiliar para combinar configurações

A classe auxiliar config_merger automaticamente carrega os arquivos de configuração adequados e combina o conteúdo deles em um único dicionário.

Para usar a classe config_merger, você precisa fornecer as seguintes informações:

  • nome do módulo
  • contexto global, incluindo o nome do ambiente

Chamar a função estática ConfigContext retorna o dicionário de configuração combinado.

O código a seguir mostra como usar essa classe:

  • O module = "frontend" especifica o contexto em que os arquivos de propriedade são carregados.
  • O ambiente é escolhido automaticamente com base em context.properties["envName"].
  • A configuração global.

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

Nos bastidores, essa classe auxiliar precisa se alinhar com as estruturas de configuração, carregar todos os níveis na ordem correta e substituir os valores de configuração adequados. Para alterar os níveis ou a ordem de substituição, modifique a classe de combinação de configurações.

Normalmente, não será necessário modificar a classe no uso cotidiano. Você geralmente edita os modelos e os arquivos de configuração adequados e, em seguida, usa o dicionário resultante com todas as configurações.

A base de código de exemplo inclui três arquivos de configuração fixados no código:

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

Crie os arquivos de configuração de organização e de departamento como links simbólicos durante a inicialização do repositório. Esses arquivos podem ficar em outro repositório de código, já que na prática não fazem parte da base de código de uma equipe de projeto, mas são compartilhados com toda a organização e todos os departamentos.

A combinação de configurações também procura arquivos que correspondam aos outros níveis da estrutura:

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

Arquivo de configuração

O Deployment Manager usa um único arquivo de configuração para cada implantação específica. Ele não pode ser compartilhado entre implantações.

Quando você usa a classe config-merger, as propriedades de configuração são completamente desanexadas deste arquivo de configuração porque você não a está usando. Em vez de usá-lo, você usa uma coleção de arquivos Python, algo que oferece muito mais flexibilidade em uma implantação. Também é possível compartilhar esses arquivos entre implantações.

Qualquer arquivo Python pode conter variáveis, o que permite armazenar sua configuração de maneira estruturada e distribuída. A melhor abordagem é usar dicionários com uma estrutura predefinida. A fusão de configurações procura um dicionário chamado configs em todos os arquivos na cadeia de mesclagem. Os configs separados são combinados em um só.

A combinação de configurações substitui as propriedades com o mesmo caminho e nome que aparecem nos dicionários várias vezes. Em alguns casos, esse comportamento é útil, como ao substituir um valor padrão por um valor específico para o contexto. No entanto, existem muitos outros casos em que é preferível evitar a substituição da propriedade. Para fazer isso, adicione um namespace separado para tornar a propriedade exclusiva. Ao adicionar um namespace no exemplo a seguir, você cria um nível adicional no dicionário de configuração, o que gera um subdicionário.

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

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

Classes auxiliares e convenções de nomenclatura

As convenções de nomenclatura são a melhor maneira de manter sua infraestrutura do Deployment Manager sob controle. Evite usar nomes vagos ou genéricos, como my project ou test instance.

O exemplo a seguir apresenta uma convenção de nomenclatura para as instâncias de toda a organização:

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

Fornecer uma função auxiliar facilita a definição do nome de cada instância de acordo com a convenção predefinida. Isso também facilita a revisão do código, já que nenhum nome de instância é gerado de qualquer outra forma. A função seleciona automaticamente os nomes de configurações dos níveis superiores. Essa abordagem ajuda a evitar entradas desnecessárias.

É possível aplicar essas convenções de nomenclatura na maioria dos recursos do Google Cloud e para rótulos. Funções mais complexas podem até mesmo gerar um conjunto de rótulos padrão.

Estrutura de pastas da base de código de exemplo

A estrutura de pastas da base de código de exemplo é flexível e pode ser personalizada. No entanto, ela foi parcialmente fixada no código da combinação de configurações e do arquivo de esquema do Deployment Manager. Isso significa que, ao fazer uma modificação, você precisará refleti-la nos arquivos da combinação de configurações e nos arquivos de esquema.

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

A pasta global contém arquivos compartilhados com diferentes equipes de projeto. Para simplificar, a pasta de configuração contém a configuração da organização e os arquivos de configuração de todos os departamentos. Neste exemplo, não há uma classe auxiliar separada para departamentos. É possível adicionar qualquer classe auxiliar no nível da organização ou do sistema.

A pasta global pode ser armazenada em um repositório Git separado. É possível fazer referência aos arquivos a partir de sistemas individuais. Também é possível usar links simbólicos, mas eles podem criar confusão ou falhas em determinados sistemas operacionais.

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

A pasta de sistemas contém um ou mais sistemas diferentes. Os sistemas são separados uns dos outros e não compartilham configurações.

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

A pasta de configuração contém todos os arquivos de configuração exclusivos de um sistema, além de fazer referência às configurações globais por links 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'
    }
  }
}

Adicione na pasta da classe auxiliar outras classes auxiliares e faça referência às classes globais.

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

Na pasta de modelos, armazene ou faça referência aos modelos do Deployment Manager. Links simbólicos também funcionam aqui.

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

Como usar a base de código de exemplo

A melhor maneira de começar a aplicar a prática hierárquica na base da sua infraestrutura como código é clonar a base de código de exemplo. Em seguida, copie o conteúdo da pasta hierarchical_configuration.

  1. Confira o repositório de exemplo.

    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. Use os seguintes links simbólicos para fazer referência aos arquivos globais e configurar o sistema no 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. Selecione o departamento adequado na lista global.

    ln -sf ../../../global/configs/Department_Data_config.py configs/department_config.py
    
  4. Para definir o contexto de ambiente correto, use o sinalizador --properties para especificar a propriedade envName. Essa propriedade permite executar o mesmo código segmentando diferentes ambientes no mesmo comando. [MY-PROJECT-ID] representa o ID do projeto do 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
    

Práticas recomendadas

As práticas recomendadas a seguir podem ajudar a estruturar seu código hierarquicamente.

Arquivos de esquema

O Deployment Manager exige que o arquivo de esquema inclua uma lista de todos os arquivos usados de alguma forma durante a implantação. Adicionar uma pasta inteira deixa seu código mais curto e genérico.

  • Classes auxiliares:
- path: helper/*.py
  • Arquivos de configuração:
- path: configs/*.py
- path: configs/*/*.py
  • Importações em massa (estilo glob)
gcloud config set deployment_manager/glob_imports True

Várias implantações

É recomendável que um sistema contenha várias implantações, o que significa que elas usam os mesmos conjuntos de configurações, mesmo de módulos diferentes. Por exemplo, rede, firewalls, back-end, front-end. Pode ser necessário acessar a saída dessas implantações de outra implantação. É possível consultar a saída da implantação depois que ela está pronta e salvá-la na pasta de configurações. Adicione esses arquivos de configuração durante o processo de combinação.

Os links simbólicos são compatíveis com os comandos gcloud deployment-manager, e os arquivos vinculados são carregados corretamente. No entanto, links simbólicos não são compatíveis com todos os sistemas operacionais.

Hierarquia de configurações

O diagrama a seguir apresenta uma visão geral dos diferentes níveis e as relações entre eles. Cada retângulo representa um arquivo de propriedades, conforme indicado pelo nome do arquivo em vermelho.

Hierarquia de configuração com diferentes níveis e os relacionamentos entre eles destacados.

Ordem de combinação com base no contexto

A combinação de configurações seleciona os arquivos de configuração adequados em cada nível de acordo com o contexto de uso de cada arquivo. O contexto representa um módulo que você está implantando em um ambiente. Ele define o sistema e o departamento automaticamente.

No diagrama a seguir, os números representam a ordem de substituição na hierarquia:

Diagrama da ordem de substituição

A seguir