App Engine 사용자 서비스에서 Cloud Identity Platform으로 마이그레이션 (모듈 21)

1. 개요

서버리스 마이그레이션 스테이션 Codelab 시리즈 (사용자 주도형, 실무 가이드) 및 관련 동영상Google Cloud 서버리스 개발자가 주로 기존 서비스에서 벗어나는 하나 이상의 마이그레이션을 통해 애플리케이션을 현대화할 수 있도록 돕기 위한 것입니다. 이렇게 하면 앱의 이식성이 높아지고 더 많은 옵션과 유연성을 제공하여 다양한 Cloud 제품과 통합하고 액세스할 수 있으며 최신 언어 출시로 더 쉽게 업그레이드할 수 있습니다. 이 시리즈는 처음에는 주로 App Engine (표준 환경) 개발자와 같은 초기 Cloud 사용자를 대상으로 하지만, Cloud Functions, Cloud Run과 같은 다른 서버리스 플랫폼이나 해당하는 경우 다른 플랫폼도 포함할 수 있을 만큼 광범위합니다.

이 Codelab의 목적은 Python 2 App Engine 개발자에게 App Engine Users API/서비스에서 Cloud Identity Platform (GCIP)으로 마이그레이션하는 방법을 보여주는 것입니다. Datastore 액세스를 위한 App Engine NDB에서 Cloud NDB로의 암시적 마이그레이션 (주로 마이그레이션 모듈 2에서 다룸)과 Python 3로의 업그레이드도 있습니다.

모듈 20에서는 모듈 1 샘플 앱에 Users API 사용을 추가하는 방법을 다룹니다. 이 모듈에서는 완료된 모듈 20 앱을 가져와 Cloud Identity Platform으로 사용을 이전합니다.

다음 실습에서는

  • App Engine 사용자 서비스Cloud Identity Platform으로 대체
  • App Engine NDB 사용을 Cloud NDB로 대체 (모듈 2 참고)
  • Firebase 인증을 사용하여 다양한 인증 ID 공급업체 설정
  • Cloud Resource Manager API를 사용하여 프로젝트 IAM 정보 가져오기
  • Firebase Admin SDK를 사용하여 사용자 정보 가져오기
  • 샘플 애플리케이션을 Python 3로 포팅

필요한 항목

설문조사

본 가이드를 어떻게 사용하실 계획인가요?

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

귀하의 Python 사용 경험이 어떤지 평가해 주세요.

초급 중급 고급

귀하의 Google Cloud 서비스 사용 경험을 평가해 주세요.

초급 중급 고급

2. 배경

App Engine Users 서비스는 App Engine 앱에서 사용할 수 있는 사용자 인증 시스템입니다. ID 공급자로 Google 로그인을 제공하고, 앱에서 사용할 수 있는 편리한 로그인 및 로그아웃 링크를 제공하며, 관리자 사용자 및 관리자 전용 기능을 지원합니다. 애플리케이션 이식성을 개선하기 위해 Google Cloud에서는 기존 App Engine 번들 서비스에서 Cloud 독립형 서비스로 마이그레이션할 것을 권장합니다(예: Users 서비스에서 Cloud Identity Platform으로).

Identity Platform은 Firebase 인증을 기반으로 하며 다중 인증, OIDC 및 SAML SSO 지원, 멀티테넌시, 99.95% SLA 등의 다양한 엔터프라이즈 기능을 추가합니다. 이러한 차이점은 Identity Platform과 Firebase 인증 제품 비교 페이지에서도 강조 표시됩니다. 두 제품 모두 사용자 서비스에서 제공하는 기능보다 훨씬 많은 기능을 제공합니다.

이 모듈 21 Codelab에서는 앱의 사용자 인증을 Users 서비스에서 모듈 20에 설명된 기능과 가장 유사한 Identity Platform 기능으로 전환하는 방법을 보여줍니다. 모듈 21에서는 Datastore 액세스를 위해 App Engine NDB에서 Cloud NDB로 마이그레이션하는 기능도 제공하여 모듈 2 마이그레이션을 반복합니다.

모듈 20 코드는 Python 2 샘플 앱으로 '광고'되지만 소스 자체는 Python 2 및 3과 호환되며 모듈 21에서 Identity Platform (및 Cloud NDB)으로 마이그레이션한 후에도 마찬가지입니다. Identity Platform으로의 이전은 선택사항이므로 Python 3로 업그레이드하는 동안 Users 서비스를 계속 사용할 수 있습니다. Python 3과 같은 2세대 런타임으로 업그레이드하는 동안 번들 서비스를 계속 사용하는 방법을 알아보려면 모듈 17 Codelab과 동영상을 참고하세요.

이 튜토리얼에서는 다음 단계를 다룹니다.

  1. 설정/사전 작업
  2. 구성 업데이트
  3. 애플리케이션 코드 수정

3. 설정/사전 작업

이 섹션에서는 다음을 수행하는 방법을 설명합니다.

  1. Cloud 프로젝트 설정
  2. 기준 샘플 앱 가져오기
  3. 기준 앱 (재)배포 및 검증
  4. 새 Google Cloud 서비스/API 사용 설정

이 단계를 따르면 독립형 Cloud 서비스로 마이그레이션할 준비가 된 작동하는 코드로 시작할 수 있습니다.

1. 프로젝트 설정

모듈 20 Codelab을 완료했으면 동일 프로젝트 (및 코드)를 다시 사용하세요. 또는 완전히 새로운 프로젝트를 만들거나 다른 기존 프로젝트를 다시 사용할 수도 있습니다. 프로젝트에 활성 결제 계정이 있고 App Engine 앱이 사용 설정되어 있는지 확인합니다. 이 Codelab을 진행하는 동안 프로젝트 ID를 찾아두고 PROJ_ID 변수가 표시될 때마다 사용합니다.

2. 기준 샘플 앱 가져오기

기본 요건 중 하나는 작동하는 모듈 20 App Engine 앱이므로 Codelab을 완료하거나 (권장; 위 링크) 저장소에서 모듈 20 코드를 복사하세요. 무엇을 사용하든 여기서 시작합니다 ('START'). 이 Codelab에서는 이전 과정을 안내하며, 모듈 21 저장소 폴더 ('FINISH')에 있는 것과 비슷한 코드로 마무리합니다.

모듈 20 저장소 폴더를 복사합니다. 아래 출력과 비슷하며, 모듈 20 Codelab을 진행한 경우 lib 폴더가 있을 수 있습니다.

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

3. 기준 앱 (재)배포 및 검증

다음 단계를 실행하여 모듈 20 앱을 배포합니다.

  1. lib 폴더가 있으면 삭제하고 pip install -t lib -r requirements.txt를 실행하여 다시 채웁니다. Python 2와 3이 모두 설치된 경우 pip2를 사용해야 할 수 있습니다.
  2. gcloud 명령줄 도구를 설치하고 초기화했으며 사용법을 검토했는지 확인합니다.
  3. gcloud 명령어를 실행할 때마다 PROJ_ID를 입력하지 않으려면 먼저 gcloud config set project PROJ_ID로 클라우드 프로젝트를 설정하세요.
  4. gcloud app deploy로 샘플 앱 배포
  5. 앱이 오류 없이 예상대로 실행되는지 확인합니다. 모듈 20 Codelab을 완료한 경우 앱은 가장 최근 방문과 함께 상단에 사용자 로그인 정보 (사용자 이메일, 가능한 경우 '관리자 배지', 로그인/로그아웃 버튼)를 표시합니다 (아래 그림 참고).

