AWS ML CI/CD와 CDC 파이프라인 구축 (2) AWS 인프라 구성: ECR, S3, IAM, CodeBuild
ECR 리포지토리부터 S3 버킷, IAM 역할, CodeConnections, CodeBuild 프로젝트 3개까지 AWS 콘솔에서 수동으로 구축한 순서와 각 buildspec이 실행하는 내용을 정리합니다.
AWS ML CI/CD와 CDC 파이프라인 구축 프로젝트 시리즈의 2편입니다. 프로젝트 소개는 0편에 있습니다.
이번 편의 범위
2026-07-24에 AWS 콘솔에서 ML CI/CD 파이프라인의 재료가 되는 리소스를 수동으로 만들었습니다. 리전은 ap-northeast-2입니다. 만든 것은 ECR 리포지토리 1개, S3 버킷 1개, IAM 역할 1개, CodeConnections 연결 1개, CodeBuild 프로젝트 3개입니다. 1편에서 로컬 검증까지 마친 코드와 buildspec이 실제로 돌아갈 자리를 만드는 작업입니다. 이 리소스들을 CodePipeline으로 조립하는 과정은 3편에서, 콘솔 수동 구축을 Terraform으로 코드화한 내용은 5편에서 다룹니다.
생성 순서가 중요한 이유
콘솔 작업이라 명령 기록 대신 생성 순서를 기록했습니다. 순서가 중요한 이유는 뒤에 만드는 리소스의 설정에 앞에 만든 리소스의 이름이나 ARN이 들어가기 때문입니다. 이 프로젝트에서의 참조 관계는 다음과 같습니다.
flowchart LR
ECR["ECR<br/>ml-training"] -->|리포지토리 이름| BS["build.yml의 ECR_REPO"]
S3["S3<br/>ml-artifacts-hoseung-2026"] -->|버킷 이름| PY["pipeline.py의 BASE_S3"]
IAM["IAM 역할<br/>sagemaker-execution-role"] -->|역할 ARN| ENV["ml-deploy의 환경변수<br/>SAGEMAKER_EXECUTION_ROLE_ARN"]
CONN["CodeConnections 연결"] -->|연결| CP["CodePipeline<br/>Source 스테이지 (3편)"]
CB["CodeBuild 프로젝트 3개"] -->|프로젝트 이름| CP
그래서 참조를 받는 쪽이 아니라 참조를 당하는 쪽부터, 즉 ECR, S3, IAM 역할, CodeConnections, CodeBuild 순서로 만들었습니다.
1. ECR 리포지토리 ml-training
학습 컨테이너 이미지를 저장하는 리포지토리입니다. 태그 설정은 Mutable, 암호화는 AES-256으로 생성했습니다. 이미지 태그는 git commit SHA를 사용합니다. 어떤 학습이 어떤 코드로 돌았는지 태그만으로 역추적하기 위한 규칙입니다.
리포지토리 이름 ml-training은 build.yml의 환경변수 ECR_REPO와 정확히 일치해야 합니다. buildspec이 이 이름으로 push 주소를 조립하기 때문입니다.
2. S3 버킷 ml-artifacts-hoseung-2026
학습 데이터 분할본과 모델, 평가 결과 같은 산출물을 저장하는 버킷입니다. ACL 비활성, 버저닝 off, 암호화는 SSE-S3로 생성했습니다. 버킷 이름은 SageMaker Pipeline 정의인 pipeline.py의 BASE_S3에 반영했습니다.
3. IAM 역할 sagemaker-execution-role
SageMaker가 학습을 실행하면서 S3와 ECR에 접근할 때 쓰는 권한 묶음입니다. 신뢰 주체를 SageMaker로 두고 관리형 정책 AmazonSageMakerFullAccess와 AmazonS3FullAccess를 부착했습니다. 이 역할의 ARN은 뒤에서 만드는 ml-deploy 프로젝트의 환경변수 SAGEMAKER_EXECUTION_ROLE_ARN으로 들어갑니다.
4. CodeConnections GitHub 연결
AWS가 GitHub 저장소를 읽어가는 인증 통로입니다. 저장소에 GitHub 앱을 설치하면 AWS 쪽에 연결 리소스가 생기는 구조라 인증 정보를 직접 다루지 않습니다. 앱 권한은 전체 계정이 아니라 이 프로젝트 저장소 1개에만 부여했습니다.
5. CodeBuild 프로젝트 3개
CodeBuild는 buildspec에 적힌 명령을 실행하는 일회용 실행 환경입니다. 실행할 명령(buildspec)은 저장소에 있고, 어떤 buildspec을 어떤 환경에서 실행할지가 CodeBuild 프로젝트 설정입니다. 3개를 만들었습니다.
| 프로젝트 | buildspec | 추가 설정 |
|---|---|---|
ml-test | ml-cicd/buildspec/test.yml | 없음 |
ml-build | ml-cicd/buildspec/build.yml | Privileged 모드 체크, 서비스 역할에 AmazonEC2ContainerRegistryPowerUser 부착 |
ml-deploy | ml-cicd/buildspec/deploy.yml | 환경변수 SAGEMAKER_EXECUTION_ROLE_ARN, 서비스 역할에 AmazonSageMakerFullAccess와 AmazonS3FullAccess 부착 |
ml-build에 Privileged 모드가 필요한 이유는 buildspec이 docker 명령을 실행하기 때문입니다. CodeBuild 기본 역할에는 ECR push 권한도 SageMaker 호출 권한도 없어서, 각 단계가 쓰는 권한만 해당 프로젝트의 서비스 역할에 부착했습니다. build 단계는 ECR, deploy 단계는 SageMaker와 S3입니다.
buildspec 3개가 실행하는 것
test.yml: lint와 단위 테스트
Python 3.12 런타임에서 uv를 설치한 뒤 다음을 실행합니다.
1
2
3
4
cd ml-cicd
uv sync --frozen
uv run ruff check src tests
uv run pytest tests/unit --junitxml=../reports/unit.xml
uv sync --frozen으로 lockfile 그대로 환경을 재조립하므로 로컬, CI, 컨테이너가 같은 uv.lock을 읽습니다. pytest 결과는 JUnit XML로 남겨 CodeBuild의 reports 기능에 등록합니다. 여기서 실패하면 CodePipeline이 이후 단계를 실행하지 않습니다.
build.yml: 이미지 빌드와 ECR push
pre_build에서 push 주소를 조립하고 ECR에 로그인합니다.
1
2
3
4
5
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGISTRY=${ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com
COMMIT_SHA=${CODEBUILD_RESOLVED_SOURCE_VERSION}
IMAGE_URI=${REGISTRY}/${ECR_REPO}:${COMMIT_SHA}
aws ecr get-login-password | docker login --username AWS --password-stdin ${REGISTRY}
build 단계에서 train.Dockerfile로 이미지를 빌드해 push합니다.
1
2
docker build -t ${IMAGE_URI} -f ml-cicd/docker/train.Dockerfile ml-cicd
docker push ${IMAGE_URI}
이 buildspec에는 두 가지 전달 장치가 있습니다. IMAGE_URI를 exported-variables로 내보내 Deploy 단계가 파이프라인 파라미터로 쓸 수 있게 하고, artifacts로 전체 파일을 넘겨 파이프라인 정의 코드가 Deploy 단계에 도착하게 합니다. 내보낸 변수가 실제로 Deploy까지 전달되게 하는 배선은 CodePipeline 정의에서 해야 하는데, 이 내용은 3편에서 다룹니다.
deploy.yml: SageMaker Pipeline upsert와 실행 시작
sagemaker SDK와 boto3를 설치한 뒤 파이프라인 정의 스크립트를 호출합니다. sagemaker SDK는 배포 도구라서 프로젝트 의존성(uv.lock)에 넣지 않고 이 단계에서만 설치합니다.
1
2
3
4
5
6
python ml-cicd/pipelines/pipeline.py \
--pipeline-name ${PIPELINE_NAME} \
--role-arn ${SAGEMAKER_EXECUTION_ROLE_ARN} \
--image-uri ${IMAGE_URI} \
--commit-sha ${CODEBUILD_RESOLVED_SOURCE_VERSION} \
--upsert --start
파이프라인 이름은 환경변수 PIPELINE_NAME(ml-train-pipeline)으로 정의되어 있습니다. 이 단계는 SageMaker Pipeline을 upsert하고 실행을 시작하는 것까지만 책임집니다. 학습 완료를 기다리지 않는 비동기 핸드오프 구조라서, 이후 흐름은 SageMaker Pipeline이 이어받습니다.
배운 것: 과금은 서버가 떠 있는 시간 기준
이날 콘솔 작업을 하면서 확인한 AWS 과금 구조입니다. 과금 기준은 API 호출 횟수가 아니라 서버가 떠 있는 시간입니다. IAM, 빈 버킷, CodeBuild 프로젝트 같은 설정류 리소스는 존재만으로는 0원입니다. 이번 편에서 만든 리소스의 과금 기준은 다음과 같습니다.
| 서비스 | 과금 |
|---|---|
| IAM | 무료 |
| CodeConnections | 무료 |
| CodeBuild | 분당 과금, 월 100분 무료 |
| ECR | 저장 GB당 월 $0.10 |
| S3 | 저장 GB당 월 $0.023 |
CodeBuild는 buildspec 실행 시점에만 일회용 실행 환경이 떠서 분 단위로 과금되고, 프로젝트 정의 자체는 무료입니다. ECR과 S3는 저장한 용량만큼만 과금됩니다. 이 설계에서 유의미한 비용 지점은 SageMaker 학습 인스턴스뿐이고, 그 앞에 수동 게이트를 두는 이야기는 3편에서 다룹니다.