如何使用MongoDB和Docker设置Flask

来自菜鸟教程
跳转至:导航、​搜索

作为 Write for DOnations 计划的一部分,作者选择了 Internet Archive 来接收捐赠。

介绍

在构建和维护多种不同技术时,开发 Web 应用程序可能会变得复杂且耗时。 考虑为降低应用程序的复杂性和生产时间而设计的更轻量级选项可以产生更灵活和可扩展的解决方案。 作为基于 Python 构建的微型 Web 框架,Flask 为开发人员提供了一种可扩展的方式,通过可以集成到项目中的扩展来扩展他们的应用程序。 为了继续开发人员技术堆栈的可扩展性,MongoDB 是一个 NoSQL 数据库,旨在扩展和处理频繁的更改。 开发人员可以使用 Docker 来简化打包和部署应用程序的过程。

Docker Compose 允许您在单个文件中定义基础设施,包括应用程序服务、网络卷和绑定挂载,从而进一步简化了开发环境。 使用 Docker Compose 比运行多个 docker container run 命令提供了易用性。 它允许您在单个 Compose 文件中定义所有服务,并使用单个命令从您的配置中创建和启动所有服务。 这可确保在整个容器基础架构中都有版本控制。 Docker Compose 使用项目名称将环境彼此隔离,这允许您在单个主机上运行多个环境。

在本教程中,您将在 Docker 容器 中使用 Flask、Nginx 和 MongoDB 构建、打包和运行待办事项 Web 应用程序。 您将在 docker-compose.yml 文件中定义整个堆栈配置,以及 Python、MongoDB 和 Nginx 的配置文件。 Flask 需要一个 Web 服务器来服务 HTTP 请求,因此您还将使用 Gunicorn,它是一个 Python WSGI HTTP 服务器来服务应用程序。 Nginx 充当反向代理服务器,将请求转发到 Gunicorn 进行处理。

先决条件

要学习本教程,您将需要以下内容:

第 1 步 — 在 Docker Compose 中编写堆栈配置

在 Docker 上构建应用程序允许您根据在 Docker Compose 中所做的配置更改轻松地对基础架构进行版本控制。 基础设施可以在单个文件中定义并使用单个命令构建。 在这一步中,您将设置 docker-compose.yml 文件来运行您的 Flask 应用程序。

docker-compose.yml 文件允许您将应用程序基础架构定义为单独的服务。 这些服务可以相互连接,并且每个服务都可以附加一个 volume 以进行持久存储。 卷存储在由 Docker 管理的主机文件系统的一部分中(Linux 上的 /var/lib/docker/volumes/)。

卷是在 Docker 中持久化数据的最佳方式,因为卷中的数据可以导出或与其他应用程序共享。 有关在 Docker 中共享数据的更多信息,您可以参考如何在 Docker 容器和主机之间共享数据

首先,在服务器的主目录中为应用程序创建一个目录:

mkdir flaskapp

进入新创建的目录:

cd flaskapp

接下来,创建 docker-compose.yml 文件:

nano docker-compose.yml

docker-compose.yml 文件以标识 Docker Compose 文件版本 的版本号开头。 Docker Compose 文件版本 3 以 Docker 引擎版本 1.13.0+ 为目标,这是此设置的先决条件。 您还将添加您将在下一步中定义的 services 标签:

码头工人-compose.yml

version: '3'
services:

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

码头工人-compose.yml

. . .
  flask:
    build:
      context: app
      dockerfile: Dockerfile
    container_name: flask
    image: digitalocean.com/flask-python:3.6
    restart: unless-stopped
    environment:
      APP_ENV: "prod"
      APP_DEBUG: "False"
      APP_PORT: 5000
      MONGODB_DATABASE: flaskdb
      MONGODB_USERNAME: flaskuser
      MONGODB_PASSWORD: your_mongodb_password
      MONGODB_HOSTNAME: mongodb
    volumes:
      - appdata:/var/www
    depends_on:
      - mongodb
    networks:
      - frontend
      - backend

build 属性定义构建的 context。 在这种情况下,app 文件夹将包含 Dockerfile

