실습 설정 안내 및 요구사항
계정과 진행 상황을 보호하세요. 이 실습을 실행하려면 항상 시크릿 브라우저 창과 실습 사용자 인증 정보를 사용하세요.

Google Cloud로 애플리케이션 개발하기: 애플리케이션 데이터 저장하기

실습 1시간 30분 universal_currency_alt 크레딧 5개 show_chart 중급
info 이 실습에는 학습을 지원하는 AI 도구가 통합되어 있을 수 있습니다.
이 콘텐츠는 아직 휴대기기에 최적화되지 않음
최상의 경험을 위해 데스크톱 컴퓨터에서 이메일로 전송된 링크를 사용하여 방문하세요.

개요

Cloud 클라이언트 라이브러리는 애플리케이션에서 Google Cloud API를 호출하는 데 권장되는 방법으로, 애플리케이션에 사용 중인 프로그래밍 언어의 고유한 규칙 및 스타일을 사용합니다. Cloud 클라이언트 라이브러리는 인증 및 재시도 로직을 비롯한 서버와의 하위 수준 통신을 처리합니다.

Firestore는 자동 확장, 고성능, 간편한 애플리케이션 개발을 위해 설계된 빠른 속도의 완전 관리형 서버리스 NoSQL 문서 데이터베이스입니다.

Cloud Storage는 전 세계 어디에서나 데이터를 제공, 분석, 보관할 수 있도록 지원하는 통합 객체 스토리지입니다.

이 실습에서는 도서 목록을 관리하는 Python 애플리케이션을 만들어 봅니다. 도서를 추가, 수정, 삭제할 수 있으며 저자, 제목, 설명 등의 데이터를 수집할 수 있습니다. 초기 애플리케이션은 데이터를 인메모리 Python 사전에 저장하므로 애플리케이션이 비정상 종료되면 모든 도서가 손실됩니다.

여러분은 모든 도서 데이터가 Firestore에 저장되도록 이 애플리케이션을 수정한 다음 도서 표지 이미지를 저장하는 기능을 추가하고, 표지 이미지를 Cloud Storage에 저장합니다.

학습할 내용

이 실습에서는 다음을 수행하는 방법에 대해 알아봅니다.

  • 간단한 Python Flask 웹 애플리케이션을 만듭니다.
  • 애플리케이션 데이터를 저장하는 Firestore 데이터베이스를 만듭니다.
  • 애플리케이션에서 사용할 이미지를 저장할 Cloud Storage 버킷을 만듭니다.

설정 및 요건

각 실습에서는 정해진 기간 동안 새 Google Cloud 프로젝트와 리소스 집합이 무료로 제공됩니다.

  1. 실습 시작 버튼을 클릭합니다. 실습 비용을 결제해야 하는 경우 결제 수단을 선택할 수 있는 팝업이 열립니다. 왼쪽에는 다음과 같은 항목이 포함된 실습 세부정보 패널이 있습니다.

    • Google Cloud 콘솔 열기 버튼
    • 남은 시간
    • 이 실습에 사용해야 하는 임시 사용자 인증 정보
    • 필요한 경우 실습 진행을 위한 기타 정보
  2. Google Cloud 콘솔 열기를 클릭합니다(Chrome 브라우저를 실행 중인 경우 마우스 오른쪽 버튼으로 클릭하고 시크릿 창에서 링크 열기를 선택합니다).

    실습에서 리소스가 가동되면 다른 탭이 열리고 로그인 페이지가 표시됩니다.

    팁: 두 개의 탭을 각각 별도의 창으로 나란히 정렬하세요.

    참고: 계정 선택 대화상자가 표시되면 다른 계정 사용을 클릭합니다.
  3. 필요한 경우 아래의 사용자 이름을 복사하여 로그인 대화상자에 붙여넣습니다.

    {{{user_0.username | "Username"}}}

    실습 세부정보 패널에서도 사용자 이름을 확인할 수 있습니다.

  4. 다음을 클릭합니다.

  5. 아래의 비밀번호를 복사하여 시작하기 대화상자에 붙여넣습니다.

    {{{user_0.password | "Password"}}}

    실습 세부정보 패널에서도 비밀번호를 확인할 수 있습니다.

  6. 다음을 클릭합니다.

    중요: 실습에서 제공하는 사용자 인증 정보를 사용해야 합니다. Google Cloud 계정 사용자 인증 정보를 사용하지 마세요. 참고: 이 실습에 자신의 Google Cloud 계정을 사용하면 추가 요금이 발생할 수 있습니다.
  7. 이후에 표시되는 페이지를 클릭하여 넘깁니다.

    • 이용약관에 동의합니다.
    • 임시 계정이므로 복구 옵션이나 2단계 인증을 추가하지 않습니다.
    • 무료 체험판을 신청하지 않습니다.

잠시 후 Google Cloud 콘솔이 이 탭에서 열립니다.

참고: Google Cloud 제품 및 서비스 목록이 있는 메뉴를 보려면 왼쪽 상단의 탐색 메뉴를 클릭하거나 검색창에 제품 또는 서비스 이름을 입력합니다. 탐색 메뉴 아이콘

Google Cloud Shell 활성화하기

Google Cloud Shell은 다양한 개발 도구가 탑재된 가상 머신으로, 5GB의 영구 홈 디렉터리를 제공하며 Google Cloud에서 실행됩니다.

Google Cloud Shell을 사용하면 명령줄을 통해 Google Cloud 리소스에 액세스할 수 있습니다.

  1. Cloud 콘솔의 오른쪽 상단 툴바에서 'Cloud Shell 열기' 버튼을 클릭합니다.

    강조 표시된 Cloud Shell 아이콘

  2. 계속을 클릭합니다.

환경을 프로비저닝하고 연결하는 데 몇 분 정도 소요됩니다. 연결되면 사용자가 미리 인증되어 프로젝트가 PROJECT_ID로 설정됩니다. 예:

