学习Docker时,我采取了这样的方法
首先
这是我在个人开发的两年中,学习Docker的最后阶段时参考了『Dockerizing Django with Postgres, Gunicorn, and Nginx』这个示范网站的记录。如果不是非常强的人也可以从这个网站中找到自己卡在哪里,并获得一些参考。
目标读者
本课程针对Django用户和Docker初学者。希望可以帮助个人开发者快速掌握Docker的使用技巧。
开发环境
Windows11Pro(2台!)
Homeでは試していませんが、たぶん問題ないです。
問題解決能力が低い初心者の時こそ、PC複数台持つことをおススメします。コスパ重視、中古でOKです。いつでもOSクリーンインストールして構わないマシンがあると心強いです。
この記事は一度で作業完了しているように書いていますが、実際はたくさんハマっています。英語サイトをググって色々試しても解決に至らないケースでは、別のPCに同じ方法で環境設定して、同じ作業を実施したところ正常動作させることができました。もうなんなの
WSL2 Ubuntu
インストール手順は色々ありそうですが、どれでも大丈夫だと思います。
VScode
VScodeターミナルで WSL2 Ubuntu bashを操作できる状態にしてください。環境設定はググってわかりやすいサイトを探してください。
Python
WSL2 Ubuntu に始めから入っているPythonは使わず、新しいバージョンのPythonを追加インストールしてそちらを使うようにしてください。
私はお手本と同じVer.3.9.6を入れました。
通过亲身经历从零开始构建的工作
『使用Docker将Django与Postgres、Gunicorn和Nginx容器化』(以下简称示例网站)从创建项目目录开始,一直到最后,都会一直跟着你,没有跳跃内容。太棒了。
当我学习时,我记录了与示例网站不同的操作、疑问、问题解决等,按照示例网站的进度进行记录。标题变为相应项目的链接。
由于这里没有重复解释详细的部分,请先查看链接到示例网站的页面。
虽然有很多地方是直接照搬示例网站,这是为了不来回跳转,以便掌握工作流程。(也是为了自己的工作流程笔记起草。。)
在阅读英文网站时,我推荐使用DeepL的应用版本。在选择文字后,按下Ctrl键并连续按两次C键,它会立即为你翻译。这是一个很了不起的时代。如果有需要,你也可以尝试使用Google翻译这个示例网站,它也很容易理解。
项目建立
在主目录中准备一个工作场所(并将其命名为”works”)。
~$ mkdir works
~$ cd works
准备项目目录(命名为hoge-on-docker)。
~/works$ mkdir hoge-on-docker
~/works$ cd hoge-on-docker
~/works/hoge-on-docker$
由于路径很长,所以在此之后的说明中
~/*$
表示。
准备一个app目录来存放Django应用程序。
~/*$ mkdir app
~/*$ cd app
Python的虚拟化
~/*/app$ python3 -m venv .
~/*/app$ . .venv/bin/activate
可能会在~/*$的状态下重新开始工作,因此虚拟化命令是 . ./app/.venv/bin/activate。当然,按照参考网站上的写法,source ./app/.venv/bin/activate也可以。只是想记录下有两种写法。
安装Django
由于缺乏专业的安全知识,我决定将Django版本更新到最新的版本。我至少想使用最新版本的框架。
(.venv)~/*/app$ pip install --upgrade pip
(.venv)~/*/app$ pip install django==4.1
(.venv)~/*/app$ django-admin startproject config .
(.venv)~/*/app$ python manage.py migrate
(.venv)~/*/app$ python manage.py runserver
请在浏览器中访问 http://localhost:8000/。如果显示火箭,就代表正常。要停止请按下 ctrl + c。
在此之前,根据《Django for Beginners》的思想,我们将存储settings.py等文件的文件夹命名为config。
在切换到Docker开发时,我们也考虑将项目名称(本例中为hoge)作为文件夹名称,但最终还是决定使用config。另外,存储Django应用程序的文件夹名称我们选择按照示例的app命名。原因如下:将目录命名为app和config的理由:
在Docker开发中,项目的根目录下会有很多docker-compose.yml等文件。将“将Django应用程序放在app文件夹下”作为自己的规则,可以更容易找到。
可以统一docker-compose.yml等文件的描述。
如果将app文件夹的下一层命名为config,可能不太容易理解这个Django应用程序是什么,但是通过查看项目的目录名就可以明白,所以选择了这样做。
通过统一使用config,后面出现的nginx.conf的描述也可以固定。
在应用程序的顶层目录下准备一个requirements.txt文件,并在其中写入django==4.1。
由于使用了基于Python Alpine镜像的Docker镜像构建过程中会安装Django的环境,所以不想将多余的包信息包含在requirements.txt中。所需要的是安装指令的清单,而不是已安装的包清单。
由于我们使用Postgres作为数据库,因此需要删除db.sqlite3文件。在构建映像时,将复制应用程序中的内容,因此需要预先删除不必要的内容。虽然在后续的操作中可能会重新创建,但如果需要的话可以再次删除。可能值得考虑查阅.dockerignore文件的使用方法并将其整合进来。你说你不去做的话会很懊悔。
Docker 是一种容器化平台。
在应用程序的根目录下创建一个 Dockerfile。(请查看参考网站以了解详细信息。)
# pull official base image
FROM python:3.9.6-alpine
# set work directory
WORKDIR /usr/src/app
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# install dependencies
RUN pip install --upgrade pip
COPY ./requirements.txt .
RUN pip install -r requirements.txt
# copy project
COPY . .
因为文件名写成了 dcokerfile 所以卡住了。如果是在VScode上,只要文件名正确,图标就会变成鲸鱼的样子。如果不仔细看的话…
在项目根目录(hoge-on-docker的直接子目录)下创建docker-compose.yml文件(与app位于同一层级)。
version: '3.8'
services:
web:
build: ./app
command: python manage.py runserver 0.0.0.0:8000
volumes:
- ./app/:/usr/src/app/
ports:
- 8000:8000
env_file:
- ./.env.dev
随后,在项目根目录创建.env.dev文件。
DEBUG=1
SECRET_KEY=〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇
DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
将下面的内容插入到 SECRET_KEY 的右边, 内容是位于 app/config 目录下的 settings.py 文件中 SECRET_KEY 的字符串, 该字符串用单引号 ‘ ‘ 括起来(不包括两端的引号)。
在settings.py文件中添加或修改
# 冒頭に追記
import os
# 下記3項目の右辺を変更
SECRET_KEY = os.environ.get("SECRET_KEY")
DEBUG = int(os.environ.get("DEBUG", default=0))
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(" ")
构建形象
移动到项目的根目录下。
~/*/app$ cd ..
使用docker-compose.yml文件来构建镜像
~/*$ docker-compose build
生成容器,后台运行(守护进程)
~/*$ docker-compose up -d
访问 http://localhost:8000/。
~/*$ docker-compose logs -f
Postgres 数据库
在本地安裝Postgres。在docker-compose.yml檔案中新增數據庫資訊。
version: '3.8'
services:
web:
build: ./app
command: python manage.py runserver 0.0.0.0:8000
volumes:
- ./app/:/usr/src/app/
ports:
- 8000:8000
env_file:
- ./.env.dev
depends_on:
- db
db:
image: postgres:13.0-alpine
volumes:
- postgres_data:/var/lib/postgresql/data/
environment:
- POSTGRES_USER=hello_django
- POSTGRES_PASSWORD=hello_django
- POSTGRES_DB=hello_django_dev
volumes:
postgres_data:
按照示例保持为 hello_django,但暂时保留此设置。如果将来出现问题,可以在那时更改。
在.env.dev文件中进行补充
DEBUG=1
SECRET_KEY=〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇
DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
SQL_ENGINE=django.db.backends.postgresql
SQL_DATABASE=hello_django_dev
SQL_USER=hello_django
SQL_PASSWORD=hello_django
SQL_HOST=db
SQL_PORT=5432
settings.py 的更改
DATABASES = {
"default": {
"ENGINE": os.environ.get("SQL_ENGINE", "django.db.backends.sqlite3"),
"NAME": os.environ.get("SQL_DATABASE", BASE_DIR / "db.sqlite3"),
"USER": os.environ.get("SQL_USER", "user"),
"PASSWORD": os.environ.get("SQL_PASSWORD", "password"),
"HOST": os.environ.get("SQL_HOST", "localhost"),
"PORT": os.environ.get("SQL_PORT", "5432"),
}
}
Dockerfile的更改
# pull official base image
FROM python:3.9.6-alpine
# set work directory
WORKDIR /usr/src/app
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# install psycopg2 dependencies
RUN apk update \
&& apk add postgresql-dev gcc python3-dev musl-dev
# install dependencies
RUN pip install --upgrade pip
COPY ./requirements.txt .
RUN pip install -r requirements.txt
# copy project
COPY . .
RUN apk update \ && apk add postgresql-dev gcc python3-dev musl-dev
原因是使用了基于alpine的镜像。根据我的记忆,当初尝试使用“slim”镜像作为基础时似乎不需要这样的操作。(我的记忆可能有误)
因为如果不小心做了一些多余的操作会遇到问题,所以对于不熟悉的人来说,最好按照示例使用alpine进行操作。
将以下内容追加到 requirements.txt 文件中。
django==4.1
psycopg2-binary==2.9.1
同时执行镜像构建和容器启动的写法如下。
~/*$ docker-compose up -d --build
迁移
~/*$ docker-compose exec web python manage.py migrate --noinput
如果在这里出现错误,可以参考示范网站进行检查。
如果Postgres正在运行,则应该能够确认默认的Django表已被创建。
~/*$ docker-compose exec db psql --username=hello_django --dbname=hello_django_dev
以下是将=#右边的输入命令。
psql (13.0)
Type "help" for help.
hello_django_dev=# \l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
------------------+--------------+----------+------------+------------+-------------------------------
hello_django_dev | hello_django | UTF8 | en_US.utf8 | en_US.utf8 |
postgres | hello_django | UTF8 | en_US.utf8 | en_US.utf8 |
template0 | hello_django | UTF8 | en_US.utf8 | en_US.utf8 | =c/hello_django +
| | | | | hello_django=CTc/hello_django
template1 | hello_django | UTF8 | en_US.utf8 | en_US.utf8 | =c/hello_django +
| | | | | hello_django=CTc/hello_django
(4 rows)
hello_django_dev=# \c hello_django_dev
You are now connected to database "hello_django_dev" as user "hello_django".
hello_django_dev=# \dt
List of relations
Schema | Name | Type | Owner
--------+----------------------------+-------+--------------
public | auth_group | table | hello_django
public | auth_group_permissions | table | hello_django
public | auth_permission | table | hello_django
public | auth_user | table | hello_django
public | auth_user_groups | table | hello_django
public | auth_user_user_permissions | table | hello_django
public | django_admin_log | table | hello_django
public | django_content_type | table | hello_django
public | django_migrations | table | hello_django
public | django_session | table | hello_django
(10 rows)
hello_django_dev=# \q
看看音量的详细资料。
~/*$ docker volume inspect hoge-on-docker_postgres_data
↑ _postgres_dataの前はプロジェクト名。自分の環境に合わせる。
只要包含以下显示就可以
[
{
"CreatedAt": "2021-08-23T15:49:08Z",
"Driver": "local",
"Labels": {
"com.docker.compose.project": "hoge-on-docker",
"com.docker.compose.version": "1.29.2",
"com.docker.compose.volume": "postgres_data"
},
"Mountpoint": "/var/lib/docker/volumes/hoge-on-docker_postgres_data/_data",
"Name": "hoge-on-docker_postgres_data",
"Options": null,
"Scope": "local"
}
]
在app目录的根目录下添加entrypoint.sh脚本。
#!/bin/sh
if [ "$DATABASE" = "postgres" ]
then
echo "Waiting for postgres..."
while ! nc -z $SQL_HOST $SQL_PORT; do
sleep 0.1
done
echo "PostgreSQL started"
fi
python manage.py flush --no-input
python manage.py migrate
exec "$@"
立即修改 entrypoint.sh 文件的执行权限。
~/*$ chmod +x app/entrypoint.sh
Dockerfile的修改
# pull official base image
FROM python:3.9.6-alpine
# set work directory
WORKDIR /usr/src/app
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# install psycopg2 dependencies
RUN apk update \
&& apk add postgresql-dev gcc python3-dev musl-dev
# install dependencies
RUN pip install --upgrade pip
COPY ./requirements.txt .
RUN pip install -r requirements.txt
# copy entrypoint.sh
COPY ./entrypoint.sh .
RUN sed -i 's/\r$//g' /usr/src/app/entrypoint.sh
RUN chmod +x /usr/src/app/entrypoint.sh
# copy project
COPY . .
# run entrypoint.sh
ENTRYPOINT ["/usr/src/app/entrypoint.sh"]
将内容添加到.env.dev文件中。
DEBUG=1
SECRET_KEY=〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇
DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
SQL_ENGINE=django.db.backends.postgresql
SQL_DATABASE=hello_django_dev
SQL_USER=hello_django
SQL_PASSWORD=hello_django
SQL_HOST=db
SQL_PORT=5432
DATABASE=postgres
建立映像並啟動容器。
~/*$ docker-compose up -d --build
只需用浏览器访问 http://localhost:8000/ ,如果显示了火箭则表示OK。
它介绍了如何仅使用Django来生成和启动容器。只需在构建时不设置环境变量DATABASE=postgres即可实现。命令的书写方式可以参考示例网站。确实,通过这种方式执行时,settings.py将会为sqlite3进行配置。
是否有必要采用这种启动方式呢?目前还想不到。也许在从头开始开发时会遇到这种情况吧?现在不管它,继续前进吧。
独角兽
在将Nginx和Django连接起来时,使用Gunicorn作为中间通信工具。需要在requirements.txt文件中进行追加。
django==4.1
gunicorn==20.1.0
psycopg2-binary==2.9.1
尽管正式服务器使用的是 nginx,但在开发阶段我想要使用 Django 内置服务器。我想要在开发和正式部署中使用不同的 docker-compose。
本番用の docker-compose.prod.yml を作成。docker-compose.yml を複製・変更して作る
本番では、runserverではなくgunicornを起動
本番では、webコンテナでホストとコンテナの連動(バインドマウント)は不要なので削除
本番用設定ファイル .env.prod を用意。開発用の設定ファイル.env.dev を複製・変更して作る
DEBUG を 1 から 0 に変更
SQL_DATABASE を hello_django_dev から hello_django_prod に変更
本番 db用の設定ファイル .env.prod.db を用意
docker-compose.yml 複製直後の docker-compose.prod.yml に含まれている、db イメージ生成に必要なPOSTGRES環境変数を、この .env.prod.db に移行する。(_dev → _prod の書替えを忘れずに)
version: '3.8'
services:
web:
build: ./app
command: gunicorn config.wsgi:application --bind 0.0.0.0:8000
ports:
- 8000:8000
env_file:
- ./.env.prod
depends_on:
- db
db:
image: postgres:13.0-alpine
volumes:
- postgres_data:/var/lib/postgresql/data/
env_file:
- ./.env.prod.db
volumes:
postgres_data:
DEBUG=0
SECRET_KEY=〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇
DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
SQL_ENGINE=django.db.backends.postgresql
SQL_DATABASE=hello_django_prod
SQL_USER=hello_django
SQL_PASSWORD=hello_django
SQL_HOST=db
SQL_PORT=5432
DATABASE=postgres
.env.prod.db的数据库名称不是_dev而是_prod。在使用复制和粘贴时请注意。
POSTGRES_USER=hello_django
POSTGRES_PASSWORD=hello_django
POSTGRES_DB=hello_django_prod
删除容器(使用-v参数也将删除相关卷)。
~/*$ docker-compose down -v
构建本地镜像并启动容器。
~/*$ docker-compose -f docker-compose.prod.yml up -d --build
使用浏览器打开 http://localhost:8000/admin,如果显示管理员登录页面,则表示正常。现在没有CSS样式也可以。一旦完成staticfile的设置,问题将得到解决。
如果出现异常,检查日志。请注意,如果不指定文件名,默认会读取 docker-compose.yml。如果想要查看生产环境容器的详细信息,请不要忘记指定文件名 -f docker-compose.prod.yml。
~/*$ docker-compose -f docker-compose.prod.yml logs -f
生产Dockerfile
需要准备适用于生产的Dockerfile。
在当前的Dockerfile和entrypoint.sh的组合中,每次启动容器都需要进行一次构建,
python manage.py flush --no-input データベースのクリア
python manage.py migrate マイグレート
在本地环境中执行这个没有问题,但是在生产环境中被执行这个就不好了。数据库迁移可以用『没有变更所以不需要』来解决,但是清空数据库则是个大问题。
在应用程序的根目录下准备 entrypoint.prod.sh 这个文件。
#!/bin/sh
if [ "$DATABASE" = "postgres" ]
then
echo "Waiting for postgres..."
while ! nc -z $SQL_HOST $SQL_PORT; do
sleep 0.1
done
echo "PostgreSQL started"
fi
exec "$@"
接下来,改变文件的执行权限。
~/*$ chmod +x app/entrypoint.prod.sh
准备 Dockerfile.prod
为了减少实际图像的容量,使用了Builder和Final两个步骤的结构。采用了Multi-stage Build的方法。
Builder
psycopg2を動かすのに必要なアプリのインストール。
flake8(lint)をインストールし実行してDjangoのapp内のコードに問題が無いかチェック。
pip wheel ***** で requirements.txtのパッケージインストールに必要な wheel群を構築。wheel構築の利点やオプションの解説は、https://kurozumi.github.io/pip/reference/pip_wheel.html を参照。
Final
libpq(PostgreSQLのC言語インタフェース)のインストール。
Builderで使ったrequirements.txtと、Builder で構築したwheel群の取り込み。これを使ってインストール。
entrypoint.prod.shのコピーなど
###########
# BUILDER #
###########
# pull official base image
FROM python:3.9.6-alpine as builder
# set work directory
WORKDIR /usr/src/app
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# install psycopg2 dependencies
RUN apk update \
&& apk add postgresql-dev gcc python3-dev musl-dev
# lint
RUN pip install --upgrade pip
RUN pip install flake8==3.9.2
COPY . .
RUN flake8 --ignore=E501,F401 .
# install dependencies
COPY ./requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /usr/src/app/wheels -r requirements.txt
#########
# FINAL #
#########
# pull official base image
FROM python:3.9.6-alpine
# create directory for the app user
RUN mkdir -p /home/app
# create the app user
RUN addgroup -S app && adduser -S app -G app
# create the appropriate directories
ENV HOME=/home/app
ENV APP_HOME=/home/app/web
RUN mkdir $APP_HOME
WORKDIR $APP_HOME
# install dependencies
RUN apk update && apk add libpq
COPY --from=builder /usr/src/app/wheels /wheels
COPY --from=builder /usr/src/app/requirements.txt .
RUN pip install --no-cache /wheels/*
# copy entrypoint.prod.sh
COPY ./entrypoint.prod.sh .
RUN sed -i 's/\r$//g' $APP_HOME/entrypoint.prod.sh
RUN chmod +x $APP_HOME/entrypoint.prod.sh
# copy project
COPY . $APP_HOME
# chown all the files to the app user
RUN chown -R app:app $APP_HOME
# change to the app user
USER app
# run entrypoint.prod.sh
ENTRYPOINT ["/home/app/web/entrypoint.prod.sh"]
sed命令是“流编辑器”的缩写。根据指定的命令对指定的文件进行处理。按行读取输入,进行文本转换和编辑,然后按行输出。支持正则表达式。
【参考】更改换行符
由于这次想要直接覆盖文件,所以使用-i选项
运行 sed -i ‘s/\r$//g’ $APP_HOME/entrypoint.prod.sh
正则表达式的部分还不够熟练,需要进一步学习。
请将当前的 docker-compose.prod.yml 文件中的 web 项修改为读取 Dockerfile.prod 文件。
web:
build:
context: ./app
dockerfile: Dockerfile.prod
command: gunicorn hello_django.wsgi:application --bind 0.0.0.0:8000
ports:
- 8000:8000
env_file:
- ./.env.prod
depends_on:
- db
准备好了就试一下。先清空并构建,然后执行。
~/*$ docker-compose -f docker-compose.prod.yml down -v
~/*$ docker-compose -f docker-compose.prod.yml up -d --build
我认为会出现这种错误。
executor failed running [/bin/sh -c flake8 --ignore=E501,F401 .]: exit code: 1
ERROR: Service 'web' failed to build : Build failed
flake8是一个代码检查工具。似乎也会涉及到Django自动生成的部分。确实,我对此不太了解,所以只将config内的内容作为检查对象。
在Dockerfile.prod中,
将
RUN flake8 –ignore=E501,F401 .
更改为
RUN flake8 –ignore=E501,F401 ./config。
以后,如果添加了Django应用程序,则应该检查这些目录,但在感到需要之前,只对config进行检查即可,然后继续。(请不要模仿这种随意的态度)
再编译并迁移。需要超过10分钟。太长了。。
~/*$ docker-compose -f docker-compose.prod.yml up -d --build
~/*$ docker-compose -f docker-compose.prod.yml exec web python manage.py migrate --noinput
只需要一个选项,用中文将以下内容重新表述:
在浏览器中访问 http://localhost:8000/admin 并显示即可,无需CSS样式。(尚未添加CSS样式)
多阶段构建使图像数据的容量应该大大减小。(忘记记录前一个状态。)
Nginx ( 或译为”恩增” )
将Nginx引入系统中
在docker-compose.prod.yml文件中进行补充说明
nginx:
build: ./nginx
ports:
- 1337:80
depends_on:
- web
在项目根目录(即hoge-on-docker/)下创建nginx文件夹,并在此文件夹下创建Dockerfile和nginx.conf。
FROM nginx:1.21-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d
upstream config {
server web:8000;
}
server {
listen 80;
location / {
proxy_pass http://config;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}
}
修改docker-compose.prod.yml文件中web端口的配置。原先使用ports参数将其连接到外部,但是通过nginx代理后,只有容器能够连接到web,所以需要改为expose参数。
建议在某个时间点确认无法直接访问http://localhost:8000。
web:
build:
context: ./app
dockerfile: Dockerfile.prod
command: gunicorn hello_django.wsgi:application --bind 0.0.0.0:8000
expose:
- 8000
env_file:
- ./.env.prod
depends_on:
- db
确认动作
~/*$ docker-compose -f docker-compose.prod.yml down -v
~/*$ docker-compose -f docker-compose.prod.yml up -d --build
~/*$ docker-compose -f docker-compose.prod.yml exec web python manage.py migrate --noinput
使用浏览器访问 http://localhost:1337/
Not Found
The requested resource was not found on this server.
如果显示为OK,那就可以了。如果是 http://localhost:1337/admin,就会显示无样式的管理页面登录。
让我们关闭容器吧 (Let’s shut down the container)
~/*$ docker-compose -f docker-compose.prod.yml down -v
静态文件
设置静态文件
在进入本题之前,请在不忘记的情况下更改settings.py中的区域设置。
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'
接下来,在 settings.py 文件中对于静态文件的修改和追加进行处理。
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
只有在开发模式下(即当 DEBUG=1 时),访问 http://localhost:8000/static/* 时,会去查找 app/staticfiles/*。虽然尚未准备 staticfiles 目录。。Django 内置服务器在使用 python manage.py runserver 命令启动时,会自动实现这个功能。
然而,在生产服务器中使用的 nginx 需要手动设置各种配置。
按顺序进行生产环境的设置。
version: '3.8'
services:
web:
build:
context: ./app
dockerfile: Dockerfile.prod
command: gunicorn config.wsgi:application --bind 0.0.0.0:8000
volumes:
- static_volume:/home/app/web/staticfiles
expose:
- 8000
env_file:
- ./.env.prod
depends_on:
- db
db:
image: postgres:13.0-alpine
volumes:
- postgres_data:/var/lib/postgresql/data/
env_file:
- ./.env.prod.db
nginx:
build: ./nginx
volumes:
- static_volume:/home/app/web/staticfiles
ports:
- 1337:80
depends_on:
- web
volumes:
postgres_data:
static_volume:
说实话,我对示例网站中以下的文字理解不了。如果能读懂来源链接中的FAQ,可能就能清楚明白。但我现在没有这么大的能力,暂时保留。
为什么这很必要?
Docker Compose通常将命名卷作为root挂载。而且由于我们使用的是非root用户,如果目录不存在,在运行collectstatic命令时会出现权限被拒绝的错误。
为了解决这个问题,你可以选择:在Dockerfile中创建该文件夹(来源)
在目录挂载后更改其权限(来源)
我们选择了前者。
“`yaml
– static_volume:/home/app/web/staticfiles
“`
目前无法自信地理解其原因。。现在记录下自己的理解。`collectstatic`命令被用来收集和存储静态文件的位置是在web容器的`/home/app/web/staticfiles`目录下。
web容器通过`volumes`配置将静态文件保留在与Docker相关的管理区域的`static_volume`中。
nginx通过(稍后将在nginx.conf中添加)nginx.conf文件,当收到对`http://〇〇〇〇〇/static/*`的访问请求时,会去nginx容器的`/home/app/web/staticfiles`目录中查找对应文件。
通过nginx的`volumes`配置将`static_volume:/home/app/web/staticfiles`进行关联,因此web端存储在static_volume中的静态文件也会被复制到nginx端的`/home/app/web/staticfiles`目录中。
最终,nginx能够显示出`/home/app/web/staticfiles`中存在的静态文件。
以上序列是否准确无误?
虽然有些不明白的地方,但是相信样本,继续前进吧!
在Dockerfile.prod中添加一行代码来创建staticfiles目录。
ENV HOME=/home/app
ENV APP_HOME=/home/app/web
RUN mkdir $APP_HOME
RUN mkdir $APP_HOME/staticfiles
WORKDIR $APP_HOME
nginx将路由到staticfiles。
upstream config {
server web:8000;
}
server {
listen 80;
location / {
proxy_pass http://config;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}
location /static/ {
alias /home/app/web/staticfiles/;
}
}
開始進行動作確認
~/*$ docker-compose -f docker-compose.prod.yml up -d --build
请大家先继续前进,因为我认为不会发生ポカミス导致的错误。
出现了错误。
・・・・・・
=> CACHED [builder 5/9] RUN pip install flake8==3.9.2 0.0s
=> CANCELED [builder 6/9] COPY . . 2.5s
------
> [stage-1 4/15] RUN mkdir /home/app/web/staticfiles:
#7 1.632 mkdir: can't create directory '/home/app/web/staticfiles': No such file or directory
------
executor failed running [/bin/sh -c mkdir $APP_HOME/staticfiles]: exit code: 1
ERROR: Service 'web' failed to build : Build failed
在编辑docker-compose.prod.yml时,我错误地粘贴了位置。
RUN mkdir $APP_HOME/staticfiles
RUN mkdir $APP_HOME
按照以下顺序进行了处理,将其上下对换后,成功进行了构建。
错误处理完毕。
继续迁移
~/*$ docker-compose -f docker-compose.prod.yml exec web python manage.py migrate --noinput
如果执行collectstatic操作,它将把多个static目录中的信息汇总(复制)到staticfiles目录中。
~/*$ docker-compose -f docker-compose.prod.yml exec web python manage.py collectstatic --no-input --clear
让我们检查管理员管理页面。如果你能访问http://localhost:1337/admin 并且看到了美观的CSS装饰,那就表示成功了!
让容器停止运行吧
~/*$ docker-compose -f docker-compose.prod.yml down -v
媒体文件
为了对Media Files进行功能验证,需要实际上传图像并准备一个应用程序来显示这些图像。
为了将容器中的编辑内容反映到主机上,需要在带有绑定挂载设置的开发模式下进行构建。
在这里提到的主机是指在WSL2 Ubuntu上创建的数据。
~/*$ docker-compose up -d --build
在 web 容器中执行 Django 的 startapp 命令。
~/*$ docker-compose exec web python manage.py startapp upload
如果在这里,主机端的app目录也生成了upload,那么就说明绑定挂载生效了。
虽然在前面稍微提到过,但我遇到了完全没有绑定挂载起作用的情况。在另一台电脑上重新创建项目后就成功了,原因不明。。
刚才和之前相反,主机上进行的更改应该会反映在容器中。
在主机上的 WSL2 Ubuntu 上更改并保存 $~/works/hoge-on-docker/app/config/ 目录下的 settings.py 文件。
INSTALLED_APPS = [
・・・・・・,
・・・,
'upload',
]
覆盖保存,没有任何问题。
下一步是在容器端生成并修改在主机端反映的上传文件。
from django.shortcuts import render
from django.core.files.storage import FileSystemStorage
def image_upload(request):
if request.method == "POST" and request.FILES["image_file"]:
image_file = request.FILES["image_file"]
fs = FileSystemStorage()
filename = fs.save(image_file.name, image_file)
image_url = fs.url(filename)
print(image_url)
return render(request, "upload.html", {
"image_url": image_url
})
return render(request, "upload.html")
无法进行覆盖保存。。VScode显示错误消息。
'views.py' を保存できませんでした。
ファイル 'vscode-remote://wsl+ubuntu/home/●●●/works/hoge-on-docker/app/upload/views.py' を
書き込むことができません (NoPermissions (FileSystemError):
Error: EACCES: permission denied, open '/home/●●●/works/hoge-on-docker/app/upload/views.py')
使用WSL2运行Docker时解决权限问题的简单方法(使用docker-compose.yml)。让我采用这篇文章中的WSL2解决方案。
~/*$ sudo chown -R $USER:$USER .
如果能按下”再试行”按钮,则表示成功进行了覆盖保存操作。如果没有任何反应,即可确认成功保存。这也意味着同时将更改反映到容器中。
接下来,创建一个名为 app/upload/templates 的目录,并在其中创建 upload.html。
{% block content %}
<form action="{% url "upload" %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<input type="file" name="image_file">
<input type="submit" value="submit" />
</form>
{% if image_url %}
<p>File uploaded at: <a href="{{ image_url }}">{{ image_url }}</a></p>
{% endif %}
{% endblock %}
from django.contrib import admin
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
from upload.views import image_upload
urlpatterns = [
path("", image_upload, name="upload"),
path("admin/", admin.site.urls),
]
if bool(settings.DEBUG):
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "mediafiles"
如果在主机上创建了新文件,则应该会出现与容器权限不匹配的情况,所以在这个阶段可能需要再次执行以下命令:
~/*$ sudo chown -R $USER:$USER .
我要试试开车。
~/*$ docker-compose up -d --build
使用浏览器访问 http://localhost:8000/ 。准备一张图片,选择文件并提交。点击显示的链接,如果能够显示图片就说明成功。
因为在开发模式下进行了一些进展,所以需要在正式环境中进行相应的更改。
需要给docker-compose.prod.yml文件中web和nginx的卷添加补充。
version: '3.8'
services:
web:
build:
context: ./app
dockerfile: Dockerfile.prod
command: gunicorn config.wsgi:application --bind 0.0.0.0:8000
volumes:
- static_volume:/home/app/web/staticfiles
- media_volume:/home/app/web/mediafiles
expose:
- 8000
env_file:
- ./.env.prod
depends_on:
- db
db:
image: postgres:13.0-alpine
volumes:
- postgres_data:/var/lib/postgresql/data/
env_file:
- ./.env.prod.db
nginx:
build: ./nginx
volumes:
- static_volume:/home/app/web/staticfiles
- media_volume:/home/app/web/mediafiles
ports:
- 1337:80
depends_on:
- web
volumes:
postgres_data:
static_volume:
media_volume:
# create the appropriate directories
ENV HOME=/home/app
ENV APP_HOME=/home/app/web
RUN mkdir $APP_HOME
RUN mkdir $APP_HOME/staticfiles
RUN mkdir $APP_HOME/mediafiles
WORKDIR $APP_HOME
upstream config {
server web:8000;
}
server {
listen 80;
location / {
proxy_pass http://config;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}
location /static/ {
alias /home/app/web/staticfiles/;
}
location /media/ {
alias /home/app/web/mediafiles/;
}
}
关闭开发模式容器。
~/*$ docker-compose down -v
构建生产容器
~/*$ docker-compose -f docker-compose.prod.yml up -d --build
错误处理(如果没有错误,请继续前进)
=> [builder 6/9] COPY . . 3.6s
=> ERROR [builder 7/9] RUN flake8 --ignore=E501,F401 ./config 3.9s
------
> [builder 7/9] RUN flake8 --ignore=E501,F401 ./config:
#18 2.898 ./config/urls.py:14:81: W292 no newline at end of file
------
executor failed running [/bin/sh -c flake8 --ignore=E501,F401 ./config]: exit code: 1
ERROR: Service 'web' failed to build : Build failed
当我从flake8得到urls.py文件的最后一行没有换行符的警告时,我意识到复制粘贴时需要注意。
即使添加了最后一行,如果存在制表符,也是不被允许的。为了避免这种情况,我们应该将最后一行留空。
重新构建后,问题得以解决。
接下来执行数据迁移和静态文件收集。
~/*$ docker-compose -f docker-compose.prod.yml exec web python manage.py migrate --noinput
~/*$ docker-compose -f docker-compose.prod.yml exec web python manage.py collectstatic --no-input --clear
构建通过后,可以访问http://localhost:1337/,但在选择图像文件后,点击图像显示页面的链接时出现错误。
Forbidden (403)
CSRF verification failed. Request aborted.
More information is available with DEBUG=True.
从Django4(4.1?)开始,需要在settings.py中添加CSRF_TRUSTED_ORIGINS。
CSRF_TRUSTED_ORIGINS = ['http://localhost:1337',]
重新构建。。(如上所述,应设置为DEBUG=1以便详细检查错误)
再次flake8
=> [builder 6/9] COPY . . 2.6s
=> ERROR [builder 7/9] RUN flake8 --ignore=E501,F401 ./config 4.3s
------
> [builder 7/9] RUN flake8 --ignore=E501,F401 ./config:
#18 3.614 ./config/settings.py:134:48: E231 missing whitespace after ','
------
executor failed running [/bin/sh -c flake8 --ignore=E501,F401 ./config]: exit code: 1
ERROR: Service 'web' failed to build : Build failed
不消失逗号后的空格”のお手当”。很麻烦啊。。
CSRF_TRUSTED_ORIGINS = ['http://localhost:1337', ]
这次一定!
~/*$ docker-compose -f docker-compose.prod.yml up -d --build
~/*$ docker-compose -f docker-compose.prod.yml exec web python manage.py migrate --noinput
~/*$ docker-compose -f docker-compose.prod.yml exec web python manage.py collectstatic --no-input --clear
完成了。
在示例网站中,如果保持现状,上传文件容量上限为1MB。需要修改nginx.conf。
location / {
proxy_pass http://hello_django;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
client_max_body_size 100M;
}
这就是在示范网站上可以进行的全部内容的结束。
结论
有一种介绍了SSL化的方法,但我觉得自己无法实现,所以放弃了。经过调查,我决定使用https-portal。
最后
在我写文章的过程中,我搜到了《追求理解Docker镜像的教程》。太厉害了!我要好好学习一下。添加SSL加密的https-portal等待下次机会吧。等我有了动力再写。
↓ 我写了《Docker学习的收尾阶段,SSL支持和部署》。