개요

금융권 회사에서 Sagemaker Studio를 Prod 수준으로 구축 한 뒤 내용을 정리해둔다.

필자는 사실 개발자이고 인프라쪽 부서해 속해본적도 devops 부서에 속해본적도 없다.

그러나 서버 아키텍처 관련해서 업무는 꽤 진행해 본 부분이 있고 웹, 빅데이터, AI쪽일을 모두 해봐서 이 경험을 토대로 설계해보았다.
(무엇보다 주안점을 둔 것은 금융권에 부합하는 보안을 확보하기 위해 최대한 고려하였다.)

해당 글에서는 Sagemaker Studio를 구축한 뒤 실제로 사용하기 위한 기본 기능들을 검증하여 소개한다.

플랫폼 구축

설계 및 구현한 아키텍처는 아래와 같다.

플랫폼 구축에 고려한 점

구축에 고려한점은 아래와 같다.

VPC 및 인터넷 통신

  • sagemaker studio는 격리된 vpc 환경 하에서 실행되며 필요시 Nat Gateway로 외부망 통신을 한다.
    • routing table 관리로 인터넷 여부는 수동 컨트롤 하며 더 고도화 되면 squid proxy의 도입도 가능할듯 하다.
  • VPC 내의 모든 네트워크 통신은 VPC Flowlog로 로깅함

Endpoint 사용

  • AWS Region 내 vpc 외부 통신은 모두 Endpoint를 통해 효율적으로 통신한다.
    • public망으로 다시 나갔다가 들어오는 것은 비용이든 보안이든 비효율적이기 때문
  • s3는 endpoint gateway로 만들어 scalability를 확보했다.

S3

  • sagemaker studio에서 로컬 작업은 EFS, 영구 저장은 S3 Bucket으로 설계하였다.

KMS를 통한 암호화

  • EFS, S3 Bucket은 SSE-CMK를 통해 데이터를 암호화 하였다.
    • S3는 부가적으로 Bucket Key를 활성화 하여 KMS 트래픽 및 비용을 최소화 하였다.
  • S3는 KMS 암호화 없는 upload가 되지 않도록 policy를 추가하였다.

Enterprise 보안

  • Sagemaker IAM을 정의하여 최소한의 권한만 부여
  • endpoint에도 보안을 걸어 필요한 최소한의 버킷만 접근가능하게 함
  • security group으로 주피터 통신 및 필요한 최소한의 포트만 허용
  • s3상에서도 보안을 따로 적용

접속방법

아래와 같은 식으로 접속 할 수 있다.

접속주소 : https://{account_id}.signin.aws.amazon.com/console/ 와 같다.

접속하면 관리 콘솔 화면을 볼 수 있다.

Services 클릭 후 Amazon SageMaker를 클릭한다.


왼쪽 메뉴에서 Studio를 클릭하면 SageMaker 도메인 메뉴가 나온다.

이미 생성된 계정 중에 본인 계정의 앱시작 버튼을 누른다.

width:600px

이중에서 앱 시작 -> Studio 클릭 시 Sagemaker Studio를 시작한다.

스튜디오가 실행되면 노트북 실행이 정상동작하는지 확인해보자.

File -> New -> Notebook을 클릭한다.

노트북 실행 시 사용할 도커이미지 및 커널을 선택하라고 나온다.

회사에서 쓰려면 아래 그림처럼 커스텀한 이미지를 만들어놓고 선택하면 된다.

스튜디오 간의 보안정책

Sagemaker Studio IDE는 본인만 사용할 수 있게 보안설정을 하였다. 이유는 간단한데 상식적으로 각자 사용하는 개발툴을 서로 공유해서 쓰지는 않기 때문이다.

이를 위해 보안 쪽에 트릭을 좀 걸었다. 더 나아가 팀별로 공유하여 사용하는것도 보안정책 튜닝을 통해 가능하다.

만약 협업 필요 시 노트북 공유(읽기전용)를 통해 가능하다. 더불어 코드 저장소는 공동사용 영역이기 때문에 운영에 크게 불편함은 없을듯 하다.

아래와 같이 다른사람의 노트북에 접속 시도 시 접근이 제한된다.


형상관리 연동방법: 기초 설정

git을 제대로 사용하기 위해 주피터나 터미널 창에서 git 기본정보 설정을 수행한다.

더불어 ssh 기반 키 인증이 필요하면 file -> new -> terminal 통해서 직접 ssh key를 심으면 된다.

aws code commit을 통한 git clone을 맛만 보자.

원하는 repo에 HTTPS 아이콘을 클릭하면 git repo 주소를 복사 할 수 있다.

스튜디오 좌측에서 Git모양 아이콘을 클릭 후 Clone a Repository를 선택한다.

위에서 획득한 HTTPS 주소를 입력하고 CLONE 한다.

클론이 완료되면 / 경로 아래에 클론된 디렉토리가 표시된다.

아래의 신규파일을 Git Commit 후 Push 해보기로 하자.

아래 네모 영역에 마우스를 갖다대서 +를 하면 stage영역으로 파일이 이동한다.

Stage 영역으로 옮겨진 2개의 파일을 확인 할 수 있다.

