AWS S3

게시: by Creative Commons Licence




░ AWS S3


1. 참조

언제나 그렇듯 출발은 공식 문서이다.

AWS 공식문서 - Amazon S3란 무엇입니까?

어떤 기술문서이든 영문이든 국문이든 상관없이 어느 정도 알지 못하면 읽기 어렵다.


다음 블로그도 좋다. 이 포스트에서 많이 참고하였다.

이한영의 블로그- AWS EC2 (Ubuntu)에 Nginx와 uWSGI를 사용하여 Django 배포

나채원의 블로그- [Deploy] Django 프로젝트 배포하기 - 7. Amazon S3




2. Amazon S3 기본 개념

Amazon S3 ( Amazon Simple Storage Service )는 인터넷용 스토리지 서비스이다. 개발자는 S3가 제공하는 간단한 웹 서비스 인터페이스를 사용하여 웹에서 언제 어디서나 원하는 양의 데이터를 저장하고 검색할 수 있다.


1) 버킷 - Amazon S3에 저장된 객체의 컨테이너.

  • 모든 객체는 어떤 버킷에 포함된다.
    • abc라는 이름의 버킷에 속한 photos/puppy.jpg
    • 해당 URL은 http://abc.s3.amazonaws.com/photos/puppy.jpg
  • 가장 높은 수준에서 Amazon S3의 네임스페이스를 구성함.
  • 스토리지 및 데이터 전송 요금을 책임질 계정을 식별하는 기준이 됨.


2) 객체 - S3에서 저장 기본 개체

  • (이름) - 버킷 안에서 객체를 고유하게 식별하는 식별자로서 객체와 1:1 대응관계를 가진다.
  • 객체의 구성 = 객체 데이터 + 메타데이터
  • 객체 데이터는 볼 수 없고, 메타데이터에 객체를 파이썬 딕셔너리 형태로 설명함


3) 리전 - 사용자는 자신의 버킷을 저장할 리전을 선택할 수 있다.

  • 특히 아시아 태평양(서울) 리전 - 서울의 Amazon S3 서버 사용
  • 지연 시간 / 비용 줄이기, 요구 사항 준수


4) Amazon S3 데이터 일관성 모델

  • 모든 리전의 S3 버킷에 있는 객체들에게 읽기 후 쓰기의 일관성을 제공함.


위와 같은 개념을 가지고 Amazon S3에서 ➀ 버킷 만들고 ➁ 데이터를 버킷에 저장하여 객체를 만들며 ➂ 데이터를 다운로드하는 등에 ➃ 접근권한을 설정할 수 있고 ➄ 이러한 모든 것들을 개발할 수 있도록 REST API 또는 AWS SDK를 제공한다.




3. 이용하기


1) IAM유저에 S3접근권한 부여

  • AWS 로그인 후 AWS Management Console에서 AWS 서비스 - IAM 선택

aws service IAM

  • 대시보드에서 사용자 선택 - 보안상태에서 보안수준이 높을 수록 좋다.

aws dashboard

  • 사용자 이름 선택

aws user id

  • 권한 추가 클릭

aws iam permission

  • 권한 부여 - 기존 정책 직접 연결 - AmazonS3FullAccess

aws AmazonS3FullAccess


2) 버킷 생성하기

AWS 공식문서- 버킷 규제 및 제한

  • AWS 계정 당 100개의 버킷을 만들 수 있다.
  • 버킷 이름은 DNS 이름 지정 방식을 준수해야 한다.


➀ GUI방식- S3 콘솔 사용

AWS 공식문서-How Do I Create an S3 Bucket?

여기에 영문 설명이 잘 되어 있습니다.

aws s3 console


➁ boto3 - AWS SDK for Python

이 포스트에서는 파이썬용 AWS SDK인 boto3를 사용할 것이다. boto3는 AWS의 거의 모든 서비스를 다룰 수 있다. 여기서는 Amazon S3에 대한 것만 소개한다.

boto3 Code Examples- Amazon S3

boto3 User Guides-Amazon S3

boto3 공식문서

boto3가 제공하는 AWS 서비스들

AWS 공식문서- 버킷 생성 예제


import boto3

# S3 client 생성
s3 = boto3.client('s3')

# 클라이언트의 요청 - 버킷 생성하기 
s3.create_bucket(
  Bucket='<버켓이름>', 
  CreateBucketConfiguration={
    'LocationConstraint': 'ap-northeast-2'
  }
)




✔ boto3

boto3 공식문서 - Boto is the Amazon Web Services (AWS) SDK for Python, which allows Python developers to write software that makes use of Amazon services like S3 and EC2.


