éèITæ¬éšïŒå
ŒXIæ¬éšïŒã®äžéã§ãã çŸåšã¯äž»ã«ã¢ããªã±ãŒã·ã§ã³ãšã³ãžãã¢ã®æèã«ãããã¢ãŒããã¯ããšããŠæ¥ã
ã¢ãŒããã¯ãã£èšèšïŒã³ãŒãã£ã³ã°ã«å€ããã§ãããŸãã ä»åã¯ãClaude Codeã§IaCã³ãŒããæžããéã®åå¿ãæ®ããŠãããããããã°ã«èµ·ãããã®ã§ãã èªã¿é²ããããã§ãæåã«ç§ã®ã€ã³ãã©é¢é£ã®ã¹ãã«ã»ãããèšèŒããŠãããŸãã AWSã§é »åºã®ãµãŒãã¹ãšåœ¹å²ãããã¯çè§£ããŠãããè³æ Œã¯äžåããªãã Dockerã«ã€ããŠã¯çè§£ããŠãããk8sã¬ãã«ã«ãªããšæªããããªããšãªããããã€ã¯ã§ããã ãã¹ãäºååããŠäºåèœã¡ããããã®äœã¬ã€ã€å匷éã ã³ã³ãœãŒã«ããããã§ãããã€ãã§ããã IaCã¯åããŠæžããTypeScriptã¯æžãããã¢ããªéçºã§Claude Codeã䜿ã£ãŠããã ã¢ãããŒã·ã§ã³ æ€èšŒ ç°å¢ (1)äœãã¬ãŒãã¬ãŒã«ãåŒããªãã·ã³ãã«ãªããã³ããã§å®è¡ (2)課é¡ãæœåº (3)ã¬ãŒãã¬ãŒã«ãšããŠèšèšãšããã³ããããã¥ãŒãã³ã°ãã©ã®çšåºŠè§£æ¶ãããããç¢ºèª åºåãããã³ãŒã (4)æ§æã®æ¯èŒïŒAs-Is â To-BeïŒ ç·è© æåã«åããã³ãŒãã¯ã©ã®ã¬ãã«ãïŒ ã¬ãŒãã¬ãŒã«åŸã«åºãŠããã³ãŒãã¯ã©ã®ã¬ãã«ãïŒ ã©ã®ã¬ãã«ã§ããã°äœ¿ãããªãããïŒ å·¥å€«ãããšãããïŒ æåŸã« ã¢ãããŒã·ã§ã³ äžäººã®ã¢ããªã±ãŒã·ã§ã³ãšã³ãžãã¢ãšããŠã2025幎11æã«Opus 4.5ãåºããšãã¯è¡æã§ããã ãããŸã§ã¯Claude Codeçã«ããTUIã«ããæäœãã§ããããã«ãªã£ãããšã§ãã³ãŒãã£ã³ã°é床ã¯çéã«ãªã£ãããããã¯ã·ã§ã³ã§äœ¿ãã«ã¯ããäžæ©ãšããæè§ŠããããŸããããããOpus 4.5ã®ç»å Žã«ããããã®ã¢ãŠãããããèšç»èœåã¯é£èºãããšæããŠããŸããå®éã«ã¢ããªã±ãŒã·ã§ã³ã®æèã§ã¯ãããã¯ã·ã§ã³ã¬ãã«ã®ã³ãŒãã£ã³ã°ã«Claude Codeãæ¡çšãã倧å¹
ãªéçºå¹çã®åäžãå®çŸããŠããŸãã ããŠãããªã£ãŠãããšããããŸã§ã€ã³ãã©çµéšãã»ãŒ0ã®ç§ããã©ã®çšåºŠIaCã³ãŒãã«ããã€ã³ãã©æ§ç¯ãå¯èœã«ãªãã®ãæ°ã«ãªã£ãŠããŸããã Claude Codeãç»å Žãã5æé ã«ã¯ãIaCã«ãããŠã¯ä»¥äžã®ãããªèª²é¡ããããŸããããããã¯ãã¹ãŠIaCã¯ãç¶æ
æ©æ¢°ãã«éããªãããšããçãã課é¡ã§ããããã®çµæåºãŠããã³ãŒãããã®ãŸãŸåããªãããšãé »çºããŠããŸãã (1)ãã£ãŒãããã¯ã«ãŒããæ¥µç«¯ã«é
ãã ã¢ããªã®å Žåã¯Unit Testâãã°åºåã®å³æãã£ãŒãããã¯ã®æµãã§ãããIaCã§ã¯synth, deployã®æµãããšãããŠæ°åïœæ°10åãããããšã©ãŒè¡šçŸãææ§ãªããšããã (2)ãã°ã«åºãªãæé»ç¥ãå€ãããã AZå¶çŽâClaudeãçæããã³ãŒãã§AZãç¡æå®ãAWSãã©ã³ãã å²åœãæ¥ç¶äžå ã«ãŒãã£ã³ã°å°çâprivate subnetããå€ã«åºãããªããNAT Gatewayããªã IAMã®è©äŸ¡é ãã©ãã«âAllowãããDenyãåªå
ããããDenyã®å®çŸ©ãèŠéã (3)粟床ãšããŠã®åé¡ã ãµãŒãã¹ïŒStackïŒå®çŸ©ã®äŸåé¢ä¿ããã©ã¡ãŒã¿ã®çµã¿åãããè€éåãããããããæ£ããã³ãŒããçæã§ããªã (4)ææ°ãã©ã¡ãŒã¿ãžã®è¿œåŸ ææ°ã®ã¯ã©ãŠãæ©èœã«è¿œåŸããããšãããšãã¢ãã«åŽãæ°ãµãŒãã¹ã»ãã©ã¡ãŒã¿ã«è¿œåŸã§ããŠããªãã±ãŒã¹ãå€ãã Opus 4.5ã§ã¯ãç¥èã®ã«ãããªãæ¥ã䌞ã³ãããšã§ææ°ã®ç¥èŠãå¢ãããã€ã³ã³ããã¹ããŠã£ã³ããŠã200kããŒã¯ã³ãšãªããåäœã®StackãèŠãŠã€ã³ãã©æ§æãèŠãã®ã§ã¯ãªãå
šäœã®ç¶æ
ãèŠãªãã差忀ç¥ã§ããããã«ãªã£ãŠããŸããããã«ãããå°ãªããã°ãããé«ã粟床ãæåŸ
ã§ããããã«ãª\ ããŸãã\ ã£ãã¯ãã§ãã (åè)Claudeã®ãã¬ãŒãã³ã°ããŒã¿ã¯ã©ã®çšåºŠææ°ã§ããïŒ æ€èšŒ ããŠãããã€ã課é¡ãæžããŸãããã(4)ã«ã€ããŠã¯è§Šãããæ¢åã®åŠç¿ã¢ãã«ã§ã©ããŸã§(1)~(3)ã®èª²é¡ã«å¯Ÿå¿ã§ããããæ€èšŒããŸãã ãŸãã¯ã æäœéã®æç€ºã§ã©ã®çšåºŠãã£ãŠãããã®ã ããããã©ã®çšåºŠã¬ãŒãã¬ãŒã«ãåŒãããšã§äœ¿ãç©ã«ãªãã®ãïŒãªããªãã®ã ã¬ãŒãã¬ãŒã«ãåŒãããã«ã©ã®çšåºŠã®ã¹ãã«ãå¿
èŠãªã®ã ãæ€èšŒããŸããããã«ããããä»ã®ã¢ãã«ã§ããã°ãã©ã®çšåºŠã€ã³ãã©ç¥èãã€ããŠããã°ïŒã©ã®çšåºŠèšèšèœåãããã°ïŒå®åã§æŽ»çšã§ããã®ãããšããææšãæããã«ããŠãããããšèããŠããŸããæ¹éãšããŠã¯ã (1)äœãã¬ãŒãã¬ãŒã«ãåŒããªãã·ã³ãã«ãªããã³ããã§å®è¡ (2)課é¡ãæœåº (3)ã¬ãŒãã¬ãŒã«ãšããŠèšèšãšããã³ããããã¥ãŒãã³ã°ãã©ã®çšåºŠè§£æ¶ãããããç¢ºèª (4)æ§æã®æ¯èŒïŒAs-Is â To-BeïŒ ä»åã¯ãAWSäžã«æ§ç¯ãECSãDBã¯Auroraã®è¶
ç°¡åãªæ§æã§æ€èšŒããŸãã ç°å¢ æ€èšŒèªäœã¯2026幎1æã§ããã以äžã®ããŒã«ã»ã¢ãã«ãå©çšããŸãã Claude Code v2.1.21 ã¢ãã« Opus 4.5 (1)äœãã¬ãŒãã¬ãŒã«ãåŒããªãã·ã³ãã«ãªããã³ããã§å®è¡ æåã®ããã³ããã¯è¶
ç°¡åã«ä»¥äžã§ãã ECSãAuroraã®æ§æãCDKã§äœã£ãŠãã ããã ããã€ãã®è³ªåãè¿ã£ãŠããŸãã CDKã®ããã°ã©ãã³ã°èšèªã¯äœã䜿ããŸããïŒ â TypeScript (Recommended) ECSã®ã³ã³ãã¥ãŒãã£ã³ã°ã¿ã€ãã¯ã©ã¡ãã§ããïŒ â Fargate (Recommended) Auroraã®ãšã³ãžã³ã¯ã©ã¡ãã䜿ããŸããïŒ â PostgreSQL (Recommended) Auroraã®ãããã€æ¹åŒã¯ã©ã¡ãã§ããïŒ â Serverless v2 (Recommended) åºãŠããã³ãŒããèŠãŠã¿ãŸãããã import * as cdk from 'aws-cdk-lib'; import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as ecs from 'aws-cdk-lib/aws-ecs'; import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; import * as rds from 'aws-cdk-lib/aws-rds'; import { Construct } from 'constructs'; export class CdkTestStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // VPC const vpc = new ec2.Vpc(this, 'Vpc', { maxAzs: 2, natGateways: 1, }); // Aurora PostgreSQL Serverless v2 const dbCluster = new rds.DatabaseCluster(this, 'AuroraCluster', { engine: rds.DatabaseClusterEngine.auroraPostgres({ version: rds.AuroraPostgresEngineVersion.VER_16_4, }), serverlessV2MinCapacity: 0.5, serverlessV2MaxCapacity: 4, writer: rds.ClusterInstance.serverlessV2('Writer'), vpc, vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, defaultDatabaseName: 'appdb', removalPolicy: cdk.RemovalPolicy.DESTROY, }); // ECS Cluster const cluster = new ecs.Cluster(this, 'EcsCluster', { vpc, }); // ECS Fargate Service with ALB const fargateService = new ecs_patterns.ApplicationLoadBalancedFargateService( this, 'FargateService', { cluster, taskSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, cpu: 256, memoryLimitMiB: 512, desiredCount: 1, taskImageOptions: { image: ecs.ContainerImage.fromRegistry('nginx:latest'), containerPort: 80, environment: { DB_HOST: dbCluster.clusterEndpoint.hostname, DB_PORT: dbCluster.clusterEndpoint.port.toString(), DB_NAME: 'appdb', }, secrets: { DB_SECRET: ecs.Secret.fromSecretsManager(dbCluster.secret!), }, }, publicLoadBalancer: true, }, ); // Allow ECS tasks to connect to Aurora dbCluster.connections.allowDefaultPortFrom(fargateService.service); // Outputs new cdk.CfnOutput(this, 'AlbDnsName', { value: fargateService.loadBalancer.loadBalancerDnsName, description: 'ALB DNS Name', }); new cdk.CfnOutput(this, 'DbClusterEndpoint', { value: dbCluster.clusterEndpoint.hostname, description: 'Aurora Cluster Endpoint', }); } } äœæããããªãœãŒã¹ã¯ä»¥äžã®éãã§ãã ãªãœãŒã¹ 詳现 VPC 2 AZ, Public/Private ãµãããããNAT GatewayÃ1 Aurora PostgreSQL Serverless v2, Privateã«é
眮 ECS Fargate ALBä»ã Security Group ECSâAuroraïŒPostgreSQL 5432ã®ã¿èš±å¯ïŒ Secrets Manager AuroraèªèšŒæ
å ±ãèªåçæãECSã¿ã¹ã¯ã«DB_SECRETãšããŠä»äž (2)課é¡ãæœåº äžèšã³ãŒãã«ãã課é¡ã¯ä»¥äžã§ãã ãã¡ãã¯èªåã§æŽãã ãããã®ãå«ããæçµçã«ChatGPT5.2ïŒCodexïŒã«ã¬ãã¥ãŒããŠããããªããå
šéãèšèŒããŠããŸããæ®æ®µããã³ãŒãã£ã³ã°ãšãŒãžã§ã³ããåºåããã³ãŒãã®ã¬ãã¥ãŒã«ã¯Codexãå©çšããŠãããçæå
ãšã¯å¥ãã³ããŒã®ã¢ãã«ã«å°éå®¶ã®ç«å Žã§ã¬ãã¥ãŒãããããšã§ãåäžã¢ãã«ã®ãã€ã¢ã¹ãé¿ããçãããããŸãã No èª²é¡ èª²é¡åé¡ åªå
床 詳现 1 Aurora ã RemovalPolicy.DESTROY éçš / ã»ãã¥ãªã㣠must ã¹ã¿ãã¯åé€ã眮æã§ DBãåé€ ããåŸãŸããæ¬çªã¯ RETAIN ãåºæ¬ã«ãåé€ä¿è·ãæå¹åãã¹ãã§ãã 2 Aurora ã®åé€ä¿è·ïŒDeletionProtectionïŒãæªèšå® éçš / ã»ãã¥ãªã㣠must ãªããã¹ïŒ cdk destroy çïŒã誀眮æã§æ¶ãããªã¹ã¯ãæ®ããŸãã 3 ããã¯ã¢ããä¿æïŒbackup retentionïŒçã®ããŒã¿ä¿è·ãèã éçš must Aurora åŽã®ããã¯ã¢ããä¿ææ¥æ°ã»åŸ©æ§èšèšãæç€ºããŠããŸãããæäœã§ãä¿ææéãèŠä»¶åããåŸ©æ§æé ãæ³å®ãã¹ãã§ãã 4 NAT Gateway ã 1 å°ïŒ2AZã§ãåäžNATïŒ å¯çšæ§ / ã³ã¹ã must 2AZ æ§æã§ã NAT ãåäžã ãš NATé
眮AZé害æã« private åŽã®å€åãéä¿¡ãç Žç¶» ããããã§ããå ããŠã¯ãã¹AZçµç±ã³ã¹ããå¢ããã¡ã 5 ALB ã publicLoadBalancer: true åºå® ã»ãã¥ãªã㣠/ éçš must ç¡æ¡ä»¶ã§ã€ã³ã¿ãŒãããå
¬éåæã«ãªããŸããçšéèŠä»¶ïŒç€Ÿå
åã/internal ãå
¬éãïŒã決ããŠéžã¶ã¹ãã§ãã 6 HTTPS èšå®ããªãïŒèšŒææž/HTTPâHTTPSãªãã€ã¬ã¯ãïŒ ã»ãã¥ãªã㣠must çŸç¶ã¯ HTTP ã®ãŸãŸå
¬éã«ãªããã¡ãACM èšŒææžã®èšå®ãš 443 çµç«¯ã80â443 ãªãã€ã¬ã¯ããå
¥ããã®ãåºæ¬ã§ãã 7 Secrets ã®æž¡ãæ¹ããsecretå
šäœãã1倿°ã«å
¥ããŠãã ä¿å®æ§ / ã»ãã¥ãªã㣠recommend DB_SECRET ã« JSON å
šäœãå
¥ã圢ã«ãªããã¢ããªåŽãè§£éäŸåã§å£ããããã fromSecretsManager(secret, 'password') ã®ããã«ããŒæå®ããŠæž¡ãæ¹ãå
ç¢ã§ãã 8 dbCluster.secret! ã® non-null assertion ä¿å®æ§ recommend å°æ¥ã®å€æŽãæ¡ä»¶åå²ã§ secret ããªãæ§æã«å¯ããšå®è¡æã«ç Žç¶»ããŸããååšåæãã³ãŒãã§æ
ä¿ããããçææ¡ä»¶ãæç€ºãã¹ãã§ãã 9 DB ãžã®æ¥ç¶æ
å ±ã env ã«çŽå
¥ãïŒhost/port/nameïŒ ã»ãã¥ãªã㣠/ ä¿å®æ§ recommend æ©åŸ®åºŠã¯äœãããç°å¢å·®åã倿Žã«åŒ±ããã¢ããªèšå®ãšããŠäžå
管çïŒSSM/Secrets/ConfigïŒãæ¥ç¶æåååãªã©ãæ€èšã 10 ç£èŠ/ãã°èšèšãã»ãŒãªãïŒALBã¢ã¯ã»ã¹ãã°ãECSãã°ãã¢ã©ãŒã çïŒ éçš recommend ãããã€åŸã«é害察å¿ã§ããªãæ§æã«ãªããã¡ãæäœé CloudWatch Logsãäž»èŠã¡ããªã¯ã¹ïŒCPU/Memory/ALB 5xx/TargetResponseTimeïŒã¢ã©ãŒã ãçšæã 11 ã¹ã¿ãã¯åå²ããªãïŒNetwork/App/Db ãåäžStackïŒ ä¿å®æ§ / éçš recommend å·®åãããã€ã»æš©éåé¢ã»å€æŽç®¡çãé£ãããªããŸããç°å¢ãè²ã€ã»ã©èŸãã§ãã 12 VPC Endpoint ã䜿ãã NAT äŸåïŒECR/Logs/Secrets çïŒ ã³ã¹ã / å¯çšæ§ recommend NAT ã¯ã³ã¹ãå¢ã»åäžç¹åã«ãªãããããECR/CloudWatch Logs/Secrets Manager ãªã©ã¯ VPC Endpoint 䜵çšã§ã³ã¹ããšå¯çšæ§ãæ¹åã§ããŸãã 13 SG èšèšãæå°éïŒECSâDBã®èš±å¯ã®ã¿ã§ãæ¹éãã³ãŒãåãããŠããªãïŒ ã»ãã¥ãªã㣠nits ä»ã¯åãããå°æ¥æ¡åŒµã§ã«ãŒã«ãæ£éžããã¡ãã€ã³ããŠã³ã/ã¢ãŠãããŠã³ãæ¹éãããŒãèšèšããã¿ãŒã³åãããã 14 DB ãŠãŒã¶ãŒ/ããŒããŒã·ã§ã³æ¹éãã³ãŒãå€ ã»ãã¥ãªã㣠/ éçš nits ã©ã®èªèšŒæ¹åŒã§éçšãããïŒSecrets rotationãIAM authãèžã¿å°/SSM çµç±ãªã©ïŒãæªå®çŸ©ãèŠä»¶ãšããŠå¥é決ããé åã ç¹ã«åé¡ãªã®ã¯ä»¥äžã§ããã No5: ALB ã publicLoadBalancer: true åºå® No6: HTTPS èšå®ããªãïŒèšŒææž/HTTPâHTTPSãªãã€ã¬ã¯ãïŒ ããšããšã¢ããªã±ãŒã·ã§ã³ãšã³ãžãã¢ãšããŠã¯ã以äžãæ°ã«ãªããšããã§ãã No10: ç£èŠ/ãã°èšèšãã»ãŒãªãïŒALBã¢ã¯ã»ã¹ãã°ãECSãã°ãã¢ã©ãŒã çïŒ No11: ã¹ã¿ãã¯åå²ããªãïŒNetwork/App/Db ãåäžStackïŒ (3)ã¬ãŒãã¬ãŒã«ãšããŠèšèšãšããã³ããããã¥ãŒãã³ã°ãã©ã®çšåºŠè§£æ¶ãããããç¢ºèª åºãŠãã課é¡ç¹ãã¬ãŒãã¬ãŒã«ãšããŠæ·ãããããããã³ããããã¥ãŒãã³ã°ããŸãã ããªãã¯AWS CDKïŒTypeScriptïŒãçšããŠæ¬çªéçšåæã® ECS(Fargate) + Aurora PostgreSQL(Serverless v2) æ§æãå®è£
ããã¯ã©ãŠãã¢ãŒããã¯ãã§ãã doc/require-infra.mdã«åŸã£ãŠãããã¥ã¡ã³ãã®å
å®¹ãæŒããããå®éçšã«èããæ§æã§äœã£ãŠãã ããã äžããããã¥ã¡ã³ãã¯ä»¥äžã®ãšããã§ãã # å
±éåæ - CDK: aws-cdk-lib v2 - èšèª: TypeScript - AZ: 2AZ - ç°å¢: prod ã®ã¿ - åºåæ§æ: - lib/network-stack.ts - lib/db-stack.ts - lib/app-stack.ts - bin/app.ts - README.mdïŒãããã€æ¹æ³ãšå¿
é ãã©ã¡ãŒã¿ïŒ # ã¹ã¿ãã¯åå² ä»¥äžã® 3 Stack ã«åé¢ããããšïŒ ## NetworkStack - VPC (2AZ) - public / private subnet - NAT Gateway: AZããšã«1å°ïŒnatGateways: 2ïŒ - VPC Endpoint: - ECR(api, dkr) - CloudWatch Logs - Secrets Manager - SSM / SSMMessages / EC2Messages - å
±éã¿ã°ïŒName / Env=prodïŒ ## DbStack - Aurora PostgreSQL Serverless v2 - private subnet ã®ã¿ - Secrets ManagerïŒDBèªèšŒæ
å ±ïŒ - DBçš SecurityGroup ## AppStack - ECS Cluster - Fargate Service + ALB - ECSçš SecurityGroup - HTTPS Listener - CloudWatch Logs / Alarm # Aurora èŠä»¶ - removalPolicy = RETAIN - deletionProtection = true - backup retention = 7 days - DB 㯠public subnet ã«çœ®ããªã - Secrets Manager ãå¿
ãäœæ - dbCluster.secret! ã® non-null assertion ã¯äœ¿çšçŠæ¢ - CloudWatch Logs exportïŒpostgresqlïŒãæå¹å # ãããã¯ãŒã¯ - ECS / Aurora 㯠private subnet - NAT Gateway 㯠2 å° - ECS ã以äžã«å°éã§ããããšïŒ - ECR - CloudWatch Logs - Secrets Manager - SSM # ALB / HTTPS - ALB 㯠publicïŒinternet-facingïŒ - ACM Certificate ARN 㯠props ã§åãåã - Listener: - 443 HTTPS - 80 â 443 redirect - SecurityGroup: - ALB: inbound 443 ã®ã¿ïŒIPå¶åŸ¡ã¯WAFã§è¡ãïŒ - ECS: ALB SG ãã app port ã®ã¿ - DB: ECS SG ãã 5432 ã®ã¿ # WAF - AWS WAFv2 ã ALB ã«é¢é£ä»ãã - èš±å¯ããéä¿¡å
IP 㯠allowlist æ¹åŒãšããïŒãã以å€ã¯ãããã¯ïŒ - IP setïŒIPv4ïŒãäœæããCIDR ã®ãªã¹ãã props ã§åãåããããã«ãã - äŸïŒ["203.0.113.10/32","198.51.100.0/24"] - WebACL ã®ã«ãŒã«åªå
åºŠïŒ 1) Allow: æå® IP set ã«äžèŽ 2) Default action: Block - visibilityConfig ãæå¹åããWAF ã®ã¡ããªã¯ã¹/ãµã³ããªã³ã°ãã°ãæå¹åãã # Secrets / æ¥ç¶æ
å ± - Secrets 㯠JSONããŒåäœã§ ECS ã«æž¡ãïŒ - DB_USER - DB_PASSWORD - DB_HOST / DB_PORT / DB_NAME 㯠Parameter å - Secret å
šäœã1倿°ã«æž¡ããªã # Secrets / æ¥ç¶æ
å ± - Secrets 㯠JSONããŒåäœã§ ECS ã«æž¡ãïŒ - DB_USER - DB_PASSWORD - DB_HOST / DB_PORT / DB_NAME 㯠Parameter å - Secret å
šäœã1倿°ã«æž¡ããªã # ç£èŠ / ãã° - ECS Container Logs â CloudWatch Logs - Log retention = 30 days - CloudWatch Alarm: - ALB 5xx - ECS CPU / Memory # ä¿å®æ§ - Network â Db â App ã®äŸåæ¹åãå®ã - VPC 㯠NetworkStack ãã export - DB endpoint / secret 㯠DbStack ãã export # SGèšèš - SGã¯æç€ºçã«äœæ - allowDefaultPortFrom ã®ã¿ã¯çŠæ¢ - ã©ã®SGâã©ã®SGãåããããã«ãŒã«ãæžã # ã³ã³ãã - nginx:latest ã®ãã㪠latest ã¿ã°ã¯çŠæ¢ - åºå®ã¿ã° or digest ãäœ¿çš # åºåæåŸ
- å Stack ã®è²¬åãæç¢º - prod åºå®æ§æ - README ã«ä»¥äžãèšèŒïŒ - ãããã€æé - å¿
é ãã©ã¡ãŒã¿ïŒACM ARN çïŒ åºåãããã³ãŒã lib/network-stack.ts import * as cdk from 'aws-cdk-lib'; import * as ec2 from 'aws-cdk-lib/aws-ec2'; import { Construct } from 'constructs'; export class NetworkStack extends cdk.Stack { public readonly vpc: ec2.IVpc; constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // ---------------------------------------------------------------- // VPC â 2AZ / public + private / NAT Gateway x2 // ---------------------------------------------------------------- const vpc = new ec2.Vpc(this, 'Vpc', { maxAzs: 2, natGateways: 2, subnetConfiguration: [ { cidrMask: 24, name: 'Public', subnetType: ec2.SubnetType.PUBLIC, }, { cidrMask: 24, name: 'Private', subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, ], }); // ---------------------------------------------------------------- // VPC Endpoints â Interface // ---------------------------------------------------------------- // ECR API vpc.addInterfaceEndpoint('EcrApiEndpoint', { service: ec2.InterfaceVpcEndpointAwsService.ECR, }); // ECR Docker (dkr) vpc.addInterfaceEndpoint('EcrDkrEndpoint', { service: ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER, }); // CloudWatch Logs vpc.addInterfaceEndpoint('CloudWatchLogsEndpoint', { service: ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS, }); // Secrets Manager vpc.addInterfaceEndpoint('SecretsManagerEndpoint', { service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER, }); // SSM vpc.addInterfaceEndpoint('SsmEndpoint', { service: ec2.InterfaceVpcEndpointAwsService.SSM, }); // SSM Messages vpc.addInterfaceEndpoint('SsmMessagesEndpoint', { service: ec2.InterfaceVpcEndpointAwsService.SSM_MESSAGES, }); // EC2 Messages vpc.addInterfaceEndpoint('Ec2MessagesEndpoint', { service: ec2.InterfaceVpcEndpointAwsService.EC2_MESSAGES, }); // S3 Gateway (ECR ã€ã¡ãŒãžã¬ã€ã€ãŒååŸã«å¿
èŠ) vpc.addGatewayEndpoint('S3Endpoint', { service: ec2.GatewayVpcEndpointAwsService.S3, }); // ---------------------------------------------------------------- // å
±éã¿ã° // ---------------------------------------------------------------- cdk.Tags.of(this).add('Env', 'prod'); this.vpc = vpc; } } lib/app-stack.ts import * as cdk from 'aws-cdk-lib'; import * as acm from 'aws-cdk-lib/aws-certificatemanager'; import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch'; import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as ecs from 'aws-cdk-lib/aws-ecs'; import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; import * as logs from 'aws-cdk-lib/aws-logs'; import * as rds from 'aws-cdk-lib/aws-rds'; import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; import * as wafv2 from 'aws-cdk-lib/aws-wafv2'; import { Construct } from 'constructs'; export interface AppStackProps extends cdk.StackProps { readonly vpc: ec2.IVpc; readonly dbCluster: rds.DatabaseCluster; readonly dbSecurityGroup: ec2.ISecurityGroup; readonly dbSecret: secretsmanager.ISecret; readonly certificateArn: string; readonly allowedCidrs: string[]; } export class AppStack extends cdk.Stack { constructor(scope: Construct, id: string, props: AppStackProps) { super(scope, id, props); const { vpc, dbCluster, dbSecurityGroup, dbSecret, certificateArn, allowedCidrs } = props; const appPort = 8080; // ---------------------------------------------------------------- // ACM Certificate (ARN ããåç
§) // ---------------------------------------------------------------- const certificate = acm.Certificate.fromCertificateArn( this, 'Certificate', certificateArn, ); // ---------------------------------------------------------------- // Security Groups // ---------------------------------------------------------------- // ALB SG â inbound 443 ã®ã¿ const albSg = new ec2.SecurityGroup(this, 'AlbSecurityGroup', { vpc, description: 'Security group for Application Load Balancer', allowAllOutbound: true, }); albSg.addIngressRule( ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS inbound', ); // ECS SG â ALB SG ãã app port ã®ã¿ const ecsSg = new ec2.SecurityGroup(this, 'EcsSecurityGroup', { vpc, description: 'Security group for ECS Fargate tasks', allowAllOutbound: true, }); ecsSg.addIngressRule( albSg, ec2.Port.tcp(appPort), 'Allow traffic from ALB on app port', ); // DB SG â ECS SG ãã 5432 ã®ã¿ // 埪ç°äŸåãåé¿ãããã CfnSecurityGroupIngress ãçŽæ¥äœæ new ec2.CfnSecurityGroupIngress(this, 'DbFromEcsIngress', { ipProtocol: 'tcp', fromPort: 5432, toPort: 5432, groupId: dbSecurityGroup.securityGroupId, sourceSecurityGroupId: ecsSg.securityGroupId, description: 'Allow PostgreSQL access from ECS tasks', }); // ---------------------------------------------------------------- // ECS Cluster // ---------------------------------------------------------------- const cluster = new ecs.Cluster(this, 'EcsCluster', { vpc, containerInsightsV2: ecs.ContainerInsights.ENABLED, }); // ---------------------------------------------------------------- // Task Definition // ---------------------------------------------------------------- const taskDef = new ecs.FargateTaskDefinition(this, 'TaskDef', { cpu: 512, memoryLimitMiB: 1024, }); const logGroup = new logs.LogGroup(this, 'AppLogGroup', { retention: logs.RetentionDays.ONE_MONTH, removalPolicy: cdk.RemovalPolicy.DESTROY, }); const container = taskDef.addContainer('app', { image: ecs.ContainerImage.fromRegistry('nginx:1.27-alpine'), logging: ecs.LogDrivers.awsLogs({ logGroup, streamPrefix: 'app', }), environment: { DB_HOST: dbCluster.clusterEndpoint.hostname, DB_PORT: dbCluster.clusterEndpoint.port.toString(), DB_NAME: 'appdb', }, secrets: { DB_USER: ecs.Secret.fromSecretsManager(dbSecret, 'username'), DB_PASSWORD: ecs.Secret.fromSecretsManager(dbSecret, 'password'), }, healthCheck: { command: ['CMD-SHELL', `curl -f http://localhost:${appPort}/ || exit 1`], interval: cdk.Duration.seconds(30), timeout: cdk.Duration.seconds(5), retries: 3, startPeriod: cdk.Duration.seconds(60), }, }); container.addPortMappings({ containerPort: appPort, protocol: ecs.Protocol.TCP, }); // ---------------------------------------------------------------- // Fargate Service // ---------------------------------------------------------------- const service = new ecs.FargateService(this, 'FargateService', { cluster, taskDefinition: taskDef, desiredCount: 2, securityGroups: [ecsSg], vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, minHealthyPercent: 100, maxHealthyPercent: 200, enableExecuteCommand: true, }); // ---------------------------------------------------------------- // ALB â public (IP å¶åŸ¡ã¯ WAF ã§å®æœ) // ---------------------------------------------------------------- const alb = new elbv2.ApplicationLoadBalancer(this, 'Alb', { vpc, internetFacing: true, securityGroup: albSg, vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, }); // HTTPS Listener (443) const httpsListener = alb.addListener('HttpsListener', { port: 443, protocol: elbv2.ApplicationProtocol.HTTPS, certificates: [certificate], }); httpsListener.addTargets('EcsTarget', { port: appPort, protocol: elbv2.ApplicationProtocol.HTTP, targets: [service], healthCheck: { path: '/', healthyHttpCodes: '200', interval: cdk.Duration.seconds(30), timeout: cdk.Duration.seconds(5), healthyThresholdCount: 2, unhealthyThresholdCount: 3, }, }); // HTTP â HTTPS redirect (80 â 443) alb.addListener('HttpRedirectListener', { port: 80, protocol: elbv2.ApplicationProtocol.HTTP, defaultAction: elbv2.ListenerAction.redirect({ protocol: 'HTTPS', port: '443', permanent: true, }), }); // ---------------------------------------------------------------- // WAFv2 â IP allowlist // ---------------------------------------------------------------- const ipSet = new wafv2.CfnIPSet(this, 'AllowedIpSet', { scope: 'REGIONAL', ipAddressVersion: 'IPV4', addresses: allowedCidrs, description: 'Allowed source IP addresses', }); const webAcl = new wafv2.CfnWebACL(this, 'WebAcl', { scope: 'REGIONAL', defaultAction: { block: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, metricName: 'ProdWebAcl', sampledRequestsEnabled: true, }, rules: [ { name: 'AllowFromIpSet', priority: 1, action: { allow: {} }, statement: { ipSetReferenceStatement: { arn: ipSet.attrArn, }, }, visibilityConfig: { cloudWatchMetricsEnabled: true, metricName: 'AllowFromIpSet', sampledRequestsEnabled: true, }, }, ], }); new wafv2.CfnWebACLAssociation(this, 'WebAclAssociation', { resourceArn: alb.loadBalancerArn, webAclArn: webAcl.attrArn, }); // ---------------------------------------------------------------- // CloudWatch Alarms // ---------------------------------------------------------------- // ALB 5xx new cloudwatch.Alarm(this, 'Alb5xxAlarm', { metric: alb.metrics.httpCodeElb(elbv2.HttpCodeElb.ELB_5XX_COUNT, { period: cdk.Duration.minutes(5), statistic: 'Sum', }), threshold: 10, evaluationPeriods: 2, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, alarmDescription: 'ALB 5xx errors exceeded threshold', treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING, }); // ECS CPU Utilization new cloudwatch.Alarm(this, 'EcsCpuAlarm', { metric: service.metricCpuUtilization({ period: cdk.Duration.minutes(5), statistic: 'Average', }), threshold: 80, evaluationPeriods: 3, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, alarmDescription: 'ECS CPU utilization exceeded 80%', treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING, }); // ECS Memory Utilization new cloudwatch.Alarm(this, 'EcsMemoryAlarm', { metric: service.metricMemoryUtilization({ period: cdk.Duration.minutes(5), statistic: 'Average', }), threshold: 80, evaluationPeriods: 3, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, alarmDescription: 'ECS memory utilization exceeded 80%', treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING, }); // ---------------------------------------------------------------- // å
±éã¿ã° // ---------------------------------------------------------------- cdk.Tags.of(this).add('Env', 'prod'); // ---------------------------------------------------------------- // Outputs // ---------------------------------------------------------------- new cdk.CfnOutput(this, 'AlbDnsName', { value: alb.loadBalancerDnsName, description: 'ALB DNS Name', }); new cdk.CfnOutput(this, 'AlbArn', { value: alb.loadBalancerArn, description: 'ALB ARN', }); new cdk.CfnOutput(this, 'EcsClusterName', { value: cluster.clusterName, description: 'ECS Cluster Name', }); new cdk.CfnOutput(this, 'EcsServiceName', { value: service.serviceName, description: 'ECS Service Name', }); } } lib/db-stack.ts import * as cdk from 'aws-cdk-lib'; import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as logs from 'aws-cdk-lib/aws-logs'; import * as rds from 'aws-cdk-lib/aws-rds'; import { Construct } from 'constructs'; export interface DbStackProps extends cdk.StackProps { readonly vpc: ec2.IVpc; } export class DbStack extends cdk.Stack { public readonly dbCluster: rds.DatabaseCluster; public readonly dbSecurityGroup: ec2.ISecurityGroup; public readonly dbSecret: cdk.aws_secretsmanager.ISecret; constructor(scope: Construct, id: string, props: DbStackProps) { super(scope, id, props); const { vpc } = props; // ---------------------------------------------------------------- // Security Group â DB // ---------------------------------------------------------------- const dbSg = new ec2.SecurityGroup(this, 'DbSecurityGroup', { vpc, description: 'Security group for Aurora PostgreSQL cluster', allowAllOutbound: false, }); // ---------------------------------------------------------------- // Aurora PostgreSQL Serverless v2 // ---------------------------------------------------------------- const cluster = new rds.DatabaseCluster(this, 'AuroraCluster', { engine: rds.DatabaseClusterEngine.auroraPostgres({ version: rds.AuroraPostgresEngineVersion.VER_16_4, }), serverlessV2MinCapacity: 0.5, serverlessV2MaxCapacity: 4, writer: rds.ClusterInstance.serverlessV2('Writer'), readers: [ rds.ClusterInstance.serverlessV2('Reader', { scaleWithWriter: true, }), ], vpc, vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, securityGroups: [dbSg], defaultDatabaseName: 'appdb', credentials: rds.Credentials.fromGeneratedSecret('dbadmin'), backup: { retention: cdk.Duration.days(7), }, deletionProtection: true, removalPolicy: cdk.RemovalPolicy.RETAIN, cloudwatchLogsExports: ['postgresql'], cloudwatchLogsRetention: logs.RetentionDays.ONE_MONTH, storageEncrypted: true, }); // Secret ã®å®å
šãªååŸïŒnon-null assertion çŠæ¢ïŒ const secret = cluster.secret; if (!secret) { throw new Error('Aurora cluster secret was not created'); } // ---------------------------------------------------------------- // å
±éã¿ã° // ---------------------------------------------------------------- cdk.Tags.of(this).add('Env', 'prod'); // ---------------------------------------------------------------- // Export // ---------------------------------------------------------------- this.dbCluster = cluster; this.dbSecurityGroup = dbSg; this.dbSecret = secret; } } (4)æ§æã®æ¯èŒïŒAs-Is â To-BeïŒ ã¬ãŒãã¬ãŒã«ååŸã§ã³ãŒãæ§æãã©ãå€ãã£ãããæ¯èŒããŸãã As-IsïŒã¬ãŒãã¬ãŒã«ãªãïŒåäž StackïŒ CdkTestStackïŒ1ãã¡ã€ã«ã«å
šéšå
¥ãïŒ ââ VPCïŒNATÃ1, Endpoint ãªãïŒ ââ AuroraïŒDESTROY, åé€ä¿è·ãªã, ããã¯ã¢ããæªèšå®ïŒ ââ ECS Fargate â ââ ALBïŒHTTP ã®ã¿, publicLoadBalancer: trueïŒ â ââ Secret å
šäœã 1 倿°ã§æž¡ã â ââ nginx:latest ââ SG: allowDefaultPortFrom ã®ã¿ ââ ç£èŠãªã, WAF ãªã To-BeïŒã¬ãŒãã¬ãŒã«ããïŒ3 Stack åé¢ïŒ NetworkStack ââ VPCïŒ2AZ, NATÃ2, VPC Endpoints 8çš®ïŒ â vpc ã props ã§æž¡ã DbStack ââ Aurora Serverless v2ïŒRETAIN, åé€ä¿è·, backup 7æ¥, æå·åïŒ ââ DB SecurityGroupïŒoutbound ãå¶éïŒ ââ SecretïŒfromGeneratedSecret â ååšãã§ãã¯ä»ãã§ååŸïŒ â dbCluster / dbSecurityGroup / dbSecret ã props ã§æž¡ã AppStack ââ ECS (Fargate) + ALB (HTTPS 443 + 80â443 redirect) ââ WAFv2ïŒIP allowlist, ããã©ã«ã BlockïŒ ââ SG ãã§ãŒã³: ALB(:443) â ECS(:8080) â DB(:5432) ââ Secrets 㯠JSONããŒåäœã§æž¡ãïŒDB_USER / DB_PASSWORDïŒ ââ CloudWatch LogsïŒ30æ¥ä¿æïŒ ââ CloudWatch AlarmsïŒALB 5xx / ECS CPU / ECS MemoryïŒ äž»ãªå·®åããŸãšãããšä»¥äžã§ãã èŠ³ç¹ As-Is To-Be Stack åå² 1 Stack ã«å
šéšå
¥ã Network / Db / App ã® 3 åå² NAT Gateway 1 å°ïŒAZ é害㧠Private éä¿¡æïŒ 2 å°ïŒAZ ããšïŒ VPC Endpoint ãªãïŒå
šéä¿¡ã NAT çµç±ïŒ ECR / Logs / Secrets / SSM ç 8 çš® Aurora åé€ä¿è· DESTROY + ä¿è·ãªã RETAIN + deletionProtection + backup 7æ¥ HTTPS ãªãïŒHTTP å
¬éïŒ ACM èšŒææž + 443 çµç«¯ + 80â443 redirect WAF ãªã IP allowlistïŒããã©ã«ã BlockïŒ SG èšèš allowDefaultPortFrom ã®ã¿ æç€ºçã« 3 SG ãäœæããã§ãŒã³æ¥ç¶ Secrets ã®æž¡ãæ¹ Secret å
šäœã 1 倿° username / password ãããŒåäœã§åé¢ ã³ã³ããã¿ã° nginx:latest nginx:1.27-alpine ïŒåºå®ã¿ã°ïŒ ç£èŠ ãªã CloudWatch Logs + AlarmsïŒ5xx / CPU / MemoryïŒ å€éšãã©ã¡ãŒã¿ ããŒãã³ãŒã certificateArn / allowedCidrs ãã³ã³ããã¹ã倿°ã§æ³šå
¥ No æ®ã£ãŠããèª²é¡ èª²é¡åé¡ åªå
床 詳现 1 ã³ã³ããïŒnginxïŒãš appPort=8080 ããã«ã¹ãã§ãã¯ãäžæŽå å¯çšæ§ / éçš must nginx ããã©ã«ã㯠80ãçŸç¶ã® curl http://localhost:8080/ ãš ALB ã¿ãŒã²ãã(8080)ãæç«ããã ã¿ã¹ã¯ãunhealthyã«ãªãç¶ãã ã appPort=80 ã«ããããããnginx ã® listen ã 8080 ã«å€æŽããã 2 LogGroup ã RemovalPolicy.DESTROY éçš / ã»ãã¥ãªã㣠recommend prod åºå®æ§æã§ãã°ã DESTROY ã¯äºæ
æã®èª¿æ»ã»ç£æ»ã«åŒ±ãã RETAIN æšå¥š ïŒretention 30æ¥èšå®ã¯è¯ãïŒã 3 ALB SG ã® inbound ã anyIpv4ïŒWAFåæã§ã âãããã¯ãŒã¯å¢çâ ãšããŠç·©ãïŒ ã»ãã¥ãªã㣠recommend WAF allowlist ã§å¶åŸ¡ããæ¹éã¯OKã ããSG ã 0.0.0.0/0 ã ãš WAFç¡å¹åã»èª€èšå®ã»é¢é£ä»ãæŒãã®éã«å³å
šéæŸã«ãªããããã 4 NATÃ2 ãš Endpoint 倿°ã䜵åïŒã³ã¹ãæé©åæ¹éãæªæ±ºïŒ ã³ã¹ã / éçš recommend ç®çã«å¯ŸããŠäºéæè³ã«ãªããã¡ã ãNATãæžãããŠEndpointã§å¯ãããorãEndpointãçµã£ãŠNATã§å¯ããã ã®æ¹éãæ±ºãããïŒECR/Logs/Secrets/SSMã¯æ®ãããªã©ïŒã 5 CfnSecurityGroupIngress æ¡çšçç±ïŒåŸªç°äŸååé¿ïŒãäžéæ ä¿å®æ§ nits å¿
èŠæ§ãæç¢ºã§ãªããšä¿å®æã«æ··ä¹±ãããããéåžžã® dbSg.addIngressRule(ecsSg, ...) ã§æç«ãããªãçµ±äžãæç«ããªããªã ãªã埪ç°ããã ãã³ã¡ã³ãã§æ®ãã 6 DBæ¥ç¶æ
å ±ã® Parameter åãæªå®è£
ïŒèŠä»¶ã¯ãããã³ãŒãåæ ãèãïŒ ä¿å®æ§ / éçš nits çŸç¶ DB_HOST/PORT/NAME ãçŽæžãå¯ããèŠä»¶éããªã SSM Parameter Store ãªã©ã§ç®¡ç ããç°å¢å·®åã»å€æŽå®¹ææ§ãäžããã 7 ECS Exec ã®éçšåæïŒIAM/ç£æ»/å©çšå¶åŸ¡ïŒãæªå®çŸ© ã»ãã¥ãªã㣠/ éçš nits enableExecuteCommand: true ã¯è¯ããã 誰ãã»ãã€ã»ã©ãèš±å¯ ãããïŒIAMæ¡ä»¶ãCloudTrailç£æ»ãæé ïŒãèšèšã«èœãšããšæ¬çªéçšã§æãã«ããã 8 ãªãŒãã¹ã±ãŒã«æŠç¥ãæªå®çŸ©ïŒåºå® desiredCount=2ïŒ å¯çšæ§ / ã³ã¹ã nits æå°æ§æãšããŠã¯å¯ã ããæ¬çªåæãªã CPU/Memory/ALB ææšã§ AutoScaling ãæ€èšãããïŒã¹ãã€ã¯èæ§ã»ã³ã¹ãæé©åïŒã 9 ALB ã¢ã¯ã»ã¹ãã°ïŒS3ïŒãªã©ç£æ»ãã°ãæªå®è£
éçš / ã»ãã¥ãªã㣠nits ç£æ»ãé害解æã®èгç¹ã§ãèŠä»¶æ¬¡ç¬¬ã§ã¯ ALB access log ãæå¹åãããïŒå人æ
å ±ãä¿ç®¡ããªã·ãŒå«ããŠèŠæ€èšïŒã 10 ACM èšŒææžãåç
§ã®ã¿ã§äœæããŠããããæŽæ°ã©ã€ããµã€ã¯ã«ã IaC 管çå€ éçš / ã»ãã¥ãªã㣠recommend fromCertificateArn ã§æ¢åèšŒææžãåç
§ããŠããã ãã§ãèšŒææžã®çºè¡ã»æŽæ°ã CDK 管çå€ãå€éš CA ããã®ã€ã³ããŒãèšŒææžã ã£ãå ŽåãACM ã¯èªåæŽæ°ããªãããæå¹æéåãã®éçšäºæ
ãªã¹ã¯ãããã 11 ãã¡ã€ã³ã»DNSïŒRoute 53ïŒãæ§æã«å«ãŸããŠããããIaC 管çç¯å²ãæªå®çŸ© éçš / èšèš recommend Route 53 Hosted ZoneãALB ãžã® Alias ã¬ã³ãŒããACM DNS æ€èšŒçš CNAME ãæªå®çŸ©ããã¡ã€ã³ã IaC ã®å€ã«ããããèšŒææžã CDK ã§äœæã§ããŠããªããã©ããŸã§ã IaC ã§ç®¡çãããã®æ¹é決å®ãå¿
èŠã 12 Aurora ã®ããŒãžã§ã³ç®¡çæ¹éãæªå®çŸ©ããã€æ¬çªã§ Serverless v2ïŒminCapacity 0.5ïŒã®åŠ¥åœæ§ãæªæ€èš å¯çšæ§ / ã³ã¹ã recommend ã¡ãžã£ãŒããŒãžã§ã³ã®ã¢ããã°ã¬ãŒãæŠç¥ïŒ16â17çïŒãæªå®çŸ©ããŸã Serverless v2 ã® minCapacity 0.5 ACU ã¯äœãã©ãã£ãã¯æã®ã³ãŒã«ãã¹ã¿ãŒããã³ã¹ãäºæž¬ã®äžå®å®ãããããæ¬çªã§ã¯ Provisioned or minCapacity åŒãäžãã®æ€èšãèŠãã ïŒåœç¶ã§ããïŒmust, recommendãæžããŸããããããã€ããã¢ããªã®ç¹æ§ã«äŸåãããã®ãé€ããšããããã¯ã·ã§ã³ã«æã£ãŠããã«ã¯æ®èª²é¡ãšããŠä»¥äžã®ä¿®æ£ãå¿
èŠãšãªãããã§ãã ALB/App/DBãããããã©ã®ãµããããã«é
眮ããããæç¢ºã«ã PRIVATE_WITH_EGRESS ã§ã¯ãªãå°ãªããšãALB㯠PRIVATE_ISOLATED ãå©çšããã ALBãNATä»ããµããããã«çœ®ãçç±ããªãã LogGroup㯠RemovalPolicy.DESTROY ã§ã¯ãªã RETAIN ãšãããã SGã§ãallowlistã®CIDRã«çµã£ãŠWAFãšäºéé²åŸ¡ã ACMãpropã§åãåãããšã¯æèšããŠãããã®ã®ãACMãçºè¡ããã¹ã¿ãã¯ãæç€ºçã«æå®ããŠããªãã£ãããšã§èšŒææžãæ£ããæ©èœããªããããæç€ºçã«ACMãäœæããã¹ã¿ãã¯ãäœæã ãããã§ããããã ç·è© ããŠãä»ååºãŠããã³ãŒãã®è©äŸ¡ãšãã©ããã£ãã¹ãã«ã®äººã䜿ãããªãããããšãã£ã芳ç¹ã§ãŸãšããŸãã ãŸãã¯ã€ã³ãã©ãšã³ãžãã¢ã«ã€ããŠä»¥äžã®ããã«ã¬ãã«ãå®çŸ©ããŸãã ãžã¥ãã¢ã¬ãã« âåãæ§æâ ãçŽ æ©ãçµããïŒVPC/ECS/RDSãã€ãªããçéãããïŒ ãã ã æ¬çªã®å®å
šèŠä»¶ïŒåé€èæ§ã»HTTPSã»ç£æ»/éçšã»å¢çèšèšïŒãããã©ã«ãã§èœãšããã¡ çæç©ã¯ãPoC/ãã¢å質ãã«ãªãããã ããã«ã¬ãã« æ¬çªã®ã¬ãŒãã¬ãŒã«ïŒRETAINãDeletionProtectionãHTTPSãSGåé¢ããã°/ç£èŠãVPC Endpoint çïŒ ãæèããŠèšèšã«èœãšãã ãã ã现éšïŒããŒã/ãã«ã¹ãã§ãã¯æŽåããµããããã®çœ®ãæ¹ãWAF/SGã®å€å±€é²åŸ¡ãéçšãããŒïŒã§ç©Žãæ®ãããã ãã¬ãã¥ãŒåæã§æ¬çªåè£ããŸã§æã£ãŠããã ã·ãã¢ã¬ãã« èšèšæå³ã»éçšã»å€æŽèæ§ïŒå°æ¥ã®èŠä»¶å€æŽ/èª€å€æŽ/ç£æ»å¯Ÿå¿ïŒãŸã§å«ããŠãå£ãã«ããCDKã«ã§ãã ãã¬ãŒããªãïŒå¯çšæ§/ã³ã¹ã/ã»ãã¥ãªãã£ïŒãåæããèšèªåããCDKãžåæ ã§ãã âåãâã ãã§ãªã äºæ
ããªã/ç¶ç¶éçšã§ãã ãããã©ã«ãã«ã§ãã æåã«åããã³ãŒãã¯ã©ã®ã¬ãã«ãïŒ ãžã¥ãã¢ã¬ãã«ã§ããPoCã®å質ãšããŠãå±ããã¬ãã«ã象城çãªçç±ãšããŠã¯ã DBã RemovalPolicy.DESTROY HTTPSéä¿¡ããªããã€ãããªãã¯å
šéæŸ Secretsã®äœ¿ãæ¹ãé NATãå¯çšæ§ããã°ç£èŠçãæªå®çŸ© ã¬ãŒãã¬ãŒã«åŸã«åºãŠããã³ãŒãã¯ã©ã®ã¬ãã«ãïŒ ããã«ã¬ãã«ã§ãããã¬ãã¥ãŒåæã§æ¬çªåè£ã è¯ãç¹ãšããŠã¯ã AuroraïŒRETAIN + deletionProtection + backup + logs export 3ã¹ã¿ãã¯åå²ïŒNetwork/Db/AppïŒã§ä¿å®æ§ãäžãã£ã HTTPSçµç«¯ã80â443 redirect VPC Endpoints/SSMç³»ãå
¥ããéçšå°ç·ïŒECS ExecïŒãæç«ãããã WAF allowlist ãå®è£
ïŒå
¥å£å¶åŸ¡ãã³ãŒãåïŒ ã¬ãã¥ãŒã§ã·ãã¢ã¬ãã«ã®äººã«åŒŸããŠããã ãå¿
èŠãããç¹ãšããŠã¯ããŸã 課é¡ããããŸãã nginxãªã®ã« appPort=8080ããã«ã¹ãã§ãã¯ã®äžæŽå LogGroupãDESTROYïŒæ¬çªç°å¢ãšããŠã¯åŒ±ãïŒ public ALB + SG anyIpv4 ã§ âWAFäŸåã匷ãâïŒå€å±€é²åŸ¡ãèãïŒ NATÃ2 + Endpoint倧éã®âæ¹éâãææ§ïŒã³ã¹ãèšèšãæ®ãïŒ ã©ã®ã¬ãã«ã§ããã°äœ¿ãããªãããïŒ çµè«ãšããŠãCDKãèªããã·ãã¢ãŸãã¯ããã«ã§ãäžäœã¯ã©ã¹ã®äººãèšèšè
å
Œã¬ãã¥ã¢ãšããŠäœ¿ããšãæ¬çªç°å¢ã«æã£ãŠãããããªãšããæè§Šã§ãã ã¢ããªã±ãŒã·ã§ã³åæ§ã«ã现éšãçè§£ã§ãã人ã§ããã°IaCã³ãŒããClaude Codeã§æ§ç¯ããããšã¯å¯èœã«ãªã£ãŠãããªãšæããŸããã 工倫ãããšãããïŒ Security HubãAWS ConfigããŸãprowlerãªã©ã®ã»ãã¥ãªãã£ã»ã³ã³ãã©ã€ã¢ã³ã¹ãã§ãã¯ã®ä»çµã¿ãå©çšããããšã§ãã£ãŒãããã¯ã«ãŒããåã AWSå
¬åŒã®MCPçµç±ã§ææ°ããã¥ã¡ã³ããåç
§ããã ãšãã£ãããšãå å³ããããšã§ãããå°ãå®çšçãªã·ã¹ãã ã«ä»äžããããšãã§ããã§ãããã æåŸã« ç§ã®ãããªã€ã³ãã©çµéšããªã人éã§ãããã®ã¬ãã«ã®æ§æã«æã£ãŠãããã®ã¯çŽ çŽã«æåããŸããã ãã¡ããæ¥åžžããåžžã«AWSç°å¢ãè§Šã£ãŠãã人ããããããåœç¶ãããããããã¯ã穎ã ããããšæããããããããŸããããäžçªæåããã®ã¯ãå匷é床ãäžãã£ããç¹ã§ããä»åã®æ§æã«éã£ã話ã§ããã°ããã®ã®æ°æéã§ã€ã³ãã©ãžã¥ãã¢ãšã³ãžãã¢ãšåä¹ãããããã«ã¯ãªã£ãããªãšã ããã¯AIãåºå§ããŠã¢ããªã±ãŒã·ã§ã³éçºã«ãèšãããŠããããšã§ããããçè§£è² åµãã宿žãç¶ããããšã§AIãšå
±åããªããæé·ãç¶ããããã®ã¯ã€ã³ãã©ã«ã€ããŠãåãã ãšå®æããŸãããä»åŸã¯ç©æ¥µçã«IaCã«ãClaude Codeãå©çšããŠãããããšæããŸãã ç§ãã¡ã¯äžç·ã«åããŠããã仲éãåéããŠããŸãïŒ é»éç·ç ãã£ãªã¢æ¡çšãµã€ã é»éç·ç æ°åæ¡çšãµã€ã å·çïŒ @kamino.shunichiro ã¬ãã¥ãŒïŒ @ozaki.hisanori ïŒ Shodo ã§å·çãããŸãã ïŒ