Estruturar o Deployment Manager para utilização em grande escala

Quando o seu sistema de "infraestrutura como código" cresce além do exemplo "Hello World" sem planeamento, o código tende a tornar-se não estruturado. As configurações não planeadas são codificadas. A capacidade de manutenção diminui drasticamente.

Use este documento para estruturar as suas implementações de forma mais eficiente e em grande escala.

Além disso, aplique a sua convenção de nomenclatura e práticas recomendadas internas nas suas equipas. Este documento destina-se a um público-alvo com conhecimentos técnicos avançados e pressupõe que tem conhecimentos básicos de Python, Google Cloud infraestrutura, Deployment Manager e, geralmente, infraestrutura como código.

Antes de começar

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

Para implementações grandes com mais de uma dúzia de recursos, as práticas recomendadas padrão exigem que use uma quantidade significativa de propriedades externas (parâmetros de configuração) para evitar a codificação rígida de strings e lógica em modelos genéricos. Muitas destas propriedades estão parcialmente duplicadas devido a ambientes semelhantes, como o ambiente de desenvolvimento, teste ou produção, e serviços semelhantes. Por exemplo, todos os serviços padrão estão a ser executados numa pilha LAMP semelhante. Seguir estas práticas recomendadas resulta num grande conjunto de propriedades de configuração com uma grande quantidade de duplicações que podem tornar-se difíceis de manter, aumentando assim a probabilidade de erro humano.

A tabela seguinte é um exemplo de código para ilustrar as diferenças entre uma configuração hierárquica e uma única configuração por implementação. A tabela realça uma duplicação comum numa única configuração. Ao usar a configuração hierárquica, a tabela mostra como mover as secções repetidas para um nível superior na hierarquia para evitar a repetição e diminuir as probabilidades 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 gerir melhor uma grande base de código, use um esquema hierárquico estruturado com uma união em cascata das propriedades de configuração. Para tal, usa vários ficheiros para a configuração, em vez de apenas um. Além disso, trabalha com funções auxiliares e partilha parte da base de código na sua organização.

Estruturar e aplicar o código em cascata de forma hierárquica oferece várias vantagens:

  • Quando divide a configuração em vários ficheiros, melhora a estrutura e a legibilidade das propriedades. Também evita duplicá-las.
  • Cria a união hierárquica para aplicar os valores em cascata de forma lógica, criando ficheiros de configuração de nível superior reutilizáveis em projetos ou componentes.
  • Define cada propriedade apenas uma vez (exceto substituições), evitando a necessidade de lidar com espaços de nomes nos nomes das propriedades.
  • Os seus modelos não precisam de saber sobre o ambiente real, porque a configuração adequada é carregada com base nas variáveis adequadas.

Estruturar a sua base de código hierarquicamente

Uma implementação do Deployment Manager contém uma configuração YAML ou um ficheiro de esquema, juntamente com vários ficheiros Python. Em conjunto, estes ficheiros formam a base de código de uma implementação. Os ficheiros Python podem ter finalidades diferentes. Pode usar os ficheiros Python como modelos de implementação, como ficheiros de código gerais (classes auxiliares) ou como ficheiros de código que armazenam propriedades de configuração.

Para estruturar a sua base de código hierarquicamente, usa alguns ficheiros Python como ficheiros de configuração, em vez do ficheiro de configuração padrão. Esta abordagem oferece-lhe maior flexibilidade do que associar a implementação a um único ficheiro YAML.

Tratar a sua infraestrutura como código real

Um princípio importante para o código limpo é não se repetir (DRY). Defina tudo apenas uma vez. Esta abordagem torna a base de código mais limpa, mais fácil de rever e validar, e mais fácil de manter. Quando uma propriedade tem de ser modificada apenas num local, o risco de erro humano diminui.

Para uma base de código mais leve com ficheiros de configuração mais pequenos e duplicação mínima, use estas diretrizes para estruturar as suas configurações de modo a seguir o princípio DRY.

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

Os princípios fundamentais para estruturar a sua base de código de forma clara e hierárquica são usar organizações, departamentos, ambientes e módulos. Estes princípios são opcionais e extensíveis. Para ver um diagrama da hierarquia da base de código de exemplo, que segue estes princípios, consulte a hierarquia de configuração.

No diagrama seguinte, um módulo é implementado num ambiente. A união de configurações seleciona os ficheiros de configuração adequados em cada nível com base no contexto em que são usados. Também define automaticamente o sistema e o departamento.

