Cloud Run(フルマネージド)での Django の実行

Django のようなステートフル アプリケーションをデプロイするには、複数のサービスを統合して相互に運用し、ひとまとまりのプロジェクトとする必要があります。

このチュートリアルは、Django ウェブ開発の知識があることを前提としています。サンプルコードは、ベースビュー、モデル、ルート構成を実装します。このコードサンプルは、インタラクティブな Django 管理と組み合わせて、マネージド データベース、オブジェクト ストレージ、暗号化されたシークレットを統合し、サーバーレス コンピューティングでパイプラインをビルドする方法を示しています。

このチュートリアルでは Django について具体的に説明しますが、このデプロイ プロセスは WagtailDjango CMS などの他の Django ベースのフレームワークでも使用できます。

デプロイのアーキテクチャを示す図。
Django サイトは、複数のバッキング サービスを使用してさまざまなデータ型(リレーショナル データベース情報、メディア アセット、構成シークレット、コンテナ イメージ)を格納する Cloud Run から提供されます。バックエンド サービスは、ビルドタスクと移行タスクの一環として Cloud Build により更新されます。

料金

始める前に

  1. Google アカウントにログインします。

    Google アカウントをまだお持ちでない場合は、新しいアカウントを登録します。

  2. Google Cloud Console の [プロジェクト セレクタ] ページで、Google Cloud プロジェクトを選択または作成します。

    [プロジェクトの選択] ページに移動

  3. Cloud プロジェクトに対して課金が有効になっていることを確認します。プロジェクトに対して課金が有効になっていることを確認する方法を学習する

  4. Cloud Run, Cloud SQL, Cloud Build, Secret Manager, and Compute Engine API を有効にします。

    API を有効にする

  5. Cloud SDK をインストールして初期化します。
  6. このチュートリアルで使用するアカウントに十分な権限が付与されていることを確認してください。

Django アプリのクローンを作成する

事前準備が完了したら、Django サンプルアプリをダウンロードしてデプロイします。以降のセクションでは、アプリの構成、実行、デプロイの手順を順番に説明します。

Django アプリのクローンを作成する

Django サンプルアプリのコードは、GitHub の GoogleCloudPlatform/python-docs-samples リポジトリにあります。

  1. ZIP ファイルとしてサンプルをダウンロードして展開するか、ローカルマシンにリポジトリのクローンを作成します。

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git

  2. サンプルコードのあるディレクトリに移動します。

Linux / macOS

  cd python-docs-samples/run/django

Windows

  cd python-docs-samples\run\django

バッキング サービスの準備

このチュートリアルでは、さまざまな Google Cloud サービスを使用して、デプロイ済みの Django プロジェクトをサポートするデータベース、メディア ストレージ、シークレット ストレージを準備します。これらのサービスは、特定のリージョンにデプロイされます。サービス間の効率を高めるために、すべてのサービスを同じリージョンにデプロイすることをおすすめします。最も近いリージョンの詳細については、リージョン別に提供されるプロダクトをご覧ください。

Cloud SQL for PostgreSQL インスタンスを設定する

Django は複数のリレーショナル データベースをサポートしていますが、Cloud SQL で提供されるデータベース タイプの 1 つである PostgreSQL を最もサポートしています。

以降のセクションでは、polls アプリ用の PostgreSQL インスタンス、データベース、データベース ユーザーの作成について説明します。

PostgreSQL インスタンスを作成する

Console

  1. Cloud Console で、[Cloud SQL インスタンス] ページに移動します。

    Cloud SQL インスタンス ページに移動

  2. [インスタンスを作成] をクリックします。

  3. [PostgreSQL] をクリックします。

  4. [インスタンス ID] フィールドに、インスタンスの名前(INSTANCE_NAME)を入力します。

  5. postgres ユーザーのパスワードを入力します。

  6. 他のフィールドはデフォルト値を使用します。

  7. [作成] をクリックします。

gcloud

  1. PostgreSQL インスタンスを作成します。

    gcloud sql instances create INSTANCE_NAME \
        --project PROJECT_ID \
        --database-version POSTGRES_12 \
        --tier db-f1-micro \
        --region REGION
    

    以下を置き換えます。

    インスタンスの作成と使用準備が完了するまでに数分かかります。

  2. postgres ユーザーのパスワードを設定します。

    gcloud sql users set-password postgres \
        --instance INSTANCE_NAME --prompt-for-password
    