Cloud Shell 터미널에 강조 표시된 프로젝트 ID

gcloud는 Google Cloud의 명령줄 도구입니다. Cloud Shell에 사전 설치되어 있으며 명령줄 자동 완성을 지원합니다.

  • 다음 명령어를 사용하여 사용 중인 계정 이름을 나열할 수 있습니다.
gcloud auth list

출력:

Credentialed accounts: - @.com (active)

출력 예시:

Credentialed accounts: - google1623327_student@qwiklabs.net
  • 다음 명령어를 사용하여 프로젝트 ID를 나열할 수 있습니다.
gcloud config list project

출력:

[core] project =

출력 예시:

[core] project = qwiklabs-gcp-44776a13dea667a6 참고: gcloud 전체 문서는 gcloud CLI 개요 가이드를 참조하세요.

작업 1. 간단한 Python Flask 웹 애플리케이션 만들기 및 테스트하기

이 작업에서는 도서 목록을 저장하는 데 사용되는 Python 애플리케이션을 만들고 테스트합니다.

참고: 대부분의 언어에서 들여쓰기는 코드의 가독성을 높이는 데 사용되지만 Python은 코드 블록을 나타내는 데 들여쓰기를 사용하므로 들여쓰기가 정확해야 합니다. 들여쓰기에 일관된 개수의 공백을 사용해야 합니다. 들여쓰기에 스페이스바와 탭을 혼합하여 사용하면 문제가 발생할 수 있습니다. 이 실습에서는 Python 들여쓰기에 4개의 공백을 사용합니다.

Cloud Shell이 승인되었는지 확인하기

  1. Cloud Shell이 승인되었는지 확인하려면 Cloud Shell 셸에서 다음 명령어를 실행합니다.

    gcloud auth list
  2. Cloud Shell을 승인하라는 메시지가 표시되면 승인을 클릭합니다.

앱 디렉터리 만들기

앱 디렉터리를 만들려면 다음 명령어를 실행합니다.

mkdir ~/bookshelf

애플리케이션 파일은 ~/bookshelf 디렉터리에 생성됩니다.

요구사항 지정 및 설치하기

Python requirements 파일은 프로젝트에 필요한 종속 항목을 나열하는 간단한 텍스트 파일입니다. requirements 파일에는 세 가지 모듈이 필요합니다.

이 앱은 Python을 사용하여 웹 애플리케이션을 간단하게 설계할 수 있도록 해주는 웹 프레임워크 모듈인 Flask로 작성되어 있습니다. Linux에서 실행되는 Python HTTP 서버인 Gunicorn을 사용하여 애플리케이션을 실행합니다. 마지막으로, 애플리케이션의 정보를 기록하는 데 Cloud Logging이 사용됩니다.

  1. requirements 파일을 만들려면 다음 명령어를 실행합니다.

    cat > ~/bookshelf/requirements.txt <<EOF Flask==3.1.1 gunicorn==23.0.0 google-cloud-logging==3.12.1 EOF

    requirements.txt 파일은 애플리케이션에서 사용하는 Flask, Gunicorn, Google Cloud Logging의 버전을 지정합니다.

  2. 선택한 버전의 종속 항목을 설치하려면 다음 명령어를 실행합니다.

    pip3 install -r ~/bookshelf/requirements.txt --user

    pip는 Python용 패키지 설치 프로그램입니다. 이 pip3 명령어는 Python 버전 3에서 사용할 수 있도록 requirements.txt 파일에 지정된 패키지를 설치합니다.

books 데이터베이스 구현 만들기

  1. books 데이터베이스 코드를 만들려면 다음 명령어를 실행합니다.

    cat > ~/bookshelf/booksdb.py <<EOF db = {} # global in-memory python dictionary, key should always be a string next_id = 1 # next book ID to use def get_next_id(): """ Return the next ID. Automatically increments when retrieving one. """ global next_id id = next_id # next ID is 1 higher next_id = next_id + 1 # return a string version of the ID return str(id) def read(book_id): """ Return the details for a single book. """ # retrieve a book from the database by ID data = db[str(book_id)] return data def create(data): """ Create a new book and return the book details. """ # get a new ID for the book book_id = get_next_id() # set the ID in the book data data['id'] = book_id # store book in database db[book_id] = data return data def update(data, book_id): """ Update an existing book, and return the updated book's details. """ # book ID should always be a string book_id_str = str(book_id) # add ID to the book data data['id'] = book_id_str # update book in the database db[book_id_str] = data return data def delete(book_id): """ Delete a book in the database. """ # remove book from database del db[str(book_id)] # no return required def list(): """ Return a list of all books in the database. """ # empty list of books books = [] # retrieve each item in database and add to the list for k in db: books.append(db[k]) # return the list return books EOF

    도서는 키-값 쌍 저장을 위한 데이터 구조인 Python 사전에 저장됩니다. 키는 고유해야 하므로 get_next_id() 함수는 호출될 때마다 새 ID를 생성합니다.

    read(book_id) 함수는 제공된 book_id에 상응하는 항목을 가져옵니다.

    create(data) 함수는 새 ID를 가져오고, 해당 ID를 해당 도서의 데이터에 저장한 다음, 데이터 항목을 사전에 저장하여 도서를 데이터베이스에 추가합니다.

    update(data, book_id) 함수는 제공된 ID를 해당 도서의 데이터에 저장한 다음 데이터 항목을 사전에 저장하여 데이터베이스에서 도서를 업데이트합니다.

    delete(book_id) 함수는 제공된 book_id 키를 사용하여 데이터베이스에서 해당 항목을 삭제합니다.

    list() 함수는 데이터베이스의 각 도서를 포함하는 Python 목록을 반환합니다. 이 함수는 사전에 대해 루프를 돌려서 각 항목을 가져오는 방법으로 이 목록을 가져옵니다. 각 항목은 id 키를 사용하여 ID를 저장합니다.

HTML 템플릿 파일 만들기

