DjangoをCloud Runにデプロイ

DjangoをCloud Runにデプロイした時のメモ。

以下の記事を参考にしました。

Cloud Run での Django  |  Google Codelabs

今回はSQLiteを使用しているためCloud SQLは除外しています。
また、SQLiteは別で設定予定のため今回の作業には含まれません。

Cloud API を有効化

gcloud services enable \
  run.googleapis.com \
  cloudbuild.googleapis.com \
  secretmanager.googleapis.com \
  artifactregistry.googleapis.com

環境変数を設定

export PROJECT_ID=$(gcloud config get-value core/project)
export REGION=us-central1

サービスアカウントを作成

gcloud iam service-accounts create cloudrun-sa
export SERVICE_ACCOUNT=$(gcloud iam service-accounts list \
    --filter cloudrun-sa --format "value(email)")

Artifact Registry を作成

export ARTIFACT_REGISTRY={$REGION}-docker.pkg.dev/{$PROJECT_ID}/containers

Storage バケットを作成

export GS_BUCKET_NAME={$PROJECT_ID}-media
gcloud storage buckets create gs://{$GS_BUCKET_NAME} --location {$REGION} 

バケットを管理する権限をサービスアカウントに付与する。

gcloud storage buckets add-iam-policy-binding gs://{$GS_BUCKET_NAME} \
    --member serviceAccount:{$SERVICE_ACCOUNT} \
    --role roles/storage.admin

Secret Managerに保存

echo GS_BUCKET_NAME=\"{$GS_BUCKET_NAME}\" >> .env
echo SECRET_KEY=\"$(cat /dev/urandom | LC_ALL=C tr -dc 'a-zA-Z0-9' | fold -w 50 | head -n 1)\" >> .env
echo DEBUG=True >> .env
gcloud secrets create application_settings --data-file .env

サービス アカウントにこのシークレットへのアクセスを許可する。

gcloud secrets add-iam-policy-binding application_settings \
  --member serviceAccount:{$SERVICE_ACCOUNT} --role roles/secretmanager.secretAccessor

作成されたことを確認。

gcloud secrets versions list application_settings
rm .env

設定ファイルを作成

記事を参考に設定ファイルを作成します。

mv myproject/settings.py myproject/basesettings.py
touch myproject/settings.py

myproject/settings.py

import io
import os
from urllib.parse import urlparse

import environ

# Import the original settings from each template
from .basesettings import *

# Load the settings from the environment variable
env = environ.Env()
env.read_env(io.StringIO(os.environ.get("APPLICATION_SETTINGS", None)))

# Setting this value from django-environ
SECRET_KEY = env("SECRET_KEY")

# Ensure myproject is added to the installed applications
if "myproject" not in INSTALLED_APPS:
    INSTALLED_APPS.append("myproject")

# If defined, add service URLs to Django security settings
CLOUDRUN_SERVICE_URLS = env("CLOUDRUN_SERVICE_URLS", default=None)
if CLOUDRUN_SERVICE_URLS:
    CSRF_TRUSTED_ORIGINS = env("CLOUDRUN_SERVICE_URLS").split(",")
    # Remove the scheme from URLs for ALLOWED_HOSTS
    ALLOWED_HOSTS = [urlparse(url).netloc for url in CSRF_TRUSTED_ORIGINS]
else:
    ALLOWED_HOSTS = ["*"]

# Default false. True allows default landing pages to be visible
DEBUG = env("DEBUG", default=False)

# Set this value from django-environ
DATABASES = {"default": env.db("DATABASE_URL", default="sqlite:///db.sqlite3")}

# Define static storage via django-storages[google]
GS_BUCKET_NAME = env("GS_BUCKET_NAME")
STATICFILES_DIRS = []
GS_DEFAULT_ACL = "publicRead"
STORAGES = {
    "default": {
        "BACKEND": "storages.backends.gcloud.GoogleCloudStorage",
    },
    "staticfiles": {
        "BACKEND": "storages.backends.gcloud.GoogleCloudStorage",
    },
}

requirements.txt(追加)

gunicorn
django-storages[google]
django-environ

Procfileを作成

touch Procfile

Procfile

web: gunicorn --bind 0.0.0.0:$PORT --workers 1 --threads 8 --timeout 0 myproject.wsgi:application

イメージをビルドする

gcloud builds submit --pack image={$ARTIFACT_REGISTRY}/myimage

イメージ名は適宜変更します。

Cloud Runにデプロイ

