Strutturazione di Deployment Manager per l'utilizzo su larga scala

Quando il sistema "Infrastructure as Code" cresce oltre l'esempio "Hello World" senza pianificazione, il codice tende a diventare non strutturato. Le configurazioni non pianificate sono 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 convenzione di denominazione e le best practice interne ai tuoi team. Questo documento è rivolto a un pubblico tecnicamente avanzato e presuppone che tu abbia una conoscenza di base di Python, dell'infrastruttura Google Cloud, di Deployment Manager e, in generale, di Infrastructure as Code.

Prima di iniziare

Più ambienti con una singola base di codice

Per i deployment di grandi dimensioni con più di una dozzina di risorse, le best practice standard richiedono di utilizzare una quantità significativa di proprietà esterne (parametri di configurazione), in modo da evitare di codificare stringhe e logica in 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. Il rispetto di queste best practice comporta un ampio insieme di proprietà di configurazione con un'elevata quantità di duplicazioni che possono diventare difficili da gestire, aumentando così la possibilità di errori umani.

La tabella seguente è un esempio di codice per illustrare le differenze tra la configurazione gerarchica e una singola configurazione per ogni implementazione. La tabella evidenzia una duplicazione comune in una singola configurazione. Se utilizzi una configurazione gerarchica, la tabella mostra come spostare le sezioni ripetute a un livello superiore nella gerarchia per evitare ripetizioni e ridurre le probabilità di errori umani.

Modello Configurazione gerarchica senza ridondanza Configurazione singola con ridondanza

project_config.py

config = { 'ProjectId': 'qwerty123456', 'ProjectOwner': 'Bob', 'ProjectAbbrevation': 'SNP' }

N/D

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 una base di codice 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 anziché uno solo. Inoltre, utilizzi funzioni di supporto e condividi parte della base di codice con l'intera organizzazione.

L'esempio di codice allegato a questo documento, Organization_with_departments, contiene un codebase con una struttura predefinita, ma personalizzabile, uno script di supporto per l'unione delle 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 la definizione di una struttura gerarchica del codice offrono diversi vantaggi:

  • Se dividi la configurazione in più file, migliori la struttura e la leggibilità delle proprietà. Inoltre, eviti di duplicarli.
  • Progetta l'unione gerarchica in modo che i valori vengano applicati in modo gerarchico in modo logico, creando file di configurazione di primo livello riutilizzabili in più progetti o componenti.
  • Definisci ogni proprietà una sola volta (tranne le sostituzioni), evitando di dover gestire lo spazio dei nomi nei nomi delle proprietà.
  • I modelli non devono conoscere l'ambiente effettivo, perché la configurazione appropriata viene caricata in base alle variabili appropriate.

Strutturare il codebase in modo gerarchico

Un deployment di Deployment Manager contiene una configurazione YAML o un file schema, oltre 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 generale (classi di supporto) o come file di codice che memorizzano le 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.

Trattare l'infrastruttura come codice reale

Un principio importante per il codice pulito è Don't Repeat Yourself (DRY). Definisci tutto una sola volta. Questo approccio rende la base di codice più chiara, più facile da esaminare e convalidare e più facile da gestire. Quando una proprietà deve essere modificata in un solo punto, il rischio di errore umano diminuisce.

Per una base di codice più leggera con file di configurazione più piccoli e una duplicazione minima, utilizza queste linee guida per strutturare le configurazioni in modo da seguire il principio DRY.

Organizzazioni, reparti, ambienti e moduli

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