907e64c19ef964f8.png

일반 사용자로 로그인하면 사용자의 이메일 주소가 표시되고 '로그인' 버튼이 '로그아웃' 버튼으로 변경됩니다.

ad7b59916b69a035.png

관리자 사용자로 로그인하면 사용자 이메일 주소가 옆에 '(관리자)'와 함께 표시됩니다.

867bcb3334149e4.png

4. 새 Google Cloud API/서비스 사용 설정

소개

Module 20 앱은 추가 설정이 필요하지 않은 번들 서비스인 App Engine NDB 및 Users API를 사용하지만 독립형 Cloud 서비스는 추가 설정이 필요합니다. 업데이트된 앱은 Cloud NDB 클라이언트 라이브러리를 통해 Cloud Identity Platform과 Cloud Datastore를 모두 사용합니다. 또한 App Engine 관리 사용자를 확인해야 하므로 Cloud Resource Manager API도 사용해야 합니다.

비용

  • App Engine과 Cloud Datastore에는 '상시 무료' 등급 할당량이 있으므로 이 한도를 초과하지 않는 한 이 튜토리얼을 완료하는 데 비용이 발생하지 않습니다. 자세한 내용은 App Engine 가격 책정 페이지Cloud Datastore 가격 책정 페이지를 참고하세요.
  • Cloud Identity Platform 사용 요금은 월간 활성 사용자 수 (MAU) 또는 인증 확인 수에 따라 청구되며, 각 사용 모델에 대해 '무료' 버전이 제공됩니다. 자세한 내용은 가격 책정 페이지를 참고하세요. 또한 App Engine과 Cloud Datastore에는 결제가 필요하지만 GCIP 자체는 측정 도구가 없는 일일 할당량을 초과하지 않는 한 결제를 사용 설정하지 않아도 되므로 결제가 필요한 Cloud API/서비스가 포함되지 않은 Cloud 프로젝트에 이를 고려해 보세요.
  • Cloud Resource Manager API는 가격 책정 페이지에 따라 대부분 무료로 사용할 수 있습니다.

사용자는 선호도에 따라 Cloud 콘솔 또는 명령줄 (Cloud SDK의 일부인 gcloud 명령어를 통해)에서 Cloud API를 사용 설정합니다. Cloud Datastore 및 Cloud Resource Manager API부터 시작해 보겠습니다.

Cloud Console 사용

Cloud 콘솔에서 API 관리자의 라이브러리 페이지 (올바른 프로젝트)로 이동하여 검색창을 사용하여 API를 검색합니다. c7a740304e9d35b.png

다음 API를 사용 설정합니다.

각 API에 대해 사용 설정 버튼을 찾아 클릭합니다. 결제 정보를 입력하라는 메시지가 표시될 수 있습니다. 예를 들어 Resource Manager API 페이지는 다음과 같습니다.

fc7bd8f4c49d12e5.png

사용 설정되면 (일반적으로 몇 초 후) 버튼이 관리로 변경됩니다.

8eca12d6cc7b45b0.png

다음과 같은 방법으로 Cloud Datastore를 사용 설정합니다.

83811599b110e46b.png

명령줄에서

콘솔에서 API를 사용 설정하면 시각적으로 유용하지만 명령줄을 선호하는 사용자도 있습니다. 한 번에 원하는 수의 API를 사용 설정할 수 있다는 추가적인 이점도 있습니다. 다음과 같이 Cloud Datastore 및 Cloud Resource Manager API를 모두 사용 설정하고 작업이 완료될 때까지 기다리려면 다음 명령어를 실행하세요.

$ gcloud services enable cloudresourcemanager.googleapis.com datastore.googleapis.com
Operation "operations/acat.p2-aaa-bbb-ccc-ddd-eee-ffffff" finished successfully.

결제 정보를 입력하라는 메시지가 표시될 수 있습니다.

위 명령어에 사용된 각 API의 'URL'을 API 서비스 이름이라고 하며, 각 API의 라이브러리 페이지 하단에서 확인할 수 있습니다. 자체 앱에 다른 Cloud API를 사용 설정하려면 해당 API 페이지에서 각 서비스 이름을 확인하세요. 이 명령어는 사용 설정할 수 있는 API의 모든 서비스 이름을 나열합니다.

gcloud services list --available --filter="name:googleapis.com".

Cloud 콘솔에서든 명령줄에서든 위의 단계를 완료하면 샘플이 이제 해당 API에 액세스할 수 있습니다. 다음 단계는 Cloud ID 플랫폼을 사용 설정하고 필요한 코드 변경을 수행하는 것입니다.

Cloud ID Platform 사용 설정 및 설정 (Cloud 콘솔만 해당)

Cloud Identity Platform은 Google Cloud 외부의 리소스(예: Firebase 인증)에 연결되거나 이를 사용하므로 마켓 서비스입니다. 현재는 Cloud 콘솔에서만 Marketplace 서비스를 사용 설정할 수 있습니다. 다음 단계를 따르세요.

  1. Cloud Marketplace의 Cloud Identity Platform 페이지로 이동하여 사용 설정 버튼을 클릭합니다. 메시지가 표시되면 Firebase 인증에서 업그레이드합니다. 이렇게 하면 배경 섹션에서 설명한 것과 같은 추가 기능이 잠금 해제됩니다. 사용 설정 버튼이 강조 표시된 Marketplace 페이지는 다음과 같습니다. 28475f1c9b29de69.png
  2. Identity Platform을 사용 설정하면 ID 공급업체 페이지로 자동 이동될 수 있습니다. 아니라면 이 편리한 링크를 사용하여 이동하세요. fc2d92d42a5d1dd7.png
  3. Google 인증 제공자를 사용 설정합니다. 설정된 제공업체가 없으면 제공업체 추가를 클릭하고 Google을 선택합니다. 이 화면으로 돌아오면 Google 항목이 사용 설정되어 있어야 합니다. Google은 App Engine 사용자 서비스를 경량 Google 로그인 서비스로 미러링하기 위해 이 튜토리얼에서 사용하는 유일한 인증 제공업체입니다. 자체 앱에서 추가 인증 제공업체를 사용 설정할 수 있습니다.
  4. Google 및 원하는 다른 인증 제공자를 선택하고 설정한 후 애플리케이션 설정 세부정보를 클릭하고 표시되는 대화상자 창에서 웹 탭의 config 객체에 있는 apiKeyauthDomain를 복사하여 안전한 곳에 저장합니다. 전체를 복사하지 않는 이유는 무엇인가요? 이 대화상자의 스니펫은 하드코딩되어 있으며 오래되었으므로 가장 중요한 부분을 저장하고 동시 Firebase Auth 사용량이 더 많은 코드에서 사용하세요. 값을 복사하여 안전한 곳에 저장한 후 닫기 버튼을 클릭하여 필요한 모든 설정을 완료합니다. bbb09dcdd9be538e.png

4. 구성 업데이트

구성 업데이트에는 다양한 구성 파일을 변경하는 것과 Cloud Identity Platform 생태계 내에서 App Engine과 동일한 항목을 만드는 것이 모두 포함됩니다.

appengine_config.py

  • Python 3으로 업그레이드하는 경우 appengine_config.py 삭제
  • Identity Platform으로 현대화하되 Python 2를 유지하려는 경우 파일을 삭제하지 마세요. 대신 Python 2 백포트 중에 업데이트할 예정입니다.

requirements.txt

모듈 20의 requirements.txt 파일에는 Flask만 나열되어 있습니다. 모듈 21의 경우 다음 패키지를 추가합니다.

