CertNote/ Commute Study Notes
Roadmap
KOEN
CLF-C02 · FoundationalCloud Practitioner - Foundational
DVA-C02 · AssociateDeveloper - Associate
SAA-C03 · AssociateSolutions Architect - Associate
SOA-C02 · AssociateCloudOps Engineer - Associate
SAP-C02 · ProfessionalSolutions Architect - Professional
DOP-C02 · ProfessionalDevOps Engineer - Professional
  • Week 1
    • 1.DevOps as an Operating Model: The Five Axes of CALMS and the Truth DORA Proved Through Measurement
    • 2.Well-Architected Framework: Rereading It Through the Six Lenses of DevOps
    • 3.The AWS DevOps Tool Map: The Code* Series and the Real Picture Beyond It
    • 4.Multi-Account Strategy: The Real Picture of Organizations, Control Tower, and IAM Identity Center
    • 5.Week 1 Wrap-Up: Cementing the DevOps Thinking Frame with Scenarios
  • Week 2
  • Week 3
  • Week 4
  • Week 5
  • Week 6
  • Week 7
  • Week 8
    • 1.CloudFormation Advanced: Nested·Cross-Stack and the Deep Story of Modularization
    • 2.StackSets: The Deep Story of Deploying IaC to Thousands of Accounts
    • 3.Custom Resource·Hooks·Change Set: CloudFormation's Extension and Validation Mechanisms
    • 4.CDK·CDK Pipelines·Terraform: Deep Comparison of Modern IaC Tools and Self-Evolving Pipelines
    • 5.Week 8 Integrated Scenarios: Where IaC Tools Meet Inside a Single Incident
  • Week 9
  • Week 10
  • Week 11
  • Week 12
  • Week 13
  • Week 14
  • Week 15
  • Week 16
Linux Master 2 · 2급KAIT Linux Master - Level 2
SCS-C03 · SpecialtySecurity - Specialty
Linux Master 1 · 1급KAIT Linux Master - Level 1
MLA-C01 · AssociateMachine Learning Engineer - Associate
AIF-C01 · FoundationalAI Practitioner - Foundational
DEA-C01 · AssociateData Engineer - Associate
MLS-C01 · SpecialtyMachine Learning - Specialty
SQLD · 개발자SQL Developer (SQLD)

© CertNote · 주간랩스

  • About
  • Changelog
  • Contact
  • Terms
  • Privacy
← DOP-C02/Week 8/Day 1

Based on the DOP-C02 exam guide·Report an error

DOP-C02· ProWeek 8 · Day 1~40 min read

Day 1 - CloudFormation Advanced: Nested·Cross-Stack and the Deep Story of Modularization

When you first use CloudFormation, you stuff everything into one file. VPC, security groups, EC2, RDS, ALB all go into one template, and once it exceeds 500 lines, reading git diff becomes impossible. At some point someone says "let's separate the VPC out," and true modularization begins. But the moment you separate it, two paths appear. One is Nested Stack where the parent template directly calls child templates, and the other is Cross-Stack where completely independent stacks exchange values through Export/ImportValue. Both claim the same purpose of "reuse," but their operational models are exact opposites.

Today we examine why these two patterns exist simultaneously, how the software engineering concept of coupling (dating back to 1968) reappears in IaC, the accidents caused by Export being locked while in use, why Stack Policy is a different protective layer from IAM, the internal workings of dynamic references like {{resolve:secretsmanager:...}} and their limitations, and the subtle differences between the DeletionPolicy/UpdateReplacePolicy/UpdatePolicy trio. In DOP exams, this area produces 3-5 scenario questions per test asking "which pattern should you choose."

Nested Stack and Cross-Stack — Two Answers to the Same Problem of Coupling

In software engineering, coupling (how dependent two modules are on each other) and cohesion (how much elements within a module serve a single purpose) were formalized by Larry Constantine in 1968. CloudFormation's two modularization patterns sit at opposite ends of the coupling axis.

Nested Stack is tight coupling (parent-child). You declare an AWS::CloudFormation::Stack resource in the parent template and point TemplateURL at the child template. When the parent Stack updates, child Stacks are re-evaluated too, and when the parent Stack is deleted, child Stacks are deleted together. The lifecycle is bound as one. In exchange, the parent can directly receive the child Stack's Output via !GetAtt VpcStack.Outputs.VpcId, making dependency relationships clear and change tracking easy.