Nel seguente diagramma, un modulo viene di cui viene eseguito il deployment in un ambiente. La combinazione delle configurazioni seleziona i file di configurazione appropriati su ogni livello in base al contesto in cui vengono utilizzati. Inoltre, definisce 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à dell'organizzazione

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

  2. Proprietà del reparto

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

  3. Proprietà di sistema (progetto)

    Ogni reparto contiene sistemi. Un sistema è uno stack di 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 di supporto (ad esempio 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, ad esempio 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 anziché mantenerlo come un unico blocco monolitico di grandi dimensioni. Ad esempio, potresti spostare la rete e la sicurezza di base in blocchi separati. Puoi anche separare i livelli di backend, frontend e database in moduli distinti. I moduli sono modelli sviluppati da terze parti in cui aggiungi solo la configurazione appropriata. A livello di modulo, puoi definire proprietà pertinenti solo per determinati moduli, incluse quelle progettate per sovrascrivere le proprietà ereditate a livello di sistema. I livelli di ambiente e modulo sono suddivisioni parallele in un sistema, ma i moduli seguono gli ambienti nel processo di unione.

  6. Proprietà dei moduli specifiche per l'ambiente

    Alcune proprietà del modulo potrebbero dipendere anche dall'ambiente, ad esempio dimensioni delle istanze, immagini, endpoint. Le proprietà del modulo specifiche dell'ambiente sono il livello più specifico e l'ultimo punto dell'unione con gerarchia per l'override dei valori definiti in precedenza.

Classe di assistenza per l'unione delle configurazioni

La classe config_merger è una classe di supporto 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 alla funzione statica ConfigContext restituisce il dizionario di configurazione unito.

Il seguente codice mostra come utilizzare questa classe:

  • module = "frontend" specifica il contesto in cui vengono caricati i file di 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 di supporto deve allinearsi alle strutture di configurazione, caricare tutti i livelli nell'ordine corretto e sovrascrivere i valori di configurazione appropriati. Per modificare i livelli o l'ordine di sovrascrittura, modifica la classe di unione della configurazione.

Nell'uso quotidiano e di routine, in genere non hai bisogno di partecipare a questo corso. In genere, modifichi i modelli e i file di configurazione appropriati, quindi utilizzi il dizionario di output contenente tutte le configurazioni.

La base di codice 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'inizializzazione del repository. Questi file possono trovarsi in un repository di codice separato, poiché non fanno parte in modo logico della base di codice di un team di progetto, ma sono condivisi nell'intera organizzazione e nel reparto.

L'unione della configurazione cerca anche i file corrispondenti ai livelli rimanenti della tua struttura:

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

File di configurazione

Deployment Manager utilizza un unico file di configurazione per un deployment specifico. Non può essere condiviso tra i deployment.

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

Qualsiasi file Python può contenere variabili, che ti consentono di memorizzare la configurazione in modo strutturato, ma distribuito. L'approccio migliore è utilizzare i dizionari con una struttura concordata. L'unione delle configurazioni cerca un dizionario chiamato configs in ogni file della catena di unione. I singoli configs vengono uniti in un unico canale.

Durante l'unione, se una proprietà con lo stesso percorso e nome viene visualizzata più volte nei dizionari, l'unione della configurazione la sovrascrive. In alcuni casi, questo comportamento è utile, ad esempio quando un valore predefinito viene override da un valore specifico del contesto. Tuttavia, esistono molti altri casi in cui è consigliabile evitare di sovrascrivere la proprietà. Per evitare l'overwriting di una proprietà, aggiungi uno spazio dei nomi separato per renderla univoca. Nell'esempio seguente viene aggiunto 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',
    }
}

Classi di supporto e convenzioni di denominazione

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

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

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 di supporto semplifica la denominazione di ogni istanza in base alla convenzione concordata. Inoltre, semplifica la revisione del codice perché nessun nome di istanza proviene da un'altra funzione. La funzione recupera 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 per le etichette. Le funzioni più complesse possono anche generare un insieme di etichette predefinite.

Struttura della cartella del codice di esempio

La struttura delle cartelle del codice di esempio è flessibile e personalizzabile. Tuttavia, è parzialmente hardcoded nell'unione della configurazione e nel file dello schema di Deployment Manager, il che significa che se apporti una modifica, devi rifletterla nei file di unione della configurazione e dello 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 la configurazione dell'organizzazione e tutti i file di configurazione dei reparti. In questo esempio, non è presente una classe di helper distinta per i reparti. Puoi aggiungere qualsiasi classe di supporto a livello di organizzazione o sistema.

La cartella globale può trovarsi in un repository Git separato. Puoi fare riferimento ai suoi file dai singoli sistemi. Puoi anche utilizzare i link simbolici, ma potrebbero creare confusione o interruzioni in determinati 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 dei sistemi contiene uno o più sistemi diversi. I sistemi sono separati e non condividono configurazioni.

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

La cartella di configurazione contiene tutti i file di configurazione specifici di questo sistema, a cui fa riferimento anche per le 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 di supporto, puoi aggiungere altri moduli di supporto e fare riferimento ai moduli 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 della base di codice di esempio

Il modo migliore per iniziare ad applicare questa pratica gerarchica come base della tua infrastruttura come codice è clonare la base di codice di esempio e copiare i contenuti della cartella hierarchical_configuration.

  1. Consulta 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, 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 contesto dell'ambiente corretto, utilizza il flag --properties per specificare la proprietà envName. Questa proprietà ti consente di eseguire lo stesso codice, con targeting di diversi ambienti, con lo stesso comando. [MY-PROJECT-ID] rappresenta l'ID progetto 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 dello schema, è un requisito di Deployment Manager elencare ogni file che utilizzi in qualsiasi modo durante il deployment. L'aggiunta di un'intera cartella rende il codice più breve e generico.

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

Deployment multipli

È una best practice che un sistema contenga più implementazioni, il che significa che useranno gli stessi insiemi di configurazioni, anche se sono moduli diversi, ad esempio networking, firewall, backend, frontend. Potresti dover accedere all'output di questi implementazioni da un altro deployment. Puoi eseguire query sull'output del deployment quando è pronto e salvarlo nella cartella delle 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 ogni sistema operativo.

Gerarchia di configurazione

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

Gerarchia di configurazione con diversi livelli e relative relazioni.

Ordine di unione sensibile al contesto

L'unione delle configurazioni seleziona i file di configurazione appropriati su ogni livello in base al contesto in cui viene utilizzato ciascun file. Il contesto è un modulo di cui stai eseguendo 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