이제 requirements.txt의 콘텐츠가 다음과 같이 표시됩니다.

flask
google-auth
google-cloud-ndb
google-cloud-resource-manager
firebase-admin

app.yaml

  • Python 3으로 업그레이드하면 app.yaml 파일이 간소화됩니다. 런타임 지시어를 제외한 모든 항목을 삭제하고 현재 지원되는 Python 3 버전으로 설정합니다. 현재 예시에서는 버전 3.10을 사용합니다.
  • Python 2를 계속 사용하는 경우 아직 여기에서 아무 조치도 취하지 마세요.

이전:

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

모듈 20 샘플 앱에는 정적 파일 핸들러가 없습니다. 앱이 지원하는 경우 그대로 둡니다. 원하는 경우 모든 스크립트 핸들러를 삭제하거나 app.yaml 이전 가이드에 설명된 대로 핸들을 auto로 변경하여 참조용으로 그대로 둘 수 있습니다. 이러한 변경사항으로 Python 3의 업데이트된 app.yaml이 다음과 같이 간소화됩니다.

AFTER:

runtime: python310

기타 구성 업데이트

Python 2를 유지하든 Python 3로 포팅하든 lib 폴더가 있으면 삭제합니다.

5. 애플리케이션 코드 수정

이 섹션에서는 App Engine 사용자 서비스를 Cloud ID 플랫폼으로 대체하는 기본 애플리케이션 파일 main.py의 업데이트를 다룹니다. 기본 애플리케이션을 업데이트한 후 웹 템플릿 templates/index.html를 업데이트합니다.

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

가져오기를 업데이트하고 애플리케이션 리소스를 초기화하려면 아래 단계를 따르세요.

  1. 가져오기의 경우 App Engine NDB를 Cloud NDB로 바꿉니다.
  2. Cloud NDB와 함께 Cloud Resource Manager도 가져옵니다.
  3. Identity Platform은 Firebase 인증을 기반으로 하므로 Firebase Admin SDK를 가져옵니다.
  4. Cloud API에는 API 클라이언트가 필요하므로 Flask를 초기화한 바로 아래에서 Cloud NDB를 초기화합니다.

여기서는 Cloud Resource Manager 패키지를 가져오지만 앱 초기화의 후반 단계에서 사용합니다. 아래는 모듈 20의 가져오기 및 초기화와 위의 변경사항을 구현한 후 섹션이 표시되는 방식입니다.

이전:

from flask import Flask, render_template, request
from google.appengine.api import users
from google.appengine.ext import ndb

app = Flask(__name__)

AFTER:

from flask import Flask, render_template, request
from google.auth import default
from google.cloud import ndb, resourcemanager
from firebase_admin import auth, initialize_app

# initialize Flask and Cloud NDB API client
app = Flask(__name__)
ds_client = ndb.Client()

App Engine 관리 사용자 지원

관리자 사용자 인식을 지원하는 앱에 추가할 두 가지 구성요소가 있습니다.

  • _get_gae_admins() - 관리자 사용자 집합을 대조합니다. 한 번 호출되고 저장됩니다.
  • is_admin() - 로그인한 사용자가 관리자인지 확인합니다. 모든 사용자 로그인 시 호출됩니다.

유틸리티 함수 _get_gae_admins()는 Resource Manager API를 호출하여 현재 Cloud IAM allow-policy를 가져옵니다. 허용 정책은 어떤 주 구성원 (사용자, 서비스 계정 등)에게 어떤 역할이 부여되는지 정의하고 적용합니다. 설정에는 다음이 포함됩니다.

  • Cloud 프로젝트 ID (PROJ_ID) 가져오기
  • Resource Manager API 클라이언트 만들기 (rm_client)
  • App Engine 관리자 역할(_TARGETS)의 읽기 전용 집합 만들기

리소스 관리자에는 Cloud 프로젝트 ID가 필요하므로 google.auth.default()를 가져오고 해당 함수를 호출하여 프로젝트 ID를 가져옵니다. 이 호출에는 URL과 유사하지만 OAuth2 권한 범위인 매개변수가 포함되어 있습니다. 클라우드에서 앱을 실행할 때(예: Compute Engine VM 또는 App Engine 앱) 광범위한 권한이 있는 기본 서비스 계정이 제공됩니다. 최소 권한의 권장사항에 따라 사용자 관리형 서비스 계정을 직접 만드는 것이 좋습니다.

API 호출의 경우 앱의 범위를 제대로 작동하는 데 필요한 최소 수준으로 추가로 줄이는 것이 좋습니다. 만들려는 Resource Manager API 호출은 get_iam_policy()이며, 이 호출은 작동하려면 다음 범위 중 하나가 필요합니다.

  • https://www.googleapis.com/auth/cloud-platform
  • https://www.googleapis.com/auth/cloud-platform.read-only
  • https://www.googleapis.com/auth/cloudplatformprojects
  • https://www.googleapis.com/auth/cloudplatformprojects.readonly

샘플 앱에는 허용 정책에 대한 읽기 전용 액세스 권한만 필요합니다. 정책을 수정하지 않으며 전체 프로젝트에 대한 액세스 권한이 필요하지 않습니다. 즉, 앱에 처음 세 가지 권한이 필요하지 않습니다. 마지막 하나만 필요하며 샘플 앱에 구현할 것입니다.

함수의 기본 본문은 빈 관리자 사용자 집합 (admins)을 만들고 get_iam_policy()를 통해 allow_policy를 가져오며 모든 바인딩을 순환하여 App Engine 관리자 역할을 구체적으로 찾습니다.

  • roles/viewer
  • roles/editor
  • roles/owner
  • roles/appengine.appAdmin

발견된 각 타겟 역할에 대해 해당 역할에 속한 사용자를 수집하여 전체 관리자 사용자 집합에 추가합니다. 이 App Engine 인스턴스의 수명 동안 찾아서 캐시된 모든 관리자 사용자를 상수 (_ADMINS)로 반환하여 종료됩니다. 곧 해당 통화가 표시됩니다.

Cloud NDB API 클라이언트 (ds_client)를 인스턴스화한 바로 아래에 다음 _get_gae_admins() 함수 정의를 main.py에 추가합니다.

def _get_gae_admins():
    'return set of App Engine admins'
    # setup constants for calling Cloud Resource Manager API
    _, PROJ_ID = default(  # Application Default Credentials and project ID
            ['https://www.googleapis.com/auth/cloudplatformprojects.readonly'])
    rm_client = resourcemanager.ProjectsClient()
    _TARGETS = frozenset((     # App Engine admin roles
            'roles/viewer',
            'roles/editor',
            'roles/owner',
            'roles/appengine.appAdmin',
    ))

    # collate users who are members of at least one GAE admin role (_TARGETS)
    admins = set()                      # set of all App Engine admins
    allow_policy = rm_client.get_iam_policy(resource='projects/%s' % PROJ_ID)
    for b in allow_policy.bindings:     # bindings in IAM allow-policy
        if b.role in _TARGETS:          # only look at GAE admin roles
            admins.update(user.split(':', 1).pop() for user in b.members)
    return admins

