学习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。

不用 pip freeze > requirements.txt 吗?
由于使用了基于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 . .
为了安装psycopg2,需要进行以下操作:
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。

关于Postgres Notes的备忘录
它介绍了如何仅使用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命令进行的操作(实际上还没有完全理解。只是留下备忘录的感觉…)
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中创建该文件夹(来源)

在目录挂载后更改其权限(来源)
我们选择了前者。

对于web和nginx两个容器中都存在以下内容的`volumes`配置:
“`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支持和部署》。

广告
将在 10 秒后关闭
bannerAds