在本教程中,我们将看看如何用Postgres和Docker来设置Flask。对于生产环境,我们将添加Gunicorn、Traefik和Let’s Encrypt。
项目设置
首先创建一个项目目录。
复制代码
请随意将virtualenv和Pip换成Poetry或Pipenv。更多信息,请查看现代Python环境。
然后,创建以下文件和文件夹。
复制代码
将Flask添加到_requirements.txt_中。
从 “services/web “中安装该软件包。
复制代码
接下来,让我们在___init.py___中创建一个简单的Flask应用程序。
复制代码
然后,为了配置Flask CLI工具,以便从命令行运行和管理应用程序,在_services/web/manage.py_中添加以下内容。
复制代码
在这里,我们创建了一个新的FlaskGroup
实例,用与Flask应用相关的命令来扩展正常的CLI。
从 “web “目录中运行服务器。
复制代码
导航到127.0.0.1:5000,你应该看到。
完成后关闭服务器。从虚拟环境中退出,并把它也删除。
Docker
安装Docker,如果你还没有的话,然后在 “web “目录中添加一个_Docker文件_。
复制代码
因此,我们从一个基于slim
的Docker镜像开始,用于Python 3.9.5。然后我们设置了一个工作目录和两个环境变量。
PYTHONDONTWRITEBYTECODE
:防止Python将pyc文件写入磁盘(相当于python -B
选项)。PYTHONUNBUFFERED
: 防止Python对stdout和stder进行缓冲(相当于python -u
选项)
最后,我们复制了_requirements.txt_文件,安装了依赖项,并复制了Flask应用本身。
回顾一下Docker for Python Developers,了解更多关于结构化Docker文件的信息,以及为基于Python的开发配置Docker的一些最佳实践。
接下来,在项目根目录下添加一个_docker-compose.yml_文件。
复制代码
查看Compose文件的参考信息,了解该文件的工作原理。
构建镜像。
一旦镜像构建完成,运行容器。
导航到http://127.0.0.1:5000/,再次查看hello world的正确性检查。
如果没有成功,请通过
docker-compose logs -f
,检查日志中是否有错误。
为了配置Postgres,我们需要在_docker-compose.yml_文件中添加一个新服务,设置Flask-SQLAlchemy,并安装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 run -h 0.0.0.0
运行。
然后,在 “项目 “目录下添加一个名为_config.py_的新文件,我们将在这里定义特定环境的配置变量。
复制代码
在这里,数据库是根据我们刚刚定义的DATABASE_URL
环境变量来配置的。请注意默认值。
更新___init__.py_以在启动时拉入配置。
复制代码
在_requirements.txt_中加入Flask-SQLAlchemy和Psycopg2。
复制代码
再次更新___init__.py_以创建一个新的SQLAlchemy
实例并定义一个数据库模型。
复制代码
在数据库模型上使用dataclass装饰器有助于我们序列化数据库对象。
最后,更新_manage.py_。
复制代码
这将在CLI中注册一个新的命令,create_db
,这样我们就可以在命令行中运行它,我们很快就会用它来将模型应用到数据库中。
建立新的镜像并启动两个容器。
复制代码
创建表。
复制代码
得到以下错误?
复制代码
运行
docker-compose down -v
,把卷和容器一起删除。然后,重新构建镜像,运行容器,并应用迁移。
确保users
表被创建。
复制代码
你可以通过运行来检查卷是否也被创建了。
复制代码
你应该看到类似的内容。
复制代码
导航到http://127.0.0.1:5000。理智检查显示一个空的列表。这是因为我们还没有填充users
表。让我们在_manage.py_中添加一个CLI种子命令,将样本users
添加到用户表中。
复制代码
试试吧。
复制代码
再次导航到http://127.0.0.1:5000。你现在应该看到。
Gunicorn
继续,对于生产环境,让我们在需求文件中添加Gunicorn,一个生产级的WSGI服务器。
复制代码
因为我们仍然想在开发中使用Flask的内置服务器,所以在项目根目录下创建一个新的compose文件,名为_docker-compose.prod.yml_,用于生产。
复制代码
如果你有多个环境,你可能想看看使用docker-compose.override.yml配置文件。使用这种方法,你会把你的基本配置添加到_docker-compose.yml_文件中,然后使用_docker-compose.override.yml_文件来根据环境覆盖这些配置设置。
请注意默认的command
。我们正在运行Gunicorn而不是Flask开发服务器。我们还从web
服务中移除卷,因为我们在生产中不需要它。
将开发容器(以及使用-v标志的相关卷)关闭。
然后,建立生产镜像并启动容器。
复制代码
创建表并应用种子。
复制代码
验证hello_flask_prod
数据库和users
表一起被创建。测试一下http://127.0.0.1:5000/。
同样,如果容器不能启动,通过
docker-compose -f docker-compose.prod.yml logs -f
,检查日志中的错误。
生产Docker文件
在 “web “目录下创建一个新的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用于复杂的服务
在 “services “目录下添加一个名为 “traefik “的新文件夹,以及以下文件。
复制代码
你的项目结构现在应该是这样的。
复制代码
在_traefik.dev.toml_中添加以下内容。
复制代码
在这里,由于我们不想暴露db
服务,我们将exposedByDefault设置为false
。要手动暴露一个服务,我们可以在Docker Compose文件中添加"traefik.enable=true"
的标签。
接下来,更新_docker-compose.yml_文件,使我们的web
服务被Traefik发现,并添加一个新的traefik
服务。
复制代码
首先,web
服务只暴露给端口为5000
的其他容器。我们还在web
服务上添加了以下标签。
traefik.enable=true
使得Traefik能够发现该服务traefik.http.routers.flask.rule=Host(`flask.localhost`)
当请求有Host=flask.localhost
,请求会被重定向到这个服务上
注意到traefik
服务内的卷。
./services/traefik/traefik.dev.toml:/etc/traefik/traefik.toml
将本地配置文件映射到容器中的配置文件,这样设置就会保持同步/var/run/docker.sock:/var/run/docker.sock:ro
,使Traefik能够发现其他容器。
为了测试,首先要关闭所有现有的容器。
复制代码
建立新的开发镜像并启动容器。
复制代码
创建表格并应用种子。
复制代码
导航到flask.localhost。你应该看到。
你也可以通过cURL进行测试。
复制代码
接下来,在flask.localhost:8081,检查仪表板 。
完成后将容器和卷关闭。
让我们加密
我们已经成功创建了一个Flask、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 应用程序的入口点设置为端口 443entryPoints.web.http.redirections.entryPoint
将所有不安全的请求重定向到安全端口exposedByDefault = false
取消所有的服务dashboard = true
启用监控仪表板
最后,请注意。
复制代码
这里是Let’s Encrypt配置的位置。我们定义了证书的存储位置以及验证类型,也就是HTTP挑战。
接下来,假设你更新了域名的DNS记录,创建两个新的A记录,都指向你的计算实例的公共IP。
flask-traefik.your-domain.com
– 用于网络服务dashboard-flask-traefik.your-domain.com
– 用于 Traefik 仪表板
确保用你的实际域名替换
your-domain.com
。
接下来,像这样更新_docker-compose.prod.yml_。
复制代码
同样,确保用你的实际域名替换
your-domain.com
。
这里有什么新内容?
在web
服务中,我们添加了以下标签。
traefik.http.routers.flask.rule=Host(`flask-traefik.your-domain.com`)
将主机改为实际域名traefik.http.routers.flask.tls=true
启用HTTPStraefik.http.routers.flask.tls.certresolver=letsencrypt
设置证书颁发者为Let’s Encrypt
接下来,对于traefik
服务,我们添加了适当的端口和一个用于证书目录的卷。这个卷可以确保即使容器被关闭,证书也能持续存在。
至于标签。
traefik.http.routers.dashboard.rule=Host(`dashboard-flask-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 BasicAuth
中间件traefik.http.middlewares.auth.basicauth.users
定义登录用的用户名和散列密码
你可以使用htpasswd工具创建一个新的密码哈希值。
复制代码
随意使用env_file
,将用户名和密码存储为环境变量
复制代码
更新_Dockerfile.traefik_。
复制代码
接下来,旋转新的容器。
复制代码
创建表并应用种子。
复制代码
确保这两个URL是有效的。
另外,确保当你访问上述URL的HTTP版本时,你会被重定向到HTTPS版本。
最后,Let’s Encrypt证书的有效期为90天。Treafik会在幕后自动为你处理证书的更新,这样你就可以少操心一件事了
总结
在本教程中,我们介绍了如何将一个带有Postgres的Flask应用进行容器化开发。我们还创建了一个可用于生产的Docker Compose文件,设置了Traefik和Let’s Encrypt以通过HTTPS为应用程序提供服务,并启用了一个安全仪表板来监控我们的服务。
在实际部署到生产环境方面,你可能想使用一个。
你可以在flask-docker-traefikrepo中找到这些代码。