사용자가 앱에 로그인하면 다음이 발생합니다.

  1. 사용자가 Firebase에 로그인하면 웹 템플릿에서 간단한 확인이 이루어집니다.
  2. 템플릿에서 인증 상태가 변경되면 핸들러가 다음 함수인 is_admin()/is_admin에 Ajax 스타일 fetch() 호출이 이루어집니다.
  3. Firebase ID 토큰은 POST 본문에서 is_admin()로 전달되며, is_admin()는 헤더에서 토큰을 가져와 Firebase Admin SDK를 호출하여 유효성을 검사합니다. 유효한 사용자인 경우 이메일 주소를 추출하고 관리자인지 확인합니다.
  4. 그런 다음 불리언 결과가 성공적인 200으로 템플릿에 반환됩니다.

_get_gae_admins() 바로 뒤에 main.pyis_admin()를 추가합니다.

@app.route('/is_admin', methods=['POST'])
def is_admin():
    'check if user (via their Firebase ID token) is GAE admin (POST) handler'
    id_token = request.headers.get('Authorization')
    email = auth.verify_id_token(id_token).get('email')
    return {'admin': email in _ADMINS}, 200

두 함수의 모든 코드는 Users 서비스, 특히 is_current_user_admin() 함수에서 제공되는 기능을 복제해야 합니다. 모듈 20의 이 함수 호출은 대체 솔루션을 구현하는 모듈 21과 달리 모든 작업을 처리했습니다. 다행히도 앱이 더 이상 App Engine 전용 서비스에 종속되지 않으므로 앱을 Cloud Run 또는 기타 서비스로 이동할 수 있습니다. 또한 _TARGETS에서 원하는 역할로 전환하기만 하면 자체 앱의 '관리 사용자' 정의를 변경할 수 있지만 사용자 서비스는 App Engine 관리자 역할에 대해 하드코딩됩니다.

Firebase 인증 초기화 및 App Engine 관리 사용자 캐시

Flask 앱이 초기화되고 Cloud NDB API 클라이언트가 생성되는 것과 거의 같은 위치에서 Firebase Auth를 초기화할 수도 있었지만 모든 관리자 코드가 정의될 때까지는 그럴 필요가 없었습니다. 지금이 바로 그 시점입니다. 마찬가지로 이제 _get_gae_admins()가 정의되었으므로 이를 호출하여 관리자 사용자 목록을 캐시합니다.

is_admin()의 함수 본문 바로 아래에 다음 줄을 추가합니다.

# initialize Firebase and fetch set of App Engine admins
initialize_app()
_ADMINS = _get_gae_admins()

데이터 모델 업데이트 방문

Visit 데이터 모델은 변경되지 않습니다. Datastore 액세스에는 Cloud NDB API 클라이언트 컨텍스트 관리자 ds_client.context()를 명시적으로 사용해야 합니다. 코드에서 이는 Python with 블록 내에서 store_visit()fetch_visits() 모두에 Datastore 호출을 래핑한다는 의미입니다. 이 업데이트는 모듈 2와 동일합니다. 다음과 같이 변경합니다.

이전:

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)

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

def fetch_visits(limit):
    'get most recent visits'
    return Visit.query().order(-Visit.timestamp).fetch(limit)

AFTER:

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)

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

def fetch_visits(limit):
    'get most recent visits'
    with ds_client.context():
        return Visit.query().order(-Visit.timestamp).fetch(limit)

사용자 로그인 로직을 웹 템플릿으로 이동

App Engine 사용자 서비스는 서버 측인 반면 Firebase 인증 및 Cloud ID Platform은 주로 클라이언트 측입니다. 따라서 Module 20 앱의 사용자 관리 코드 대부분이 Module 21 웹 템플릿으로 이동합니다.

main.py에서 웹 컨텍스트는 템플릿에 5개의 필수 데이터를 전달합니다. 나열된 처음 4개는 사용자 관리에 연결되어 있으며 사용자의 로그인 여부에 따라 달라집니다.

  • who - 로그인한 경우 사용자의 이메일, 그렇지 않은 경우 user
  • admin - 로그인한 사용자가 관리자인 경우 (관리자) 배지
  • sign - 로그인 또는 로그아웃 버튼 표시
  • link - 버튼 클릭 시 로그인 또는 로그아웃 링크
  • visits - 가장 최근 방문

이전:

@app.route('/')
def root():
    'main application (GET) handler'
    store_visit(request.remote_addr, request.user_agent)
    visits = fetch_visits(10)

    # put together users context for web template
    user = users.get_current_user()
    context = {  # logged in
        'who':   user.nickname(),
        'admin': '(admin)' if users.is_current_user_admin() else '',
        'sign':  'Logout',
        'link':  '/_ah/logout?continue=%s://%s/' % (
                      request.environ['wsgi.url_scheme'],
                      request.environ['HTTP_HOST'],
                  ),  # alternative to users.create_logout_url()
    } if user else {  # not logged in
        'who':   'user',
        'admin': '',
        'sign':  'Login',
        'link':  users.create_login_url('/'),
    }

    # add visits to context and render template
    context['visits'] = visits  # display whether logged in or not
    return render_template('index.html', **context)

모든 사용자 관리가 웹 템플릿으로 이동하므로 방문만 남게 되어 기본 핸들러가 모듈 1 앱에 있었던 상태로 돌아갑니다.

AFTER:

@app.route('/')
def root():
    'main application (GET) handler'
    store_visit(request.remote_addr, request.user_agent)
    visits = fetch_visits(10)
    return render_template('index.html', visits=visits)

웹 템플릿 업데이트

이전 섹션의 모든 업데이트는 템플릿에서 어떻게 표시되나요? 주로 사용자 관리를 앱에서 템플릿에서 실행되는 Firebase 인증으로 이동하고 이동한 모든 코드를 JavaScript로 부분적으로 포팅합니다. main.py가 상당히 축소되었으므로 templates/index.html에서도 비슷한 성장세를 보일 것으로 예상됩니다.

이전:

<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
</head>
<body>
<p>
Welcome, {{ who }} <code>{{ admin }}</code>
<button id="logbtn">{{ sign }}</button>
</p><hr>

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

<script>
document.getElementById("logbtn").onclick = () => {
    window.location.href = '{{ link }}';
};
</script>
</body>
</html>

전체 웹 템플릿을 아래 콘텐츠로 바꿉니다.

AFTER:

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

<script type="module">
// import Firebase module attributes
import {
        initializeApp
} from "https://www.gstatic.com/firebasejs/9.10.0/firebase-app.js";
import {
        GoogleAuthProvider,
        getAuth,
        onAuthStateChanged,
        signInWithPopup,
        signOut
} from "https://www.gstatic.com/firebasejs/9.10.0/firebase-auth.js";

// Firebase config:
// 1a. Go to: console.cloud.google.com/customer-identity/providers
// 1b. May be prompted to enable GCIP and upgrade from Firebase
// 2. Click: "Application Setup Details" button
// 3. Copy: 'apiKey' and 'authDomain' from 'config' variable
var firebaseConfig = {
        apiKey: "YOUR_API_KEY",
        authDomain: "YOUR_AUTH_DOMAIN",
};

// initialize Firebase app & auth components
initializeApp(firebaseConfig);
var auth = getAuth();
var provider = new GoogleAuthProvider();
//provider.setCustomParameters({prompt: 'select_account'});

// define login and logout button functions
function login() {
    signInWithPopup(auth, provider);
};

function logout() {
    signOut(auth);
};

// check if admin & switch to logout button on login; reset everything on logout
onAuthStateChanged(auth, async (user) => {
    if (user && user != null) {
        var email = user.email;
        who.innerHTML = email;
        logbtn.onclick = logout;
        logbtn.innerHTML = "Logout";
        var idToken = await user.getIdToken();
        var rsp = await fetch("/is_admin", {
                method: "POST",
                headers: {Authorization: idToken}
        });
        var data = await rsp.json();
        if (data.admin) {
            admin.style.display = "inline";
        }
    } else {
        who.innerHTML = "user";
        admin.style.display = "none";
        logbtn.onclick = login;
        logbtn.innerHTML = "Login";
    }
});
</script>
</head>

