python / Django
作成日:2020/3/3 更新日:2020/3/5

Djangoで迅速な開発を行うための構造とCI/CD [ 3.x対応 ]

Django3.x系に対応したプロジェクトの構造、テストの仕方、CI/CDの設定について、8年ほど使ってきた経験から良いと思うGCPを活用した実装方法を解説しています。最も優先していることはモジュール化を図り、将来、同じ機能を反復して開発しないようにすることで、そのための設計の一例として読んで頂けると幸いです。

私はPythonを書き始めて8年程経っていまして、利用用途としてはWeb系の開発が多く、特にLinuxが好きなこともあり、小さいシステム、再利用性を設計の優先事項としていた自分にとって、Djangoはうってつけのフレームワークだったので現在もほとんどのWebシステムはDjangoで開発することが多いです。
Djangoはモジュール化を推進しており、作れば作るほど、将来、反復して開発する作業が無くなるので、使い込むほど開発効率が良くなり、特に受託開発では重宝すると思います。

django/django
The Web framework for perfectionists with deadlines. - django/django

Djangoのプロジェクトを開始するときは長年使ってきて、最終形みたいな構造がありまして、コード内に直接注釈を追加して説明します。

.
├── .idea # メモ類
├── .trash # 開発中のメールファイル、ログなど
├── .vscode # vscodeの設定
├── .dockerignore # Dockerにマウントしないファイル、ディレクトリの設定
├── .env # pipenvで読み込む環境変数
├── .gcloudignore # GCP Container Registryにマウントしないファイル、ディレクトリの設定
├── .gitignore # バージョン管理に含めないファイル、ディレクトリの設定
├── .git # バージョン管理
├── Dockerfile # 最新のDebian系イメージをベースにしたWeb向けのPythonイメージ
├── Pipfile # Pythonライブラリの依存管理
├── Pipfile.lock
├── README.md
├── cloudbuild.yaml # GCP、CloudBuildで実行するCI/CDの設定
├── docker-compose.yaml # Djangoアプリケーション以外の依存しているミドルウェア e.g. PostgreSQL, Redis, 他マイクロサービスなど
├── iam.json # GCPで発行している開発環境中に使うサービスアカウント
├── manage.py
├── pytest.ini # Pytestの設定
├── requirements.txt
└── src # プロジェクトソース
    ├── __init__.py
    ├── app # モジュール、過去に作ったモジュールをこの単位で追加している e.g. app, payment, blog
    │   ├── __init__.py
    │   ├── admin.py # 自動生成して実装 ※後述
    │   ├── cfg.py # AppConfig Class
    │   ├── decorators.py # デコレーター
    │   ├── forms.py # フォーム
    │   ├── locale # モジュールの翻訳セット
    │   │   ├── en
    │   │   │   └── LC_MESSAGES
    │   │   │       ├── django.mo
    │   │   │       └── django.po
    │   │   └── ja
    │   │       └── LC_MESSAGES
    │   │           ├── django.mo
    │   │           └── django.po
    │   ├── migrations # スキーマのマイグレーション
    │   │   ├── 0001_initial.py
    │   │   └── __init__.py
    │   ├── models.py # モデル
    │   ├── signals.py # モデルのアクションから実行させたいフック処理やサブプロセスで実行した処理
    │   ├── static # モジュールの静的ファイルはGCSを使う
    │   │   ├── css
    │   │   │   ├── uikit-core-rtl.css
    │   │   │   ├── uikit-core-rtl.min.css
    │   │   │   ├── uikit-core.css
    │   │   │   ├── uikit-core.min.css
    │   │   │   ├── uikit-rtl.css
    │   │   │   ├── uikit-rtl.min.css
    │   │   │   ├── uikit.css
    │   │   │   └── uikit.min.css
    │   │   ├── img
    │   │   │   └── photo.jpg
    │   │   ├── js
    │   │   │   ├── components
    │   │   │   │   ├── countdown.js
    │   │   │   │   ├── countdown.min.js
    │   │   │   │   ├── filter.js
    │   │   │   │   ├── filter.min.js
    │   │   │   │   ├── lightbox-panel.js
    │   │   │   │   ├── lightbox-panel.min.js
    │   │   │   │   ├── lightbox.js
    │   │   │   │   ├── lightbox.min.js
    │   │   │   │   ├── notification.js
    │   │   │   │   ├── notification.min.js
    │   │   │   │   ├── parallax.js
    │   │   │   │   ├── parallax.min.js
    │   │   │   │   ├── slider-parallax.js
    │   │   │   │   ├── slider-parallax.min.js
    │   │   │   │   ├── slider.js
    │   │   │   │   ├── slider.min.js
    │   │   │   │   ├── slideshow-parallax.js
    │   │   │   │   ├── slideshow-parallax.min.js
    │   │   │   │   ├── slideshow.js
    │   │   │   │   ├── slideshow.min.js
    │   │   │   │   ├── sortable.js
    │   │   │   │   ├── sortable.min.js
    │   │   │   │   ├── tooltip.js
    │   │   │   │   ├── tooltip.min.js
    │   │   │   │   ├── upload.js
    │   │   │   │   └── upload.min.js
    │   │   │   ├── uikit-core.js
    │   │   │   ├── uikit-core.min.js
    │   │   │   ├── uikit-icons.js
    │   │   │   ├── uikit-icons.min.js
    │   │   │   ├── uikit.js
    │   │   │   └── uikit.min.js
    │   │   ├── manifest.json
    │   │   ├── package.json
    │   │   └── yarn.lock
    │   ├── templates # モジュールのテンプレート
    │   │   ├── _base.html
    │   │   ├── _footer.html
    │   │   ├── _global_menu.html
    │   │   ├── _input_alert.html
    │   │   ├── dashboard.html
    │   │   ├── free-content.html
    │   │   ├── index.html
    │   │   ├── login.html
    │   │   ├── logout.html
    │   │   ├── paid-content.html
    │   │   └── registration.html
    │   ├── templatetags # モジュールのカスタムテンプレートタグ
    │   │   ├── __init__.py
    │   │   └── customtag.py
    │   ├── tests.py # モジュールのテスト
    │   ├── urls.py # モジュールのルーティング
    │   └── views.py # モジュールのコントローラー
    ├── asgi.py # asgi
    ├── settings.py # プロジェクトの設定
    ├── urls.py # プロジェクトのルーティング
    └── wsgi.py # wsgi

