# 📦 CloudFormation: template, stack và change set

## Mục tiêu bài học

Sau bài này, bạn sẽ:

- Đọc hiểu cấu trúc một template CloudFormation và biết section nào dùng để làm gì.
- Phân biệt rõ **template**, **stack**, **change set** và khi nào phải dùng change set.
- Xử lý được các tình huống rollback, `UPDATE_ROLLBACK_COMPLETE` và drift.
- Chọn đúng cơ chế truyền tham số và secret (parameter, `NoEcho`, SSM, Secrets Manager) theo câu hỏi thi.

## Ôn nhanh bài trước

Bài trước bạn đã đi qua luồng CodePipeline, CodeDeploy và SAM. Điểm cần nhớ: SAM chính là một lớp chuyển đổi (transform) của CloudFormation, nghĩa là file `template.yaml` của SAM khi deploy vẫn biến thành một stack CloudFormation bên dưới. Vì vậy hiểu CloudFormation là điều kiện để hiểu luôn cả SAM.

## Lý thuyết

### Template, stack và change set là gì?

Ba khái niệm này hay bị hỏi lẫn lộn, hãy tách rõ:

| Khái niệm | Bản chất | Ví dụ |
| --- | --- | --- |
| Template | File JSON/YAML mô tả tài nguyên mong muốn | `template.yaml` trong repo |
| Stack | Một tập tài nguyên được tạo và quản lý như một đơn vị từ template | `prod-api-stack` |
| Change set | Bản xem trước (preview) những gì sẽ thay đổi trước khi apply | Xem trước khi thêm một Lambda |

Nói ngắn: template là bản thiết kế, stack là ngôi nhà đã xây, change set là bản dự toán sửa nhà trước khi cho thợ đập tường.

```mermaid
flowchart LR
    T["📄<br/>Template YAML"] --> CS["👁️<br/>Change set"]
    CS --> S["🏗️<br/>Stack"]
    S --> R["🧱<br/>Resources"]
    R --> D["🚨<br/>Drift detection"]
```

### Các section trong template

Một template có các section sau, thứ tự này cũng là thứ tự ưu tiên khi ôn thi:

- `AWSTemplateFormatVersion`: phiên bản template, hiện chỉ có `2010-09-09`.
- `Description`: mô tả ngắn.
- `Parameters`: giá trị truyền vào lúc deploy, có `Type`, `Default`, `AllowedValues`.
- `Mappings`: bảng tra cứu tĩnh, ví dụ map Region sang AMI ID.
- `Conditions`: điều kiện boolean, dùng với `Fn::If` để bật/tắt tài nguyên theo môi trường.
- `Transform`: khai báo `AWS::Serverless-2016-10-31` khi dùng SAM.
- `Resources`: **section duy nhất bắt buộc**, mọi thứ khác đều tùy chọn.
- `Outputs`: xuất giá trị, có thể dùng `Export` để stack khác `Fn::ImportValue`.

> [!IMPORTANT]
> Câu hỏi kinh điển: section nào bắt buộc trong template? Chỉ có `Resources`. Nếu đề cho một template chỉ có `Parameters` và `Outputs`, template đó invalid.

### Pseudo parameters và intrinsic functions cần nhớ

Pseudo parameters có sẵn, không cần khai báo: `AWS::Region`, `AWS::AccountId`, `AWS::StackName`, `AWS::NoValue`.

Vài hàm hay gặp:

- `Ref` lấy giá trị tham số hoặc ID tài nguyên; với resource thì trả về physical ID.
- `Fn::GetAtt` lấy attribute cụ thể, ví dụ `!GetAtt MyBucket.Arn`.
- `Fn::Sub` thay biến trong chuỗi, gọn hơn nối chuỗi.
- `Fn::ImportValue` lấy giá trị `Output` được `Export` từ stack khác.
- `Fn::If` kết hợp với `Conditions`, dùng `AWS::NoValue` để bỏ hẳn thuộc tính.

### Truyền tham số và secret

Đây là vùng dễ mất điểm nhất. Nguyên tắc:

- **Không hard-code secret** trong template.
- `NoEcho: true` chỉ che giá trị trong log/console, **không mã hoá** gì cả. Đừng nhầm nó với bảo mật thật.
- Với secret thật, dùng **SSM Parameter Store** (SecureString) hoặc **Secrets Manager**, rồi tham chiếu động: `{{resolve:ssm:/myapp/db-password}}` hoặc `{{resolve:secretsmanager:myapp/db:SecretString:password}}`.
- Tham chiếu `resolve:` được giải quyết **tại thời điểm deploy**, nên giá trị secret không nằm trong template.
- Trên EC2/ECS/Lambda, nên dùng IAM role + Secrets Manager thay vì nhét secret vào environment variable dạng plaintext.