1) boto3를 사용하는 이유

  • REST API 요청 ( AWS 공식문서 - REST API를 사용하여 요청 )

    • Amazon S3 저장소에 있는 my_image.jpg를 삭제하려면, 다음과 같이 REST API 요청을 보내야 한다.
      DELETE /my_image.jpg HTTP/1.1
      Host: bucket.s3.amazonaws.com
      Date: Wed, 12 Oct 2009 17:50:00 GMT
      Authorization: <authorization string>
      Content-Type: text/plain
    
  • 파이썬에서 위 REST API 요청을 처리하기

    • requests 모듈 등을 사용하여 아래처럼 작성한다.

      import requests
      
      def delete(file_name):
          url = 'http://bucket.s3.amazonaws.com/{}'.format(file_name)
          response = requests.delete(url, credentials=...)
          return response == 204
      
  • 각양각색의 여러 REST API 요청에 대응할 수 있는 파이썬 코드를 일일이 작성하며 고생하기보다 누군가 잘 작성한 코드를 가져다 쓰는 것이 낫다.

  • 파이썬의 강점인 모듈화와 코드의 재사용성을 누리기 위해 boto3 파이썬 패키지를 사용한다.


2) boto3 설치/설정/간단 사용법

(1) 설치

➜  pip install boto3

(2) 환경설정

설치 후 사용 전 AWS 계정에 대한 인증 작업(authentication credentials)과 리전 등에 대한 환경설정이 필요하다.

  • 보통 IAM Console을 통해 인증작업이 이루어진다.

  • 만약 AWS CLI가 설치되었다면 ( ~/.aws/credentials에 인증파일을 작성한다고 가정한다.) 다음과 같이 인증한다.

    ➜  aws configure
    
    # ~/.aws/credentials
    [default]
    aws_access_key_id = <YOUR_ACCESS_KEY>
    aws_secret_access_key = <YOUR_SECRET_KEY>
    
    # ~/.aws/config : 리전(보통 서울)을 정해야 한다. 
    [default]
    region=ap-northeast-2
    

(3) 사용

import boto3

# Amazon S3를 사용하자 
s3 = boto3.resource('s3')

# 내 계정의 버킷이름들 보기
for bucket in s3.buckets.all():
    print(bucket.name)
# 파일 업로드
data = open('test.jpg', 'rb')
s3.Bucket('<my-bucket>').put_object(
    Key='test.jpg',
    Body=data
)




4. Django와 연결하기


1) 개요 - Django의 파일저장시스템

☛참조 File storage API

이제까지 Amazon S3의 기본개념, IAM 유저에 제한된 S3 접근권한을 부여하는 방법, S3에 버킷을 생성하는 방법 등을 배웠다. 특히 S3에 버킷을 생성하는데에 파이썬용 AWS SDK인 boto3를 사용하는 방법이 있고, 이것이 S3에 대한 REST API 클라이언트 요청을 파이썬으로 구현한 것임을 알게 되었다.

이제 남은 작업은 Django 프로젝트에서 생성한 static 파일들이나 media 파일들을 Amazon S3의 버킷에 저장/수정/삭제/추가를 할 수 있는 저장시스템을 구축하는 것이다.

문제는 Django의 기본 저장시스템(storage system)이 파일저장시스템(File storage system)이므로 Amazon S3와 통신하며 위 남은 작업을 처리할 수 있는 기능이 필요하다. 이 기능을 직접 파이썬으로 구현할 수 있으나 누군가 잘 작성한 모듈을 재사용하는 것이 낫다.

이 포스트에서는 그러한 기능을 담당할 모듈로 django-storage 파이썬 패키지를 사용한다.


참고로 Django의 built-in 파일 저장 시스템을 살펴보자.

  • FileSystemStorage 클래스에서 Storage 클래스를 상속받는다.
  • File관리에 필요한 delete, path, exists, listdir, url 등 메서드들을 직접 정의해주어야 함을 알 수 있다.
# django.core.files.storage

class Storage(object):
    # A base storage class
    def open(self, name, mode='rb'):
        return self._open(name, mode)  
    def save(self, name, content, max_length=None):
        return self._save(name, content)
    def delete(self, name):
        raise NotImplementedError('subclasses of Storage must provide a delete() method')
    def exists(self, name):
        raise NotImplementedError('subclasses of Storage must provide an exists() method') 
    def listdir(self, path):
        raise NotImplementedError('subclasses of Storage must provide an exists() method') 
        
        
@deconstructible        
class FileSystemStorage(Storage):
    # Standard filesystem storage
    def __init__(self, location=None, ...):
        ...
    def _open(self, name, mode='rb') :
    def _save ...
    def exists(self, path):
    ...



2) django_storages

django-storages 공식문서 - Amazon S3에 연결하기

django-storages 공식문서

AWS Signature Version 4

  • django_storages 파이썬 패키지는 원격 저장소(Amazon S3, Azure Storage, DropBox, Google Cloud Storage 등)와 통신할 수 있는 저장 시스템 클래스를 미리 구현하였다.


➀ 설치

# 터미널에서 
➜  pip install django_storages


