Skip to content

Latest commit

 

History

History
341 lines (255 loc) · 13 KB

File metadata and controls

341 lines (255 loc) · 13 KB

Bakery

멀티 아키텍처(amd64, arm64) 컨테이너 이미지를 온디맨드로 빌드하는 분산 빌드 시스템입니다. 빌드 요청 시에만 AWS ECS Fargate나 Kubernetes에서 빌드 컨테이너를 실행하고, 완료 후 자동으로 정리합니다.

프로젝트 배경

CI/CD 환경에서 컨테이너 이미지를 빌드할 때, 언제 빌드가 트리거될지 모르는 상황에서 전용 빌드 인스턴스를 상시 띄워놓는 것은 비용 낭비입니다. 또한 EKS Fargate는 arm64를 지원하지 않아, 멀티 아키텍처 빌드 시 QEMU 에뮬레이션에 의존하게 되는데 이는 느리고 불안정합니다.

이 프로젝트는 다음 문제를 해결하기 위해 시작되었습니다:

  • 온디맨드 빌드: 빌드 요청이 들어올 때만 ECS Fargate나 K8s에서 빌드 컨테이너를 실행합니다. 상시 인스턴스가 필요 없어 비용 효율적입니다.
  • 네이티브 멀티 아키텍처 빌드: amd64와 arm64 이미지를 각각의 네이티브 환경에서 병렬로 빌드합니다. QEMU 에뮬레이션 없이 빠르고 안정적입니다.
  • 빌드 인프라 분리: 빌드 작업을 ECS나 Kubernetes에 위임하여 CI 러너의 리소스를 절약하고, 빌드 환경을 독립적으로 스케일링할 수 있습니다.
  • DinD 불필요: Kaniko를 사용해 Docker 데몬 없이 이미지를 빌드합니다. privileged 모드가 필요 없습니다.
  • docker-compose.yaml 호환: 기존 docker-compose.yaml을 그대로 활용하여 멀티 서비스 빌드를 설정할 수 있습니다.

아키텍처

┌──────────┐         ┌──────────────┐         ┌─────────────────┐
│  Client  │───S3───>│    Server    │──ECS──> │  Agent (amd64)  │
│          │──HTTP──>│ (Controller) │  or K8S │  Agent (arm64)  │
│          │<──Logs──│              │<──Logs──│  (Kaniko 실행)   │
└──────────┘         └──────────────┘         └─────────────────┘

Client: 소스코드를 tar.gz로 압축해 S3에 업로드하고, Server에 빌드를 요청합니다. 빌드 로그를 실시간 스트리밍으로 수신하여 출력합니다.

Server (Controller): 빌드 요청을 받아 아키텍처별 태스크를 생성하고, ECS 또는 Kubernetes에 Agent 컨테이너를 실행합니다. Agent의 로그를 수집하여 Client에 전달합니다.

Agent: S3에서 소스코드를 다운로드하고 Kaniko로 이미지를 빌드합니다. pre/post 스크립트 실행을 지원하며, 빌드 결과를 Server에 보고합니다.

설정

환경 변수

.env.example.env로 복사하여 환경에 맞게 수정합니다.

cp .env.example .env

공통 (Server, Client 모두 필요)

변수 설명
S3_ENDPOINT S3 엔드포인트 (예: s3.amazonaws.com)
S3_REGION S3 리전
S3_BUCKET 빌드 컨텍스트를 저장할 S3 버킷
S3_SSL SSL 사용 여부 (true/false)
CONTROLLER_URL Server의 공개 URL

Server 전용

변수 설명
AWS_REGION AWS 리전
ECS_CLUSTER ECS 클러스터 이름
ECS_SUBNETS ECS 서브넷 (쉼표 구분)
ECS_SECURITY_GROUPS ECS 보안 그룹 (쉼표 구분)
ECS_EXEC_ROLE_ARN ECS 실행 역할 ARN
ECS_TASK_ROLE_ARN ECS 태스크 역할 ARN
AGENT_IMAGE Agent 컨테이너 이미지
AGENT_IMAGE_SECRET_ARN Agent 이미지 pull용 시크릿 ARN
K8S_NAMESPACE Kubernetes 네임스페이스
BUILD_TASK_TIMEOUT 빌드 태스크 타임아웃 (기본: 10m)
BUILD_RESULT_TIMEOUT 빌드 결과 대기 타임아웃 (기본: 10m)
DEFAULT_BUILD_CPU 기본 CPU (기본: 0.5)
DEFAULT_BUILD_MEMORY 기본 메모리 (기본: 2G)

Client 전용

변수 설명
LOG_FORMAT 로그 형식 (simple, plain, json)

빌드 설정 파일 (config.yaml)

client-config.yaml.example을 참고하여 config.yaml을 작성합니다.

