데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      AWS CDK로 구축하는 Vision AI 모델 서빙 파이프라인 실전 가이드

      자니큐 26.04.24
      1,319 5 0
      DEVOTEE 요약
      본 블로그는 AWS CDK를 활용해 Vision AI 모델을 프로덕션 서비스(VAIaaS)로 서빙하는 경험을 공유합니다. 복잡한 AWS 인프라(API Gateway, Lambda, SageMaker 등)의 의존성을 CDK로 효율적으로 관리하며 안정적이고 확장 가능한 아키텍처를 구축하는 과정을 상세히 설명합니다. 또한, SageMaker 엔드포인트 배포 시간, Lambda 호출 타임아웃, 비용 최적화 등 실제 운영에서 겪었던 문제점과 그 해결책을 제시하며 실질적인 개발 및 운영 노하우를 제공합니다.
      DEVOTEE 추천 블로그

      이 글은 실제 프로덕션에서 운영을 위해 개발을 진행했던 VAIaaS(Vision AI as a Service) 프로젝트를 기반으로 작성되었습니다.

      CDK + API Gateway + Lambda + SageMaker 조합으로 Vision AI 모델 서빙 파이프라인을 구축한 경험을 공유합니다.


      들어가며

      Vision AI 모델을 개발하는 것과 그 모델을 실제 서비스로 제공하는 것은 전혀 다른 문제입니다.

      연구 환경에서 높은 정확도를 달성한 모델이라도, 이를 안정적이고 확장 가능한 API 서비스로 전환하는 과정에는 수많은 엔지니어링 과제가 숨어 있습니다.

      인프라 프로비저닝, 모델 배포 자동화, 요청 처리 파이프라인 설계, 비용 최적화, 그리고 운영 모니터링까지

      — 이 모든 것을 체계적으로 관리하기 위해 하나의 단일화된 개발과 배포 즉 데브옵스를 위한 프레임워크가 필요합니다.

      AWS를 클라우드 인프라로 사용할 때, AWS CDK를 선택하는 것은 이를 위한 훌륭한 선택지가 될 수 있습니다.


      이 글에서는 AWS클라우드 위에서 모델, 특히 Vision모델을 서빙하기 위한 아키텍처와 CDK 예제를 공유합니다.

      단순한 튜토리얼이 아니라, 프로덕션 환경에서 마주친 문제들과 그 해결 과정을 솔직하게 담았습니다.


      1. 왜 AWS CDK인가?

      저는 이전 글(AWS의 IaC Framework CDK 소개에서 CDK의 기본 개념과 Terraform과의 비교를 다룬 바 있습니다.

      AWS 클라우드 인프라 위에서 Vision AI 모델을 서빙하기 위한 서비스를 구축하면서 CDK를 선택한 이유는 더욱 명확해졌습니다.


      Vision AI 모델 서빙을 위한 파이프라인은 단순한 웹 서비스와 달리, 여러 AWS 서비스 간의 복잡한 의존 관계를 가집니다.

      S3 버킷이 먼저 생성되어야 SageMaker 모델이 아티팩트를 참조할 수 있고, SageMaker 엔드포인트가 준비되어야 Lambda가 추론을 호출할 수 있습니다.

      이러한 의존성 그래프를 YAML이나 HCL로 관리하는 것은 파일이 커질수록 점점 더 어려워집니다.


      반면 CDK는 TypeScript의 타입 시스템과 클래스 상속을 활용하여 이 복잡성을 프로그래밍적으로 관리할 수 있게 해줍니다.

      StorageStack이 생성한 S3 버킷 참조를 ModelStack에 직접 전달하고, ModelStack의 엔드포인트 이름을 ApiStack의 Lambda 환경 변수로 주입하는 과정이 타입 안전하게 이루어집니다.


      2. Vision AI 서비스 전체 아키텍처

      아래는 여러 종류의 Vision AI 모델들을 End-to-End로 서빙하기 위한 가칭 'VAIaaS (Vision AI as a Service)' 프로젝트의 전체 아키텍처 다이어그램입니다.


      요청 흐름을 단계별로 설명하면 다음과 같습니다.


      클라이언트가 POST /v1/analyze 엔드포인트로 이미지 데이터를 전송하면, Amazon API Gateway가 요청을 수신합니다.

      Lambda Authorizer가 JWT 토큰 또는 API Key를 검증한 후, Router Lambda가 요청을 라우팅합니다.

      Pre-processing Lambda는 이미지를 S3에 업로드하고 모델 입력 형식에 맞게 전처리(리사이즈, 정규화)를 수행합니다.

      전처리된 데이터는 SageMaker 실시간 엔드포인트로 전달되어 추론이 실행되고, 결과는 Post-processing Lambda를 통해 클라이언트 친화적인 JSON 형식으로 변환되어 반환됩니다.

      CDK Stack 구조


      전체 인프라는 4개의 CDK Stack으로 분리되어 있습니다.

      Stack

      주요 리소스

      역할

      StorageStack

      S3 (Input/Output/Model)

      데이터 및 모델 아티팩트 저장

      ModelStack

      SageMaker Model, EndpointConfig, Endpoint

      AI 모델 호스팅

      ApiStack

      API Gateway, Lambda x3

      요청 처리 파이프라인

      ObservabilityStack

      CloudWatch, X-Ray

      모니터링 및 추적

      Stack을 분리한 이유는 독립적인 배포와 롤백이 가능하기 때문입니다.

      모델을 업데이트할 때는 ModelStack만 재배포하면 되고, API 로직을 수정할 때는 ApiStack만 변경하면 됩니다.

      이는 운영 환경에서 매우 중요한 설계 결정입니다.


      3. StorageStack: 데이터 레이어 구성

      // lib/storage-stack.ts
      import * as cdk from 'aws-cdk-lib';
      import * as s3 from 'aws-cdk-lib/aws-s3';
      import { Construct } from 'constructs';
      
      export class StorageStack extends cdk.Stack {
        // 다른 Stack에서 참조할 수 있도록 public으로 노출
        public readonly inputBucket: s3.Bucket;
        public readonly outputBucket: s3.Bucket;
        public readonly modelBucket: s3.Bucket;
      
        constructor(scope: Construct, id: string, props?: cdk.StackProps) {
          super(scope, id, props);
      
          // 입력 이미지 버킷: 수명 주기 정책으로 비용 최적화
          this.inputBucket = new s3.Bucket(this, 'InputBucket', {
            bucketName: `vaiaas-input-${this.account}-${this.region}`,
            removalPolicy: cdk.RemovalPolicy.RETAIN,
            lifecycleRules: [
              {
                // 처리 완료된 입력 이미지는 7일 후 자동 삭제
                expiration: cdk.Duration.days(7),
                prefix: 'processed/',
              },
            ],
            cors: [
              {
                allowedMethods: [s3.HttpMethods.PUT, s3.HttpMethods.POST],
                allowedOrigins: ['*'],
                allowedHeaders: ['*'],
              },
            ],
          });
      
          // 결과 버킷: Intelligent-Tiering으로 비용 최적화
          this.outputBucket = new s3.Bucket(this, 'OutputBucket', {
            bucketName: `vaiaas-output-${this.account}-${this.region}`,
            removalPolicy: cdk.RemovalPolicy.RETAIN,
            intelligentTieringConfigurations: [
              {
                name: 'EntireBucket',
                archiveAccessTierTime: cdk.Duration.days(90),
                deepArchiveAccessTierTime: cdk.Duration.days(180),
              },
            ],
          });
      
          // 모델 아티팩트 버킷: 버전 관리 활성화
          this.modelBucket = new s3.Bucket(this, 'ModelBucket', {
            bucketName: `vaiaas-models-${this.account}-${this.region}`,
            versioned: true,
            removalPolicy: cdk.RemovalPolicy.RETAIN,
          });
        }
      }

      실전 팁: S3 버킷 이름에 ${this.account}-${this.region}을 포함시키면 멀티 리전 배포 시 이름 충돌을 방지할 수 있습니다.

      또한 removalPolicy: RETAIN은 실수로 스택을 삭제해도 데이터가 보존되도록 하는 중요한 설정입니다.


      4. ModelStack: SageMaker 엔드포인트 구성

      SageMaker 엔드포인트 구성은 CDK에서 가장 복잡한 부분 중 하나입니다.

      모델 아티팩트 경로, 컨테이너 이미지, 인스턴스 타입, 오토스케일링 정책을 모두 코드로 관리해야 합니다.

      // lib/model-stack.ts
      import * as cdk from 'aws-cdk-lib';
      import * as sagemaker from 'aws-cdk-lib/aws-sagemaker';
      import * as iam from 'aws-cdk-lib/aws-iam';
      import * as s3 from 'aws-cdk-lib/aws-s3';
      import { Construct } from 'constructs';
      
      interface ModelStackProps extends cdk.StackProps {
        modelBucket: s3.Bucket;
        modelVersion: string; // e.g., 'v1.2.0'
      }
      
      export class ModelStack extends cdk.Stack {
        public readonly endpointName: string;
      
        constructor(scope: Construct, id: string, props: ModelStackProps) {
          super(scope, id, props);
      
          this.endpointName = `vaiaas-endpoint-${props.modelVersion.replace(/\./g, '-')}`;
      
          // SageMaker 실행 역할
          const sagemakerRole = new iam.Role(this, 'SageMakerRole', {
            assumedBy: new iam.ServicePrincipal('sagemaker.amazonaws.com'),
            managedPolicies: [
              iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSageMakerFullAccess'),
            ],
          });
      
          // 모델 버킷 읽기 권한 부여
          props.modelBucket.grantRead(sagemakerRole);
      
          // SageMaker 모델 정의
          // PyTorch 추론 컨테이너 사용 (AWS Deep Learning Container)
          const model = new sagemaker.CfnModel(this, 'VisionAIModel', {
            modelName: `vaiaas-model-${props.modelVersion.replace(/\./g, '-')}`,
            executionRoleArn: sagemakerRole.roleArn,
            primaryContainer: {
              // AWS 제공 PyTorch 추론 컨테이너
              image: `763104351884.dkr.ecr.${this.region}.amazonaws.com/pytorch-inference:2.1.0-gpu-py310-cu118-ubuntu20.04-sagemaker`,
              modelDataUrl: `s3://${props.modelBucket.bucketName}/models/${props.modelVersion}/model.tar.gz`,
              environment: {
                SAGEMAKER_PROGRAM: 'inference.py',
                SAGEMAKER_SUBMIT_DIRECTORY: '/opt/ml/model/code',
                MODEL_VERSION: props.modelVersion,
              },
            },
          });
      
          // 엔드포인트 설정: GPU 인스턴스 + 데이터 캡처
          const endpointConfig = new sagemaker.CfnEndpointConfig(this, 'EndpointConfig', {
            endpointConfigName: `vaiaas-config-${props.modelVersion.replace(/\./g, '-')}`,
            productionVariants: [
              {
                variantName: 'AllTraffic',
                modelName: model.modelName!,
                instanceType: 'ml.g4dn.xlarge', // NVIDIA T4 GPU
                initialInstanceCount: 1,
                initialVariantWeight: 1,
              },
            ],
            // 추론 데이터 캡처: 모델 품질 모니터링에 활용
            dataCaptureConfig: {
              enableCapture: true,
              initialSamplingPercentage: 10,
              destinationS3Uri: `s3://${props.modelBucket.bucketName}/data-capture/`,
              captureOptions: [
                { captureMode: 'Input' },
                { captureMode: 'Output' },
              ],
            },
          });
      
          // 실시간 엔드포인트 배포
          const endpoint = new sagemaker.CfnEndpoint(this, 'Endpoint', {
            endpointName: this.endpointName,
            endpointConfigName: endpointConfig.endpointConfigName!,
          });
      
          // 오토스케일링 설정 (Application Auto Scaling)
          // 최소 1개, 최대 4개 인스턴스로 트래픽에 따라 자동 조정
          const scalingTarget = new cdk.CfnResource(this, 'ScalingTarget', {
            type: 'AWS::ApplicationAutoScaling::ScalableTarget',
            properties: {
              MaxCapacity: 4,
              MinCapacity: 1,
              ResourceId: `endpoint/${this.endpointName}/variant/AllTraffic`,
              ScalableDimension: 'sagemaker:variant:DesiredInstanceCount',
              ServiceNamespace: 'sagemaker',
              RoleARN: sagemakerRole.roleArn,
            },
          });
          scalingTarget.addDependency(endpoint);
      
          new cdk.CfnOutput(this, 'EndpointNameOutput', {
            value: this.endpointName,
            exportName: 'VaiaasSageMakerEndpointName',
          });
        }
      }

      실전 팁: ml.g4dn.xlarge는 NVIDIA T4 GPU를 탑재한 인스턴스로, Vision AI 추론에 비용 대비 성능이 가장 좋습니다.

      처음에는 ml.g4dn.2xlarge를 사용했다가 실제 부하 테스트 결과 ml.g4dn.xlarge로도 충분하다는 것을 확인하고 변경했습니다.

      인스턴스 타입 선택 전에 반드시 실제 모델의 메모리 사용량과 추론 시간을 측정하세요.


      5. ApiStack: 요청 처리 파이프라인

      API 레이어는 세 개의 Lambda 함수로 구성됩니다. 각 Lambda는 단일 책임 원칙(SRP)에 따라 설계되어 독립적으로 배포하고 테스트할 수 있습니다.

      // lib/api-stack.ts
      import * as cdk from 'aws-cdk-lib';
      import * as apigateway from 'aws-cdk-lib/aws-apigateway';
      import * as lambda from 'aws-cdk-lib/aws-lambda';
      import * as iam from 'aws-cdk-lib/aws-iam';
      import * as s3 from 'aws-cdk-lib/aws-s3';
      import { Construct } from 'constructs';
      
      interface ApiStackProps extends cdk.StackProps {
        inputBucket: s3.Bucket;
        outputBucket: s3.Bucket;
        sagemakerEndpointName: string;
      }
      
      export class ApiStack extends cdk.Stack {
        constructor(scope: Construct, id: string, props: ApiStackProps) {
          super(scope, id, props);
      
          // Lambda 공통 레이어 (공통 유틸리티, boto3 최신 버전 등)
          const commonLayer = new lambda.LayerVersion(this, 'CommonLayer', {
            code: lambda.Code.fromAsset('lambda/layers/common'),
            compatibleRuntimes: [lambda.Runtime.PYTHON_3_11],
            description: 'Common utilities and dependencies',
          });
      
          // 1. Lambda Authorizer: JWT 검증
          const authorizerFn = new lambda.Function(this, 'AuthorizerFunction', {
            runtime: lambda.Runtime.PYTHON_3_11,
            code: lambda.Code.fromAsset('lambda/authorizer'),
            handler: 'index.handler',
            environment: {
              JWT_SECRET_ARN: 'arn:aws:secretsmanager:...',
            },
            timeout: cdk.Duration.seconds(5),
          });
      
          const authorizer = new apigateway.TokenAuthorizer(this, 'JwtAuthorizer', {
            handler: authorizerFn,
            resultsCacheTtl: cdk.Duration.minutes(5), // 인증 결과 캐싱으로 레이턴시 감소
          });
      
          // 2. Router Lambda: 요청 라우팅 및 S3 업로드
          const routerFn = new lambda.Function(this, 'RouterFunction', {
            runtime: lambda.Runtime.PYTHON_3_11,
            code: lambda.Code.fromAsset('lambda/router'),
            handler: 'index.handler',
            layers: [commonLayer],
            environment: {
              INPUT_BUCKET: props.inputBucket.bucketName,
              OUTPUT_BUCKET: props.outputBucket.bucketName,
              SAGEMAKER_ENDPOINT: props.sagemakerEndpointName,
            },
            timeout: cdk.Duration.seconds(30),
            memorySize: 512,
            tracing: lambda.Tracing.ACTIVE, // X-Ray 추적 활성화
          });
      
          // S3 및 SageMaker 권한 부여
          props.inputBucket.grantReadWrite(routerFn);
          props.outputBucket.grantWrite(routerFn);
          routerFn.addToRolePolicy(new iam.PolicyStatement({
            actions: ['sagemaker:InvokeEndpoint'],
            resources: [`arn:aws:sagemaker:${this.region}:${this.account}:endpoint/${props.sagemakerEndpointName}`],
          }));
      
          // 3. API Gateway 구성
          const api = new apigateway.RestApi(this, 'VaiaaasApi', {
            restApiName: 'VAIaaS API',
            description: 'Vision AI as a Service REST API',
            deployOptions: {
              stageName: 'v1',
              tracingEnabled: true,
              metricsEnabled: true,
              loggingLevel: apigateway.MethodLoggingLevel.INFO,
              // 스로틀링: 초당 100 요청, 버스트 200 요청
              throttlingRateLimit: 100,
              throttlingBurstLimit: 200,
            },
            // CORS 설정
            defaultCorsPreflightOptions: {
              allowOrigins: apigateway.Cors.ALL_ORIGINS,
              allowMethods: ['POST', 'OPTIONS'],
              allowHeaders: ['Content-Type', 'Authorization'],
            },
          });
      
          // POST /v1/analyze 엔드포인트
          const analyzeResource = api.root.addResource('analyze');
          analyzeResource.addMethod(
            'POST',
            new apigateway.LambdaIntegration(routerFn, {
              timeout: cdk.Duration.seconds(29), // API Gateway 최대 타임아웃은 29초
            }),
            {
              authorizer,
              authorizationType: apigateway.AuthorizationType.CUSTOM,
              // 요청 검증: Content-Type 헤더 필수
              requestValidator: new apigateway.RequestValidator(this, 'RequestValidator', {
                restApi: api,
                validateRequestBody: true,
                validateRequestParameters: true,
              }),
            }
          );
      
          new cdk.CfnOutput(this, 'ApiUrl', {
            value: api.url,
            description: 'VAIaaS API Gateway URL',
          });
        }
      }


      6. 실전에서 마주친 문제들

      문제 1: SageMaker 엔드포인트 콜드 스타트

      SageMaker 실시간 엔드포인트는 인스턴스가 항상 실행 중이므로 콜드 스타트 문제는 없습니다.

      그러나 처음 배포 시 엔드포인트가 Creating 상태에서 InService 상태로 전환되는 데 5~15분이 소요됩니다.

      CDK 배포 중에 이 대기 시간이 포함되어 전체 배포 시간이 길어집니다.

      해결책으로는 CDK의 <span style="color: #181818">waitForDeployment</span> 옵션을 <span style="color: #181818">false</span>로 설정하고, 별도의 배포 파이프라인에서 엔드포인트 상태를 폴링하는 방식을 채택했습니다.

      문제 2: Lambda에서 SageMaker 호출 시 타임아웃

      API Gateway의 최대 통합 타임아웃은 29초입니다. 고해상도 이미지에 대한 Vision AI 추론이 이 한계를 초과하는 경우가 발생했습니다.

      해결책으로 두 가지 접근을 병행했습니다. 첫째, Pre-processing Lambda에서 이미지를 모델 입력 크기(예: 640×640)로 리사이즈하여 추론 시간을 단축했습니다.

      둘째, 처리 시간이 긴 요청에 대해서는 비동기 처리 패턴을 도입했습니다.

      클라이언트는 요청 ID를 즉시 받고, SQS를 통해 비동기로 처리된 결과를 나중에 폴링하는 방식입니다.

      문제 3: SageMaker 엔드포인트 비용 최적화

      ml.g4dn.xlarge 인스턴스는 시간당 약 $0.736입니다. 24시간 운영하면 월 약 $530이 발생합니다. 트래픽이 없는 야간 시간대에도 비용이 발생하는 것이 문제였습니다.

      해결책으로 SageMaker Serverless Inference를 검토했으나, GPU를 지원하지 않아 CPU 추론으로 전환해야 했습니다.

      결국 트래픽 패턴을 분석하여 오토스케일링의 <span style="color: #181818">MinCapacity</span>를 야간에는 0으로 조정하는 스케줄링을 적용했습니다.

      이를 통해 월 비용을 약 40% 절감했습니다.


      7. 배포 및 운영

      CDK 배포 명령

      # 전체 스택 배포 (처음 배포 시)
      cdk bootstrap aws://ACCOUNT_ID/REGION
      cdk deploy --all
      
      # 특정 스택만 배포 (모델 업데이트 시)
      cdk deploy ModelStack --parameters modelVersion=v1.3.0
      
      # 배포 전 변경 사항 확인
      cdk diff ApiStack

      모델 업데이트 워크플로우

      새로운 모델 버전을 배포할 때는 Blue/Green 배포 전략을 사용합니다.

      SageMaker는 엔드포인트 업데이트 시 기존 인스턴스를 유지하면서 새 인스턴스를 준비하고, 준비가 완료되면 트래픽을 전환합니다.

      CDK에서는 EndpointConfig를 새로 생성하고 EndpointendpointConfigName을 업데이트하는 것으로 이를 구현할 수 있습니다.

      모니터링 대시보드

      CloudWatch 대시보드에서 다음 지표를 모니터링합니다.

      지표

      임계값

      알람 조건

      SageMaker ModelLatency

      5,000ms

      P99 초과 시

      SageMaker Invocations

      -

      분당 호출 수 추적

      Lambda Duration

      25,000ms

      P95 초과 시

      API Gateway 5XXError

      1%

      에러율 초과 시

      SageMaker CPUUtilization

      80%

      오토스케일링 트리거


      마치며

      AWS CDK로 Vision AI 서빙을 위한 파이프라인을 구축하면서 가장 크게 느낀 점은, 인프라를 코드로 관리한다는 것이 단순히 편의성의 문제가 아니라 서비스 품질의 문제라는 것입니다.

      CDK 덕분에 모델 업데이트, 인프라 변경, 환경 복제가 모두 코드 리뷰 프로세스를 거치게 되었고, 이는 운영 안정성을 크게 향상시켰습니다.


      다음 글에서는 이 파이프라인에 SageMaker Model Monitor를 연동하여 프로덕션 환경에서 모델 드리프트를 감지하는 방법을 다룰 예정입니다.


      이 글이 Vision AI모델을 활용한 서비스를 구축하려는 분들께 도움이 되길 바랍니다. 질문이나 피드백은 댓글로 남겨주세요!


      참고 자료

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      자니큐 님의 최신 블로그

      더보기

      DEVOTEE 추천 블로그

      동영상 기고하기