목표 : AWS ECR - ECS를 활용하여 Docker 이미지 CI/CD
전체 플로우
코드를 main branch에 push
→ GitHub Actions가 이미지 빌드 & ECR push
→ ECS 서비스 업데이트
→ ALB 헬스체크 통과 시 트래픽 전환
사전 세팅
가정)
Region : ap-northeast-2
app port : 8080
RDS : created
ECR 레포지토리 생성
콘솔] ECR → Repositories → Create repository
예)
Name: plus-app
Tag immutability: Mutable (학습용)
Scan on push: Enabled
생성 후 URI 기록
{ACCOUNT_ID}.dkr.ecr.ap-northeast-2.amazonaws.com/plus-app
네트워크 설정
VPC
콘솔] VPC→ Your VPCs → Create VPC
예)
VPC-only
Name: plus-vpc
IPv4 CIDR: 10.0.0.0/16
Create
콘솔] Subnets → Create subnet
퍼블릭 서브넷 2개 (AZ 다르게)
예)
VPC: plus-vpc (위의 VPC)
Subnet 1
Name: public-a
AZ: ap-northeast-2a
IPv4 CIDR block: 10.0.1.0/24
=> Add new subnet
Subnet 2
Name: public-c
AZ: ap-northeast-2c
IPv4 CIDR block: 10.0.1.0/24
Create
Internet Gateway
콘솔] Internet gateways → Create internet gateway
예)
Name: plus-igw
Actions → Attach to VPC → <plus-vpc>
)Route Table (퍼블릭용) 생성
콘솔] Route tables → Create route table
예)
Name: public-rt
VPC: plus-vpc
생성된 RT에 Route 수정 (상단 **Routes** 탭 → **Edit routes**
)
Add route
Destination: 0.0.0.0/0
Target: plus-igw
생성된 RT에 Subnet 추가 (**Subnet associations** → **Edit subnet associations**
)
Security Group (SG) 설정
콘솔] **EC2** → 왼쪽 **Security Groups** → **Create security group**
ALB-SG
예)
VPC: plus-vpc
Inbound: HTTP 80 from 0.0.0.0/0 (또는 HTTPS 443)
Outbound: 0.0.0.0/0 허용 (기본)
ECS-TASK-SG
예)
VPC: plus-vpc
Inbound: TCP 8080 from **ALB-SG** (Source에 SG 지정)
Outbound: 0.0.0.0/0 허용 (기본)
Task SG는 8080을 ALB-SG에서만 받도록 설정!! (직접 외부 공개 X)
(옵션) CloudWatch Log Group
콘솔] CloudWatch → Logs → Create log group
예)
Name: /ecs/plus-app
IAM Role
Task Execution Role
콘솔] IAM → Roles → Create role
예)
Name: ecsTaskExecutionRole
Trusted entity: AWS service -> Elastic Container Service -> Elastic Container Service Task
Permissions: AmazonECSTaskExecutionRolePolicy (AWS 관리형)
생성 후 ARN 기록
arn:aws:iam::<ACCOUNT_ID>:role/ecsTaskExecutionRole
Task Role
예)
Name: ecsAppTaskRole
Trusted entity: AWS service -> Elastic Container Service -> Elastic Container Service Task
Permission: None (추후에 attach 예정)
Github OIDC 배포 Role
Identity providers
콘솔] IAM → Identity providers → Add provider
Provider type: OpenID Connect
Provider URL: <https://token.actions.githubusercontent.com>
Audience: sts.amazonaws.com
Role 생성
예)
Name: GitHubActionsEcsDeployRole
Trusted entity: Web identity → 위 OIDC 선택
GitHub Organization: 깃헙 소유자 (조직 레포면 org 명, 개인 레포면 사용자명)
생성 후 해당 Role의 Trust policy 조건에 Repo/Branch 제한 추가
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:<YOUR_GH_USER>/<YOUR_GH_REPO>:ref:refs/heads/main"
}
}
}
]
}
해당 Role의 Permissions policy
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EcrGetAuthToken",
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
},
{
"Sid": "EcrPushPull",
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:CompleteLayerUpload",
"ecr:UploadLayerPart",
"ecr:InitiateLayerUpload",
"ecr:PutImage",
"ecr:BatchGetImage"
],
"Resource": "arn:aws:ecr:ap-northeast-2:<ACCOUNT_ID>:repository/plus-app"
},
{
"Sid": "EcsRegisterAndDeploy",
"Effect": "Allow",
"Action": [
"ecs:RegisterTaskDefinition",
"ecs:DescribeTaskDefinition",
"ecs:DescribeServices",
"ecs:UpdateService"
],
"Resource": "*"
},
{
"Sid": "PassOnlyTaskRoles",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": [
"arn:aws:iam::<ACCOUNT_ID>:role/ecsTaskExecutionRole",
"arn:aws:iam::<ACCOUNT_ID>:role/ecsAppTaskRole"
],
"Condition": {
"StringEquals": {
"iam:PassedToService": "ecs-tasks.amazonaws.com"
}
}
}
]
}
ecr:GetAuthorizationToken의 Allow를 “*”로 설정하지 않을 시 아래와 같은 오류 발생함
Error: User: arn:aws:sts::<ACCOUNT_ID>:assumed-role/GitHubActionsEcsDeployRole/GitHubActions is not authorized to perform: ecr:GetAuthorizationToken on resource: * because no identity-based policy allows the ecr:GetAuthorizationToken action
생성 후 role ARN 기록
arn:aws:iam::<ACCOUNT_ID>:role/GitHubActionsEcsDeployRole
ECS 클러스터 생성 (Fargate)
콘솔] ECS → Clusters → Create cluster
예)
Name: plus-ecs-cluster
옵션: 기본값
ECS Task Definition 생성
콘솔] ECS → Task definitions → Create new task definition
예)
Name: plus-task
Launch type: Fargate
Task role: ecsAppTaskRole(없으면 None)
Task execution role: ecsTaskExecutionRole
Task size: CPU 1 vCPU, Memory 2GB
Container 추가:
Name: app
Image: (임시) public.ecr.aws/amazonlinux/amazonlinux:latest (나중에 CI로 덮어씀)
Port mappings: 8080/tcp
Log configuration: awslogs
Log group: /ecs/plus-app
Region: ap-northeast-2
Stream prefix: ecs
Health check:
Command: CMD-SHELL
Value: curl -f <http://localhost:8080/actuator/health> || exit 1
Interval: 15s
Timeout: 5s
Retries: 3
Start period: 30s
Create
Environement variables
Key | Type | 예시 |
---|---|---|
SPRING_PROFILES_ACTIVE | value | prod |
SPRING_DATASOURCE_URL | value | jdbc:mysql://<RDS_ENDPOINT>:3306/<DB_NAME>?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul |
SPRING_DATASOURCE_USERNAME | value | <DB_USERNAME> |
SPRING_DATASOURCE_PASSWORD | valueFrom | arn:aws:secretsmanager:ap-northeast-2:<ACCOUNT_ID>:secret:prod/db/password-<DB_PW_ARN>:SPRING_DATASOURCE_PASSWORD:: |
SPRING_JPA_HIBERNATE_DDL_AUTO | value | update |
SERVER_PORT | value | 8080 |
LOGGING_LEVEL_ROOT | value | INFO |
JAVA_OPTS | value | -XX:+UseContainerSupport -XX:MaxRAMPercentage=75 -Duser.timezone=Asia/Seoul |
JWT_SECRET_KEY | valueFrom | arn:aws:secretsmanager:ap-northeast-2:<ACCOUNT_ID>:secret:prod/app/jwt-<JWT_ARN>:JWT_SECRET_KEY:: |
(S3 사용 시) APP_S3_BUCKET | value | spring-plus-bucket-2025 (본인의 S3 bucket) |
(S3 사용 시) APP_S3_BASE_FOLDER | value | profiles (본인의 S3 폴더) |
(S3 사용 시) APP_S3_PRESIGN_TTL_SECONDS | value | 600 |
비밀 값은 Secrets Manager의 valueFrom으로 주입하는 것이 좋음 (위 표의 DB_PW, JWT_KEY)
Secret 값 추가 방법
ALB + Target Group + ECS Service
S3 사용 시 ecsAppTaskRole에 권한 부여 필요
Spring Boot Dockerfile
: repo root에 Dockerfile
생성
GitHub Actions
RDS VPC 설정
서비스 재배포
그 외 발생 가능한 오류