使用Docker+Django+Nginx+MySQL+Gunicorn进行环境搭建,支持M1Mac
前提
-
- フレームワークはDjango
-
- DBはMySQL
-
- アプリケーションサーバはGunicorn
-
- WebサーバはNginx
-
- DB側のコンテナ名はmysql、django側のコンテナ名はapp、nginx側はwebにします
-
- 開発用はdocker-compose.yml(Django+MySQL)として作成します
-
- 本番用はdocker-compose.prod.yml(Django+MySQL+Nginx)として作成します
- 作成するプロジェクト名はdjangopjにしていますが別のプロジェクト名で作成する際はdjangopjと置き換えて作成してください
总结
如标题所示,
-
- Django
-
- MySQL
-
- Nginx
Gunicorn
我将解释如何创建一个使用容器开发环境的方法,同时使用requirements.txt来进行软件包管理。
另外,在文章的后半部分作为应用篇
-
- Poetryを使った開発環境の構築方法
- Pydanticを使った環境変数の管理方法
我将解释关于这个问题,并希望首次使用Docker的人先理解基本的构建方法,使用requirements.txt进行挑战。
目录结构
请按照以下方式创建初始目录结构:
创建一个名为”containers”的文件夹,并在其中创建”Django”、”MySQL”和”Nginx”文件夹。
另外,在”Nginx”文件夹中也创建一个名为”conf.d”的文件夹。
tree
.
├── containers
│ ├── django
│ │ └── Dockerfile
│ ├── mysql
│ │ ├── Dockerfile
│ │ ├── init.sql
│ │ └── my.cnf
│ └── nginx
│ ├── Dockerfile
│ └── conf.d
│ └── default.conf
├── .gitignore
├── .env
├── .env.prod
├── entrypoint.sh
├── docker-compose.prod.yml
├── docker-compose.yml
└── requirements.txt
创建文件
-
- DjangoのDockerfile
-
- MySQLのDockerfile
-
- NginxのDockerfile
-
- my.cnf(MySQL用の設定ファイル)
-
- init.sql(MySQLのユーザに権限を付与)
-
- default.conf(Nginx用の設定ファイル)
-
- docker-compose.yml(開発用)
-
- docker-compose.prod.yml(本番用)
-
- requirements.txt(ざっくり言うとRailsでいうGemfileにあたる)
-
- .env(開発用の環境変数の設定ファイル)
-
- .env.prod(本番用の環境変数の設定ファイル)
-
- entrypoint.sh(Djangoのコマンドを実行する用のシェルスクリプト)
- .gitignore
以下是以順序進行說明的創建方法。
为什么要将开发环境和生产环境分开?
如果使用Django+MySQL+Nginx的配置进行开发,则由于Nginx仅具有显示静态文件的功能,因此必须每次重新启动容器才能反映View和Model的更改(换句话说,无法进行热重载,开发效率较低)。
因此,开发可以在Django+MySQL容器中进行,而在生产环境中,可以通过Nginx端口来查看页面。实际上,在生产环境中,我们会使用AWS的ECS或Fargate等服务,但在本文中,我也会介绍在本地使用的开发和生产环境的Dockerfile和docker-compose.yml的编写方法。
请为每个文件填写必要的代码。
Dockerfile(Django)-Dockerfile文件(Django)
# Pythonのイメージを指定
FROM python:3
# PYTHONDONTWRITEBYTECODEとPYTHONUNBUFFEREDはオプション
# pycファイル(および__pycache__)の生成を行わないようにする
ENV PYTHONDONTWRITEBYTECODE=1
# 標準出力・標準エラーのストリームのバッファリングを行わない
ENV PYTHONUNBUFFERED=1
# コンテナのワークディレクトリを/codeに指定
WORKDIR /code
# ローカルのrequirements.txtをコンテナの/codeフォルダ直下に置く
COPY requirements.txt /code/
# コンテナ内でpipをアップグレード
RUN pip install --upgrade pip
# pip install -r requirements.txtを実行
RUN pip install -r requirements.txt
# ソースコードをコンテナにコピー
COPY . /code/
# entrypoint.shに実行権限を付与
RUN chmod 755 entrypoint.sh
MySQL是一种开源的关系型数据库管理系统。
# MySQL8系のイメージを指定
FROM mysql:8.0
# MySQLのローカルの設定ファイルをコンテナにコピー
COPY containers/mysql/my.cnf /etc/mysql/conf.d/my.cnf
# init.sqlをコンテナの/docker-entrypoint-init.db.dと共有
COPY containers/mysql/init.sql /docker-entrypoint-initdb.d
Nginx — 引擎执行 — 一款流行的高性能Web服务器。
FROM nginx:1.21-alpine
# ローカルのdefault.confをコンテナにコピー
COPY containers/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf
my.cnf(MySQL的配置文件)
# MySQLサーバーへの設定
[mysqld]
# 文字コード/照合順序の設定
character_set_server=utf8mb4
collation_server=utf8mb4_bin
# タイムゾーンの設定
default_time_zone=SYSTEM
log_timestamps=SYSTEM
# MySQL8.0以上用のデフォルト認証プラグインの設定
default_authentication_plugin=caching_sha2_password
# mysqlオプションの設定
[mysql]
# 文字コードの設定
default_character_set=utf8mb4
# mysqlクライアントツールの設定
[client]
# 文字コードの設定
default_character_set=utf8mb4
创建.sql
-- MYSQL_USERに権限を付与
-- 今回はdjangoというユーザを指定
GRANT ALL PRIVILEGES ON *.* TO 'django'@'%';
FLUSH PRIVILEGES;
默认.conf(适用于Nginx的配置文件)
# Django(Gunicorn)の8000番ポートとつなぐ
upstream django {
# サーバにDjangoのコンテナ名を指定。今回はapp
# ポートはDjangoのコンテナの8000番ポート
server app:8000;
}
server {
# HTTPの80番ポートを指定
listen 80;
server_name 0.0.0.0;
# プロキシ設定
# 実際はNginxのコンテナにアクセスしてるのをDjangoにアクセスしてるかのようにみせる
location / {
proxy_pass http://django;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
}
# djangoの静的ファイル(HTML、CSS、Javascriptなど)を管理
location /static/ {
alias /static/;
}
}
docker-compose.yml(用于开发)
# docker-composeのバージョンを指定
version: "3.9"
# db(MySQL),app(Django)のコンテナを作成
services:
db:
# コンテナ名をmysqlに設定
container_name: mysql
# MySQLのDockerfileをビルドする
build:
# ビルドコンテキストはカレントディレクトリ
context: .
dockerfile: containers/mysql/Dockerfile
# M1チップでも動くように
# Intel Macの場合あってもなくても動く
platform: linux/x86_64
# DBのボリュームを指定
# ローカルの/data/dbをコンテナの/var/lib/mysqlにマウントする
volumes:
- db_data:/var/lib/mysql
# 環境変数を.envを使って設定
env_file:
- .env
# DBのコンテナのヘルスチェックを行う
# mysqladmin(MySQLサーバーの管理を行うクライアントを使ってDBコンテナ自身(127.0.0.1)にpingを送ってヘルスチェックを行う
healthcheck:
test: mysqladmin ping -h 127.0.0.1 -u$$MYSQL_USER -p$$MYSQL_PASSWORD
# ヘルスチェックのインターバルは10秒
interval: 10s
# タイムアウト時間は10秒
timeout: 10s
# リトライ回数は3回
retries: 3
# ヘルスチェックが失敗しても無視する時間は30秒
start_period: 30s
app:
# コンテナ名をappに設定
container_name: app
# DjangoのDockerfileをビルドする
build:
# ビルドコンテキストはカレントディレクトリ
context: .
dockerfile: containers/django/Dockerfile
# ボリュームを指定
# ローカルのカレントディレクトリをコンテナの/codeにマウントする
# ローカルの/staticをコンテナの/staticにマウントする
volumes:
- .:/code
- ./static:/static
# ローカルの8000番ポートとコンテナの8000番ポートをつなぐ
ports:
- "8000:8000"
# シェルスクリプトを実行
command: sh -c "/code/entrypoint.sh"
# 環境変数を.envを使って設定
env_file:
- .env
# 先にdbを起動してからappを起動する
depends_on:
db:
# dbのヘルスチェックが終わってからappを起動させる
condition: service_healthy
volumes:
db_data:
static:
docker-compose.prod.yml(用于生产环境)
version: "3.9"
# db(MySQL),app(Django),web(Nginx)のコンテナを作成
services:
db:
container_name: mysql
build:
context: .
dockerfile: containers/mysql/Dockerfile
platform: linux/x86_64
volumes:
- db_data:/var/lib/mysql
# コンテナ内の環境変数を.env.prodを使って設定
env_file:
- .env.prod
healthcheck:
test: mysqladmin ping -h 127.0.0.1 -u$$MYSQL_USER -p$$MYSQL_PASSWORD
interval: 10s
timeout: 10s
retries: 3
start_period: 30s
app:
container_name: app
build:
context: .
dockerfile: containers/django/Dockerfile
volumes:
- .:/code
- ./static:/static
# 8000番ポートをNginx側が接続できるよう開く
expose:
- "8000"
command: sh -c "/code/entrypoint.sh"
# コンテナ内の環境変数を.env.prodを使って設定
env_file:
- .env.prod
depends_on:
db:
# dbのヘルスチェックが終わってからappを起動させる
condition: service_healthy
web:
# コンテナ名をwebに指定
container_name: web
# NginxのDockerfileをビルドする
build:
# ビルドコンテキストはカレントディレクトリ
context: .
dockerfile: containers/nginx/Dockerfile
# ボリュームを指定
# ローカルの/staticをコンテナの/staticにマウントする
volumes:
- ./static:/static
# ローカルの80番ボートをコンテナの80番ポートとつなぐ
ports:
- "80:80"
# 先にappを起動してからwebを起動する
depends_on:
- app
volumes:
db_data:
static:
要求文本
-
- Django
-
- mysqlclient
- Gunicorn
我将在 Django 容器中安装。
Django>=3.0,<4.0
mysqlclient>=1.3.6,<2.0
gunicorn>=19.9.0,<20.0
.env (development purpose)
地圖環境(開發目的地)
写入MySQL的root用户密码等信息到docker-compose.yml或Django的settings.py是危险的,因此我们使用.env文件。
这次是用于开发的,所以需要将DEBUG设置为True。
由于有.gitignore(稍后会提到),.env文件不会上传到GitHub。
这次的内容如下所示。
MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=django-db
MYSQL_USER=django
MYSQL_PASSWORD=django
# SECRET_KEYに任意の値を入力
SECRET_KEY=django
ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
# 開発環境のためTrue
DEBUG=True
生产环境配置文件 (本番用)
接下来,我们将设置用于生产环境的环境变量。
实际上,生产环境的环境变量通常保存在AWS的参数存储或其他类似的地方,但在本次中我们将使用.env.prod文件。
由于这是生产环境,所以需要将DEBUG设置为False。
MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=django-db
MYSQL_USER=django-prod
MYSQL_PASSWORD=django-prod
# SECRET_KEYについては本番環境では推測されない値に変更しておきましょう
SECRET_KEY=xdmjx=9l@x)-jitznpb^%yjn6h=7g)$%e8_+1s)o+8o79csa4d
ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
# 開発環境のためFalse
DEBUG=False
入口脚本.sh
在这里定义了用于收集Django迁移、管理界面以及Django Rest Framework静态文件的命令。
此外,由于在开发环境和生产环境中使用的命令不同,将它们写在一个Shell脚本中不仅可以避免在docker-compose.yml中重复编写类似的描述,还可以提高可读性。
#!/bin/sh
因为shebang是用来执行shell脚本的咒语,所以不要忘记写上。
#!/bin/sh
python manage.py makemigrations --noinput
python manage.py migrate --noinput
python manage.py collectstatic --noinput
# 環境変数のDEBUGの値がTrueの時はrunserverを、Falseの時はgunicornを実行します
if [ $DEBUG = "True" ]
then
python manage.py runserver 0.0.0.0:8000
else
# gunicornを起動させる時はプロジェクト名を指定します
# 今回はdjangopjにします
gunicorn djangopj.wsgi:application --bind 0.0.0.0:8000
fi
忽略文件
从GitHub的官方网站创建Python的.gitignore文件
由于.gitignore中没有.env.prod,因此需要添加
另外,在实际开发中,migration和static不需要受Git管理,因此也需要添加到.gitignore中。
# Environments
.env
# .env.prodを.gitignoreする
.env.prod
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
・・・
# ignore static files
static/
# ignore migration files
migrations/
让我们开始构建图像,直到展示Django的页面。
這次我們希望通過訪問Nginx的端口來顯示Django的界面,因此我們將使用docker-compose.prod.yml(生產環境)。
如果是在開發中,請將指定的文件替換為docker-compose.yml,然後訪問8000端口。
在docker-compose中创建Docker镜像(初次)
在创建新项目时,请指定项目名称和要创建的目录,并执行以下命令。本次将在当前目录下创建一个名为djangopj的项目。
docker-compose -f docker-compose.prod.yml run app django-admin startproject djangopj .
运行后,本地目录结构如下所示:
由于data/db内的文件非常多,因此省略显示。
tree
.
├── containers
│ ├── django
│ │ └── Dockerfile
│ ├── mysql
│ │ ├── Dockerfile
│ │ ├── init.sql
│ │ └── my.cnf
│ └── nginx
│ ├── Dockerfile
│ └── conf.d
│ └── default.conf
├── djangopj
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── .env
├── .env.prod
├── .gitignore
├── docker-compose.prod.yml
├── docker-compose.yml
├── entrypoint.sh
├── manage.py
├── requirements.txt
└── static
如果已经有一个项目存在的话
GitHubにあるソースコードをcloneする場合など、プロジェクトが作成済みの時は以下のコマンドを実行します
docker-compose -f docker-compose.prod.yml build
settings.pyのDATABASESを変更
Django的默认数据库是SQlite,因此需要将其更改为MySQL。
from pathlib import Path
# osのモジュールをインポート
import os
# [・・・]
# SECRET_KEYを.envから取得
SECRET_KEY = os.environ.get("SECRET_KEY")
# DEBUGを.envから取得
# envファイルにTrue、Falseと書くとDjangoがString型と認識してしまいます
# os.environ.get("DEBUG") == "True"を満たすとboolean型のTrueになり、
# env内のDEBUGがTrue以外ならFalseになります
DEBUG = os.environ.get("DEBUG") == "True"
# ALLOWED_HOSTSを.envから取得
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS").split(" ")
# [・・・]
# MySQLのパラメータを.envから取得
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
# コンテナ内の環境変数をDATABASESのパラメータに反映
"NAME": os.environ.get("MYSQL_DATABASE"),
"USER": os.environ.get("MYSQL_USER"),
"PASSWORD": os.environ.get("MYSQL_PASSWORD"),
"HOST": "db",
"PORT": 3306,
"OPTIONS": {
"init_command": "SET sql_mode='STRICT_TRANS_TABLES'",
},
}
}
# [・・・]
# 言語を日本語に設定
LANGUAGE_CODE = "ja"
# タイムゾーンをAsia/Tokyoに設定
TIME_ZONE = "Asia/Tokyo"
# [・・・]
# STATIC_ROOTを設定
# Djangoの管理者画面にHTML、CSS、Javascriptが適用されます
STATIC_ROOT = "/static/"
STATIC_URL = "/static/"
コンテナを起動
コンテナをデタッチモードで起動する
デタッチモード起動することでコンテナの中に入らずにバックグラウンドで起動させることができます
docker-compose -f docker-compose.prod.yml up -d
让我们尝试访问 127.0.0.1:80。
ホストからNginxのポートに接続します
ブラウザにアクセスし、NginxのポートからDjangoのポートへアクセスできます
以下のページが表示されます
请访问 127.0.0.1:80/admin,并且成功地在以下页面上显示出来完成了。
如果将DEBUG设置为True,则会显示下面的图像。
如果没有显示类似上述的画面,则可能是由于MySQL容器在初次启动时没有成功启动
docker-compose -f docker-compose.prod.yml down
停止容器后
docker compose -f docker-compose.prod.yml up -d
请再次启动容器试试看。
当无法连接时,请再次在本地环境中执行以下命令:docker compose -f docker-compose.prod.yml up -d。
连接到上游时发生失败(111:连接被拒绝)。
需要重新审视Nginx的default.conf文件。
upstream django {
server app:8000;
}
请使用docker ps命令确认是否指定了Django容器名称,因为经常会出现在服务器后面错误地指定了Nginx容器名称而不是Django的情况。
django.db.utils.OperationalError:严重错误:数据库不存在
如果.env文件中的MYSQL_DATABASE名称不符合命名规则,可能会出现上述错误。请从以下网站确认您设置的数据库名称是否符合MySQL的命名规则。
利用诗歌构建容器环境
可以更方便地管理和发布Python的软件包
-
- コマンド一つでパッケージをインストールできる
-
- パッケージ間の依存関係を自動で解消してくれる
- Blackなどの設定を共通のファイルにまとめることができる
如果可能的话,请在实际的开发现场中学会使用它,因为它非常方便。请参考以下文章以获取详细信息。
目录结构
关于目录结构,requirements.txt将被替换为pyproject.toml。
tree
.
├── containers
│ ├── django
│ │ └── Dockerfile
│ ├── mysql
│ │ ├── Dockerfile
│ │ ├── init.sql
│ │ └── my.cnf
│ └── nginx
│ ├── Dockerfile
│ └── conf.d
│ └── default.conf
├── .gitignore
├── .env
├── .env.prod
├── entrypoint.sh
├── docker-compose.prod.yml
├── docker-compose.yml
└── pyproject.toml
pyproject.toml 的中文释义。
和requirements.txt一样
[tool.poetry.dependencies]
安装以下的软件包。
[tool.poetry]
name = "api"
version = "0.1.0"
description = "api"
authors = ["shun198"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.11"
Django = "^4.2.3"
djangorestframework = "^3.14.0"
mysqlclient = "^2.1.1"
gunicorn = "^20.1.0"
Dockerfile(Django)
FROM python:3.11
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /code
COPY pyproject.toml /code/
# Initialize python project with Poetry
RUN pip install --upgrade pip && pip install poetry
RUN poetry install
# entrypoint.shに実行権限を付与
RUN chmod 755 entrypoint.sh
创建新项目
在创建新项目时,请输入以下命令。
docker-compose -f docker-compose.prod.yml run app poetry run django-admin startproject djangopj .
如果能达到以下目标,就算是成功了。
tree
.
├── containers
│ ├── django
│ │ └── Dockerfile
│ ├── mysql
│ │ ├── Dockerfile
│ │ ├── init.sql
│ │ └── my.cnf
│ └── nginx
│ ├── Dockerfile
│ └── conf.d
│ └── default.conf
├── djangopj
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── .env
├── .env.prod
├── .gitignore
├── docker-compose.prod.yml
├── docker-compose.yml
├── entrypoint.sh
├── manage.py
├── poetry.lock
├── pyproject.toml
└── static
使用Pydantic管理环境变量
Pydantic是一个使用Python类型注解提供类型提示并帮助简化验证错误的库。它可以用于环境变量的设置。
-
- 使用している環境変数の型が直感的にわかる
- 環境変数関連でエラーが発生するとPydanticのエラーが出るのでデバッグが容易になる
有许多好处
pyproject.toml 文件
我将安装Pydantic。
[tool.poetry]
name = "djangopj"
version = "0.1.0"
description = "api"
authors = ["shun198"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.11"
Django = "^4.2.3"
djangorestframework = "^3.14.0"
mysqlclient = "^2.1.1"
gunicorn = "^20.1.0"
pydantic = "^1.10.6"
设置环境变量
这次我们将在djangopj中创建一个environment.py文件。
tree
.
├── containers
│ ├── django
│ │ └── Dockerfile
│ ├── mysql
│ │ ├── Dockerfile
│ │ ├── init.sql
│ │ └── my.cnf
│ └── nginx
│ ├── Dockerfile
│ └── conf.d
│ └── default.conf
├── djangopj
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── environment.py
│ ├── urls.py
│ └── wsgi.py
├── .env
├── .env.prod
├── .gitignore
├── docker-compose.prod.yml
├── docker-compose.yml
├── entrypoint.sh
├── manage.py
├── poetry.lock
├── pyproject.toml
└── static
环境.py
请按照以下方式设置环境变量。
"""環境変数定義用のモジュール"""
from pydantic import BaseSettings
class DjangoSettings(BaseSettings):
"""Django関連の環境変数を設定するクラス"""
SECRET_KEY: str = "secretkey"
ALLOWED_HOSTS: str = "localhost 127.0.0.1 [::1] back web"
MYSQL_DATABASE: str = "django"
MYSQL_USER: str = "django"
MYSQL_PASSWORD: str = "django"
MYSQL_HOST: str = "db"
MYSQL_PORT: int = 3306
django_settings = DjangoSettings()
如果导入django_settings如下所示,则可以设置环境变量:
使用编辑器的自动补全功能,您可以无误地设置相应的环境变量,从而加快开发速度。
此外,您可以像上面那样设置默认值,
但是,在启动MySQL容器时,需要MySQL环境变量在docker-compose.yml文件中。
MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=django-db
MYSQL_USER=django
MYSQL_PASSWORD=django
from .environment import django_settings
# SECRET_KEYを.envから取得
SECRET_KEY = django_settings.SECRET_KEY
# ALLOWED_HOSTSを.envから取得
ALLOWED_HOSTS = django_settings.ALLOWED_HOSTS.split()
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
# コンテナ内の環境変数をDATABASESのパラメータに反映
"NAME": django_settings.MYSQL_DATABASE,
"USER": django_settings.MYSQL_USER,
"PASSWORD": django_settings.MYSQL_PASSWORD,
"HOST": django_settings.MYSQL_HOST,
"PORT": django_settings.MYSQL_PORT,
"OPTIONS": {
"init_command": "SET sql_mode='STRICT_TRANS_TABLES'",
},
}
}
以下是以上的内容。
介紹這篇文章
我还写了以下的文章,如果您能阅读并觉得不错,我会非常高兴。
请提供引用的来源。