🖼️ 이미지 자동 처리 파이프라인

120

왜 이미지 자동 처리 파이프라인을 만들까?

여러분이 커머스 플랫폼의 개발자라고 상상해 보세요. 판매자가 상품 이미지를 올리면 다음 작업이 필요합니다:

이미지 처리 파이프라인 아키텍처 — S3 + Lambda + Rekognition
  • 원본 보관: 고해상도 원본을 안전하게 저장
  • 썸네일 생성: 상품 목록에 표시할 300x300px 이미지
  • 워터마크 삽입: 무단 도용 방지를 위한 반투명 텍스트 오버레이
  • 포맷 변환: JPEG를 WebP로 변환하여 용량 30-50% 절감 (사용자 로딩 속도 향상)

처음에는 "이미지 올릴 때 서버에서 처리하면 되지 않나?"라고 생각할 수 있습니다. 하지만:

  • 이미지 처리는 CPU 집약적 작업 — 웹 서버 응답 시간이 크게 늘어남
  • 갑자기 이미지 100장이 동시에 올라오면 서버 과부하 발생
  • 처리 도중 서버가 죽으면 어디까지 처리했는지 알 수 없음

이 문제를 해결하는 패턴이 이벤트 기반 파이프라인입니다:

  1. S3에 이미지가 업로드되면 자동으로 파이프라인이 시작됩니다
  2. 각 단계(리사이징, 워터마크, 변환)를 독립된 Lambda가 처리합니다
  3. Step Functions가 순서 제어와 에러 핸들링을 담당합니다
  4. 처리 완료 시 SNS로 알림을 보냅니다

서버를 관리할 필요 없이, 이미지 1장이든 1만 장이든 자동으로 스케일링됩니다. 이것이 서버리스 파이프라인의 힘입니다.

실습을 시작하기 전에 AWS 콘솔에 로그인되어 있는지 확인하세요. 리전은 ap-northeast-2 (서울) 을 사용합니다.

아키텍처 개요

이미지 처리 파이프라인 상세 흐름도

파이프라인 처리 흐름

다이어그램 로딩 중...
이미지 처리 파이프라인 상태 머신 흐름

비용 예측

비용 계산기

2시간
0h24h
50 GB
0 GB100 GB
S3 스토리지

GB/월

$1.2500
Lambda 호출

100만 요청당 $0.20 + 실행시간

$0.0040
Step Functions

상태 전환 1,000당 $0.025

$0.0040
SNS 알림

요청 100만당 $0.50

$0.0020
예상 총 비용$1.2600

* 실제 비용은 AWS 요금 정책에 따라 달라질 수 있습니다.

Step 1: S3 버킷 생성 (Input / Output)

원본 이미지를 받을 Input 버킷과, 처리된 이미지를 저장할 Output 버킷 두 개를 만듭니다.

  1. AWS 콘솔 → S3 검색 → 버킷 만들기 클릭
  2. 버킷 이름: image-pipeline-input-{계정ID} 입력 (S3 버킷 이름은 전세계에서 고유해야 합니다)
  3. 리전: 아시아 태평양(서울) 확인
  4. 나머지 설정은 기본값 → 버킷 만들기 클릭
  5. 동일한 방식으로 image-pipeline-output-{계정ID} 버킷 생성
  6. Input 버킷 클릭 → 폴더 만들기 → 이름: uploads → 생성 (이 폴더에 업로드된 파일만 파이프라인이 처리)
  7. Output 버킷에도 thumbnails, watermarked, webp 폴더를 각각 생성
코드
aws s3 mb s3://image-pipeline-input-$(aws sts get-caller-identity --query Account --output text)
aws s3 mb s3://image-pipeline-output-$(aws sts get-caller-identity --query Account --output text)

버킷 이름에 계정 ID를 포함하면 전세계에서 고유한 이름을 쉽게 만들 수 있습니다. 계정 ID는 AWS 콘솔 우측 상단 사용자명 클릭 → "계정 ID" 에서 확인할 수 있습니다.

