La lliçó anterior va acabar amb una limitació concreta: red-mercadofresco.yaml descriu sis subxarxes gairebé idèntiques escrites a mà, una darrere l'altra, perquè YAML no té bucles. Tampoc no té funcions, ni tipus, ni cap manera d'encapsular «la xarxa estàndard de MercadoFresco» en alguna cosa que es reutilitzi en tres entorns. I no hi ha manera d'escriure una prova que verifiqui que cap plantilla no obre el port 22 al món. L'AWS Cloud Development Kit resol les quatre coses d'una sola manera: deixant que la infraestructura s'escrigui en un llenguatge de programació de veritat.
El més important des del primer minut és que el CDK no substitueix CloudFormation. Tot el de 09-01 —piles, conjunts de canvis, deriva, DeletionPolicy, ROLLBACK_COMPLETE— continua sent exactament igual de cert. El CDK escriu les plantilles per tu.
Avís de cost. El CDK és gratuït; pagues els recursos que crea.
cdk bootstrapcrea un bucket d'S3, un repositori d'ECR i cinc rols d'IAM per compte i regió: cèntims al mes mentre estiguin buits. Els exemples d'aquesta lliçó despleguen una VPC amb NAT (uns 0,045 USD/hora cadascun) i un ALB (uns 0,025 USD/hora): si segueixes els exercicis, destrueix les piles en acabar amb l'ordre de l'apartat de neteja. Dades fictícies.
Contingut
- Què és el CDK i quina relació té amb CloudFormation
- Per què compensa, i quan CloudFormation pur continua sent millor
- Instal·lació,
cdk initi estructura del projecte - App, Stack, Construct i els tres nivells de constructes
- La xarxa de MercadoFresco en CDK
- Un constructe propi:
MercadoFrescoVpc - Les capes d'aplicació i integració en Python
- Actius: el codi de les Lambda dins de la pila
- El cicle de vida:
synth,diff,deploy,destroyibootstrap - Context, entorns explícits i una pila per entorn
- Aspectes: etiquetatge obligatori i regles d'auditoria
- Proves d'infraestructura
- Recursos amb estat,
RemovalPolicyi el perill decdk destroy - CDK Pipelines: el pipeline que es desplega a si mateix
- Terraform, CDK for Terraform i la comparació honesta
- Cost i neteja
- Errors habituals i consells
- Exercicis
- Conclusió
Què és el CDK i quina relació té amb CloudFormation
El CDK és una biblioteca de classes —disponible en TypeScript, JavaScript, Python, Java, C# i Go— amb la qual es descriu la infraestructura com a objectes. Quan executes cdk synth, aquests objectes es sintetitzen: produeixen una plantilla de CloudFormation, que després es desplega com una pila normal.
flowchart LR
A[Codi TypeScript<br/>o Python] -->|cdk synth| B[Plantilla<br/>CloudFormation]
B --> C[cdk.out/<br/>artefacte de nuvol]
C -->|cdk deploy| D[Conjunt de canvis<br/>a CloudFormation]
D --> E[Pila desplegada:<br/>recursos reals]
E -->|cdk diff| A
D'aquest flux en surten tres conseqüències pràctiques que convé interioritzar abans d'escriure una línia:
- L'artefacte de desplegament continua sent una plantilla. Pots llegir-la, versionar-la, revisar-la i desplegar-la sense el CDK.
cdk.out/conté el resultat exacte de la síntesi, icdk synth > plantilla.yamlte'l deixa en un fitxer. - Els errors de CloudFormation continuen passant. Un
ROLLBACK_COMPLETEen una pila del CDK es diagnostica i es resol com a 09-01: mirant els esdeveniments de la pila. - El codi no s'executa a AWS. S'executa al teu portàtil o a CodeBuild, i la seva única sortida és JSON. No hi ha res «viu» del CDK al compte tret del que crea el bootstrap.
Per què compensa, i quan CloudFormation pur continua sent millor
El que aporta un llenguatge de programació no és una sintaxi més bonica: són cinc capacitats que YAML no té. Bucles, per a les sis subxarxes. Condicionals de veritat, avaluats en la síntesi i no en el desplegament, cosa que fa que la plantilla generada sigui més simple, no més complexa. Tipus, que converteixen un error de propietat en una fallada de compilació a l'editor, no en un CREATE_FAILED al cap de vuit minuts. Reutilització, encapsulant patrons en classes amb nom. I proves, executables al pipeline com les del codi d'aplicació.
Però el CDK no és la resposta correcta en tots els casos, i convé dir-ho sense adorns:
| Criteri | CloudFormation pur | AWS CDK |
|---|---|---|
| Equip sense cultura de programació | Millor: un YAML el llegeix qualsevol | Pitjor: cal saber TypeScript o Python |
| Infraestructura estable, que canvia dues vegades l'any | Millor: res a mantenir | Pitjor: dependències que cal actualitzar cada trimestre |
| Auditoria externa que revisa la infraestructura | Millor: l'artefacte és el que es revisa | Pitjor: cal auditar codi i plantilla generada |
| Molta repetició (subxarxes, entorns, comptes) | Pitjor: copiar i enganxar | Millor: bucles i constructes |
| Necessitat de proves automàtiques | Pitjor: cfn-guard i poca cosa més |
Millor: proves unitàries reals |
| Abstraccions pròpies compartides entre equips | Pitjor: mòduls poc estesos | Millor: una biblioteca versionada |
| Corba d'entrada | Millor: s'aprèn en una tarda | Pitjor: una setmana fins a ser productiu |
| Depuració d'una fallada rara | Pitjor i millor alhora: només hi ha una capa | Pitjor: dues capes, codi i plantilla |
MercadoFresco tria CDK per dues raons concretes de la seva situació: té tres entorns que han de ser idèntics i un equip de dues persones que ja programen cada dia. Si l'equip fos de sistemes purs i la infraestructura portés dos anys sense tocar-se, la resposta correcta seria quedar-se en YAML.
Instal·lació, cdk init i estructura del projecte
El CDK s'instal·la amb npm, fins i tot per a projectes en Python: l'eina de línia d'ordres és Node.
npm install -g aws-cdk # eina de linia d'ordres
cdk --version # 2.x
mkdir infra-cdk && cd infra-cdk
cdk init app --language typescript # o --language pythoncdk init genera un projecte executable. El més important de la seva estructura:
| Fitxer | Què és |
|---|---|
bin/infra-cdk.ts |
Punt d'entrada: instancia l'App i les piles |
lib/*-stack.ts |
Les piles: aquí hi viu la infraestructura |
test/*.test.ts |
Proves amb Jest, ja configurades |
cdk.json |
Configuració: ordre d'execució, context i banderes de compatibilitat |
cdk.out/ |
Sortida de la síntesi. No es versiona |
package.json |
Dependències: aws-cdk-lib i constructs |
Un advertiment sobre cdk.json: la secció context inclou banderes de funcionalitat (@aws-cdk/aws-*:featureFlag) que canvien el comportament per omissió dels constructes. Es generen amb la versió del CDK que va crear el projecte i no s'han de tocar a mà: modificar-les pot canviar l'ARN o el nom de recursos ja desplegats, amb reemplaçament inclòs.
Al repositori mercadofresco-infra, el projecte viu a infra-cdk/ al costat de les plantilles de 09-01, que es mantenen mentre dura la migració.
App, Stack, Construct i els tres nivells de constructes
Tres conceptes, i tota la resta se'n dedueix:
- App: l'aplicació CDK completa, arrel de l'arbre. Una App conté piles.
- Stack: la unitat de desplegament, que correspon 1:1 amb una pila de CloudFormation. Els límits de 09-01 —500 recursos— continuen aplicant-se.
- Construct: qualsevol node de l'arbre. Un recurs, un grup de recursos o una pila sencera són constructes. Tots reben els mateixos tres arguments:
scope(el pare),id(únic entre germans) iprops.
L'id és l'equivalent del nom lògic de 09-01, i n'hereta la regla més important: canviar l'id d'un constructe desplegat destrueix i recrea el recurs. El CDK compon els identificadors lògics concatenant el camí de l'arbre i afegint-hi un sufix, així que moure un constructe de lloc també el recrea.
Els constructes tenen tres nivells d'abstracció, i el mateix bucket il·lustra la diferència:
// NIVELL 1 (L1): calc directe del recurs de CloudFormation. Prefix Cfn.
// No posa res per defecte: si no ho declares, no existeix.
new s3.CfnBucket(this, 'FotosL1', {
bucketName: 'mercadofresco-catalogo-fotos',
bucketEncryption: { serverSideEncryptionConfiguration: [
{ serverSideEncryptionByDefault: { sseAlgorithm: 'aws:kms' } } ] },
versioningConfiguration: { status: 'Enabled' },
});
// NIVELL 2 (L2): API amb valors per defecte assenyats, tipus i metodes de conveniencia.
const fotos = new s3.Bucket(this, 'FotosL2', {
encryption: s3.BucketEncryption.KMS,
encryptionKey: clauDades,
versioned: true,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, // per defecte ja ho bloqueja
});
fotos.grantRead(rolMiniatures); // genera la politica d'IAM minima necessaria
// NIVELL 3 (L3): patro complet. Diversos recursos coordinats amb una sola crida.
new patterns.ApplicationLoadBalancedFargateService(this, 'Tienda', { /* ... */ });| Nivell | Què és | Quan fer-lo servir |
|---|---|---|
L1 (Cfn*) |
Traducció automàtica de l'esquema de CloudFormation | Un recurs o propietat que l'L2 encara no admet |
| L2 | API acurada, valors per defecte segurs, mètodes grant*, metric* |
El 90 % del temps |
| L3 | Patrons que combinen diversos recursos | Prototips i arquitectures estàndard |
Dos avisos. De l'L2 sempre pots baixar a l'L1 amb recurso.node.defaultChild as s3.CfnBucket per tocar una propietat que l'API no exposa; és la vàlvula d'escapament que evita quedar-se bloquejat. I els L3 són còmodes i opacs: creen més recursos dels que esperes i la seva configuració fina costa més que escriure-la a mà, així que MercadoFresco els fa servir per prototipar i després baixa a L2.
Una altra peça que estalvia molt de temps són els mètodes grant*: cola.grantConsumeMessages(rol) genera la política d'IAM correcta —inclosos els permisos sobre la clau de KMS— sense que ningú escrigui un JSON. És, a la pràctica, el mínim privilegi de 04-01 per defecte.
La xarxa de MercadoFresco en CDK
Les 190 línies de YAML de 09-01 es converteixen en això:
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import { Construct } from 'constructs';
export class RedStack extends cdk.Stack {
public readonly vpc: ec2.Vpc;
constructor(scope: Construct, id: string, props: cdk.StackProps & { entorno: string }) {
super(scope, id, props);
this.vpc = new ec2.Vpc(this, 'Vpc', {
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
maxAzs: 2, // eu-west-1a i eu-west-1b
// Un NAT per AZ en produccio; nomes un a la resta. La condicio s'avalua AQUI,
// en la sintesi, aixi que la plantilla generada no porta cap Fn::If.
natGateways: props.entorno === 'produccion' ? 2 : 1,
subnetConfiguration: [
{ name: 'publica', subnetType: ec2.SubnetType.PUBLIC, cidrMask: 24 },
{ name: 'app', subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, cidrMask: 24 },
{ name: 'datos', subnetType: ec2.SubnetType.PRIVATE_ISOLATED, cidrMask: 24 },
],
gatewayEndpoints: {
S3: { service: ec2.GatewayVpcEndpointAwsService.S3 },
},
});
}
}Vint-i-vuit línies contra cent noranta, i el resultat sintetitzat és equivalent: la mateixa VPC, sis subxarxes (tres configuracions × dues AZ), la passarel·la d'internet, els NAT, quatre taules de rutes amb les seves associacions i l'endpoint d'S3. El que ha desaparegut no és la infraestructura, és la repetició mecànica.
Val la pena aturar-se en tres detalls:
subnetConfigurationés el bucle. Declares tres tipus de subxarxa i el CDK en crea una per AZ, calculant els CIDR automàticament a partir del bloc de la VPC i decidrMask. Afegir una tercera AZ és canviarmaxAzs: 2per3.SubnetTypecodifica la intenció, no la implementació:PRIVATE_WITH_EGRESSsignifica «privada amb sortida per NAT» iPRIVATE_ISOLATED, «sense sortida a internet». Les subxarxes de dades de MercadoFresco són exactament això segon, i el CDK no els crea ruta cap a0.0.0.0/0perquè el tipus ho prohibeix.- El condicional del NAT es resol en la síntesi. A 09-01 calia un
Mappings, unaConditioni unFn::If; aquí és un operador ternari de TypeScript i la plantilla resultant no té cap condició. Aquesta és la diferència entre avaluar en temps de síntesi i avaluar en temps de desplegament, i és la raó per la qual les plantilles generades solen ser més simples que les escrites a mà.
Un matís honest: aquesta concisió té un preu. L'L2 d'ec2.Vpc pren decisions per tu —el repartiment de CIDR, els noms, el nombre de taules de rutes— i per saber exactament què ha creat cal mirar la plantilla sintetitzada. Per això cdk synth no és una ordre de depuració ocasional: és part del flux normal.
Un constructe propi: MercadoFrescoVpc
La reutilització de veritat arriba en encapsular el patró en una classe amb les decisions de MercadoFresco ja preses:
export interface MercadoFrescoVpcProps {
readonly entorno: 'desarrollo' | 'preproduccion' | 'produccion';
readonly cidr?: string;
}
export class MercadoFrescoVpc extends Construct {
public readonly vpc: ec2.Vpc;
public readonly sgTienda: ec2.SecurityGroup;
constructor(scope: Construct, id: string, props: MercadoFrescoVpcProps) {
super(scope, id);
const esProduccion = props.entorno === 'produccion';
this.vpc = new ec2.Vpc(this, 'Vpc', {
ipAddresses: ec2.IpAddresses.cidr(props.cidr ?? '10.0.0.0/16'),
maxAzs: 2,
natGateways: esProduccion ? 2 : 1,
subnetConfiguration: [
{ name: 'publica', subnetType: ec2.SubnetType.PUBLIC, cidrMask: 24 },
{ name: 'app', subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, cidrMask: 24 },
{ name: 'datos', subnetType: ec2.SubnetType.PRIVATE_ISOLATED, cidrMask: 24 },
],
gatewayEndpoints: { S3: { service: ec2.GatewayVpcEndpointAwsService.S3 } },
// Els registres de flux son obligatoris en produccio (05-03) i cars en desenvolupament.
flowLogs: esProduccion ? { registro: { trafficType: ec2.FlowLogTrafficType.ALL } } : {},
});
this.sgTienda = new ec2.SecurityGroup(this, 'SgTienda', {
vpc: this.vpc,
description: 'Instancies de la botiga de MercadoFresco',
allowAllOutbound: true,
});
}
}Fer-lo servir als tres entorns és una línia per entorn, i qualsevol millora del patró arriba als tres alhora: si demà es decideix activar els registres de flux també a preproducció, es canvia en un sol lloc. Això és el que a 09-01 no existia, perquè un Mappings amb tres columnes no és una abstracció: és una taula.
La convenció de MercadoFresco és que els constructes propis exposen el que els consumidors necessiten i res més —aquí, vpc i sgTienda—, per poder canviar l'interior sense trencar res a ningú. És encapsulació aplicada a infraestructura, i és exactament el motiu pel qual compensa un llenguatge de programació.
Les capes d'aplicació i integració en Python
El CDK és multillenguatge de veritat: les biblioteques es generen amb jsii des de la mateixa font en TypeScript, així que l'API és idèntica tret de l'estil dels noms. El Luis prefereix Python per a la capa d'aplicació:
from aws_cdk import Stack, Duration, aws_ec2 as ec2, aws_autoscaling as asg
from aws_cdk import aws_elasticloadbalancingv2 as elb, aws_ssm as ssm
from constructs import Construct
class AplicacionStack(Stack):
def __init__(self, scope: Construct, id: str, *, vpc: ec2.IVpc, entorno: str, **kwargs):
super().__init__(scope, id, **kwargs)
mides = {"desarrollo": ("t3.small", 1, 2),
"preproduccion": ("t3.medium", 2, 3),
"produccion": ("m6i.large", 2, 4)}
tipus, minim, maxim = mides[entorno]
self.alb = elb.ApplicationLoadBalancer(
self, "Alb", vpc=vpc, internet_facing=True,
load_balancer_name=f"alb-mercadofresco-tienda-{entorno}")
grup = asg.AutoScalingGroup(
self, "Tienda", vpc=vpc,
instance_type=ec2.InstanceType(tipus),
machine_image=ec2.MachineImage.from_ssm_parameter(
f"/mercadofresco/{entorno}/ami-tienda"),
min_capacity=minim, max_capacity=maxim,
vpc_subnets=ec2.SubnetSelection(subnet_group_name="app"),
health_check=asg.HealthCheck.elb(grace=Duration.seconds(120)))
# Escalat per peticions: el pic dels divendres son 900 comandes/hora
grup.scale_on_request_count("PorPeticiones", target_requests_per_minute=600)
oient = self.alb.add_listener("Https", port=443, open=True)
oient.add_targets("Tienda", port=8080, targets=[grup],
health_check=elb.HealthCheck(path="/salud",
interval=Duration.seconds(15)))
ssm.StringParameter(self, "DnsAlb",
parameter_name=f"/mercadofresco/{entorno}/alb/dns",
string_value=self.alb.load_balancer_dns_name)Aquí passen coses que en YAML costaven desenes de línies. add_listener i add_targets creen l'oient, el grup de destinació, el registre de l'ASG i les regles del grup de seguretat necessàries perquè l'ALB arribi a les instàncies: el SourceSecurityGroupId de 09-01 el dedueix el CDK del graf d'objectes. I scale_on_request_count crea la política d'escalat amb la seva alarma de CloudWatch associada.
La capa d'integració és igual de compacta, i mostra el patró de 07-05 amb la cua de missatges fallits:
from aws_cdk import aws_sqs as sqs, aws_sns as sns, aws_sns_subscriptions as subs
fallits = sqs.Queue(self, "PedidosFallidos",
queue_name=f"mercadofresco-pedidos-fallidos-{entorno}",
retention_period=Duration.days(14),
encryption=sqs.QueueEncryption.KMS, encryption_master_key=clau)
comandes = sqs.Queue(self, "Pedidos",
queue_name=f"cola-mercadofresco-pedidos-{entorno}",
visibility_timeout=Duration.seconds(180),
encryption=sqs.QueueEncryption.KMS, encryption_master_key=clau,
dead_letter_queue=sqs.DeadLetterQueue(max_receive_count=5, queue=fallits))
tema = sns.Topic(self, "PedidoConfirmado",
topic_name=f"mercadofresco-pedido-confirmado-{entorno}", master_key=clau)
tema.add_subscription(subs.SqsSubscription(comandes, raw_message_delivery=True))add_subscription crea la subscripció i la política de la cua que permet a SNS escriure-hi, amb la condició aws:SourceArn inclosa. És el bloc de vint línies de la solució de l'exercici de 09-01, reduït a una crida i sense possibilitat d'oblidar-se'n.
Actius: el codi de les Lambda dins de la pila
Hi ha una cosa que CloudFormation no sap fer tota sola i que el CDK resol de manera gairebé invisible: pujar el codi. Una plantilla pot declarar una funció Lambda, però el seu codi ha de ser ja en un bucket, amb una clau que algú ha hagut d'omplir a mà o des d'un script. A 09-01 això quedava fora de la plantilla i ho feia el pipeline.
Els actius (assets) del CDK tanquen aquest forat: durant cdk deploy, l'eina empaqueta el directori indicat, en calcula l'empremta, el puja al bucket del bootstrap i substitueix la referència a la plantilla.
from aws_cdk import aws_lambda as lambda_, aws_lambda_event_sources as fonts
miniatures = lambda_.Function(
self, "GenerarMiniaturas",
function_name=f"mercadofresco-generar-miniaturas-{entorno}",
runtime=lambda_.Runtime.PYTHON_3_12,
handler="index.handler",
code=lambda_.Code.from_asset("lambdas/miniaturas"), # el directori s'empaqueta i es puja
memory_size=1024,
timeout=Duration.seconds(30),
environment={"BUCKET_FOTOS": fotos.bucket_name})
fotos.grant_read_write(miniatures) # politica d'IAM + permisos sobre la clau KMS
reservar = lambda_.Function(self, "ReservarStock", ...)
reservar.add_event_source(fonts.SqsEventSource(comandes, batch_size=10))Tres conseqüències pràctiques. La primera: l'empremta del contingut forma part del nom de l'actiu, així que canviar el codi canvia l'actiu i cdk diff ho detecta; si no canvia, el desplegament no toca la funció. La segona: add_event_source crea el mapatge d'origen d'esdeveniments i els permisos de consum sobre la cua, inclosa la clau de KMS. I la tercera, que és un advertiment: els actius s'acumulen al bucket del bootstrap i ningú no els esborra, així que la regla de cicle de vida de l'apartat de neteja no és opcional al cap de sis mesos.
Un matís sobre el repartiment de responsabilitats. Que el CDK pugui desplegar el codi no vol dir que ho hagi de fer en producció: MercadoFresco manté la separació de 08-05 —l'artefacte de la botiga es construeix una vegada i el desplega CodeDeploy— i fa servir actius només per a les Lambda, el codi de les quals és petit i va acoblat a la seva infraestructura. Barrejar el cicle de vida del codi d'aplicació amb el de la infraestructura torna a ajuntar el que el mòdul 8 va separar.
El cicle de vida: synth, diff, deploy, destroy i bootstrap
cdk bootstrap aws://111122223333/eu-west-1 # una vegada per compte i regio
cdk ls # llista les piles de l'App
cdk synth MercadoFrescoRedProduccion # sintetitza i mostra la plantilla
cdk diff MercadoFrescoRedProduccion # compara amb el que hi ha desplegat
cdk deploy MercadoFrescoRedProduccion # desplega (crea un conjunt de canvis)
cdk deploy --all --require-approval any-change
cdk destroy MercadoFrescoRedDesarrollo # esborra la pila| Ordre | Què fa de veritat |
|---|---|
synth |
Executa el teu codi i escriu l'artefacte a cdk.out/. No toca AWS |
diff |
Sintetitza i compara amb la pila desplegada; equival a un conjunt de canvis llegible |
deploy |
Puja l'artefacte al bucket del bootstrap i executa el desplegament a CloudFormation |
destroy |
delete-stack, amb les mateixes regles de DeletionPolicy de 09-01 |
watch |
Redesplega en calent en desar; només per a desenvolupament, mai en producció |
cdk bootstrap es mereix una explicació, perquè és la part que més desconcerta. Crea una pila anomenada CDKToolkit amb: un bucket d'S3 per als artefactes (plantilles grans, codi de Lambda, actius), un repositori d'ECR per a imatges de contenidor, i cinc rols d'IAM —de desplegament, de publicació d'actius, de cerca, d'execució de CloudFormation i d'imatges—. Sense ella, cdk deploy falla amb un missatge explícit.
Hi ha tres coses que cal saber sobre el bootstrap: és per compte i per regió, així que desplegar a us-east-1 per a un certificat de CloudFront exigeix fer-hi el bootstrap també; la versió importa, i una versió antiga provoca fallades críptiques que es resolen tornant-lo a executar; i els rols que crea són potents per omissió, amb AdministratorAccess al rol d'execució de CloudFormation, cosa que en un compte compartit és una decisió que cal prendre conscientment —--cloudformation-execution-policies permet acotar-ho—.
cdk diff és l'equivalent quotidià del conjunt de canvis de 09-01, i la seva sortida marca amb [-], [+] i [~] el que s'esborra, s'afegeix i es modifica, assenyalant a més els canvis que provoquen reemplaçament. La regla de MercadoFresco és la mateixa que allà: cap desplegament sense haver llegit el diff.
Context, entorns explícits i una pila per entorn
Aquí s'ataca de cara el problema que apareix a cada incident: que preproducció no és igual que producció. La solució del CDK és fer que les tres piles surtin del mateix codi, amb un fitxer de configuració per entorn com a única diferència.
// config/entornos.ts — l'unica font de diferencies entre entorns
export interface ConfigEntorno {
readonly nombre: 'desarrollo' | 'preproduccion' | 'produccion';
readonly cuenta: string;
readonly region: string;
readonly cidr: string;
readonly retencionRegistrosDias: number;
readonly protegerRecursos: boolean;
}
export const ENTORNOS: Record<string, ConfigEntorno> = {
desarrollo: { nombre: 'desarrollo', cuenta: '111122223333', region: 'eu-west-1',
cidr: '10.2.0.0/16', retencionRegistrosDias: 7, protegerRecursos: false },
preproduccion: { nombre: 'preproduccion', cuenta: '111122223333', region: 'eu-west-1',
cidr: '10.1.0.0/16', retencionRegistrosDias: 30, protegerRecursos: true },
produccion: { nombre: 'produccion', cuenta: '111122223333', region: 'eu-west-1',
cidr: '10.0.0.0/16', retencionRegistrosDias: 365, protegerRecursos: true },
};// bin/infra-cdk.ts — el punt d'entrada instancia les piles dels tres entorns
const app = new cdk.App();
for (const config of Object.values(ENTORNOS)) {
const sufix = config.nombre.charAt(0).toUpperCase() + config.nombre.slice(1);
const env = { account: config.cuenta, region: config.region }; // ENTORN EXPLICIT
const xarxa = new RedStack(app, `MercadoFrescoRed${sufix}`, { env, config });
new AplicacionStack(app, `MercadoFrescoAplicacion${sufix}`, { env, config, vpc: xarxa.vpc });
new IntegracionStack(app, `MercadoFrescoIntegracion${sufix}`, { env, config });
}Queden nou piles amb noms predictibles: MercadoFrescoRedProduccion, MercadoFrescoAplicacionPreproduccion, i així. I la diferència entre entorns ja no és un misteri arqueològic: és un fitxer de vint línies que es llegeix en deu segons. La primera vegada que es va generar, la Marta va descobrir tres divergències que feia mesos que hi eren: preproducció tenia un sol NAT declarat com si fos producció, la retenció de registres era de 7 dies als tres entorns i el CIDR de desenvolupament se solapava amb el de preproducció.
flowchart TB
App[App CDK<br/>bin/infra-cdk.ts] --> C[config/entornos.ts]
App --> D[Desarrollo]
App --> P[Preproduccion]
App --> R[Produccion]
D --> D1[MercadoFrescoRedDesarrollo]
D --> D2[MercadoFrescoIntegracionDesarrollo]
D --> D3[MercadoFrescoAplicacionDesarrollo]
R --> R1[MercadoFrescoRedProduccion]
R --> R2[MercadoFrescoIntegracionProduccion]
R --> R3[MercadoFrescoAplicacionProduccion]
R1 -->|vpc| R3
Dos conceptes importants d'aquest bloc:
Entorns explícits. Si omets env, la pila és agnòstica d'entorn i no pot fer servir informació real del compte: Vpc.fromLookup, el nombre d'AZ o els certificats existents. El CDK genera llavors plantilles amb dues AZ fictícies i sorpreses en desplegar. Declara sempre env amb compte i regió literals.
Context i cdk.context.json. Les cerques (fromLookup) consulten el compte durant la síntesi i en guarden el resultat a la memòria cau dins de cdk.context.json, que sí que es versiona: garanteix que la síntesi sigui reproduïble i que el pipeline no depengui de consultes en viu. Quan la realitat canvia, es refresca amb cdk context --clear. La regla pràctica: preferir referències explícites a cerques, perquè una cerca és una dependència oculta sobre l'estat del compte.
El pas de valors entre piles és més natural que a 09-01: passar xarxa.vpc a AplicacionStack fa que el CDK creï automàticament l'exportació i la importació de CloudFormation. És còmode i té el mateix parany que allà —una exportació en ús és un forrellat— amb un agreujant: el CDK la crea sense que la vegis. Si dues piles han d'evolucionar per separat, continua sent millor publicar a Parameter Store i llegir amb StringParameter.valueForStringParameter.
Aspectes: etiquetatge obligatori i regles d'auditoria
Un aspecte és un visitant que recorre tot l'arbre de constructes i actua sobre cada node. Serveix per a dues coses que en YAML eren impossibles: aplicar alguna cosa a tots els recursos de cop i auditar regles sobre el que es desplegarà.
L'etiquetatge obligatori de MercadoFresco és trivial amb Tags, que internament és un aspecte:
cdk.Tags.of(app).add('Proyecto', 'mercadofresco');
cdk.Tags.of(xarxa).add('Entorno', config.nombre);
cdk.Tags.of(xarxa).add('Componente', 'red');
cdk.Tags.of(xarxa).add('Propietario', 'marta');
cdk.Tags.of(xarxa).add('CentroCoste', 'plataforma');I una regla d'auditoria pròpia és una classe de vint línies:
import { IAspect, Annotations } from 'aws-cdk-lib';
import { IConstruct } from 'constructs';
/** Prohibeix buckets sense xifrar i grups de seguretat amb el port 22 obert al mon. */
export class ReglasMercadoFresco implements IAspect {
public visit(node: IConstruct): void {
if (node instanceof s3.CfnBucket && !node.bucketEncryption) {
// addError fa que 'cdk synth' FALLI. addWarning nomes avisa.
Annotations.of(node).addError('Tot bucket ha de declarar xifratge (04-02).');
}
if (node instanceof ec2.CfnSecurityGroupIngress &&
node.fromPort === 22 && node.cidrIp === '0.0.0.0/0') {
Annotations.of(node).addError('Prohibit obrir el port 22 al mon.');
}
}
}
cdk.Aspects.of(app).add(new ReglasMercadoFresco());Tres detalls que marquen la diferència entre que això funcioni i que doni falsos negatius. Els aspectes visiten constructes L1, així que cal comprovar CfnBucket i no Bucket: els L2 creen L1 per sota i és allà on hi ha les propietats reals. addError atura la síntesi, cosa que converteix la regla en una porta de qualitat de veritat i no en un avís que ningú no llegeix. I els aspectes s'executen després que l'arbre estigui construït però abans de sintetitzar, per la qual cosa també poden modificar recursos —afegir xifratge en comptes de fallar—, tot i que MercadoFresco prefereix fallar: una infraestructura que es corregeix sola amaga l'error en comptes d'arreglar-lo.
Això és el que a 09-01 feia cfn-guard amb un llenguatge a part. Aquí és el mateix llenguatge, amb el mateix editor, el mateix depurador i les mateixes proves.
Proves d'infraestructura
La piràmide de proves de 08-05 tenia un forat evident: la infraestructura no es provava. Amb el CDK sí, perquè una plantilla sintetitzada és un objecte JSON contra el qual es poden fer assercions.
import { App } from 'aws-cdk-lib';
import { Template, Match } from 'aws-cdk-lib/assertions';
describe('RedStack de produccio', () => {
const app = new App();
const pila = new RedStack(app, 'Prueba', { env: { account: '111122223333',
region: 'eu-west-1' },
config: ENTORNOS.produccion });
const plantilla = Template.fromStack(pila);
test('crea sis subxarxes', () => {
plantilla.resourceCountIs('AWS::EC2::Subnet', 6);
});
test('produccio te dos NAT, un per AZ', () => {
plantilla.resourceCountIs('AWS::EC2::NatGateway', 2);
});
test('les subxarxes de dades no tenen sortida a internet', () => {
// Cap ruta cap a un NAT no pot penjar de la taula de rutes de dades
plantilla.hasResourceProperties('AWS::EC2::Subnet', Match.objectLike({
Tags: Match.arrayWith([Match.objectLike({ Value: Match.stringLikeRegexp('.*datos.*') })]),
}));
});
test('cap grup de seguretat no obre el 22 al mon', () => {
const sgs = plantilla.findResources('AWS::EC2::SecurityGroup');
for (const sg of Object.values(sgs)) {
const regles = sg.Properties?.SecurityGroupIngress ?? [];
expect(regles.filter((r: any) => r.FromPort === 22 && r.CidrIp === '0.0.0.0/0')).toHaveLength(0);
}
});
test('la plantilla no canvia sense voler', () => {
expect(plantilla.toJSON()).toMatchSnapshot(); // prova d'instantania
});
});Hi ha dues famílies de proves i serveixen per a coses diferents:
| Tipus | Què comprova | Avantatge | Risc |
|---|---|---|---|
Assercions (hasResourceProperties, resourceCountIs) |
Invariants concrets que t'importen | Expressen intenció; sobreviuen a refactoritzacions | Només detecten el que has escrit |
Instantànies (toMatchSnapshot) |
Que la plantilla no canviï sense voler | Detecten qualsevol canvi, inclosos els de la biblioteca | S'actualitzen amb -u sense mirar, i deixen de servir |
MercadoFresco fa servir totes dues amb una regla estricta: les assercions cobreixen el que no pot fallar mai —sis subxarxes, dos NAT en producció, sense SSH obert, xifratge a tots els buckets— i les instantànies actuen de xarxa de seguretat. I una norma de revisió: una instantània actualitzada en una PR obliga a explicar el diff a la descripció. Sense aquesta norma, jest -u es converteix en un reflex i la prova deixa de valer res.
El guany real es veu en actualitzar aws-cdk-lib: la instantània mostra exactament què ha canviat a les plantilles generades abans de desplegar res. En una actualització menor de la biblioteca, MercadoFresco va descobrir així un canvi de comportament per omissió en el xifratge d'una cua que ningú no hauria notat.
Recursos amb estat, RemovalPolicy i el perill de cdk destroy
La RemovalPolicy del CDK és la DeletionPolicy de 09-01, amb una diferència perillosa: els constructes L2 trien un valor per omissió, i no sempre el que esperes.
taula = dynamodb.Table(self, "Carritos",
table_name=f"mercadofresco-carritos-{entorno}",
partition_key=dynamodb.Attribute(name="idCarrito", type=dynamodb.AttributeType.STRING),
billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST,
removal_policy=RemovalPolicy.RETAIN if config.proteger else RemovalPolicy.DESTROY,
point_in_time_recovery=True)
cluster = rds.DatabaseCluster(self, "Pedidos",
engine=rds.DatabaseClusterEngine.aurora_postgres(
version=rds.AuroraPostgresEngineVersion.VER_15_4),
removal_policy=RemovalPolicy.SNAPSHOT, # instantania final abans d'esborrar
deletion_protection=True, # i a mes, proteccio d'esborrat a RDS
storage_encrypted=True)| Recurs | RemovalPolicy per omissió a l'L2 |
Què significa |
|---|---|---|
s3.Bucket |
RETAIN |
El bucket sobreviu a cdk destroy |
dynamodb.Table |
RETAIN |
La taula sobreviu |
rds.DatabaseCluster |
SNAPSHOT |
Instantània final i esborrat |
logs.LogGroup |
RETAIN |
Els registres sobreviuen |
sqs.Queue, sns.Topic, ec2.Vpc |
DESTROY |
S'esborren sense més |
I aquí hi ha el perill real, que a MercadoFresco és concret perquè els tres entorns comparteixen el compte 111122223333: cdk destroy --all no pregunta a quin entorn pertany cada pila. Un --all llançat des del directori equivocat, o un cdk destroy MercadoFrescoRedProduccion amb un autocompletat desafortunat, toca producció. Tres mesures, en ordre d'eficàcia:
deletion_protectionitermination_protectiona les piles de producció, que fan que l'esborrat falli al servei, no a la bona voluntat de qui escriu l'ordre.cdk.StackacceptaterminationProtection: true.- Mai
--allen un terminal humà. Els desplegaments de producció surten del pipeline; en local s'anomena la pila sencera. - Comptes separats, que és la solució de veritat i arriba a 09-04. Mentrestant, les dues anteriors són pedaços conscients.
Un últim matís: RemovalPolicy.RETAIN deixa el recurs orfe, i el següent cdk deploy intentarà crear-ne un altre amb el mateix nom físic i fallarà. És la contrapartida de la seguretat, i la raó per la qual els noms físics fixos —table_name, bucket_name— tenen cost, igual que a 09-01.
CDK Pipelines: el pipeline que es desplega a si mateix
Queda l'última peça de l'asimetria del mòdul 8: el pipeline es va crear a mà. pipelines.CodePipeline ho resol amb una propietat anomenada autoactualització: el pipeline es defineix al mateix repositori que la infraestructura i, a cada execució, la seva primera etapa s'actualitza a si mateixa abans de desplegar res.
const pipeline = new pipelines.CodePipeline(this, 'Pipeline', {
pipelineName: 'pipeline-mercadofresco-infra',
synth: new pipelines.ShellStep('Synth', {
input: pipelines.CodePipelineSource.connection('mercadofresco/mercadofresco-infra', 'main', {
connectionArn: 'arn:aws:codeconnections:eu-west-1:111122223333:connection/conn-mercadofresco-github',
}),
commands: ['npm ci', 'npm run build', 'npm test', 'npx cdk synth'],
}),
});
// Una etapa per entorn. L'aprovacio manual nomes abans de produccio.
pipeline.addStage(new EtapaMercadoFresco(this, 'Desarrollo', { config: ENTORNOS.desarrollo }));
pipeline.addStage(new EtapaMercadoFresco(this, 'Preproduccion', { config: ENTORNOS.preproduccion }));
pipeline.addStage(new EtapaMercadoFresco(this, 'Produccion', { config: ENTORNOS.produccion }), {
pre: [new pipelines.ManualApprovalStep('AprobacionMarta')],
post: [new pipelines.ShellStep('Humo', { commands: ['./pruebas/humo.sh produccion'] })],
});Un Stage és un grup de piles que es despleguen juntes —xarxa, dades, integració, aplicació i observabilitat d'un entorn— i el CDK dedueix l'ordre de desplegament del graf de dependències entre elles. npm test executa les proves de la secció anterior, així que una plantilla que violi una regla no arriba a desplegar-se.
Amb això, MercadoFresco tanca el cercle del mòdul 8: el pipeline-mercadofresco-tienda desplega l'aplicació i el pipeline-mercadofresco-infra desplega la infraestructura i a si mateix. Afegir una etapa nova és una PR, no un clic a la consola.
Dos advertiments. El primer: el pipeline autoactualitzable és potent i perillós —qui pugui fusionar a main pot canviar el mateix pipeline—, així que la protecció de branca de 08-01 i la revisió obligatòria deixen de ser bones pràctiques i passen a ser un control de seguretat. El segon: l'etapa de síntesi necessita els permisos de cerca, així que cdk.context.json versionat no és opcional si vols síntesis reproduïbles.
Terraform, CDK for Terraform i la comparació honesta
Terraform, de HashiCorp, és l'alternativa multiproveïdor més estesa: llenguatge declaratiu propi (HCL), estat en un fitxer que cal emmagatzemar i bloquejar, i proveïdors per a AWS, Azure, GCP, Datadog o GitHub. CDK for Terraform (CDKTF) permet escriure configuració de Terraform amb TypeScript o Python, igual que el CDK amb CloudFormation.
| Criteri | CloudFormation | AWS CDK | Terraform |
|---|---|---|---|
| Llenguatge | YAML/JSON | TypeScript, Python, Java, C#, Go | HCL (o TS/Python amb CDKTF) |
| Estat | Gestionat per AWS | Gestionat per AWS | Fitxer propi: cal allotjar-lo i bloquejar-lo |
| Multiproveïdor | No | No | Sí, és la seva raó de ser |
| Vista prèvia | Conjunt de canvis | cdk diff |
terraform plan, més detallat |
| Recursos nous d'AWS | Amb retard ocasional | El de l'L1, més l'L2 després | Ràpid, de vegades abans |
| Reversió automàtica | Sí, la pila reverteix sola | Sí, és CloudFormation | No: es queda a mitges i es corregeix |
| Deriva | Detecció nativa | La de CloudFormation | plan la mostra a cada execució |
| Cost operatiu | Cap | Cap | Gestionar l'estat (o pagar HCP Terraform) |
| Comunitat de mòduls | Escassa | Creixent (Construct Hub) | Enorme: el registre de mòduls |
La recomanació és senzilla i no depèn de gustos. Si tot és a AWS, CloudFormation o CDK, perquè l'estat gestionat i la reversió automàtica són avantatges reals que es paguen cars amb Terraform. Si hi ha diversos proveïdors —AWS més Cloudflare, més GitHub, més Datadog— Terraform guanya, i el cost de mantenir l'estat es justifica. MercadoFresco és sencer a AWS i ja té el pipeline muntat sobre serveis natius: canviar seria feina sense retorn.
Cost i neteja
El CDK és gratuït; el bootstrap deixa un bucket, un repositori d'ECR i cinc rols el cost dels quals és menyspreable mentre estiguin buits. Convé, això sí, posar una regla de cicle de vida al bucket d'actius, que creix a cada desplegament i no es neteja sol.
cdk destroy MercadoFrescoAplicacionDesarrollo MercadoFrescoRedDesarrollo # en aquest ordre
cdk ls # comprovar que ja no hi son
aws ec2 describe-addresses --query 'Addresses[?AssociationId==null]' --output tableL'ordre importa per la mateixa raó que a 09-01: primer els consumidors, després la xarxa. Si una pila no es deixa esborrar, la causa sol ser una exportació en ús o un bucket amb objectes, i es diagnostica als esdeveniments de CloudFormation, no al CDK.
Errors Habituals i Consells
Error: canviar l'id d'un constructe desplegat. Reanomenar 'Vpc' per 'VpcPrincipal' recrea el recurs, igual que a 09-01. Consell: els id són immutables a la pràctica; si de debò cal reorganitzar, cdk diff avisa —llegeix-lo—, i overrideLogicalId permet conservar l'identificador anterior.
Error: desplegar sense cdk diff. El CDK fa tan fàcil desplegar que la gent es salta la vista prèvia. Consell: cdk diff a cada PR, publicat com a comentari, i --require-approval any-change en producció.
Error: piles sense env explícit. Produeixen plantilles amb dues AZ fictícies i fromLookup no funciona. Consell: declara env amb compte i regió literals a totes les piles.
Error: cdk destroy --all al compte compartit. És el risc concret de MercadoFresco fins a 09-04. Consell: terminationProtection: true a les piles de producció, anomenar sempre la pila, i mai --all fora d'un entorn de proves.
Error: actualitzar instantànies amb -u sense mirar. Converteix la prova en decoració. Consell: exigir en la revisió que tota instantània actualitzada vingui amb l'explicació del diff.
Error: creure que l'L2 fa el que tu faries. ec2.Vpc pren dotzenes de decisions per tu. Consell: cdk synth i llegir la plantilla la primera vegada que facis servir un constructe nou; és l'única manera de saber què has demanat de veritat.
Consell: fixa la versió d'aws-cdk-lib i actualitza-la a propòsit. Amb les proves d'instantània, cada actualització t'ensenya exactament què canvia a les plantilles abans de tocar res.
Consell: fes servir els mètodes grant* en comptes d'escriure polítiques. Generen el mínim privilegi real, inclosos els permisos sobre KMS que gairebé tothom oblida.
Consell: no fiquis lògica de negoci al codi d'infraestructura. El CDK convida a fer-ho per ser un llenguatge complet. Una pila que llegeix una base de dades durant la síntesi és una pila que no es pot sintetitzar al pipeline.
Exercicis
Exercici 1: el constructe de cua amb DLQ
Escriu un constructe propi ColaMercadoFresco (TypeScript o Python) que encapsuli el patró de 07-05: una cua principal amb la seva cua de missatges fallits, xifratge amb alias/mercadofresco-datos, maxReceiveCount configurable amb valor per omissió de 5, retenció de 14 dies a la DLQ, i una alarma de CloudWatch que salti quan la DLQ tingui missatges. Ha d'exposar la cua principal, la DLQ i un mètode concederConsumo(rol). Fes-lo servir per crear les quatre cues de MercadoFresco. Indica quina RemovalPolicy poses a cada cua i per què.
Exercici 2: les proves que falten
Escriu quatre proves per a RedStack i AplicacionStack que verifiquin invariants que a MercadoFresco li importen de debò: (a) que en producció hi ha dos NAT i en desenvolupament un; (b) que cap subxarxa de dades no té ruta cap a un NAT; (c) que l'ALB només accepta HTTPS; (d) que tots els recursos etiquetables porten les cinc etiquetes obligatòries. Per a cadascuna, digues si la resoldries amb asserció o amb instantània i per què, i què caldria a més de la prova perquè la regla es compleixi sempre.
Exercici 3: migrar sense recrear
MercadoFresco ja té la pila mercadofresco-red-produccion desplegada amb la plantilla YAML de 09-01, amb la VPC i les sis subxarxes en producció. La Marta vol passar aquesta pila a CDK sense recrear res i sense tallar el servei. Descriu l'estratègia completa: quines opcions hi ha, quina tries i per què, quines ordres executes, com comproves que la migració no canviarà res, i què fas si cdk diff mostra diferències. Explica també quin risc concret té l'opció que descartes.
Solucions
Solució 1
export interface ColaMercadoFrescoProps {
readonly nombre: string;
readonly entorno: string;
readonly clave: kms.IKey;
readonly maxIntentos?: number;
readonly tiempoVisibilidad?: cdk.Duration;
readonly temaAlertas: sns.ITopic;
}
export class ColaMercadoFresco extends Construct {
public readonly cola: sqs.Queue;
public readonly fallidos: sqs.Queue;
constructor(scope: Construct, id: string, props: ColaMercadoFrescoProps) {
super(scope, id);
this.fallidos = new sqs.Queue(this, 'Fallidos', {
queueName: `mercadofresco-${props.nombre}-fallidos-${props.entorno}`,
retentionPeriod: cdk.Duration.days(14),
encryption: sqs.QueueEncryption.KMS,
encryptionMasterKey: props.clave,
removalPolicy: cdk.RemovalPolicy.RETAIN, // guarda missatges sense processar: no s'esborra mai
});
this.cola = new sqs.Queue(this, 'Principal', {
queueName: `cola-mercadofresco-${props.nombre}-${props.entorno}`,
visibilityTimeout: props.tiempoVisibilidad ?? cdk.Duration.seconds(180),
encryption: sqs.QueueEncryption.KMS,
encryptionMasterKey: props.clave,
deadLetterQueue: { queue: this.fallidos, maxReceiveCount: props.maxIntentos ?? 5 },
removalPolicy: props.entorno === 'produccion'
? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY,
});
// Qualsevol missatge a la DLQ es un incident: llindar 0, una avaluacio.
this.fallidos.metricApproximateNumberOfMessagesVisible()
.createAlarm(this, 'AlarmaDlq', {
alarmName: `mercadofresco-${props.nombre}-fallidos`,
threshold: 0, evaluationPeriods: 1,
comparisonOperator: cw.ComparisonOperator.GREATER_THAN_THRESHOLD,
treatMissingData: cw.TreatMissingData.NOT_BREACHING,
})
.addAlarmAction(new actions.SnsAction(props.temaAlertas));
}
public concederConsumo(rol: iam.IGrantable): void {
this.cola.grantConsumeMessages(rol); // inclou els permisos sobre la clau KMS
}
}for (const nombre of ['pedidos', 'correo', 'almacen', 'analitica']) {
new ColaMercadoFresco(this, `Cola${nombre}`, { nombre, entorno: config.nombre,
clave, temaAlertas: alertas });
}Les polítiques d'esborrat. La DLQ sempre RETAIN, a tots els entorns: conté missatges que no es van processar i que algú haurà d'examinar; esborrar-la en destruir una pila destrueix l'evidència d'un incident. La cua principal, RETAIN en producció i DESTROY a la resta: en producció hi pot haver missatges en vol la pèrdua dels quals seria pèrdua de comandes, mentre que en desenvolupament un residu que impedeix recrear la pila costa més del que protegeix.
I un detall que es nota en fer-lo servir quatre vegades: el bucle de quatre línies substitueix cent vint línies de YAML amb dotze recursos, i garanteix que les quatre cues tinguin exactament la mateixa configuració —xifratge, reintents, alarma—, que és justament el que avui no es compleix a MercadoFresco.
Solució 2
test("(a) el nombre de NAT depen de l'entorn", () => {
const prod = Template.fromStack(new RedStack(new App(), 'P', { env, config: ENTORNOS.produccion }));
const dev = Template.fromStack(new RedStack(new App(), 'D', { env, config: ENTORNOS.desarrollo }));
prod.resourceCountIs('AWS::EC2::NatGateway', 2);
dev.resourceCountIs('AWS::EC2::NatGateway', 1);
});
test("(c) l'ALB nomes accepta HTTPS", () => {
plantillaApp.hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener',
Match.objectLike({ Port: 443, Protocol: 'HTTPS' }));
const oients = plantillaApp.findResources('AWS::ElasticLoadBalancingV2::Listener');
expect(Object.values(oients).filter((o: any) => o.Properties.Port === 80)).toHaveLength(0);
});(a) Asserció. És un invariant de negoci amb dos valors concrets, i la prova ho expressa millor que qualsevol instantània. A més sobreviu a refactoritzacions: si demà es reorganitzen els constructes, la prova continua valent.
(b) Asserció, però indirecta. Comprovar «cap subxarxa de dades no té ruta cap a un NAT» requereix seguir la relació entre RouteTable, Route i SubnetRouteTableAssociation a la plantilla, cosa que és tediosa. El que és pragmàtic és afirmar que no existeix cap AWS::EC2::Route amb NatGatewayId la taula de la qual estigui associada a una subxarxa datos, i recolzar-se en el fet que el CDK garanteix la propietat per construcció en fer servir PRIVATE_ISOLATED. La prova val com a xarxa de seguretat davant d'un canvi de tipus de subxarxa.
(c) Asserció, amb la part negativa inclosa. Comprovar que existeix un oient 443 no n'hi ha prou: el que importa és que no existeixi un al 80 sense redirecció. Les proves que només verifiquen presència deixen passar precisament els errors per addició, que són els que passen.
(d) Ni asserció ni instantània: un aspecte. Una prova que recorri tots els recursos comprovant etiquetes és fràgil, perquè molts tipus no les admeten i caldria mantenir la llista d'excepcions. El correcte és un aspecte que faci fallar la síntesi si falta una etiqueta obligatòria, més una prova que l'aspecte està registrat a l'App. Es prova el mecanisme, no cada instància.
Què cal a més de les proves. Res d'això no serveix si les proves no bloquegen: han d'executar-se a npm test dins de l'etapa de síntesi de CDK Pipelines, i la construcció ha de fallar, no avisar. És la mateixa conclusió de 08-02, aplicada a la infraestructura: una comprovació que no bloqueja és documentació.
Solució 3
Hi ha dues opcions i no són equivalents.
La primera és recrear la xarxa en CDK i migrar: desplegar una VPC nova al costat de la vella, moure les instàncies, la base de dades i l'ALB, i esborrar l'antiga. És neta conceptualment i és la que es descarta, perquè implica moure Aurora i l'ALB entre VPC: canvi d'endpoints, finestra de tall, risc sobre les dades de clients i, sobretot, un benefici nul —la xarxa resultant és idèntica—. Tot el risc, cap guany.
La segona, la triada, és fer que el CDK adopti la pila existent. La clau és que una pila de CloudFormation no sap si la seva plantilla l'ha escrita una persona o el CDK: només compara identificadors lògics. Si la plantilla sintetitzada és equivalent, l'actualització no toca cap recurs.
El procediment. S'escriu la pila CDK amb el mateix nom de pila, mercadofresco-red-produccion, i es forcen els identificadors lògics perquè coincideixin amb els de la plantilla YAML, fent servir overrideLogicalId recurs a recurs:
const cfnVpc = this.vpc.node.defaultChild as ec2.CfnVPC;
cfnVpc.overrideLogicalId('Vpc'); // el nom logic exacte de red-mercadofresco.yamlLa comprovació, que és el cor de l'exercici:
cdk synth mercadofresco-red-produccion > /tmp/cdk.yaml
aws cloudformation get-template --stack-name mercadofresco-red-produccion \
--template-stage Processed --query TemplateBody > /tmp/actual.json
cdk diff mercadofresco-red-produccion # el veredicte: ha de sortir buitUn cdk diff buit és la condició d'acceptació. Mentre mostri alguna cosa, no es desplega.
Si mostra diferències, hi ha tres casos. Si són només metadades del CDK (CDKMetadata, la versió de l'analitzador) s'accepten: no afecten cap recurs. Si són propietats cosmètiques —una descripció, una etiqueta— es decideix cas per cas i es documenta. I si hi ha algun [~] amb reemplaçament o qualsevol [-], s'atura: significa que la plantilla generada no és equivalent i cal ajustar el codi fins que ho sigui. Un [-] sobre una subxarxa en producció és exactament el desastre que aquesta migració existeix per evitar.
La xarxa de seguretat, en dos passos. Abans de res, --enable-termination-protection a la pila i DeletionPolicy: Retain als recursos crítics, perquè ni tan sols un error greu destrueixi res. I l'assaig complet primer en desenvolupament, després en preproducció i només llavors en producció, que és el mateix ordre de promoció de 08-04 aplicat a la infraestructura.
Conclusió
La mateixa infraestructura, en un llenguatge de programació. Les 190 línies de YAML de la xarxa s'han convertit en 28 línies de TypeScript que sintetitzen una plantilla equivalent, i les sis subxarxes gairebé idèntiques han desaparegut dins d'un subnetConfiguration de tres entrades que el CDK multiplica per AZ. El que no ha canviat és el que hi ha a sota: el CDK no substitueix CloudFormation, escriu les plantilles per tu, i els conjunts de canvis, la deriva, ROLLBACK_COMPLETE i les polítiques d'eliminació de 09-01 continuen governant el que passa de veritat.
Tens els tres nivells de constructes —l'L1 que calca CloudFormation, l'L2 amb valors per defecte segurs i mètodes grant* que generen el mínim privilegi, i l'L3 còmode i opac— amb la vàlvula d'escapament de node.defaultChild per baixar de nivell quan l'API es queda curta. Tens el constructe propi MercadoFrescoVpc, que és la diferència entre una taula de tres columnes i una abstracció de veritat: una millora del patró arriba als tres entorns alhora. I tens el cicle de vida complet, amb cdk bootstrap explicat —el bucket, el repositori d'ECR i els cinc rols, per compte i regió, amb permisos d'administració que cal acotar conscientment— i amb la regla que s'hereta de 09-01: cap desplegament sense haver llegit el diff.
Els tres entorns surten ara del mateix codi, i la diferència entre preproducció i producció ha deixat de ser arqueologia: és config/entornos.ts, vint línies llegibles en deu segons, que en escriure's per primera vegada va destapar tres divergències que feia mesos que hi eren. Amb entorns explícits a totes les piles —sense env no hi ha cerques i sí AZ fictícies— i cdk.context.json versionat perquè la síntesi sigui reproduïble al pipeline.
I la infraestructura entra per fi a la piràmide de proves de 08-05. Els aspectes apliquen l'etiquetatge obligatori de cop i auditen regles —buckets sense xifrar, el port 22 obert— amb addError, que atura la síntesi en comptes d'avisar; visitant sempre constructes L1, que és on hi ha les propietats reals. Les assercions cobreixen el que no pot fallar mai i les instantànies actuen de xarxa de seguretat, amb la norma de revisió que les manté vives: una instantània actualitzada obliga a explicar el diff. Més CDK Pipelines, que tanca l'asimetria del mòdul 8 desplegant la infraestructura i actualitzant-se a si mateix —i que converteix la protecció de branca de 08-01 en un control de seguretat, perquè qui fusiona a main canvia el pipeline—.
Queda un risc assenyalat tres vegades i no resolt: cdk destroy apuntant a producció. La protecció de terminació i la disciplina d'anomenar sempre la pila són pedaços conscients, no solucions, perquè el problema de fons és que els tres entorns comparteixen el compte 111122223333. Mentre això continuï sent cert, una ordre mal escrita, una variable d'entorn oblidada o un --all al directori equivocat poden arribar a la botiga en producció. I el radi d'explosió no és l'única cosa: les quotes es comparteixen, un desplegament de desenvolupament pot esgotar el límit d'adreces IP elàstiques de producció, la factura no se separa de veritat i cap política d'IAM no aïlla del tot algú que ja té permisos amplis al compte.
A 09-03, «AWS Elastic Beanstalk», fem un pas enrere per mirar l'alternativa gestionada: què fa per tu una plataforma com a servei, quan hauria estat la decisió correcta per a MercadoFresco —i per què avui ja no ho és—. Després, 09-04, «AWS Organizations», ataca el problema de fons: separar de veritat els entorns en comptes diferents, amb polítiques de control de serveis, facturació consolidada i una línia base que es desplega sola a cada compte nou.
Curs d'AWS
Mòdul 1: Introducció a AWS
- Què és AWS?
- Configuració del teu compte d'AWS
- Infraestructura global d'AWS
- Consola d'administració d'AWS
- AWS CLI i SDK
Mòdul 2: Serveis principals d'AWS
Mòdul 3: Xarxes i lliurament de contingut
- Amazon VPC
- Grups de seguretat i llistes de control d'accés
- Elastic Load Balancing
- Amazon CloudFront
- Route 53
Mòdul 4: Seguretat i identitat
- AWS Identity and Access Management (IAM)
- AWS Key Management Service (KMS)
- Secrets Manager i Parameter Store
- AWS Shield
- AWS WAF
Mòdul 5: Monitoratge i gestió
- Amazon CloudWatch
- AWS X-Ray i traçabilitat distribuïda
- AWS CloudTrail
- AWS Config
- AWS Trusted Advisor
Mòdul 6: Bases de dades
- Com triar la base de dades adequada
- Amazon DynamoDB
- Amazon Aurora
- Amazon Redshift
- Amazon ElastiCache
Mòdul 7: Integració d'aplicacions
- Amazon SQS
- Amazon SNS
- Amazon EventBridge
- AWS Step Functions
- Patrons d'integració: idempotència, reintents i cues de missatges fallits