커밋메세지를 적고 커밋 버튼을 눌러보자.

width:200px

정상적으로 커밋한 뒤 네모 아이콘을 눌러 원본 repo로 push한다.

정상적으로 git push가 완료됨을 알려준다.

노트북 공유

필요시 읽기전용 으로 내 노트북을 동료에게 공유할 수 있다.

실제 노트북 공유파일은 sagemaker studio 구축 시 지정한 s3 bucket에 저장된다. 이 부분의 권한이나 KMS 설정이 제대로 되지 않으면 노트북 공유 기능이 정상동작 안할수 있으니 참고하자.

공유를 원하는 노트북에 들어가 Share를 클릭한다.

필요에 따라 output 포함여부를 선택하고 Create를 누른다.

노트북 공유용 URL이 생성되었다. 이 링크를 공유하면 된다.

타 계정에서 노트북 공유 링크에 접속해보면 읽기 전용이라는 경고와 함께 노트북을 볼 수 있다.

필요시 노트북을 복사해서 내 스튜디오에서 돌려 볼 수 있다.

S3 사용방법

kms연동한 s3로 CRUD하는 코드를 직접 작성하였다. 혹시 필요한 사람은 참고하자.


import os import boto3 from botocore.client import Config from Logger import Logger class S3Manager(object): def __init__(self, bucket_name='버킷이름'): self.logger = Logger.get_logger() self.cli = boto3.client('s3', config=Config(signature_version='s3v4')) self.bucket_name = bucket_name self.s3_kms_id = 'kms' def list_buckets(self): response = self.cli.list_buckets() return response def put_object(self, key_id, body): response = self.cli.put_object(Bucket=self.bucket_name, Key=key_id, Body=body, ServerSideEncryption='aws:kms', SSEKMSKeyId=self.s3_kms_id) return response def get_object(self, key_id): try: response = self.cli.get_object(Bucket=self.bucket_name, Key=key_id) return response['Body'].read() except self.cli.exceptions.NoSuchKey: self.logger.info(f'{key_id} does not exist.') return None def delete_object(self, key_id): response = self.cli.delete_object(Bucket=self.bucket_name, Key=key_id) return response def upload_dir(self, local_dir_path, s3_dir_path): self.logger.info('Uploading results to s3 initiated...') self.logger.info(f'local_path:{local_dir_path}, s3_path:{s3_dir_path}') try: for path, subdirs, files in os.walk(local_dir_path): for file in files: dest_path = path.replace(local_dir_path, '') s3file_path = os.path.normpath(s3_dir_path + '/' + dest_path + '/' + file) local_file_path = os.path.join(path, file) self.cli.upload_file(local_file_path, self.bucket_name, s3file_path, ExtraArgs={'ServerSideEncryption': 'aws:kms', 'SSEKMSKeyId': self.s3_kms_id}) self.logger.info(f'upload : {local_file_path} to Target: {s3file_path} Success.') except Exception as e: self.logger.info(e) raise e def upload_file(self, local_file_path, s3file_path): try: self.cli.upload_file(local_file_path, self.bucket_name, s3file_path, ExtraArgs={'ServerSideEncryption': 'aws:kms', 'SSEKMSKeyId': self.s3_kms_id}) self.logger.info(f'upload : {local_file_path} to Target: {s3file_path} Success.') except Exception as e: self.logger.info(e) raise e def download_dir(self, s3_dir_path, local_dir_path=os.getcwd()): self.logger.info('Downloading results to s3 initiated...') self.logger.info(f's3_path:{s3_dir_path}, local_path:{local_dir_path}') bucket = boto3.resource('s3').Bucket(self.bucket_name) for obj in bucket.objects.filter(Prefix=s3_dir_path): local_obj_path = os.path.join(local_dir_path, obj.key) if not os.path.exists(os.path.dirname(local_obj_path)): os.makedirs(os.path.dirname(local_obj_path)) bucket.download_file(obj.key, local_obj_path) self.logger.info(f'download : {obj.key} to Target: {local_obj_path} Success.') def download_file(self, s3_file_path, local_file_path): bucket = boto3.resource('s3').Bucket(self.bucket_name) bucket.download_file(s3_file_path, local_file_path) self.logger.info(f'download : {s3_file_path} to Target: {local_file_path} Success.') def list_objects(self, prefix='', delimiter=''): response = self.cli.list_objects_v2(Bucket=self.bucket_name, Prefix=prefix, Delimiter=delimiter) return response['Contents'] if 'Contents' in response else None def list_dir(self, prefix='', delimiter='/'): response = self.cli.list_objects_v2(Bucket=self.bucket_name, Prefix=prefix, Delimiter=delimiter) list = [] if 'Contents' in response: list += [item['Key'] for item in response['Contents'] if 'Key' in item] if 'CommonPrefixes' in response: list += [prefix['Prefix'] for prefix in response['CommonPrefixes']] return list

실행 인스턴스 성능 변경

스튜디오 오른쪽 상단에 cpu 표시된 부분을 클릭하면 실행자원을 변경 할 수 있다.

필요시 GPU 모델링도 가능하다.

과금과 연결되므로 오남용은 주의하여야 한다.