データベースの作成

Console

  1. Cloud Console で、[Cloud SQL インスタンス] ページに移動します。

    Cloud SQL インスタンス ページに移動

  2. INSTANCE_NAME インスタンスを選択します。

  3. [データベース] タブに移動します。

  4. [データベースを作成] をクリックします。

  5. [データベース名] ダイアログで「DATABASE_NAME」と入力します。

  6. [作成] をクリックします。

gcloud

  • 最近作成したインスタンス内にデータベースを作成します。

    gcloud sql databases create DATABASE_NAME \
        --instance INSTANCE_NAME
    

    DATABASE_NAME を、このインスタンス内のデータベースの名前に置き換えます。

ユーザーを作成する

Console

  1. Cloud Console で、Cloud Shell をアクティブにします。

    Cloud Shell をアクティブにする

  2. Cloud Shell で、組み込みの gcloud クライアントを使用して INSTANCE_NAME インスタンスに接続します。

    gcloud sql connect INSTANCE_NAME --user postgres
    
  3. postgres ユーザー パスワードを入力します。

    現在、psql を使用しています。postgres=> プロンプトが表示されます。

  4. ユーザーを作成します。

    CREATE USER django WITH PASSWORD 'PASSWORD';
    

    PASSWORD を、十分に秘密性の高いパスワードに置き換えます。

  5. 新しいデータベースに対する完全な権限を新しいユーザーに付与します。

    GRANT ALL PRIVILEGES ON DATABASE DATABASE_NAME TO django;
    
  6. psql を終了します。

    \q
    

gcloud

  1. SQL インスタンスへの接続を開始します。

    gcloud sql connect INSTANCE_NAME --user postgres
    

    INSTANCE_NAME は、作成した Cloud SQL インスタンスに置き換えます。

  2. postgres ユーザー パスワードを入力します。

    現在、psql を使用しています。postgres=> プロンプトが表示されます。

  3. ユーザーを作成します。

    CREATE USER django WITH PASSWORD 'PASSWORD';
    
  4. 新しいデータベースに対する完全な権限を新しいユーザーに付与します。

    GRANT ALL PRIVILEGES ON DATABASE DATABASE_NAME TO django;
    
  5. psql を終了します。

    \q
    

Cloud Storage バケットを設定する

Django に含まれている静的アセットやユーザーがアップロードしたメディアを、Cloud Storage を使用する可用性の高いオブジェクト ストレージに保存できます。django-storages[google] パッケージは、Django とこのストレージ バックエンドとのインタラクションを処理します。

Console

  1. Cloud Console の [Cloud Storage ブラウザ] ページに移動します。

    Cloud Storage ブラウザページに移動

  2. [バケットを作成] をクリックします。
  3. [バケットの作成] ダイアログ内で、以下の属性を指定します。
    • バケット名要件を満たす、一意のバケット名。
    • デフォルトのストレージ クラス: Standard
    • ロケーション: MEDIA_BUCKET
  4. [作成] をクリックします。

gcloud

gsutil コマンドライン ツールは、Cloud SDK の一部としてインストールされます。

  • Cloud Storage バケットを作成します。

    gsutil mb -l REGION gs://PROJECT_ID_MEDIA_BUCKET
    

    MEDIA_BUCKET は、メディア バケットの接尾辞に置き換えます。プロジェクト ID と組み合わされることで、一意のバケット名になります。

Secret Manager にシークレット値を保存する

バッキング サービスが構成されたので、Django はこれらのサービスに関する情報を必要とします。このチュートリアルでは、これらの値を Django のソースコードに直接入力せず、Secret Manager を使用してこの情報を安全に保存します。

Cloud Run と Cloud Build は、プロジェクト番号を含むメールアドレスで識別される、それぞれのサービス アカウントを使用してシークレットとやり取りします。

Secret Manager シークレットとして Django 環境ファイルを作成する

