Strutturare Deployment Manager per l'utilizzo su larga scala

Quando il tuo sistema "Infrastructure as Code" va oltre l'esempio "Hello World" senza pianificazione, il codice tende a diventare non strutturato. Le configurazioni non pianificate sono impostate come hardcoded. La manutenibilità diminuisce drasticamente.

Utilizza questo documento, insieme al relativo esempio di codice, per strutturare i tuoi deployment in modo più efficiente e su larga scala.

Inoltre, applica la tua convenzione di denominazione e le best practice interne a tutti i tuoi team. Questo documento è rivolto a un pubblico tecnicamente avanzato e presuppone che tu possieda una conoscenza di base di Python, infrastruttura Google Cloud, Deployment Manager e, in generale, Infrastructure as Code.

Prima di iniziare

Più ambienti con un unico codebase

Per deployment di grandi dimensioni con più di una decina di risorse, le best practice standard richiedono di utilizzare una quantità significativa di proprietà esterne (parametri di configurazione), in modo da evitare l'hardcoded delle stringhe e della logica in modelli generici. Molte di queste proprietà sono parzialmente duplicate a causa di ambienti simili, ad esempio l'ambiente di sviluppo, test o produzione e servizi simili. Ad esempio, tutti i servizi standard sono in esecuzione su uno stack LAMP simile. Seguendo queste best practice, avrai creato un ampio insieme di proprietà di configurazione con un'elevata quantità di duplicati che possono diventare difficili da gestire, aumentando così la possibilità di errore umano.

La seguente tabella è un esempio di codice che illustra le differenze tra una configurazione gerarchica e una singola per deployment. La tabella evidenzia una duplicazione comune in un'unica configurazione. Utilizzando la configurazione gerarchica, la tabella mostra come spostare le sezioni ripetute a un livello superiore nella gerarchia per evitare ripetizioni e ridurre le probabilità di errore umano.

Modello Configurazione gerarchica senza ridondanza Configurazione singola con ridondanza

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

Per gestire meglio un codebase di grandi dimensioni, utilizza un layout gerarchico strutturato con un'unione a cascata delle proprietà di configurazione. Per farlo, utilizza più file per la configurazione e non uno solo. Inoltre, utilizzi le funzioni helper e condividi parte del codebase in tutta l'organizzazione.

L'esempio di codice che accompagna questo documento, Organization_with_departments, contiene un codebase con una struttura predefinita ma personalizzabile, uno script helper per unire le configurazioni, funzioni helper per la denominazione e un set completo di configurazioni di esempio. Puoi trovare questo esempio funzionante nel repository GitHub di esempi di Deployment Manager.

La strutturazione e la strutturazione gerarchica del codice offre diversi vantaggi:

  • Quando suddividi la configurazione in più file, migliori la struttura e la leggibilità delle proprietà. Inoltre, eviterai di duplicarle.
  • Puoi progettare l'unione gerarchica per trasmettere a cascata i valori in modo logico, creando file di configurazione di primo livello riutilizzabili in più progetti o componenti.
  • Puoi definire ogni proprietà una sola volta (tranne le sovrascritture), evitando di dover occuparti del pacing dei nomi nei nomi delle proprietà.
  • Non è necessario che i modelli siano a conoscenza dell'ambiente effettivo, perché la configurazione appropriata viene caricata in base alle variabili appropriate.

Strutturazione gerarchica del codebase

Un deployment di Deployment Manager contiene una configurazione YAML o un file di schema, insieme a diversi file Python. Insieme, questi file formano il codebase di un deployment. I file Python possono servire a scopi diversi. Puoi utilizzare i file Python come modelli di deployment, file di codice generali (classi helper) o file di codice in cui sono archiviate le proprietà di configurazione.

Per strutturare il codebase in modo gerarchico, puoi utilizzare alcuni file Python come file di configurazione anziché come file di configurazione standard. Questo approccio offre una maggiore flessibilità rispetto al collegamento del deployment a un singolo file YAML.

Trattare la tua infrastruttura come un vero codice