您使用 container_name 属性为每个容器定义一个名称。 image 属性指定镜像名称以及 Docker 镜像将被标记为什么。 restart 属性定义了容器应该如何重新启动——在你的例子中是 unless-stopped。 这意味着您的容器只会在 Docker 引擎停止/重新启动或您明确停止容器时停止。 使用 unless-stopped 属性的好处是,一旦 Docker 引擎重新启动或发生任何错误,容器将自动启动。

environment 属性包含传递给容器的环境变量。 您需要为环境变量 MONGODB_PASSWORD 提供安全密码。 volumes 属性定义了服务正在使用的卷。 在您的情况下,卷 appdata 安装在 /var/www 目录的容器内。 depends_on 属性定义了 Flask 正常运行所依赖的服务。 在这种情况下,flask 服务将依赖于 mongodb,因为 mongodb 服务充当应用程序的数据库。 depends_on 确保 flask 服务仅在 mongodb 服务正在运行时运行。

networks 属性将 frontendbackend 指定为 flask 服务可以访问的网络。

定义了 flask 服务后,您就可以将 MongoDB 配置添加到文件中了。 在此示例中,您将使用官方的 4.0.8 版本 mongo 图像。 将以下代码添加到 flask service 之后的 docker-compose.yml 文件中:

码头工人-compose.yml

. . .
  mongodb:
    image: mongo:4.0.8
    container_name: mongodb
    restart: unless-stopped
    command: mongod --auth
    environment:
      MONGO_INITDB_ROOT_USERNAME: mongodbuser
      MONGO_INITDB_ROOT_PASSWORD: your_mongodb_root_password
      MONGO_INITDB_DATABASE: flaskdb
      MONGODB_DATA_DIR: /data/db
      MONDODB_LOG_DIR: /dev/null
    volumes:
      - mongodbdata:/data/db
    networks:
      - backend

此服务的 container_namemongodb,重启策略为 unless-stopped。 您使用 command 属性来定义将在容器启动时执行的命令。 命令 mongod --auth 将禁止在没有凭据的情况下登录 MongoDB shell,这将通过要求身份验证来保护 MongoDB。

环境变量 MONGO_INITDB_ROOT_USERNAMEMONGO_INITDB_ROOT_PASSWORD 使用给定的凭据创建 root 用户,因此请务必使用强密码替换占位符。

MongoDB 默认将其数据存储在 /data/db 中,因此 /data/db 文件夹中的数据将写入命名卷 mongodbdata 以进行持久化。 因此,在重新启动时您不会丢失数据库。 mongoDB 服务不暴露任何端口,因此只能通过 backend 网络访问该服务。

接下来,您将为您的应用程序定义 Web 服务器。 将以下代码添加到您的 docker-compose.yml 文件中以配置 Nginx:

码头工人-compose.yml

. . .
  webserver:
    build:
      context: nginx
      dockerfile: Dockerfile
    image: digitalocean.com/webserver:latest
    container_name: webserver
    restart: unless-stopped
    environment:
      APP_ENV: "prod"
      APP_NAME: "webserver"
      APP_DEBUG: "false"
      SERVICE_NAME: "webserver"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - nginxdata:/var/log/nginx
    depends_on:
      - flask
    networks:
      - frontend

在这里,您定义了 buildcontext,即包含 Dockerfilenginx 文件夹。 使用 image 属性,您可以指定用于标记和运行容器的图像。 ports 属性将配置 Nginx 服务通过 :80:443volumesnginxdata 卷挂载到容器内公开访问在 /var/log/nginx 目录下。

您已将 Web 服务器服务 depends_on 所在的服务定义为 flask。 最后,networks 属性定义了网络 Web 服务器服务将有权访问 frontend

接下来,您将创建 桥接网络 以允许容器相互通信。 将以下行附加到文件的末尾:

码头工人-compose.yml

. . .
networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

您为要连接的服务定义了两个网络 - frontendbackend。 前端服务,例如 Nginx,将连接到 frontend 网络,因为它需要可公开访问。 后端服务,比如MongoDB,会连接到backend网络,防止未经授权的访问服务。