gcloud run deploy django-cloudrun \
  --region $REGION \
  --image {$ARTIFACT_REGISTRY}/myimage \
  --set-secrets APPLICATION_SETTINGS=application_settings:latest \
  --service-account $SERVICE_ACCOUNT \
  --allow-unauthenticated

デプロイが完了して表示されたURLを選択するとDjangoのスタートページが表示されます。

変更の適用

変更があった場合は、以下の操作を行います。

  1. 新しいイメージをビルド
  2. Cloud Run を更新
gcloud builds submit --pack image=${ARTIFACT_REGISTRY}/myimage
gcloud run services update django-cloudrun \
  --region $REGION \
  --image ${ARTIFACT_REGISTRY}/myimage

.envを読み込み

ローカル環境で開発する時は、環境変数.envから読み込めるほうが都合がいい場合もあったのでmyproject/settings.pyに以下を追加しました。

from pathlib import Path

# プロジェクトのルートディレクトリを取得
BASE_DIR = Path(__file__).resolve().parent.parent

# .envファイルを読み込む
env_path = os.path.join(BASE_DIR, '.env')
if Path(env_path).is_file():
  env.read_env(env_path)

YouTubeの動画から字幕テキストを取得する

前回の続きでYouTubeの動画から字幕テキストを取得するときのメモ。

tsyknsr.hatenablog.com

import csv
from youtube_transcript_api import YouTubeTranscriptApi

def get_video_transcript(video_url):
    # 動画IDをURLから抽出
    video_id = video_url.split('v=')[-1].split('&')[0]
    
    try:
        # 字幕を取得
        transcript = YouTubeTranscriptApi.get_transcript(video_id, languages=['ja'])
        # 字幕テキストを結合
        transcript_text = ' '.join([entry['text'] for entry in transcript])
        return transcript_text
    except Exception as e:
        print(f"Error retrieving transcript: {e}")
        return None
    
def main():   
    # video_list.csvからURLを読み込み中
    video_transcripts = []
    with open('video_list.csv', mode='r', encoding='utf-8') as file:
        reader = csv.reader(file)
        next(reader)  # ヘッダをスキップ
        for row in reader:
            print(f"Title: {row[0]}")
            video_url = row[1]
            transcript_text = get_video_transcript(video_url)
            video_transcripts.append({'title': row[0], 'url': video_url, 'transcript': transcript_text})

    # 取得した字幕テキストをCSVに出力
    with open('video_transcripts.csv', mode='w', newline='', encoding='utf-8') as file:
        writer = csv.writer(file)
        writer.writerow(['title', 'url', 'transcript'])
        for video_transcript in video_transcripts:
            writer.writerow([video_transcript['title'], video_transcript['url'], video_transcript['transcript']])

if __name__ == "__main__":
    main()

YouTubeのチャンネルから動画のタイトルとURLの一覧を取得する

YouTubeAPIを使用して動画のタイトルとURLの一覧を取得するプログラムを作成した時のメモ。

import csv
from googleapiclient.discovery import build

# YouTube Data API キーを設定
API_KEY = ''

# チャンネルURLを設定
CHANNEL_URL=''

def get_channel_videos(channel_url):
    # YouTube APIクライアントを作成
    youtube = build('youtube', 'v3', developerKey=API_KEY)
    
    # チャンネルIDを取得
    channel_id = channel_url.split('/')[-1]
    
    # チャンネルの動画を取得
    videos = []
    next_page_token = None
    
    while True:
        request = youtube.search().list(
            part='snippet',
            channelId=channel_id,
            maxResults=50,
            type='video',
            pageToken=next_page_token
        )
        response = request.execute()
        
        for item in response['items']:
            video_title = item['snippet']['title']
            video_url = f"https://www.youtube.com/watch?v={item['id']['videoId']}"
            videos.append({'title': video_title, 'url': video_url})
        
        next_page_token = response.get('nextPageToken')
        if not next_page_token:
            break
    
    return videos

def main():
    video_list = get_channel_videos(CHANNEL_URL)

    with open('video_list.csv', mode='w', newline='', encoding='utf-8') as file:
        writer = csv.writer(file)
        writer.writerow(['title', 'url'])
        for video in video_list:
            writer.writerow([video['title'], video['url']])

if __name__ == "__main__":
    main()

2023年買ってよかったもの

iPhone15 Pro

毎年値段が上がるので周囲ではAndroidに転向する人も出てきましたが今年も頑張って購入しました。

Ankerのモバイルバッテリー

サイズ感がちょうど良いのとケーブルを持ち歩く必要がないので最近の普段使いはこれに落ち着きました。

aarkeの炭酸水メーカー

