AWS 기술 블로그

Amazon SageMaker를 활용한 기계 학습에서 EFS 안의 민감 정보를 삭제하기 위한 서버리스 솔루션

민감 정보를 활용하는 기계 학습 환경

기계 학습 훈련 과정에서 개인 식별 정보나 생체 인식 정보 등 민감한 정보를 다루는 경우가 있습니다. 무엇보다 안전하게 데이터를 활용해야 해서 보안을 중요하게 생각해야 합니다. 네트워크 접근 통제와 사용자 접근 관리, 암호화, 이상 감지 등 다양한 방법으로 안전한 기계 학습이 이루어지도록 해야 합니다. 또한 1년 이상 장기 미사용 사용자는 비활성화 처리하여 별도 관리해야 하고 일정 시간이 후에는 개인 정보를 삭제해야 합니다. 이러한 휴면 계정 정책이 아니더라도 개인이 직접 개인 정보 삭제를 요청한 경우에는 해당 정보를 즉시 파기해야 합니다.

Amazon SageMaker를 활용하여 기계 학습 훈련을 하는 도중에 민감 정보 삭제를 요청받은 경우, 즉시 처리하기 어려운 상황이 있을 수 있습니다. 대용량의 데이터를 장기 보관하기 위하여 Amazon Simple Storage Service (S3)를 활용하고, 빠른 기계 학습 훈련을 위해서 Amazon Elastic File Service (EFS)를 병행해서 활용하는 경우입니다. 이럴 경우, 민감 정보가 양쪽 저장소 동시에 존재할 수 있어서 S3에서 데이터를 삭제한다고 하더라도 EFS 볼륨에 같은 정보가 남아있을 수 있기 때문에, 별도로 삭제해야 합니다.

SageMaker 노트북에 접속해서 연결된 EFS 볼륨의 특정 파일을 삭제하는 것도 한 가지 방법이지만, 본 게시물에서는 보다 효율적으로 처리하기 위하여 S3 오브젝트 이벤트와 AWS Lambda를 활용하는 방법을 소개합니다.

민감 정보 핀셋 제거 자동화 솔루션

배경에서 설명한 것과 같이 기계 학습 훈련 과정 중에 민감 정보만 골라내어 처리하는 방법이 필요합니다. 이미 앞에서 기술하였듯이 S3만 활용하여 기계 학습 훈련을 수행한다면, S3에서 해당 정보를 삭제하는 것만으로도 충분합니다. 그러나 S3는 많은 양의 데이터를 안전하게 보관하기 위한 오브젝트 저장소로는 적합하지만, 기계 학습 훈련 과정을 빠르게 진행하기에는 아쉬운 점이 있습니다. 따라서 SageMaker 노트북과 근접한 위치에 있는 별도의 파일 저장소가 있을 필요가 있고, 이를 위하여 EFS를 활용할 수 있습니다. 기계 학습에 적합한 최적의 데이터 저장소를 선택하는 기준에 대한 것은 Amazon SageMaker 훈련작업을 위한 최적의 데이터소스 선택하기의 내용을 참고하시기를 바랍니다.

S3와 EFS를 함께 사용하여 기계 학습을 수행하는 중에 민감한 정보에 대한 즉시 삭제 요청이 들어오면 핀셋으로 작은 물건을 집어내듯이 세밀한 조작을 통해서 특정 대상만 골라내어 삭제할 방법이 필요합니다. 요구사항은 1) S3와 EFS 볼륨에 분산된 있는 데이터를 요청 즉시 모두 삭제해야 하고 2) 빈번히 발생하는 작업이 아니기 때문에, 요청이 있을 때만 수행될 수 있는 비용 효율적인 방법이어야 하며 3) 격리된 네트워크인 VPC 안에 있는 EFS 볼륨에 접근하여 삭제 작업을 수행해야 하는 간편한 방법을 찾는 것입니다.

