모듈 8: App Engine ndb 및 taskqueue에서 Cloud NDB 및 Cloud Tasks로 마이그레이션

이 Codelab 시리즈(사용자 주도형, 실무 가이드)는 Google App Engine(표준) 개발자가 일련의 마이그레이션을 통해 자신의 앱을 현대화할 수 있도록 돕기 위한 것입니다. 가장 중요한 단계는 차세대 런타임이 더 유연하고 다양한 서비스 옵션을 제공하기 때문에 원래의 런타임 번들 서비스로부터 벗어나는 것입니다. 새로운 세대의 런타임으로 이동하면 Google Cloud 제품과 보다 쉽게 통합하고, 지원되는 다양한 서비스를 사용하고, 최신 출시 버전을 지원할 수 있습니다.

이 Codelab은 App Engine push 태스크와 taskqueue API/라이브러리를 Cloud Tasks로 마이그레이션하는 데 도움이 됩니다. 앱이 태스크 큐를 사용하지 않는 경우 App Engine push 태스크를 Cloud Tasks로 마이그레이션하는 방법을 배우기 위한 연습으로 이 Codelab을 사용할 수 있습니다.

학습 목표

  • App Engine taskqueue에서 Cloud Tasks로 마이그레이션
  • Cloud Tasks로 push 태스크 만들기
  • App Engine ndb에서 Cloud NDB로 마이그레이션(모듈 2와 동일)

필요한 사항

설문조사

이 Codelab을 어떻게 사용할 예정인가요?

읽기만 할 계획입니다. 읽은 다음 연습 활동을 완료할 계획입니다.

이전(모듈 7) Codelab에서 App Engine push 태스크를 샘플 앱에 추가했으므로 이제 이를 Cloud Tasks로 마이그레이션할 수 있습니다. 이 가이드의 마이그레이션 기능에는 다음과 같은 기본 단계가 사용됩니다.

  1. 설정/사전 작업
  2. 구성 파일 업데이트
  3. 기본 애플리케이션 업데이트

이 가이드의 주요 부분을 진행하기 전에 프로젝트를 설정하고 코드를 가져온 후 기본 앱을 배포하여 작동하는 코드로 시작할 수 있도록 준비합니다.

1. 프로젝트 설정

모듈 7 Codelab을 완료하는 데 사용한 것과 동일한 프로젝트를 다시 사용하는 것이 좋습니다. 또는 완전히 새로운 프로젝트를 만들거나 다른 기존 프로젝트를 다시 사용할 수도 있습니다. 프로젝트에 활성 결제 계정이 있고 App Engine(앱)이 사용 설정되어 있는지 확인합니다.

2. 기준 샘플 앱 가져오기

이 Codelab의 기본 요건 중 하나는 작동하는 모듈 7 샘플 앱을 준비하는 것입니다. 샘플 앱이 없는 경우에는 계속하기 전에 모듈 7 가이드(위 링크)를 완료하세요. 가이드 내용을 잘 알고 계시다면 아래 모듈 7 코드를 선택하여 시작하세요.

그 코드가 사용자의 것이든 Google의 것이든 관계없이 모듈 7 코드에서부터 시작합니다. 이 모듈 2 Codelab은 각 단계를 진행합니다. 완료되었을 때는 완료 지점의 코드와 비슷해야 합니다(Python 2~3의 선택적인 포트 포함).

모듈 7 파일(사용자 또는 Google의 파일)의 디렉터리는 다음과 같습니다.

$ ls
README.md               appengine_config.py     requirements.txt
app.yaml                main.py                 templates

모듈 7 가이드를 완료했으면 Flask 및 해당 종속 항목이 포함된 lib 폴더가 있을 것입니다.

3. 모듈 7 앱 (재)배포

이제 남은 사전 작업 실행 단계는 다음과 같습니다.

  1. gcloud 명령줄 도구 사용 방법을 다시 숙지합니다(필요한 경우).
  2. 모듈 7 코드를 App Engine에 (재)배포합니다(필요한 경우).