こういったオシャレ家電は使わなくなることのほうが多いのですが、自宅で炭酸水をつくれることが意外と便利で日常的に使う家電の仲間入りをしました。

ダンベル 10kg

体力維持レベルの運動が目的なので10kgでも充分でした。しっかりしたダンベルは数万円するので購入に勇気がいりますが、こちらは1万円台で購入しやすい価格帯ということも満足度につながりました。ダンベルが良かったので調子に乗ってベンチも買いましたが、残念ながら埃をかぶりはじめています。

耐熱ボウル

耐熱ボウルの5点セットです。最初は欲しいサイズの1点だけを予定していましたが、実際に使ってみるとそれぞれのサイズで使い分けたほうが圧倒的に料理しやすいのでセットを購入して良かったと思います。

マキネッタ

夏場にアイスカフェラテをつくるのに大活躍しました。手入れも大雑把でいいので性分に合っていました。豆を自分で挽くか悩みましたが、面倒になり使用する機会が減ると思ったので、豆を購入した時にお店で挽いてもらいました。

RICOH GR IIIx

サイズ感と重さと軽さが自分の生活に合っていました。iPhone15 Pro購入以降は持ち出す回数が減りましたが、旅行に行った時などにGRを使用しています。数年前に思い切って購入したミラーレス一眼は、今年は使用する機会がなかったので来年売却するか悩んでいます。

漫画

最近読んで面白い作品でした。

PS5

年末年始のお供にホグワーツ・レガシーと合わせて購入しました。ゲームはエンタメの最先端のひとつであることを学びました。

Rustでdotenvyを使用する

Rustでdotenvのcrateはメンテナンスが行われていないらしく以下の記事を参考にdotenvyを使用しました。

zenn.dev

github.com

$ cargo add dotenvy

適当に.envを作成

FOO=bar
use dotenvy::dotenv;
use std::env;

fn main() {
    dotenv().expect(".env file not found");

    let val = env::var("FOO").expect("FOO not set");
    println!("Foo: {val}");
}
$ cargo run
Foo: bar

Microsoft Remote DesktopでHHKBを使う

Windows環境での作業が必要になったので、 リモートデスクトップMacからWindowsを使用するための準備を行いました。

リモートデスクトップ接続するためのアプリはいくつか候補がありましたが、 今回は用途に合っていた「Microsoft Remote Desktop」を選びました。

Windows 11 Homeを使用していましたが、 Microsoft Remote Desktopで接続するにはProエディションにする必要があったため Windows 11 Proを購入しました。

普段Macを使用しているので、 Microsoft Remote Desktopで接続した時のWindowsキーIMEの切り替え操作に馴染めず、 Karabinerに以下のルールを追加しました。

  • 左のcommandキーをWinのControlキー
  • 右のcommandキーをWinのIME切り替え
{
  "title": "Custom rules for Remote Desktop",
  "rules": [
    {
      "description": "Swap left_control and left_command in Remote Desktop",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "left_command",
            "modifiers": {
              "optional": ["any"]
            }
          },
          "to": [
            {
              "key_code": "left_control"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_if",
              "bundle_identifiers": [
                "^com\\.microsoft\\.rdc\\.macos$"
              ]
            }
          ]
        }
      ]
    },
    {
      "description": "Toggle IME with right_command in Remote Desktop",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "right_command",
            "modifiers": {
              "optional": ["any"]
            }
          },
          "to": [
            {
              "key_code": "grave_accent_and_tilde",
              "modifiers": ["right_alt"]
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_if",
              "bundle_identifiers": [
                "^com\\.microsoft\\.rdc\\.macos$"
              ]
            }
          ]
        }
      ]
    }
  ]
}

さくらVPSにPostgresqlをインストール

さくらVPSPostgresqlをインストールした時のメモ

# yum update -y
# yum install -y https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-7-x86_64/pgdg-centos96-9.6-3.noarch.rpm
# yum install -y postgresql96-server postgresql96-contrib postgresql96-devel
# psql --version
psql (PostgreSQL) 9.6.11

初期化

# export PGSETUP_INITDB_OPTIONS="--encoding=UTF-8 --no-locale"
# /usr/pgsql-9.6/bin/postgresql96-setup initdb
# systemctl enable postgresql-9.6
# systemctl start postgresql-9.6

認証方法を変更

$ sudo vim /var/lib/pgsql/9.6/data/pg_hba.conf

peerをtrustに、identをmd5に変更

# "local" is for Unix domain socket connections only
local   all             all                                     trust
# IPv4 local connections:
host    all             all             127.0.0.1/32            md5
# IPv6 local connections:
host    all             all             ::1/128                 md5

参考