Strutturazione di Deployment Manager per l'uso su larga scala

Mantieni tutto organizzato con le raccolte Salva e classifica i contenuti in base alle tue preferenze.

Quando il tuo sistema "Infrastruttura come codice" va oltre l'esempio "Hello World" senza pianificazione, il codice tende a non strutturarsi. Le configurazioni non pianificate sono hardcoded. La manutenibilità cala drasticamente.

Utilizza questo documento, insieme all'esempio di codice associato, 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 abbia una conoscenza di base di Python, infrastruttura Google Cloud, Deployment Manager e, in generale, Infrastructure as Code.

Prima di iniziare

Più ambienti con un singolo 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 stringhe hardcoded e modelli logici verso modelli generici. Molte di queste proprietà sono parzialmente duplicate a causa di ambienti simili, come ambienti 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, otterrai una grande quantità di proprietà di configurazione con un'elevata quantità di duplicati che può diventare difficile da gestire, aumentando così le probabilità di errore umano.

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

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, devi utilizzare più file per la configurazione, anziché uno solo. Inoltre, collabori con le funzioni helper e condividi parte del codebase nella tua organizzazione.

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

La strutturazione e a cascata del codice offre diversi vantaggi:

  • Quando suddividi la configurazione in più file, puoi migliorare la struttura e la leggibilità delle proprietà. Evita anche di duplicarle.
  • Tu progetti l'unione gerarchica per posizionare i valori in modo logico, creando file di configurazione di primo livello riutilizzabili tra progetti o componenti.
  • Devi definire ogni proprietà una sola volta (diversa da quella di sovrascrittura), evitando di dover gestire problemi di nome.
  • I tuoi modelli non hanno bisogno di conoscere l'ambiente effettivo, perché la configurazione appropriata viene caricata in base alle variabili appropriate.

Strutturazione del codebase in modo gerarchico

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

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

Considerare la tua infrastruttura come un codice reale

Uno dei principi più importanti per gestire il codice è Non ripeterti. Definisci tutto una sola volta. Questo approccio rende il codebase più pulito, più facile da esaminare e convalidare e più facile da gestire. Quando una proprietà deve essere modificata in un unico posto, il rischio di errori umani diminuisce.

Per un codebase più leggero con file di configurazione più piccoli e minimo duplicazione, utilizza queste linee guida per strutturare le configurazioni in base al principio DRY.

Organizzazioni, dipartimenti, ambienti e moduli

I principi di base per strutturare il codebase in modo chiaro e gerarchico sono l'uso di organizzazioni, reparti, ambienti e moduli. Questi principi sono facoltativi ed espandibili. 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. L'unione di configurazione seleziona i file di configurazione appropriati a ogni livello in base al contesto in cui viene utilizzata. 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

    Questo è il livello più alto nella tua struttura. A questo livello, puoi archiviare le proprietà di configurazione, come organization_name, organization_abbreviation, che utilizzi nella convenzione di denominazione e nelle funzioni helper che vuoi condividere e applicare a tutti i team.

  2. Reparti

    Le organizzazioni contengono dipartimenti, se presenti nella tua struttura. In ogni file di configurazione di ogni reparto, condividi le proprietà non utilizzate da altri dipartimenti, ad esempio department_name o cost_center.

  3. Proprietà di sistema (progetto)

    Ciascun 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 tuo 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 il tuo sistema abbia più ambienti, come Dev, Test o Prod, e, facoltativamente, QA e Staging, che sono abbastanza simili tra loro. Idealmente, utilizzano lo stesso codebase e si differenziano 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, suddividila in più moduli anziché mantenerlo come un unico blocco monolitico. Ad esempio, puoi spostare il networking e la sicurezza di base in blocchi separati. Puoi anche separare i livelli backend, frontend e database in moduli separati. I moduli sono modelli sviluppati da terze parti, in cui puoi aggiungere solo la configurazione appropriata. A livello di modulo puoi definire proprietà pertinenti solo per determinati moduli, incluse le proprietà progettate per sovrascrivere le proprietà a livello di sistema ereditate. L'ambiente e i livelli dei moduli sono divisioni parallele in un sistema, ma i moduli seguono gli ambienti nel processo di unione.

  6. Proprietà del modulo specifico per l'ambiente

    Alcune proprietà del modulo possono dipendere anche dall'ambiente, ad esempio dalle dimensioni delle istanze, dalle immagini e dagli endpoint. Le proprietà del modulo specifiche per l'ambiente sono il livello più specifico e l'ultimo punto dell'unione a cascata per sovrascrivere i valori definiti in precedenza.

Classe helper per l'unione di configurazioni