```cloze
Section duy nhất bắt buộc trong một CloudFormation template là {{c1::Resources}}.
Để che giá trị tham số trong console và log, ta đặt thuộc tính {{c2::NoEcho}} bằng true, nhưng nó không mã hoá giá trị.
Cơ chế trả về giá trị secret tại thời điểm deploy là tham chiếu động {{c3::resolve}} (ví dụ `resolve:ssm` hoặc `resolve:secretsmanager`).
Khi CloudFormation phát hiện tài nguyên bị thay đổi ngoài stack, ta gọi đó là {{c4::drift}}.
```

### Rollback, failure và drift

Khi tạo mới thất bại, CloudFormation rollback và tài nguyên bị xoá (trừ khi bạn bật `DisableRollback` hoặc `OnFailure=DO_NOTHING`).

Khi update thất bại:

- Stack quay về trạng thái cũ với `UPDATE_ROLLBACK_COMPLETE`.
- Nếu chính bước rollback cũng fail, stack kẹt ở `UPDATE_ROLLBACK_FAILED`. Khi đó dùng **ContinueUpdateRollback** với `ResourcesToSkip` để gỡ kẹt.

**Drift** là khi tài nguyên thực tế khác với template (ai đó sửa tay trên console). Drift detection chỉ **báo cáo**, không tự sửa. Bạn phải cập nhật lại template hoặc revert thủ công.

**DeletionPolicy** và **UpdateReplacePolicy** quyết định chuyện gì xảy ra khi tài nguyên bị xoá/thay thế: `Retain` giữ lại, `Snapshot` chụp bản sao (dùng cho RDS, EBS), `Delete` là mặc định.

## Ví dụ minh hoạ

**Ví dụ 1: Tạo stack từ template đơn giản.**

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: Demo stack
Parameters:
  Env:
    Type: String
    AllowedValues: [dev, prod]
Conditions:
  IsProd: !Equals [!Ref Env, prod]
Resources:
  DataBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    Properties:
      BucketName: !Sub "demo-${Env}-data-${AWS::AccountId}"
Outputs:
  BucketName:
    Value: !Ref DataBucket
    Export:
      Name: DataBucketName