Cross-Stack is loose coupling between independent Stacks. When a network Stack publishes a value via Outputs.Export.Name and an app Stack receives it with !ImportValue Network-VpcId, the two Stacks have completely independent lifecycles so they can be deployed, updated, and deleted separately. But the "contract" exchanging values gets bound in Export name as a global namespace, which becomes the starting point for the pitfall we'll see next.

💡 Related Theory: The coupling-cohesion principle was formalized in Edward Yourdon and Larry Constantine's 1979 book Structured Design, and later evolved into Robert Martin's SOLID principles (especially Single Responsibility and Interface Segregation). The choice between Nested vs Cross-Stack in CloudFormation is essentially the same tradeoff. Nested bundles things in one transaction gaining consistency but can't isolate change impact. Cross-Stack gains isolation but must manage the explicit contract of Export names. Kubernetes's Helm umbrella chart (Nested) and Helm dependencies + separate releases (Cross) sit on exactly the same axis.

🔍 Deeper: CloudFormation internally views Stack as a "transaction unit for resources." In Nested Stack, if one of 50 child Stacks fails, the entire parent Stack rolls back (UPDATE_ROLLBACK_IN_PROGRESS). That is, if the 49th child fails, all previous 48 are rolled back to their prior state. This is the cost of tight coupling. Meanwhile, if the app Stack fails in Cross-Stack, the network Stack remains unchanged. In large environments, it's standard to break Stacks by domain (network/common IAM/per-service) to reduce the "blast radius of failure."

🎯 Scenario: "A company runs 200 microservices where the entire infrastructure Stack gets stuck in UPDATE_IN_PROGRESS for 30 minutes because of one service's IAM Role addition. How to improve?" — The answer is breaking Stacks by domain (network/common IAM/per-service) and connecting with Cross-Stack Export/Import. One service change won't block other service deployments.

When Export is in Use — The Real Meaning of Locking

The biggest pitfall of Cross-Stack appears here. The moment a network Stack exports VpcId and an app Stack imports it with !ImportValue Network-VpcId, that Output becomes "locked". You can't change the name or value of the Output, and scarier, you can't delete the network Stack itself. It's a safety mechanism CloudFormation built to maintain Export global consistency, but in actual operations this frequently causes accidents.

# Network Stack
Outputs:
  VpcId:
    Value: !Ref Vpc
    Export:
      Name: !Sub '${AWS::StackName}-VpcId'   # "prod-network-VpcId"
 
# App Stack (different Stack)
Resources:
  Sg:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !ImportValue prod-network-VpcId   # ← From this moment Network Stack's VpcId Export locks

In this locked state, if you need to update the network Stack to change the Export value (the ID returned by !Ref Vpc), for example through VPC recreation, the update fails entirely. If even one Export is in use, you can't change its value. So the operations team enters a migration window executing a 3-stage deployment: "Stage 1: Remove ImportValue from app Stack", "Stage 2: Update network Stack", "Stage 3: Add ImportValue back to app Stack". If this repeats every time, teams eventually switch to SSM Parameter Store or Service Catalog.

⚠️ Pitfall: You can check who's using an Export beforehand with aws cloudformation list-exports and aws cloudformation list-imports --export-name <name>. If the README of the Stack publishing the Export doesn't document "Export change procedure," six months later no one knows who uses that Export, and trying to change it stops all prod deployments. In 2022, a gaming company was blocked from deploying anything new for 12 hours while expanding VPC because of exactly this.

📚 Case Study: AWS's own documentation (Best Practices) even recommends switching to SSM Parameter Store when Stack count grows, instead of Export/ImportValue. Parameter Store has no locking so it's freely changeable, and when using dynamic references '{{resolve:ssm:/network/vpc-id:1}}', you can specify the version to control unintended change impact. The downside is dependent Stacks don't auto-update when changed (only at deploy time).

Internal Workings of Dynamic References

Syntax like '{{resolve:secretsmanager:prod/db:SecretString:password}}' appears throughout CloudFormation templates. Understanding how it works cuts operational accidents in half.

Dynamic references are a mechanism where CloudFormation fetches the secret from external services immediately before creating/updating resources and temporarily injects it. The template itself never contains plaintext passwords, uploaded template files have no plaintext, and ChangeSet shows it only in masked form. Supported sources are three.

