在本教程中,我们将看看如何用Postgres和Docker来设置Django。对于生产环境,我们将添加Gunicorn、Traefik和Let's Encrypt。
项目设置
首先创建一个项目目录。
请随意将virtualenv和Pip换成Poetry或Pipenv。更多信息,请查看现代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镜像开始。然后我们设置了一个工作目录和两个环境变量。
PYTHONDONTWRITEBYTECODE:防止Python将pyc文件写入磁盘(相当于python -B选项)。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。
- 反向代理和负载平衡器
- 通过Let's Encrypt,自动签发和更新SSL证书,开箱即用。
- 将Traefik用于简单的、基于Docker的微服务。
Nginx。
- 网络服务器、反向代理和负载平衡器
- 比Traefik稍快一些
- 使用Nginx处理复杂的服务
添加一个名为traefik.dev.toml的新文件。
在这里,由于我们不想暴露db 服务,我们将exposedByDefault设置为false 。要手动暴露一个服务,我们可以在Docker Compose文件中添加"traefik.enable=true" 标签。
接下来,更新docker-compose.yml文件,使我们的web 服务被Traefik发现,并添加一个新的traefik 服务。
首先,web 服务只暴露给端口为8000 的其他容器。我们还在web 服务上添加了以下标签。
traefik.enable=true使得Traefik能够发现该服务traefik.http.routers.django.rule=Host(`django.localhost`)当请求有 ,请求就会被重定向到这个服务。Host=django.localhost
注意到traefik 服务内的卷。
"$PWD/traefik.dev.toml:/etc/traefik/traefik.toml"将本地配置文件映射到容器中的配置文件,这样设置就能保持同步了"/var/run/docker.sock:/var/run/docker.sock:ro"使得Traefik能够发现其他容器
为了测试,首先要关闭所有现有的容器。
建立新的开发镜像并启动容器。
导航到django.localhost:8008/。你应该看到Django的欢迎页面。
接下来,在django.localhost:8081查看仪表板。
完成后将容器和卷关闭。
让我们加密
我们已经成功创建了一个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)用你的实际电子邮件地址。
这里发生了什么。
entryPoints.web为我们不安全的HTTP应用程序设置入口点,即80端口entryPoints.websecure为我们的安全 HTTPS 应用程序设置入口点为 443 端口entryPoints.web.http.redirections.entryPoint将所有不安全的请求重定向到安全端口exposedByDefault = false取消所有的服务dashboard = true启用监控仪表板
最后,要注意的是。
这里是Let's Encrypt配置的位置。我们定义了证书的存储位置和验证类型,也就是HTTP挑战。
接下来,假设你更新了域名的DNS记录,创建两个新的A记录,都指向你的计算实例的公共IP。
django-traefik.your-domain.com- 用于网络服务dashboard-django-traefik.your-domain.com- 为Traefik仪表板
确保用你的实际域名替换
your-domain.com。
接下来,像这样更新docker-compose.prod.yml。
同样,确保用你的实际域名替换
your-domain.com。
这里有什么新内容?
在web 服务中,我们添加了以下标签。
traefik.http.routers.django.rule=Host(`django-traefik.your-domain.com`)将主机改为实际域名traefik.http.routers.django.tls=true启用HTTPStraefik.http.routers.django.tls.certresolver=letsencrypt设置证书颁发者为Let's Encrypt
接下来,对于traefik 服务,我们添加了适当的端口和一个用于证书目录的卷。这个卷可以确保即使容器被关闭,证书也能持续存在。
至于标签。
traefik.http.routers.dashboard.rule=Host(`dashboard-django-traefik.your-domain.com`)定义了仪表盘的主机,所以它可以被访问到$Host/dashboard/traefik.http.routers.dashboard.tls=true启用HTTPStraefik.http.routers.dashboard.tls.certresolver=letsencrypt将证书解析器设置为Let's Encrypttraefik.http.routers.dashboard.middlewares=auth启用 中间件HTTP BasicAuthtraefik.http.middlewares.auth.basicauth.users定义了用户名和用于登录的哈希密码
你可以使用htpasswd工具创建一个新的密码哈希值。
请随意使用env_file ,将用户名和密码存储为环境变量
接下来,像这样更新config/settings.py中的ALLOWED_HOSTS 环境变量。
最后,添加一个新的Dockerfile,名为Dockerfile.traefik。
接下来,启动新的容器。
确保这两个URL是有效的。
另外,确保当你访问上述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为应用程序提供服务,并启用了一个安全仪表盘来监控我们的服务。
在实际部署到生产环境方面,你可能想使用一个。
你可以在django-docker-traefikrepo中找到这些代码。