为了账号安全,请及时绑定邮箱和手机立即绑定

使用Docker部署Django应用:搭配Postgres、Gunicorn和Nginx的实战指南

本文是一步步的教学指南,详细说明如何在Docker中配置Django与Postgres一起运行。对于生产环境,我们将加入Nginx和Gunicorn。我们还将讨论如何使用Nginx来托管Django的静态和媒体文件。

依赖

Django on Docker 系列:

项目启动

创建一个新的项目目录,并且在其中建立一个新的Django项目。

您可以将 virtualenv 和 Pip 替换成 Poetry Pipenv 。更多,请参阅 现代 Python 开发环境 .

在浏览器中打开 http://localhost:8000/ 查看 Django 欢迎界面。完成后关闭服务器。之后退出并删除虚拟环境。我们现在有了一个可以使用的简单的 Django 项目。

在'app' 目录下创建一个_requirements.txt_文件,并将Django作为依赖项添加进去。

既然我们要迁移到Postgres,可以去‘app’目录下删除_db.sqlite3_文件了。

你的项目文件夹应该如下所示:

Docker(一个开源的应用容器引擎)

如果你还没有安装Docker的话,可以在“app”目录中添加一个Dockerfile

所以我们从一个基于 slim-busterPython 3.11.4 的 Docker 镜像 开始。然后我们定义了 工作目录 以及两个环境变量:

  1. PYTHONDONTWRITEBYTECODE:禁止 Python 生成 pyc 文件(等同于使用 python -B 参数)
  2. PYTHONUNBUFFERED:防止 Python 缓存标准输出和标准错误(等同于使用 python -u 参数)

最后,我们升级了Pip,复制了_requirements.txt_文件(注意保留英文文件名),安装了依赖,并复制了Django项目。

Review Docker 最佳实践指南:Python 开发人员版 _以了解更多关于如何结构化 Dockerfile 的信息,另外还有了解一些针对 Python 项目配置 Docker 的最佳实践。
</TRANSLATION>

接下来,请在项目根目录添加一个 docker-compose.yml 文件即可。

查看 Compose 文件参考 ,了解这个文件是如何工作的.

更新 settings.py 文件中的 SECRET_KEYDEBUGALLOWED_HOSTS 变量。

确保在文件顶部加入导入语句:

# 在这里添加具体的导入语句,例如:import 模块名

然后,在项目根目录下,创建一个 .env.dev 文件来存储开发环境变量值,例如:

创建镜像。

镜像构建完成后,运行该容器,

访问 http://localhost:8000/ 再次查看欢迎页面。

如果还是不行,可以检查日志中的错误,通过 _docker-compose logs -f_

Postgres(PostgreSQL数据库)

要配置 PostgreSQL,我们需要在 docker-compose.yml 文件中添加一个新的服务,更新 Django 的设置,并安装 Psycopg2

首先,在 docker-compose.yml 文件中添加一个名为 db 的新服务:

为了在容器的生命周期结束后仍然保留数据,我们配置了一个卷。我们将 postgres_data 绑定到容器中的 /var/lib/postgresql/data/ 路径。

我们也添加了一个环境变量来定义默认数据库的名称为,并设置了用户名和密码以供使用。

查看Postgres Docker Hub页面上的环境变量部分以获取更多信息。

我们还需要给web服务添加一些新的环境变量,请参考以下内容更新_.env.dev_文件如下所示:

新的环境变量

settings.py 文件里更新 DATABASES 部分:

这里,数据库是根据我们刚刚定义的环境变量进行配置的。请留意默认设置。

requirements.txt 中添加 Psycopg2:

构建新的镜像,并启动两个容器。

运行迁移命令:

    $ docker-compose exec web python manage.py migrate --noinput

这条命令用于执行Docker-compose中定义的web服务的数据库迁移。docker-compose 是一个用于定义和运行多容器Docker应用的工具,web 表示要执行命令的服务,python manage.py migrate 是运行Django应用的数据库迁移命令,--noinput 参数表示在迁移过程中不提示输入。

遇到这样的错误?

_django.db.utils.OperationalError: FATAL: 数据库“hello_djangodev”不存在

运行docker-compose down -v以移除卷和容器。然后,重新构建镜像,运行容器,并执行迁移。

检查默认的Django表是否已经创建好了。

你可以通过运行以下操作检查磁盘卷是否已创建:

你应该看到类似的东西:

接下来,在“app”目录中添加一个_entrypoint.sh_文件,以确保在执行迁移和运行Django开发服务器前Postgres运行正常。

修改本地文件权限:

然后,更新 Dockerfile,复制 entrypoint.sh 文件,并将其设置为 Docker entrypoint 命令。