Django の起動に必要な設定を、保護された .env ファイルに保存します。サンプルコードは、Secret Manager API を使用してシークレット値を取得し、django-environ パッケージを使用して Django 環境に値を読み込みます。シークレットは、Cloud Build と Cloud Run の両方でアクセスできるように構成されます。これは、これらのサービスが両方とも Django を実行するためです。

  1. .env という名前のファイルを作成し、データベースの接続文字列、メディア バケット名、新しい SECRET_KEY 値を定義します。

    DATABASE_URL=postgres://django:PASSWORD@//cloudsql/PROJECT_ID:REGION:INSTANCE_NAME/DATABASE_NAME
    GS_BUCKET_NAME=PROJECT_ID_MEDIA_BUCKET
    SECRET_KEY=(a random string, length 50)
    
  2. シークレットを Secret Manager に保存します。

Console

  1. Cloud Console で、[シークレット マネージャー] ページに移動します。

    シークレット マネージャー ページに移動

  2. [シークレットの作成] をクリックします。

  3. [名前] フィールドに「django_settings」と入力します。

  4. [ファイルをアップロード] ダイアログで、ローカルに保存されている .env ファイルを選択します。

  5. [シークレットの作成] をクリックします。

  6. [django_settings の詳細] で、プロジェクト番号をメモします。

    projects/PROJECTNUM/secrets/django_settings
    
  7. [メンバーを追加] をクリックします。

  8. [新しいメンバー] フィールドに「PROJECTNUM-compute@developer.gserviceaccount.com」と入力し、Enter を押します。

  9. [新しいメンバー] フィールドに「PROJECTNUM@cloudbuild.gserviceaccount.com」と入力し、Enter を押します。

  10. [ロール] プルダウン メニューで [Secret Manager のシークレット アクセサー] を選択します。

  11. [保存] をクリックします。

gcloud

  1. 新しいシークレット(django_settings)を作成します。

    gcloud secrets create django_settings --replication-policy automatic
    
  2. .env ファイルをシークレットのバージョンとして追加します。

    gcloud secrets versions add django_settings --data-file .env
    
  3. シークレットの作成を確認するには、次のことを確認します。

    gcloud secrets describe django_settings
    

    プロジェクト番号をメモします。

    projects/PROJECTNUM/secrets/django_settings
    
  4. Cloud Run サービス アカウントにシークレットへのアクセス権を付与します。

    gcloud secrets add-iam-policy-binding django_settings \
        --member serviceAccount:PROJECTNUM-compute@developer.gserviceaccount.com \
        --role roles/secretmanager.secretAccessor
    
  5. Cloud Build サービス アカウントにシークレットへのアクセス権を付与します。

    gcloud secrets add-iam-policy-binding django_settings \
        --member serviceAccount:PROJECTNUM@cloudbuild.gserviceaccount.com \
        --role roles/secretmanager.secretAccessor
    

    出力で、bindings が 2 つのサービス アカウントをメンバーとしてリストしていることを確認します。

Django の管理者パスワード用のシークレットを作成する

Django 管理ユーザーは通常、インタラクティブ管理コマンド createsuperuser を実行して作成されます。

このチュートリアルでは、データ移行を使用して管理ユーザーを作成し、Secret Manager からパスワードを取得します。

Console

  1. Cloud Console で、[シークレット マネージャー] ページに移動します。
  2. [シークレットの作成] をクリックします。

  3. [名前] フィールドに「superuser_password」と入力します。

  4. [シークレット値] フィールドにランダムなパスワードを入力します。

  5. [シークレットの作成] をクリックします。

  6. [superuser_password の詳細] で、プロジェクト番号をメモします(projects/PROJECTNUM/secrets/superuser_password)。

  7. [メンバーを追加] をクリックします。

  8. [新しいメンバー] フィールドに「PROJECTNUM@cloudbuild.gserviceaccount.com」と入力し、Enter を押します。

  9. [ロール] プルダウン メニューで [Secret Manager のシークレット アクセサー] を選択します。

  10. [保存] をクリックします。

