
介绍
当你用多种不同的技术开发和维护一个网络应用时会非常复杂和消耗时间。考虑通过轻量级的设计来减少复杂度和开发时间可以更加灵活和可控。作为Python平台的一个微型框架,Flask通过可以集成到项目中的插件,为开发者提供了一种可扩展的方式。为了扩展开发者的技术栈,MongoDB是为处理大规模和多变数据提供而设计的一种NoSQL数据库。开发者可以利用Docker技术简化应用的打包和部署工作。
Docker Compose通过允许你在单独文件里自定义基础组件来进一步简化了开发环境,这些组件包括你的应用服务、网络卷、挂载目录。使用Docker Compose提供了在运行多个Docker容器运行命令时的易用性。它允许你在一个单独的Compose文件里定义你所有的服务,并且你可以只用一条命令实现从配置文件创建并运行所有的服务。这保证在容器结构中贯穿着版本控制。Docker Compose使用项目名来实现环境的相互隔离,这允许你在一个主机上运行多个环境。
在这篇教程中你将会使用Flask、Nginx和Docker容器中的MongoDB实现创建、打包、运行接下来的网络应用。你将会定义全栈配置文件docker-compose.yml和Python,MongoDB,Nginx的配置文件。Flask需要一个网络服务器软件来支持HTTP请求,因此你也会用到Gunicorn,这是一个Python平台下支持WSGI HTTP请求的服务器,来运行应用。Nginx实现将请求反向代理到Gunicorn处理。
准备
为了接下来的内容,你需要:
一个按照“初始化服务器”教程配置好的具有sudo权限的非root用户。按照步骤1和步骤2“如何安装和使用Docker”的指引安装的Docker。
按照步骤1“如何安装Docker Compose”的指引安装的Docker Compose。
步骤1:完成Docker Compose的堆栈配置
在Docker上构建应用程序允许你根据在Docker Compose中所做的配置更改轻松地对基础结构进行版本设置。基础结构可以在单个文件中定义,并使用单个命令构建。在此步骤中,你将设置docker-compose.yml文件以运行Flask应用程序。
docker-compose.yml文件允许你将应用程序基础结构定义为单个服务。这些服务可以相互连接,并且每个服务都可以附加一个卷以进行持久存储。卷存储在Docker管理的主机文件系统的一部分中(Linux上的/var/lib/docker/volumes/)。
卷是在Docker中保存数据的最佳方式,因为卷中的数据可以导出或与其他应用程序共享。有关在Docker中共享数据的其他信息,请参阅“如何在Docker容器和主机之间共享数据”。
首先,在你的服务器的主目录中为应用创建一个目录:

进入到新建的目录下:

下一步,创建docker-compose.yml:

docker-compose.yml文件以标识docker compose文件版本的版本号开头。docker compose文件版本3的目标是docker引擎版本1.13.0+,这是此设置的先决条件。你还将添加下一步将定义的服务标记:

docker-compose.yml
现在将flask定义为docker-compose.yml文件中的第一个服务。添加以下代码以定义Flask服务:

docker-compose.yml
build属性定义了这次构建生成的上下文。在本例中,app目录将包含Dockerfile的应用程序文件夹。
你可以使用container_name属性为每个容器定义一个名称。image 属性指定镜像名称以及Docker镜像将被标记为什么。restart属性定义容器在你的情况下应该如何重新启动,在本例中被设置为“unless-stopped”。这意味着你的容器只有在Docker引擎停止/重新启动或显式停止容器时才会停止。使用unless-stopped属性的好处是,一旦Docker Engine重新启动或发生任何错误,容器将自动启动。
environment属性包含传递给容器的环境变量。你需要为环境变量MONGODB_PASSWORD提供安全密码。volumes属性定义服务正在使用的卷。在本例中,卷appdata装载在/var/www目录下的容器中。depends_on属性定义了Flask正常运行所依赖的服务。在本例中,flask服务将依赖于mongodb,因为mongodb服务充当应用程序的数据库。dependents确保flask服务仅在mongodb服务运行时运行。
networks属性指定flask服务前端和后端将有权访问的网络。
定义了flask服务之后,就可以将MongoDB 配置添加到文件中了。在本例中,你将使用官方版本的4.0.8 mongo镜像。在Flask服务之后,将以下代码添加到docker-compose.yml文件中:

