모듈 1: App Engine webapp2에서 Flask로 마이그레이션

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

이 초기 가이드에서는 App Engine 앱의 웹 프레임워크를 현대화하기 위한 첫 번째 마이그레이션 단계인 webapp2에서 Flask로 이전하는 방법을 설명합니다. 앱에서는 라우팅을 처리하는 모든 웹 프레임워크를 사용할 수 있지만 이 가이드에서는 커뮤니티에서 널리 사용하는 Flask를 사용합니다.

학습 목표

  • 타사 라이브러리(내장 또는 기타) 사용
  • 구성 파일 업데이트
  • 간단한 앱을 webapp2에서 Flask로 마이그레이션

필요한 사항

설문조사

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

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

webapp 프레임워크는 App Engine이 2008년에 처음 Python 2.5에 출시될 때 번들로 제공되었습니다. 2013년에 2.7 런타임이 2.5의 지원을 중단하자 몇 년 뒤 후속 버전인 webapp2로 바뀌었습니다.

webapp2(문서 참조)가 여전히 존재하고 WSGI를 준수하는 웹 프레임워크로 App Engine 외부에서 사용할 수 있지만 애플리케이션의 해당 코드에 자체 사용자 라우팅은 수행하지 않습니다. 대신 App Engine, 구성 파일, 개발자에게 의존하여 웹 트래픽을 해당 '핸들러'로 라우팅합니다. 또한 webapp2의 중요한 이점은 App Engine의 번들 서비스와 복잡하게 묶여있어 Python 3에서 작동하더라도 효과적으로 지원을 중단한다는 것입니다(관련 문제 참조).

이 모듈은 단순한 webapp2 앱을 Flask로 마이그레이션하기 위한 실무 환경, App Engine에서 지원하는 프레임워크, Google Cloud 외부의 많은 서비스를 제공하여 앱을 훨씬 쉽게 이동할 수 있게 해줍니다. 자신의 애플리케이션을 이동하는 프레임워크로 Flask를 원하지 않으면 자체 라우팅을 실행하는 다른 프레임워크를 선택할 수 있습니다. 이 Codelab은 정보 기술 의결결정자(ITDM)와 개발자에게 마이그레이션 단계가 무엇인지 보여주므로, 실제로 어떤 프레임워크로 마이그레이션하든 상관없이 이 프로세스를 숙지할 수 있습니다.

이 마이그레이션의 기본 단계는 다음과 같습니다.

  1. 설정/사전 작업
  2. Flask 타사 라이브러리 추가
  3. 애플리케이션 파일 업데이트
  4. HTML 템플릿 파일 업데이트

이 가이드의 주요 부분을 확인하기 전에 프로젝트를 설정하고, 코드를 가져온 다음, gcloud 명령어를 (다시) 숙지하고, 기본 앱을 배포하여 작동하는 코드를 시작할 수 있도록 준비합니다.

1. 프로젝트 설정

기존 개발자는 App Engine 대시보드에서 이미 실행 중인 서비스를 확인할 수 있습니다. 이 가이드에서는 새 프로젝트를 만들거나 이 가이드에 기존 프로젝트를 다시 사용하는 것이 좋습니다. 프로젝트에 활성 결제 계정이 있고 App Engine(앱)이 사용 설정되어 있는지 확인합니다.

2. 기준 샘플 앱 다운로드

GAE 마이그레이션 저장소에는 필요한 모든 코드가 있습니다. 클론하거나 ZIP 파일을 다운로드합니다. 이 자습서에서는 모듈 0 폴더(시작)에 있는 코드로 시작하고, 가이드를 완료하면 코드가 모듈 1 폴더(완료)와 일치해야 합니다. 그렇지 않은 경우 차이를 확인하여 다음 실습으로 넘어갑니다.

모듈 0 폴더에는 POSIX ls 명령어로 설명한 다음과 같은 파일이 있어야 합니다.

$ ls
app.yaml        index.html      main.py

3. gcloud 명령어를 (다시) 숙지합니다.

컴퓨터에 아직 gcloud 명령어가 없으면 Google Cloud SDK를 설치하고 gcloud가 실행 경로의 일부로 제공되는지 확인한 후 다음 gcloud 명령어를 숙지합니다.

  1. gcloud components update — Google Cloud SDK 업데이트
  2. gcloud auth login — 인증된 계정에 로그인
  3. gcloud config list — GCP 프로젝트 구성 설정 나열
  4. gcloud config set project PROJECT_ID - GCP 프로젝트 ID 설정
  5. gcloud app deploy - App Engine 애플리케이션 배포