<body>
<p>
Welcome, <span id="who"></span> <span id="admin"><code>(admin)</code></span>
<button id="logbtn"></button>
</p><hr>

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

<script>
var who    = document.getElementById("who");
var admin  = document.getElementById("admin");
var logbtn = document.getElementById("logbtn");
</script>
</body>
</html>

이 HTML 본문에는 여러 구성요소가 있으므로 하나씩 살펴보겠습니다.

Firebase 가져오기

HTML 문서의 헤더에 있는 동안 페이지 제목을 지나 필요한 Firebase 구성요소를 가져옵니다. 이제 Firebase 구성요소가 효율성을 위해 여러 모듈로 분리되었습니다. Firebase를 초기화하는 코드는 기본 Firebase 앱 모듈에서 가져오고, Firebase 인증, Google을 인증 제공업체로 관리, 로그인 및 로그아웃, 인증 상태 변경 '콜백'을 관리하는 함수는 모두 Firebase 인증 모듈에서 가져옵니다.

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

<script type="module">
// import Firebase module attributes
import {
        initializeApp
} from "https://www.gstatic.com/firebasejs/9.10.0/firebase-app.js";
import {
        GoogleAuthProvider,
        getAuth,
        onAuthStateChanged,
        signInWithPopup,
        signOut
} from "https://www.gstatic.com/firebasejs/9.10.0/firebase-auth.js";

Firebase 구성

이 튜토리얼의 앞부분에 나오는 Identity Platform 설정 부분에서 애플리케이션 설정 세부정보 대화상자에서 apiKeyauthDomain을 저장했습니다. 다음 섹션의 firebaseConfig 변수에 이러한 값을 추가합니다. 자세한 안내 링크는 주석에 제공됩니다.

// Firebase config:
// 1a. Go to: console.cloud.google.com/customer-identity/providers
// 1b. May be prompted to enable GCIP and upgrade from Firebase
// 2. Click: "Application Setup Details" button
// 3. Copy: 'apiKey' and 'authDomain' from 'config' variable
var firebaseConfig = {
        apiKey: "YOUR_API_KEY",
        authDomain: "YOUR_AUTH_DOMAIN",
};

Firebase 초기화

다음 섹션에서는 이 구성 정보로 Firebase를 초기화합니다.

// initialize Firebase app & auth components
initializeApp(firebaseConfig);
var auth = getAuth();
var provider = new GoogleAuthProvider();
//provider.setCustomParameters({prompt: 'select_account'});

이렇게 하면 Google을 인증 제공업체로 사용할 수 있으며 브라우저 세션에 등록된 Google 계정이 하나뿐인 경우에도 계정 선택기를 표시하는 주석 처리된 옵션이 제공됩니다. 즉, 계정이 여러 개인 경우 예상대로 이 '계정 선택기'가 표시됩니다. a38369389b7c4c7e.png 하지만 세션에 사용자가 한 명만 있는 경우 사용자 상호작용 없이 로그인 프로세스가 자동으로 완료됩니다. (팝업이 표시되었다가 사라집니다.) 맞춤 매개변수 줄의 주석 처리를 해제하면 앱에 즉시 로그인하는 대신 한 사용자에게 계정 선택기 대화상자가 표시되도록 강제할 수 있습니다. 사용 설정된 경우 단일 사용자 로그인에도 계정 선택기가 표시됩니다. b75624cb68d94557.png

로그인 및 로그아웃 함수

다음 코드 줄은 로그인 또는 로그아웃 버튼 클릭을 위한 함수를 구성합니다.

// define login and logout button functions
function login() {
    signInWithPopup(auth, provider);
};

function logout() {
    signOut(auth);
};

로그인 및 로그아웃 작업

<script> 블록의 마지막 주요 섹션은 인증 변경 (로그인 또는 로그아웃)마다 호출되는 함수입니다.

// check if admin & switch to logout button on login; reset everything on logout
onAuthStateChanged(auth, async (user) => {
    if (user && user != null) {
        var email = user.email;
        who.innerHTML = email;
        logbtn.onclick = logout;
        logbtn.innerHTML = "Logout";
        var idToken = await user.getIdToken();
        var rsp = await fetch("/is_admin", {
                method: "POST",
                headers: {Authorization: idToken}
        });
        var data = await rsp.json();
        if (data.admin) {
            admin.style.display = "inline";
        }
    } else {
        who.innerHTML = "user";
        admin.style.display = "none";
        logbtn.onclick = login;
        logbtn.innerHTML = "Login";
    }
});
</script>
</head>

'사용자 로그인' 템플릿 컨텍스트를 보낼지 아니면 '사용자 로그아웃' 컨텍스트를 보낼지 결정하는 20번 모듈의 코드가 여기로 전환됩니다. 상단의 조건은 사용자가 로그인에 성공한 경우 true을 생성하여 다음 작업을 트리거합니다.

  1. 사용자의 이메일 주소가 표시되도록 설정되어 있습니다.
  2. 로그인 버튼이 로그아웃으로 변경됩니다.
  3. (admin) 관리자 사용자 배지를 표시할지 여부를 확인하기 위해 /is_admin에 대한 Ajax 스타일 호출이 이루어집니다.

사용자가 로그아웃하면 else 절이 실행되어 모든 사용자 정보가 재설정됩니다.

  1. 사용자 이름이 user로 설정됨
  2. 관리자 배지가 삭제됨
  3. 로그아웃 버튼이 다시 로그인으로 변경됨

템플릿 변수

헤더 섹션이 끝나면 필요에 따라 변경되는 HTML 요소로 대체되는 템플릿 변수로 본문이 시작됩니다.

  1. 표시된 사용자 이름
  2. (admin) 관리자 배지 (해당하는 경우)
  3. 로그인 또는 로그아웃 버튼
<body>
<p>
Welcome, <span id="who"></span> <span id="admin"><code>(admin)</code></span>
<button id="logbtn"></button>
</p><hr>

최근 방문 및 HTML 요소 변수

최근 방문 코드는 변경되지 않으며 최종 <script> 블록은 로그인 및 로그아웃 시 변경되는 HTML 요소의 변수를 설정합니다.

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

<script>
var who    = document.getElementById("who");
var admin  = document.getElementById("admin");
var logbtn = document.getElementById("logbtn");
</script>
</body>
</html>

이로써 App Engine NDB 및 Users API에서 Cloud NDB 및 Identity Platform으로 전환하고 Python 3으로 업그레이드하기 위해 애플리케이션과 웹 템플릿에서 필요한 변경사항이 완료되었습니다. 새로운 모듈 21 샘플 앱에 오신 것을 환영합니다. Google 버전은 Module 21b 저장소 폴더에서 검토할 수 있습니다.

Codelab의 다음 부분은 선택사항 (*)이며 앱이 Python 2에 남아 있어야 하는 사용자만을 위한 것으로, 작동하는 Python 2 모듈 21 앱에 도달하는 데 필요한 단계를 안내합니다.

6. *Python 2 백포트

이 선택 섹션은 Identity Platform 마이그레이션을 실행하지만 Python 2 런타임에서 계속 실행해야 하는 개발자를 위한 것입니다. 이 문제가 우려되지 않는다면 이 섹션을 건너뛰세요.