S3 버킷 이름 규칙: 소문자, 숫자, 하이픈(-)만 사용 가능합니다. 밑줄(_), 대문자, 공백은 허용되지 않습니다. 또한 3-63자 사이여야 하며, 한 번 삭제한 버킷 이름은 바로 재사용할 수 없을 수 있습니다.

Step 2: Lambda 함수 작성 (리사이징 / 워터마크 / 변환)

파이프라인의 각 단계를 담당할 Lambda 함수 3개를 만듭니다. Python의 Pillow 라이브러리를 사용합니다.

진행률 0/10
  1. 1먼저 Pillow Lambda Layer를 준비합니다: mkdir -p python && cd python pip install Pillow -t . cd .. && zip -r pillow-layer.zip python/
  2. 2Lambda 콘솔 → 왼쪽 메뉴 계층 → 계층 생성 → 이름: pillow-layer, ZIP 업로드, 런타임: Python 3.12
  3. 3Lambda 콘솔 → 함수 생성 → image-resize (런타임: Python 3.12)
  4. 4구성 탭 → 일반 구성 → 메모리: 512MB, 타임아웃: 30초로 변경 (이미지 처리에 충분한 자원)
  5. 5계층 섹션 → pillow-layer 추가
  6. 6image-resize 함수 코드 작성: S3에서 원본 이미지를 다운로드 → Pillow로 300x300 썸네일 생성 → Output 버킷의 thumbnails/ 폴더에 저장
  7. 7image-watermark 함수 생성: 리사이징된 이미지에 Pillow의 ImageDraw로 반투명 텍스트 워터마크 삽입
  8. 8image-convert 함수 생성: 최종 이미지를 Pillow의 save(format='WEBP', quality=80)으로 WebP 변환 후 webp/ 폴더에 저장
  9. 9세 함수 모두 실행 역할에 AmazonS3FullAccess 정책 연결
  10. 10환경 변수 추가: INPUT_BUCKET, OUTPUT_BUCKET 에 각각 버킷 이름 설정
코드
# image-resize Lambda 핵심 코드 (Python 3.12)
import boto3, io, os
from PIL import Image
 
s3 = boto3.client('s3')
OUTPUT_BUCKET = os.environ['OUTPUT_BUCKET']
 
def lambda_handler(event, context):
    bucket = event['bucket']
    key = event['key']
 
    # S3에서 원본 이미지 다운로드
    response = s3.get_object(Bucket=bucket, Key=key)
    img = Image.open(io.BytesIO(response['Body'].read()))
 
    # 300x300 썸네일 생성 (비율 유지)
    img.thumbnail((300, 300), Image.LANCZOS)
 
    # Output 버킷에 저장
    buffer = io.BytesIO()
    img.save(buffer, format='JPEG', quality=85)
    buffer.seek(0)
 
    output_key = f"thumbnails/{key.split('/')[-1]}"
    s3.put_object(Bucket=OUTPUT_BUCKET, Key=output_key, Body=buffer, ContentType='image/jpeg')
 
    return {**event, 'resizedKey': output_key, 'resizedBucket': OUTPUT_BUCKET}

Step 3: Step Functions 상태 머신 정의

세 Lambda 함수를 순서대로 실행하고, 각 단계에서 에러가 발생하면 에러 핸들러로 분기하는 상태 머신을 만듭니다.

  1. Step Functions 콘솔 → 상태 머신 생성 클릭
  2. 코드로 작성 선택 → ASL(Amazon States Language) 정의 입력
  3. 이름: image-processing-pipeline
  4. 각 단계(Resize → Watermark → Convert)를 Task 상태로 연결
  5. 에러 처리: 각 Task에 Catch 블록 추가하여 실패 시 에러 핸들러로 이동
  6. 마지막에 SNS 알림 전송 단계 추가
  7. 실행 역할: 새 역할 생성 선택 (Lambda 호출 + SNS 발행 권한 자동 부여)
