Aller au contenu principal

Stacks Imbriquées et StackSets


Introduction

Pour les infrastructures complexes, CloudFormation offre deux mécanismes d'organisation :

  • Nested Stacks : Stacks enfants appelées depuis une stack parent
  • StackSets : Déploiement sur plusieurs comptes/régions

Nested Stacks

Concept

Une nested stack est une stack créée comme ressource d'une autre stack.

# parent-stack.yaml
Resources:
NetworkStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.amazonaws.com/my-bucket/network.yaml
Parameters:
VPCCidr: 10.0.0.0/16

Structure type

infrastructure/
├── parent.yaml # Stack principale
├── templates/
│ ├── network.yaml # VPC, Subnets, IGW
│ ├── security.yaml # Security Groups, IAM
│ ├── database.yaml # RDS, ElastiCache
│ └── application.yaml # EC2, ALB, ASG
└── deploy.sh

Stack parent

AWSTemplateFormatVersion: '2010-09-09'
Description: Stack parent orchestrant toute l'infrastructure

Parameters:
Environment:
Type: String
AllowedValues: [dev, staging, prod]
Default: dev

TemplateBucket:
Type: String
Description: Bucket S3 contenant les templates

Resources:
# Stack réseau
NetworkStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub 'https://s3.amazonaws.com/${TemplateBucket}/network.yaml'
Parameters:
Environment: !Ref Environment
VPCCidr: '10.0.0.0/16'
Tags:
- Key: StackType
Value: Network

# Stack sécurité (dépend du réseau)
SecurityStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub 'https://s3.amazonaws.com/${TemplateBucket}/security.yaml'
Parameters:
Environment: !Ref Environment
VPCId: !GetAtt NetworkStack.Outputs.VPCId
Tags:
- Key: StackType
Value: Security

# Stack base de données
DatabaseStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub 'https://s3.amazonaws.com/${TemplateBucket}/database.yaml'
Parameters:
Environment: !Ref Environment
VPCId: !GetAtt NetworkStack.Outputs.VPCId
SubnetIds: !GetAtt NetworkStack.Outputs.PrivateSubnets
SecurityGroupId: !GetAtt SecurityStack.Outputs.DatabaseSecurityGroup
Tags:
- Key: StackType
Value: Database

# Stack application
ApplicationStack:
Type: AWS::CloudFormation::Stack
DependsOn: DatabaseStack
Properties:
TemplateURL: !Sub 'https://s3.amazonaws.com/${TemplateBucket}/application.yaml'
Parameters:
Environment: !Ref Environment
VPCId: !GetAtt NetworkStack.Outputs.VPCId
PublicSubnets: !GetAtt NetworkStack.Outputs.PublicSubnets
PrivateSubnets: !GetAtt NetworkStack.Outputs.PrivateSubnets
SecurityGroupId: !GetAtt SecurityStack.Outputs.AppSecurityGroup
DatabaseEndpoint: !GetAtt DatabaseStack.Outputs.DatabaseEndpoint
Tags:
- Key: StackType
Value: Application

Outputs:
VPCId:
Value: !GetAtt NetworkStack.Outputs.VPCId

ApplicationURL:
Value: !GetAtt ApplicationStack.Outputs.LoadBalancerDNS

DatabaseEndpoint:
Value: !GetAtt DatabaseStack.Outputs.DatabaseEndpoint

Stack enfant : network.yaml

AWSTemplateFormatVersion: '2010-09-09'
Description: Stack réseau

Parameters:
Environment:
Type: String
VPCCidr:
Type: String
Default: '10.0.0.0/16'

Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCidr
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-vpc'

PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Select [0, !Cidr [!Ref VPCCidr, 6, 8]]
AvailabilityZone: !Select [0, !GetAZs '']
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-public-1'

PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Select [1, !Cidr [!Ref VPCCidr, 6, 8]]
AvailabilityZone: !Select [1, !GetAZs '']
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-public-2'

PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Select [2, !Cidr [!Ref VPCCidr, 6, 8]]
AvailabilityZone: !Select [0, !GetAZs '']
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-private-1'

PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Select [3, !Cidr [!Ref VPCCidr, 6, 8]]
AvailabilityZone: !Select [1, !GetAZs '']
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-private-2'

InternetGateway:
Type: AWS::EC2::InternetGateway

VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway

# ... routes, NAT, etc.

Outputs:
VPCId:
Description: VPC ID
Value: !Ref VPC

PublicSubnets:
Description: Public subnet IDs
Value: !Join [',', [!Ref PublicSubnet1, !Ref PublicSubnet2]]

PrivateSubnets:
Description: Private subnet IDs
Value: !Join [',', [!Ref PrivateSubnet1, !Ref PrivateSubnet2]]

VPCCidr:
Description: VPC CIDR
Value: !GetAtt VPC.CidrBlock

Accéder aux outputs des nested stacks