global:
  # 실행 플랫폼: ecs 또는 k8s
  platform: ecs

  # 기본 아키텍처
  arch: amd64

  # Agent 컨테이너에 전달할 환경 변수
  env:
    FOO: bar

  # Kaniko 실행 전 스크립트
  pre-script: |
    echo 'setting up...'

  # Kaniko 빌드 성공 후 스크립트
  post-script: |
    echo 'done'

  # 프라이빗 레지스트리 인증 정보
  kaniko-credentials:
  - registry: registry.example.com
    username: user
    password: pass

  # Kaniko 빌드 옵션
  kaniko:
    context-path: .
    dockerfile: Dockerfile
    destination: registry.example.com/myapp:latest
    build-args:
      BASE_IMAGE: alpine:latest
    cache:
      enable: true
      repo: cache.example.com
      ttl: 24h

# 아키텍처별 빌드 설정 (global 설정을 상속하며, 동일 키는 override)
bake:
- arch: amd64
  kaniko:
    build-args:
      BUILD_PLATFORM: amd64

- arch: arm64
  kaniko:
    dockerfile: Dockerfile.arm64
    build-args:
      BUILD_PLATFORM: arm64

bake 항목의 각 설정은 global 설정을 상속받으며, 동일한 키가 있으면 override됩니다. env, build-args 같은 맵 타입은 병합(merge)되고, 나머지는 덮어씁니다.

docker-compose.yaml 모드

기존 docker-compose.yaml을 그대로 사용하여 빌드할 수 있습니다. x-bake.platforms로 아키텍처를 지정합니다.

services:
  myapp:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        VERSION: "1.0.0"
      x-bake:
        platforms:
        - linux/amd64
        - linux/arm64
    image: registry.example.com/myapp:1.0.0

AWS ECS 설정

ECS를 빌드 플랫폼으로 사용할 경우 아래의 AWS 리소스와 IAM 권한이 필요합니다. examples/server/terraform/에 참고용 Terraform 구성이 포함되어 있습니다.

인프라

  • ECS 클러스터: Fargate 및 Fargate Spot 용량 공급자
  • VPC 서브넷: Agent 컨테이너가 S3, Controller, 컨테이너 레지스트리에 접근할 수 있도록 인터넷 액세스(또는 NAT 게이트웨이)가 가능한 서브넷
  • S3 버킷: 빌드 컨텍스트 저장용

IAM 역할

Server용 권한 1개와 Agent용 ECS 태스크 역할 2개, 총 3개의 권한 세트가 필요합니다.

1. Server (Controller)

Server가 실행되는 환경(EC2 인스턴스 프로파일, ECS 태스크 역할, CI 러너 역할 등)에 아래 권한이 필요합니다.

ECS — 태스크 정의 관리 및 Agent 태스크 실행:

Action 용도
ecs:RegisterTaskDefinition 아키텍처/리소스 조합별 태스크 정의 생성
ecs:DescribeTaskDefinition 태스크 정의 존재 여부 확인
ecs:DeregisterTaskDefinition 서버 시작 시 기존 태스크 정의 정리
ecs:ListTaskDefinitions 정리 대상 태스크 정의 조회
ecs:ListTaskDefinitionFamilies 정리 대상 태스크 정의 패밀리 조회
ecs:RunTask Fargate에서 Agent 컨테이너 실행
ecs:DescribeTasks Agent 태스크 상태 모니터링

Secrets Manager — Agent 이미지 pull을 위한 프라이빗 레지스트리 인증 관리:

Action 용도
secretsmanager:CreateSecret AGENT_IMAGE_SECRET_ARN이 미지정 시 새 시크릿 생성
secretsmanager:DescribeSecret 기존 시크릿의 ARN 조회

IAM:

Action 용도
iam:PassRole 태스크 정의 등록 시 실행 역할과 태스크 역할을 ECS에 전달

CloudWatch Logs (선택, ECS_LOG_GROUP 설정 시):

Action 용도
logs:CreateLogStream Agent 컨테이너의 로그 스트림 생성
logs:PutLogEvents Agent 로그를 CloudWatch에 기록

2. Agent 실행 역할 (ECS_EXEC_ROLE_ARN)

ECS가 Agent 컨테이너 이미지를 pull하고 로그를 전송할 때 사용하는 역할입니다. 태스크 정의의 executionRoleArn에 지정됩니다.

권한 용도
AmazonECSTaskExecutionRolePolicy (관리형 정책) ECR에서 컨테이너 이미지 pull, CloudWatch 로그 기록
secretsmanager:GetSecretValue (AGENT_IMAGE_SECRET_ARN 대상) 프라이빗 레지스트리에서 Agent 이미지 pull (선택)

3. Agent 태스크 역할 (ECS_TASK_ROLE_ARN)

Agent 컨테이너가 런타임에 S3에서 빌드 컨텍스트를 다운로드하기 위해 사용하는 역할입니다.

