[django] 장고에 S3연결하기. 프로필 사진 업로드, 조회 기능 만들기

     

목차

     

     

    🌃 서론

    옛날이나 지금이나 파일 업로드/다운로드는 어떤 서비스를 만들던지 필요했던 기능이다. 그냥 게시판을  만들어도 사진 업로드 기능이 필요하기 때문이다. 요즘엔 클라우드를 통해 인프라를 쉽게 구축할 수 있는 만큼 대용량의 사진들도 쉽게 서버에 업로드하여 서비스할 수 있게 되었다. AWS에 S3는 이 기능을 사용하기 적합한 클라우드 서비스 중 하나이다.

     

    이번 포스팅에서는 장고에서 S3를 연동하기 위해 S3 버킷 생성부터 사진 업로드 API까지 만들어볼 예정이다.

     

     

     

    🪣 S3 버킷 만들기

    먼저 할일은 S3 버킷을 만드는 것이다. AWS를 사용하기 위한 가입절차는 이 포스팅에서는 생략한다.

     

    AWS 콘솔에서 S3를 검색해서 S3메뉴로 가면 버킷을 만들 수 있다. S3 버킷을 만들면서 특별한 설정을 할 게 없다. 

     

    이름 설정

     

     

    버킷의 이름을 정하고 객체 소유권은 권장 설정을 사용한다. 

     

    중요한 설정

     

    중요한건 아래 있는 모든 퍼블릭 액세스 차단이다. 디폴트로 이게 켜져 있는데, 퍼블릭 차단이 켜져 있으면 외부에서 s3접근이 안된다. 즉 이미지를 올려도 사용자가 볼 수 없다는 뜻이다. 

     

    어차피 서버에서 사용해서 클라로 내려주는 거 아니에요?라고 생각할 수 있다. 하지만 이미지를 사용하는 대부분의 서비스는 아래와 같은 구조로 구성되어 있다.

     

    이미지 저장 서비스 FLOW

     

    이미지를 저장하는 과정은, 클라에서 이미지를 업로드하면 우선 1. S3에 파일을 업로드 한다. 그 후 2. 업로드한 파일의 경로를 별도의 DB에 저장한다. 파일을 저장하지 않고 경로를 저장한다는 것이 중요하다.

     

    이미지 사용 서비스 FLOW

     

    클라에서 이미지를 사용할 때는 1. 서버에 먼저 이미지의 경로를 물어본다. 그럼 서버는 DB에 저장된 이미지 경로를 내려주고, 클라는 2. 이 경로를 활용하여 다시 S3에 이미지를 조회한다. 이때 대부분의 클라는 사용자들의 PC 거나 스마트폰이다. 따라서 어떤 IP에서 접속할지 모르고 공개된 인터넷망을 사용하기 때문에 퍼블릭망에서 접근하게 된다. 만약 우리가 S3에 퍼블릭 접근을 차단한다면, 클라에서 해당 이미지를 조회할 수 없게 되기 때문에 이미지를 볼 수 없다.

     

     

    그 외 설정들

    그 외 설정들은 그냥 디폴트를 사용한다.

     

    버킷 생성 완료

     

    버킷 만들기를 누르면 S3에 버킷이 생성된다.

     

     

    🧑‍💻 django에 s3 연결 세팅하기

     

    django에서 s3에 연결하기 위해서는 boto3라는 패키지가 필요하다. boto3는 django에서 aws cli를 사용하기 위한 패키지이다.

     

    pip install boto3

     

    boto3을 사용해서 aws s3에 접근하기 위한 인증방법은 여러 가지가 있다. 

     

    1. session_profile or AWS_S3_SESSION_PROFILE
    2. access_key or AWS_S3_ACCESS_KEY_ID or AWS_S3_SECRET_ACCESS_KEY
    3. secret_key or AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY
    4. The environment variables AWS_S3_ACCESS_KEY_ID and AWS_S3_SECRET_ACCESS_KEY
    5. The environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
    6. Use Boto3’s default session

     

    공식 문서에는 위 6가지 방법을 제시하는데, 나는 여기서 5번을 사용할 예정이다. S3에 접근하기 위한 별도의 key를 발급하는 방법이다. 

     

    키를 만들기 위해서 AWS 콘솔에서 IAM메뉴로 들어간다.

     

    사용자

     

    왼쪽에 메뉴에서 사용자를 눌러서 신규 사용자를 추가한다.

    사용자 생성

     

    이름을 대충 정하고 다음을 누르면 권한 설정이 나오는데, 직접 정책 연결을 누른 후 S3 FullAccess를 선택해 준다. 여기서 선택하는 권한이 이 사용자가 할 수 있는 권한이다. 우리는 S3접근이 필요한 사용자가 필요하기 때문에 이거만 선택!

    필요한 권한만 주기

     

    다음을 누르고 사용자 생성을 누르면 사용자가 만들어진다.

     

    사용자 생성 완료

     

    사용자 상세 정보로 들어가서 궈보안 자격 증명을 누른 후 아래로 내려가면 액세스 키라는 메뉴가 있다. 여기서 액세스 키 만들기를 누르면 Access_key와 Secret_key를 만들어준다.

    액세스 키 만들기

     

    CLI로 만들기

     

    사용은 CLI로 체크하면 된다. 

     

    그후 태그값을 정하면 Access_Key와 Secrey_Key를 얻을 수 있다. 이 값을 환경변수를 이용해 django에 세팅하자.

     

    # settings.py
    
    # S3 setting
    AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID', '방금 만든 access_key')
    AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY', '방금 만든 secret_key')
    AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME', 'cholol.tistory.com')
    AWS_S3_REGION_NAME = os.environ.get('AWS_S3_REGION_NAME', 'ap-northeast-2')

     

     

    자 이제 django에서 s3를 접근 테스트를 위해 python shell에서 아래와 같이 입력해 보자.

     

    In [1]: import boto3
    
    In [2]: from settings import AWS_S3_REGION_NAME, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_STORAGE_BUCKET_NAME
    
    In [3]: s3_client = boto3.client('s3',  aws_access_key_id=AWS_ACCESS_KEY_ID, aws_secret_access_key=AWS_SECRET_ACCESS_KEY, region_name=AWS_S3_REGION_NAME)
    
    In [4]: s3_client.list_buckets()
    Out[4]: 
    {'ResponseMetadata': 
    ...
     'Buckets': [{'Name': 'cholol.tistory.com',
       'CreationDate': datetime.datetime(2024, 5, 3, 12, 56, 42, tzinfo=tzutc())},
     'Owner': {'ID': 'xxxxx'}}

     

     

    정상적으로 s3에 접근하는 것을 확인할 수 있다.

     

     

    🆙 파일 업로드 코드 만들기

     

    파일 업로드는 간단하게 만들 수 있다. 공통 함수로 만들어서 쉽게 쓸 수 있게 하자

     

    def s3_file_upload_by_file_data(upload_file, region_name, bucket_name, bucket_path, content_type=None, extension=None):
        bucket_name = bucket_name.replace('/', '')
        if content_type:
            content_type = content_type
        else:
            content_type = upload_file.content_type
        if extension:
            extension = extension
        else:
            extension = upload_file.name.split('.')[-1]
    
        now = datetime.now()
        random_str = get_random_text('A', 20)
        random_file_name = f"{now}_{random_str}.{extension}"
        upload_file_path_name = f"{bucket_path}/{random_file_name}"
    
        try:
            upload_file.seek(0)
        except Exception:
            pass
    
        s3 = boto3.resource('s3', region_name=region_name, aws_access_key_id=settings.AWS_ACCESS_KEY_ID, aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY)
    
        if s3.Bucket(bucket_name).put_object(Key=upload_file_path_name, Body=upload_file, ContentType=content_type, ACL='public-read') is not None:
            return f"https://s3-{region_name}.amazonaws.com/{bucket_name}/{bucket_path}/{random_file_name}"
    
        return False

     

     

    해당 함수에 file과 s3설정값들을 넣으면 파일을 업로드한 path를 리턴하게 된다. s3에 저장할 때 어떤 파일명으로 저장할지 정할 수 있는데 나는 20글자의 랜덤 알파벳으로 저장하게 했다. 실제로 파일에 key값은 DB에 저장하고, 그 key로 파일 경로를 찾을 것이기 때문에 사실 파일명은 의미가 없다고 생각한다.

     

    파일 다운로드는 실제로 클라에서 하기 때문에 서버에 구현할 필요가 없다.

     

     

     

    📝 마무리

    s3 사용이 이미 보편화되어서 그런지 예전에 했을 때랑 코드가 달라진 게 없다. 패키지도 많고, 찾아보면 더 편한 패키지들이 많을 것이다. 여기서 백엔드의 성능을 더 높이기 위해 s3_client를 singleton으로 만들어볼 수 있을 것 같은데, 그 정도로 많은 호출이 필요하다면 뭔가 더 체계적인 구조로 다시 만들어야 할 것 같다 ㅎㅎ

     

     

     

    반응형

    댓글

    Designed by JB FACTORY