최근에 gcloud로 App Engine 개발을 하지 않은 경우 처음 4개의 명령어(#1~#4)를 실행하여 다음 단계로 넘어가야 합니다. 이러한 명령어에 대한 간단한 개요를 살펴보겠습니다.

먼저 gcloud components update는 Cloud SDK 버전이 최신 버전인지 확인합니다. 이 명령어를 실행하면 다음과 같은 출력이 표시됩니다.

$ gcloud components update

Your current Cloud SDK version is: 317.0.0
You will be upgraded to version: 318.0.0

┌──────────────────────────────────────────────────┐
│        These components will be updated.         │
├──────────────────────────┬────────────┬──────────┤
│           Name           │  Version   │   Size   │
├──────────────────────────┼────────────┼──────────┤
│ Cloud SDK Core Libraries │ 2020.11.06 │ 15.5 MiB │
│ gcloud cli dependencies  │ 2020.11.06 │ 10.6 MiB │
└──────────────────────────┴────────────┴──────────┘

The following release notes are new in this upgrade.
Please read carefully for information about new features, breaking changes,
and bugs fixed.  The latest full release notes can be viewed at:
  https://cloud.google.com/sdk/release_notes

318.0.0 (2020-11-10)

      . . .
      (release notes)
      . . .

    Subscribe to these release notes at
    https://groups.google.com/forum/#!forum/google-cloud-sdk-announce.

Do you want to continue (Y/n)?

╔════════════════════════════════════════════════════════════╗
╠═ Creating update staging area                             ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Uninstalling: Cloud SDK Core Libraries                   ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Uninstalling: gcloud cli dependencies                    ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Installing: Cloud SDK Core Libraries                     ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Installing: gcloud cli dependencies                      ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Creating backup and activating new installation          ═╣
╚════════════════════════════════════════════════════════════╝

Performing post processing steps...done.

Update done!

To revert your SDK to the previously installed version, you may run:
  $ gcloud components update --version 317.0.0

그런 다음 gcloud auth login을 사용하여 향후 실행할 gcloud 명령어를 위해 자신을 인증합니다.

$ gcloud auth login
Your browser has been opened to visit:

    https://accounts.google.com/o/oauth2/auth?response_type=code&client_id= . . .

You are now logged in as [YOUR_EMAIL].
Your current project is [PROJECT_ID].  You can change this setting by running:
  $ gcloud config set project PROJECT_ID

gcloud config list를 사용하여 현재 프로젝트 설정을 확인합니다.

$ gcloud config list
[core]
account = YOUR_EMAIL
disable_usage_reporting = False
project = PROJECT_ID

Your active configuration is: [default]

위 명령어는 새 프로젝트를 만들거나 기존 프로젝트를 선택할 수 있도록 안내합니다. gcloud config list의 출력이 이 가이드에서 사용하려는 선택한 프로젝트와 일치하지 않는 경우 gcloud config set project PROJECT_ID를 실행하여 프로젝트 ID를 설정합니다. 그런 다음 gcloud config list를 다시 실행하여 올바른 프로젝트 ID가 설정되었는지 확인합니다.

$ gcloud config set project PROJECT_ID
Updated property [core/project].

Cloud Console을 대신 사용하려는 경우 사용자 인터페이스를 따라 필요에 따라 새 프로젝트를 만들거나 이미 갖고 있는 기존 프로젝트를 사용할 수 있습니다. 프로젝트의 대시보드에 프로젝트 ID(와 프로젝트 이름 및 번호)가 표시된 프로젝트 정보 카드가 나타납니다.

프로젝트 정보 카드

마지막 명령어(#5)는 gcloud app deploy를 App Engine에 배포하는 것입니다. 이제 시작 단계이므로 실행은 선택사항이지만 모듈 0 코드가 제대로 작동하는지 확인하기 위해 배포하는 것을 권장하지 않습니다. 실행 시 앱을 실행할 지리적 위치(일반적으로 사용자의 위치)를 선택합니다. 하지만 설정한 후에는 변경할 수 없습니다. 그런 다음 나머지 배포 정보를 확인합니다. 위 작업이 완료되면 앱이 제공되는 URL을 받게 됩니다. 다음은 변경 사항의 요약본입니다.

$ gcloud app deploy
Services to deploy:

descriptor:      [/private/tmp/mod0-baseline/app.yaml]
source:          [/private/tmp/mod0-baseline]
target project:  [PROJECT_ID]
target service:  [default]
target version:  [20201116t220827]
target url:      [https://PROJECT_ID.REG_ABBR.r.appspot.com]

Do you want to continue (Y/n)?

Beginning deployment of service [default]...
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 1 file to Google Cloud Storage                 ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [default]...done.
Setting traffic split for service [default]...done.
Deployed service [default] to [https://PROJECT_ID.REG_ABBR.r.appspot.com]

You can stream logs from the command line by running:
  $ gcloud app logs tail -s default

To view your application in the web browser run:
  $ gcloud app browse

App Engine을 한동안 사용하지 않았으면 원본 배포 appcfg.py update 명령어gcloud app deploy바뀐 것을 확인할 수 있습니다. gcloud app deploy에 관한 자세한 내용은 문서 페이지를 확인하세요.

또 다른 최근 변경사항은 http://PROJECT_ID.appspot.com에서 http://PROJECT_ID.REG_ABBR.r.appspot.com으로 조정된 배포된 앱의 URL입니다. 대부분의 앱은 최종적으로 새로운 형식으로 전환됩니다. 요청 및 라우팅 문서에서 URL 형식에 대해 자세히 알아보세요.

앱이 배포되면 브라우저를 (몇 번 정도) 새로고침하여 최신 방문수를 확인합니다.

visitme 앱

신규 앱인 경우 방문수가 1회 또는 몇 회만 표시됩니다.

Python 2 App Engine 런타임은 '내장형' 타사 라이브러리 모음을 제공합니다. 이 라이브러리는 사용할 app.yaml 파일에서 지정하기만 하면 됩니다. 이 마이그레이션에는 필수 사항은 아니지만 모듈 2를 위한 다음 마이그레이션 가이드가 있습니다.

내장형이 아닌 타사 라이브러리는 requirements.txt라는 파일에 지정해야 하며 모든 항목이 App Engine에 업로드되는 애플리케이션 코드와 동일한 디렉터리의 lib 폴더에 로컬로 설치해야 합니다. 자세한 내용은 타사 라이브러리 번들 문서를 참조하세요.

Flask와 같이 복사된 라이브러리에서는 appengine_config.py 구성 파일을 사용하여 lib 폴더에서 라이브러리를 찾도록 App Engine에 지시해야 합니다. appengine_config.py 구성 파일은 requirements.txtlib과 같은 최상위 애플리케이션 폴더에 있습니다. 가이드의 이 부분에서는 다음 작업을 수행합니다.

  • requirements.txt 만들기(복사된 [내장이 아닌] 타사 라이브러리 지정)
  • appengine_config.py 만들기(타사 라이브러리 인식)
  • (타사) 패키지 및 종속 항목 설치

1. requirements.txt 만들기

requirements.txt 파일을 만들어 패키지를 지정합니다. 이 경우 Flask는 필요한 타사 라이브러리입니다. 이 문서를 작성하는 시점에서 최신 버전은 1.1.2이므로 다음 한 줄로 requirements.txt를 만듭니다.

Flask==1.1.2

허용되는 형식에 대한 자세한 내용은 requirements.txt 문서를 참조하세요.

2. appengine_config.py 만들기

다음 단계는 App Engine이 외부 타사 라이브러리를 인식하도록 하는 것입니다. 다음 콘텐츠로 appengine_config.py라는 파일을 만듭니다.

from google.appengine.ext import vendor

# Set PATH to your libraries folder.
PATH = 'lib'
# Add libraries installed in the PATH folder.
vendor.add(PATH)

이 코드는 앞서 지정한 대로 정확히 작동합니다. 즉, App Engine이 복사된 라이브러리에 대한 lib 폴더를 가리킵니다.

3. 패키지 및 종속 항목 설치

이제 pip install 명령어를 실행하여 lib 폴더를 생성하고 Flask 및 해당 종속 항목을 설치합니다.

$ pip install -t lib -r requirements.txt

pip 또는 pip2를 사용했는지 여부와 관계없이 패키지 설치가 완료된 후 다음과 유사한 콘텐츠가 포함된 lib 폴더가 생성됩니다.

$ ls lib
bin/
click/
click-7.1.2.dist-info/
flask/
Flask-1.1.2.dist-info/
itsdangerous/
itsdangerous-1.1.0.dist-info/
jinja2/
Jinja2-2.11.2.dist-info/
markupsafe/
MarkupSafe-1.1.1.dist-info/
werkzeug/
Werkzeug-1.0.1.dist-info/

이제 애플리케이션 파일 main.py를 업데이트합니다.

1. 가져오기

모든 Python 파일에서와 같이 가져오기가 가장 먼저 나옵니다. webapp2 프레임워크 가져오기에 이어 ndb Datastore 라이브러리가 나오며, 마지막은 Django로 컴파일된 템플릿을 처리하는 App Engine 확장 프로그램입니다. 아래와 같이 표시됩니다.

  • 이전:
import webapp2
from google.appengine.ext import ndb
from google.appengine.ext.webapp import template

Flask로 이동하면 Flask와 템플릿 렌더기를 동시에 가져옵니다. webapp2 관련 가져오기의 쌍을 삭제하고 다음과 같이 바꿉니다(ndb 가져오기는 그대로 유지).

  • 이후:
from flask import Flask, render_template, request
from google.appengine.ext import ndb

2. 시작

webapp2를 사용하는 앱에는 모든 Python 파일의 모든 경로와 핸들러(다른 것도 있을 수 있음)를 나열하는 단일 배열(Python 목록)이 필요합니다.

  • 이전:
app = webapp2.WSGIApplication([
    ('/', MainHandler),
], debug=True)

app.yaml은 상위 수준의 라우팅을 수행하고 다른 핸들러를 호출할 수 있다는 점에 유의하세요. 샘플 앱은 모든 경로가 main.py 핸들러로 향할 만큼 간단합니다.

Flask는 이와 같은 라우팅 테이블을 사용하지 않으므로 main.py에서 이러한 줄을 삭제합니다. Flask도 초기화가 필요하므로 가져오기 바로 아래 main.py상단에 다음 줄을 추가합니다.

  • 이후:
app = Flask(__name__)

Flask에서는 프레임워크를 초기화한 다음 데코레이터를 사용하여 경로를 정의합니다. 또한 경로는 클래스 또는 메서드가 아닌 함수와 페어링됩니다.

이 Codelab에 Flask 가이드를 포함하는 것은 범위를 벗어나므로 시간을 들여 Flask 가이드를 살펴보고 Flask 문서를 검토하여 프레임워크를 숙지합니다.

3. 데이터 모델

변경사항이 없습니다. Datastore는 다음 Codelab에 중점을 둡니다.

4. 핸들러

사용하는 프레임워크(webapp2 또는 Flask)와 관계없이 애플리케이션은 다음 세 가지 작업을 수행합니다.

  1. 루트 경로(/) GET 요청 처리
  2. 웹페이지 '방문' 등록(Visit 객체 생성/저장)
  3. 사전 정의된 템플릿 index.html을 사용하여 최근 방문 상위 10개 표시

webapp2 프레임워크는 지원되는 각 HTTP 메서드에 대해 핸들러가 생성되는 클래스 기반 실행 모델을 사용합니다. 다음의 간단한 예시에서는 GET만 있으므로 get() 메서드가 정의됩니다.

  • 이전:
class MainHandler(webapp2.RequestHandler):
    def get(self):
        store_visit(self.request.remote_addr, self.request.user_agent)
        visits = fetch_visits(10) or ()  # empty sequence if None
        tmpl = os.path.join(os.path.dirname(__file__), 'index.html')
        self.response.out.write(template.render(tmpl, {'visits': visits}))

위에서 언급한 바와 같이 Flask는 자체 라우팅을 수행합니다. 핸들러 클래스 대신 함수를 작성하고 함수를 호출해야 하는 경로로 데코레이션합니다. 사용자는 데코레이터 호출에서 처리되는 HTTP 메서드(즉, @app.route('/app/', methods=['GET', 'POST']))를 지정할 수 있습니다. 기본값은 GET(및 암시적으로 HEAD)뿐이므로 중단될 수 있습니다.

Flask로 마이그레이션할 때 MainHandler 클래스 및 get() 메서드를 다음 Flask 라우팅 함수로 바꿉니다.

  • 이후:
@app.route('/')
def root():
    store_visit(request.remote_addr, request.user_agent)
    visits = fetch_visits(10) or ()  # empty sequence if None
    return render_template('index.html', visits=visits)

물론 이 방법은 앱을 나타내지 않으므로 이 샘플보다 더 복잡합니다. 이 가이드의 기본 목표 중 하나는 시작하는 데 도움을 주고, 일부 '메시 메모리'를 빌드하고, App Engine별 코드에서 무엇을 변경해야 하는지 파악하는 것입니다. 이 변경사항을 올바르게 적용했는지 확인하려면 모듈 1 main.py과 비교해 보세요.

5. 보조 파일

.gcloudignore 파일에 변경사항이 없습니다. 목적은 보조 Python, 소스 제어, 저장소 상용구, 기타 파일을 포함하여 애플리케이션을 배포하고 실행하는 데 필요하지 않은 파일을 App Engine에 배포하지 않도록 파일을 지정하는 데 있습니다. .gcloudignore는 다음과 같이 표시됩니다(간결성을 위해 주석 삭제).

.gcloudignore
.git
.gitignore
.hgignore
.hg/
*.pyc
*.pyo
__pycache__/
/setup.cfg
README.md

1. 템플릿 파일 이동

기준 저장소 폴더(모듈 0)에서 index.html 템플릿 파일은 애플리케이션 파일과 동일한 폴더에 있습니다. Flask를 사용하려면 HTML 폴더에 templates 파일이 있어야 하므로 이 폴더(mkdir templates)를 만든 후 index.html을 이동해야 합니다. Linux 또는 Mac OS X와 같은 POSIX 규격 시스템에서 명령어는 다음과 같습니다.

mkdir templates
mv index.html templates

2. 템플릿 파일 업데이트

index.htmltemplates로 이동한 후에는 약간만 수정해야 합니다. 원본 템플릿 파일을 전체적으로 살펴보겠습니다.

<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
<body>

<h1>VisitMe example</h1>
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
    <li>{{ visit.timestamp.ctime }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>

</body>
</html>

webapp2는 괄호 ( ) 없이 visit.timestamp.ctime과 같은 호출 함수를 실행하는 Django 템플릿을 사용하지만 Jinja2는 이를 명시적으로 필요로 합니다. 사소한 변화처럼 보일 수 있지만, Jinja 템플릿은 호출 시 인수를 전달할 수 있으므로 즉시 사용할 수 있습니다.

Django에서는 '템플릿 태그'를 만들거나 필터를 작성해야 합니다. 이러한 내용을 바탕으로 visit.timestamp.ctime 호출에 괄호 쌍을 추가하여 index.html을 업데이트합니다.

  • 이전:
<li>{{ visit.timestamp.ctime }} from {{ visit.visitor }}</li>
  • 이후:
<li>{{ visit.timestamp.ctime() }} from {{ visit.visitor }}</li>

이는 유일하게 필요한 변경사항입니다. 남은 모든 마이그레이션 Codelab에는 index.html에 대한 추가 변경사항이 없습니다.

애플리케이션 배포

이 가이드의 모든 변경사항을 완료하면 애플리케이션 폴더의 파일은 모듈 1 저장소 폴더의 파일과 동일하거나 거의 같아야 합니다. 이제 모듈 1 Flask 애플리케이션이 모듈 0 webapp2 버전과 동일하게 실행되는지 확인합니다.

앞에서 원본 모듈 0 코드를 배포할 때 사용한 gcloud app deploy 명령어를 사용합니다. 웹브라우저나 curl 또는 wget 명령어에서 PROJECT_ID.appspot.com에 액세스하여 예상대로 작동하는지 확인합니다.

서버 오류가 발생하는 경우 일반적으로 Python 코드에 오타가 있음을 의미합니다. 조사할 애플리케이션 로그를 살펴봅니다. 또한 모듈 1 저장소에 있는 파일(위 링크 참고)과 파일을 비교합니다.

선택사항: 삭제

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

선택사항: 앱 사용 중지

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

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

다음 단계

완성된 모듈 1 코드로 시작하는 마이그레이션 모듈에는 모듈 2와 7, 이 두 개가 있습니다.

  • 모듈 2(Datastore를 사용하는 경우 필요)
    • App Engine ndb에서 Cloud NDB로 마이그레이션
    • Cloud NDB로 전환하면 다른 많은 옵션을 사용할 수 있습니다.
      • Cloud Run에서 실행할 앱 컨테이너화
      • 앱을 Cloud Datastore 클라이언트 라이브러리로 추가 마이그레이션
      • Firebase Firestore에 액세스할 수 있도록 앱을 Cloud Firestore로 마이그레이션
  • 모듈 7([푸시] 태스크 큐를 사용하는 경우 필요)
    • App Engine (push) taskqueue 사용량 추가
    • 모듈 1에서 모듈 8의 Cloud Tasks로 마이그레이션하기 위한 모듈 1 앱을 준비합니다.

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

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

마이그레이션 리소스

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

Codelab

Python 2

Python 3

모듈 0

코드

(해당 없음)

모듈 1

코드

(해당 없음)

App Engine 리소스

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