Un principio importante per la pulizia del codice è Non ripetere i passaggi (DRY). Definisci tutto una sola volta. Questo approccio rende il codebase più chiaro, più semplice da esaminare e convalidare, nonché più facile da gestire. Quando una proprietà deve essere modificata in un solo posto, il rischio di errori umani diminuisce.

Per un codebase più leggero con file di configurazione più piccoli e duplicati minimi, utilizza queste linee guida per strutturare le configurazioni in modo da seguire il principio DRY.

Organizzazioni, dipartimenti, ambienti e moduli

I principi alla base per strutturare il codebase in modo pulito e gerarchico sono l'utilizzo di organizzazioni, dipartimenti, ambienti e moduli. Questi principi sono facoltativi ed estensibili. Per un diagramma della gerarchia del codebase di esempio, che segue questi principi, consulta la gerarchia di configurazione.

Nel diagramma seguente, viene eseguito il deployment di un modulo in un ambiente. La fusione della configurazione seleziona i file di configurazione appropriati a ogni livello in base al contesto in cui viene utilizzato. Definisce inoltre automaticamente il sistema e il reparto.

Un modulo di cui è stato eseguito il deployment in un ambiente

Nell'elenco seguente, i numeri rappresentano l'ordine di sovrascrittura:

  1. Proprietà organizzative

    Si tratta del livello più alto della struttura. A questo livello, puoi archiviare le proprietà di configurazione come organization_name, organization_abbreviation, che utilizzi nella convenzione di denominazione, nonché le funzioni helper che vuoi condividere e applicare a tutti i team.

  2. Proprietà di reparti

    Le organizzazioni includono reparti, se ce ne sono nella struttura. Nel file di configurazione di ogni reparto, condividi le proprietà che non sono utilizzate da altri reparti, ad esempio department_name o cost_center.

  3. Proprietà di sistema (progetto)

    Ogni reparto contiene sistemi. Un sistema è uno stack software ben definito, ad esempio la tua piattaforma di e-commerce. Non è un progetto Google Cloud, ma un ecosistema di servizi funzionante.

    A livello di sistema, il team ha molta più autonomia rispetto ai livelli superiori. Qui puoi definire funzioni helper (come project_name_generator(), instance_name_generator() o instance_label_generator()) per i parametri a livello di team e di sistema (ad esempio system_name, default_instance_size o naming_prefix).

  4. Proprietà dell'ambiente

    È probabile che nel sistema ci siano più ambienti, come Dev, Test o Prod (e, facoltativamente, QA e Staging), abbastanza simili tra loro. Idealmente, utilizzano lo stesso codebase e differiscono solo a livello di configurazione. A livello di ambiente, puoi sovrascrivere proprietà come default_instance_size per le configurazioni Prod e QA.

  5. Proprietà dei moduli

    Se il sistema è di grandi dimensioni, suddividilo in più moduli invece di conservarlo come un grande blocco monolitico. Potresti, ad esempio, spostare il networking e la sicurezza di base in blocchi separati. Puoi anche separare i livelli di backend, frontend e database in moduli separati. I moduli sono modelli sviluppati da terze parti in cui aggiungi solo la configurazione appropriata. A livello di modulo puoi definire le proprietà pertinenti solo per moduli specifici, incluse le proprietà progettate per sovrascrivere le proprietà ereditate a livello di sistema. I livelli di ambiente e modulo sono divisioni parallele in un sistema, ma i moduli seguono gli ambienti nel processo di unione.

  6. Proprietà del modulo specifiche per l'ambiente

    Alcune proprietà del modulo potrebbero dipendere anche dall'ambiente, ad esempio dimensioni dell'istanza, immagini ed endpoint. Le proprietà del modulo specifiche per l'ambiente sono il livello più specifico e l'ultimo punto dell'unione a cascata per la sovrascrittura dei valori definiti in precedenza.

Classe helper per unire le configurazioni

La classe config_merger è una classe helper che carica automaticamente i file di configurazione appropriati e unisce i relativi contenuti in un unico dizionario.

