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

1. 개요

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

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

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

다음 실습에서는

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

필요한 항목

설문조사

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

<ph type="x-smartling-placeholder"></ph> 읽어보기만 해도 됩니다. 읽고 연습 활동을 완료하세요

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

초급 중급 고급

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

<ph type="x-smartling-placeholder"></ph> 초보자 중급 숙련도

2. 배경

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

Identity Platform은 Firebase 인증을 기반으로 하며 다중 인증(MFA), OIDC, SAML SSO 지원, 멀티테넌시, 99.95% SLA 등을 제공합니다. 이러한 차이점은 Identity Platform 및 Firebase 인증 제품 비교 페이지에도 강조표시되어 있습니다. 두 제품 모두 사용자 서비스에서 제공하는 기능보다 훨씬 더 많은 기능을 갖추고 있습니다.

이 모듈 21 Codelab에서는 앱의 사용자 인증을 사용자 서비스에서 모듈 20에서 설명한 기능을 가장 근접하게 미러링하는 Identity Platform 기능으로 전환하는 방법을 보여줍니다. 또한 모듈 21은 모듈 2 마이그레이션을 반복하며 App Engine NFS에서 Datastore 액세스용 Cloud NFS로 마이그레이션하는 기능을 포함합니다.

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

이 가이드에는 다음 단계가 포함됩니다.

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

3. 설정/사전 작업

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

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

이 단계를 통해 독립형 Cloud 서비스로 마이그레이션할 준비가 된 정상적으로 작동하는 코드로 시작할 수 있습니다.

1. 프로젝트 설정

모듈 20 Codelab을 완료했다면 동일한 프로젝트와 코드를 재사용하세요. 또는 새 프로젝트를 만들거나 다른 기존 프로젝트를 재사용합니다. 프로젝트에 활성 결제 계정과 사용 설정된 App Engine 앱이 있는지 확인합니다. 프로젝트 ID를 찾아서 이 Codelab에서 유용하게 사용할 수 있도록 하고 PROJ_ID 변수가 나타날 때마다 이 ID를 사용하세요.

2. 기준 샘플 앱 가져오기

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

모듈 20 repo 폴더를 복사합니다. 아래와 같이 출력되며, 모듈 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로 Cloud 프로젝트를 설정하세요.
  4. gcloud app deploy를 사용하여 샘플 앱 배포
  5. 앱이 오류 없이 예상대로 실행되는지 확인합니다. 모듈 20 Codelab을 완료했다면 앱에서 가장 최근 방문 내역과 함께 사용자 로그인 정보(사용자 이메일, '관리자 배지', 로그인/로그아웃 버튼)를 상단에 표시합니다.

907e64c19ef964f8.png

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

ad7b59916b69a035.png

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

867bcb3334149e4.png

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

소개

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

비용

  • App Engine 및 Cloud Datastore에서 '항상 무료' 사용 Tier 할당량을 사용하며, 이 한도를 초과하지 않는 한 이 튜토리얼을 완료하면 요금이 발생하지 않습니다. 자세한 내용은 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.

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

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

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

Cloud 콘솔 또는 명령줄 중 무엇을 사용하든, 위 단계를 완료하면 샘플에서 해당 API에 액세스할 수 있습니다. 다음 단계는 Cloud Identity Platform을 사용 설정하고 필요에 따라 코드를 변경하는 것입니다.

