{"id":1161,"date":"2020-02-07T15:04:25","date_gmt":"2020-02-07T14:04:25","guid":{"rendered":"https:\/\/blog.besharp.it\/?p=1161"},"modified":"2021-03-17T15:10:13","modified_gmt":"2021-03-17T14:10:13","slug":"troposphere-un-tool-per-creare-gestire-e-mantenere-una-infrastruttura-aws-basata-su-cloudformation","status":"publish","type":"post","link":"https:\/\/blog.besharp.it\/it\/troposphere-un-tool-per-creare-gestire-e-mantenere-una-infrastruttura-aws-basata-su-cloudformation\/","title":{"rendered":"Troposphere: un tool per creare, gestire e mantenere una infrastruttura AWS basata su CloudFormation."},"content":{"rendered":"
La gran parte delle moderne applicazioni web SaaS sono sviluppate e deployate sull\u2019infrastruttura Cloud messa a disposizione da un Cloud Provider. In particolare Amazon Web Service \u00e8 stato il primo vero Cloud provider e ha saputo conquistare e mantenere la guida di questo settore di mercato grazie alla qualit\u00e0 e alla flessibilit\u00e0 dei servizi offerti.<\/span><\/p>\n Le infrastrutture create su AWS stanno diventando sempre pi\u00f9 grandi per sfruttare al massimo i vantaggi dei nuovi servizi rilasciati regolarmente da AWS. Il numero di servizi managed offerti direttamente da AWS \u00e8 enorme e cresce di anno in anno.<\/span><\/p>\n L\u2019utilizzo di servizi managed da AWS al posto, per esempio, di soluzioni custom implementate su macchine virtuali EC2 garantisce una notevole diminuzione dei costi di setup e manutenzione dell\u2019infrastruttura date che Amazon si fa carico del deployment, dell\u2019ottimizzazione, della manutenzione e anche della sicurezza di tutti i servizi offerti.<\/span><\/p>\n Inoltre la maggior parte dei servizi AWS \u00e8 progettata per essere in alta affidabilit\u00e0 senza la necessit\u00e0 di configurazioni aggiuntive particolari, consentendo agli utilizzatori di evitare un altro grosso sforzo di configurazione e test.<\/span><\/p>\n L\u2019utilizzo dei servizi managed da AWS come componenti base consente agli sviluppatori di aggredire con successo e in tempi rapidi un gran numero di problemi e di sviluppare praticamente ogni tipo di applicazione. Per esempio per una tipica applicazione web serverless moderna \u00e8 possibile usare Amazon Cognito per l’autenticazione,\u00a0 DynamoDB come database, SNS\/SES rispettivamente per notifiche push e mail, S3\/Cloudfront per servire il frontend e SQS per le code applicative interne.<\/span><\/p>\n Tuttavia la maggior parte delle applicazione ha un livello di complessit\u00e0 molto maggiore di questo (sono spesso richiesti modelli di Machine Learning, datalakes, connessioni vpn verso altri networks\/servizi, tipi diversi di database, sistemi di batch processing etc.) e quindi il numero di servizi e risorse dell\u2019infrastruttura AWS sale rapidamente a numeri molto elevati (spesso centinaia di risorse create) e le infrastrutture risultanti sono cos\u00ec grandi e complesse che non possono pi\u00f9\u00a0 essere create e mantenute \u2018a mano\u2019.<\/span><\/p>\n Infatti molto spesso modifiche ad un solo componente (per esempio un security group, un ruolo o una routing table) possono dar luogo ad effetti collaterali imprevisti rilevanti che possono bloccare vari servizi e potenzialmente l\u2019intera applicazione.<\/span><\/p>\n In questi casi la gestione dell\u2019infrastruttura tramite IaC (Infrastructure as Code) da un grosso aiuto. Tramite IaC \u00e8 infatti possibile descrivere l\u2019intera infrastruttura AWS scrivendo codice, quindi \u00e8 possibile versionarlo usando Git come per ogni altro progetto software. Quando il codice IaC viene eseguito andr\u00e0 a creare o ad aggiornare l\u2019infrastruttura per portarla ad uno stato identico a quello descritto nel codice! Se in un secondo momento dovesse sorgere la necessit\u00e0 di modificare l\u2019infrastruttura sar\u00e0 sufficiente aggiornare il codice IaC, committare la modifica ed eseguire nuovamente il codice. In caso di problemi imprevisti \u00e8 perci\u00f2 possibile effettuare il rollback di tutta l\u2019infrastruttura all’ultima versione funzionante.<\/span><\/p>\n Se ti sembra tutto troppo bello per essere vero probabilmente hai ragione! Ogni livello di astrazione che andiamo ad aggiungere ad un progetto software ha le sue problematiche e lo IaC non fa eccezione. Il primo problema che uno sviluppatore tipicamente incontra approcciandosi allo Iac \u00e8 la scelta dello strumento, infatti vi sono due framework IaC principali al momento disponibile per AWS: Terraform and CloudFormation. Inizialmente abbiamo sperimentato con terraform ma sono emersi alcuni problemi significativi:<\/span><\/p>\n L\u2019unico vero use case di Terraform \u00e8 perci\u00f2 quello che prevede la creazione coordinata di risorse su pi\u00f9 Cloud Providers (e.g. Azure, Google Cloud Platform e AWS).<\/span><\/p>\n CloudFormation invece \u00e8 a sua volta un servizio totalmente gestito da AWS: l\u2019utilizzatore deve semplicemente caricare su S3 un file YAML o JSON contenente la descrizione dell\u2019infrastruttura da ottenere e Cloudformation si prender\u00e0 carico di eseguirlo in modo sicuro e stateful. I Rollback in caso di errore sono supportati nativamente ed \u00e8 anche possibile eseguire dei dry runs del template creando dei Change Set al fine di capire quali risorse andranno ad essere effettivamente modificate durante l\u2019esecuzione vera e propria del template. In generale l\u2019esecuzione di un template Cloudformation \u00e8 significativamente meno prona ad errori di quella di un template Terraform anche grazie al fatto che il servizio \u00e8 nativo AWS.<\/span><\/p>\n Tuttavia anche CloudFormation presenta alcune alcune criticit\u00e0: i file YAML sono spesso molto lunghi e difficili sia da scrivere che da debuggare, inoltre, come nel caso di Terraform i cicli e la logica avanzata non sono supportati. Dividere un progetto in pi\u00f9 moduli tramite i Nested Stacks \u00e8 essenziale per motivi di manutenibilit\u00e0 del codice e per evitare di incorrere nel limite di 200 risorse per file, tuttavia i Nested Stack sono difficili da usare insieme ai Change Sets. Il prossimo passo nella strada verso una migliore esperienza IaC \u00e8 perci\u00f2 quello di generare i file YAML tramite un linguaggio di programmazione di alto livello come Python!<\/span><\/p>\n Anche qui abbiamo due possibilit\u00e0: AWS CDK e Troposphere. AWS CDK \u00e8 sviluppato da AWS, appena rilasciato e molto potente: con pochi comandi \u00e8 possibile creare infrastrutture piuttosto complesse. Tuttavia il suo essere di alto livello \u00e8 anche il suo pi\u00f9 grande difetto: alcune operazioni di basso livello diventano difficili da implementare e lo YAML generato \u00e8 di difficile comprensione per un essere umano dato che i nomi logici delle risorse create sono generati direttamente da CDK.<\/span><\/p>\n Al contrario Troposphere ha nella semplicit\u00e0 la sua pi\u00f9 grande forza: si tratta di un Python DSL che va a mappare le entit\u00e0 di Cloudformation in oggetti Python (e viceversa!).<\/span><\/p>\n Questo paradigma ci fornisce esattamente ci\u00f2 che stavamo cercando: un modo semplice per creare template cloudformation che non solo fanno ci\u00f2 che vogliamo, ma che vengono generati esattamente come li vogliamo da un linguaggio di alto livello con tutti i costrutti di logica avanzati cos\u00ec utili in molteplici situazioni. Inoltre l\u2019IDE di Python di nostra scelta ci aiuter\u00e0 a risolvere errori e inconsistenze ancora prima di eseguire o validare lo YAML template e la compilazione stessa del codice da Python a YAML andr\u00e0 in errore in caso di inconsistenze logiche rendendo molto pi\u00f9 rapido il debugging.<\/span><\/p>\n Presentiamo qui un semplice esempio dell\u2019uso di questo approccio per mostrarne potenza e versatili\u00e0: andremo a creare tramite Troposphere una VPC con 3 subnet, una per availability zone e la loro route table<\/span><\/p>\n Per prima cosa diamo per\u00f2 uno sguardo a come si presenter\u00e0 il cloudformation YAML template per questa semplice infrastruttura:<\/span><\/p>\n Si pu\u00f2 immediatamente notare che il codice YAML sebbene sia stato generato in modo programmatico \u00e8 immediatamente leggibile e comprensibile. Buona parte del codice di questo esempio \u00e8 in realt\u00e0 duplicato perch\u00e9 stiamo andando a creare 3 subnets con le stesse caratteristiche e associazioni alla stessa routing table, ma con nomi logici e CIDR diversi.<\/p>\n Il codice Troposphere usato per generare il template appena visto \u00e8 il seguente:<\/p>\n L\u2019esecuzione di questo script dopo aver installato Troposphere (pip install troposphere) mander\u00e0 in output il template YAML. Come si pu\u00f2 vedere il codice python \u00e8 di gran lunga pi\u00f9 compatto del template YAML e molto pi\u00f9 semplice da leggere. Oltretutto, dato che Troposphere mappa anche tutte le funzioni native di CloudFormation (Ref, Join, GettAtt etc.) non abbiamo nemmeno bisogno di imparare nulla di nuovo: ogni template di cloudformation esistente pu\u00f2 essere rapidamente convertito in un programma python.<\/p>\n A differenza di quanto si fa in CloudFormation con troposphere possiamo associare le variabili risorse a variabili Python e usare queste ultime come riferimento alla risorsa da passare come parametro alle funzioni Ref e GettAtt al posto dei nomi logici delle risorse: nell’esempio sopra abbiamo referenziato la subnet privata con<\/p>\n Questo \u00e8 un grosso vantaggio in quanto non dobbiamo pi\u00f9 ricordare i nomi delle risorse logiche definite nel template: l\u2019IDE ci aiuter\u00e0 a trovarle e ci avvertir\u00e0 di eventuali inconsistenze.<\/p>\n\n
Description: AWS CloudFormation Template to create a VPC\r\nParameters:\r\n SftpCidr:\r\n Description: SftpCidr\r\n Type: String\r\nResources:\r\n SftpVpc:\r\n Properties:\r\n CidrBlock: !Ref 'SftpCidr'\r\n EnableDnsHostnames: 'true'\r\n EnableDnsSupport: 'true'\r\n Type: AWS::EC2::VPC\r\n RouteTablePrivate:\r\n Properties:\r\n VpcId: !Ref 'SftpVpc'\r\n Type: AWS::EC2::RouteTable\r\n PrivateSubnet1:\r\n Properties:\r\n AvailabilityZone: !Select\r\n - 0\r\n - !GetAZs\r\n Ref: AWS::Region\r\n CidrBlock: !Select\r\n - 4\r\n - !Cidr\r\n - !GetAtt 'SftpVpc.CidrBlock'\r\n - 16\r\n - 8\r\n MapPublicIpOnLaunch: 'false'\r\n VpcId: !Ref 'SftpVpc'\r\n Type: AWS::EC2::Subnet\r\n PrivateSubnet2:\r\n Properties:\r\n AvailabilityZone: !Select\r\n - 1\r\n - !GetAZs\r\n Ref: AWS::Region\r\n CidrBlock: !Select\r\n - 5\r\n - !Cidr\r\n - !GetAtt 'SftpVpc.CidrBlock'\r\n - 16\r\n - 8\r\n MapPublicIpOnLaunch: 'false'\r\n VpcId: !Ref 'SftpVpc'\r\n Type: AWS::EC2::Subnet\r\n PrivateSubnet3:\r\n Properties:\r\n AvailabilityZone: !Select\r\n - 2\r\n - !GetAZs\r\n Ref: AWS::Region\r\n CidrBlock: !Select\r\n - 6\r\n - !Cidr\r\n - !GetAtt 'SftpVpc.CidrBlock'\r\n - 16\r\n - 8\r\n MapPublicIpOnLaunch: 'false'\r\n VpcId: !Ref 'SftpVpc'\r\n Type: AWS::EC2::Subnet\r\n SubnetPrivateToRouteTableAttachment1:\r\n Properties:\r\n RouteTableId: !Ref 'RouteTablePrivate'\r\n SubnetId: !Ref 'PrivateSubnet1'\r\n Type: AWS::EC2::SubnetRouteTableAssociation\r\n SubnetPrivateToRouteTableAttachment2:\r\n Properties:\r\n RouteTableId: !Ref 'RouteTablePrivate'\r\n SubnetId: !Ref 'PrivateSubnet2'\r\n Type: AWS::EC2::SubnetRouteTableAssociation\r\n SubnetPrivateToRouteTableAttachment3:\r\n Properties:\r\n RouteTableId: !Ref 'RouteTablePrivate'\r\n SubnetId: !Ref 'PrivateSubnet3'\r\n Type: AWS::EC2::SubnetRouteTableAssociation\r\n<\/pre>\n
import troposphere.ec2 as vpc\r\n\r\ntemplate = Template()\r\ntemplate.set_description(\"AWS CloudFormation Template to create a VPC\")\r\n\r\nsftp_cidr = template.add_parameter(\r\n Parameter('SftpCidr', Type='String', Description='SftpCidr')\r\n )\r\n\r\nvpc_sftp = template.add_resource(vpc.VPC(\r\n 'SftpVpc',\r\n CidrBlock=Ref(sftp_cidr),\r\n EnableDnsSupport=True,\r\n EnableDnsHostnames=True,\r\n ))\r\n \r\nprivate_subnet_route_table = template.add_resource(vpc.RouteTable(\r\n 'RouteTablePrivate',\r\n VpcId=Ref(vpc_sftp)\r\n ))\r\n\r\nfor ii in range(3):\r\n private_subnet = template.add_resource(vpc.Subnet(\r\n 'PrivateSubnet' + str(ii + 1),\r\n VpcId=Ref(vpc_sftp),\r\n MapPublicIpOnLaunch=False,\r\n AvailabilityZone=Select(ii, GetAZs(Ref(AWS_REGION))),\r\n CidrBlock=Select(ii + 4, Cidr(GetAtt(vpc_sftp, 'CidrBlock'), 16, 8))\r\n ))\r\n private_subnet_attachment = template.add_resource(vpc.SubnetRouteTableAssociation(\r\n 'SubnetPrivateToRouteTableAttachment' + str(ii + 1),\r\n SubnetId=Ref(private_subnet),\r\n RouteTableId=Ref(private_subnet_route_table)\r\n ))\r\n \r\nprint(template.to_yaml())\r\n<\/pre>\n
Ref(private_subnet_route_table), non con Ref('RouteTablePrivate').<\/pre>\n