템플릿은 정적 데이터와 동적 데이터의 자리표시자를 포함하는 파일입니다. 템플릿을 렌더링하여 최종 HTML 파일을 생성합니다.

  1. 기본 템플릿을 만들려면 다음 명령어를 실행합니다.

    mkdir ~/bookshelf/templates cat > ~/bookshelf/templates/base.html <<EOF <!DOCTYPE html> <html lang="en"> <head> <title>Bookshelf</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"> </head> <body> <div class="navbar navbar-default"> <div class="container"> <div class="navbar-header"> <div class="navbar-brand">Bookshelf</div> </div> <ul class="nav navbar-nav"> <li><a href="/">Books</a></li> </ul> </div> </div> <div class="container"> {% block content %}{% endblock %} </div> </body> </html> EOF

    애플리케이션의 각 페이지는 본문만 다르고 기본 레이아웃은 동일합니다. 3가지 기본 템플릿(목록, 보기, 양식)은 페이지 중앙에 표시할 콘텐츠를 지정하여 이 기본 템플릿을 확장합니다.

  2. 목록 템플릿을 만들려면 다음 명령어를 실행합니다.

    cat > ~/bookshelf/templates/list.html <<EOF {% extends "base.html" %} {% block content %} <h3>Books</h3> <a href="/books/add" class="btn btn-success btn-sm"> <i class="glyphicon glyphicon-plus"></i> Add book </a> {% for book in books %} <div class="media"> <a href="/books/{{book.id}}"> <div class="media-body"> <h4>{{book.title}}</h4> <p>{{book.author}}</p> </div> </a> </div> {% else %} <p>No books found</p> {% endfor %} {% endblock %} EOF

    목록 템플릿은 템플릿으로 전송된 도서 목록에 대해 루프를 돌려서 각 도서의 제목, 저자, 링크를 표시합니다. 링크를 클릭하면 해당 도서의 보기 페이지로 이동됩니다.

  3. 보기 템플릿을 만들려면 다음 명령어를 실행합니다.

    cat > ~/bookshelf/templates/view.html <<EOF {% extends "base.html" %} {% block content %} <h3>Book</h3> <div class="btn-group"> <a href="/books/{{book.id}}/edit" class="btn btn-primary btn-sm"> <i class="glyphicon glyphicon-edit"></i> Edit book </a> <a href="/books/{{book.id}}/delete" class="btn btn-danger btn-sm"> <i class="glyphicon glyphicon-trash"></i> Delete book </a> </div> <div class="media"> <div class="media-body"> <h4 class="book-title"> {{book.title}} <small>{{book.publishedDate}}</small> </h4> <h5 class="book-author">By {{book.author|default('Unknown', True)}}</h5> <p class="book-description">{{book.description}}</p> </div> </div> {% endblock %} EOF

    보기 템플릿은 도서 세부정보(제목, 저자, 출간 날짜, 설명)를 보여줍니다. 이에 더해 도서를 수정하는 버튼(사용자가 양식 템플릿으로 이동됨)과 도서를 삭제하는 버튼(도서가 삭제되고 사용자가 도서 목록으로 되돌아감)도 제공합니다.

  4. 양식 템플릿을 만들려면 다음 명령어를 실행합니다.

    cat > ~/bookshelf/templates/form.html <<EOF {# [START form] #} {% 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> <button type="submit" class="btn btn-success">Save</button> </form> {% endblock %} {# [END form] #} EOF

    양식 템플릿은 두 가지 용도로 사용됩니다. 도서를 업데이트할 때 템플릿에 현재 도서의 세부정보가 수정 가능한 상자에 표시됩니다. 저장 버튼을 클릭하면 업데이트된 양식 필드가 데이터베이스에 저장됩니다.

    새 도서를 만들면 도서 세부정보 상자가 비어 있습니다. 저장 버튼을 클릭하면 도서 데이터가 데이터베이스에 새 도서로 저장됩니다.

main 코드 파일 만들기

  1. main 코드 파일을 만들려면 다음 명령어를 실행합니다.

    cat > ~/bookshelf/main.py <<EOF from flask import current_app, Flask, redirect, render_template from flask import request, url_for import logging from google.cloud import logging as cloud_logging import booksdb app = Flask(__name__) app.config.update( SECRET_KEY='secret', # don't store SECRET_KEY in code in a production app MAX_CONTENT_LENGTH=8 * 1024 * 1024, ) app.debug = True app.testing = False # configure logging if not app.testing: logging.basicConfig(level=logging.INFO) # attach a Cloud Logging handler to the root logger client = cloud_logging.Client() client.setup_logging() def log_request(req): """ Log request """ current_app.logger.info('REQ: {0} {1}'.format(req.method, req.url)) @app.route('/') def list(): """ Display all books. """ log_request(request) # get list of books books = booksdb.list() # render list of books return render_template('list.html', books=books) @app.route('/books/<book_id>') def view(book_id): """ View the details of a specified book. """ log_request(request) # retrieve a specific book book = booksdb.read(book_id) # render book details return render_template('view.html', book=book) @app.route('/books/add', methods=['GET', 'POST']) def add(): """ If GET, show the form to collect details of a new book. If POST, create the new book based on the specified form. """ log_request(request) # Save details if form was posted if request.method == 'POST': # get book details from form data = request.form.to_dict(flat=True) # add book book = booksdb.create(data) # render book details return redirect(url_for('.view', book_id=book['id'])) # render form to add book return render_template('form.html', action='Add', book={}) @app.route('/books/<book_id>/edit', methods=['GET', 'POST']) def edit(book_id): """ If GET, show the form to collect updated details for a book. If POST, update the book based on the specified form. """ log_request(request) # read existing book details book = booksdb.read(book_id) # Save details if form was posted if request.method == 'POST': # get book details from form data = request.form.to_dict(flat=True) # update book book = booksdb.update(data, book_id) # render book details return redirect(url_for('.view', book_id=book['id'])) # render form to update book return render_template('form.html', action='Edit', book=book) @app.route('/books/<book_id>/delete') def delete(book_id): """ Delete the specified book and return to the book list. """ log_request(request) # delete book booksdb.delete(book_id) # render list of remaining books return redirect(url_for('.list')) # this is only used when running locally if __name__ == '__main__': app.run(host='127.0.0.1', port=8080, debug=True) EOF

    main.py는 애플리케이션의 진입점입니다. 이 파일은 웹 URL 라우팅을 지정하고, 템플릿을 렌더링하고, 도서 데이터베이스를 관리하는 Flask 애플리케이션을 구현합니다. 주석이 잘 작성되어 있으니 코드를 직접 살펴보세요.

애플리케이션 사용해 보기

  1. bookshelf 디렉터리의 내용을 확인하려면 다음 명령어를 실행합니다.

    cd ~ ls -R bookshelf

    2개의 Python 파일, 1개의 requirements 파일, 4개의 템플릿 파일이 있는 목록이 표시됩니다.

    bookshelf: booksdb.py main.py requirements.txt templates bookshelf/templates: base.html form.html list.html view.html
  2. Gunicorn HTTP 서버를 실행하려면 다음 명령어를 실행합니다.

    cd ~/bookshelf; ~/.local/bin/gunicorn -b :8080 main:app

    파일을 생성했다면 애플리케이션이 포트 8080에서 호스팅될 것입니다.

  3. 웹브라우저에서 애플리케이션을 실행하려면 웹 미리보기를 클릭하고 포트 8080에서 미리보기를 선택합니다.

    포트 8080에서 웹 미리보기

    브라우저에 새 탭이 열리고 애플리케이션이 실행됩니다. 이것은 모든 기존 도서의 목록을 표시하는 기준 URL입니다. 아직 도서가 없는 것을 볼 수 있습니다.

    참고: Cloud Shell을 승인하라는 메시지가 표시되면 승인을 클릭합니다.
  4. 애플리케이션 탭에서 +도서 추가를 클릭합니다.

  5. 실제 도서 또는 가상의 도서의 제목, 저자, 날짜, 설명을 입력한 다음 저장을 클릭합니다.

    보기 페이지가 표시되고 도서 세부정보를 볼 수 있습니다. 해당하는 버튼을 클릭하여 도서를 수정하거나 삭제할 수 있습니다.

  6. 페이지 상단에서 도서를 클릭합니다.

    보기 페이지가 표시되고, 추가한 도서를 목록에서 볼 수 있습니다.

    원하는 경우 애플리케이션을 탐색하여 도서를 추가, 수정 또는 삭제할 수 있습니다.

  7. Cloud Shell에서 CTRL-C를 입력하여 애플리케이션을 종료합니다.

목표를 확인하려면 내 진행 상황 확인하기를 클릭합니다. 간단한 Python Flask 웹 애플리케이션 만들기 및 테스트하기

작업 2. Firestore를 도서 데이터베이스로 사용하기

이 작업에서는 Firestore 데이터베이스를 사용하여 도서 데이터를 저장합니다.

애플리케이션은 현재 인메모리 Python 사전을 사용합니다. 따라서 앱이 종료되거나 비정상 종료될 때마다 도서가 손실됩니다. 더 나은 해결책은 지속되는 데이터베이스로 Firestore를 사용하는 것입니다.

Firestore 데이터베이스 만들기

  1. 탐색 메뉴(탐색 메뉴 아이콘)에서 모든 제품 보기 > 데이터베이스로 이동합니다.

  2. Firestore 옆에 있는 고정 아이콘을 클릭하여 탐색 메뉴에 추가한 다음 Firestore를 클릭합니다.

  3. Firestore 데이터베이스 만들기를 클릭합니다.

  4. 구성 옵션에서 Firestore 네이티브를 선택합니다.

  5. 위치 유형으로 리전을 클릭합니다.

    이 실습에서는 Firestore용으로 멀티 리전을 사용하면 안 됩니다.

  6. 리전으로 를 선택한 다음 데이터베이스 만들기를 클릭합니다.

    참고: 해당 위치에 대해 정확한 리전 를 선택해야 합니다. 드롭다운 목록에 이 리전이 표시되지 않는다면 위치 유형에 대해 멀티 리전이 아닌 리전을 올바르게 선택했는지 확인하세요.

    드롭다운 목록에 리전이 없다면 취소 후 이전 페이지로 돌아간 다음 데이터베이스 만들기 프로세스를 다시 시도하세요.

    데이터베이스를 만드는 데 몇 분 정도 걸릴 수 있습니다. Firestore의 컬렉션은 컬렉션에 문서를 추가하면 자동으로 생성되므로 지금은 도서 컬렉션을 만들 필요가 없습니다.

    Firestore 데이터베이스를 만들면 Firestore API가 사용 설정됩니다.

애플리케이션이 Firestore용 Python 클라이언트를 요구하도록 수정하기

Firestore API용 Python 클라이언트는 애플리케이션에서 Firestore 데이터에 액세스하는 데 사용됩니다. 이 클라이언트의 패키지 이름은 google-cloud-firestore이고 사용할 버전은 2.21.0입니다.

  1. 애플리케이션이 google-cloud-firestore 패키지의 2.21.0 버전을 요구하도록 수정합니다.
참고: nano, vi, Cloud Code 편집기 등 원하는 파일 편집기를 사용할 수 있습니다.
  1. 업데이트된 종속 항목을 설치하려면 다음 명령어를 실행합니다.

    pip3 install -r ~/bookshelf/requirements.txt --user

애플리케이션이 Firestore에 도서 데이터를 저장하도록 수정하기

애플리케이션이 google-cloud-firestore 패키지를 요구하도록 수정되었으므로 books 데이터베이스 구현에 이 패키지를 사용할 수 있습니다.

Firestore에서는 도서 데이터가 Firestore 데이터베이스에 저장됩니다. 데이터에 ID로 사용될 수 있는 보장된 고유 필드가 있다면 이를 Firestore에서 ID로 사용해도 됩니다. 이 예시에서는 사용 중인 데이터에 고유 필드가 없습니다. 새 도서를 만들면 Firestore가 자동으로 ID를 생성할 수 있습니다.

다음은 Firestore 클라이언트가 사용되는 방법의 예시입니다.

from google.cloud import firestore # get the client db = firestore.Client() # create a new document data = {"name": "Sue", "role": "treasurer"} member_ref = db.collection("members").document() member_ref.set(data) member_id = member_ref.get().id # retrieve a document member_ref = db.collection("members").document(member_id) member = member_ref.get() if member.exists: print(f"Document data: {member.to_dict()}") else: print("Member not found.") # update a document new_data = {"name": "Sue", "role": "president"} member_ref = db.collection("members").document(member_id) member_ref.set(new_data) # get all documents in order members = db.collection("members").order_by("name").stream() for member in members: print(f"{member.id} => {member.to_dict()}") # delete a member member_ref = db.collection("members").document(member_id) member_ref.delete()

다음으로, Firestore를 사용하도록 books 데이터베이스 구현을 수정합니다.

참고: Python 들여쓰기에는 4개의 공백을 사용해야 합니다.

현재 구현은 인메모리 Python 사전인 전역 변수 db를 사용합니다. 또한 next_id 변수와 get_next_id() 함수를 사용하여 사전에 저장된 항목의 ID를 생성합니다.

Firestore는 ID 생성을 관리합니다. books라는 컬렉션을 사용하세요. 생성된 ID를 먼저 도서의 세부정보를 포함하는 Python 사전에 추가한 후에 호출자에게 반환해야 합니다.

참고: 힌트에서는 변경해야 하는 코드가 숨겨져 있습니다. 코드를 직접 작성해 보거나, 힌트 버튼을 클릭하여 추가할 코드를 확인하세요.
  1. 파일 편집기에서 ~/bookshelf/booksdb.py 파일을 엽니다.

  2. 파일에서 다음 줄을 삭제합니다.

    db = {} # global in-memory python dictionary, key should always be a string next_id = 1 # next book ID to use def get_next_id(): """ Return the next ID. Automatically increments when retrieving one. """ global next_id id = next_id # next ID is 1 higher next_id = next_id + 1 # return a string version of the ID return str(id)

    이 구현에서는 인메모리 데이터베이스와 ID 생성 기능이 필요하지 않습니다.

  3. Firestore 클라이언트를 가져오는 코드를 추가합니다.

  1. Firestore 문서를 사전으로 변환하는 함수를 만듭니다.

호출자에게 도서를 반환할 때 호출자가 Firestore 문서를 이해하도록 요구해서는 안 됩니다. 도서 데이터베이스 함수의 인터페이스는 변경되지 않으므로 이 구현은 계속해서 도서를 Python 사전으로 반환하고 받습니다.

도서의 ID는 먼저 사전에 추가한 후에 반환해야 합니다.

Firestore 문서를 입력 파라미터로 받아서 사전을 반환하는 document_to_dict()라는 함수를 만듭니다. 사전은 문서의 키-값 쌍을 포함하며, 문서 ID를 키 id의 값으로 반환합니다. 문서가 존재하지 않으면 None을 반환해야 합니다.

  1. read() 함수가 Firestore 컬렉션 books에서 도서를 가져오도록 수정합니다. 업데이트된 함수는 document_to_dict() 함수를 호출해야 합니다.
  1. create() 함수가 Firestore 컬렉션 books에 도서를 만들도록 수정합니다.
  1. update() 함수가 Firestore 컬렉션 books에서 도서를 업데이트하도록 수정합니다.
  1. delete() 함수가 Firestore 컬렉션 books에서 도서를 삭제하도록 수정합니다.
  1. list() 함수가 Firestore 컬렉션 books에서 모든 도서의 목록을 제목을 기준으로 정렬하여 반환하도록 수정합니다.

이렇게 하면 끝입니다. booksdb.py를 업데이트함으로써 호출하는 코드를 변경하지 않고도 앱이 Firestore를 데이터베이스로 사용하도록 수정했습니다.

업데이트된 애플리케이션 테스트하기

  1. Cloud Shell에서 다음 명령어를 실행합니다.

    cd ~/bookshelf; ~/.local/bin/gunicorn -b :8080 main:app

    파일을 업데이트했다면 애플리케이션이 포트 8080에서 호스팅될 것입니다.

    참고: 가져오기 오류가 발생하는 경우 pip3를 사용하여 (google.cloud.firestore 패키지를 포함하는) 업데이트된 요구사항을 설치했는지 확인하세요. 참고: Cloud Shell을 승인하라는 메시지가 표시되면 승인을 클릭합니다.
  2. 웹브라우저에서 애플리케이션을 실행하려면 웹 미리보기를 클릭하고 포트 8080에서 미리보기를 선택합니다.

    도서가 인메모리 사전에 저장되고 있었기 때문에 데이터베이스에 도서가 없습니다.

  3. 애플리케이션 탭에서 +도서 추가를 클릭합니다.

  4. 양식에 다음 정보를 입력합니다.

    필드
    Title 햄릿
    Author 윌리엄 셰익스피어
    Date Published 1603
    Description 한 왕자가 삶과 죽음, 그리고 복수에 대해 생각한다. 언어유희가 돋보이는 작품.
  5. 저장을 클릭합니다.

    보기 페이지가 표시되고 도서 세부정보를 볼 수 있습니다.

  6. 페이지 상단에서 도서를 클릭합니다.

    목록 페이지가 표시되고 목록에 햄릿이 표시됩니다.

    참고: 원한다면 다른 도서를 추가할 수 있지만, 햄릿은 다음 작업을 진행하는 데 필요하니 수정하지 마세요.
  7. Google Cloud 콘솔의 탐색 메뉴(탐색 메뉴 아이콘)에서 Firestore를 클릭합니다.

    참고: 이미 Firestore 콘솔 페이지에 있다 하더라도 데이터베이스를 보려면 페이지로 다시 이동해야 할 수 있습니다.
  8. (기본값)을 클릭합니다.

    books 컬렉션에 햄릿에 대해 문서가 생성된 것을 볼 수 있습니다.

  9. Cloud Shell에서 CTRL-C를 입력하여 애플리케이션을 종료합니다.

작업 2 문제 해결하기

애플리케이션에서 발생하는 오류는 현재 Cloud Shell에 출력됩니다. 애플리케이션이 작동하지 않거나 데이터를 Firestore에 저장하지 않는다면 오류 정보를 참고하여 문제를 디버깅하고 해결하세요.

books 데이터베이스 코드가 작동하지 않는다면, 다음 힌트의 명령어를 사용하여 전체 booksdb.py 파일을 작동하는 코드로 바꿀 수 있습니다.

목표를 확인하려면 내 진행 상황 확인하기를 클릭합니다. Firestore를 도서 데이터베이스로 사용하기

작업 3. 도서 표지용으로 Cloud Storage 사용하기

이 작업에서는 Cloud Storage를 사용하여 도서의 표지 이미지를 저장합니다.

데이터베이스는 일반적으로 이미지를 저장하기에 적합한 위치가 아닙니다. 애플리케이션은 다른 곳에 호스팅하게 될 것이므로 Cloud Shell에 파일을 저장할 수 없습니다. Cloud Storage는 공유하려는 애셋을 저장하기에 적합한 솔루션입니다. Cloud Storage는 Google Cloud의 기본 객체 스토어입니다.

Cloud Storage 버킷 만들기

Cloud Storage를 사용하려면 데이터를 보관하는 기본 컨테이너인 Cloud Storage 버킷을 생성해야 합니다.

  1. Google Cloud 콘솔의 탐색 메뉴(탐색 메뉴 아이콘)에서 Cloud Storage > 버킷을 클릭합니다.

  2. +만들기를 클릭합니다.

  3. 버킷 이름으로 다음을 사용합니다.

    {{{ project_0.project_id | project_id }}}-covers
  4. 계속을 클릭합니다.

  5. 리전을 선택합니다.

  6. 를 선택합니다.

  7. 계속을 클릭합니다.

  8. 스토리지 클래스를 그대로 두고 계속을 클릭합니다.

  9. 이 버킷에 공개 액세스 방지 적용의 선택을 취소합니다.

    액세스 제어균일한 액세스 제어로 둡니다. 이 설정은 버킷에 추가된 모든 객체에 버킷 수준 권한을 사용합니다.

  10. 만들기를 클릭합니다.

    애플리케이션에 표지가 표시되도록 하려면 모든 사용자가 버킷 내의 객체를 읽을 수 있도록 허용해야 합니다.

  11. 권한 탭을 선택한 다음 액세스 권한 부여를 클릭합니다.

  12. 새 주 구성원으로 allUsers를 입력합니다.

  13. 역할로 기존 Cloud Storage > 저장소 기존 객체 리더를 선택합니다.

    참고: Cloud Storage > 저장소 객체 뷰어 역할은 버킷의 객체를 나열하는 권한을 포함하는데, 이 권한은 이 애플리케이션에 필요하지 않습니다. 기존 Cloud Storage > 저장소 기존 객체 리더 역할은 객체의 가져오기만 허용하는데, 이 권한이 이 사용 사례에 더 적합합니다.
  14. 저장을 클릭합니다.

  15. 확인을 요청하는 메시지가 표시되면 공개 액세스 허용을 클릭합니다.

requirements 파일 업데이트하기

  1. Cloud Shell의 파일 편집기에서 ~/bookshelf/requirements.txt 파일을 엽니다.

  2. ~/bookshelf/requirements.txt 파일에 다음 줄을 추가합니다.

    google-cloud-storage==2.17.0

    requirements.txt 파일이 다음과 같이 수정되었을 것입니다.

    Flask==3.1.1 gunicorn==23.0.0 google-cloud-logging==3.12.1 google-cloud-firestore==2.21.0 google-cloud-storage==2.17.0
  3. 파일을 저장합니다.

  4. 업데이트된 종속 항목을 설치하려면 Cloud Shell에서 다음 명령어를 실행합니다.

    pip3 install -r ~/bookshelf/requirements.txt --user

이미지를 Cloud Storage에 업로드하는 코드 만들기

storage.py 파일에는 표지 이미지를 Cloud Storage에 업로드하는 코드가 포함되어 있습니다.

  1. storage.py 파일을 만들려면 다음 명령어를 실행합니다.

    cat > ~/bookshelf/storage.py <<EOF from __future__ import absolute_import import datetime import os from flask import current_app from werkzeug.exceptions import BadRequest from werkzeug.utils import secure_filename from google.cloud import storage def _check_extension(filename, allowed_extensions): """ Validates that the filename's extension is allowed. """ _, ext = os.path.splitext(filename) if (ext.replace('.', '') not in allowed_extensions): raise BadRequest( '{0} has an invalid name or extension'.format(filename)) def _safe_filename(filename): """ Generates a safe filename that is unlikely to collide with existing objects in Cloud Storage. filename.ext is transformed into filename-YYYY-MM-DD-HHMMSS.ext """ filename = secure_filename(filename) date = datetime.datetime.utcnow().strftime("%Y-%m-%d-%H%M%S") basename, extension = filename.rsplit('.', 1) return "{0}-{1}.{2}".format(basename, date, extension) 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) # build the name of the bucket bucket_name = os.getenv('GOOGLE_CLOUD_PROJECT') + '-covers' client = storage.Client() # create a bucket object bucket = client.bucket(bucket_name) # create an object in the bucket for the specified path blob = bucket.blob(filename) # upload the contents of the string into the object blob.upload_from_string( file_stream, content_type=content_type) # get the public URL for the object, which is used for storing a reference # to the image in the database and displaying the image in the app url = blob.public_url return url def upload_image(img): """ Upload the user-uploaded file to Cloud Storage and retrieve its publicly accessible URL. """ if not img: return None public_url = upload_file( img.read(), img.filename, img.content_type ) return public_url EOF

    upload_file() 함수는 파일 스트림, 파일 이름, 파일의 콘텐츠 유형을 받습니다. 파일 이름의 확장자는 이후 단계에서 생성되는, 승인된 확장자 목록과 비교하여 검증됩니다. 그런 다음 업로드 시 동일한 파일 이름을 사용하는 도서 이미지들이 충돌하지 않도록 파일 이름에 현재 날짜 및 시간이 추가됩니다. 함수의 나머지 부분은 Cloud Storage와 상호작용합니다.

    먼저 프로젝트 ID를 사용하여 버킷 이름이 빌드됩니다.

    bucket_name = os.getenv('GOOGLE_CLOUD_PROJECT') + '-covers'

    다음으로, 지정된 버킷 및 파일 이름에 대한 객체 참조가 생성되고, 이미지 파일의 콘텐츠가 업로드됩니다.

    client = storage.Client() # create a bucket object bucket = client.bucket(bucket_name) # create an object in the bucket for the specified path blob = bucket.blob(filename)

    그런 다음 파일 데이터가 Cloud Storage에 업로드되고, 웹 앱에 표시될 수 있도록 파일이 공개로 설정됩니다.

    # upload the contents of the string into the object blob.upload_from_string( file_stream, content_type=content_type)

    그런 다음 URL이 도서 데이터베이스에 저장되어 이미지를 표시하는 데 사용될 수 있도록 반환됩니다.

도서 이미지를 표시하도록 템플릿 수정하기

템플릿은 특정 데이터로 렌더링되어 웹페이지를 생성합니다.

기본 템플릿은 변경할 필요가 없으나, 콘텐츠 템플릿(양식, 목록, 보기)은 도서 표지를 표시하고 업로드할 수 있도록 변경해야 합니다.

  1. 파일 편집기에서 ~/bookshelf/templates/form.html 파일을 엽니다.

    양식이 이미지 파일을 수집하도록 수정해야 합니다.

  2. 양식 하단에서 저장 버튼 컨트롤 위에 다음 줄을 추가합니다.

    <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>

    image 입력은 사용자가 이미지 파일을 업로드할 수 있도록 지원하며 현재 이미지를 표시합니다. 숨겨져 있는 imageUrl 입력은 이미지의 공개 URL을 저장합니다. 공개 URL은 해당 도서의 데이터베이스 항목에 추가됩니다.

    양식이 다음과 같이 수정되었을 것입니다.

    <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>
  3. 파일을 저장합니다.

  4. 파일 편집기에서 ~/bookshelf/templates/view.html 파일을 엽니다.

    도서 이미지는 도서 정보 왼쪽에 표시되어야 합니다.

  5. <div class="media"> 줄 뒤에 다음 줄을 추가합니다.

    <div class="media-left"> {% if book.imageUrl %} <img class="book-image" src="{{book.imageUrl}}" width="128" height="192" alt="book cover"> {% else %} <img class="book-image" src="https://storage.googleapis.com/cloud-training/devapps-foundations/no-cover.png" width="128" height="192" alt="no book cover"> {% endif %} </div>

    위 코드는 도서 세부정보 왼쪽에 새 섹션을 추가합니다. 도서 이미지가 있으면 이 섹션에 이미지가 표시됩니다. 도서 이미지가 없으면 이 섹션에 자리표시자 이미지가 표시됩니다.

    media div가 다음과 같이 수정되었을 것입니다.

    <div class="media"> <div class="media-left"> {% if book.imageUrl %} <img class="book-image" src="{{book.imageUrl}}" width="128" height="192" alt="book cover"> {% else %} <img class="book-image" src="https://storage.googleapis.com/cloud-training/devapps-foundations/no-cover.png" width="128" height="192" alt="no book cover"> {% endif %} </div> <div class="media-body"> <h4 class="book-title"> {{book.title}} <small>{{book.publishedDate}}</small> </h4> <h5 class="book-author">By {{book.author|default('Unknown', True)}}</h5> <p class="book-description">{{book.description}}</p> </div> </div>
  6. 파일을 저장합니다.

  7. 파일 편집기에서 ~/bookshelf/templates/list.html 파일을 엽니다.

    도서 이미지는 목록에 있는 각 도서의 왼쪽에 표시되어야 합니다.

  8. <a href="/books/{{book.id}}"> 줄 뒤에 다음 줄을 추가합니다.

    <div class="media-left"> {% if book.imageUrl %} <img src="{{book.imageUrl}}" width="128" height="192" alt="book cover"> {% else %} <img src="https://storage.googleapis.com/cloud-training/devapps-foundations/no-cover.png" width="128" height="192" alt="no book cover"> {% endif %} </div>

    보기 템플릿에 추가한 것과 같은 코드입니다.

main.py 수정하기

main 코드 파일은 양식이 게시되었을 때 이미지를 Cloud Storage에 업로드해야 하고, 이미지 URL이 도서 데이터에 추가되어야 합니다.

  1. 파일 편집기에서 ~/bookshelf/main.py 파일을 엽니다.

  2. booksdb import 줄 뒤에 다음 줄을 추가합니다.

    import storage
  3. import 줄 뒤에 upload_image_file() 메서드를 추가합니다.

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

    이 함수는 storage.py에서 만든 라이브러리 함수를 호출하여 표지 파일을 Cloud Storage에 업로드하고, 업로드된 파일의 공개 URL을 반환합니다.

  4. app.config.update 섹션에 다음 줄을 추가합니다.

    ALLOWED_EXTENSIONS=set(['png', 'jpg', 'jpeg', 'gif']),

    이 코드는 도서 표지를 업로드하는 데 사용할 수 있는 확장자를 제한합니다. 구성이 다음과 같이 수정되었을 것입니다.

    app.config.update( SECRET_KEY='secret', MAX_CONTENT_LENGTH=8 * 1024 * 1024, ALLOWED_EXTENSIONS=set(['png', 'jpg', 'jpeg', 'gif']), )
  5. add() 함수에서 data = request.form.to_dict(flat=True) 줄 뒤에 다음 코드를 추가합니다.

    image_url = upload_image_file(request.files.get('image')) # If an image was uploaded, update the data to point to the image. if image_url: data['imageUrl'] = image_url

    이 코드는 upload_image_file 함수를 호출하여 양식에 추가된 이미지를 업로드하고, 이미지 URL을 도서 데이터에 추가합니다.

  6. edit() 함수에서 data = request.form.to_dict(flat=True) 줄 뒤에 다음 코드를 추가합니다.

    image_url = upload_image_file(request.files.get('image')) # If an image was uploaded, update the data to point to the image. if image_url: data['imageUrl'] = image_url

    이 코드는 add() 함수에 추가한 것과 동일한 코드입니다.

  7. 파일을 저장합니다.

업데이트된 애플리케이션 테스트하기

  1. Cloud Shell에서 다음 명령어를 실행합니다.

    cd ~/bookshelf; ~/.local/bin/gunicorn -b :8080 main:app

    파일을 업데이트했다면 애플리케이션이 포트 8080에서 호스팅될 것입니다.

    참고: Cloud Shell을 승인하라는 메시지가 표시되면 승인을 클릭합니다.
  2. 웹브라우저에서 애플리케이션을 실행하려면 웹 미리보기를 클릭하고 포트 8080에서 미리보기를 선택합니다.

    애플리케이션이 Firestore를 사용했을 때 추가된 도서가 표시될 것입니다. 각 도서에 자리표시자 표지 이미지가 표시됩니다. 이전에 데이터베이스에 이미지 URL이 추가되지 않았기 때문입니다.

  3. 햄릿을 클릭한 다음 도서 수정을 클릭합니다.

  4. 햄릿 도서 표지 이미지를 마우스 오른쪽 버튼으로 클릭하고 컴퓨터에 hamlet.png로 저장합니다.

    햄릿 도서 표지

  5. Bookshelf 앱에서 표지 이미지에 대해 파일 선택을 클릭합니다.

  6. 다운로드한 파일(hamlet.png)을 선택하고 열기를 클릭합니다.

  7. 저장을 클릭합니다.

    이제 햄릿의 도서 이미지가 표시될 것입니다.

  8. Google Cloud 콘솔의 탐색 메뉴(탐색 메뉴 아이콘)에서 Cloud Storage > 버킷을 클릭합니다.

  9. 버킷 이름(-covers)을 클릭합니다.

    표지 이미지가 Cloud Storage에 저장됩니다.

목표를 확인하려면 내 진행 상황 확인하기를 클릭합니다. 도서 표지용으로 Cloud Storage 사용하기

수고하셨습니다

Cloud Shell에서 애플리케이션을 테스트했습니다. Cloud 클라이언트 라이브러리를 사용하여 데이터를 Firestore에 저장하고 이미지를 Cloud Storage에 저장하도록 애플리케이션을 수정했습니다.

다음 단계/더 학습하기

실습 종료하기

실습을 완료하면 실습 종료를 클릭합니다. Google Cloud Skills Boost에서 사용된 리소스를 자동으로 삭제하고 계정을 지웁니다.

실습 경험을 평가할 수 있습니다. 해당하는 별표 수를 선택하고 의견을 입력한 후 제출을 클릭합니다.

별점의 의미는 다음과 같습니다.

  • 별표 1개 = 매우 불만족
  • 별표 2개 = 불만족
  • 별표 3개 = 중간
  • 별표 4개 = 만족
  • 별표 5개 = 매우 만족

의견을 제공하고 싶지 않다면 대화상자를 닫으면 됩니다.

의견이나 제안 또는 수정할 사항이 있다면 지원 탭을 사용하세요.

Copyright 2026 Google LLC All rights reserved. Google 및 Google 로고는 Google LLC의 상표입니다. 기타 모든 회사명 및 제품명은 해당 업체의 상표일 수 있습니다.

시작하기 전에

  1. 실습에서는 정해진 기간 동안 Google Cloud 프로젝트와 리소스를 만듭니다.
  2. 실습에는 시간 제한이 있으며 일시중지 기능이 없습니다. 실습을 종료하면 처음부터 다시 시작해야 합니다.
  3. 화면 왼쪽 상단에서 실습 시작을 클릭하여 시작합니다.

시크릿 브라우징 사용

  1. 실습에 입력한 사용자 이름비밀번호를 복사합니다.
  2. 비공개 모드에서 콘솔 열기를 클릭합니다.

콘솔에 로그인

    실습 사용자 인증 정보를 사용하여
  1. 로그인합니다. 다른 사용자 인증 정보를 사용하면 오류가 발생하거나 요금이 부과될 수 있습니다.
  2. 약관에 동의하고 리소스 복구 페이지를 건너뜁니다.
  3. 실습을 완료했거나 다시 시작하려고 하는 경우가 아니면 실습 종료를 클릭하지 마세요. 이 버튼을 클릭하면 작업 내용이 지워지고 프로젝트가 삭제됩니다.

현재 이 콘텐츠를 이용할 수 없습니다

이용할 수 있게 되면 이메일로 알려드리겠습니다.

감사합니다

이용할 수 있게 되면 이메일로 알려드리겠습니다.

한 번에 실습 1개만 가능

모든 기존 실습을 종료하고 이 실습을 시작할지 확인하세요.

시크릿 브라우징을 사용하여 실습 실행하기

이 실습을 실행하는 가장 좋은 방법은 시크릿 모드 또는 시크릿 브라우저 창을 사용하는 것입니다. 개인 계정과 학생 계정 간의 충돌로 개인 계정에 추가 요금이 발생하는 일을 방지해 줍니다.