Cloud Identity Platform 사용 설정 및 설정 (Cloud Console만 해당)

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

  1. Cloud Marketplace의 Cloud Identity Platform 페이지로 이동하여 사용 설정 버튼을 클릭합니다. 메시지가 표시되면 Firebase 인증에서 업그레이드하세요. 업그레이드하면 앞서 백그라운드 섹션에서 설명한 것과 같은 추가 기능을 사용할 수 있습니다. 다음은 사용 설정 버튼이 강조 표시된 Marketplace 페이지입니다. 28475f1c9b29de69.png
  2. Identity Platform이 사용 설정되면 자동으로 ID 공급업체 페이지로 이동될 수 있습니다. 그렇지 않은 경우 이 편리한 링크를 사용하여 이동하세요. <ph type="x-smartling-placeholder">fc2d92d42a5d1dd7.png</ph>
  3. Google 인증 제공업체를 사용 설정합니다. 설정된 제공업체가 없는 경우 공급업체 추가를 클릭하고 Google을 선택합니다. 이 화면으로 돌아오면 Google 항목이 사용 설정되어 있어야 합니다. 이 가이드에서는 Google이 App Engine 사용자 서비스를 간단한 Google 로그인 서비스로 미러링하는 데 사용하는 유일한 인증 제공업체입니다. 자체 앱에서 추가 인증 제공자를 사용 설정할 수 있습니다.
  4. Google 및 원하는 다른 인증 제공업체를 선택하고 설정한 후 Application Setup Details(애플리케이션 설정 세부정보)를 클릭하고 확인하는 대화상자 창에서 웹 탭의 config 객체에 있는 apiKeyauthDomain를 복사하여 두 가지 모두 안전한 위치에 저장합니다. 전체를 복사하지 않는 이유는 무엇일까요? 이 대화상자의 스니펫은 하드코딩되고 날짜가 지정되어 있으므로 가장 중요한 비트만 저장하고 Firebase 인증을 더 동시에 사용해 코드에서 사용하면 됩니다. 값을 복사하여 안전한 위치에 저장한 후 닫기 버튼을 클릭하여 필요한 설정을 모두 완료합니다. <ph type="x-smartling-placeholder">bbb09dcdd9be538e.png</ph>

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이 다음과 같이 간소화됩니다.

변경 후:

runtime: python310

기타 구성 업데이트

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

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

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

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

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

  1. 가져오기의 경우 App Engine Dataplex를 CloudNd로 바꾸세요.
  2. Cloud Dataplex와 함께 Cloud Resource Manager도 가져옵니다.
  3. Identity Platform은 Firebase 인증을 기반으로 하므로 Firebase Admin SDK를 가져옵니다.
  4. Cloud API를 사용하려면 API 클라이언트를 사용해야 하므로 Flask를 초기화하는 바로 아래에 있는 Cloud NBS에 대해 API 클라이언트를 시작하세요.

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

변경 후:

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를 가져옵니다. allow-policy는 어떤 주 구성원 (사람 사용자, 서비스 계정 등)에게 어떤 역할이 부여되는지 정의하고 시행합니다. 설정에는 다음이 포함됩니다.

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

Resource Manager를 사용하려면 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

샘플 앱에는 allow-policy에 대한 읽기 전용 액세스 권한만 필요합니다. 정책을 수정하거나 전체 프로젝트에 액세스할 필요도 없습니다. 즉, 앱에는 처음 세 가지 권한 중 어느 것도 필요하지 않습니다. 마지막 항목만 있으면 되며 이것이 샘플 앱에 구현하는 것입니다.

함수의 본문은 빈 관리자 집합 (admins)을 만들고 get_iam_policy()를 통해 allow_policy를 가져오며 특히 App Engine 관리자 역할을 찾는 모든 바인딩을 순환합니다.

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

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

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

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()에 전달되고, 헤더에서 이를 가져오고 Firebase Admin SDK를 호출하여 검증합니다. 유효한 사용자인 경우 이메일 주소를 추출하여 관리자인지 확인합니다.
  4. 그러면 부울 결과가 성공 200으로 템플릿에 반환됩니다.

_get_gae_admins() 바로 뒤에 is_admin()main.py에 추가합니다.

@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

사용자 서비스, 특히 is_current_user_admin() 함수에서 사용할 수 있는 기능을 복제하려면 두 함수의 모든 코드가 필요합니다. 모듈 20의 이 함수 호출은 대체 솔루션을 구현하는 모듈 21과 달리 모든 어려운 작업을 거쳤습니다. 다행인 앱은 더 이상 App Engine 전용 서비스에 의존하지 않으므로 앱을 Cloud Run 또는 다른 서비스로 이전할 수 있습니다. 또한 '관리자'의 정의를 변경할 수도 있습니다. _TARGETS에서 원하는 역할로 전환하기만 하면 됩니다. 반면 사용자 서비스는 App Engine 관리자 역할용으로 하드코딩되어 있습니다.

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

Flask 앱이 초기화되고 Cloud NBS API 클라이언트가 생성된 지점 근처에서 Firebase 인증을 초기화했을 수도 있지만, 모든 관리자 코드가 정의될 때까지는 필요하지 않았습니다. 마찬가지로 이제 _get_gae_admins()가 정의되었으므로 이를 호출하여 관리자 목록을 캐시합니다.

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

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