接下来,您将使用卷来持久化数据库、应用程序和配置文件。 由于您的应用程序将使用数据库和文件,因此必须保留对它们所做的更改。 这些卷由 Docker 管理并存储在文件系统上。 将此代码添加到 docker-compose.yml 文件以配置卷:

码头工人-compose.yml

. . .
volumes:
  mongodbdata:
    driver: local
  appdata:
    driver: local
  nginxdata:
    driver: local

volumes 部分声明应用程序将用于持久化数据的卷。 在这里,您已经定义了卷 mongodbdataappdatanginxdata 分别用于持久化 MongoDB 数据库、Flask 应用程序数据和 Nginx Web 服务器日志。 所有这些卷都使用 local 驱动程序在本地存储数据。 这些卷用于保存这些数据,这样一旦你重新启动容器,MongoDB 数据库和 Nginx 网络服务器日志等数据可能会丢失。

您完整的 docker-compose.yml 文件将如下所示:

码头工人-compose.yml

version: '3'
services:

  flask:
    build:
      context: app
      dockerfile: Dockerfile
    container_name: flask
    image: digitalocean.com/flask-python:3.6
    restart: unless-stopped
    environment:
      APP_ENV: "prod"
      APP_DEBUG: "False"
      APP_PORT: 5000
      MONGODB_DATABASE: flaskdb
      MONGODB_USERNAME: flaskuser
      MONGODB_PASSWORD: your_mongodb_password
      MONGODB_HOSTNAME: mongodb
    volumes:
      - appdata:/var/www
    depends_on:
      - mongodb
    networks:
      - frontend
      - backend

  mongodb:
    image: mongo:4.0.8
    container_name: mongodb
    restart: unless-stopped
    command: mongod --auth
    environment:
      MONGO_INITDB_ROOT_USERNAME: mongodbuser
      MONGO_INITDB_ROOT_PASSWORD: your_mongodb_root_password
      MONGO_INITDB_DATABASE: flaskdb
      MONGODB_DATA_DIR: /data/db
      MONDODB_LOG_DIR: /dev/null
    volumes:
      - mongodbdata:/data/db
    networks:
      - backend

  webserver:
    build:
      context: nginx
      dockerfile: Dockerfile
    image: digitalocean.com/webserver:latest
    container_name: webserver
    restart: unless-stopped
    environment:
      APP_ENV: "prod"
      APP_NAME: "webserver"
      APP_DEBUG: "true"
      SERVICE_NAME: "webserver"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - nginxdata:/var/log/nginx
    depends_on:
      - flask
    networks:
      - frontend

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

volumes:
  mongodbdata:
    driver: local
  appdata:
    driver: local
  nginxdata:
    driver: local

验证您的配置后,保存文件并退出编辑器。

您已经在 docker-compose.yml 文件中为整个应用程序堆栈定义了 Docker 配置。 您现在将继续为 Flask 和 Web 服务器编写 Dockerfile。

第 2 步 — 编写 Flask 和 Web 服务器 Dockerfile

使用 Docker,您可以构建容器以从名为 Dockerfile 的文件运行应用程序。 Dockerfile 是一种工具,可让您创建自定义映像,您可以使用这些映像安装应用程序所需的软件并根据您的要求配置容器。 您可以将您创建的自定义镜像推送到 Docker Hub 或任何私有注册表。

在这一步中,您将为 Flask 和 Web 服务器服务编写 Dockerfile。 首先,为您的 Flask 应用程序创建 app 目录:

mkdir app

接下来,在 app 目录中为您的 Flask 应用程序创建 Dockerfile

nano app/Dockerfile

将以下代码添加到文件中以自定义您的 Flask 容器:

应用程序/Dockerfile

FROM python:3.6.8-alpine3.9

LABEL MAINTAINER="FirstName LastName <example@domain.com>"

ENV GROUP_ID=1000 \
    USER_ID=1000

WORKDIR /var/www/