SourceSyntaxCaching
SSM Parameter'{{resolve:ssm:/path/to/param:version}}'Version specifiable, latest uses creation/update time
SSM Secure String'{{resolve:ssm-secure:/path:version}}'KMS decryption then inject, version required
Secrets Manager'{{resolve:secretsmanager:secret-id:SecretString:json-key:version-stage:version-id}}'JSON key extraction, rotation version specifiable

Internally CloudFormation service calls ssm:GetParameter or secretsmanager:GetSecretValue with its Service Role (or user's IAM permissions). So if the role deploying the Stack lacks secret access, you get a Dynamic reference resolution failed error and the entire Stack stalls.

Resources:
  Db:
    Type: AWS::RDS::DBInstance
    Properties:
      MasterUsername: '{{resolve:secretsmanager:prod/db:SecretString:username}}'
      MasterUserPassword: '{{resolve:secretsmanager:prod/db:SecretString:password}}'
      # Fetches different keys from same secret — 2 Secrets Manager calls

🔍 Deeper: Dynamic references never permanently store the fetched value inside CloudFormation. That is, the next Stack update will fetch again, so rotating the secret in Secrets Manager doesn't automatically change the RDS password. To link RDS and Secrets Manager auto-rotation, you need separate AWS::SecretsManager::SecretTargetAttachment and rotation Lambda. Dynamic references are an "injection" mechanism, not a "synchronization" mechanism.

⚠️ Pitfall: With dynamic references, Stack drift detection misses secret changes. CloudFormation compares only the '{{resolve:...}}' string in the template, not the actually fetched value. So even if someone directly changes the password in Secrets Manager, drift doesn't occur. To detect secret changes you need EventBridge rules catching Secrets Manager rotation events.

Stack Policy — A Different Protection Layer from IAM

When you first see Stack Policy, it looks almost identical to IAM policy. JSON, Effect/Action/Principal/Resource structure. But why does it exist separately? IAM controls "who can do what," Stack Policy controls "within this Stack, are specific resources protected." It's not a permissions model but a change protection model.

{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "Update:*",
      "Principal": "*",
      "Resource": "*"
    },
    {
      "Effect": "Deny",
      "Action": ["Update:Replace", "Update:Delete"],
      "Principal": "*",
      "Resource": [
        "LogicalResourceId/ProdDatabase",
        "LogicalResourceId/ProdS3Bucket"
      ]
    }
  ]
}

In a Stack with this policy applied, nobody can replace or delete ProdDatabase with any IAM Role. If a PowerUser accidentally modifies the prod template wrong trying to change the RDS Engine (Engine change causes Replace), it's blocked. To proceed with changes, you must temporarily unlock the Stack Policy, and this unlock action appears in change history forcing human confirmation.

Protection MechanismWho BlocksHow
IAM PolicyUser/role unitDenies API call
Stack PolicySpecific resource in StackDenies Update type (Replace/Delete/Modify)
Termination ProtectionStack's own deletionDenies DeleteStack API
DeletionPolicy: RetainWhen resource is deletedStack deleted but resource remains
UpdateReplacePolicy: RetainWhen resource is replaced by property changeExisting resource preserved

These five work at different points, which people often confuse. DeletionPolicy acts at Stack deletion, UpdateReplacePolicy acts at replacement by property change. The same RDS has different policies when replaced by Engine change versus Stack deletion. So for prod stateful resources, the standard is setting both policies to Snapshot or Retain.

💡 Related Theory: Change protection layering applies NIST SP 800-53 CM-3 (Configuration Change Control) and CM-5 (Access Restrictions for Change) recommendations to IaC. "Change control must exist separately from permissions" — even if people have permission, there must be separate system-level guardrails, a deep security principle (defense in depth).

UpdatePolicy — Different Meanings for ASG and Lambda

UpdatePolicy is easily confused due to similar naming. Completely different from UpdateReplacePolicy (acts during replacement), it controls how to proceed when a resource is updated in-place. It's most known with AutoScalingGroup.