docker-compose.yml
此服务的container_name属性是mongodb,重新启动策略为unless-stopped。使用command属性定义启动容器时将执行的命令。命令mongod--auth将禁用在没有凭据的情况下登录mongodb shell,这将通过要求身份验证来保护mongodb。
>>> 今日签到口令:l9s0 <<<
环境变量MONGO_INITDB_ROOT_USERNAME和MONGO_INITDB_ROOT_PASSWORD创建具有给定凭据的root用户,因此请确保用强密码替换占位符。
MongoDB 默认将数据存储在/data/db中,因此/data/db文件夹中的数据将写入命名卷mongodbdata中以供持久化。因此,在重新启动时不会丢失数据库。MongoDB服务不暴露任何端口,因此只能通过后端网络访问该服务。
接下来,你将为应用程序定义web服务器。在docker-compose.yml文件中添加以下代码以配置nginx:

docker-compose.yml
这里定义了构建的上下文,它是包含Dockerfile的nginx文件夹。使用“image ”属性,可以指定用于标记和运行容器的镜像。ports属性将nginx服务配置为通过:80和:443公开访问,volumes将nginxdata卷装载到/var/log/nginx目录下的容器中。
你已经将web服务器服务所依赖的服务定义为flask。最后,networks属性定义网络服务器服务将有权限访问前端。
接下来,你将创建网络桥接以允许容器相互通信。在文件末尾追加以下行:

docker-compose.yml
你为要连接的服务定义了两个网络-frontend(前端)和backend(后端)。前端服务,例如nginx,将连接到前端网络,因为它需要公开访问。后端服务,如MongoDB,将连接到后端网络,以防止未经授权访问该服务。
接下来,你将使用卷来持久化数据库、应用程序和配置文件。由于应用程序将使用数据库和文件,因此必须保留对它们所做的更改。卷由Docker管理并存储在文件系统中。将此代码添加到docker-compose.yml文件以配置卷:

docker-compose.yml
volumes部分声明应用程序将用于持久化数据的卷。在这里,你定义了mongodb data、appdata和nginxdata卷,分别用于保存mongodb数据库、flask应用程序数据和nginx web服务器日志。所有这些卷都使用本地驱动程序在本地存储数据。这些卷用于保存这些数据,以便防止在重新启动容器后,mongodb数据库和nginx web服务器日志等数据可能会丢失。
完整的docker-compose.yml文件如下:

docker-compose.yml
在验证配置后保存文件并退出编辑器。
你已经在docker-compose.yml文件中为整个应用程序堆栈定义了docker配置。现在你将继续为Flask 和web服务器编写Dockerfiles 。
步骤2-编写Flask和web服务器Dockerfiles
使用Docker,你可以构建容器以从名为DockerFile的文件运行应用程序。Dockerfile是一个工具,它允许你创建自定义映像,你可以使用这些映像安装应用程序所需的软件,并根据需要配置容器。你可以将你创建的自定义图像推送到Docker Hub或任何私有注册表。
在这一步中,你将为Flask和web服务器服务编写DockerFile。第一步,请为Flask应用程序创建应用程序目录:

接下来,在app目录中创建flask应用的dockerfile:

向文件中添加以下代码以自定义Flask容器:

app/Dockerfile
在这个Dockerfile中,你将在3.6.8-alpine3.9映像的基础上创建一个映像,该映像基于预先安装了python 3.6.8的alpine3.9。
env指令用于定义组和用户id的环境变量。
linux标准库(lsb)指定uid和gid0-99由系统静态分配。uid100-999为系统用户和组动态分配。uid1000-59999动态分配给用户帐户。记住你可以安全地分配1000的uid和gid,此外,你还可以通过更新组id和用户id来更改uid/gid,以符合你的要求。
WORKDIR 指令定义容器的工作目录。请确保用你的姓名和电子邮件地址替换“LABEL MAINTAINER”字段。
添加以下代码块以将Flask应用程序复制到容器中并安装必要的依赖项:

app/Dockerfile
以下代码将使用add指令将文件从本地应用程序目录复制到容器上的/var/www目录。接下来,Dockerfile将使用run指令来安装Gunicorn和requirements.txt文件中指定的包,稍后将在教程中创建这些包。
以下代码块添加新用户和组并初始化应用程序:

app/Dockerfile
默认情况下,Docker容器作为root用户运行。root用户可以访问系统中的所有内容,因此安全漏洞的影响可能是灾难性的。为了降低这种安全风险,将创建一个新的用户和组,该用户和组只能访问/var/www目录。
此代码将首先使用addgroup命令创建一个名为www的新组,-g标记将组id设置为先前在dockerfile中定义的env group_id=1000变量。
adduser-d-u$user_id-g www-s/bin/sh行创建一个用户id为1000的www用户,由env变量定义。如果用户的主目录不存在,则-s标记将创建用户的主目录,并将默认登录shell设置为/bin /SH。-G标记用于将用户的初始登录组设置为WWW,该WWW是由前一个命令创建的。
user命令定义在容器中运行的程序将使用www用户。Gunicorn将监听:5000,因此你将使用expose命令打开此端口。
最后,cmd[“gunicorn”、“-w”、“4”、“--bind”、“0.0.0.0:5000”、“wsgi”]行运行命令以启动Gunicorn服务器,其中四个工作线程监听端口5000。Gunicorn文档建议,服务器中的每个核心的数量通常应该在2到4个workers 之间(2 x$num_cores)+1作为开始的workers 数量。
完成的dockerfile如下所示:

app/Dockerfile
保存文件并退出编辑器。
接下来,创建一个新目录来保存nginx配置:

然后在nginx目录中为nginx web服务器创建Dockerfile:

向文件中添加以下代码以创建Dockerfile,该Dockerfile将为nginx容器生成镜像:

nginx/Dockerfile
这个 Nginx Dockerfile 使用了一个alpine基础镜像,这是一个小型linux发行版,具有最小的受攻击面和高安全性。
在RUN 指令中,你将安装nginx并创建符号链接,以将错误和访问日志发布到标准错误(/dev/stderr)和输出(/dev/stdout)。将错误发布到标准错误和输出是一个最佳实践,因为容器是短暂的,这样日志就被发送到Docker日志,从那里你可以将日志转发到日志服务,如Elastic 堆栈,以实现持久性。完成此操作后,将运行命令删除default.conf和/var/cache/apk/*以减小镜像的大小。在一次RUN指令中执行所有这些命令会减少镜像中的层数,这也会减小最终镜像的大小。
COPY 指令复制容器内的app.conf web服务器配置。expose指令确保容器侦听端口:80和:443,因为应用程序将在:80上运行,而:443是安全端口。
最后,cmd指令定义了启动nginx服务器的命令。
保存文件并退出文本编辑器。
现在Dockerfile已经准备好了,你可以配置Nginx反向代理来将流量路由到Flask应用程序。
步骤3-配置nginx反向代理
在这一步中,你将把nginx配置为反向代理服务器,以便将请求转发到:5000上的Gunicorn。反向代理服务器用于将客户端请求定向到适当的后端服务器。它提供了额外的抽象层和控制层,以确保客户机和服务器之间的网络流量的平滑流动。
开始创建nginx/conf.d目录:

要配置nginx,你需要在nginx/conf.d/文件夹中创建app.conf文件。app.conf文件包含反向代理服务器将请求转发到Gunicorn所需的配置。

将以下内容放入app.conf文件:

nginx/conf.d/app.conf
这将首先定义上游服务器,通常是指定用于路由或负载平衡的web或app服务器。
上游服务器app_server使用server指令定义服务器地址,该指令由容器名flask:5000标识。
Nginx web服务器的配置在server块中定义。listen指令定义了服务器将侦听传入请求的端口号。error_log和access_log指令定义用于写入日志的文件。proxy_pass指令用于设置上游服务器,以便将请求转发到http://app_server。
保存并关闭文件。
配置了Nginx服务器后,你可以继续创建Flask 待办事项应用的API。
步骤4-创建Flask待办事项应用的API
既然已经构建了环境,就可以构建应用程序了。在此步骤中,你将编写一个待办事项应用程序,该应用程序将保存和显示从post请求发送的待办事项。
在应用程序目录中创建requirements.txt文件开始:

此文件用于安装应用程序的依赖项。本教程的实现将使用Flask、Flask PyMongo和requests。在requirements.txt文件中添加以下内容:

app/requirements.txt
输入依赖内容并在保存文件后退出编辑器。
接下来,在app目录中创建app.py文件,该文件包括Flask应用程序代码:

在新的app.py文件中,输入代码以导入依赖项:

app/app.py
os包用于导入环境变量。从flask库导入Flask、request和jsonify对象,分别实例化应用程序、处理请求和发送json响应。从flask_pymongo导入PyMongo 对象以与MongoDB交互。
接下来,添加连接MongoDB所需的代码:

app/app.py
Flask(__name__) 将应用程序对象加载到application 变量中。接下来,代码使用os.environ从环境变量构建MongoDB连接字符串。将application 传递到pymongo()方法将为你提供mongo对象,而mongo对象又为你提供mongo.db中的db对象。
现在,你将添加代码以创建索引消息:

app/app.py
@application.route('/')定义了api的/get路由。在这里,index()函数使用jsonify方法返回一个json字符串。
接下来,添加/todo路由以列出所有待办事项:

app/app.py
@application.route('/todo')定义了api的/todo get路由,它返回数据库中的to-dos。db.todo.find()方法返回数据库中的所有待办事项。接下来,遍历_todos以构建一个条目,该条目只包含id和todo,将这些id和todo添加到一个数组中,最后将它们作为json返回。
接下来,添加用于创建待办事项的代码:

app/app.py
@application.route('/to do')定义了api的/todo post路由,它在数据库中创建一个待办事项。get_json(force=true)获取发送到路由的json,item用于构建将保存在to-do中的json。db.todo.insert_one(项)用于将一个项插入数据库。将待办事项保存到数据库中后,将返回一个json响应,状态代码为201 created。
现在添加运行应用程序的代码:

app/app.py
条件__name__ == "__main__"用于检查模块中的全局变量__name__ 是否是程序的入口点,是否为”__main__“,然后运行应用程序。如果“__name__”等于“__main__”,则if块中的application.run(host='0.0.0.0', port=ENVIRONMENT_PORT, debug=ENVIRONMENT_DEBUG)将执行以启动应用。
接下来,我们使用os.environ.get()从环境变量中获取ENVIRONMENT_DEBUG 和ENVIRONMENT_PORT的值,使用键值作为第一个参数,使用默认值作为第二个参数。application.run()设置应用程序的主机、端口和调试值。
已完成的app.py文件如下所示:

app/app.py
保存文件并退出编辑器。
接下来,在app目录中创建wsgi.py文件。

wsgi.py文件创建一个应用程序对象(或可调用的),以便服务器可以使用它。每次请求出现时,服务器使用此对象解析url并运行请求处理程序。
将下列内容放入wsgi.py文件中,保存文件,并退出文本编辑器:

app/wsgi.py
这个wsgi.py文件从app.py文件导入application对象,并为Gunicorn服务器创建一个application对象。
待办事项应用程序现在已就位,因此你可以开始在容器中运行该应用程序。
第5步-建造和运行容器
既然已经定义了docker-compose.yml文件中的所有服务及其配置,就可以启动容器了。
由于服务是在单个文件中定义的,因此需要发出单个命令来启动容器、创建卷和设置网络。这个命令还为flask应用程序和nginx web服务器构建镜像。运行以下命令生成容器:

当第一次运行这个命令时,它会下载所有必要的docker镜像,这可能需要一些时间。镜像下载并存储在本地计算机中后,Docker Compose将创建你的容器。-d标记对进程进行守护,这允许它作为后台进程运行。
生成过程完成后,使用以下命令列出正在运行的容器:

你会得到类似以下的输出:


CONTAINER ID是用于访问容器的唯一标识符。IMAGE 定义给定容器的镜像名称。NAMES 字段是创建容器所用的服务名称,类似于CONTAINER ID,它们可用于访问容器。最后,STATUS提供了关于容器状态的信息,不管它是正在运行、重新启动还是已停止。
你已经使用docker-compose命令从配置文件构建容器。在下一步中,你将为你的应用程序创建一个MongoDB 用户。
步骤6-为MongoDB数据库创建用户
默认情况下,MongoDB允许用户在没有凭据的情况下登录并授予无限权限。在这一步中,你将创建一个专用用户来访问MongoDB数据库,从而保护数据库的安全。
为此,你需要在docker-compose.yml文件环境变量MONGO_INITDB_ROOT_USERNAME和MONGO_INITDB_ROOT_PASSWORD中为MongoDB服务设置的根用户名和密码。一般来说,在与数据库交互时最好避免使用ROOT管理帐户。因此,你将为Flask应用程序创建一个专用的数据库用户,以及允许Flask应用程序访问的新数据库。
要创建新用户,首先在MongoDB容器上启动一个交互式shell:

使用docker exec命令在正在运行的容器中运行命令,并使用-it标志在容器中运行交互式shell。
进入容器后,登录MongoDB根管理帐户:

系统会提示你输入密码,作为docker-compose.yml文件中MONGO_INITDB_ROOT_PASSWORD 变量的值。可以通过在mongodb服务中为MONGO_INITDB_ROOT_PASSWORD设置新值来更改密码,在这种情况下,必须重新运行docker compose up-d命令。
运行show dbs;命令列出所有数据库:

你会得到类似以下的输出:

admin 数据库是向用户授予管理权限的特殊数据库。如果用户具有对管理数据库的读访问权限,则他们将具有对所有其他数据库的读写权限。由于输出列出了管理数据库,用户可以访问该数据库,因此可以对所有其他数据库进行读写。
保存第一个待办事项将自动创建MongoDB数据库。使用“use database” ,MongoDB 允许你切换到不存在的数据库。它在文档保存到集合时创建数据库。因此,数据库不是在这里创建的;当你使用api在数据库中保存第一个待办事项时,就会发生这种情况。执行use命令切换到flaskdb数据库:

接下来,创建允许访问此数据库的新用户:

此命令创建一个名为flaskuser的用户,该用户对flaskdb数据库具有读写访问权限。确保在pwd字段中使用安全密码。这里的user和pwd是你在flask服务的environment variables部分的docker-compose.yml文件中定义的值。
使用以下命令登录到经过身份验证的数据库:

现在已经添加了用户,请注销数据库。

最后,退出容器:

现在你已经为flask应用程序配置了一个专用数据库和用户帐户。数据库组件已经准备好,所以现在可以继续运行Flask待办事项应用程序。
第7步-运行Flask待办事项应用程序
现在你的服务已配置并正在运行,你可以通过在浏览器中输入http://your_server_ip来测试你的应用程序。另外,可以运行curl来查看flask的json响应:

你将收到以下回复:

Flask应用程序的配置从docker-compose.yml文件传递给应用程序。数据库连接的配置是使用flask服务的environment部分中定义的MONGODB_*变量设置的。
要测试所有内容,请使用flask api创建一个待办事项。可以通过对/to do路由的post curl请求来完成此操作:

此请求将待办事项保存到MongoDB并创建状态代码为201的响应:

你可以列出MongoDB中的所有待办事项,并向/to do路由发送get请求

Output

这样,你就可以运行mongodb后端的flask api,nginx作为反向代理部署到你的服务器上。对于生产环境,可以使用sudo systemctl enable docker来确保Docker服务在运行时自动启动。
结论
在本教程中,你使用Docker、MongoDB、Nginx和Gunicorn部署了一个Flask应用程序。现在你有了一个可以扩展的现代化无状态API应用程序。虽然可以通过使用docker container run这样的命令来实现此效果,但docker-compose.yml简化了你的工作,因为此堆栈可以放入版本控制中,并根据需要进行更新。
英文原文:https://www.digitalocean.com/community/tutorials/how-to-set-up-flask-with-mongodb-and-docker
译者:QL