在此 Dockerfile 中,您将在 3.6.8-alpine3.9 映像 之上创建一个映像,该映像基于预装 Python 3.6.8 的 Alpine 3.9。

ENV 指令用于为我们的组和用户 ID 定义环境变量。 Linux Standard Base (LSB) 指定 UID 和 GID 0-99 由系统静态分配。 UID 100-999 应该为系统用户和组动态分配。 UID 1000-59999 应该为用户帐户动态分配。 记住这一点,您可以安全地分配 1000 的 UID 和 GID,此外,您可以通过更新 GROUP_IDUSER_ID 来更改 UID/GID 以满足您的要求。

WORKDIR 指令定义了容器的工作目录。 请务必将 LABEL MAINTAINER 字段替换为您的姓名和电子邮件地址。

添加以下代码块以将 Flask 应用程序复制到容器中并安装必要的依赖项:

应用程序/Dockerfile

. . .
ADD ./requirements.txt /var/www/requirements.txt
RUN pip install -r requirements.txt
ADD . /var/www/
RUN pip install gunicorn

以下代码将使用 ADD 指令将文件从本地 app 目录复制到容器上的 /var/www 目录。 接下来,Dockerfile 将使用 RUN 指令安装 Gunicorn 和在 requirements.txt 文件中指定的包,您将在本教程后面创建。

以下代码块添加新用户和组并初始化应用程序:

应用程序/Dockerfile

. . .
RUN addgroup -g $GROUP_ID www
RUN adduser -D -u $USER_ID -G www www -s /bin/sh

USER www

EXPOSE 5000

CMD [ "gunicorn", "-w", "4", "--bind", "0.0.0.0:5000", "wsgi"]

默认情况下,Docker 容器以 root 用户身份运行。 root 用户可以访问系统中的所有内容,因此安全漏洞的影响可能是灾难性的。 为了减轻这种安全风险,这将创建一个只能访问 /var/www 目录的新用户和组。

此代码将首先使用 addgroup 命令创建一个名为 www 的新组。 -g 标志将组 ID 设置为之前在 Dockerfile 中定义的 ENV GROUP_ID=1000 变量。

adduser -D -u $USER_ID -G www www -s /bin/sh 行创建一个 www 用户,用户 ID 为 1000,由 ENV 变量定义。 -s 标志创建用户的主目录(如果不存在)并将默认登录 shell 设置为 /bin/sh-G 标志用于将用户的初始登录组设置为 www,它是由前面的命令创建的。

USER 命令定义容器中运行的程序将使用 www 用户。 Gunicorn 将监听 :5000,因此您将使用 EXPOSE 命令打开此端口。

最后,CMD [ "gunicorn", "-w", "4", "--bind", "0.0.0.0:5000", "wsgi"] 行运行命令以启动 Gunicorn 服务器,其中有四个工作人员在端口 5000 上侦听。 服务器中每个核心的数量通常应该在 2-4 个工人之间,Gunicorn 文档建议 (2 x $num_cores) + 1 作为开始的工人数量。

您完成的 Dockerfile 将如下所示:

应用程序/Dockerfile

FROM python:3.6.8-alpine3.9

LABEL MAINTAINER="FirstName LastName <example@domain.com>"

ENV GROUP_ID=1000 \
    USER_ID=1000

WORKDIR /var/www/

ADD . /var/www/
RUN pip install -r requirements.txt
RUN pip install gunicorn

RUN addgroup -g $GROUP_ID www
RUN adduser -D -u $USER_ID -G www www -s /bin/sh

USER www

EXPOSE 5000

CMD [ "gunicorn", "-w", "4", "--bind", "0.0.0.0:5000", "wsgi"]

保存文件并退出文本编辑器。

接下来,创建一个新目录来保存您的 Nginx 配置:

mkdir nginx

然后在 nginx 目录中为您的 Nginx Web 服务器创建 Dockerfile

nano nginx/Dockerfile

将以下代码添加到文件中以创建将为 Nginx 容器构建映像的 Dockerfile:

nginx/Dockerfile

FROM alpine:latest

LABEL MAINTAINER="FirstName LastName <example@domain.com>"