작동하는 Python 2 버전의 Module 21 앱을 만들려면 다음이 필요합니다.

  1. 런타임 요구사항: Python 2를 지원하는 구성 파일 및 Python 3 비호환성을 방지하기 위해 기본 애플리케이션에 필요한 변경사항
  2. 사소한 라이브러리 변경사항: 필수 기능이 리소스 관리자 클라이언트 라이브러리에 추가되기 전에 Python 2가 지원 중단되었습니다. 따라서 누락된 기능에 액세스할 수 있는 대체 방법이 필요합니다.

이제 구성부터 시작하여 이러한 단계를 살펴보겠습니다.

appengine_config.py 복원

이 튜토리얼의 앞부분에서 Python 3 App Engine 런타임에서 사용되지 않으므로 appengine_config.py를 삭제하도록 안내했습니다. Python 2의 경우 보존해야 할 뿐만 아니라 내장 서드 파티 라이브러리(즉, grpciosetuptools)의 사용을 지원하도록 모듈 20 appengine_config.py을 업데이트해야 합니다. 이러한 패키지는 App Engine 앱이 Cloud NDB 및 Cloud 리소스 관리자와 같은 Cloud 클라이언트 라이브러리를 사용할 때마다 필요합니다.

이러한 패키지는 잠시 후에 app.yaml에 추가되지만 앱이 패키지에 액세스하려면 setuptoolspkg_resources.working_set.add_entry() 함수를 호출해야 합니다. 이를 통해 lib 폴더에 설치된 복사된 (자체 번들 또는 공급업체) 서드 파티 라이브러리가 기본 제공 라이브러리와 통신할 수 있습니다.

이러한 변경사항을 적용하려면 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)

이 코드만으로는 setuptoolsgrpcio 사용을 지원하기에 충분하지 않습니다. 몇 줄이 더 필요하므로 appengine_config.py를 다음과 같이 업데이트합니다.

AFTER:

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)

Cloud 클라이언트 라이브러리를 지원하는 데 필요한 변경사항에 대한 자세한 내용은 번들 서비스 마이그레이션 문서를 참고하세요.

app.yaml

appengine_config.py와 마찬가지로 app.yaml 파일도 Python 2를 지원하는 파일로 되돌려야 합니다. 원래 모듈 20 app.yaml부터 시작해 보겠습니다.

이전:

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

앞서 언급한 setuptoolsgrpcio 외에도 Cloud Storage 클라이언트 라이브러리를 사용해야 하는 종속 항목 (Identity Platform 이전과 명시적으로 관련되지 않음)이 있으며 에는 또 다른 기본 제공 서드 파티 패키지인 ssl가 필요합니다. 새 libraries 섹션에 세 가지를 모두 추가하고 해당 패키지의 사용 가능한 '최신' 버전을 선택하여 app.yaml에 추가합니다.

AFTER:

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

libraries:
- name: grpcio
  version: latest
- name: setuptools
  version: latest
- name: ssl
  version: latest

requirements.txt

모듈 21에서는 Python 3 requirements.txtGoogle Auth, Cloud NDB, Cloud Resource Manager, Firebase Admin SDK를 추가했습니다. Python 2의 상황은 더 복잡합니다.

  • Resource Manager API는 샘플 앱에 필요한 allow-policy 기능을 제공합니다. 하지만 Cloud Resource Manager 클라이언트 라이브러리최종 Python 2 버전에서는 아직 이 지원이 제공되지 않았습니다. (Python 3 버전에서만 사용할 수 있습니다.)
  • 따라서 API에서 이 기능에 액세스할 수 있는 대체 방법이 필요합니다. 이 문제를 해결하려면 하위 수준 Google API 클라이언트 라이브러리를 사용하여 API와 통신해야 합니다. 이 클라이언트 라이브러리로 전환하려면 google-cloud-resource-manager를 하위 수준 google-api-python-client 패키지로 바꾸세요.
  • Python 2가 지원 중단되었으므로 모듈 21을 지원하는 종속 항목 그래프에서는 특정 패키지를 특정 버전으로 잠가야 합니다. 일부 패키지는 Python 3 app.yaml에 지정되지 않은 경우에도 호출해야 합니다.

이전:

flask

모듈 20 requirements.txt부터 작동하는 모듈 21 앱을 위해 다음과 같이 업데이트합니다.

AFTER:

grpcio==1.0.0
protobuf<3.18.0
six>=1.13.0
flask
google-gax<0.13.0
google-api-core==1.31.1
google-api-python-client<=1.11.0
google-auth<2.0dev
google-cloud-datastore==1.15.3
google-cloud-firestore==1.9.0
google-cloud-ndb
google-cloud-pubsub==1.7.0
firebase-admin

종속 항목이 변경되면 패키지 및 버전 번호가 저장소에서 업데이트되지만 이 app.yaml은 이 글을 작성하는 시점에 작동하는 앱에 충분합니다.

기타 구성 업데이트

이 Codelab의 앞부분에서 lib 폴더를 삭제하지 않았다면 지금 삭제하세요. 새로 업데이트된 requirements.txt을 사용하여 다음 명령어를 실행하여 lib에 이러한 요구사항을 설치합니다.

pip install -t lib -r requirements.txt  # or pip2

개발 시스템에 Python 2와 3이 모두 설치되어 있는 경우 pip 대신 pip2를 사용해야 할 수 있습니다.

애플리케이션 코드 수정

다행히 필요한 변경사항 대부분은 구성 파일에 있습니다. 애플리케이션 코드에서 필요한 유일한 변경사항은 리소스 관리자 클라이언트 라이브러리 대신 하위 수준 Google API 클라이언트 라이브러리를 사용하여 API에 액세스하도록 약간 업데이트하는 것입니다. templates/index.html 웹 템플릿은 업데이트할 필요가 없습니다.

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

아래와 같이 Resource Manager 클라이언트 라이브러리 (google.cloud.resourcemanager)를 Google API 클라이언트 라이브러리 (googleapiclient.discovery)로 바꿉니다.

이전:

from flask import Flask, render_template, request
from google.auth import default
from google.cloud import ndb, resourcemanager
from firebase_admin import auth, initialize_app

AFTER:

from flask import Flask, render_template, request
from google.auth import default
from google.cloud import ndb
from googleapiclient import discovery
from firebase_admin import auth, initialize_app

App Engine 관리 사용자 지원

하위 수준 클라이언트 라이브러리 사용을 지원하려면 _get_gae_admins()에서 몇 가지 변경사항이 필요합니다. 먼저 변경사항을 살펴본 다음 업데이트할 코드를 모두 제공하겠습니다.

Python 2 코드에서는 google.auth.default()에서 반환된 사용자 인증 정보와 프로젝트 ID를 모두 사용해야 합니다. 사용자 인증 정보는 Python 3에서 사용되지 않으므로 일반적인 밑줄 ( _) 더미 변수에 할당되었습니다. Python 2 버전에서 필요하므로 밑줄을 CREDS로 변경합니다. 또한 리소스 관리자 API 클라이언트를 만드는 대신 API 클라이언트와 개념이 유사한 API 서비스 엔드포인트를 만드므로 동일한 변수 이름 (rm_client)을 유지합니다. 한 가지 차이점은 서비스 엔드포인트를 인스턴스화하려면 사용자 인증 정보 (CREDS)가 필요하다는 것입니다.

이러한 변경사항은 아래 코드에 반영되어 있습니다.

이전:

_, PROJ_ID = default(  # Application Default Credentials and project ID
        ['https://www.googleapis.com/auth/cloudplatformprojects.readonly'])
rm_client = resourcemanager.ProjectsClient()

AFTER:

CREDS, PROJ_ID = default(  # Application Default Credentials and project ID
        ['https://www.googleapis.com/auth/cloud-platform'])
rm_client = discovery.build('cloudresourcemanager', 'v1', credentials=CREDS)

또 다른 차이점은 Resource Manager 클라이언트 라이브러리는 점 속성 표기법을 사용하는 허용 정책 객체를 반환하는 반면 하위 수준 클라이언트 라이브러리는 대괄호( [ ] )가 사용되는 Python 사전(예: 하위 수준 라이브러리의 경우 binding['role']와 비교하여 Resource Manager 클라이언트 라이브러리의 경우 binding.role 사용)을 반환한다는 것입니다. 전자는 또한 하위 수준 라이브러리에서 선호하는 'CamelCased' 이름과 API 매개변수를 전달하는 약간 다른 방식과 달리 'underscore_separated' 이름을 사용합니다.

사용법의 차이는 아래와 같습니다.

이전:

allow_policy = rm_client.get_iam_policy(resource='projects/%s' % PROJ_ID)
for b in allow_policy.bindings:     # bindings in IAM allow-policy
    if b.role in _TARGETS:          # only look at GAE admin roles
        admins.update(user.split(':', 1).pop() for user in b.members)

AFTER:

allow_policy = rm_client.projects().getIamPolicy(resource=PROJ_ID).execute()
for b in allow_policy['bindings']:  # bindings in IAM allow-policy
    if b['role'] in _TARGETS:       # only look at GAE admin roles
        admins.update(user.split(':', 1).pop() for user in b['members'])

이러한 변경사항을 모두 적용하여 Python 3 _get_gae_admins()을 이에 상응하는 Python 2 버전으로 바꿉니다.

def _get_gae_admins():
    'return set of App Engine admins'
    # setup constants for calling Cloud Resource Manager API
    CREDS, PROJ_ID = default(  # Application Default Credentials and project ID
            ['https://www.googleapis.com/auth/cloud-platform'])
    rm_client = discovery.build('cloudresourcemanager', 'v1', credentials=CREDS)
    _TARGETS = frozenset((     # App Engine admin roles
            'roles/viewer',
            'roles/editor',
            'roles/owner',
            'roles/appengine.appAdmin',
    ))

    # collate users who are members of at least one GAE admin role (_TARGETS)
    admins = set()                      # set of all App Engine admins
    allow_policy = rm_client.projects().getIamPolicy(resource=PROJ_ID).execute()
    for b in allow_policy['bindings']:  # bindings in IAM allow-policy
        if b['role'] in _TARGETS:       # only look at GAE admin roles
            admins.update(user.split(':', 1).pop() for user in b['members'])
    return admins

is_admin() 함수는 이미 업데이트된 _get_gae_admins()를 사용하므로 업데이트가 필요하지 않습니다.

이로써 Python 3 모듈 21 앱을 Python 2로 백포트하는 데 필요한 변경사항이 완료되었습니다. 업데이트된 모듈 21 샘플 앱에 도착하신 것을 축하드립니다. 모든 코드는 Module 21a 저장소 폴더에서 확인할 수 있습니다.

7. 요약/삭제

Codelab의 마지막 단계는 이 앱을 실행하는 주 구성원 (사용자 또는 서비스 계정)에게 적절한 권한이 있는지 확인한 다음 앱을 배포하여 의도한 대로 작동하고 변경사항이 출력에 반영되는지 확인하는 것입니다.

IAM 허용 정책을 읽을 수 있음

앞서 App Engine 관리자 사용자로 인식되는 데 필요한 네 가지 역할을 소개해 드렸지만 이제 다섯 번째 역할을 알아야 합니다.

  • roles/viewer
  • roles/editor
  • roles/owner
  • roles/appengine.appAdmin
  • roles/resourcemanager.projectIamAdmin (IAM 허용 정책에 액세스하는 주 구성원의 경우)

roles/resourcemanager.projectIamAdmin 역할을 사용하면 주 구성원이 최종 사용자가 App Engine 관리자 역할의 구성원인지 확인할 수 있습니다. roles/resourcemanager.projectIamAdmin에 속하지 않으면 허용 정책을 가져오기 위해 Cloud Resource Manager API를 호출할 수 없습니다.

앱이 이 역할의 멤버십이 자동으로 부여되는 App Engine의 기본 서비스 계정으로 실행되므로 여기에서 명시적인 조치를 취할 필요는 없습니다. 개발 단계에서 기본 서비스 계정을 사용하더라도 앱이 제대로 작동하는 데 필요한 최소 권한이 있는 사용자 관리 서비스 계정을 만들어 사용하는 것이 좋습니다. 이러한 서비스 계정에 멤버십을 부여하려면 다음 명령어를 실행합니다.

$ gcloud projects add-iam-policy-binding PROJ_ID --member="serviceAccount:USR_MGD_SVC_ACCT@PROJ_ID.iam.gserviceaccount.com" --role=roles/resourcemanager.projectIamAdmin

PROJ_ID은 클라우드 프로젝트 ID이고 USR_MGD_SVC_ACCT@PROJ_ID.iam.gserviceaccount.com은 앱을 위해 만든 사용자 관리 서비스 계정입니다. 이 명령어는 서비스 계정이 roles/resourcemanager.projectIamAdmin에 속해 있는지 확인할 수 있는 프로젝트의 업데이트된 IAM 정책을 출력합니다. 자세한 내용은 참조 문서를 확인하세요. 다시 말하지만 이 Codelab에서는 이 명령어를 실행할 필요가 없지만, 자체 앱을 현대화할 때 참고할 수 있도록 저장해 두세요.

애플리케이션 배포 및 확인

표준 gcloud app deploy 명령어를 사용하여 앱을 클라우드에 업로드합니다. 배포가 완료되면 사용자 관리를 위해 App Engine 사용자 서비스를 Cloud Identity Platform (및 Firebase Auth)으로 성공적으로 대체한 것을 제외하고 모듈 20 앱과 거의 동일한 기능이 표시됩니다.

3a83ae745121d70.png

모듈 20과 비교했을 때 다른 점은 로그인 버튼을 클릭하면 리디렉션 대신 팝업이 표시된다는 것입니다. 아래 스크린샷에서 확인할 수 있습니다. 하지만 20번 모듈과 마찬가지로 브라우저에 등록된 Google 계정 수에 따라 동작이 약간 다릅니다.

브라우저에 등록된 사용자가 없거나 아직 로그인하지 않은 단일 사용자가 있는 경우 일반적인 Google 로그인 팝업이 표시됩니다.

8437f5f3d489a942.png

단일 사용자가 브라우저에 등록되어 있지만 다른 곳에서 로그인하는 경우 대화상자가 표시되지 않거나 팝업으로 표시되었다가 즉시 닫히고 앱이 로그인 상태로 전환됩니다(사용자 이메일 및 로그아웃 버튼이 표시됨).

일부 개발자는 단일 사용자의 경우에도 계정 선택기를 제공할 수 있습니다.

b75624cb68d94557.png

이를 구현하려면 앞서 설명한 대로 웹 템플릿에서 provider.setCustomParameters({prompt: 'select_account'}); 줄의 주석 처리를 삭제합니다.