➁ settings.py

  • INSTALLED_APPS에 'storages'를 추가한다.
# config.settings
INSTALLED_APPS = [
    ...
    'storages',
]


  • s3 관련 설정 - storage 위치와 URL 주소 그리고 AWS S3 버킷에 대한 접근권한을 다룬다.
# S3 Storage
## Django의 기본저장시스템클래스인 FileSystemStorage에서 다음과 같이 오버라이드한다. 
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
## ./manage.py collectstatic명령으로 생성된 static폴더를 S3버킷저장소로 정한다. 
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
## 미디어 파일의 URL설정: 도메인/media/<파일명>
MEDIAFILES_LOCATION = 'media'
## 스테틱 파일의 URL설정: 도메인/static/<파일명>
STATICFILES_LOCATION = 'static'

# AWS Access
## IAM 유저의 credentials.csv에 AWS Access 정보가 있다. 
AWS_ACCESS_KEY_ID = config_secret['aws']['id']
AWS_SECRET_ACCESS_KEY = config_secret['aws']['key']
AWS_STORAGE_BUCKET_NAME = config_secret['aws']['bucket']


  • 공식문서에서는 AWS Signature Version 4를 사용할 것을 권고한다.
    • 거의 대부분의 리전은 위 s3v4 버전을 지원한다.
    • S3에 대한 접근은 인증된 요청Anonymous의 요청으로 나뉘는데, SDK 요청은 모두 인증 요청인데 비해 웹에서 a태그를 클릭하는 것은 Anonymous요청이다. 알 수 없는 곳으로부터의 링크 요청에 대해 S3는 보안상 (클릭할 때마다) url의 인증 문자열을 바꾸는 것으로 보인다.
    • 따라서 이로 인하여 링크가 되지 않는 문제를 겪지 않으려면 위 s3v4 버전을 사용해야 한다.
# AWS_S3_SIGNATURE_VERSION
AWS_S3_SIGNATURE_VERSION = 's3v4'
AWS_S3_REGION_NAME = 'ap-northeast-2'


➂ storages.py

media 파일static 파일을 나누어서 저장하기 위해 다음과 같이 Customize를 한다.

from django.conf import settings
from storages.backends.s3boto3 import S3Boto3Storage

class StaticStorage(S3Boto3Storage):
    # 메서드 오버라이드
    location = settings.STATICFILES_LOCATION

class MediaStorage(S3Boto3Storage):
    location = settings.MEDIAFILES_LOCATION



✔ CORS Policy

  • 보안상의 이유로, 브라우저들이 스크립트 내에서 cross-origin HTTP요청(a도메인에서 b도메인의 자료를 요청하는 경우)을 제한하는 정책이다.
  • S3 > 권한 > CORS 구성 (HTTP 접근 제어)
  • CORS Policy때문에 이미지 파일이 링크되지 않을 수 있다.


S3 CORS



3) 테스트- 미디어파일/정적파일 저장해보기


➀ Django 프로젝트 구조

<컨테이너>
 ├── .git/ 
 ├── .gitignore
 ├── .idea/
 ├── .python-version
 └── <프로젝트>
      ├── manage.py
      ├── static
      │    └── test.txt    #
      ├── template
      │    └── index.html  # 
      ├── config
      │    ├── __init__.py
      │    ├── settings.py
      │    ├── storages.py  # S3버킷에 media/static 분리저장    
      │    ├── urls.py
      │    └── wsgi.py
      └── <장고앱>
           ├── __init__.py
           ├── admin.py
           ├── apps.py
           ├── migrations
           ├── models.py
           ├── tests.py
           └── views.py


➁ Django App - MVT 중 MV


  • models.py
class Image(models.Model):
    img = models.ImageField(upload_to='usr')


  • views.py
def show_img(request):
    if request.method == 'POST':
        img = request.FILES.get('img-file')
        Image.objects.create(img=img)
        return redirect(show_img)
    else:
        img = Image.objects.first()
    context = {
        'object': img
    }
    return render(request, 'index.html', context)


  • urls.py
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^$', show_img, name="show_img"),
]


➂ Template

  • index.html
# index.html 
<body>
    <h1>TEST!</h1>
    <form action="" method="post" enctype="multipart/form-data">
        {% csrf_token %}
        <input type="file" name="img-file">
        <input type="submit">
    </form>
{% if object.img %}
    <img src="{{ object.img.url }}" alt="">
{% else %}
{% endif %}
</body>


➃ 확인

➜  ./manage.py makemigrations
➜  ./manage.py migrate
➜  ./manage.py collectstatic

You have requested to collect static files at the destination
location as specified in your settings.

This will overwrite existing files!
Are you sure you want to do this?

Type 'yes' to continue, or 'no' to cancel: 


  • S3 버킷 확인하기

s3 bucket


  • 웹 브라우저에서 확인

s3 bucket