.env.dev 文件中添加 DATABASE 环境变量:

再试一下,

  1. 重新构建图像
  2. 启动容器实例
  3. 在浏览器中打开尝试一下 http://localhost:8000/
注:

尽管添加了Postgres,我们仍然可以为Django单独构建一个Docker容器镜像,只要DATABASE环境变量没有设置为postgres。为了测试,我们可以构建一个新的镜像之后,然后运行一个新的容器。

$ docker build -f ./app/Dockerfile -t hello_django:latest ./app
$ docker run -d -p 8006:8000 -e "SECRET_KEY=请修改我" -e "DEBUG=1" -e "DJANGO_ALLOWED_HOSTS=*" hello_django python /usr/src/app/manage.py runserver 0.0.0.0:8000
# 请根据需要修改环境变量SECRET_KEY的值。

http://localhost:8006,你应该能看到欢迎页面。

另外,你可以将_entrypoint.sh_脚本中的数据库清空和迁移操作注释掉,这样它们就不会在每次启动或重启容器时执行。

相反,你可以在容器启动之后手动运行它们,就像这样。

Gunicorn (龟牛corn)

继续往下,对于生产环境中,让我们在requirements文件中加入Gunicorn,一个生产级的WSGI服务器。

对WSGI和Gunicorn感到好奇吗?查阅课程中的_ WSGI 章节, 构建你自己的Python Web框架课程 _。

由于我们仍然想在开发中使用 Django 的内置服务器,创建一个名为 docker-compose.prod.yml 的新 compose 文件用于生产环境。

如果你有多个环境设置,可能想使用一个docker-compose.override.yml 配置文件。删除你将基础配置添加到一个 docker-compose.yml 文件中,然后使用 docker-compose.override.yml 文件来根据不同的环境调整这些设置。采取这种方法,可以更好地反映覆盖配置的概念。

请注意默认的 command。我们运行的是 Gunicorn 而不是 Django 开发服务器。因为生产环境中不需要它,所以我们从 web 服务中移除了卷。最后,我们使用了环境变量文件来定义两个服务各自的环境变量,这些环境变量将在运行时传递给容器。

将这两个文件添加到项目根目录下。你可能希望将它们排除在版本控制之外,那就把它们添加到_.gitignore_文件中。

卸载开发容器(并使用-v标志卸载相关卷)(关于如何卸载,请参考这里):

然后,构建生产环境的镜像并开始运行容器实例:

验证 hello_django_prod 数据库和默认的 Django 表是否已创建。访问管理页面 http://localhost:8000/admin。不再加载静态文件,这是因为 Debug 模式已关闭,所以这是正常的。我们很快会解决这个问题。

如果容器再次无法启动,请通过 docker-compose -f docker-compose.prod.yml logs -f 查看错误日志。

用于生产的 Dockerfile:

每次我们运行容器时,你有没有注意到我们仍然在运行数据库的刷新命令(这会清空数据库)和迁移命令?这在开发环境中是可以接受的,但在生产环境中,让我们创建一个新的入口点文件。

生产环境入口脚本 entrypoint.prod.sh :

修改本地文件权限如下:

要使用该文件,请为生产环境创建一个新的名为 Dockerfile.prod 的 Dockerfile。

在这里,我们使用了 Docker 的多阶段构建来减少最终镜像的体积。简单来说,builder 是一个临时镜像,用于构建 Python wheels。然后将 wheels 复制进最终的生产镜像中,并舍弃 builder 镜像。

你可以更进一步地利用 多阶段构建 ,并使用一个 Dockerfile,而不是创建两个 Dockerfile。想一想这种方法和使用两个不同文件相比,有哪些好处和缺点。

你发现我们创建了一个非root用户账户了吗?通常情况下,Docker容器内的进程默认以root身份运行。这样做不太好,因为如果攻击者设法从容器逃脱,他们就可以控制Docker主机。假设你在容器里获得了root权限,你同样可以在主机上获得root权限。

docker-compose.prod.yml (生产配置文件) 文件中的 web 服务,将其更新为使用 Dockerfile.prod (生产构建文件) 进行构建。

试试看,

启动并构建生产环境的Docker容器,并运行数据库迁移。
$ 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

Nginx配置

接下来,让我们加入Nginx,让它作为Gunicorn的反向代理服务器来处理客户端请求并提供静态文件。

docker-compose.prod.yml 中加上服务如下:

接下来,在本地项目根目录下创建以下文件夹和文件:

Dockerfile:

FROM nginx:1.25 RUN rm /etc/nginx/conf.d/default.conf COPY nginx.conf /etc/nginx/conf.d

