Django(Django Ninja)をCloud Runにデプロイしてみた vol.1

## 環境
MacOS Ventura
Python 3.10.7
Poetry 1.3.2

## はじめに
この記事はこちらの続きです
https://qiita.com/ps0317ix/items/ff10e0eddfc24e5db96c


## 早速始めよう
正直Googleの公式がかなり充実してるのでこれで問題ないですが、あえて噛み砕いて手順を説明します
https://cloud.google.com/python/django/run?hl=ja

必要なライブラリを追加
```terminal:terminal
poetry add psycopg2-binary gunicorn django-environ django-storages google-cloud-secret-manager
```

あとは公式ドキュメントに沿っていきます。

### 1. API の認証情報を取得して認証
```terminal:terminal
gcloud auth application-default login
```

### 2. Cloud SQL Auth プロキシをダウンロードしてローカルマシンにインストール
```terminal:terminal
curl -o cloud_sql_proxy https://dl.google.com/cloudsql/cloud_sql_proxy.darwin.arm64
chmod +x cloud_sql_proxy
```

### ３. PostgreSQL インスタンスを作成
以下に置き換えてください
INSTANCE_NAME: Cloud SQL インスタンス名
PROJECT_ID: Google Cloud プロジェクト ID
REGION: Google Cloud リージョン
```terminal:terminal
gcloud sql instances create INSTANCE_NAME \
    --project PROJECT_ID \
    --database-version POSTGRES_13 \
    --tier db-f1-micro \
    --region REGION
```

作成したインスタンス内に、データベースを作成
```terminal:terminal
gcloud sql databases create DATABASE_NAME \
    --instance INSTANCE_NAME
```

データベース ユーザーを作成
```terminal:terminal
gcloud sql users create DATABASE_USERNAME \
    --instance INSTANCE_NAME \
    --password DATABASE_PASSWORD
```


### 4. Cloud Storage バケットを設定
```terminal:terminal
gsutil mb -l REGION gs://PROJECT_ID_MEDIA_BUCKET
```


### 5. Secret Manager シークレットとして Django 環境ファイルを作成
```terminal:terminal
echo DATABASE_URL=postgres://DATABASE_USERNAME:DATABASE_PASSWORD@//cloudsql/PROJECT_ID:REGION:INSTANCE_NAME/DATABASE_NAME > .env
echo GS_BUCKET_NAME=PROJECT_ID_MEDIA_BUCKET >> .env
echo SECRET_KEY=$(cat /dev/urandom | LC_ALL=C tr -dc '[:alpha:]'| fold -w 50 | head -n1) >> .env
echo PROJECTNUM=$(gcloud projects describe PROJECT_ID --format='value(projectNumber)') >> .env
```


### 6. シークレットをSecret Managerに保存
```terminal:terminal
gcloud secrets create django_settings --data-file .env
```

### 7. 動作確認 
settings.pyに以下を追加していきます
```DJANGO_NINJA_TEMPLETE/settings.py
import io
import os
import environ
from pathlib import Path
from datetime import timedelta

import google.auth
from google.cloud import secretmanager
from google.oauth2 import service_account
from datetime import timedelta
from pathlib import Path
from storages.backends.gcloud import GoogleCloudStorage
```
```DJANGO_NINJA_TEMPLETE/settings.py
env = environ.Env(DEBUG=(bool, True))
env_file = os.path.join(BASE_DIR, ".env")

try:
    _, os.environ["GOOGLE_CLOUD_PROJECT"] = google.auth.default()
except google.auth.exceptions.DefaultCredentialsError:
    pass

if os.path.isfile(env_file):
    print('Use a local secret file, if provided')
    env.read_env(env_file)
    service_account_json = get_or_create_service_account_json(BASE_DIR)
    credentials = service_account.Credentials.from_service_account_info(service_account_json)
elif os.environ.get("GOOGLE_CLOUD_PROJECT", None):
    print('Pull secrets from Secret Manager')
    project_id = os.environ.get("GOOGLE_CLOUD_PROJECT")

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

    env.read_env(io.StringIO(payload))
elif os.getenv("TRAMPOLINE_CI", False):
    print('Create local settings if running with CI, for unit testing')
    placeholder = (
        f"SECRET_KEY=a\n"
        "GS_BUCKET_NAME=None\n"
        f"DATABASE_URL=sqlite://{os.path.join(BASE_DIR, 'db.sqlite3')}"
    )
    env.read_env(io.StringIO(placeholder))
    service_account_json = get_or_create_service_account_json(BASE_DIR)
    credentials = service_account.Credentials.from_service_account_info(service_account_json)
else:
    raise Exception("No local .env or GOOGLE_CLOUD_PROJECT detected. No secrets found.")

SECRET_KEY = os.getenv('SECRET_KEY')
```
DJANGO_NINJA_TEMPLETEにutils.pyを追加
```DJANGO_NINJA_TEMPLETE/utils.py
import json
import os

from google.cloud import secretmanager


def get_service_account_json():
    project_num = os.environ.get("PROJECTNUM")

    client = secretmanager.SecretManagerServiceClient()
    name = f"projects/{project_num}/secrets/service_account/versions/latest"
    response = client.access_secret_version(name=name)
    secret_string = response.payload.data.decode("UTF-8")
    return json.loads(secret_string)


def get_service_account_json_from_file(json_file_path):
    with open(json_file_path, "r") as json_file:
        return json.load(json_file)


def store_service_account_json_to_file(json_data, json_file_path):
    with open(json_file_path, "w") as json_file:
        json.dump(json_data, json_file)


def get_or_create_service_account_json(base_dir):
    json_file_path = os.path.join(base_dir, "static", "service_account.json")

    if os.path.isfile(json_file_path):
        return get_service_account_json_from_file(json_file_path)

    json_data = get_service_account_json()
    store_service_account_json_to_file(json_data, json_file_path)

    return json_data
```

GCPでサービスアカウントを作成し、シークレットキーを作成します
https://cloud.google.com/iam/docs/creating-managing-service-account-keys?hl=ja

Secret Managerを開き、『service_account』という名のシークレットを作成し、上記で作成したJSONの内容を追加してください。
https://console.cloud.google.com/security/secret-manager

Cloud Proxyを起動
```terminal:terminal
./cloud_sql_proxy -instances="PROJECT_ID:REGION:INSTANCE_NAME"=tcp:5432
```

次にstaticというフォルダを作成し、以下のコマンドを実行し、Djangoを起動します
```terminal:terminal
python manage.py makemigrations
python manage.py migrate
python manage.py collectstatic
python manage.py runserver
```

立ち上がれば一旦OK！！