이번 예제에서는 위 요구사항을 만족하게 하기 위한 솔루션을 보여 줍니다. SageMaker 노트북이 EFS를 활용하여 기계학습을 하는 상황에서 특정 데이터를 S3와 EFS에서 동시에 삭제하는 방법을 보여줍니다. 예제에서는 기계 학습을 위한 훈련 및 검증용 데이터를 S3에 저장한다고 가정하였고, 학습용 데이터를 EFS 볼륨에 내려받는 상황을 보여 주기 위하여 Lambda 함수를 활용하였습니다. S3에 파일을 업로드 해서 새로운 오브젝트가 생성되면, 자동으로 EFS 볼륨에 같은 파일을 내려받도록 하였습니다. 이 부분은 예제를 위해 간단하게 작성한 것이며, 실제 상황에서는 학습용 데이터를 EFS에 내려받는 정책에 따라 달라질 수 있습니다. 마지막으로, 정보를 삭제 요청 받았다는 상황을 가정하여 S3 버킷에서 오브젝트를 삭제하면 자동으로 Lambda 함수가 EFS 볼륨의 데이터를 삭제합니다. 아래 그림은 솔루션이 적용된 아키텍처를 보여줍니다.

그림 1. 금융환경을 위한 기계 학습 아키텍처

사전 준비 사항

예제를 실행하기 위해서는 다음이 필요합니다.

솔루션 예제는 AWS CDK 버전 2를 기준으로 작성하였습니다.

솔루션 구현

단계 1: CDK 애플리케이션 생성

먼저 CDK 애플리케이션을 생성할 곳에 디렉토리를 생성합니다. 예제에서는 delete_sync 라는 이름을 사용하였습니다. 그리고 생성한 디렉토리 내부로 이동합니다.

mkdir delete_sync
cd delete_sync

이제 CDK 애플리케이션을 생성하겠습니다.

cdk init --language python

CDK 애플리케이션이 생성되었습니다. 이제 실제 동작할 코드를 작성합니다. CDK를 통해서 자동으로 생성된 자원을 살펴보면 CDK 애플리케이션 설정인 cdk.json 파일과 CDK 애플리케이션 코드인 app.py 파일, 그리고 의존성 패키지들을 담은 requirements.txt 파일이 있는 것을 볼 수 있습니다.

단계2: EFS, Lambda, S3 스택(Stack) 정의

스택(Stack)은 CDK로 관리하는 AWS 자원의 묶음입니다. 예제에서는 기계 학습용 데이터를 저장하는 S3 버킷과 EFS 볼륨, 그리고 두 저장소 사이에서 지정한 데이터를 삭제하는 Lambda 함수를 하나의 스택으로 만들 것입니다. 이미 delete_sync 디렉토리 안에는 delete_sync_stack.py 파일이 만들어져 있습니다. 이제 이 파일에 필요한 내용을 추가하여 스택을 구성하겠습니다.

네트워크 구성

아래 예제에서는 가장 먼저 VPC를 생성 합니다. 그리고 Lambda와 S3 그리고 EFS 볼륨 사이에서 안전한 사설 통신이 가능하도록 VPC 엔드포인트(VPC Endpoint)를 설정합니다. AWS 서비스는 기본적으로 인터넷을 통해서 접근할 수 있지만, VPC 엔드포인트를 연결하면 VPC 내부에서 인터넷 연결없이 AWS 서비스에 접근할 수 있도록 만들어 줍니다. S3 서비스에 접근하기 위한 게이트웨이 방식의 VPC 엔드포인트와, Lambda 서비스를 연동하기 위한 인터페이스 방식의 VPC 엔드포인트를 생성합니다.

EFS 볼륨 연결

다음으로, fs라는 이름의 EFS 볼륨을 생성 합니다. EFS 볼륨은 일반적인 용도에 적합한 성능을 내도록 GENERAL_PURPOSE 를 선택하였고, 필요에 따라 버스팅 모드로 동작하도록 설정하였습니다. 또한 데이터 삭제 정책으로 DESTROY를 지정하여 스택이 삭제될 때 볼륨 안의 파일도 함께 삭제하도록 설정하였습니다. 그리고 수명 주기 설정을 AFTER_14_DAYS로 지정하여 14일 동안 한 번도 사용하지 않은 파일은 Infrequent Access (IA) 스토리지 클래스로 전환하도록 설정 하였습니다. 마지막으로 Lambda 함수에서 EFS 볼륨에 접속할 수 있도록 엑세스 포인트(Access Point)를 생성하였습니다.

Lambda 함수 생성

솔루션의 핵심 중 첫 번째는 SageMaker 노트북 내부 사용자로 접속하지 않고도 S3 오브젝트와 EFS 파일 양쪽에 접근하는 것입니다. 이를 위하여 서버리스 컴퓨팅 서비스인 Lambda 함수를 활용하도록 하겠습니다. Lambda는 S3 오브젝트에 접근할 수 있으며, EFS 볼륨에 마운트 할 수 있습니다. 따라서 간단하면서도 쉽게 양 쪽 저장소에 접근 가능합니다.