gcloud

  1. 新しいシークレット(superuser_password)を作成します。

    gcloud secrets create superuser_password --replication-policy automatic
    
  2. このシークレットのバージョンとして、ランダムに生成されたパスワードを追加します。

    cat /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c30 > superuser_password
    
    gcloud secrets versions add superuser_password --data-file superuser_password
    
  3. Cloud Build にシークレットへのアクセス権を付与します。

    gcloud secrets add-iam-policy-binding superuser_password \
        --member serviceAccount:PROJECTNUM@PROJECTNUM@cloudbuild.gserviceaccount.com \
        --role roles/secretmanager.secretAccessor
    

    出力で、bindings が Cloud Build のみをメンバーとしてリストしていることを確認します。

Cloud Build に Cloud SQL へのアクセス権を付与する

Cloud Build でデータベースの移行を適用するには、Cloud Build に Cloud SQL へのアクセス権を付与する必要があります。

Console

  1. Cloud Console で、[Identity and Access Management] ページに移動します。

    [Identity and Access Management] ページに移動

  2. PROJECTNUM@cloudbuild.gserviceaccount.com のエントリを編集するには、 [編集] をクリックします。

  3. [別のロールを追加] をクリックします。

  4. [ロールを選択] ダイアログで、[Cloud SQL クライアント] を選択します。

  5. [保存] をクリックします。

gcloud

  1. Cloud Build に Cloud SQL へのアクセス権を付与します。

    gcloud projects add-iam-policy-binding PROJECT_ID \
        --member serviceAccount:PROJECTNUM@cloudbuild.gserviceaccount.com \
        --role roles/cloudsql.client
    

アプリケーションの最初のデプロイ

バッキング サービスを設定できたので、Cloud Run サービスをデプロイできるようになりました。

  1. 提供された cloudmigrate.yaml を使用して、Cloud Build を使用してイメージをビルドし、データベースの移行を実行し、静的アセットにデータを入力します。

    gcloud builds submit --config cloudmigrate.yaml
        --substitutions _INSTANCE_NAME=INSTANCE_NAME,_REGION=REGION
    

    最初のビルドが完了するまで数分かかります。

  2. ビルドが成功したら、Cloud Run サービスを初めてデプロイし、サービス リージョン、ベースイメージ、接続された Cloud SQL インスタンスを設定します。

    gcloud run deploy polls-service \
        --platform managed \
        --region REGION \
        --image gcr.io/PROJECT_ID/polls \
        --add-cloudsql-instances PROJECT_ID:REGION:INSTANCE_NAME \
        --allow-unauthenticated
    

    サービスの URL とともに、デプロイが成功したことを示す出力が表示されます。

    Service [polls-service] revision [polls-service-00001-tug] has been deployed
    and is serving 100 percent of traffic at https://polls-service--uc.a.run.app
    
  3. デプロイされたサービスを確認するには、サービスの URL に移動します。

    polls アプリケーションのランディング ページのスクリーンショット。
    サービスの URL に「You're at the polls index」と表示されている場合、Django アプリケーションは正常にデプロイされています。
  4. Django 管理にログインするには、URL に /admin を加え、ユーザー名 admin と前に設定したパスワードでログインします。

    Django 管理のスクリーンショット
    認証が完了すると、Django 管理が表示され、Polls アプリに質問モデルが表示されます。

最小権限の設定(省略可)

デフォルトでは、このサービスはデフォルトのコンピューティング サービス アカウントでデプロイされます。ただし、場合によってはデフォルトのサービス アカウントを使用すると、多すぎる権限が付与されることがあります。さらに制限が必要な場合は、独自のサービス アカウントを作成し、サービスに必要な権限のみを割り当てる必要があります。必要な権限は、特定のサービスで使用されるリソースに応じて、サービスごとに異なる可能性があります。

このサービスに必要な最小限のプロジェクト ロールは次のとおりです。

  • Cloud Run 起動元
  • Cloud SQL クライアント
  • メディア バケットの Storage 管理者
  • Django 設定シークレットの Secret Manager Accessor。(Django 管理シークレットへのアクセスは、サービス自体には必要ありません。)