WebAsg:
  Type: AWS::AutoScaling::AutoScalingGroup
  UpdatePolicy:
    AutoScalingRollingUpdate:
      MinInstancesInService: 4         # Always serve traffic with minimum 4
      MaxBatchSize: 2                   # Replace 2 at a time
      PauseTime: PT5M                   # 5 min wait between each batch
      WaitOnResourceSignals: true       # Wait for cfn-signal
      SuspendProcesses:                 # Suspend ASG processes during update
        - HealthCheck
        - ReplaceUnhealthy
        - AZRebalance
        - AlarmNotification
        - ScheduledActions
  Properties:
    MinSize: 4
    MaxSize: 12
    DesiredCapacity: 6
    LaunchTemplate: ...

WaitOnResourceSignals: true means the EC2 UserData calls cfn-signal to report "I've booted normally," and the next batch doesn't start until that signal arrives. Without this, ASG starts the next batch as soon as instances are RUNNING, and new instances failing health checks during that time might silently cause full service death.

ResourceUpdatePolicy TypeWhat Controls
AWS::AutoScaling::AutoScalingGroupAutoScalingRollingUpdateGradual instance replacement
AWS::AutoScaling::AutoScalingGroupAutoScalingReplacingUpdateCreate new ASG and shift traffic
AWS::AutoScaling::AutoScalingGroupAutoScalingScheduledActionBehavior when scheduled action exists
AWS::Lambda::AliasCodeDeployLambdaAliasUpdateCanary/Linear traffic shift
AWS::ElastiCache::ReplicationGroupUseOnlineReshardingOnline resharding

🔍 Deeper: AutoScalingReplacingUpdate creates the ASG itself. That is, spin up new instances in a new ASG, both ASGs briefly coexist, ELB Target Group switches to only new ASG instances, then old ASG is deleted. Think of it as the EC2 version of blue/green. RollingUpdate is in-place replacement; ReplacingUpdate is blue/green at ASG level.

CFN Helper Scripts — A Disappearing Bootstrap Pattern

The pattern of EC2 instances self-configuring with cfn-init during boot was standard in the early-to-mid 2010s. Now it's less frequent as workloads move to AMI baking (Packer), containers, EKS, Lambda, but it still appears in exams.