이제 delete-sync라는 Lambda 함수를 생성 합니다. Lambda 함수는 지정된 VPC와 연동되며, Python 3.8 환경에서 실행되도록 설정하였습니다. 그리고 EFS 볼륨 엑세스 포인트에 마운트 하도록 설정하였고, 최대 실행 시간은 300초로 지정하였습니다. 실제 작업을 수행할 함수의 코드는 별도의 파일로 작성할 예정이며, 스택 생성 코드에 대한 설명을 마친 후에 자세하게 설명하겠습니다.

S3 오브젝트 이벤트 감지 설정

솔루션의 핵심 중 두 번째는 이벤트 기반 처리 방식입니다. 따라서 기능 동작을 위해 항상 컴퓨팅 자원을 실행해 둘 필요없이 실제 사건이 발생할 때 자동으로 컴퓨팅 자원이 할당되고 필요한 작업을 수행합니다. 작업이 끝나면 자동으로 컴퓨팅 자원을 삭제하기 때문에 사용한 만큼만 비용이 청구됩니다. 작업 종료 후 자동으로 자원을 삭제하기 때문에 실수로 인한 비용 낭비도 없습니다.

먼저 S3 버킷을 생성 합니다. 기본적인 접근 제어는 BUCKET_OWNER_FULL_CONTROL 을 지정하여 소유자가 모든 권한을 가지도록 했습니다. 그리고 버킷을 퍼블릭 환경으로 열어두는 것을 차단하는 기능을 설정하였습니다. EFS 볼륨과 마찬가지로 스택을 삭제하면 버킷 안의 데이터를 함께 삭제하도록 설정했습니다. 이러한 삭제 정책은 테스트 환경을 위하여 적용한 것이며, 운영 환경에서는 만약의 사고를 대비하여 스택이 삭제되더라도 데이터는 삭제 않도록 정책을 적용하는 것이 좋습니다.

마지막으로 생성한 버킷에 이벤트 처리기를 등록하여 S3 버킷에 오브젝트를 저장하거나 삭제할 때 Lambda 함수를 호출하도록 설정하였습니다. 시연을 위하여 오브젝트 업로드(생성)와 삭제 작업에 대한 이벤트를 처리하도록 하였으나 솔루션을 실제 적용할 때는 삭제 이벤트만 처리하도록 변경할 수 있습니다.

from aws_cdk import (
    Duration,
    Stack,
    RemovalPolicy,
    aws_ec2,
    aws_efs,
    aws_iam,
    aws_lambda,
    aws_s3,
    aws_s3_notifications
)
from constructs import Construct

class DeleteSyncStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # The code that defines your stack goes here
        vpc = aws_ec2.Vpc(self, "vpc", max_azs = 2)
        vpc.add_gateway_endpoint("s3-vpce", service = aws_ec2.GatewayVpcEndpointAwsService.S3)
        vpc.add_interface_endpoint("lambda-vpce", service = aws_ec2.InterfaceVpcEndpointAwsService.LAMBDA_)

        fs = aws_efs.FileSystem(
            self, "efs", vpc = vpc,
            lifecycle_policy = aws_efs.LifecyclePolicy.AFTER_14_DAYS,
            performance_mode = aws_efs.PerformanceMode.GENERAL_PURPOSE,
            throughput_mode = aws_efs.ThroughputMode.BURSTING,
            removal_policy = RemovalPolicy.DESTROY
        )

        # create a new access point from the filesystem
        # set /export/lambda as the root of the access point
        fs_ap = fs.add_access_point(
            "efs_ap", path="/export/lambda",
            create_acl = aws_efs.Acl(owner_uid="1001", owner_gid="1001", permissions="750"),
            posix_user = aws_efs.PosixUser(uid="1001", gid="1001")
        )

        fn = aws_lambda.Function(
            self, "delete-sync", vpc = vpc,
            code = aws_lambda.Code.from_asset("func"),
            handler = "delete_sync.lambda_handler",
            runtime = aws_lambda.Runtime.PYTHON_3_8,
            timeout = Duration.seconds(300),
            # mount the access point to /mnt/data in the lambda runtime environment
            filesystem = aws_lambda.FileSystem.from_efs_access_point(fs_ap, "/mnt/data")
        )
        fn.role.add_managed_policy(aws_iam.ManagedPolicy.from_aws_managed_policy_name("AmazonS3FullAccess"))
        fn.role.add_managed_policy(aws_iam.ManagedPolicy.from_aws_managed_policy_name("AmazonElasticFileSystemClientReadWriteAccess"))

        bucket = aws_s3.Bucket(
            self, "s3",
            access_control = aws_s3.BucketAccessControl.BUCKET_OWNER_FULL_CONTROL,
            encryption = aws_s3.BucketEncryption.S3_MANAGED,
            block_public_access = aws_s3.BlockPublicAccess.BLOCK_ALL,
            removal_policy = RemovalPolicy.DESTROY,
            auto_delete_objects = True
        )

        trigger = aws_s3_notifications.LambdaDestination(fn)
        trigger.bind(self, bucket)

        # Assign notification for S3 event type (ex. OBJECT_CREATED)
        bucket.add_event_notification(aws_s3.EventType.OBJECT_CREATED_PUT, trigger)
        bucket.add_event_notification(aws_s3.EventType.OBJECT_REMOVED_DELETE, trigger)

