2026-05-28 — ECS 클러스터를 삭제하지 않고 사용 차단하기 (3단계)¶
한 줄 요약
ECS 클러스터를 물리적으로 삭제하지 않은 채 누구도 다시 사용할 수 없도록 잠그는 절차. 워크로드 중단(Level 1) → 인프라 잠금(Level 2) → IAM 정책 차단 (Level 3)의 3단으로 강도 조절.
- 대상 클러스터:
compositet3-prod-240531-cluster - 계정:
790590917886(reco-kyle) - 리전:
us-east-1 - 작업일: 2026-05-28
1. 배경/목적¶
해당 클러스터는 워크로드가 거의 종료되었으나, 운영 기록·태그·태스크 정의 등을 유지하기 위해 삭제는 하지 않기로 결정되었다. 동시에 누가 실수로(또는 의도적으로) 다시 태스크를 띄우는 일도 막아야 했다. → "사용 안 됨"을 강제하기 위한 다층 방어.
클러스터의 구성 요소¶
이 클러스터가 "사용"될 수 있는 모든 경로를 막아야 한다.
flowchart LR
user[사람/CI] -->|UpdateService| svc[Service]
user -->|RunTask| task[Task]
svc --> task
task --> cp[Capacity Provider]
cp --> asg[Auto Scaling Group]
asg --> ec2[EC2 instances]
→ 막아야 할 layer: Service → Task / Capacity Provider → ASG / IAM 호출 자체.
2. 절차¶
Level 1 — 워크로드 즉시 중단 (되돌리기 쉬움)¶
모든 서비스를 desiredCount=0으로 만들고, 진행 중인 태스크를 강제 종료.
PROFILE=reco-kyle; REGION=us-east-1
CLUSTER=compositet3-prod-240531-cluster
# 1) 활성 서비스 desiredCount=0
aws ecs update-service --profile $PROFILE --region $REGION \
--cluster $CLUSTER --service compositet3-prod-240531-ec2-spot \
--desired-count 0
# (desired=0이 아닌 서비스가 더 있으면 모두 같은 방식으로 0으로 만들 것)
# 2) 현재 PROVISIONING/RUNNING 중인 태스크 식별
aws ecs list-tasks --profile $PROFILE --region $REGION --cluster $CLUSTER
# 3) 태스크 강제 stop (위 명령으로 얻은 taskId)
aws ecs stop-task --profile $PROFILE --region $REGION \
--cluster $CLUSTER --task <TASK_ID> \
--reason "Manual lockdown: cluster being decommissioned (no-delete)"
→ 약점: 누가 desiredCount를 다시 올리면 다시 동작. ASG도 그대로라 EC2가 다시
떠 버린다. 그래서 Level 2가 필요.
Level 2 — 인프라 잠금¶
ASG의 min/max/desired를 모두 0으로 고정해 EC2 인스턴스 자체가 못 뜨도록
하고, Capacity Provider의 managed scaling도 비활성화한다.
PROFILE=reco-kyle; REGION=us-east-1
# ASG min=max=desired=0
for ASG in compositet3-prod-240531-ondemand-autoscaling-group \
compositet3-prod-240531-spot-autoscaling-group; do
aws autoscaling update-auto-scaling-group --profile $PROFILE --region $REGION \
--auto-scaling-group-name "$ASG" \
--min-size 0 --max-size 0 --desired-capacity 0
done
# Capacity Provider의 managed scaling/termination 둘 다 DISABLED
# (둘 중 하나만 DISABLED 시도하면 ClientException: "Managed scaling can't be
# disabled if managed termination is enabled." 발생 — 반드시 함께 꺼야 함)
for CP in compositet3-prod-240531-ondemand-asg-capacity-provider \
compositet3-prod-240531-spot-asg-capacity-provider; do
aws ecs update-capacity-provider --profile $PROFILE --region $REGION \
--name "$CP" \
--auto-scaling-group-provider '{"managedScaling":{"status":"DISABLED"},"managedTerminationProtection":"DISABLED"}'
done
update-capacity-provider는 비동기
updateStatus가 UPDATE_IN_PROGRESS로 바뀐 뒤 잠시(보통 10–30초) 후
UPDATE_COMPLETE가 된다. 같은 CP에 연속 호출하면 두 번째 호출이
UPDATE_FAILED가 되니, 한 호출의 UPDATE_COMPLETE를 확인한 뒤 다음 호출을
해야 한다.
Level 3 — IAM 정책으로 차단 (가장 강함)¶
해당 클러스터를 대상으로 한 호출 자체를 Deny로 막는다.
PROFILE=oldplicar-kyle # ← IAM은 보통 별도 권한이 필요
ACCOUNT_ID=790590917886
CLUSTER_ARN="arn:aws:ecs:us-east-1:${ACCOUNT_ID}:cluster/compositet3-prod-240531-cluster"
cat > /tmp/deny-ecs-cluster.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyRunTaskOnCluster",
"Effect": "Deny",
"Action": ["ecs:RunTask", "ecs:StartTask"],
"Resource": "*",
"Condition": {"ArnEquals": {"ecs:cluster": "${CLUSTER_ARN}"}}
},
{
"Sid": "DenyServiceMutationsOnCluster",
"Effect": "Deny",
"Action": [
"ecs:CreateService", "ecs:UpdateService",
"ecs:CreateTaskSet", "ecs:UpdateTaskSet",
"ecs:UpdateServicePrimaryTaskSet"
],
"Resource": "*",
"Condition": {"ArnEquals": {"ecs:cluster": "${CLUSTER_ARN}"}}
},
{
"Sid": "DenyClusterCapacityChanges",
"Effect": "Deny",
"Action": [
"ecs:PutClusterCapacityProviders",
"ecs:UpdateCluster",
"ecs:UpdateCapacityProvider"
],
"Resource": [
"${CLUSTER_ARN}",
"arn:aws:ecs:us-east-1:${ACCOUNT_ID}:capacity-provider/compositet3-prod-240531-ondemand-asg-capacity-provider",
"arn:aws:ecs:us-east-1:${ACCOUNT_ID}:capacity-provider/compositet3-prod-240531-spot-asg-capacity-provider"
]
}
]
}
EOF
aws iam create-policy --profile $PROFILE \
--policy-name DenyEcsCluster-compositet3-prod-240531 \
--description "Deny RunTask/StartTask/CreateService/UpdateService and capacity changes on the decommissioned cluster." \
--policy-document file:///tmp/deny-ecs-cluster.json
생성 후 적용 대상에 attach:
# 사용자/역할/그룹에 attach
aws iam attach-user-policy --user-name <user> --policy-arn <POLICY_ARN>
aws iam attach-role-policy --role-name <role> --policy-arn <POLICY_ARN>
aws iam attach-group-policy --group-name <grp> --policy-arn <POLICY_ARN>
본인 권한에 영향을 줄 수 있다
Deny 정책은 명시적 Allow를 이기므로, 자기 자신에게 attach하면 본인도 잠긴다. Organization SCP로 적용할 때는 루트/관리자 계정/긴급 복구 역할은 반드시 제외되도록 조건을 걸 것.
3. 검증¶
PROFILE=reco-kyle; REGION=us-east-1
CLUSTER=compositet3-prod-240531-cluster
# 클러스터: 태스크 0, 인스턴스 0
aws ecs describe-clusters --profile $PROFILE --region $REGION --clusters $CLUSTER \
--query 'clusters[0].{runningTasks:runningTasksCount,pendingTasks:pendingTasksCount,activeServices:activeServicesCount,containerInstances:registeredContainerInstancesCount}'
# 모든 서비스 desired=0
for svc in compositet3-prod-240531-ec2-spot compositet3-prod-240531-external compositet3-prod-240531-ec2-ondemand; do
aws ecs describe-services --profile $PROFILE --region $REGION --cluster $CLUSTER --services "$svc" \
--query 'services[0].{name:serviceName,desired:desiredCount,running:runningCount}'
done
# ASG: min=max=desired=0
aws autoscaling describe-auto-scaling-groups --profile $PROFILE --region $REGION \
--auto-scaling-group-names \
compositet3-prod-240531-ondemand-autoscaling-group \
compositet3-prod-240531-spot-autoscaling-group \
--query 'AutoScalingGroups[].{name:AutoScalingGroupName,min:MinSize,max:MaxSize,desired:DesiredCapacity,instances:length(Instances)}'
# Capacity Provider: managed scaling/termination DISABLED, updateStatus=UPDATE_COMPLETE
aws ecs describe-capacity-providers --profile $PROFILE --region $REGION \
--capacity-providers \
compositet3-prod-240531-ondemand-asg-capacity-provider \
compositet3-prod-240531-spot-asg-capacity-provider \
--query 'capacityProviders[].{name:name,managedScaling:autoScalingGroupProvider.managedScaling.status,managedTermination:autoScalingGroupProvider.managedTerminationProtection,updateStatus:updateStatus}'
성공 기준:
runningTasks,pendingTasks,containerInstances모두 0- 모든 서비스의
desiredCount/runningCount모두 0 - 두 ASG 모두
min=max=desired=0,instances=0 - 두 Capacity Provider 모두
managedScaling=DISABLED,managedTerminationProtection=DISABLED,updateStatus=UPDATE_COMPLETE - IAM 정책 attach 후, 차단된 사용자/역할로
RunTask/UpdateService시도 시AccessDeniedException발생
4. 함정과 노하우¶
update-capacity-provider응답에 표시되는 값은 갱신 전 값일 수 있다. 실제 적용 여부는describe-capacity-providers의updateStatus로 확인할 것.- managed scaling만 끄려고 하면 실패한다. managed termination이 ENABLED인 상태에서는 managed scaling을 DISABLED로 못 바꾼다. 함께 DISABLED로 변경할 것.
- ASG가
min=0,max=0이어도 누가update-service로 desired를 올리면 ECS는 태스크 placement를 시도한다. 인스턴스가 없으니 결국 실패하지만, 에러 이벤트가 쌓이고 알람을 만들 수 있다. 그래서 IAM Deny까지 가는 게 가장 안전. - CodePipeline/EventBridge로 트리거되는 RunTask가 있는지 별도 확인 필요. 이번 케이스에는 없었지만 일반적으로 체크할 것.
5. 롤백¶
각 레벨의 역순으로 풀면 된다.
# Level 3: IAM Deny 정책 detach + 삭제
aws iam detach-user-policy --user-name <user> --policy-arn <POLICY_ARN>
aws iam delete-policy --policy-arn <POLICY_ARN>
# Level 2: Capacity Provider 재활성화
for CP in compositet3-prod-240531-ondemand-asg-capacity-provider \
compositet3-prod-240531-spot-asg-capacity-provider; do
aws ecs update-capacity-provider --profile reco-kyle --region us-east-1 \
--name "$CP" \
--auto-scaling-group-provider '{"managedScaling":{"status":"ENABLED","targetCapacity":100},"managedTerminationProtection":"ENABLED"}'
done
# ASG 용량 복구 (원래 값으로 — 사전에 메모해둘 것)
for ASG in compositet3-prod-240531-ondemand-autoscaling-group \
compositet3-prod-240531-spot-autoscaling-group; do
aws autoscaling update-auto-scaling-group --profile reco-kyle --region us-east-1 \
--auto-scaling-group-name "$ASG" \
--min-size <ORIG_MIN> --max-size <ORIG_MAX> --desired-capacity <ORIG_DESIRED>
done
# Level 1: 서비스 desiredCount 복구
aws ecs update-service --profile reco-kyle --region us-east-1 \
--cluster compositet3-prod-240531-cluster \
--service compositet3-prod-240531-ec2-spot \
--desired-count <ORIG_DESIRED>
원래 값 기록
Level 2를 적용하기 전에 ASG/CP의 원래 값을 describe-auto-scaling-groups,
describe-capacity-providers로 떠서 별도 위치(예: 이 글의 코멘트, Linear
티켓 등)에 저장해두면 롤백이 쉽다.