WebInstance:
  Type: AWS::EC2::Instance
  Metadata:
    AWS::CloudFormation::Init:
      configSets:
        default: [install, configure, start]
      install:
        packages:
          yum:
            nginx: []
            python3: []
        files:
          /etc/nginx/conf.d/app.conf:
            content: !Sub |
              server {
                listen 80;
                location / { proxy_pass http://localhost:8080; }
              }
            owner: root
            group: root
            mode: '000644'
      configure:
        commands:
          01_set_hostname:
            command: !Sub 'hostnamectl set-hostname web-${Environment}'
      start:
        services:
          sysvinit:
            nginx:
              enabled: true
              ensureRunning: true
              files: [/etc/nginx/conf.d/app.conf]
  CreationPolicy:
    ResourceSignal:
      Timeout: PT10M
  Properties:
    UserData: !Base64
      Fn::Sub: |
        #!/bin/bash -xe
        yum install -y aws-cfn-bootstrap
        /opt/aws/bin/cfn-init -v -s ${AWS::StackName} -r WebInstance --configsets default --region ${AWS::Region}
        /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebInstance --region ${AWS::Region}

CreationPolicy.ResourceSignal.Timeout: PT10M means if cfn-signal doesn't arrive within 10 minutes, the instance creation fails. This pairs with ASG's UpdatePolicy.WaitOnResourceSignals. cfn-init is designed with idempotency — running the same configSet multiple times produces the same result. cfn-hup runs as a daemon and re-runs cfn-init when it detects metadata changes (Stack update) — configuration changes without instance reboot.

📚 Case Study: Until around 2018, many companies installed OS packages with cfn-init, but this added 3-5+ minutes to instance boot time and exposed direct yum/apt mirror failure. So now standard is pre-baking AMIs with Packer and using cfn-init only for minimal environment variable injection and cfn-signal. AMI baking reduces boot time to under 30 seconds with the GoldenAMI pattern.

Drift Detection — Detecting Without Auto-Fixing

aws cloudformation detect-stack-drift inspects all Stack resources, comparing template definition to actual state. Results show IN_SYNC, MODIFIED, DELETED, or NOT_CHECKED, and provide detailed comparison for each property. Interestingly, CloudFormation doesn't auto-fix drift.

This is intentional design. Drift means "someone changed this via console/CLI directly," and that change might be an emergency patch or a mistake. If CloudFormation auto-reverts, it erases emergency patches causing bigger disasters. So drift takes the conservative approach — "detect and alert only," and fixes are executed by humans via ChangeSet or separate tools (AWS Config Rules, Custom Lambda).

ToolDrift DetectionAuto-FixWhen to Use
CFN Drift Detection✅ Stack unit❌Manual/scheduled run
AWS Config Rules✅ Resource unit✅ (Remediation)Continuous monitoring
Terraform plan✅ State comparison❌ (refresh accepts)CI pipeline
Pulumi refresh✅❌CI pipeline

🎯 Scenario: "Prod RDS backup retention changed from 7 to 3 days. Track who changed it when and auto-restore." — Answer is (1) CloudTrail tracks RDS modify calls, (2) Config Rule rds-instance-deletion-protection-enabled and similar managed rules detect changes, (3) SSM Automation Document registered as Config Remediation auto-restores. CFN Drift alone detects only, not sufficient.

Summary

Today's picture has five parts. First, Nested Stack and Cross-Stack are two answers to modularization and the 1968 coupling principle applies directly to IaC. Second, Cross-Stack Export locks when in use — operations standards should consider Parameter Store migration. Third, dynamic references are fetch mechanisms, not sync — Drift Detection can't catch it, so EventBridge separately monitors. Fourth, Stack Policy / Termination Protection / DeletionPolicy / UpdateReplacePolicy all work at different points and form a multi-layer protection. Fifth, Drift Detection deliberately only detects, and auto-fixes separate to Config Rules + SSM Automation.

Next we'll see StackSets extending these patterns to thousands of accounts and multiple regions through Organizations integration, raising governance to another level.

📝 Practice Questions

Click a choice to reveal the answer and explanation.

Question 1

A company runs 200 microservices of infrastructure in a single CloudFormation stack. An IAM change for one service locks the entire stack in UPDATE_IN_PROGRESS for 30 minutes. What is the most appropriate way to solve this?

Question 2

The export prod-network-VpcId is being used as an ImportValue by another stack. What happens when a network stack update tries to recreate the VPC (logical ID reused, physical ID changed)?

Question 3

Which of the following is a correct statement about the dynamic reference '{{resolve:secretsmanager:prod/db:SecretString:password}}'?

Question 4

What is the most accurate difference between a stack policy and an IAM policy?

Question 5

What is the most complete combination to protect a prod RDS instance?

Question 6

What is the effect of WaitOnResourceSignals: true in the ASG UpdatePolicy AutoScalingRollingUpdate?

Question 7

What is the most accurate reason CFN drift detection does not provide automatic remediation?

📌 Today's Summary

The five key takeaways from today are as follows. First, nested stacks (tight coupling, one transaction) and cross-stack (loose coupling, independent lifecycles) are an IaC application of the 1968 coupling principle. Second, an export is locked while in use, so switching to Parameter Store is recommended. Third, a dynamic reference is a fetch, not a synchronization, and drift detection cannot catch it. Fourth, stack policy / termination protection / DeletionPolicy / UpdateReplacePolicy are protection layers at different points in time and should be used together as defense in depth. Fifth, drift detection intentionally only detects, and automatic remediation is separated out to Config rules + SSM Automation.

PreviousWeek 7 Review: All the Pieces of Serverless CI/CD in One PictureWeek 7 · Day 5Next StackSets: The Deep Story of Deploying IaC to Thousands of AccountsWeek 8 · Day 2

Official references

  • DOP-C02 official exam page(opens in a new tab)
  • AWS IAM documentation(opens in a new tab)
  • AWS CloudFormation documentation(opens in a new tab)
  • Amazon EC2 Auto Scaling documentation(opens in a new tab)
  • Amazon RDS · Aurora documentation(opens in a new tab)
  • AWS Systems Manager documentation(opens in a new tab)

On this page

  • Nested Stack and Cross-Stack — Two Answers to the Same Problem of Coupling
  • When Export is in Use — The Real Meaning of Locking
  • Internal Workings of Dynamic References
  • Stack Policy — A Different Protection Layer from IAM
  • UpdatePolicy — Different Meanings for ASG and Lambda
  • CFN Helper Scripts — A Disappearing Bootstrap Pattern
  • Drift Detection — Detecting Without Auto-Fixing
  • Summary
  • Practice Questions
  • Today's Summary