Action Resource 용도
s3:GetObject arn:aws:s3:::<bucket>/* 빌드 컨텍스트 다운로드
s3:ListBucket arn:aws:s3:::<bucket> 빌드 컨텍스트 버킷 내 객체 목록 조회

Client 권한

Client는 빌드 컨텍스트를 S3에 업로드하기 위한 권한이 필요합니다.

Action Resource 용도
s3:PutObject arn:aws:s3:::<bucket>/* 빌드 컨텍스트 tar.gz 업로드

보안 그룹

Agent 보안 그룹은 아웃바운드 인터넷 액세스만 필요합니다. 인바운드 규칙은 필요하지 않습니다.

방향 프로토콜 포트 대상 용도
Egress All All 0.0.0.0/0 S3, Controller, 컨테이너 레지스트리 접근

Kubernetes 배포

examples/server/k8s/에 Kustomize 기반의 Controller Server 배포 예시가 포함되어 있습니다.

디렉토리 구조

examples/server/k8s/
├── .env                  # Server 환경 변수 (Secret으로 생성)
├── configs/
│   └── config.yaml       # K8s Agent 설정 (ConfigMap으로 생성)
├── deploy.yaml           # Deployment
├── sa.yaml               # ServiceAccount (Server, Agent)
├── role.yaml             # Role (Server, Agent)
├── rolebinding.yaml      # RoleBinding
├── svc.yaml              # Service (headless)
├── ing.yaml              # Ingress
└── kustomization.yaml

환경 변수 설정

.env 파일에 Server에 필요한 환경 변수를 작성합니다. Kustomize의 secretGenerator가 이 파일을 읽어 Kubernetes Secret으로 생성합니다.

S3_ENDPOINT=s3.amazonaws.com
S3_REGION=ap-northeast-2
S3_BUCKET=my-build-bucket
S3_SSL=true
CONTROLLER_URL=https://bakery.example.com
AWS_REGION=ap-northeast-2
ECS_CLUSTER=bakery-cluster
ECS_SUBNETS=subnet-xxx,subnet-yyy
ECS_SECURITY_GROUPS=sg-xxx
ECS_EXEC_ROLE_ARN=arn:aws:iam::<account-id>:role/bakery-agent-execution
ECS_TASK_ROLE_ARN=arn:aws:iam::<account-id>:role/bakery-agent-task
AGENT_IMAGE=docker.io/rayshoo/bakery-agent:v1.0.2
CLEANUP_ECS_TASK_DEFINITIONS=true

kustomization.yaml에서 이 파일을 secretGenerator로 참조합니다:

secretGenerator:
- name: bakery
  envs:
  - .env

배포

kubectl apply -k examples/server/k8s/

사용법

예시

배포 및 CI/CD 연동 예시가 examples/ 아래에 준비되어 있습니다:

examples/
├── server/                  # Server 배포 예시
│   ├── k8s/                 # Kubernetes (Kustomize) 매니페스트
│   └── terraform/           # AWS ECS 인프라 (Terraform)
└── client/                  # Client CI/CD 연동 예시
    ├── .github-actions.yml  # GitHub Actions 워크플로우
    └── .gitlab.yml          # GitLab CI/CD 파이프라인
  • Server: examples/server/에서 Kubernetes 및 Terraform 기반 bakery-server 배포 예시를 확인할 수 있습니다.
  • Client: examples/client/에서 bakery-client를 사용해 컨테이너 이미지를 빌드하고 push하는 CI/CD 파이프라인 예시를 확인할 수 있습니다 (GitHub Actions, GitLab CI).

Client CLI 옵션

bakery-client \
  --config config.yaml \        # 빌드 설정 파일 (선택)
  --compose compose.yaml \      # docker-compose 파일 (선택)
  --services "app,worker" \     # 빌드할 서비스 필터 (선택, 비워두면 전체)
  --async \                     # 비동기 빌드 모드
  --repo .                      # 소스코드 경로 (기본: 현재 디렉토리)

--config--compose를 함께 사용하면, config.yaml의 global 설정이 base로 적용되고 compose 파일의 서비스별 설정이 merge됩니다.

빌드 흐름

  1. Client가 소스코드를 tar.gz로 압축하여 S3에 업로드합니다
  2. Client가 빌드 설정(YAML)과 함께 Server에 POST 요청을 보냅니다
  3. Server가 아키텍처별 빌드 태스크를 생성합니다
  4. Server가 ECS 또는 Kubernetes에 Agent 컨테이너를 실행합니다
  5. Agent가 S3에서 소스코드를 다운로드하고 Kaniko로 빌드합니다
  6. Agent가 빌드 로그를 실시간으로 Server에 전송합니다
  7. Client가 Server에서 로그를 스트리밍으로 수신합니다
  8. 빌드 완료 후 이미지가 지정된 레지스트리에 push됩니다

컨테이너 이미지 빌드

# 전체 서비스 이미지 빌드 (bakery-server, bakery-client, bakery-agent) 후 레지스트리에 push
make bake