用Postgres、Gunicorn和Traefik对Django进行Docker化

290 阅读7分钟

在本教程中,我们将看看如何用Postgres和Docker来设置Django。对于生产环境,我们将添加Gunicorn、Traefik和Let's Encrypt。

项目设置

首先创建一个项目目录。

请随意将virtualenv和Pip换成PoetryPipenv。更多信息,请查看现代Python环境

接下来,让我们安装Django并创建一个简单的Django应用程序。

运行该应用程序。

导航到http://localhost:8000/,查看Django的欢迎界面。完成后关闭服务器并从虚拟环境中退出。同时删除虚拟环境。现在我们有一个简单的Django项目可以使用。

在 "app "目录下创建一个requirements.txt文件,并将Django作为一个依赖项。

由于我们将转移到Postgres,继续从 "app "目录中删除db.sqlite3文件。

你的项目目录应该是这样的。

Docker

安装Docker,如果你还没有的话,然后在 "app "目录中添加一个Docker文件

所以,我们从Python 3.9.5的slim Docker镜像开始。然后我们设置了一个工作目录和两个环境变量。

  1. PYTHONDONTWRITEBYTECODE:防止Python将pyc文件写入磁盘(相当于python -B 选项)。
  2. PYTHONUNBUFFERED:防止 Python 缓冲 stdout 和 stderr (相当于python -u 选项)

最后,我们复制了requirements.txt文件,安装了依赖项,并复制了项目。

回顾一下Docker for Python Developers,了解更多关于结构化Docker文件的信息,以及为基于Python的开发配置Docker的一些最佳实践。

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

查看Compose文件的参考信息,了解该文件的工作原理。

构建镜像。

一旦镜像构建完成,运行容器。

导航到http://localhost:8008,再次查看欢迎页面。

如果不能通过docker-compose logs -f ,请检查日志中是否有错误。

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

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

为了让数据在容器寿命结束后仍能持续存在,我们配置了一个卷。这个配置将把postgres_data 绑定到容器中的"/var/lib/postgresql/data/"目录。

我们还添加了一个环境键来定义默认数据库的名称,并设置了一个用户名和密码。

查看Postgres Docker Hub页面的 "环境变量 "部分以了解更多信息。

请注意web 服务中的新命令。

while !</dev/tcp/db/5432; do sleep 1 将持续到Postgres启动。一旦启动, 运行。python manage.py runserver 0.0.0.0:8000

为了配置Postgres,在*requirements.*txt中添加django-environ,以加载/读取环境变量,以及Psycopg2

config/settings.py的顶部初始化environ。

然后,更新DATABASES dict。

django-environ会自动解析我们添加到docker-compose.yml中的数据库连接URL字符串。

同时更新DEBUG 变量。

构建新的镜像,并将两个容器旋转起来。

运行初始迁移。

确保默认的Django表被创建。

你可以通过运行以下程序来检查卷是否已经创建。

你应该看到类似的东西。

Gunicorn

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

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

如果你有多个环境,你可能想看看使用docker-compose.override.yml配置文件。使用这种方法,你会把你的基本配置添加到docker-compose.yml文件中,然后使用docker-compose.override.yml文件来根据环境覆盖这些配置设置。

请注意默认的command 。我们正在运行Gunicorn而不是Django开发服务器。我们还从web 服务中移除卷,因为我们在生产中不需要它。

将开发容器(以及使用-v 标志的相关卷)关闭

然后,建立生产镜像并启动容器。

运行迁移程序。

验证django_traefik 数据库和默认的Django表是否被创建。测试一下http://localhost:8008/admin 的管理页面静态文件没有被正确加载。这是预料之中的事情。我们将很快解决这个问题。

同样,如果容器无法启动,通过docker-compose -f docker-compose.prod.yml logs -f ,检查日志中的错误。

生产Docker文件

创建一个新的Dockerfile,叫做Dockerfile.prod,用于生产构建。