デザインリソースはヒアリングに応じてフレームワークを変えています。特に指定がなければUIKITを使うことが多いです。

プロジェクトを始める

この記事はDjangoをGCPにデプロイする前提で開発する場合の実践的な内容になっています。GCPを使うことを前提としておりますので、まずはGCPにプロジェクトを作成し、必要な前提条件を満たしましょう。アカウント作成、支払情報設定の説明は省略します。

Googleアカウントを作成、もしくは既存アカウントでGCPプロジェクトを作成する
Google Cloud Storageを利用するため支払情報を設定する
IAMでローカル環境でストレージへの読み書き、一括アップロードを行うためのサービスアカウントを作成して認証鍵を保存する
Storage, CloudBuild, Container Registry, CloudRun, CloudSQLのAPIを有効にする
IAMでCloudBuildのデフォルトアカウントに必要な権限を移譲する

IAMでGoogle Cloud Storage(GCS)へローカルから接続するためのサービスアカウントを作る

GCPの「IAMと管理」からサービスアカウントを選択して、役割に適当な名前をつけてサービスアカウントを作成しましょう。作成したアカウントがリストに表示されるので、メニューを押して鍵を作成でJSONを選択します。続いて、左メニューから「IAM」を選択して、アカウントリストからさきほど作成したアカウントの編集ボタンを押し、役割を「ストレージの管理者」に設定すれば、ダウンロードしたサービスアカウントファイルにGCSへの権限が付与されているます。後ほどプロジェクト内で使うので、ここでは便宜上「iam.json」で保存しておいてください。

IAMでローカル環境でストレージへの読み書き、一括アップロードを行うためのサービスアカウントを作成して認証鍵を保存する

GCP製品を利用するためのAPIを有効化する

GCPメニューから必要な製品を選択すると、APIが有効でない場合は「有効にする」と表示されるので、必要なAPIの製品のリンクを押して、有効化しましょう。

Storage, CloudBuild, Container Registry, CloudRun, CloudSQLのAPIを有効にする

CloudBuildでコンテナイメージの作成、CloudRunへデプロイできるように権限を付与する

さきほど開いた「IAMと管理」の画面にCloudBuildに割当てられたサービスアカウントがあるので、権限を追加しましょう。

IAMでCloudBuildのデフォルトアカウントに必要な権限を移譲する

「CloudRun管理者」「CoudRunサービスエージェント」「ストレージ管理者」「AppEngine管理者」「プロジェクト編集者」を付与して保存しましょう。

Djangoのプロジェクトを作成する

準備お疲れさまでした🤯
これでGCPへデプロイする準備が整ったので、GCP向けのDjangoプロジェクトを作成しましょう。まずはプロジェクトのディレクトリを作成して、pipenvで環境を構築します。この記事は複数回に渡って、ハンズオン形式で進めるため、Gitにコードを用意していますので、以下からクローンしてください。

ksh3/django-startproject
Contribute to ksh3/django-startproject development by creating an account on GitHub.


コードの内容については複数回に渡って、実践で効率よく開発するためのノウハウと、無駄のない実装方法について解説します。興味がありましたら読んで頂けると嬉しいです。

※このプロジェクトはGCPのストレージと以下のライブラリに依存しています。

# Pipfile

[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[scripts]

[dev-packages]
flake8 = "*"
ipython = "*"
pytest = "*"
pytest-django = "*"
autopep8 = "*"
factory_boy = "*"

[packages]
django-storages = {extras = ["google"],version = "*"}
pytz = "*"
Django = "*"
django-extensions = "*"
uwsgi = "*"
psycopg2-binary = "*"
bcrypt = "*"
uvicorn = "*"
google-auth = "*"
django-widget-tweaks = "*"

[requires]
python_version = "3.7"

Djangoの説明は端折って、CloudBuildによるCI/CDとCloudRunへのデプロイについて知りたい方は以下の記事を参考にしてください。

速くて軽量なAPI、Starletteを使ってGCPにデプロイする Part4 | loFT LLC | Python+Django+GCPが得意な開発会社 | 東京 武蔵野市
loFT LLC | Python+Django+GCPが得意な開発会社 | 東京 武蔵野市 | 今回はPythonの便利なテストライブラリpytestを使ったテストを作成します。Starletteにテスト用のクライアントは実装されているのでそれを使って、前回追加した機能のテストを行い、テストが無事に通ったらGCPのCloudBuildを使ってCI/CDを行い、CloudRunへデプロイするまでを解説しています。

関連記事

python / Django
作成日:2020/3/5 更新日:2020/3/6
DjangoでGCPを活用した実践的な開発 Part1 [ 3.x対応 ]
Djangpの経験が8年ほどになる筆者が、Djangoを使った実践的な開発について、筆者が用意したGoogle Cloud Platformに最適化したDjangoのGitコードを使いながら、ユースケースを挙げて解説しています。

ポリシー

この記事のすべてまたは一部の複製は、著作権者の同意なしでは禁止されています。 引用については著者名と記事のURLが表示されている場合に限り認められます。