La classe config_merger è una classe helper che carica automaticamente i file di configurazione appropriati e unisce i 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 alla funzione statica ConfigContext restituisce il dizionario di configurazione unito.

Il codice seguente mostra come utilizzare questo corso:

  • L'elemento module = "frontend" specifica il contesto in cui vengono caricati i file delle proprietà.
  • L'ambiente viene scelto 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.

Generalmente e di routine, non è necessario toccare questo corso. In genere, modifichi i modelli e i file di configurazione appropriati, poi utilizzi il dizionario di output con tutte le configurazioni al suo interno.

Il codebase di esempio contiene i seguenti tre file di configurazione 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 essere inseriti in un repository di codice separato, dato che non fa parte logica del codebase del team di progetto, ma è condiviso nell'intera organizzazione e nel reparto.

La fusione di 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 file di configurazione, che è un unico file per un deployment specifico. Non può essere condiviso tra i deployment.

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

Qualsiasi file Python può contenere variabili, che ti consentono di archiviare la tua configurazione in modo strutturato, ma distribuito. L'approccio migliore consiste nell'utilizzare dizionari con una struttura concordata. La fusione di configurazione cerca un dizionario chiamato configs in ogni file della catena di unione. Le unità configs separate vengono unite in una sola.

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

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

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

Lezioni e convenzioni di denominazione

Le convenzioni di denominazione sono il modo migliore per tenere sotto controllo l'infrastruttura di Deployment Manager. Non vuoi utilizzare nomi vaghi o generici, ad esempio 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"])

Se fornisci una funzione helper, puoi assegnare facilmente un nome a ogni istanza in base alla convenzione concordata. Semplifica inoltre la revisione del codice perché nessun nome di istanza proviene da funzioni diverse da questa funzione. La funzione raccoglie automaticamente i nomi dalle configurazioni di livello superiore. Questo approccio aiuta a evitare contenuti non necessari.

Puoi applicare queste convenzioni di denominazione alla maggior parte delle risorse di Google Cloud e alle etichette. Anche funzioni più complesse possono 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 nella fusione di configurazione e nel file di schema Deployment Manager, il che significa che se apporti una modifica, devi riflettere tali modifiche nei file di unione e schema di configurazione.

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

La cartella globale contiene file condivisi tra i diversi team di progetto. Per semplicità, la cartella di configurazione contiene la configurazione dell'organizzazione e tutti i file di configurazione del reparto. In questo esempio non esiste una classe helper separata per i reparti. Puoi aggiungere qualsiasi classe helper a livello di organizzazione o di sistema.

La cartella globale può essere contenuta in un repository Git separato. Puoi fare riferimento ai suoi file dai singoli sistemi. Potete anche utilizzare link simbolici, che potrebbero creare confusione o interruzioni in alcuni sistemi operativi.

├── 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 Systems 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 del 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 corsi helper e fare riferimento ai corsi globali.

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

Nella cartella dei modelli, 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 della tua infrastruttura come codice è clonare il codebase di esempio e copiare i contenuti della cartella gerarchica_configurazione.

  1. Dai un'occhiata al 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, utilizza i seguenti link simbolici per fare riferimento ai file globali nel 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 giusto contesto di ambiente, utilizza il flag --properties per specificare la proprietà envName. Questa proprietà ti consente di eseguire lo stesso codice, scegliendo come target diversi ambienti, 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 seguenti best practice possono aiutarti a strutturare il codice in modo gerarchico.

File di schema

Nel file di schema è obbligatorio specificare ogni file che utilizzi in qualsiasi modo durante il deployment. L'aggiunta di un'intera cartella rende il codice più breve e generico.

  • Corsi helper:
- 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

Si tratta di una best practice per contenere un deployment, ovvero utilizzare gli stessi set di configurazione, anche se si tratta di moduli diversi, ad esempio networking, firewall, backend, frontend. Potresti dover accedere all'output di questi deployment da un altro deployment. Puoi eseguire query sull'output del deployment quando è pronto e salvarlo nella cartella Configuration. 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 ogni sistema operativo.

Gerarchia delle configurazioni

Lo schema seguente è una panoramica dei diversi livelli e delle relative relazioni. Ogni rettangolo rappresenta un file di proprietà, come indicato dal nome file in rosso.

Gerarchia della configurazione con livelli diversi e relative relazioni definite.

Ordine di unione sensibile al contesto

La fusione di configurazione seleziona i file di configurazione appropriati a ogni livello in base al contesto in cui viene utilizzato ogni file. Il contesto è un modulo che stai eseguendo il deployment in un ambiente. Questo contesto definisce automaticamente il sistema e il reparto.

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

Diagramma dell'ordine di sovrascrittura

Passaggi successivi