在这里,我们使用了Docker多阶段构建来减少最终镜像的大小。基本上,builder 是一个临时镜像,用于构建Python轮子。然后,这些轮子被复制到最终的生产镜像上,而builder 镜像被丢弃。

你可以进一步采用多阶段构建方法,使用一个Docker文件,而不是创建两个Docker文件。想想使用这种方法比使用两个不同的文件有什么利弊。

你注意到我们创建了一个非root用户吗?默认情况下,Docker在容器内以root身份运行容器进程。这是一个不好的做法,因为攻击者如果设法突破容器,就可以获得对Docker主机的root权限。如果你在容器中是root,你在主机上也是root。

更新docker-compose.prod.yml文件中的web 服务,用Dockerfile.prod构建。

试试吧。

Traefik

接下来,让我们把Traefik,一个反向代理,加入到这个组合中。

初次接触Traefik?请看正式的入门指南。

Traefik vs Nginx:Traefik是一个现代的HTTP反向代理和负载平衡器。它经常被比作Nginx,一个网络服务器和反向代理。由于Nginx主要是一个网络服务器,它可以用来提供一个网页,也可以作为一个反向代理和负载平衡器。一般来说,Traefik更容易启动和运行,而Nginx的功能更全面。

Traefik

  1. 反向代理和负载平衡器
  2. 通过Let's Encrypt,自动签发和更新SSL证书,开箱即用。
  3. 将Traefik用于简单的、基于Docker的微服务。

Nginx

  1. 网络服务器、反向代理和负载平衡器
  2. 比Traefik稍快一些
  3. 使用Nginx处理复杂的服务

添加一个名为traefik.dev.toml的新文件。

在这里,由于我们不想暴露db 服务,我们将exposedByDefault设置为false 。要手动暴露一个服务,我们可以在Docker Compose文件中添加"traefik.enable=true" 标签。

接下来,更新docker-compose.yml文件,使我们的web 服务被Traefik发现,并添加一个新的traefik 服务。

首先,web 服务只暴露给端口为8000 的其他容器。我们还在web 服务上添加了以下标签。

  1. traefik.enable=true 使得Traefik能够发现该服务
  2. traefik.http.routers.django.rule=Host(`django.localhost`) 当请求有 ,请求就会被重定向到这个服务。Host=django.localhost

注意到traefik 服务内的卷。

  1. "$PWD/traefik.dev.toml:/etc/traefik/traefik.toml" 将本地配置文件映射到容器中的配置文件,这样设置就能保持同步了
  2. "/var/run/docker.sock:/var/run/docker.sock:ro" 使得Traefik能够发现其他容器

为了测试,首先要关闭所有现有的容器。

建立新的开发镜像并启动容器。

导航到django.localhost:8008/。你应该看到Django的欢迎页面。

接下来,在django.localhost:8081查看仪表板

traefik dashboard

完成后将容器和卷关闭。

让我们加密

我们已经成功创建了一个Django、Docker和Traefik在开发模式下的工作实例。对于生产来说,你需要配置Traefik来通过Let's Encrypt管理TLS证书。简而言之,Traefik会自动联系证书颁发机构来颁发和更新证书。

由于Let's Encrypt不会为localhost ,你需要在云计算实例(如DigitalOceandroplet或AWS EC2实例)上旋转你的生产容器。你还需要一个有效的域名。如果你没有,你可以在Freenom创建一个免费域名。

我们使用DigitalOcean液滴和Docker机器来快速配置Docker的计算实例,并部署生产容器来测试Traefik的配置。查看Docker文档中的DigitalOcean例子,了解更多关于使用Docker机器配置液滴的信息。

假设你配置了一个计算实例并设置了一个免费域名,你现在就可以在生产模式下设置Traefik了。

首先,将生产版本的Traefik配置添加到一个名为traefik.prod.toml的文件中。