# Dans la stack parent
!GetAtt NestedStackName.Outputs.OutputName

# Exemple
DatabaseEndpoint: !GetAtt DatabaseStack.Outputs.Endpoint
VPCId: !GetAtt NetworkStack.Outputs.VPCId

Script de déploiement

#!/bin/bash
# deploy.sh

STACK_NAME="my-infrastructure"
BUCKET_NAME="my-cfn-templates"
ENVIRONMENT="prod"
REGION="eu-west-1"

# Upload des templates
echo "Uploading templates to S3..."
aws s3 sync ./templates s3://${BUCKET_NAME}/ --delete

# Déployer la stack parent
echo "Deploying parent stack..."
aws cloudformation deploy \
--template-file parent.yaml \
--stack-name ${STACK_NAME} \
--parameter-overrides \
Environment=${ENVIRONMENT} \
TemplateBucket=${BUCKET_NAME} \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND \
--region ${REGION}

echo "Deployment complete!"

StackSets

Les StackSets permettent de déployer des stacks sur plusieurs comptes et régions.

Créer un StackSet

# Créer le StackSet
aws cloudformation create-stack-set \
--stack-set-name my-stackset \
--template-body file://template.yaml \
--parameters ParameterKey=Environment,ParameterValue=prod \
--permission-model SERVICE_MANAGED \
--auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false

# Ajouter des instances (déploiements)
aws cloudformation create-stack-instances \
--stack-set-name my-stackset \
--deployment-targets OrganizationalUnitIds=ou-xxxxx \
--regions eu-west-1 us-east-1 ap-northeast-1 \
--operation-preferences FailureTolerancePercentage=10,MaxConcurrentPercentage=25

Template StackSet

AWSTemplateFormatVersion: '2010-09-09'
Description: Template pour déploiement multi-compte via StackSet

Parameters:
Environment:
Type: String
Default: production

Resources:
# Bucket S3 de logs (un par compte/région)
LogsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub 'logs-${AWS::AccountId}-${AWS::Region}'
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true

# Config Rule (même config partout)
RequireEncryptionRule:
Type: AWS::Config::ConfigRule
Properties:
ConfigRuleName: require-s3-encryption
Description: Vérifie que les buckets S3 sont chiffrés
Source:
Owner: AWS
SourceIdentifier: S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED

Outputs:
LogsBucketName:
Value: !Ref LogsBucket
Description: Nom du bucket de logs

Permissions StackSets

Pour les déploiements cross-account, vous avez besoin de rôles IAM :

# Dans le compte administrateur
AWSTemplateFormatVersion: '2010-09-09'
Description: Rôle administrateur pour StackSets

Resources:
AdministrationRole:
Type: AWS::IAM::Role
Properties:
RoleName: AWSCloudFormationStackSetAdministrationRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: cloudformation.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: StackSetAdministration
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: sts:AssumeRole
Resource: 'arn:aws:iam::*:role/AWSCloudFormationStackSetExecutionRole'
# Dans chaque compte cible
AWSTemplateFormatVersion: '2010-09-09'
Description: Rôle d'exécution pour StackSets

Parameters:
AdministratorAccountId:
Type: String
Description: ID du compte administrateur

Resources:
ExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: AWSCloudFormationStackSetExecutionRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS: !Sub 'arn:aws:iam::${AdministratorAccountId}:root'
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AdministratorAccess

Nested Stacks vs Cross-Stack References vs StackSets

AspectNested StacksCross-Stack (Export/Import)StackSets
ScopeMême stackMême région/compteMulti-compte/région
CouplageFortMoyenFaible
LifecycleEnsembleIndépendantIndépendant
Use caseModularitéPartage de ressourcesGouvernance

Bonnes pratiques

1. Structure modulaire

infrastructure/
├── main.yaml # Stack parent
├── templates/
│ ├── network/
│ │ └── vpc.yaml
│ ├── security/
│ │ ├── iam.yaml
│ │ └── security-groups.yaml
│ ├── data/
│ │ ├── rds.yaml
│ │ └── elasticache.yaml
│ └── compute/
│ ├── ecs.yaml
│ └── alb.yaml
└── parameters/
├── dev.json
├── staging.json
└── prod.json

2. Versionner les templates

Resources:
NetworkStack:
Type: AWS::CloudFormation::Stack
Properties:
# Utiliser des versions dans S3
TemplateURL: !Sub 'https://s3.amazonaws.com/${Bucket}/v1.2.0/network.yaml'

3. Timeouts pour les nested stacks

Resources:
DatabaseStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: ...
TimeoutInMinutes: 30 # RDS peut prendre du temps

Résumé

ConceptDescription
Nested StackStack enfant déployée par une stack parent
AWS::CloudFormation::StackType de ressource pour créer une nested stack
StackSetDéploiement multi-compte/région
GetAtt...OutputsAccéder aux outputs d'une nested stack

← Fonctions Intrinsèques | Bonnes Pratiques →