사용자가 여러 명인 경우 계정 선택기 대화상자가 팝업됩니다 (아래 참고). 아직 로그인하지 않은 경우 사용자에게 메시지가 표시됩니다. 이미 로그인한 경우 팝업이 사라지고 앱이 로그인 상태로 전환됩니다.

c454455b6020d5e4.png

모듈 21의 로그인 상태는 모듈 20의 사용자 인터페이스와 동일하게 표시됩니다.

49ebe4dcc1eff11f.png

관리 사용자가 로그인한 경우에도 마찬가지입니다.

44302f35b39856eb.png

모듈 21과 달리 모듈 20은 항상 앱 (서버 측 코드)에서 웹 템플릿 콘텐츠의 로직에 액세스합니다. 모듈 20의 결함은 최종 사용자가 앱을 처음 방문할 때 한 번의 방문이 등록되고 사용자가 로그인할 때 또 다른 방문이 등록된다는 것입니다.

모듈 21의 경우 로그인 로직은 웹 템플릿 (클라이언트 측 코드)에서만 발생합니다. 표시할 콘텐츠를 결정하기 위해 필요한 서버 측 트립이 없습니다. 서버에 대한 유일한 호출은 최종 사용자가 로그인한 후 관리자 사용자를 확인하는 것입니다. 즉, 로그인 및 로그아웃은 추가 방문으로 등록되지 않으므로 사용자 관리 작업의 경우 가장 최근 방문 목록이 일정하게 유지됩니다. 위 스크린샷을 보면 여러 사용자 로그인에 걸쳐 동일한 4개의 방문이 표시됩니다.

모듈 20 스크린샷은 이 Codelab의 시작 부분에 '이중 방문 버그'를 보여줍니다. 각 로그인 또는 로그아웃 작업에 대해 별도의 방문 로그가 표시됩니다. 시간순으로 정렬된 각 스크린샷의 가장 최근 방문 타임스탬프를 확인합니다.

삭제

일반

지금까지 완료한 경우 청구가 발생하지 않도록 App Engine 앱을 사용 중지하는 것이 좋습니다. 하지만 테스트나 실험을 더 진행하고 싶다면 App Engine 플랫폼에 무료 할당량이 있으므로 해당 사용량 등급을 초과하지 않는 한 요금이 청구되지 않습니다. 이는 컴퓨팅에 대한 요금이며 관련 App Engine 서비스에 대한 요금도 부과될 수 있으므로 자세한 내용은 가격 책정 페이지를 확인하세요. 이 이전과 관련된 다른 클라우드 서비스는 별도로 청구됩니다. 두 경우 모두 해당하는 경우 아래의 '이 Codelab에만 해당' 섹션을 참고하세요.

완전한 공개를 위해 말씀드리면 App Engine과 같은 Google Cloud 서버리스 컴퓨팅 플랫폼에 배포하면 약간의 빌드 및 스토리지 비용이 발생합니다. Cloud Build에는 Cloud Storage와 마찬가지로 자체 무료 할당량이 있습니다. 해당 이미지를 저장하면 할당량의 일부가 사용됩니다. 하지만 무료 등급이 없는 지역에 거주할 수도 있으므로 스토리지 사용량을 파악하여 잠재적인 비용을 최소화하세요. 검토해야 하는 특정 Cloud Storage '폴더'는 다음과 같습니다.

  • console.cloud.google.com/storage/browser/LOC.artifacts.PROJECT_ID.appspot.com/containers/images
  • console.cloud.google.com/storage/browser/staging.PROJECT_ID.appspot.com
  • 위의 스토리지 링크는 PROJECT_IDLOC에 따라 달라집니다. 예를 들어 앱이 미국에서 호스팅되는 경우 'us'입니다.

반면에 이 애플리케이션 또는 기타 관련 이전 Codelab을 계속하지 않고 모든 것을 완전히 삭제하려면 프로젝트를 종료하세요.

이 Codelab에만 해당

아래에 나열된 서비스는 이 Codelab에만 해당합니다. 자세한 내용은 각 제품의 문서를 참고하세요.

다음 단계

이 튜토리얼 외에도 기존 번들 서비스에서 벗어나는 데 중점을 둔 다른 이전 모듈은 다음과 같습니다.

  • 모듈 2: App Engine ndb에서 Cloud NDB로 마이그레이션
  • 모듈 7~9: App Engine 태스크 큐 (푸시 태스크)에서 Cloud Tasks로 마이그레이션
  • 모듈 12~13: App Engine Memcache에서 Cloud Memorystore로 이전
  • 모듈 15~16: App Engine Blobstore에서 Cloud Storage로 이전
  • 모듈 18~19: App Engine 태스크 큐 (풀 태스크)에서 Cloud Pub/Sub로 이전

App Engine은 더 이상 Google Cloud의 유일한 서버리스 플랫폼이 아닙니다. 소규모 App Engine 앱이 있거나 기능이 제한된 앱이 있는데 이를 독립형 마이크로서비스로 전환하려는 경우 또는 모놀리식 앱을 재사용 가능한 여러 구성요소로 분할하려는 경우 Cloud Functions로 이전하는 것이 좋습니다. 컨테이너화가 애플리케이션 개발 워크플로의 일부가 된 경우, 특히 CI/CD (지속적 통합/지속적 배포) 파이프라인으로 구성된 경우 Cloud Run으로 이전하는 것이 좋습니다. 이러한 시나리오는 다음 모듈에서 다룹니다.

  • App Engine에서 Cloud Functions로 이전: 모듈 11 참고
  • App Engine에서 Cloud Run으로 마이그레이션: 모듈 4에서 Docker를 사용하여 앱을 컨테이너화하거나 모듈 5에서 컨테이너, Docker 지식 또는 Dockerfile 없이 컨테이너화하는 방법을 알아보세요.

다른 서버리스 플랫폼으로 전환하는 것은 선택사항이며, 변경하기 전에 앱과 사용 사례에 가장 적합한 옵션을 고려하는 것이 좋습니다.

다음에 고려할 마이그레이션 모듈과 관계없이 모든 서버리스 마이그레이션 스테이션 콘텐츠 (Codelabs, 동영상, 소스 코드[사용 가능한 경우])는 오픈소스 저장소에서 액세스할 수 있습니다. 저장소의 README에서는 고려해야 할 이전과 관련 '순서'의 이전 모듈에 관한 안내도 제공합니다.

8. 추가 리소스

아래에는 이 또는 관련 이전 모듈을 자세히 살펴보는 개발자를 위한 추가 리소스가 나열되어 있습니다. 아래에서 이 콘텐츠에 대한 의견을 제공하고, 코드 링크와 유용할 수 있는 다양한 문서를 확인할 수 있습니다.

Codelabs 문제/의견

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

마이그레이션 리소스

모듈 20 (시작) 및 모듈 21 (완료)의 저장소 폴더 링크는 아래 표에서 찾을 수 있습니다.

Codelab

Python 2

Python 3

모듈 20

코드

(해당 없음)

모듈 21 (이 Codelab)

코드

코드

온라인 참조

다음은 이 튜토리얼과 관련된 리소스입니다.

Cloud Identity Platform 및 Cloud Marketplace

Cloud Resource Manager, Cloud IAM, Firebase Admin SDK

App Engine 사용자, App Engine NDB, Cloud NDB, Cloud Datastore

기타 이전 모듈 참조

App Engine 마이그레이션

App Engine 플랫폼

Cloud SDK

기타 클라우드 정보

동영상

라이선스

이 작업물은 Creative Commons Attribution 2.0 일반 라이선스에 따라 사용이 허가되었습니다.