```

Đọc từ trên xuống: `Parameters` cho phép chọn môi trường, `Conditions` tạo `IsProd` (ở đây chưa dùng nhưng để thấy pattern), `Resources` tạo bucket với tên dựng bằng `Fn::Sub`, `DeletionPolicy: Retain` nghĩa là xoá stack vẫn giữ bucket. `Outputs` export tên bucket để stack khác dùng `Fn::ImportValue`.

**Ví dụ 2: Deploy có change set.**

```steps
1. Tạo change set: `aws cloudformation create-change-set --stack-name prod-api --template-body file://template.yaml --change-set-name v2 --capabilities CAPABILITY_IAM`
2. Xem thay đổi: `aws cloudformation describe-change-set --change-set-name v2 --stack-name prod-api`
3. Nếu thấy resource bị Replace mà không mong muốn, sửa template rồi tạo lại change set
4. Apply: `aws cloudformation execute-change-set --change-set-name v2 --stack-name prod-api`
```

Lưu ý bước 1: nếu template có tạo IAM resource, **bắt buộc** `--capabilities CAPABILITY_IAM` hoặc `CAPABILITY_NAMED_IAM`, nếu không sẽ báo lỗi `InsufficientCapabilities`.

**Ví dụ 3: Tình huống thi.**

> Đề: một stack đang ở `UPDATE_ROLLBACK_FAILED` do một resource bị xoá tay. Cách xử lý?
>
> Đáp án: dùng `ContinueUpdateRollback` và chỉ định `ResourcesToSkip` cho tài nguyên lỗi, sau đó sửa lại template cho khớp thực tế.

## Thảo luận và suy ngẫm

- Trong team bạn, ai được phép sửa tay trên console? Nếu có người sửa, bạn sẽ phát hiện drift bằng cách nào?
- Với secret trong pipeline, bạn chọn SSM Parameter Store hay Secrets Manager? Tiêu chí nào quyết định (chi phí, rotation, cross-account)?
- Nếu một thay đổi template khiến RDS bị Replace, bạn sẽ chặn nó ở bước nào trong quy trình?

## Góc chuyên gia

Vài bẫy mình thấy nhiều người thi trượt vì nó:

- **NoEcho không phải mã hoá.** Đề rất thích câu "làm sao bảo mật password trong template" và đáp án đúng thường là Secrets Manager hoặc SSM SecureString, không phải `NoEcho`.
- **Change set không bắt buộc.** Bạn vẫn update trực tiếp được. Nhưng câu hỏi kiểu "cần xem trước thay đổi trước khi apply" thì đáp án là change set.
- **Drift detection không tự fix.** Nó chỉ báo. Đọc kỹ đề để không chọn nhầm "CloudFormation tự động đồng bộ".
- **`Resources` là bắt buộc**, mọi section khác optional.
- **Export không xoá được khi còn stack khác import.** Nếu cố xoá, bạn nhận lỗi và phải gỡ `ImportValue` trước.
- **Nested stack** dùng `AWS::CloudFormation::Stack` với `TemplateURL` phải nằm trên S3, không phải đường dẫn local.
- **`CAPABILITY_IAM`** là lỗi hay gặp khi deploy qua CLI/pipeline mà quên.
- Mẹo thi: khi đề nhắc "hạ tầng như code", "lặp lại được", "rollback tự động" thì nghĩ ngay CloudFormation; khi nhắc "xem trước thay đổi" thì nghĩ change set.

```flashcard
[
  { "front": "Section nào là bắt buộc trong CloudFormation template?", "back": "Resources. Mọi section khác đều tùy chọn." },
  { "front": "Template khác stack ở điểm nào?", "back": "Template là file mô tả tài nguyên; stack là tập tài nguyên đã được tạo và quản lý từ template đó." },
  { "front": "Change set dùng để làm gì?", "back": "Xem trước những thay đổi sẽ áp dụng lên stack trước khi execute." },
  { "front": "NoEcho: true có mã hoá giá trị tham số không?", "back": "Không. Nó chỉ che giá trị trong log/console, không mã hoá." },
  { "front": "Cách an toàn để đưa secret vào template là gì?", "back": "Tham chiếu động resolve:ssm hoặc resolve:secretsmanager, được giải quyết tại thời điểm deploy." },
  { "front": "Stack ở trạng thái UPDATE_ROLLBACK_FAILED thì xử lý thế nào?", "back": "Dùng ContinueUpdateRollback, có thể chỉ định ResourcesToSkip cho tài nguyên lỗi." },
  { "front": "Drift là gì?", "back": "Tài nguyên thực tế khác với định nghĩa trong template, thường do sửa tay ngoài stack." },
  { "front": "Drift detection có tự động sửa tài nguyên không?", "back": "Không. Nó chỉ phát hiện và báo cáo." },
  { "front": "DeletionPolicy: Retain làm gì?", "back": "Giữ lại tài nguyên khi stack bị xoá hoặc tài nguyên bị thay thế." },
  { "front": "Fn::ImportValue dùng để lấy gì?", "back": "Giá trị Output đã được Export từ một stack khác." },
  { "front": "Lỗi InsufficientCapabilities khi deploy thường do thiếu gì?", "back": "Thiếu CAPABILITY_IAM hoặc CAPABILITY_NAMED_IAM khi template tạo tài nguyên IAM." },
  { "front": "Nested stack khai báo bằng resource type nào và TemplateURL lấy từ đâu?", "back": "AWS::CloudFormation::Stack, TemplateURL phải trỏ tới một object trên S3." },
  { "front": "Transform AWS::Serverless-2016-10-31 dùng cho cái gì?", "back": "Chuyển template SAM thành CloudFormation chuẩn khi deploy." }
]
```

## Tóm tắt và bước tiếp theo

Bốn ý mang vào phòng thi:

1. `Resources` là section bắt buộc; template là bản thiết kế, stack là thực thể, change set là bản xem trước.
2. Secret phải đi qua SSM SecureString hoặc Secrets Manager bằng tham chiếu `resolve:`, không hard-code, không tin `NoEcho`.
3. Update fail thì rollback về `UPDATE_ROLLBACK_COMPLETE`; kẹt ở `UPDATE_ROLLBACK_FAILED` thì dùng ContinueUpdateRollback.
4. Drift chỉ báo cáo, không tự sửa.

Bài tiếp theo chuyển sang nhóm dịch vụ tích hợp: **SQS, SNS, EventBridge và Step Functions**. Bạn sẽ học cách chọn đúng dịch vụ cho từng kiểu giao tiếp (queue, pub/sub, event bus, orchestration) và các bẫy hay gặp khi đề hỏi về decoupling.