必要な権限を持つサービス アカウントを作成するには、次の手順を実施します。

  1. Cloud Shell で、必要なロールを持つサービス アカウントを作成します。

    gcloud iam service-accounts create polls-service-account
    SERVICE_ACCOUNT=polls-service-account@PROJECT_ID.iam.gserviceaccount.com
    
    # Cloud Run Invoker
    gcloud iam service-accounts add-iam-policy-binding PROJECT_ID \
        --member serviceAccount:${SERVICE_ACCOUNT} \
        --role roles/run.Invoker
    
    # Cloud SQL Client
    gcloud iam service-accounts add-iam-policy-binding PROJECT_ID \
        --member serviceAccount:${SERVICE_ACCOUNT} \
        --role roles/cloudsql.client
    
    # Storage Admin, on the media bucket
    gsutil iam ch \
        serviceAccount:${SERVICE_ACCOUNT}:roles/storage.objectAdmin \
        gs://MEDIA_BUCKET
    
    # Secret Accessor, on the Django settings secret.
    gcloud secrets add-iam-policy-binding django_settings \
        --member serviceAccount:${SERVICE_ACCOUNT} \
        --role roles/secretmanager.secretAccessor
    
  2. サービスを新しいサービス アカウントに関連付けてデプロイします。

    gcloud run deploy polls-service \
        --platform managed \
        --region REGION \
        --service-account ${SERVICE_ACCOUNT}
    

アプリケーションの再デプロイ

最初のプロビジョニングとデプロイの手順は複雑でしたが、再デプロイは比較的簡単です。

  1. Cloud Build のビルドと移行スクリプトを実行します。

    gcloud builds submit --config cloudmigrate.yaml \
        --substitutions _INSTANCE_NAME=INSTANCE_NAME,_REGION=REGION
    
  2. リージョンとイメージのみを指定して、サービスをデプロイします。

    gcloud run deploy polls-service \
        --platform managed \
        --region REGION \
        --image gcr.io/PROJECT_ID/polls
    

コードの説明

Django サンプルアプリは、Django の標準ツールを使用して作成されています。以下のコマンドは、プロジェクトと polls アプリを作成します。

django-admin startproject mysite .
python manage.py startapp polls

ベースビュー、モデル、ルート構成は、最初の Django アプリを作成するパート 1およびパート 2)からコピーされました。

Secret Manager のシークレット

settings.py ファイルには、Secret Manager Python API を使用して指定されたシークレットの最新バージョンを取得し、(django-environ を使用して)その環境に pull するコードが含まれています。

        import google.auth
        from google.cloud import secretmanager_v1

        _, project = google.auth.default()

        if project:
            client = secretmanager_v1.SecretManagerServiceClient()

            SETTINGS_NAME = os.environ.get("SETTINGS_NAME", "django_settings")
            name = f"projects/{project}/secrets/{SETTINGS_NAME}/versions/latest"
            payload = client.access_secret_version(name=name).payload.data.decode(
                "UTF-8"
            )

    with open(env_file, "w") as f:
        f.write(payload)

env = environ.Env()
env.read_env(env_file)

django_settings は、構成する必要のあるさまざまなシークレットの数を減らすために、複数のシークレット値を格納するために使用されていました。

superuser_passwordコマンドラインから直接作成することもできますが、この方法ではなくファイルベースの方法が使用されました。コマンドラインから生成したとしたら、head -c を使用してランダムに生成された文字列の長さを慎重に決定し、ファイルの最後に改行文字がないことを確認する必要がありました(これは、パスワードを Django 管理に入力する際の問題の原因になります)。

データベースへの接続

django_settings シークレットには特殊な形式のデータベース接続文字列が含まれているため、django-environ のデータベース ヘルパーを呼び出して DATABASES 値を割り当てることができます。

# Use django-environ to define the connection string
DATABASES = {"default": env.db()}

このメソッドは、データベース接続情報のコンポーネントに個別の値を定義するのではなく、使用されていました。

クラウドに保存された静的

また、settings.py ファイルは、django-storages を使用して Cloud Storage メディア バケットをプロジェクトに直接統合します。

# Define static storage via django-storages[google]
GS_BUCKET_NAME = env("GS_BUCKET_NAME")

DEFAULT_FILE_STORAGE = "storages.backends.gcloud.GoogleCloudStorage"
STATICFILES_STORAGE = "storages.backends.gcloud.GoogleCloudStorage"
GS_DEFAULT_ACL = "publicRead"

Cloud Build による自動化