Um módulo implementado num ambiente

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

  1. Propriedades organizacionais

    Este é o nível mais elevado na sua estrutura. Neste nível, pode armazenar propriedades de configuração, como organization_name e organization_abbreviation, que usa na sua convenção de nomenclatura, e funções auxiliares que quer partilhar e aplicar em todas as equipas.

  2. Propriedades do departamento

    As organizações contêm departamentos, se tiver departamentos na sua estrutura. No ficheiro de configuração de cada departamento, partilhe propriedades que não sejam usadas por outros departamentos, por exemplo, department_name ou cost_center.

  3. Propriedades do sistema (projeto)

    Cada departamento contém sistemas. Um sistema é uma pilha de software bem definida, por exemplo, a sua plataforma de comércio eletrónico. Não é um Google Cloud projeto, mas sim um ecossistema de serviços funcional.

    Ao nível do sistema, a sua equipa tem muito mais autonomia do que nos níveis superiores. Aqui, pode definir funções auxiliares (como project_name_generator(), instance_name_generator() ou instance_label_generator()) para os parâmetros ao nível da equipa e do sistema (por exemplo, system_name, default_instance_size ou naming_prefix).

  4. Propriedades do ambiente

    É provável que o seu sistema tenha vários ambientes, como Dev, Test ou Prod (e, opcionalmente, QA e Staging), que são bastante semelhantes entre si. Idealmente, usam a mesma base de código e diferem apenas ao nível da configuração. Ao nível do ambiente, pode substituir propriedades, como default_instance_size, para as configurações Prod e QA.

  5. Propriedades dos módulos

    Se o seu sistema for grande, divida-o em vários módulos, em vez de o manter como um grande bloco monolítico. Por exemplo, pode mover a rede e a segurança principais para blocos separados. Também pode separar as camadas de back-end, front-end e base de dados em módulos separados. Os módulos são modelos desenvolvidos por terceiros, nos quais adiciona apenas a configuração adequada. Ao nível do módulo, pode definir propriedades relevantes apenas para módulos específicos, incluindo propriedades concebidas para substituir propriedades herdadas ao nível do sistema. Os níveis de ambiente e módulo são divisões paralelas num sistema, mas os módulos seguem os ambientes no processo de união.

  6. Propriedades do módulo específicas do ambiente

    Algumas das propriedades do módulo também podem depender do ambiente, por exemplo, tamanhos de instâncias, imagens e pontos finais. As propriedades do módulo específicas do ambiente são o nível mais específico e o último ponto na união em cascata para substituir os valores definidos anteriormente.

Classe auxiliar para unir configurações

A classe config_merger é uma classe auxiliar que carrega automaticamente os ficheiros de configuração adequados e une o respetivo conteúdo num único dicionário.

Para usar a classe config_merger, tem de fornecer as seguintes informações:

  • O nome do módulo.
  • O contexto global, que contém o nome do ambiente.

Chamar a função estática ConfigContext devolve o dicionário de configuração unido.

O código seguinte mostra como usar esta classe:

  • O module = "frontend" especifica o contexto no qual os ficheiros de propriedades são carregados.
  • O ambiente é escolhido automaticamente a partir de context.properties["envName"].
  • A configuração global.

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

Nos bastidores, esta classe auxiliar tem de estar alinhada com as suas estruturas de configuração, carregar todos os níveis pela ordem certa e substituir os valores de configuração adequados. Para alterar os níveis ou a ordem de substituição, modifique a classe de união da configuração.

Na utilização diária e de rotina, normalmente, não precisa de tocar nesta classe. Normalmente, edita os modelos e os ficheiros de configuração adequados e, em seguida, usa o dicionário de saída com todas as configurações.

O exemplo de base de código contém os seguintes três ficheiros de configuração codificados:

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

Pode criar os ficheiros de configuração da organização e do departamento como links simbólicos durante o início do repositório. Estes ficheiros podem residir num repositório de código separado, uma vez que não fazem logicamente parte da base de código de uma equipa de projeto, mas são partilhados por toda a organização e departamento.

A união de configurações também procura ficheiros que correspondam aos restantes níveis da sua estrutura:

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

Ficheiro de configuração

O Deployment Manager usa um ficheiro de configuração, que é um único ficheiro para uma implementação específica. Não pode ser partilhado entre implementações.

Quando usa a classe config-merger, as propriedades de configuração estão completamente separadas deste ficheiro de configuração porque não o está a usar. Em alternativa, usa uma coleção de ficheiros Python, o que lhe dá muito mais flexibilidade numa implementação. Estes ficheiros também podem ser partilhados em várias implementações.