RUN apk --update add nginx && \
    ln -sf /dev/stdout /var/log/nginx/access.log && \
    ln -sf /dev/stderr /var/log/nginx/error.log && \
    mkdir /etc/nginx/sites-enabled/ && \
    mkdir -p /run/nginx && \
    rm -rf /etc/nginx/conf.d/default.conf && \
    rm -rf /var/cache/apk/*

COPY conf.d/app.conf /etc/nginx/conf.d/app.conf

EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]

这个 Nginx Dockerfile 使用 alpine 基础镜像,这是一个小型 Linux 发行版,具有最小的攻击面,旨在确保安全。

RUN 指令中,您正在安装 nginx 以及创建符号链接以发布错误和访问日志到标准错误 (/dev/stderr) 和输出 ([ X178X])。 将错误发布到标准错误和输出是最佳实践,因为容器是短暂的,这样做会将日志发送到 docker logs,然后您可以从那里将日志转发到 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 目录开始:

mkdir nginx/conf.d

配置Nginx,需要在nginx/conf.d/文件夹下创建一个app.conf文件,配置如下。 app.conf 文件包含反向代理将请求转发给 Gunicorn 所需的配置。

nano nginx/conf.d/app.conf

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

nginx/conf.d/app.conf

upstream app_server {
    server flask:5000;
}

server {
    listen 80;
    server_name _;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    client_max_body_size 64M;

    location / {
        try_files $uri @proxy_to_app;
    }

    location @proxy_to_app {
        gzip_static on;

        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $http_host;
        proxy_buffering off;
        proxy_redirect off;
        proxy_pass http://app_server;
    }
}

这将首先定义上游服务器,它通常用于指定用于路由或负载平衡的Web或应用服务器。

您的上游服务器 app_server 使用 server 指令定义服务器地址,该指令由容器名称 flask:5000 标识。

Nginx Web 服务器的配置在 server 块中定义。 listen 指令定义您的服务器将侦听传入请求的端口号。 error_logaccess_log 指令定义用于写入日志的文件。 proxy_pass 指令用于设置上游服务器将请求转发到 http://app_server

保存并关闭文件。

配置好 Nginx Web 服务器后,您可以继续创建 Flask 待办事项 API。

第 4 步——创建 Flask To-do API

现在您已经构建了您的环境,您已经准备好构建您的应用程序了。 在这一步中,您将编写一个待办事项 API 应用程序,该应用程序将保存和显示从 POST 请求发送的待办事项。

通过在 app 目录中创建 requirements.txt 文件开始:

nano app/requirements.txt

此文件用于安装应用程序的依赖项。 本教程的实现将使用 FlaskFlask-PyMongorequests。 将以下内容添加到 requirements.txt 文件中:

应用程序/requirements.txt

Flask==1.0.2
Flask-PyMongo==2.2.0
requests==2.20.1

输入需求后保存文件并退出编辑器。

接下来,创建 app.py 文件以包含 app 目录中的 Flask 应用程序代码:

nano app/app.py

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

应用程序/应用程序.py

import os
from flask import Flask, request, jsonify
from flask_pymongo import PyMongo

os 包用于导入环境变量。 从 flask 库中,您导入了 Flaskrequestjsonify 对象以分别实例化应用程序、处理请求和发送 JSON 响应。 从 flask_pymongo 您导入了 PyMongo 对象以与 MongoDB 交互。

接下来,添加连接 MongoDB 所需的代码:

应用程序/应用程序.py

. . .
application = Flask(__name__)

application.config["MONGO_URI"] = 'mongodb://' + os.environ['MONGODB_USERNAME'] + ':' + os.environ['MONGODB_PASSWORD'] + '@' + os.environ['MONGODB_HOSTNAME'] + ':27017/' + os.environ['MONGODB_DATABASE']

mongo = PyMongo(application)
db = mongo.db

Flask(__name__) 将应用程序对象加载到 application 变量中。 接下来,代码使用 os.environ 从环境变量构建 MongoDB 连接字符串。 将 application 对象传递给 PyMongo() 方法将为您提供 mongo 对象,从而为您提供来自 [X146X 的 db 对象]。

现在您将添加代码以创建索引消息:

应用程序/应用程序.py

. . .
@application.route('/')
def index():
    return jsonify(
        status=True,
        message='Welcome to the Dockerized Flask MongoDB app!'
    )

@application.route('/') 定义了 API 的 / GET 路由。 在这里,您的 index() 函数使用 jsonify 方法返回一个 JSON 字符串。

接下来,添加 /todo 路由以列出所有待办事项:

应用程序/应用程序.py

. . .
@application.route('/todo')
def todo():
    _todos = db.todo.find()

    item = {}
    data = []
    for todo in _todos:
        item = {
            'id': str(todo['_id']),
            'todo': todo['todo']
        }
        data.append(item)

    return jsonify(
        status=True,
        data=data
    )

@application.route('/todo') 定义了 API 的 /todo GET 路由,它返回数据库中的待办事项。 db.todo.find() 方法返回数据库中的所有待办事项。 接下来,您遍历 _todos 以构建一个 item,其中仅包含来自将它们附加到 data 的对象的 idtodo ] 数组,最后将它们作为 JSON 返回。

接下来,添加用于创建待办事项的代码:

应用程序/应用程序.py

. . .
@application.route('/todo', methods=['POST'])
def createTodo():
    data = request.get_json(force=True)
    item = {
        'todo': data['todo']
    }
    db.todo.insert_one(item)

    return jsonify(
        status=True,
        message='To-do saved successfully!'
    ), 201

@application.route('/todo') 定义了 API 的 /todo POST 路由,它在数据库中创建一个待办事项。 request.get_json(force=True) 获取您发布到路由的 JSON,而 item 用于构建将保存在待办事项中的 JSON。 db.todo.insert_one(item) 用于将一项插入数据库。 待办事项保存在数据库中后,您将返回一个带有 201 CREATED 状态代码的 JSON 响应。

现在添加代码来运行应用程序:

应用程序/应用程序.py

. . .
if __name__ == "__main__":
    ENVIRONMENT_DEBUG = os.environ.get("APP_DEBUG", True)
    ENVIRONMENT_PORT = os.environ.get("APP_PORT", 5000)
    application.run(host='0.0.0.0', port=ENVIRONMENT_PORT, debug=ENVIRONMENT_DEBUG)

条件 __name__ == "__main__" 用于检查模块中的全局变量 __name__ 是否是程序的入口点,是否为 "__main__",然后运行应用程序。 如果 __name__ 等于 "__main__",则 if 块内的代码将使用此命令 application.run(host='0.0.0.0', port=ENVIRONMENT_PORT, debug=ENVIRONMENT_DEBUG) 执行应用程序。

接下来,我们使用 os.environ.get() 从环境变量中获取 ENVIRONMENT_DEBUGENVIRONMENT_PORT 的值,使用键作为第一个参数,默认值作为第二个参数。 application.run() 设置应用程序的 hostportdebug 值。

完成的 app.py 文件将如下所示:

应用程序/应用程序.py

import os
from flask import Flask, request, jsonify
from flask_pymongo import PyMongo

application = Flask(__name__)

application.config["MONGO_URI"] = 'mongodb://' + os.environ['MONGODB_USERNAME'] + ':' + os.environ['MONGODB_PASSWORD'] + '@' + os.environ['MONGODB_HOSTNAME'] + ':27017/' + os.environ['MONGODB_DATABASE']

mongo = PyMongo(application)
db = mongo.db

@application.route('/')
def index():
    return jsonify(
        status=True,
        message='Welcome to the Dockerized Flask MongoDB app!'
    )

@application.route('/todo')
def todo():
    _todos = db.todo.find()

    item = {}
    data = []
    for todo in _todos:
        item = {
            'id': str(todo['_id']),
            'todo': todo['todo']
        }
        data.append(item)

    return jsonify(
        status=True,
        data=data
    )

@application.route('/todo', methods=['POST'])
def createTodo():
    data = request.get_json(force=True)
    item = {
        'todo': data['todo']
    }
    db.todo.insert_one(item)

    return jsonify(
        status=True,
        message='To-do saved successfully!'
    ), 201

if __name__ == "__main__":
    ENVIRONMENT_DEBUG = os.environ.get("APP_DEBUG", True)
    ENVIRONMENT_PORT = os.environ.get("APP_PORT", 5000)
    application.run(host='0.0.0.0', port=ENVIRONMENT_PORT, debug=ENVIRONMENT_DEBUG)

保存文件并退出编辑器。

接下来,在app目录下创建wsgi.py文件。

nano app/wsgi.py

wsgi.py 文件创建一个应用程序对象(或可调用对象),以便服务器可以使用它。 每次请求到来时,服务器都会使用此应用程序对象在解析 URL 时运行应用程序的请求处理程序。

将以下内容放入wsgi.py文件中,保存文件,退出文本编辑器:

应用程序/wsgi.py

from app import application

if __name__ == "__main__":
  application.run()

这个 wsgi.py 文件从 app.py 文件中导入应用程序对象,并为 Gunicorn 服务器创建一个应用程序对象。

待办事项应用程序现已到位,因此您已准备好开始在容器中运行应用程序。

第 5 步 - 构建和运行容器

现在您已经在 docker-compose.yml 文件中定义了所有服务及其配置,您可以启动容器了。

由于服务是在单个文件中定义的,因此您需要发出单个命令来启动容器、创建卷和设置网络。 此命令还为您的 Flask 应用程序和 Nginx Web 服务器构建映像。 运行以下命令来构建容器:

docker-compose up -d

第一次运行该命令时,它将下载所有必要的 Docker 映像,这可能需要一些时间。 一旦图像被下载并存储在您的本地机器中,docker-compose 将创建您的容器。 -d 标志守护进程,允许它作为后台进程运行。

构建过程完成后,使用以下命令列出正在运行的容器:

docker ps

您将看到类似于以下内容的输出:

OutputCONTAINER ID        IMAGE                           COMMAND                  CREATED             STATUS              PORTS                                      NAMES
f20e9a7fd2b9        digitalocean.com/webserver:latest   "nginx -g 'daemon of…"   2 weeks ago         Up 2 weeks          0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp   webserver
3d53ea054517        digitalocean.com/flask-python:3.6   "gunicorn -w 4 --bin…"   2 weeks ago         Up 2 weeks          5000/tcp                                   flask
96f5a91fc0db        mongo:4.0.8                     "docker-entrypoint.s…"   2 weeks ago         Up 2 weeks          27017/tcp                                  mongodb

CONTAINER ID 是用于访问容器的唯一标识符。 IMAGE 定义给定容器的图像名称。 NAMES 字段是创建容器的服务名称,类似于 CONTAINER ID 可用于访问容器。 最后,STATUS 提供有关容器状态的信息,无论是运行、重新启动还是停止。

您已使用 docker-compose 命令从配置文件构建容器。 在下一步中,您将为您的应用程序创建一个 MongoDB 用户。

第 6 步 — 为您的 MongoDB 数据库创建用户

默认情况下,MongoDB 允许用户在没有凭据的情况下登录并授予无限权限。 在这一步中,您将通过创建一个专用用户来访问它来保护您的 MongoDB 数据库。

为此,您需要在 docker-compose.yml 文件环境变量 MONGO_INITDB_ROOT_USERNAMEMONGO_INITDB_ROOT_PASSWORD 中为 mongodb 服务设置的 root 用户名和密码。 通常,在与数据库交互时最好避免使用 root 管理帐户。 相反,您将为 Flask 应用程序创建一个专用的数据库用户,以及一个允许 Flask 应用程序访问的新数据库。

要创建一个新用户,首先在 mongodb 容器上启动一个交互式 shell:

docker exec -it mongodb bash

您可以使用 docker exec 命令在正在运行的容器内运行命令,并使用 -it 标志在容器内运行交互式 shell。

进入容器后,登录 MongoDB root 管理帐户:

mongo -u mongodbuser -p

系统将提示您输入作为 docker-compose.yml 文件中 MONGO_INITDB_ROOT_PASSWORD 变量值的密码。 可以通过在 mongodb 服务中为 MONGO_INITDB_ROOT_PASSWORD 设置新值来更改密码,在这种情况下,您必须重新运行 docker-compose up -d 命令。

运行 show dbs; 命令列出所有数据库:

show dbs;

您将看到以下输出:

Outputadmin    0.000GB
config   0.000GB
local    0.000GB
5 rows in set (0.00 sec)

admin 数据库是一个特殊的数据库,它授予用户管理权限。 如果用户对 admin 数据库具有读取权限,则他们将对所有其他数据库具有读取和写入权限。 由于输出列出了 admin 数据库,因此用户可以访问该数据库,因此可以读取和写入所有其他数据库。

保存第一个待办事项会自动创建MongoDB数据库。 MongoDB 允许您使用 use database 命令切换到不存在的数据库。 当文档保存到集合时,它会创建一个数据库。 因此这里没有创建数据库; 当您通过 API 在数据库中保存第一个待办事项时,就会发生这种情况。 执行use命令切换到flaskdb数据库:

use flaskdb

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

db.createUser({user: 'flaskuser', pwd: 'your password', roles: [{role: 'readWrite', db: 'flaskdb'}]})
exit

此命令创建一个名为 flaskuser 的用户,该用户具有 readWrite 访问 flaskdb 数据库的权限。 请务必在 pwd 字段中使用安全密码。 这里的 userpwd 是您在 docker-compose.yml 文件中为 flask 服务的环境变量部分下定义的值。

使用以下命令登录到经过身份验证的数据库:

mongo -u flaskuser -p your password --authenticationDatabase flaskdb

现在您已经添加了用户,请退出数据库。

exit

最后,退出容器:

exit

您现在已经为您的 Flask 应用程序配置了一个专用的数据库和用户帐户。 数据库组件已准备就绪,现在您可以继续运行 Flask 待办事项应用程序。

第 7 步 — 运行 Flask 待办事项应用程序

现在您的服务已配置并正在运行,您可以通过在浏览器中导航到 http://your_server_ip 来测试您的应用程序。 此外,您可以运行 curl 来查看来自 Flask 的 JSON 响应:

curl -i http://your_server_ip

您将收到以下回复:

Output{"message":"Welcome to the Dockerized Flask MongoDB app!","status":true}

Flask 应用程序的配置从 docker-compose.yml 文件传递给应用程序。 使用 flask 服务的 environment 部分中定义的 MONGODB_* 变量设置有关数据库连接的配置。

要测试所有内容,请使用 Flask API 创建待办事项。 您可以通过对 /todo 路由的 POST curl 请求来执行此操作:

curl -i -H "Content-Type: application/json" -X POST -d '{"todo": "Dockerize Flask application with MongoDB backend"}' http://your_server_ip/todo

当待办事项保存到 MongoDB 时,此请求会导致状态码为 201 CREATED 的响应:

Output{"message":"To-do saved successfully!","status":true}

您可以通过对 /todo 路由的 GET 请求列出来自 MongoDB 的所有待办事项:

curl -i http://your_server_ip/todo
Output{"data":[{"id":"5c9fa25591cb7b000a180b60","todo":"Dockerize Flask application with MongoDB backend"}],"status":true}

有了这个,你已经将一个运行 MongoDB 后端的 Flask API Docker 化,并将 Nginx 作为部署到服务器的反向代理。 对于生产环境,您可以使用 sudo systemctl enable docker 来确保您的 Docker 服务在运行时自动启动。

结论

在本教程中,您使用 Docker、MongoDB、Nginx 和 Gunicorn 部署了一个 Flask 应用程序。 您现在拥有一个功能强大且可扩展的现代无状态 API 应用程序。 虽然您可以通过使用 docker container run 之类的命令来实现此结果,但 docker-compose.yml 可以简化您的工作,因为可以将此堆栈放入版本控制中并根据需要进行更新。

从这里您还可以查看我们进一步的 Python 框架教程