데이터 모델 업데이트로 이동

Visit 데이터 모델은 변경되지 않습니다. Datastore에 액세스하려면 Cloud Datalab API 클라이언트 컨텍스트 관리자(ds_client.context())를 명시적으로 사용해야 합니다. 즉, 코드에서 store_visit()fetch_visits() 모두 Datastore 호출을 Python with 블록 내에 래핑합니다. 이 업데이트는 모듈 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)

변경 후:

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 Identity Platform은 주로 클라이언트 측입니다. 따라서 모듈 20 앱의 사용자 관리 코드는 대부분 모듈 21 웹 템플릿으로 이동합니다.

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

  • who: 로그인한 경우 사용자의 이메일, 그렇지 않은 경우 사용자
  • 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 앱의 이전 상태로 다시 가져옵니다.

변경 후:

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

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

변경 후:

<!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에서 '로그인된 사용자' 메시지를 전송할지 여부를 결정하는 코드 템플릿 컨텍스트 vs. '로그아웃한 사용자' 컨텍스트가 여기에서 전환됩니다 사용자가 성공적으로 로그인한 경우 상단의 조건부 결과는 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 NBS 및 Users API에서 Cloud NBS 및 Identity Platform으로 전환하고 Python 3으로 업그레이드하기 위해 필요한 변경사항을 마칩니다. 축하합니다. 새로운 모듈 21 샘플 앱에 오신 것을 환영합니다. 이 버전은 모듈 21b 저장소 폴더에서 검토할 수 있습니다.

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

6. *Python 2 백포트

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

모듈 21 앱의 Python 2 버전을 만들려면 다음이 필요합니다.

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

구성부터 시작해 보겠습니다.

appengine_config.py 복원

이 튜토리얼 앞부분에서는 Python 3 App Engine 런타임에서 사용하지 않는 appengine_config.py를 삭제하는 방법을 안내했습니다. Python 2의 경우 유지되어야 할 뿐만 아니라 내장된 서드 파티 라이브러리, 즉 grpciosetuptools의 사용을 지원하도록 모듈 20 appengine_config.py를 업데이트해야 합니다. 이러한 패키지는 App Engine 앱에서 CloudNDB 및 Cloud Resource Manager와 같은 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를 다음과 같이 업데이트합니다.

변경 후:

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 클라이언트 라이브러리를 사용해야 하고 다른 기본 제공 서드 파티 패키지인 ssl가 필요한 종속 항목 (Identity Platform 이전과 명시적으로 관련되지는 않음)이 있습니다. 새 libraries 섹션에 세 가지를 모두 추가하고 'latest'를 선택합니다. 이러한 패키지의 사용 가능한 버전을 app.yaml로 업데이트합니다.

변경 후:

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의 경우 Google 인증, CloudNDB, Cloud Resource Manager, Firebase Admin SDK를 Python 3 requirements.txt에 추가했습니다. Python 2의 상황은 더 복잡합니다.

  • Resource Manager API는 샘플 앱에 필요한 허용 정책 기능을 제공합니다. 안타깝게도 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 앱을 위해 다음과 같이 업데이트합니다.

변경 후:

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를 사용해야 할 수 있습니다.

애플리케이션 코드 수정

다행히 필요한 변경사항은 대부분 구성 파일에 있습니다. 애플리케이션 코드에서 유일하게 변경할 사항은 Resource Manager 클라이언트 라이브러리 대신 하위 수준의 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

변경 후:

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로 변경합니다. 또한 Resource Manager 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()

변경 후:

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 클라이언트 라이브러리는 점 속성 표기법을 사용하는 allow-policy 객체를 반환하는 반면 하위 수준 클라이언트 라이브러리는 대괄호 ( [ ])가 사용된 Python 사전을 반환한다는 것입니다. 예를 들어 Resource Manager 클라이언트 라이브러리에는 binding.role를 사용하고 하위 수준 라이브러리에는 binding['role']를 사용합니다. 전자의 경우 'underscore_delimited'도 사용합니다. 'CamelCased'를 선호하는 하위 수준 라이브러리 비교 API 매개변수를 전달하는 방법이 약간 다릅니다.

이러한 사용 방식의 차이점은 다음과 같습니다.