코드
{
  "StartAt": "ResizeImage",
  "States": {
    "ResizeImage": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-2:ACCOUNT:function:image-resize",
      "Next": "WatermarkImage",
      "Retry": [{"ErrorEquals": ["States.TaskFailed"], "MaxAttempts": 2, "BackoffRate": 2}],
      "Catch": [{ "ErrorEquals": ["States.ALL"], "Next": "HandleError" }]
    },
    "WatermarkImage": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-2:ACCOUNT:function:image-watermark",
      "Next": "ConvertFormat",
      "Catch": [{ "ErrorEquals": ["States.ALL"], "Next": "HandleError" }]
    },
    "ConvertFormat": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-2:ACCOUNT:function:image-convert",
      "Next": "NotifyComplete"
    },
    "NotifyComplete": {
      "Type": "Task",
      "Resource": "arn:aws:states:::sns:publish",
      "Parameters": {
        "TopicArn": "arn:aws:sns:ap-northeast-2:ACCOUNT:image-pipeline-notify",
        "Message": "이미지 처리가 완료되었습니다."
      },
      "End": true
    },
    "HandleError": {
      "Type": "Pass",
      "Result": "처리 중 오류 발생",
      "End": true
    }
  }
}

ASL의 Retry 블록에서 BackoffRate: 2는 재시도 간격을 점점 늘리는 설정입니다. 첫 번째 재시도는 1초 후, 두 번째는 2초 후에 실행됩니다. 이를 지수 백오프(Exponential Backoff) 라고 하며, 일시적 장애에서의 복구율을 높입니다.

Step 4: S3 이벤트 트리거 연결

이미지가 S3에 업로드되면 자동으로 Step Functions가 시작되도록 이벤트를 연결합니다.

진행률 0/8
  1. 1Input 버킷 클릭 → 속성 탭 → 페이지 하단 이벤트 알림 섹션 → 이벤트 알림 생성 클릭
  2. 2이벤트 알림 이름: trigger-pipeline
  3. 3접두사: uploads/ (이 폴더에 업로드된 파일만 트리거)
  4. 4접미사: .jpg (JPG 파일만 처리 — PNG도 원하면 별도 규칙 추가)
  5. 5이벤트 유형: s3:ObjectCreated:Put 체크
  6. 6대상: Step Functions 상태 머신 선택 → image-processing-pipeline 선택
  7. 7변경 사항 저장 클릭
  8. 8테스트: 아무 JPG 이미지를 uploads/ 폴더에 업로드하고, Step Functions 콘솔에서 실행이 시작되는지 확인

Step 5: SNS 완료 알림 설정

파이프라인 처리가 완료되면 관리자에게 이메일 알림을 보내도록 설정합니다.

진행률 0/5
  1. 1SNS 콘솔 → 토픽 생성 → 유형: 표준 → 이름: image-pipeline-notify → 토픽 생성
  2. 2구독 생성 → 프로토콜: 이메일 → 본인 이메일 입력 → 구독 생성
  3. 3이메일에서 확인 링크 클릭 → 구독 상태가 확인됨으로 변경 확인
  4. 4Step Functions 상태 머신의 ASL에서 NotifyComplete 단계의 TopicArn을 실제 ARN으로 교체
  5. 5Step Functions 실행 역할에 AmazonSNSFullAccess 정책 연결 (SNS 발행 권한)

SNS 토픽 ARN은 SNS 콘솔에서 토픽을 클릭하면 상단에 표시됩니다. 형식: arn:aws:sns:ap-northeast-2:{계정ID}:image-pipeline-notify

Step 6: 완성 후 통합 테스트

파이프라인 전체를 테스트합니다.

진행률 0/7
  1. 1테스트용 JPG 이미지를 준비합니다 (1MB 이하 권장)
  2. 2S3 콘솔 → Input 버킷 → uploads/ 폴더 → 업로드 클릭 → 이미지 선택 → 업로드
  3. 3Step Functions 콘솔 → image-processing-pipeline → 최근 실행 클릭
  4. 4실행 그래프에서 각 단계가 초록색(성공)으로 표시되는지 확인
  5. 5각 단계를 클릭하면 입력/출력 JSON을 볼 수 있습니다 — 데이터가 단계마다 어떻게 전달되는지 확인
  6. 6Output 버킷 확인: thumbnails/ 폴더에 리사이즈된 이미지가 있는지 watermarked/ 폴더에 워터마크가 삽입된 이미지가 있는지 webp/ 폴더에 WebP 형식으로 변환된 이미지가 있는지
  7. 7이메일로 SNS 완료 알림을 수신했는지 확인