cloudmigrate.yaml ファイルは、一般的なイメージのビルドステップ(コンテナ イメージを作成してコンテナ レジストリに push する)だけでなく、Django の migrate コマンドと collectstatic コマンドも実行します。これにはデータベースへのアクセスが必要です。これは、app-engine-exec-wrapperCloud SQL Proxy のヘルパー)を使用して実行されます。

steps:
  - id: "build image"
    name: "gcr.io/cloud-builders/docker"
    args: ["build", "-t", "gcr.io/${PROJECT_ID}/${_SERVICE_NAME}", "."]

  - id: "push image"
    name: "gcr.io/cloud-builders/docker"
    args: ["push", "gcr.io/${PROJECT_ID}/${_SERVICE_NAME}"]

  - id: "apply migrations"
    name: "gcr.io/google-appengine/exec-wrapper"
    args:
      [
        "-i",
        "gcr.io/$PROJECT_ID/${_SERVICE_NAME}",
        "-s",
        "${PROJECT_ID}:${_REGION}:${_INSTANCE_NAME}",
        "-e",
        "SETTINGS_NAME=${_SECRET_SETTINGS_NAME}",
        "--",
        "python",
        "manage.py",
        "migrate",
      ]

  - id: "collect static"
    name: "gcr.io/google-appengine/exec-wrapper"
    args:
      [
        "-i",
        "gcr.io/$PROJECT_ID/${_SERVICE_NAME}",
        "-s",
        "${PROJECT_ID}:${_REGION}:${_INSTANCE_NAME}",
        "-e",
        "SETTINGS_NAME=${_SECRET_SETTINGS_NAME}",
        "--",
        "python",
        "manage.py",
        "collectstatic",
        "--no-input",
      ]

substitutions:
  _INSTANCE_NAME: django-instance
  _REGION: us-central1
  _SERVICE_NAME: polls-service
  _SECRET_SETTINGS_NAME: django-settings

images:
  - "gcr.io/${PROJECT_ID}/${_SERVICE_NAME}"

この構成では代入変数が使用されます。ファイルの値を直接変更すると、移行時に --substitutions フラグを省略できます。

この構成では、既存の移行のみが適用されます。この構成を拡張して移行を生成するには、makemigrations ステップを追加します。

2 つのコマンドを実行せずに 1 つの構成にデプロイを含めるように Cloud Build の構成を拡張するには、Cloud Build を使用した git による継続的デプロイをご覧ください。説明されているように、これには IAM の変更が必要です。

データ移行によるスーパーユーザーの作成

Django 管理コマンド createsuperuser はインタラクティブにのみ実行できます。つまり、ユーザーがプロンプトに従い情報を入力する場合です。このコマンドを Cloud SQL Proxy で使用し、ローカルの Docker セットアップ内でコマンドを実行できますが、別の方法は、データ移行としてスーパーユーザーを作成することです。

import os

from django.db import migrations
from django.db.backends.postgresql.schema import DatabaseSchemaEditor
from django.db.migrations.state import StateApps
import google.auth
from google.cloud import secretmanager_v1

def createsuperuser(apps: StateApps, schema_editor: DatabaseSchemaEditor) -> None:
    """
    Dynamically create an admin user as part of a migration
    Password is pulled from Secret Manger (previously created as part of tutorial)
    """
    if os.getenv("TRAMPOLINE_CI", None):
        admin_password = "test"
    else:
        client = secretmanager_v1.SecretManagerServiceClient()

        # Get project value for identifying current context
        _, project = google.auth.default()

        # Retrieve the previously stored admin password
        PASSWORD_NAME = os.environ.get("PASSWORD_NAME", "superuser_password")
        name = f"projects/{project}/secrets/{PASSWORD_NAME}/versions/latest"
        admin_password = client.access_secret_version(name=name).payload.data.decode(
            "UTF-8"
        )

    # Create a new user using acquired password
    from django.contrib.auth.models import User

    User.objects.create_superuser("admin", password=admin_password)

class Migration(migrations.Migration):

    initial = True

    dependencies = []

    operations = [migrations.RunPython(createsuperuser)]

クリーンアップ

  1. Cloud Console で [リソースの管理] ページに移動します。

    [リソースの管理] に移動

  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。