Per utilizzare la classe config_merger, devi fornire le seguenti informazioni:

  • Il nome del modulo.
  • Il contesto globale, che contiene il nome dell'ambiente.

La chiamata della funzione statica ConfigContext restituisce il dizionario della configurazione unita.

Il seguente codice mostra come utilizzare questo corso:

  • module = "frontend" specifica il contesto in cui vengono caricati i file delle proprietà.
  • L'ambiente viene selezionato automaticamente da context.properties["envName"].
  • La configurazione globale.

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

Dietro le quinte, questa classe helper deve allinearsi alle strutture di configurazione, caricare tutti i livelli nell'ordine corretto e sovrascrivere i valori di configurazione appropriati. Per cambiare i livelli o l'ordine di sovrascrittura, modifica la classe di unione della configurazione.

Nell'uso quotidiano e di routine, in genere non è necessario toccare questo corso. Solitamente si modificano i modelli e i file di configurazione appropriati, quindi si utilizza il dizionario di output con tutte le configurazioni.

Il codebase di esempio contiene i seguenti tre file di configurazione impostati come hardcoded:

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

Puoi creare i file di configurazione dell'organizzazione e del reparto come link simbolici durante l'avvio del repository. Questi file possono trovarsi in un repository di codice separato, poiché non fa parte logicamente del codebase di un team di progetto, ma è condiviso tra l'intera organizzazione e il reparto.

La combinazione della configurazione cerca anche i file che corrispondono ai livelli rimanenti della struttura:

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

File di configurazione

Deployment Manager utilizza un solo file di configurazione, ovvero un singolo file per un deployment specifico. Non può essere condiviso tra i deployment.

Quando utilizzi la classe config-merger, le proprietà di configurazione vengono completamente scollegate da questo file di configurazione perché non la stai utilizzando. Utilizza invece una raccolta di file Python, che offre molta più flessibilità in un deployment. Questi file possono anche essere condivisi tra i deployment.

Qualsiasi file Python può contenere variabili, in modo da poter archiviare la configurazione in modo strutturato, ma distribuito. L'approccio migliore è quello di usare dizionari con una struttura concordata. L'unione della configurazione cerca un dizionario chiamato configs in ogni file nella catena di unione. Questi elementi configs separati vengono uniti in un'unica entità.

Durante l'unione, quando una proprietà con lo stesso percorso e lo stesso nome viene visualizzata più volte nei dizionari, l'unione della configurazione sovrascrive la proprietà. In alcuni casi questo comportamento è utile, ad esempio quando un valore predefinito viene sovrascritto da un valore specifico del contesto. Tuttavia, ci sono molti altri casi in cui vuoi evitare di sovrascrivere la proprietà. Per evitare la sovrascrittura di una proprietà, aggiungi alla proprietà uno spazio dei nomi separato per renderla univoca. Nell'esempio seguente aggiungi uno spazio dei nomi creando un livello aggiuntivo nel dizionario di configurazione, che crea un elemento secondario.

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

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

Classi helper e convenzioni di denominazione

Le convenzioni di denominazione sono il modo migliore per tenere sotto controllo la tua infrastruttura Deployment Manager. Non utilizzare nomi vaghi o generici, come my project o test instance.

L'esempio seguente è una convenzione di denominazione a livello di organizzazione per le istanze:

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

Fornire una funzione helper semplifica l'assegnazione del nome a ogni istanza in base alla convenzione concordata. Inoltre, semplifica la revisione del codice perché nessun nome di istanza proviene da nessun altro nome di istanza diverso da questa funzione. La funzione preleva automaticamente i nomi dalle configurazioni di livello superiore. Questo approccio consente di evitare input non necessari.

Puoi applicare queste convenzioni di denominazione alla maggior parte delle risorse Google Cloud e alle etichette. Le funzioni più complesse possono anche generare un insieme di etichette predefinite.

Struttura delle cartelle del codebase di esempio

La struttura di cartelle del codebase di esempio è flessibile e personalizzabile. Tuttavia, è parzialmente hardcoded nell'unione della configurazione e nel file di schema di Deployment Manager, il che significa che se apporti una modifica, devi riflettere queste modifiche nei file di unione della configurazione e di schema.

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