예제 1. Lambda 스택 (delete_sync/delete_sync_stack.py)

단계 3: 민감 정보 삭제 함수 구현

이제 파일 삭제를 수행할 함수를 작성합니다. 이 전에 단계에서 작성한 Lambda 함수 생성 코드는 from_asset() 함수를 사용하여 실제 실행할 Python 스크립트를 불러옵니다. 따라서 app.py 와 같은 위치에 func 라는 디렉토리를 생성하고 그 안에 delete_sync.py 파일을 생성해야 합니다. 파일 안에 들어갈 함수의 내용은 아래에 있습니다. 이 함수는 S3에서 오브젝트 삭제 이벤트가 발생하면, 이벤트 정보를 받은 다음 EFS 볼륨 안에 있는 파일을 찾아서 삭제하는 작업을 수행합니다.

가장 먼저 이벤트를 처리하는 lambda_handler() 함수에서는 이벤트 정보에서 오브젝트 이름을 찾습니다. 오브젝트 이름은 EFS 볼륨에서 삭제해야할 파일을 찾을 때 사용합니다. 다음으로 이벤트의 종류가 생성 (ObjectCreated:Put)인 지 아니면 삭제 (ObjectRemoved:Delete)인 지 확인합니다. 생성 이벤트일 경우라면, S3 오브젝트를 EFS 볼륨에 내려받습니다. 반대로 삭제 이벤트일 경우라면, EFS 볼륨에서 같은 이름의 파일을 찾아서 삭제합니다. EFS 볼륨에 파일을 내려 받거나, EFS 볼륨에 있는 파일을 삭제하는 내용은 del_sync() 함수에 있습니다.

import boto3
import json
import logging
import urllib.parse
import os

logger = logging.getLogger()
logger.setLevel(logging.INFO)

s3 = boto3.client('s3')
mnt = '/mnt/data/'

def lambda_handler(event, context):
    logger.info(json.dumps(event))

    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
    event_name = event['Records'][0]['eventName']

    if event_name == 'ObjectCreated:Put':
        gen_obj(bucket, key)

    if event_name == 'ObjectRemoved:Delete':
        del_sync(key)

def del_sync(object_name):
    try:
        path = mnt + object_name
        os.remove(path)

        logger.info('The object has been removed')
        logger.info(object_name)
        logger.info(os.listdir(mnt))
    except Exception as e:
        print(e)
        raise e

def gen_obj(bucket_name, object_name):
    try:
        path = mnt + object_name
        s3.download_file(bucket_name, object_name, path)

        logger.info('An object has been created')
        logger.info(object_name)
        logger.info(os.listdir(mnt))
    except Exception as e:
        print(e)
        raise e

예제 2. 민감 정보 삭제 함수 구현 (func/delete_sync.py)

단계 4: CDK 애플리케이션 생성

CDK 애플리케이션을 실행하기 위하여 Python 가상 환경을 만들고 그 안에 필수 의존성 패키지들을 설치합니다.

source .venv/bin/activate
pip install pip —upgrade
pip install -r requirements.txt

준비가 다 되었습니다. CDK 애플리케이션을 배포하여 AWS 자원을 생성합니다.

cdk deploy --all

솔루션 실행 및 동작 확인