이러한 단계를 성공적으로 실행하고, 작동하는지 확인한 후에는 이 가이드로 이동하여, 구성 파일 작업을 시작합니다.

requirements.txt

모듈 7의 requirements.txt는 필요한 패키지로 Flask만 나열합니다. Cloud NDB 및 Cloud Tasks에는 자체 클라이언트 라이브러리가 있으므로 이 단계에서는 해당 패키지를 requirements.txt에 추가합니다. 그러면 다음과 같이 표시됩니다.

Flask==1.1.2
google-cloud-ndb==1.7.1
google-cloud-tasks==1.5.0

각 라이브러리의 최신 버전을 사용하는 것이 좋습니다. 이 문서 작성 당시의 버전 번호는 Python 2의 최신 버전입니다. Python 3에 상응하는 패키지의 버전이 더 높을 수 있습니다. 완료 저장소 폴더의 코드는 더 자주 업데이트되며 최신 릴리스를 포함할 수 있습니다. 하지만 이는 보통 고정되어 있는 Python 2 라이브러리에 사용될 가능성이 낮습니다.

app.yaml

libraries 섹션의 app.yaml에서 grpciosetuptools 내장 라이브러리를 참조합니다.

libraries:
- name: grpcio
  version: 1.0.0
- name: setuptools
  version: 36.6.0

appengine_config.py

pkg_resources를 사용하여 이 기본 제공 라이브러리를 Flask와 같은 복사된 타사 라이브러리와 Google Cloud 클라이언트 라이브러리에 연결하려면 appengine_config.py를 업데이트합니다.

import pkg_resources
from google.appengine.ext import vendor

# Set PATH to your libraries folder.
PATH = 'lib'
# Add libraries installed in the PATH folder.
vendor.add(PATH)
# Add libraries to pkg_resources working set to find the distribution.
pkg_resources.working_set.add_entry(PATH)

애플리케이션 파일이 main.py이므로 이 섹션의 모든 변경사항은 이 파일에만 영향을 줍니다.

가져오기 및 초기화 업데이트

현재 앱은 기본 제공되는 google.appengine.api.taskqueuegoogle.appengine.ext.ndb 라이브러리를 사용합니다.

  • 이전:
from datetime import datetime
import logging
import time
from flask import Flask, render_template, request
from google.appengine.api import taskqueue
from google.appengine.ext import ndb

둘 다 google.cloud.ndbgoogle.cloud.tasks로 바꿉니다. 또한 Cloud Tasks에서는 태스크의 페이로드를 JSON으로 인코딩해야 하므로 json도 가져옵니다. 완료되면 main.pyimport 섹션이 다음과 같이 표시됩니다.

  • 이후:
from datetime import datetime
import json
import logging
import time
from flask import Flask, render_template, request
from google.cloud import ndb, tasks

Cloud Tasks(및 Cloud NDB)로 마이그레이션

  • 이전:
def store_visit(remote_addr, user_agent):
    'create new Visit entity in Datastore'
    Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

모듈 2에서 모든 Datastore 액세스에 컨텍스트 관리자를 추가한 것 외에 store_visit()에서 달리 변경된 사항은 없습니다. 이는 with 문에서 래핑된 새 Visit 항목의 생성을 이동하는 형태로 나타납니다.

  • 이후:
def store_visit(remote_addr, user_agent):
    'create new Visit entity in Datastore'
    with ds_client.context():
        Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

현재 Cloud Tasks를 사용하려면 (App Engine 코드가 없더라도) Google Cloud 프로젝트에 App Engine을 사용 설정해야 합니다. 그렇지 않으면 태스크 큐가 작동하지 않습니다. 자세한 내용은 문서의 이 섹션을 참조하세요. Cloud Tasks는 App Engine(App Engine '대상')에서 실행되는 태스크를 지원하지만 Cloud Functions, Cloud Run, GKE, Compute Engine이나 심지어 온프렘 서버와 같이 공개 IP 주소가 있는 모든 HTTP 엔드포인트(HTTP 대상)에서도 실행할 수 있습니다. 이 간단한 앱에서는 App Engine 대상을 태스크에 사용합니다.

