Python과 함께 Cloud Storage 사용

Python용 Bookshelf 가이드 중 이 부분에서는 샘플 앱이 Google Cloud Storage에 이미지를 저장하는 방법을 보여줍니다.

이 페이지는 여러 페이지로 구성된 가이드의 일부입니다. 처음부터 시작하여 설정 안내를 보려면 Python Bookshelf 앱으로 이동하세요.

Cloud Storage 버킷 만들기

Cloud Storage를 사용하면 바이너리 데이터를 저장하고 제공할 수 있습니다. 버킷은 바이너리 객체의 상위 컨테이너입니다.

다음 안내에서는 Cloud Storage 버킷을 만드는 방법을 보여줍니다. 버킷은 Cloud Storage에서 데이터를 보관하는 기본 컨테이너입니다.

  1. 터미널 창에서 다음 명령어를 입력합니다.

    gsutil mb gs://[YOUR-BUCKET-NAME]

    입력할 항목의 의미는 다음과 같습니다.

    • [YOUR-BUCKET-NAME]은 Cloud Storage 버킷의 이름을 의미합니다.
  2. 버킷의 기본 액세스제어 목록(ACL)을 public-read로 설정하여 Bookshelf 앱에 업로드된 이미지를 확인합니다.

    gsutil defacl set public-read gs://[YOUR-BUCKET-NAME]

    설정 구성

    이 섹션에서는 3-binary-data 디렉토리의 코드를 사용합니다. 이 디렉토리에서 파일을 수정하고 명령어를 실행하세요.

    1. 수정하기 위해 config.py를 엽니다.
    2. PROJECT_ID 값을 GCP 콘솔에 표시되는 프로젝트 ID로 설정합니다.
    3. DATA_BACKEND구조화된 데이터 사용 가이드에서 사용한 값과 같은 값으로 설정합니다.
    4. Cloud SQL 또는 MongoDB를 사용하는 경우, Cloud SQL 또는 Mongo 섹션의 값을 구조화된 데이터 사용 단계에서 사용한 값과 같은 값으로 설정합니다.
    5. CLOUD_STORAGE_BUCKET 값을 Cloud Storage 버킷 이름으로 설정합니다.

    6. config.py를 저장하고 닫습니다.

    Cloud SQL을 사용하는 경우 다음을 수행합니다.

    1. 수정하기 위해 app.yaml을 엽니다.
    2. cloud_sql_instances 값을 config.py CLOUDSQL_CONNECTION_NAME에 사용한 값과 같은 값으로 설정합니다. project:region:cloudsql-instance 형식이어야 합니다. 이 전체 줄의 주석 처리를 삭제합니다.
    3. app.yaml을 저장하고 닫습니다.

    종속 항목 설치

    다음 명령어를 입력하여 가상 환경을 만들고 종속 항목을 설치합니다.

    Linux/macOS

    virtualenv -p python3 env
    source env/bin/activate
    pip install -r requirements.txt
    

    Windows

    virtualenv -p python3 env
    env\scripts\activate
    pip install -r requirements.txt
    

    로컬 머신에서 앱 실행:

    1. 로컬 웹 서버를 시작합니다.

      python main.py
      
    2. 웹브라우저에서 다음 주소를 입력합니다.

      http://localhost:8080

    이제 앱의 웹페이지를 찾아보고 표지 이미지가 있는 도서를 추가, 편집, 삭제할 수 있습니다.

    작업자를 종료한 다음 로컬 웹 서버를 종료하려면 Control+C를 누르세요.

    App Engine 가변형 환경에 앱 배포

    1. 샘플 앱을 배포합니다.

      gcloud app deploy
      
    2. 웹브라우저에서 다음 주소를 입력합니다. [YOUR_PROJECT_ID]를 프로젝트 ID로 바꿉니다.

      https://[YOUR_PROJECT_ID].appspot.com
      

    앱을 업데이트하는 경우 앱을 처음 배포할 때 사용한 같은 명령어를 입력하여 업데이트된 버전을 배포할 수 있습니다. 새로 배포하면 앱의 새 버전을 만들고 기본 버전으로 승격합니다. 이전 버전의 앱은 연결된 VM 인스턴스와 마찬가지로 유지됩니다. 이러한 모든 앱 버전과 VM 인스턴스는 청구 가능한 리소스입니다.

    기본 이외의 앱 버전을 삭제하여 비용을 줄일 수 있습니다.

    앱 버전을 삭제하는 방법은 다음과 같습니다.

    1. GCP Console에서 App Engine 버전 페이지로 이동합니다.

      버전 페이지로 이동

    2. 삭제할 표준이 아닌 앱 버전 옆의 확인란을 클릭합니다.
    3. 페이지 상단의 삭제 버튼을 클릭하여 앱 버전을 삭제합니다.

    청구 가능한 리소스 삭제에 대한 자세한 내용은 이 가이드의 마지막 단계에서 삭제를 참조하세요.

    애플리케이션 구조

    바이너리 데이터 샘플 구조

    애플리케이션은 도서 정보에 구조화된 데이터베이스(Cloud Datastore, Cloud SQL, MongoDB)를 계속 사용하면서 Cloud Storage를 사용하여 바이너리 데이터(이 경우는 그림)를 저장합니다.

    코드 이해하기

    이 섹션에서는 애플리케이션 코드에 대해 단계별로 알아보고 이 코드의 작동 방식을 설명합니다.

    사용자 업로드 처리

    사용자가 이미지를 업로드할 수 있도록 enctypemultipart/form-data로 설정하여 추가/수정 양식이 파일 업로드를 허용하도록 수정되었으며 이미지에 대한 새로운 필드가 추가되었습니다.

    {% extends "base.html" %}
    
    {% block content %}
    <h3>{{action}} book</h3>
    
    <form method="POST" enctype="multipart/form-data">
    
      <div class="form-group">
        <label for="title">Title</label>
        <input type="text" name="title" id="title" value="{{book.title}}" class="form-control"/>
      </div>
    
      <div class="form-group">
        <label for="author">Author</label>
        <input type="text" name="author" id="author" value="{{book.author}}" class="form-control"/>
      </div>
    
      <div class="form-group">
        <label for="publishedDate">Date Published</label>
        <input type="text" name="publishedDate" id="publishedDate" value="{{book.publishedDate}}" class="form-control"/>
      </div>
    
      <div class="form-group">
        <label for="description">Description</label>
        <textarea name="description" id="description" class="form-control">{{book.description}}</textarea>
      </div>
    
      <div class="form-group">
        <label for="image">Cover Image</label>
        <input type="file" name="image" id="image" class="form-control"/>
      </div>
    
      <div class="form-group hidden">
        <label for="imageUrl">Cover Image URL</label>
        <input type="text" name="imageUrl" id="imageUrl" value="{{book.imageUrl}}" class="form-control"/>
      </div>
    
      <button type="submit" class="btn btn-success">Save</button>
    </form>
    
    {% endblock %}

    Flask 프레임워크에는 파일 업로드를 파싱하는 기본 제공 기능이 있습니다. Flask를 통해 요청 객체의 files 필드에서 파일 객체 사용이 가능하게 됩니다. 사용자가 파일을 업로드하지 않으면 image 값이 존재하지 않을 수 있으므로 get 메소드를 사용하여 일반 사전 구문 대신 필드에 액세스합니다. 이렇게 하면 파일이 없는 경우 예외가 발생되는 KeyError 대신 None이 반환됩니다.

    image_url = upload_image_file(request.files.get('image'))

    upload_image_file 호출은 crud.py의 도우미 함수에 전달됩니다. 이 도우미 함수는 해당 Cloud 버킷과 구성 파일에서 정의된 확장자와 함께 storage.upload_file 함수를 호출합니다.

    def upload_image_file(file):
        """
        Upload the user-uploaded file to Google Cloud Storage and retrieve its
        publicly-accessible URL.
        """
        if not file:
            return None
    
        public_url = storage.upload_file(
            file.read(),
            file.filename,
            file.content_type
        )
    
        current_app.logger.info(
            "Uploaded file %s as %s.", file.filename, public_url)
    
        return public_url

    Cloud Storage에 업로드

    crud.py에서 storage.upload_file을 호출하여 Cloud Storage로 파일 업로드를 처리한 후 이 코드는 이미지 파일 확장자가 있는지 확인하여 이미지만 업로드되도록 합니다. 이미지가 Cloud Storage에서 호스팅되기 때문에 악성 파일로 인해 앱이 손상될 위험은 없지만 적절한 유형의 파일만 허용하는 것이 좋습니다. Python 2 및 Python 3를 모두 지원하기 위해 six 라이브러리를 사용합니다.

    def upload_file(file_stream, filename, content_type):
        """
        Uploads a file to a given Cloud Storage bucket and returns the public url
        to the new object.
        """
        _check_extension(filename, current_app.config['ALLOWED_EXTENSIONS'])
        filename = _safe_filename(filename)
    
        client = _get_storage_client()
        bucket = client.bucket(current_app.config['CLOUD_STORAGE_BUCKET'])
        blob = bucket.blob(filename)
    
        blob.upload_from_string(
            file_stream,
            content_type=content_type)
    
        url = blob.public_url
    
        if isinstance(url, six.binary_type):
            url = url.decode('utf-8')
    
        return url

    upload_file 함수는 Cloud Storage에서 반환한 이미지의 공개 URL을 반환합니다. 그 다음 URL은 데이터베이스에 저장됩니다.

    if image_url:
        data['imageUrl'] = image_url

    Cloud Storage에서 이미지 제공

    이미지의 공개 URL을 갖고 있기 때문에 제공하는 것은 간단합니다. 요청이 Google의 글로벌 지원 인프라를 활용하고 애플리케이션이 이미지 요청에 응답할 필요가 없기 때문에 Cloud Storage에서 직접 제공하는 것이 유용합니다. 이렇게 하면 다른 요청에 대한 CPU 주기를 확보할 수 있습니다.

    <div class="media-left">
      {% if book.imageUrl %}
        <img class="book-image" src="{{book.imageUrl}}">
      {% else %}
        <img class="book-image" src="http://placekitten.com/g/128/192">
      {% endif %}
    </div>
이 페이지가 도움이 되었나요? 평가를 부탁드립니다.

다음에 대한 의견 보내기...