기준: DOP-C02 시험 가이드오류 제보
이번 주를 관통하는 하나의 질문은 "빌드 환경을 얼마나 코드처럼 다루는가"였다. buildspec.yml은 빌드 절차를, 캐시 전략은 빌드 속도를, secrets 설정은 보안 경계를, VPC 모드는 네트워크 경계를 코드로 표현한다. ARM 빌드까지 더하면 아키텍처 선택도 빌드 파이프라인 안으로 들어온다.
DOP-C02 시험은 이 요소들을 따로 묻지 않는다. "이 회사에 이런 제약이 있을 때 어떤 구성이 맞는가"라는 통합 형태로 묻는다. 오늘은 그 통합 판단력을 연습한다. 먼저 핵심 비교 구도를 한눈에 정리한 다음, 함정 패턴을 짚고, 시나리오 12문항으로 판단력을 검증한다.
주간 복습이 단순한 요약이 되면 안 된다. "왜 A이고 왜 B가 아닌지"를 스스로 설명할 수 있는 수준이 목표다. 설명을 못하면 시험장에서 90%가 맞아 보이는 보기 두 개 앞에서 멈춘다.
복습의 목적: Week 3 각 Day의 내용은 독립적으로 배웠지만, 실제 문제는 이 지식이 섞인다. "VPC 모드 빌드에서 Secrets Manager를 쓰는데 네트워크 에러가 난다" 같은 문제는 VPC + Secrets Manager + 엔드포인트를 동시에 알아야 풀린다. 오늘은 그 연결을 만드는 날이다.
buildspec.yml의 4단계(install → pre_build → build → post_build)는 순서에 의미가 있다. install은 런타임 자체를 준비하고, pre_build는 로그인·환경 확인, build는 실제 컴파일·테스트, post_build는 이미지 push·태그·알림을 담당한다. finally 블록은 어느 단계에서든 실패해도 실행된다 — 정리 작업(임시 파일 삭제, 실패 알림)에 쓴다.
version: 0.2
phases:
install:
runtime-versions:
java: corretto17
commands:
- echo "도구 설치"
pre_build:
commands:
- aws ecr get-login-password | docker login --username AWS --password-stdin $ECR_URI
build:
commands:
- mvn test
- docker build -t $IMAGE_TAG .
post_build:
commands:
- docker push $IMAGE_TAG
finally:
- echo "빌드 종료 (성공/실패 무관)"
env:
secrets-manager:
DB_PASS: prod/myapp:db_password
exported-variables:
- IMAGE_TAG
reports:
JUnitResults:
files:
- '**/surefire-reports/*.xml'
file-format: JUNITXML
cache:
paths:
- '/root/.m2/**/*'
exported-variables는 CodePipeline과 통합 시 중요하다. CodeBuild가 생성한 변수(예:IMAGE_TAG)를 다음 Action이 참조할 수 있게 한다. ECS Blue/Green 파이프라인에서 CodeBuild가 이미지 URI를exported-variables로 내보내고, Deploy Action이 이를 받아 Task Definition에 주입하는 패턴이 시험에 나온다.
finally블록은 프로그래밍 언어의try-finally패턴을 빌드 단계에 적용한 것이다. 실패 시에도 반드시 실행되어야 하는 "정리 작업"을finally에 두는 것은 Resource Acquisition Is Initialization(RAII) 원칙과 맥락이 같다 — 자원(로그, 임시 파일, 외부 서비스 상태)은 사용 후 반드시 정리되어야 한다.
캐시는 "어디에 저장하는가"와 "무엇을 캐시하는가"로 나뉜다.
| 캐시 유형 | 저장 위치 | 호스트 이동 시 | 적합한 대상 | 주의점 |
|---|---|---|---|---|
| Local Source | 빌드 호스트 디스크 | 미스 | Git 소스 클론 시간 절감 | 호스트 고정 환경에서만 효과 |
| Local Docker Layer | 빌드 호스트 Docker | 미스 | Dockerfile 레이어 재사용 | privilegedMode: true 필수 |
| S3 Custom | S3 버킷 | 히트 | node_modules, .m2, .gradle | 오브젝트 크기 주의 (500MB+) |
| BuildKit + ECR Registry | ECR | 히트 | Docker 레이어 (npm install 포함) | ECR Pull Through Cache 조합 |
# S3 캐시 예시
cache:
type: S3
location: my-codebuild-cache/java-build
paths:
- '/root/.m2/**/*'
- '/root/.gradle/caches/**/*'# Local Docker Layer 캐시 (privilegedMode 필요)
cache:
type: LOCAL
modes:
- LOCAL_DOCKER_LAYER_CACHE
- LOCAL_SOURCE_CACHE⚠️ 함정:
node_modules를 S3 캐시로 올릴 때 디렉터리가 500MB 이상이면 S3 업로드/다운로드 오버헤드가npm install시간보다 길어지는 역효과가 난다. 이 경우 Dockerfile에npm install레이어를 분리하고 BuildKit + ECR Registry Cache를 쓰는 것이 더 효율적이다.
대형 Java 프로젝트(Maven 의존성 3GB+)에서 S3 캐시를 적용했더니 오히려 빌드가 느려진 사례가 있다. 원인은 3GB S3 다운로드 시간(2분)이 Maven 의존성 해결(40초)보다 길었기 때문이다. 해결책은 S3 캐시 경로를
~/.m2/repository/com/mycompany/**/*처럼 내부 아티팩트만 포함하도록 좁히는 것이었다.
비밀값인가?
├─ 자동 회전 필요?
│ └─ YES → Secrets Manager ($0.40/시크릿/월)
│ ├─ RDS 자격 증명: RDS Rotation Lambda 자동 생성
│ ├─ 커스텀 값: 직접 Rotation Lambda 작성
│ └─ buildspec: env.secrets-manager
└─ 단순 설정값, 회전 불필요?
├─ 무료면 충분? → Parameter Store Standard (무료)
│ └─ buildspec: env.parameter-store
└─ TPS > 40 필요? → Parameter Store Advanced ($0.05/1만 API 호출)
Cross-account 시나리오: 다른 계정의 Secrets Manager에 있는 비밀을 CodeBuild가 읽으려면
secretsmanager:GetSecretValue + kms:Decrypt 권한Secrets Manager의 자동 회전은 Rotation Lambda가 실제 교체를 수행하는 구조다. Lambda가 새 비밀을 생성하고(
createSecret) → DB에 적용하고(setSecret) → 검증하고(testSecret) → 활성화(finishSecret)하는 4단계 프로토콜이다. 이 프로토콜 이해가 "회전 중 서비스 다운타임이 없는 이유"를 설명한다 —AWSPENDING라벨의 새 버전이 준비되고 검증된 후에야AWSCURRENT로 승격된다.
Parameter Store의 40 TPS 제한은 대규모 Lambda 환경에서 실제 병목이 된다. 콜드 스타트 시 수백 개 Lambda가 동시에 Parameter Store에서 설정을 읽으면
ThrottlingException이 발생한다. 해결 방법: (1) Parameter Store Advanced로 업그레이드(TPS 한도 증가), (2) Lambda 초기화 시 설정을 메모리에 캐시(같은 실행 환경은 재사용), (3) Secrets Manager(더 높은 TPS 한도) 사용.
VPC 모드 CodeBuild는 일반 인터넷에 직접 접근할 수 없다. 외부 서비스마다 적절한 경로가 필요하다.
[CodeBuild Container in Private Subnet]
│
├─ S3 (artifact, cache) → S3 Gateway Endpoint (무료, 라우팅 테이블)
├─ Secrets Manager → Interface Endpoint ($0.01/시간)
├─ KMS → Interface Endpoint ($0.01/시간)
├─ ECR API → Interface Endpoint ($0.01/시간)
├─ ECR DKR (Docker 레이어) → Interface Endpoint ($0.01/시간)
├─ CloudWatch Logs → Interface Endpoint 또는 NAT Gateway
└─ 외부 인터넷 (npm, Maven Central 등) → NAT Gateway 필수
⚠️ 함정: "VPC 모드 켰더니 S3 권한 에러가 난다"의 원인은 거의 S3 Gateway Endpoint 미설정이다. Gateway Endpoint는 라우팅 테이블에 자동 추가되지 않는다 — 직접 VPC 설정에서 연결하고, 빌드 서브넷의 라우팅 테이블에 포함해야 한다. Interface Endpoint와 달리 무료이므로 VPC 빌드에서는 항상 설정한다.
한 팀이 VPC 모드로 CodeBuild를 마이그레이션한 후 빌드 비용이 월 $200 증가했다. 원인을 추적하니 NAT Gateway를 통해 S3에 접근하는 데이터 전송 비용이었다. S3 Gateway Endpoint를 추가하자 비용이 $0으로 줄었다. ECR Interface Endpoint도 추가해 Docker 레이어 pull 비용도 절감했다. VPC 모드는 네트워크 경로 최적화가 비용에 직결된다.
| 항목 | AWS Managed Image | Custom Image (ECR) |
|---|---|---|
| 유지보수 | AWS가 패치 | 팀이 직접 빌드·푸시 |
| 빌드 시작 속도 | 빠름 (사전 준비) | 느릴 수 있음 (이미지 pull) |
| 도구 커스터마이징 | 제한적 | 완전 자유 |
imagePullCredentialsType | CODEBUILD | SERVICE_ROLE |
privilegedMode | 불필요 (보통) | Docker 빌드 시 필요 |
| 비용 | 없음 | ECR 저장 + 전송 |
Custom Image의 imagePullCredentialsType: SERVICE_ROLE은 CodeBuild Service Role이 ECR에서 이미지를 pull하는 권한을 쓴다는 뜻이다. 이 Role에 ecr:GetDownloadUrlForLayer, ecr:BatchGetImage, ecr:GetAuthorizationToken 권한이 필요하다.
Custom Image를 쓰는 핵심 이유는 빌드 재현성(Reproducibility)이다. 팀이 직접 관리하는 이미지는 "언제 빌드해도 같은 도구 버전으로 같은 결과"를 보장한다. AWS Managed Image는 AWS가 업데이트하면 빌드 결과가 달라질 수 있다 (Python 3.11.1 → 3.11.4 패치 등). 금융·의료 같이 감사 추적이 필요한 환경에서는 Custom Image + 이미지 다이제스트(태그 아닌 SHA256) 지정이 표준이다.
| Batch 유형 | 구조 | 적합한 케이스 |
|---|---|---|
build-list | 독립 빌드 병렬 실행 | 멀티 아키텍처 (x86/ARM), 멀티 서비스 |
build-matrix | 변수 조합의 모든 경우 | 멀티 런타임 × 멀티 OS 조합 |
build-graph | 의존 관계 있는 DAG | 공통 빌드 → 의존 빌드 순서 보장 |
# build-matrix 예시: Python 3.9/3.10/3.11 × Linux/ARM
batch:
build-matrix:
static:
ignore-failure: false
dynamic:
buildspec:
- buildspec_linux.yml
- buildspec_arm.yml
env:
variables:
PYTHON_VERSION:
- "3.9"
- "3.10"
- "3.11"이 설정은 2 × 3 = 6개 빌드를 병렬로 실행한다.
build-graph는 DAG(Directed Acyclic Graph) 의존성을 지원한다. "공통 라이브러리를 먼저 빌드하고, 성공하면 그 아티팩트를 사용하는 서비스 A/B/C를 병렬 빌드"하는 패턴이다. 모노레포에서 특히 유용하다.depend-on필드로 의존성을 선언하면 CodeBuild가 토폴로지 정렬을 자동으로 수행한다.
| 항목 | On-Demand | Reserved Capacity Fleet |
|---|---|---|
| 과금 방식 | 빌드 시간(분) 단위 | Fleet 유지 시간 (빌드 여부 무관) |
| Provisioning 대기 | 10~30초 | 거의 0초 |
| 적합 환경 | 산발적 빌드 | 연속적·고빈도 빌드 |
| 최소 Fleet 크기 | N/A | 1대 |
손익분기점 계산 예:
Fleet의 경제성은 고정비 vs 변동비 선택이다. 수요가 예측 가능하고 지속적이면 고정비(Fleet)가 유리하고, 수요가 불규칙하면 변동비(On-Demand)가 유리하다. AWS의 Reserved Instance와 동일한 논리다. "하루 중 특정 시간에만 집중"하는 빌드 패턴이라면 Fleet 크기를 시간대별로 달리 설정하거나, Auto Scaling이 지원되는 Fleet을 고려한다.
함정 1: "빠르게 하려면 Compute Type을 올려라"
항상 틀리다. I/O 바운드 빌드(패키지 다운로드, S3 업로드, ECR push)는 CPU를 올려도 속도가 안 변한다. CloudWatch의 BuildDuration과 각 페이즈 시간을 먼저 측정하고, 병목 페이즈에 맞는 수단(캐시, 병렬, 네트워크 경로)을 쓴다.
함정 2: "시크릿은 env.variables에 KMS로 암호화하면 된다"
env.variables는 buildspec.yml에 평문으로 적히고 빌드 로그에 노출된다. KMS 암호화 여부와 무관하게, 값을 buildspec에 직접 쓰면 누출 위험이 있다. 반드시 env.secrets-manager 또는 env.parameter-store를 쓴다.
함정 3: "Local Cache를 쓰면 항상 빠르다" 빌드 호스트가 바뀌면 캐시 미스. S3 Cache가 더 안정적이다. 단, S3 Cache도 오브젝트가 너무 크면 다운로드 오버헤드가 역효과를 낸다.
함정 4: "ARM 빌드는 QEMU로 해도 된다" 동작은 하지만 느리고, native code(C/C++/Rust) 컴파일에서 에뮬레이션 불안정 문제가 생길 수 있다. ARM_CONTAINER + native 빌드가 표준이다.
함정 5: "VPC 모드면 모든 AWS 서비스에 자동 접근된다" 틀리다. VPC 모드는 빌드 컨테이너를 VPC에 붙이는 것뿐이다. 각 AWS 서비스에 접근하려면 VPC Endpoint(Gateway 또는 Interface)를 별도 설정해야 한다. 인터넷이 필요하면 NAT Gateway도 필요하다.
함정 6: "Secrets Manager는 항상 Parameter Store보다 낫다" 비용이 다르다. 회전 불필요한 단순 설정값 95개에 Secrets Manager를 쓰면 월 $38이지만, Parameter Store Standard를 쓰면 $0이다. 요구사항(회전 필요 여부)에 따라 도구를 선택한다.
함정 종합: 이 함정들의 공통 패턴은 "요구사항을 보지 않고 도구 이름으로 답을 고른다"는 것이다. 시험 문제에서 요구사항 키워드를 먼저 찾는 습관이 핵심이다. "자동 회전" → Secrets Manager, "호스트 고정" → Local Cache, "ARM 워크로드" → ARM_CONTAINER, "Private 리소스" → VPC 모드 + 엔드포인트.
🎯 시나리오: 한 팀이 "빌드가 느리다"고 보고했다. CloudWatch를 보니 전체 빌드 8분 중
DOWNLOAD_SOURCE2분 +PRE_BUILD(npm install) 4분 + 실제BUILD2분이었다. 어디부터 최적화할까? 정답은 PRE_BUILD(npm install)가 최대 병목이므로 S3 Cache 또는 BuildKit + ECR Cache 적용이 첫 번째다.DOWNLOAD_SOURCE는 Local Source Cache로 줄일 수 있지만 빌드 호스트가 바뀌면 효과가 없으므로 Git shallow clone 설정도 고려한다. Compute Type 업그레이드는 이 케이스에서 효과 없다.
한 주 동안 배운 내용을 하나의 흐름으로 연결하면:
buildspec.yml → 빌드 절차를 코드로 표현. 각 페이즈는 명확한 역할이 있고, finally는 정리 작업이다.
캐시 → 빌드 속도의 실질적 개선. 호스트 고정 여부와 오브젝트 크기가 전략을 결정한다.
Secrets → 비밀값은 반드시 env.secrets-manager 또는 env.parameter-store로. 회전 필요 여부가 Secrets Manager/Parameter Store를 가른다.
VPC 모드 → Private 리소스 접근의 유일한 방법. 엔드포인트 설계와 서브넷 크기가 운영 품질을 결정한다.
Build Batch → 병렬화 전략. 독립 빌드는 build-list, 조합은 build-matrix, 의존성은 build-graph.
Custom Image → 재현성이 중요한 환경에서 AWS Managed Image 대신. SERVICE_ROLE 자격 증명 유형이 필수.
Reserved Fleet → 고빈도 빌드에서 Provisioning 오버헤드 제거. 고정비와 변동비의 손익분기점 계산이 도입 결정을 만든다.
Week 3 전체 사례: 어떤 회사가 모노레포에서 10개 마이크로서비스를 빌드한다. 각 서비스는 x86과 ARM 이미지가 필요하고(20개 빌드), DB 마이그레이션 SQL도 실행해야 하며, 모든 비밀번호는 30일마다 회전한다. 네트워크는 Private VPC 안에 있다. 이 시나리오에서의 정답 조합: Build Batch build-list(20개 병렬) + VPC 모드(Private DB 접근) + S3 Gateway Endpoint(artifact 업로드) + Secrets Manager with Rotation(DB 비밀번호) + ARM_CONTAINER + LINUX_CONTAINER 혼합(멀티 아키텍처) + Reserved Fleet(하루 수십 번 빌드) + ECR Pull Through Cache(Docker Hub Rate Limit 방지).
선택지를 클릭하면 정답·해설이 펼쳐집니다.
문제 1
npm install이 매 빌드 4분을 차지한다. 빌드 호스트는 매번 다를 수 있다. 가장 효과적인 개선은?
문제 2
빌드가 Private 서브넷의 Aurora PostgreSQL에 마이그레이션 SQL을 실행해야 한다. 가장 적절한 구성은?
문제 3
DB 비밀번호가 30일마다 자동 회전된다. 빌드가 항상 현재 유효한 비밀번호로 DB 마이그레이션을 실행해야 한다. 가장 적절한 구성은?
문제 4
Docker Hub Rate Limit으로 매일 오전 빌드가 실패한다. 장기적으로 가장 효과적인 해결은?
문제 5
Java와 Python 서비스를 동시에 빌드하고, 각각 ARM/x86 이미지를 만들어야 한다. 총 4개 빌드 조합이 필요하다. 가장 효율적인 구성은?
문제 6
빌드당 Provisioning 시간이 30초이고 하루 300번 빌드한다. 월 150시간이 Provisioning 낭비다. 가장 직접적인 해결은?
문제 7
100개 시크릿 중 회전 필요한 것은 5개다. 월 비용을 최소화하는 구성은?
문제 8
VPC 빌드를 시작하니 The maximum number of network interfaces has been reached for subnet 에러가 난다. 가장 적절한 대응은?
문제 9
빌드 컨테이너에서 실패를 디버깅하려고 내부에 직접 접속이 필요하다. 가장 적절한 방법은?
문제 10
JUnit 테스트 결과를 CodeBuild 콘솔에서 시각화하고, 실패 시 SNS 알림을 보내려면?
문제 11
멀티 계정 환경에서 CodeBuild가 Account B의 Secrets Manager 시크릿을 읽으려 한다. Account A에 CodeBuild가 있다. 필요한 구성 두 가지는?
문제 12
CodeBuild 빌드에서 SBOM(Software Bill of Materials)을 생성해 공급망 보안을 강화하려 한다. 가장 적합한 구현 방식은?