Qualquer ficheiro Python pode conter variáveis, o que lhe permite armazenar a sua configuração de forma estruturada, mas distribuída. A melhor abordagem é usar dicionários com uma estrutura acordada. A união de configurações procura um dicionário denominado configs em todos os ficheiros na cadeia de união. Esses registos separados configs são unidos num só.

Durante a união, quando uma propriedade com o mesmo caminho e nome aparece nos dicionários várias vezes, a união de configurações substitui essa propriedade. Em alguns casos, este comportamento é útil, como quando um valor predefinido é substituído por um valor específico do contexto. No entanto, existem muitos outros casos em que quer evitar substituir a propriedade. Para evitar a substituição de uma propriedade, adicione-lhe um espaço de nomes separado para a tornar única. No exemplo seguinte, adiciona um espaço de nomes criando um nível adicional no dicionário de configuração, o que cria um sub-dicioná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 forma de manter a sua infraestrutura do Deployment Manager sob controlo. Não quer usar nomes vagos ou genéricos, como my project ou test instance.

O exemplo seguinte é uma convenção de nomenclatura ao nível da organização para instâncias:

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

A disponibilização de uma função auxiliar facilita a atribuição de um nome a cada instância com base na convenção acordada. Também facilita a revisão do código, uma vez que nenhum nome de instância vem de outro local que não esta função. A função seleciona automaticamente os nomes das configurações de nível superior. Esta abordagem ajuda a evitar a introdução desnecessária de dados.

Pode aplicar estas convenções de nomenclatura à maioria dos Google Cloud recursos e para etiquetas. As funções mais complexas podem até gerar um conjunto de etiquetas predefinidas.

Estrutura de pastas da base de código de exemplo

A estrutura das pastas da base de código de exemplo é flexível e personalizável. No entanto, está parcialmente codificado no integrador de configuração e no ficheiro de esquema do Deployment Manager, o que significa que, se fizer uma modificação, tem de refletir essas alterações no integrador de configuração e nos ficheiros de esquema.

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

A pasta global contém ficheiros partilhados entre diferentes equipas de projeto. Para simplificar, a pasta de configuração contém a configuração da organização e os ficheiros de configuração de todos os departamentos. Neste exemplo, não existe uma classe auxiliar separada para os departamentos. Pode adicionar qualquer classe auxiliar ao nível da organização ou do sistema.

A pasta global pode estar num repositório Git separado. Pode consultar os respetivos ficheiros a partir dos sistemas individuais. Também pode usar ligações simbólicas, mas estas podem criar confusão ou falhas em 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

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

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

A pasta de configuração contém todos os ficheiros de configuração exclusivos deste sistema, e também faz referência às configurações globais através de 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'
    }
  }
}

Na pasta auxiliar, pode adicionar mais classes auxiliares e fazer 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, pode armazenar ou consultar os modelos do Deployment Manager. Os links simbólicos também funcionam aqui.

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

Práticas recomendadas

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

Ficheiros de esquema

No ficheiro de esquema, é um requisito do Deployment Manager listar todos os ficheiros que usa de alguma forma durante a implementação. A adição de uma pasta inteira torna o código mais curto e genérico.

  • Classes auxiliares:
- path: helper/*.py
  • Ficheiros 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 implementações

É uma prática recomendada que um sistema contenha várias implementações, o que significa que usam os mesmos conjuntos de configurações, mesmo que sejam módulos diferentes, por exemplo, rede, firewalls, back-end e front-end. Pode ter de aceder ao resultado destas implementações a partir de outra implementação. Pode consultar o resultado da implementação após esta estar pronta e guardá-lo na pasta configurations. Pode adicionar estes ficheiros de configuração durante o processo de união.

Os links simbólicos são suportados pelos comandos gcloud deployment-manager e os ficheiros associados são carregados corretamente. No entanto, os links simbólicos não são suportados em todos os SOs.

Hierarquia de configuração

O diagrama seguinte é uma vista geral dos diferentes níveis e das respetivas relações. Cada retângulo representa um ficheiro de propriedades, conforme indicado pelo nome do ficheiro a vermelho.

Hierarquia de configuração com diferentes níveis e respetivas relações indicadas.

Ordem de união sensível ao contexto

A união de configurações seleciona os ficheiros de configuração adequados em cada nível com base no contexto em que cada ficheiro é usado. O contexto é um módulo que está a implementar num ambiente. Este contexto define automaticamente o sistema e o departamento.

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

Diagrama da ordem de substituição

O que se segue?