이전:

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)

변경 후:

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 샘플 앱에 오신 것을 환영합니다. 모듈 21a 저장소 폴더에서 모든 코드를 확인할 수 있습니다.

7. 요약/삭제

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

IAM allow-policy를 읽을 수 있습니다.

앞서 App Engine 관리자로 인식되어야 하는 4가지 역할을 소개했지만 이제는 다섯 번째 역할을 살펴보겠습니다.

  • roles/viewer
  • roles/editor
  • roles/owner
  • roles/appengine.appAdmin
  • roles/resourcemanager.projectIamAdmin (IAM allow-policy에 액세스하는 주 구성원용)

roles/resourcemanager.projectIamAdmin 역할을 통해 주 구성원은 최종 사용자가 App Engine 관리자 역할의 구성원인지 확인할 수 있습니다. roles/resourcemanager.projectIamAdmin에 멤버십이 없으면 Cloud Resource Manager API를 호출하여 allow-policy를 가져올 수 없습니다.

이 역할의 멤버십이 자동으로 부여되는 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는 Cloud 프로젝트 ID이고 USR_MGD_SVC_ACCT@PROJ_ID.iam.gserviceaccount.com은 앱을 위해 만드는 사용자 관리형 서비스 계정입니다. 이 명령어는 프로젝트의 업데이트된 IAM 정책을 출력하며 여기서 서비스 계정의 roles/resourcemanager.projectIamAdmin 멤버십이 있는지 확인할 수 있습니다. 자세한 내용은 참조 문서를 확인하세요. 이 Codelab에서는 해당 명령어를 실행할 필요가 없지만 자체 앱을 현대화하기 위한 참조로 저장하면 됩니다.

애플리케이션 배포 및 확인

표준 gcloud app deploy 명령어로 클라우드에 앱을 업로드합니다. 배포가 완료되면 사용자 관리를 위해 App Engine 사용자 서비스를 Cloud Identity Platform (및 Firebase 인증)으로 대체했다는 점을 제외하고 모듈 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 스크린샷은 'double-visit 버그'를 보여줍니다. 살펴보겠습니다 각 로그인 또는 로그아웃 작업에 대해 별도의 방문 로그가 표시됩니다. 시간순으로 표시된 각 스크린샷에서 최근 방문의 타임스탬프를 확인합니다.

삭제

일반

이 작업이 완료되면 요금이 청구되지 않도록 App Engine 앱을 사용 중지하는 것이 좋습니다. 하지만 추가 테스트 또는 실험을 원하는 경우 App Engine 플랫폼에서 무료 할당량을 사용할 수 있으므로 이 사용 등급을 초과하지 않는 한 요금이 청구되지 않습니다. 이는 컴퓨팅을 위한 것이며, 관련 App Engine 서비스에 대한 요금이 부과될 수 있으므로 자세한 내용은 가격 책정 페이지를 확인하세요. 이 마이그레이션에 다른 Cloud 서비스가 포함된 경우 별도로 요금이 청구됩니다. 두 경우 모두 해당하는 경우 '이 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 Dataplex로 마이그레이션
  • 모듈 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로 마이그레이션

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

  • App Engine에서 Cloud Functions로 마이그레이션: 모듈 11 참조
  • App Engine에서 Cloud Run으로 마이그레이션: Docker로 앱을 컨테이너화하려면 모듈 4를 참조하고, 컨테이너, Docker 지식 또는 Dockerfile 없이 마이그레이션하려면 모듈 5를 참조하세요.

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

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

8. 추가 리소스

아래에는 이 이전 모듈 또는 관련 이전 모듈을 자세히 살펴보는 개발자를 위한 추가 리소스가 나열되어 있습니다. 아래에서 이 콘텐츠에 관한 의견을 제공하고 코드 링크 및 도움이 될 만한 다양한 문서를 찾을 수 있습니다.

Codelabs 문제/의견

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

마이그레이션 리소스

모듈 20 (START)과 모듈 21 (FINISH)의 저장소 폴더 링크는 아래 표에서 찾을 수 있습니다.

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 NoSQL, Cloud NBS, Cloud Datastore

기타 마이그레이션 모듈 참조

App Engine 마이그레이션

App Engine 플랫폼

Cloud SDK

기타 클라우드 정보

동영상

라이선스

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