CDK를 통해 생성한 자원은 VPC 네트워크, EFS 볼륨, S3 버킷, Lambda 함수 입니다. 솔루션의 동작을 확인하기 위하여 먼저 데이터 생성을 시도해 보겠습니다. S3 서비스로 이동하면 여러 버킷이 보입니다. 필터에 lambda-data-efs-s3 를 입력하면 CDK로 생성한 버킷만 표시 됩니다. 버킷 이름 뒤에 임의의 문자와 숫자가 붙어 있는 것을 볼 수 있습니다. 이러한 임의의 값은 같은 CDK 코드로 자원 생성 시 중복을 피하기 위하여 자동으로 붙이는 값입니다. 생성된 버킷으로 이동한 다음 파일을 올립니다. 예제에서는 image001.png 파일을 업로드 하였습니다.

그림 2. Amazon S3 버킷

S3 버킷에 파일을 업로드 하였을 경우, EFS 볼륨에 바로 다운로드 하도록 Lambda 함수를 작성하였습니다. 이 작업은 기계 학습을 위하여 S3에 있는 학습 데이터를 SageMaker에 연결된 EFS 볼륨으로 가져오는 상황을 모사한 것입니다. Lambda 함수가 잘 동작했는 지 확인하기 위하여 로그를 살펴보겠습니다. Amazon CloudWatch 서비스로 이동하여 로그 그룹(Log groups)를 선택합니다. S3 버킷과 마찬가지로 lambda-data-sync-efs-s3-deletesync를 필터에 입력하여 원하는 로그 그룹을 찾습니다.

그림 3. Amazon CloudWatch Logs

로그 그룹안에 있는 로그를 선택합니다. 로그를 살펴 보면 S3 버켓에서 image001.png 라는 이름의 오브젝트를 내려받은 다음 파일이름을 표시하고, 바로 이어서 logger.info(os.listdir(mnt)) 결과를 확인할 수 있습니다. 이 것은 EFS 볼륨에 대한 파일 목록 조회 결과이며, image001.png가 EFS 볼륨 안에 있는 것을 확인할 수 있습니다.

그림 4. 오브젝트 생성 시 Lambda 함수 로그

이제 솔루션의 목적인 특정 사용자 정보 삭제를 수행해 보겠습니다. 특정 사용자 정보를 기계 학습 작업에서 즉시 배제해달라는 요청을 받았다고 가정하였으며, 해당 정보를 삭제하기 위하여 S3 버킷으로 이동합니다. 처음에 파일을 올렸던 버킷으로 이동하여 image001.png를 선택합니다. 그리고 선택한 오브젝트를 완전히 삭제합니다.

오브젝트가 삭제되었다는 문구가 표시되었다면, EFS 볼륨에서도 삭제되었는 확인하기 위하여 CloudWatch 서비스로 이동합니다. Lambda 함수 로그를 확인했던 로그 그룹으로 이동하여 새로 올라온 로그를 살펴봅니다. 그림과 같이 image001.png 파일이 표시되고 바로 이어서 빈 목록이 나타납니다. 이것은 목록 조회 결과가 없다는 의미이며, EFS 볼륨에서 선택한 파일이 삭제되었다는 뜻입니다. 제대로 동작하는지 확인하여 위하여 여러 개의 파일을 올려보고, 특정 파일만 삭제해 볼 수 있습니다.

그림 5. 오브젝트 삭제 시 Lambda 함수 로그

리소스 정리하기

향후 불필요한 과금이 청구되는 것을 방지하기 위해서 이 게시글에서 사용한 AWS 리소스는 삭제해 주시길 바랍니다.

cdk destroy —all
rm -rf .env

정리

SageMaker와 EFS를 활용한 기계 학습 환경에서 민감 정보를 자동으로 즉시 삭제하기 위한 S3와 EFS 데이터 삭제 동기화 솔루션을 알아보았습니다. 이 솔루션은 SageMaker 뿐아니라, EFS를 연결하여 사용하는 다양한 애플리케이션에서도 활용할 수 있습니다. 비슷한 고민을 갖고 있는 분들이 있으시다면, 본 예제처럼 비용 효율적인 이벤트 기반 서버리스 솔루션을 활용해 보시길 바랍니다.

Young-ook Kim

Young-ook Kim

김영욱 솔루션스 아키텍트는 다 년간의 클라우드 기반 대규모 글로벌 서비스의 설계, 개발 운영 경험을 바탕으로 다양한 산업군의 고객들이 효율적이고 안정적인 서비스를 운영할 수 있도록 기술 지원을 하고 있습니다. DevOps, 재해복구, 가버넌스, 비용 분석 등의 영역에서 AWS 클라우드를 잘 구성하고 운영할 수 있도록 다양한 모범 사례 공유, 기술 자문 등의 업무를 수행하고 있습니다.