트러블슈팅

S3 이벤트 트리거가 동작하지 않으면:

  1. 이벤트 알림에서 접두사(uploads/)와 접미사(.jpg)가 정확한지 확인
  2. 대상(Step Functions)의 ARN이 올바른지 확인
  3. S3가 Step Functions을 호출할 수 있는 리소스 기반 정책이 필요합니다 — EventBridge를 통한 트리거로 변경하는 것을 권장합니다

Lambda에서 "Unable to import module 'PIL'" 에러: Pillow Lambda Layer가 올바르게 연결되어 있는지 확인하세요. Layer의 디렉토리 구조가 중요합니다: python/PIL/ 또는 python/lib/python3.12/site-packages/PIL/ 경로에 파일이 있어야 합니다.

Lambda 타임아웃 에러 (Task timed out after X seconds): 이미지 크기가 크면 처리 시간이 길어집니다. Lambda 함수의 타임아웃을 30초 이상으로 늘리고, 메모리를 512MB 이상으로 설정하세요. 일반적으로 5MB 이하 이미지는 512MB 메모리에서 10초 이내에 처리됩니다.

Step Functions에서 "Access Denied" 에러: Step Functions 실행 역할에 Lambda 호출 권한과 SNS 발행 권한이 모두 필요합니다. 상태 머신 생성 시 "새 역할 생성"을 선택하면 자동으로 부여되지만, Lambda ARN을 나중에 변경한 경우 역할 업데이트가 필요합니다.

완성 후 추가 검증

파이프라인의 안정성을 검증하는 추가 테스트를 수행합니다.

진행률 0/4
  1. 1다양한 이미지 크기 테스트: 100KB, 1MB, 5MB 이미지를 각각 업로드하여 모두 처리되는지 확인
  2. 2지원하지 않는 형식 테스트: .png 파일을 uploads/에 업로드 → 접미사 필터(.jpg)로 인해 파이프라인이 트리거되지 않음을 확인
  3. 3에러 핸들링 테스트: Lambda에서 의도적 에러 발생 → Step Functions 실행 그래프에서 HandleError 상태로 전환 확인
  4. 4Output 버킷 파일 비교: 원본 이미지 크기와 처리된 이미지 크기를 비교하여 리사이징과 WebP 변환이 실제로 용량을 줄였는지 확인

핵심 개념 확인

확장 아이디어

기본 파이프라인이 완성되었으니, 더 발전시켜 보세요:

  1. 다중 해상도 생성: Parallel 상태로 300x300, 600x600, 1200x1200 세 가지 크기를 동시에 생성
  2. 이미지 메타데이터 추출: Pillow의 exif 모듈로 촬영 날짜, 카메라 정보 등을 DynamoDB에 저장
  3. Amazon Rekognition 연동: AI로 이미지에 부적절한 콘텐츠가 있는지 자동 감지
  4. CloudFront CDN 배포: Output 버킷에 CloudFront를 연결하여 전세계에서 빠르게 접근
  5. PNG/GIF 지원 확장: 접미사 필터를 추가하고 Lambda에서 포맷별 분기 처리

학습 정리

핵심 치트시트

리소스 정리

실습 완료 후 반드시 아래 순서대로 리소스를 정리하여 불필요한 과금을 방지하세요.

  1. S3 버킷 내 모든 객체 삭제 (Input, Output 버킷) — 버킷이 비어있어야 삭제 가능
  2. S3 버킷 삭제 (2개)
  3. Step Functions 상태 머신 삭제
  4. Lambda 함수 삭제 (image-resize, image-watermark, image-convert)
  5. Lambda Layer 삭제 (Pillow)
  6. IAM 역할 삭제 (Lambda 실행 역할, Step Functions 실행 역할)
  7. SNS 주제 삭제
  8. CloudWatch 로그 그룹 삭제