请确保将 [[email protected]](https://testdriven.io/cdn-cgi/l/email-protection)用你的实际电子邮件地址。

这里发生了什么。

  1. entryPoints.web 为我们不安全的HTTP应用程序设置入口点,即80端口
  2. entryPoints.websecure 为我们的安全 HTTPS 应用程序设置入口点为 443 端口
  3. entryPoints.web.http.redirections.entryPoint 将所有不安全的请求重定向到安全端口
  4. exposedByDefault = false 取消所有的服务
  5. dashboard = true 启用监控仪表板

最后,要注意的是。

这里是Let's Encrypt配置的位置。我们定义了证书的存储位置和验证类型,也就是HTTP挑战

接下来,假设你更新了域名的DNS记录,创建两个新的A记录,都指向你的计算实例的公共IP。

  1. django-traefik.your-domain.com - 用于网络服务
  2. dashboard-django-traefik.your-domain.com - 为Traefik仪表板

确保用你的实际域名替换your-domain.com

接下来,像这样更新docker-compose.prod.yml

同样,确保用你的实际域名替换your-domain.com

这里有什么新内容?

web 服务中,我们添加了以下标签。

  1. traefik.http.routers.django.rule=Host(`django-traefik.your-domain.com`) 将主机改为实际域名
  2. traefik.http.routers.django.tls=true 启用HTTPS
  3. traefik.http.routers.django.tls.certresolver=letsencrypt 设置证书颁发者为Let's Encrypt

接下来,对于traefik 服务,我们添加了适当的端口和一个用于证书目录的卷。这个卷可以确保即使容器被关闭,证书也能持续存在。

至于标签。

  1. traefik.http.routers.dashboard.rule=Host(`dashboard-django-traefik.your-domain.com`) 定义了仪表盘的主机,所以它可以被访问到$Host/dashboard/
  2. traefik.http.routers.dashboard.tls=true 启用HTTPS
  3. traefik.http.routers.dashboard.tls.certresolver=letsencrypt 将证书解析器设置为Let's Encrypt
  4. traefik.http.routers.dashboard.middlewares=auth 启用 中间件HTTP BasicAuth
  5. traefik.http.middlewares.auth.basicauth.users 定义了用户名和用于登录的哈希密码

你可以使用htpasswd工具创建一个新的密码哈希值。

请随意使用env_file ,将用户名和密码存储为环境变量

接下来,像这样更新config/settings.py中的ALLOWED_HOSTS 环境变量。

最后,添加一个新的Dockerfile,名为Dockerfile.traefik

接下来,启动新的容器。

确保这两个URL是有效的。

  1. django-traefik.your-domain.com
  2. dashboard-django-traefik.your-domain.com/dashboard/

另外,确保当你访问上述URL的HTTP版本时,你会被重定向到HTTPS版本。

最后,Let's Encrypt证书的有效期为90天。Treafik会在幕后自动为你处理证书的更新,这样你就可以少操心一件事了

静态文件

由于Traefik不提供静态文件,我们将使用WhiteNoise来管理静态资产。

首先,将软件包添加到requirements.txt文件中。

像这样更新config/settings.py中的中间件。

然后用STATIC_ROOT 配置处理你的静态文件。

最后,添加压缩和缓存支持。

为了测试,更新图像和容器。

收集静态文件。

确保静态文件在django-traefik.your-domain.com/admin 上被正确提供。

总结

在本教程中,我们学习了如何将一个带有Postgres的Django应用程序进行容器化开发。我们还创建了一个生产就绪的Docker Compose文件,设置了Traefik和Let's Encrypt以通过HTTPS为应用程序提供服务,并启用了一个安全仪表盘来监控我们的服务。

在实际部署到生产环境方面,你可能想使用一个。

  1. 完全管理的数据库服务--如RDSCloud SQL--而不是在容器中管理你自己的Postgres实例。
  2. 服务的非root用户

你可以在django-docker-traefikrepo中找到这些代码。