Cloud NDB 및 Cloud Tasks를 사용하려면 일부 설정이 필요합니다. Flask 초기화 아래 main.py 상단에서 Cloud NDB 및 Cloud Tasks를 초기화합니다. 또한 push 태스크가 실행될 위치를 나타내는 상수도 정의합니다.

app = Flask(__name__)
ds_client = ndb.Client()
ts_client = tasks.CloudTasksClient()

PROJECT_ID = 'PROJECT_ID'  # replace w/your own
REGION = 'REGION'    # replace w/your own
QUEUE_NAME = 'default'     # replace w/your own if desired
QUEUE_PATH = ts_client.queue_path(PROJECT_ID, REGION, QUEUE_NAME)

태스크 큐를 생성한 후 프로젝트의 PROJECT_ID와 태스크가 실행되는 REGION(App Engine 리전과 동일해야 함)과 push 큐의 이름을 입력합니다. App Engine에는 'default' 큐가 있으므로 Google에서 이 이름을 사용합니다. 하지만 꼭 사용해야 하는 것은 아닙니다.

default 큐는 특별하며 App Engine API를 사용하는 경우를 비롯하여 특정 상황에서는 자동으로 생성됩니다. 따라서 모듈 7과 동일한 프로젝트를 (다시) 사용하는 경우 default는 이미 존재합니다. 하지만 모듈 8용으로 프로젝트를 만든 경우 default를 수동으로 생성해야 합니다. default 큐에 대한 자세한 내용은 queue.yaml 문서를 참조하세요.

ts_client.queue_path()의 목적은 태스크를 만드는 데 필요한 태스크 큐의 '정규화된 경로 이름'(QUEUE_PATH)을 만드는 것입니다. 또한 태스크 매개변수를 지정하는 JSON 구조도 필요합니다.

task = {
    'app_engine_http_request': {
        'relative_uri': '/trim',
        'body': json.dumps({'oldest': oldest}).encode(),
        'headers': {
            'Content-Type': 'application/json',
        },
    }
}

위에 무엇이 보이시나요?

  1. 태스크 대상 정보 제공:
    • App Engine 대상의 경우 요청 유형으로 app_engine_http_request를 지정하고 relative_uri는 App Engine 태스크 핸들러입니다.
    • HTTP 대상의 경우 http_requesturl을 대신 사용합니다.
  2. body: (push) 태스크에 보낼 JSON 및 유니코드 문자열 인코딩 매개변수
  3. JSON으로 인코딩된 Content-Type 헤더를 명시적으로 지정합니다.

여기에서 옵션에 관한 자세한 내용은 문서를 참조하세요.

설정이 완료되면 fetch_visits()를 업데이트합니다. 이전 가이드의 내용은 다음과 같습니다.

  • 이전:
def fetch_visits(limit):
    'get most recent visits and add task to delete older visits'
    data = Visit.query().order(-Visit.timestamp).fetch(limit)
    oldest = time.mktime(data[-1].timestamp.timetuple())
    oldest_str = time.ctime(oldest)
    logging.info('Delete entities older than %s' % oldest_str)
    taskqueue.add(url='/trim', params={'oldest': oldest})
    return (v.to_dict() for v in data), oldest_str

필수 업데이트 항목:

  1. App Engine ndb에서 Cloud NDB로 전환
  2. 가장 오래된 방문의 타임스탬프를 추출하는 새 코드
  3. Cloud Tasks를 사용하여 App Engine taskqueue 대신 새 태스크 만들기

fetch_visits()는 다음과 같이 표시됩니다.

  • 이후:
def fetch_visits(limit):
    'get most recent visits and add task to delete older visits'
    with ds_client.context():
        data = Visit.query().order(-Visit.timestamp).fetch(limit)
    oldest = time.mktime(data[-1].timestamp.timetuple())
    oldest_str = time.ctime(oldest)
    logging.info('Delete entities older than %s' % oldest_str)
    task = {
        'app_engine_http_request': {
            'relative_uri': '/trim',
            'body': json.dumps({'oldest': oldest}).encode(),
            'headers': {
                'Content-Type': 'application/json',
            },
        }
    }
    ts_client.create_task(parent=QUEUE_PATH, task=task)
    return (v.to_dict() for v in data), oldest_str

코드 업데이트 요약:

  • Cloud NDB로 전환한다는 것은 with 문 내에서 Datastore 코드를 이동하는 것을 의미합니다.
  • Cloud Tasks로 전환하면 taskqueue.add() 대신 ts_client.create_task()가 사용됩니다.
  • 큐의 전체 경로와 task 페이로드 전달(앞에서 설명)

(push) 태스크 핸들러 업데이트

(push) 태스크 핸들러 함수는 변경해야 할 부분이 거의 없습니다.

  • 이전:
@app.route('/trim', methods=['POST'])
def trim():
    '(push) task queue handler to delete oldest visits'
    oldest = request.form.get('oldest', type=float)
    keys = Visit.query(
            Visit.timestamp < datetime.fromtimestamp(oldest)
    ).fetch(keys_only=True)
    nkeys = len(keys)
    if nkeys:
        logging.info('Deleting %d entities: %s' % (
                nkeys, ', '.join(str(k.id()) for k in keys)))
        ndb.delete_multi(keys)
    else:
        logging.info('No entities older than: %s' % time.ctime(oldest))
    return ''   # need to return SOME string w/200

쿼리 요청과 삭제 요청 모두 컨텍스트 관리자 with 문 내에 모든 Datastore 액세스를 포함하기만 하면 됩니다. 이를 염두에 두고 trim() 핸들러를 다음과 같이 업데이트하세요.

  • 이후:
@app.route('/trim', methods=['POST'])
def trim():
    '(push) task queue handler to delete oldest visits'
    oldest = float(request.get_json().get('oldest'))
    with ds_client.context():
        keys = Visit.query(
                Visit.timestamp < datetime.fromtimestamp(oldest)
        ).fetch(keys_only=True)
        nkeys = len(keys)
        if nkeys:
            logging.info('Deleting %d entities: %s' % (
                    nkeys, ', '.join(str(k.id()) for k in keys)))
            ndb.delete_multi(keys)
        else:
            logging.info('No entities older than: %s' % time.ctime(oldest))
    return ''   # need to return SOME string w/200

이 Codelab 또는 다음 Codelab에서는 templates/index.html에 변경사항이 없습니다.

애플리케이션 배포

코드가 컴파일되고 다시 배포되는지 등 모든 변경사항을 다시 확인합니다. 앱이 여전히 작동하는지 확인합니다. 모듈 7과 동일한 출력을 사용해야 합니다. 최근 내부의 배선을 바꾸었으므로 모든 것이 정상적으로 작동해야 합니다.

모듈 7 Codelab을 수행하지 않고 이 가이드를 바로 시작한 경우에도 앱 자체가 변경되지 않습니다. 앱은 모든 방문을 기본 웹페이지(/)에 등록하고 사이트를 충분히 방문하면 다음과 같이 표시될 뿐 아니라 10회를 초과하는 모든 방문을 삭제했다고 사용자에게 알립니다.

모듈 7 visitme 앱

이것으로 이 Codelab을 마칩니다. 이제 사용자의 코드는 모듈 8 저장소에 있는 것과 일치해야 합니다. push 태스크 마이그레이션의 가장 중요한 단계를 완료하셨습니다. 모듈 9(아래 Codelab 링크)는 선택사항이며 사용자가 Python 3 및 Cloud Datastore로 이동하는 데 도움이 됩니다.

