
這篇把我在實物上設定 EC2 container log遇到的問題及做法完整記下來

## 為什麼要用 CloudWatch Logs

以前要看 container logs 必須 SSH 進 EC2 跑 `docker logs`，但當環境變多（staging + prod）後就很難管理。CloudWatch Logs 可以讓你直接在 AWS Console 或 CLI 看日誌，不需要每次 SSH。

## 整體架構

```
Docker containers on EC2
  ├── nginx
  ├── api
  └── postgres
        │
        │  each container's stdout/stderr
        │  sent via Docker's awslogs driver
        │  directly to AWS CloudWatch Logs
        ▼
CloudWatch Logs
  ├── /side-project/dev/nginx
  ├── /side-project/dev/api
  └── /side-project/dev/postgres
```

要讓這件事成立，需要三個東西：

1. **CloudWatch 有 log group 接收**（Terraform 建）
2. **EC2 有權限寫入 CloudWatch**（IAM role + instance profile）
3. **Docker containers 知道要送去哪**（docker-compose 設定 awslogs driver）

## 查看 Logs

```bash
# CLI (requires AWS profile)
aws logs tail /side-project/dev/api --follow --profile side-project
aws logs tail /side-project/dev/postgres --follow --profile side-project
aws logs tail /side-project/dev/nginx --follow --profile side-project

# Console:
# CloudWatch > Log groups > /side-project/dev/*
```

Local dev 不走 CloudWatch，直接用 `docker compose logs`。

## IAM 概念

### IAM Role — 門禁卡

IAM Role 就像一張門禁卡，上面寫著：
- **誰可以戴這張卡**（Trust Policy / Assume Role Policy）
- **戴了這張卡可以進哪些房間**（Permission Policy）

```
aws_iam_role "ec2"
├── Trust Policy:      only EC2 service can assume this role
└── Permission Policy: allows writing to CloudWatch Logs
```

### Instance Profile — 名牌夾

EC2 不能直接掛 IAM Role，必須透過 Instance Profile 這個「容器」間接掛。這是 AWS 的設計限制：

```
IAM Role (access card) ──▶ Instance Profile (badge holder) ──▶ EC2 Instance (person)
```

Terraform 範例：

```hcl
resource "aws_iam_role" "ec2" { ... }

resource "aws_iam_instance_profile" "ec2" {
  role = aws_iam_role.ec2.name
}

resource "aws_instance" "api" {
  iam_instance_profile = aws_iam_instance_profile.ec2.name
}
```

注意：對已存在的 EC2 補上 `iam_instance_profile` 會觸發 instance 重建（AWS 限制）。

### PassRole — 交出門禁卡的權限

GitHub Actions 透過 Terraform 幫 EC2 掛 IAM Role。AWS 會要求執行者具備「把門禁卡交出去」的權限：`iam:PassRole`。

如果沒有這個限制，任何有 IAM 權限的人都能把超級管理員 role 掛到任意 EC2。

```hcl
{
  Action   = ["iam:PassRole"]
  Resource = "arn:aws:iam::*:role/side-project-ec2-*"
  Condition = {
    StringEquals = {
      "iam:PassedToService" = "ec2.amazonaws.com"
    }
  }
}
```

最佳實務是把 PassRole 獨立成一個 statement，跟其他 IAM 操作（CreateRole 等）分開。

## AWS 權限結構

每條 IAM Statement 由三個部分組成：

```hcl
{
  Action   = "some operation"
  Resource = "which resources"
  Condition = { ... }
}
```

### 為什麼有些權限要用 `Resource = "*"`

像 `logs:DeleteLogGroup` 這種操作是針對特定 log group，可以做資源限制：

```hcl
{
  Action   = "logs:DeleteLogGroup"
  Resource = "arn:aws:logs:us-east-1:*:log-group:/side-project/*"
}
```

但 `logs:DescribeLogGroups` 是列表操作，AWS 不會用你指定的 resource pattern 來比對，必須用 `*`。這是唯讀操作，安全性可接受。

| 權限類型 | Resource 可以限定嗎？ | 原因 |
|----------|----------------------|------|
| 增刪改操作（CreateLogGroup, DeleteLogGroup 等） | 可以，限定到 `/side-project/*` | 操作針對具體資源 |
| 列表操作（DescribeLogGroups） | 不行，必須用 `*` | AWS 不支援列表 API 的資源限定 |

### 舊 API vs 新 API

AWS 會更名 API，以下兩組功能相同：

| 功能 | 舊 API | 新 API |
|------|--------|--------|
| 對 log group 加 tag | `logs:TagLogGroup` | `logs:TagResource` |
| 列出 log group 的 tags | `logs:ListTagsLogGroup` | `logs:ListTagsForResource` |

Terraform AWS provider 已改用新 API，但為了相容我會兩組都寫。

## Bootstrap vs Main Terraform

| | Bootstrap | Main Terraform |
|--|-----------|---------------|
| 在哪裡跑 | 本地（手動） | GitHub Actions（CI/CD） |
| 管什麼 | GitHub Actions 的 IAM 權限、S3 state bucket | EC2、Security Group、EIP、IAM Role、CloudWatch |
| 為什麼分開 | CI/CD 不能自己幫自己加權限（雞生蛋） | 需要先有權限才能 apply |
| 多久跑一次 | 很少，只有改 CI/CD 權限時 | 每次 push 到 main |

流程：

```
1. run bootstrap terraform apply locally
   → GitHub Actions gets new permissions

2. push to main
   → infra.yml: terraform apply (creates IAM role, log groups, EC2)
   → deploy.yml: SSH into EC2, deploy containers
```

## Docker awslogs Driver

```yaml
# docker-compose.prod.yml
services:
  api:
    logging:
      driver: awslogs
      options:
        awslogs-group: /side-project/${APP_ENV}/api
        awslogs-region: ${AWS_REGION}
        tag: api
```

- `driver: awslogs`：Docker 不存 logs 在本機，改送到 CloudWatch
- `${APP_ENV}` / `${AWS_REGION}`：從 `.env` 讀取
- `tag`：在 CloudWatch 裡辨識 log stream

## Conclusion

把容器日誌集中到 CloudWatch 後，排錯速度大幅提升，而且 multi-env 管理也更乾淨。這套做法不只適用 Side-Project，任何 EC2 + Docker 的部署都能直接套用。

## 參考連結

- [Docker awslogs logging driver](https://docs.docker.com/engine/logging/drivers/awslogs/)
- [IAM Roles for Amazon EC2 - Instance profiles](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2_instance-profiles.html)
- [Grant permissions to pass IAM roles to AWS services (EC2)](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/permission-to-pass-iam-roles.html)
- [IAM condition keys - iam:PassedToService](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_iam-condition-keys.html)
- [Amazon CloudWatch Logs - Service authorization reference](https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazoncloudwatchlogs.html)
- [AWS CLI - logs tail](https://docs.aws.amazon.com/cli/latest/reference/logs/tail.html)