查阅_使用NGINX和NGINX Plus作为与uWSGI和Django配合使用的应用网关配置 获取更多有关如何配置Nginx与Django配合使用的资料。

然后,在 docker-compose.prod.yml 文件中,将 web 服务配置的 ports 替换为 expose

现在,8000端口仅对内部的其他Docker服务开放,不会再映射到主机了。

关于端口和暴露的区别,可以参考一下这个问题的讨论在Stack Overflow上的内容.

再试试看。

上述命令用于在生产环境中停止并重建Docker容器,然后执行数据库迁移。

$ 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上启动并正常运行。

你的项目结构现在应该如下所示:(请查看项目的根目录)。

做好了就把容器拿下去:

由于 Gunicorn 是一个应用服务器(应用程序服务器),它不会提供静态文件。在这种情况下,我们应该如何处理静态文件和媒体文件呢?

静态文件

更新一下 settings.py 文件

开发

任何对 **http://localhost:8000/static/*** 的请求现在都将从 staticfiles 目录中处理。

测试时,首先像平常一样重新构建镜像并启动新的容器实例。确保静态文件仍然可以通过http://localhost:8000/admin 正确提供进行验证。

制作

在生产环境中,在 _docker-compose.prod.yml 中为 webnginx 服务添加一个卷,以便每个容器都可以共享一个名为 "staticfiles" 的目录。

我们还需要在_Dockerfile.prod_中创建“/home/app/web/staticfiles”目录。

这为什么有必要?

Docker Compose 通常会将命名卷挂载为 root 用户权限。因为我们在使用非 root 用户,如果该目录不存在,运行 collectstatic 命令时会遇到权限被拒绝的错误。

要解决这个问题,你可以:

或者结合使用多种方法。

  1. 在Dockerfile中增加文件夹创建指令 (来源)
  2. 挂载目录后调整其权限 (来源)

我们用了前者。

接下来,更新Nginx配置,将静态文件请求指向“staticfiles”目录:

关闭开发容器:

测试:

再次,对http://localhost:1337/static/*的请求将被处理来自"staticfiles"目录。

(Note: After revisiting the suggestions, the most fluent and contextually appropriate version seems to be:)

再次,对http://localhost:1337/static/*的请求将由"staticfiles"目录处理。

访问 http://localhost:1337/admin,并确保它们加载正确。

你也可以通过运行 docker-compose -f docker-compose.prod.yml logs -f 查看日志,确认静态文件请求是否已成功通过 Nginx 提供。

做完后把容器带来:

为了测试媒体文件的处理功能,首先创建一个新的Django应用程序。

运行 `docker-compose up -d --build` 命令来启动并构建容器,然后使用 `docker-compose exec web python manage.py startapp upload` 命令在web服务中执行Python管理命令以启动应用程序。

settings.py 文件里,把新应用程序加到 INSTALLED_APPS

文件路径:app/upload/views.py

在“app/upload”目录下新建一个名为“templates”的目录, 并在该目录中创建一个新的模板文件,命名为“upload.html”。

_app/hellodjango/urls.py :

_app/hellodjango/settings.py

开发部分

测试:

你应该能够在此 http://localhost:8000/ 上传图片,然后在该地址 http://localhost:8000/media/IMAGE_FILE_NAME 查看图片。

制作

在生产环境中,为webnginx组件添加一个额外的卷。

在名为 Dockerfile.prod 的文件中,添加以下命令来创建文件夹(folder):/home/app/web/mediafiles

再更新一下 Nginx 配置:

settings.py 文件中添加以下内容:

重建:

再试一次最后:

  1. http://localhost:1337/上传一张图片到该地址。
  2. 之后,在http://localhost:1337/media/IMAGE_FILE_NAME查看上传的图片。

_如果你看到 413 Request Entity Too Large 错误,你需要增加客户端请求正文的最大允许大小,可以在Nginx配置的服务器或位置上下文中进行设置。_

例如:

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; }

结尾

在这篇教程中,我们介绍了怎样为开发环境将一个使用 Postgres 的 Django 网络应用容器化。我们还创建了一个生产就绪的 Docker Compose 文件,该文件配置了 Gunicorn 和 Nginx 以处理静态和媒体文件。你现在可以在本地测试一下生产环境下的设置。

就实际部署到生产环境而言,你可能希望使用的是:比如

  1. 完全托管的数据库服务 — 如 RDSCloud SQL — 而不是在容器中自行管理 Postgres 数据库。
  2. dbnginx 服务的非 root 用户账户

其他生产技巧,可以查看此讨论

你可以在这个django-on-docker代码库找到代码。

还有一个较旧的Pipenv版本的代码在这里可以找到它 .

谢谢你看完了这篇

Django与Docker系列:

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消