선택사항: 삭제

다음 마이그레이션 Codelab으로 이동할 준비가 될 때까지 비용이 결제되지 않도록 하려면 삭제를 수행해야 합니다. 기존 개발자라면 App Engine 가격 책정 정보를 이미 잘 알고 계실 것입니다.

선택사항: 앱 사용 중지

다음 가이드로 이동할 준비가 되지 않았으면 비용 발생을 방지하기 위해 앱을 사용 중지하세요. 다음 Codelab으로 이동할 준비가 되었으면 이를 다시 사용 설정하면 됩니다. 앱이 사용 중지되면 비용을 일으키는 트래픽이 수행되지 않습니다. 하지만 무료 할당량을 초과할 경우 해당 Datastore 사용량에 대한 비용이 결제될 수 있습니다. 따라서 제한에 걸리지 않도록 충분히 삭제해야 합니다.

반면에 마이그레이션을 계속하지 않고 모든 것을 완전히 삭제하려면 프로젝트를 종료하면 됩니다.

다음 단계

이 가이드 이외에 다음 단계는 모듈 9와 그 Codelab 및 Python 3으로의 포팅입니다. 일부 사용자는 이 단계를 맞이할 준비가 안 되어 있기 때문에 이는 선택사항입니다. Cloud NDB에서 Cloud Datastore로의 선택적 포트도 있습니다. 이는 무제한으로 선택 가능하며 NDB에서 이전하고 Cloud Datastore를 사용하는 코드를 통합하려는 사용자에게만 제공됩니다. 마이그레이션은 모듈 3 마이그레이션 Codelab과 동일합니다.

  • 모듈 9 Python 2에서 3으로, Cloud NDB를 Cloud Datastore로 마이그레이션
    • Python 3으로 포팅하는 선택적 마이그레이션 모듈
    • 또한 Cloud NDB에서 Cloud Datastore로의 선택적 마이그레이션(모듈 3과 동일)이 포함됩니다.
    • Cloud Tasks v1에서 v2로 소규모 마이그레이션(Python 2용 클라이언트 라이브러리 고정)
  • 모듈 4: Docker를 사용하여 Cloud Run으로 마이그레이션합니다.
    • Docker를 사용하여 Cloud Run에서 실행하기 위해 앱을 컨테이너화합니다.
    • 이 마이그레이션을 통해 Python 2를 계속 사용할 수 있습니다.
  • 모듈 5: Cloud Buildpacks를 사용하여 Cloud Run으로 마이그레이션
    • Cloud Buildpacks를 사용하여 Cloud Run에서 실행할 앱 컨테이너화
    • Docker, 컨테이너, Dockerfile에 대해 알 필요가 없습니다.
    • 앱이 이미 Python 3로 마이그레이션되어 있어야 합니다(Buildpacks는 Python 2를 지원하지 않음).
  • 모듈 6: Cloud Firestore로 마이그레이션
    • Cloud Firestore로 마이그레이션하여 Firebase 기능에 액세스
    • Cloud Firestore는 Python 2를 지원하지만 이 Codelab은 Python 3에서만 사용할 수 있습니다.

App Engine 마이그레이션 모듈 Codelab 문제/의견

이 Codelab에 문제가 발견된 경우 문제를 기록하기 전에 먼저 비슷한 기록이 있는지 검색해보세요. 검색 및 새 문제 만들기 링크:

마이그레이션 리소스

모듈 7(시작) 및 모듈 8(완료)의 저장소 폴더 링크는 아래 표에서 찾을 수 있습니다. 또한 클론 또는 ZIP 파일로 다운로드할 수 있는 모든 App Engine Codelab 마이그레이션 저장소에서 액세스할 수도 있습니다.

Codelab

Python 2

Python 3

모듈 7

코드

(해당 없음)

모듈 8

코드

(해당 없음)

App Engine 리소스

다음은 이 특정 마이그레이션과 관련된 추가적인 리소스입니다.