La cartella globale contiene file condivisi tra diversi team di progetto. Per semplicità, la cartella di configurazione contiene i file di configurazione dell'organizzazione e di tutti i reparti. In questo esempio non è previsto un corso helper separato per i dipartimenti. Puoi aggiungere qualsiasi corso helper a livello di organizzazione o di sistema.

La cartella globale può trovarsi in un repository Git separato. Puoi fare riferimento ai relativi file dai singoli sistemi. Puoi usare anche link simbolici, ma in alcuni sistemi operativi potrebbero creare confusione o interruzioni.

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

La cartella dei sistemi contiene uno o più sistemi diversi. I sistemi sono separati l'uno dall'altro e non condividono le configurazioni.

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

La cartella di configurazione contiene tutti i file di configurazione univoci per questo sistema, facendo riferimento anche alle configurazioni globali tramite link simbolici.

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

Nella cartella helper, puoi aggiungere ulteriori classi helper e fare riferimento alle classi globali.

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

Nella cartella template puoi archiviare o fare riferimento ai modelli di Deployment Manager. Anche i link simbolici funzionano qui.

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

Utilizzo del codebase di esempio

Il modo migliore per iniziare ad applicare questa pratica gerarchica come base dell'infrastruttura come codice è clonare il codebase di esempio e copiare i contenuti della cartella gerarchia_configuration.

  1. Controlla il repository di esempio.

    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. Per configurare il sistema, usa i seguenti link simbolici per fare riferimento ai file globali al contesto locale.

    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. Seleziona il reparto appropriato dall'elenco globale.

    ln -sf ../../../global/configs/Department_Data_config.py configs/department_config.py
    
  4. Per impostare il contesto corretto dell'ambiente, utilizza il flag --properties per specificare la proprietà envName. Questa proprietà ti consente di eseguire lo stesso codice, scegliendo come target ambienti diversi, con lo stesso comando. [MY-PROJECT-ID] rappresenta l'ID del tuo progetto 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
    

best practice

Le best practice riportate di seguito possono aiutarti a strutturare il codice in modo gerarchico.

File di schema

Nel file dello schema, Deployment Manager impone di elencare ogni file che utilizzi durante il deployment. L'aggiunta di un'intera cartella rende il codice più breve e più generico.

  • Corsi di supporto:
- path: helper/*.py
  • File di configurazione:
- path: configs/*.py
- path: configs/*/*.py
  • Importazioni collettive (stile glob)
gcloud config set deployment_manager/glob_imports True

Deployment multipli

Come best practice, un sistema deve contenere più deployment, ovvero utilizzare gli stessi set di configurazioni, anche se si tratta di moduli diversi, ad esempio networking, firewall, backend e frontend. Potresti dover accedere all'output di questi deployment da un altro deployment. Quando è pronto, puoi eseguire query sull'output del deployment e salvarlo nella cartella Configurazioni. Puoi aggiungere questi file di configurazione durante il processo di unione.

I link simbolici sono supportati dai comandi gcloud deployment-manager e i file collegati vengono caricati correttamente. Tuttavia, i link simbolici non sono supportati in tutti i sistemi operativi.

Gerarchia di configurazione

Il seguente diagramma offre una panoramica dei diversi livelli e delle loro relazioni. Ogni rettangolo rappresenta un file delle proprietà, come indicato dal nome file in rosso.

Gerarchia di configurazione con diversi livelli e le relative relazioni.

Ordine di unione sensibile al contesto

La fusione delle configurazioni seleziona i file di configurazione appropriati in ogni livello in base al contesto in cui viene utilizzato ciascun file. Il contesto è un modulo di cui esegui il deployment in un ambiente. Questo contesto definisce automaticamente il sistema e il reparto.

Nel seguente diagramma, i numeri rappresentano l'ordine di sovrascrittura nella gerarchia:

Diagramma dell'ordine di sovrascrittura

Passaggi successivi