将FastAPI应用程序部署到Elastic Beanstalk上完整教程

663 阅读11分钟

在本教程中,我们将了解将FastAPI应用程序部署到AWS Elastic Beanstalk的过程。

目标

在本教程结束时,你将能够。

  1. 解释什么是Elastic Beanstalk
  2. 初始化和配置Elastic Beanstalk
  3. 对运行在Elastic Beanstalk上的应用程序进行故障排除
  4. 将Elastic Beanstalk与RDS结合起来
  5. 通过AWS证书管理器获得一个SSL证书
  6. 使用SSL证书,在HTTPS上为你的应用程序提供服务

什么是Elastic Beanstalk?

AWS Elastic Beanstalk(EB)是一种易于使用的服务,用于部署和扩展Web应用程序。它连接了多个AWS服务,如计算实例(EC2)、数据库(RDS)、负载平衡器(应用负载平衡器)和文件存储系统(S3),仅举几例。EB允许你快速开发和部署你的网络应用,而不用考虑底层的基础设施。它支持用Go、Java、.NET、Node.js、PHP、Python和Ruby开发的应用程序。如果你需要配置自己的软件栈,或者部署用EB目前不支持的语言(或版本)开发的应用程序,EB也支持Docker。

典型的Elastic Beanstalk设置。

Elastic Beanstalk Architecture

AWS Elastic Beanstalk没有额外收费。你只需为你的应用程序所消耗的资源付费。

要了解更多关于Elastic Beanstalk的信息,请查看AWS ElasticBeanstalk官方文档什么是AWS Elastic Beanstalk?

弹性Beanstalk的概念

在进入教程本身之前,让我们看看与Elastic Beanstalk有关的几个关键概念

  1. 一个**应用程序**是Elastic Beanstalk组件的逻辑集合,包括环境、版本和环境配置。一个应用程序可以有多个版本
  2. 一个**环境**是运行一个应用程序版本的AWS资源的集合。
  3. 一个**平台**是操作系统、编程语言运行时间、Web服务器、应用服务器和Elastic Beanstalk组件的组合。

这些术语将在整个教程中使用。

项目设置

在本教程中,我们将部署一个名为fastapi-songs的简单FastAPI应用程序。

在跟随本教程的过程中,通过部署自己的应用程序来检查你的理解。

首先,从GitHub上的存储库中抓取代码:

$ git clone git@github.com:duplxey/fastapi-songs.git
$ cd fastapi-songs

创建一个新的虚拟环境并激活它:

$ python3 -m venv venv && source venv/bin/activate

安装需求并初始化数据库:

(venv)$ pip install -r requirements.txt (venv)$ python init_db.py

运行服务器:

(venv)$ uvicorn main:app --reload

打开你最喜欢的网络浏览器并导航到:

  1. http://localhost:8000- 应该显示 "fastapi-songs "文本
  2. http://localhost:8000/songs- 应该显示一个歌曲列表

在继续之前,请务必注册一个AWS账户。通过创建一个账户,你也可能有资格获得AWS免费层

Elastic Beanstalk命令行界面(EB CLI)允许你执行各种操作来部署和管理你的Elastic Beanstalk应用程序和环境。

有两种安装EB CLI的方式。

  1. 通过EB CLI安装程序
  2. 通过pip (awsebcli)

建议使用安装程序(第一个选项)全局安装EB CLI(在任何特定的虚拟环境之外),以避免可能的依赖性冲突。更多细节请参考这个解释

安装完EB CLI后,你可以通过运行来检查版本。

$ eb --version  
EB CLI 3.20.3 (Python 3.10.)

如果该命令不起作用,你可能需要将EB CLI添加到$PATH

EB CLI命令的列表和它们的描述可以在EB CLI命令参考中找到。

初始化Elastic Beanstalk

一旦我们运行了EB CLI,我们就可以开始与Elastic Beanstalk进行交互。让我们来初始化一个新的项目和一个EB环境。

初始化

在项目根目录下("fastapi-songs"),运行:

$ eb init

你会被提示一些问题。

默认区域

你的Elastic Beanstalk环境(和资源)的AWS区域。如果你不熟悉不同的AWS区域,请查看AWS区域和可用区。一般来说,你应该选择离你的客户最近的地区。请记住,资源价格因地区而异。

应用程序名称

这是你的Elastic Beanstalk应用程序的名称。我建议直接按回车键,使用默认的:"fastapi-songs"。

平台和平台分支

EB CLI会检测你是否使用Python环境。之后,它会给你不同的Python版本和亚马逊Linux版本,你可以使用。选择 "Python 3.8运行于64位的Amazon Linux 2"。

CodeCommit

CodeCommit是一个安全的、高度可扩展的、可管理的源码控制服务,它可以托管私有的 Git 仓库。我们不会使用它,因为我们已经在使用GitHub进行源代码控制。所以说 "不"。

SSH

为了以后连接到EC2实例,我们需要设置SSH。在提示时说 "是"。

密钥对

为了连接到EC2实例,我们需要一个RSA密钥对。继续并生成一个,它将被添加到你的"~/.ssh "文件夹。

在你回答完所有问题后,你会注意到在你的项目根中有一个名为".elasticbeanstalk "的隐藏目录。该目录应该包含一个config.yml文件,其中有你刚才提供的所有数据。

.elasticbeanstalk
└── config.yml

该文件应该包含类似的内容:

branch-defaults:
  master:
    environment: null
    group_suffix: null
global:
  application_name: fastapi-songs
  branch: null
  default_ec2_keyname: aws-eb
  default_platform: Python 3.8 running on 64bit Amazon Linux 2
  default_region: us-west-2
  include_git_submodules: true
  instance_profile: null
  platform_name: null
  platform_version: null
  profile: eb-cli
  repository: null
  sc: git
  workspace_type: Application

创建

接下来,让我们创建Elastic Beanstalk环境并部署应用程序:

$ eb create

同样,你会被提示几个问题。

环境名称

这代表了EB环境的名称。我建议坚持使用默认的:"fastapi-songs-env"。

在你的环境中添加└-env└-dev 后缀被认为是很好的做法,这样你可以很容易地将EB应用程序与环境区分开来。

DNS CNAME前缀

你的网络应用将可以在%cname%.%region%.elasticbeanstalk.com 。同样,使用默认值。

负载平衡器

负载平衡器在你的环境的实例中分配流量。选择 "应用程序"。

如果你想了解不同的负载平衡器类型,请查看Elastic Beanstalk环境的负载平衡器

Spot Fleet请求

Spot Fleet请求允许你根据你的标准,按需启动实例。我们不会在本教程中使用它们,所以说 "不"。

--

就这样,环境将被启动。

  1. 你的代码将被压缩并上传到一个新的S3 Bucket。
  2. 之后,各种AWS资源将被创建,如负载平衡器、安全和自动扩展组,以及EC2实例。

一个新的应用程序也将被部署。

这将需要大约三分钟,所以请随意拿杯咖啡。

部署完成后,EB CLI将修改*.elasticbeanstalk/config.yml*。

你的项目结构现在应该是这样的:

|-- .elasticbeanstalk
|   └-- config.yml
|-- .gitignore
|-- README.md
|-- database.py
|-- default.db
|-- init_db.py
|-- main.py
|-- models.py-- requirements.txt

状态

一旦你部署了你的应用程序,你可以通过运行来检查它的状态:

$ eb status

Environment details for: fastapi-songs-env
  Application name: fastapi-songs
  Region: us-west-2
  Deployed Version: app-82fb-220311_171256090207
  Environment ID: e-nsizyek74z
  Platform: arn:aws:elasticbeanstalk:us-west-2::platform/Python 3.8 running on 64bit Amazon Linux 2/3.3.11
  Tier: WebServer-Standard-1.0
  CNAME: fastapi-songs-env.us-west-2.elasticbeanstalk.com
  Updated: 2022-03-11 23:16:03.822000+00:00
  Status: Launching
  Health: Red

你可以看到,我们环境的当前健康状况是Red ,这意味着出了问题。先不要担心这个,我们会在接下来的步骤中解决这个问题。

你还可以看到AWS给我们分配了一个CNAME,这是我们EB环境的域名。我们可以通过打开浏览器并导航到CNAME来访问Web应用程序。

打开

$ eb open

这个命令将打开你的默认浏览器并导航到CNAME域名。你会看到502 Bad Gateway ,我们很快会在这里解决这个问题。

控制台

$ eb console

这个命令将在你的默认浏览器中打开Elastic Beanstalk控制台:

Elastic Beanstalk Console

同样,你可以看到环境的健康状况是 "Severe",我们将在下一步解决这个问题。

配置一个环境

在上一步中,我们尝试访问我们的应用程序,它返回502 Bad Gateway 。这背后有三个原因。

  1. Python需要PYTHONPATH ,以便在我们的应用程序中找到模块。
  2. 默认情况下,Elastic Beanstalk会尝试从application.py启动WSGI应用程序,但这并不存在。
  3. 如果没有另外指定,Elastic Beanstalk会尝试用Gunicorn来服务Python应用程序。Gunicorn本身与FastAPI不兼容,因为FastAPI使用最新的ASGI标准。

让我们来修复这些错误。

在项目根目录下创建一个名为".ebextensions "的新文件夹。在新创建的文件夹中创建一个名为01_fastapi.config的文件:

# .ebextensions/01_fastapi.config

option_settings:
  aws:elasticbeanstalk:application:environment:
    PYTHONPATH: "/var/app/current:$PYTHONPATH"
  aws:elasticbeanstalk:container:python:
    WSGIPath: "main:app"

注意:

  1. 我们将PYTHONPATH 设置为我们 EC2 实例上的 Python 路径(docs)。
  2. 我们把WSGIPath 改为我们的 WSGI 应用程序(docs)。

EB*.config*文件是如何工作的?

  1. 你可以有你想要的数量。
  2. 它们是按照以下顺序加载的。01_x, 02_x, 03_x, 等等。
  3. 你不必记住这些设置;你可以通过运行eb config ,列出你所有的环境设置。

如果你想了解更多关于高级环境定制的信息,请查看用配置文件进行高级环境定制

由于FastAPI本身不支持Gunicorn,我们将不得不修改我们的web 进程命令,以使用Uvicorn工作类

Uvicorn是为FastAPI推荐的ASGI网络应用服务器。

在项目根目录下创建一个名为Procfile的新文件:

# Procfile

web: gunicorn main:app --workers=4 --worker-class=uvicorn.workers.UvicornWorker

如果你要部署自己的应用程序,请确保将uvicorn 作为依赖项添加到requirements.txt中。

关于Procfile的更多信息,请查看用Procfile配置WSGI服务器

最后,我们必须告诉Elastic Beanstalk在部署新的应用版本时初始化数据库。在*.ebextensions/01_fastapi.config*的末尾添加以下内容:

# .ebextensions/01_fastapi.config

container_commands:
  01_initdb:
    command: "source /var/app/venv/*/bin/activate && python3 init_db.py"
    leader_only: true

现在,每次我们部署新的应用程序版本时,EB环境都会执行上述命令。我们使用了leader_only ,所以只有第一个EC2实例会执行它们(在我们的EB环境运行多个EC2实例的情况下)。

Elastic Beanstalk配置支持两种不同的命令部分,commandscontainer_commands。它们之间的主要区别是在部署过程中何时运行。

  1. commands 在应用程序和Web服务器设置完毕、应用程序版本文件被提取之前运行。
  2. container_commands 在应用程序和Web服务器设置完毕、应用程序版本档案提取完毕之后,但在应用程序版本部署之前(在文件从暂存文件夹移到最终位置之前)运行。

在这一点上,你的项目结构应该是这样的。

|-- .ebextensions
|   └-- 01_fastapi.config
|-- .elasticbeanstalk
|   └-- config.yml
|-- .gitignore
|-- Procfile
|-- README.md
|-- database.py
|-- default.db
|-- init_db.py
|-- main.py
|-- models.py
`-- requirements.txt

将修改提交到git并部署:

$ git add .
$ git commit -m "updates for eb"

$ eb deploy

你会注意到,如果你不提交,Elastic Beanstalk就不会检测到这些变化。这是因为EB与git集成,只检测已提交(更改)的文件。

部署完成后,运行eb open ,看看是否一切顺利。之后,在URL中添加/songs ,看看歌曲是否还能被显示。

耶!我们的应用程序的第一个版本现在已经部署。

配置RDS

如果你正在部署fastapi-songs,你会发现它默认使用SQLite数据库。虽然这对开发来说是完美的,但你通常希望在生产中使用一个更强大的数据库,如Postgres或MySQL。让我们看看如何把SQLite换成Postgres

本地Postgres

首先,让我们在本地运行Postgres。你可以从PostgreSQL下载,或者启动一个Docker容器。

$ docker run --name fastapi-songs-postgres -p 5432:5432 \
    -e POSTGRES_USER=fastapi-songs -e POSTGRES_PASSWORD=complexpassword123 \
    -e POSTGRES_DB=fastapi-songs -d postgres

检查容器是否正在运行:

$ docker ps -f name=fastapi-songs-postgres

CONTAINER ID   IMAGE      COMMAND                  CREATED              STATUS              PORTS                    NAMES
c05621dac852   postgres   "docker-entrypoint.s…"   About a minute ago   Up About a minute   0.0.0.0:5432->5432/tcp   fastapi-songs-postgres

现在,让我们尝试用我们的FastAPI应用程序连接到它。

database.py中像这样修改DATABASE_URL

# database.py

DATABASE_URL = \
    'postgresql://{username}:{password}@{host}:{port}/{database}'.format(
        username='fastapi-songs',
        password='complexpassword123',
        host='localhost',
        port='5432',
        database='fastapi-songs',
    )

之后,从create_engine 中删除connect_args ,因为check_same_thread 只对SQLite有要求

# database.py

engine = create_engine(
    DATABASE_URL,
)

接下来,安装psycopg2-binary,它是Postgres所需要的。

(venv)$ pip install psycopg2-binary==2.9.3

把它添加到requirements.txt中:

fastapi==0.75.0 psycopg2-binary==2.9.3 SQLAlchemy==1.4.32 uvicorn[standard]==0.17.6

删除现有的数据库*default.*db,然后初始化新的数据库:

(venv)$ python init_db.py

运行服务器:

(venv)$ uvicorn main:app --reload

通过检查http://localhost:8000/songs,确保歌曲仍然能够正确地被提供。

AWS RDS Postgres

要为生产设置Postgres,首先运行以下命令,打开AWS控制台:

$ eb console

点击左侧栏的 "配置",向下滚动到 "数据库",然后点击 "编辑"。

用以下设置创建一个数据库,并点击 "应用"。

  • 引擎:postgres
  • 引擎版本:12.9(较早的Postgres版本,因为db.t2.micro在13.1+中不可用)。
  • 实例类:db.t2.micro
  • 存储空间。5GB(应该是绰绰有余)
  • 用户名:选择一个用户名
  • 密码:选择一个强密码

如果你想保持在AWS免费层内,确保你选择db.t2.micro。RDS的价格会根据你选择的实例等级而成倍增加。如果你不想用micro ,请确保查看AWS PostgreSQL的价格

RDS settings

环境更新完成后,EB会自动将以下DB凭证传递给我们的FastAPI应用。

RDS_DB_NAME
RDS_USERNAME
RDS_PASSWORD
RDS_HOSTNAME
RDS_PORT

现在我们可以在database.py中使用这些变量来连接到我们的数据库。用以下内容替换DATABASE_URL

if 'RDS_DB_NAME' in os.environ:
    DATABASE_URL = \
        'postgresql://{username}:{password}@{host}:{port}/{database}'.format(
            username=os.environ['RDS_USERNAME'],
            password=os.environ['RDS_PASSWORD'],
            host=os.environ['RDS_HOSTNAME'],
            port=os.environ['RDS_PORT'],
            database=os.environ['RDS_DB_NAME'],
        )
else:
    DATABASE_URL = \
        'postgresql://{username}:{password}@{host}:{port}/{database}'.format(
            username='fastapi-songs',
            password='complexpassword123',
            host='localhost',
            port='5432',
            database='fastapi-songs',
        )

不要忘记在database.py 的顶部导入os 包:

import os

你最终的database.py文件应该是这样的。

将修改提交到git并部署。

$ git add . $ git commit -m "updates for eb"  $ eb deploy

等待部署完成。一旦完成,运行eb open ,在一个新的浏览器标签中打开你的应用程序。通过在/songs 列举歌曲,确保一切工作仍然正常。

使用证书管理器的HTTPS

本教程的这一部分要求你有一个域名。

需要一个便宜的域名来练习吗?一些域名注册商对'.xyz'域名有优惠。另外,你也可以在Freenom创建一个免费的域名。如果你没有域名,但仍想使用HTTPS,你可以创建并签署一个X509证书

为了通过HTTPS为你的应用程序提供服务,我们需要。

  1. 申请并验证SSL/TLS证书
  2. 将你的域名指向你的EB CNAME
  3. 修改负载平衡器以提供HTTPS服务
  4. 修改你的应用程序设置

请求和验证SSL/TLS证书

导航到AWS证书管理器控制台。点击 "请求一个证书"。将证书类型设置为 "公共 "并点击 "下一步"。在表格输入中输入你的完全合格域名,将 "验证方法 "设置为 "DNS验证",然后点击 "请求"。

AWS Request Public Certificate

然后你会被转到一个页面,在那里你可以看到你所有的证书。你刚刚创建的证书应该有一个 "待验证 "的状态。

要让AWS签发证书,你首先要证明你是该域名的所有者。在表中,点击证书,查看 "证书详情"。注意 "CNAME名称 "和 "CNAME值"。为了验证域名的所有权,你需要在域名的DNS设置中创建一个CNAME记录"。为此要使用 "CNAME名称 "和 "CNAME值"。一旦完成,亚马逊将需要几分钟的时间来接收域名的变化并颁发证书。状态应该从 "待验证 "变为 "已签发"。

将域名指向EB CNAME

接下来,你需要将你的域名(或子域名)指向你的EB环境CNAME。在你的域名的DNS设置中,添加另一条CNAME记录,其值为你的EB CNAME -- 例如,fastapi-songs-dev.us-west-2.elasticbeanstalk.com

等待几分钟,让你的DNS刷新,然后在你的浏览器中从你的域名的http:// 味道来测试。

修改负载均衡器以提供HTTPS服务

回到Elastic Beanstalk控制台,点击 "配置"。然后,在 "负载平衡器 "类别中,点击 "编辑"。点击 "添加监听器",创建一个监听器,详情如下。

  1. 端口 - 443
  2. 协议 - HTTPS
  3. SSL证书 - 选择你刚刚创建的证书

点击 "添加"。然后,滚动到页面底部,点击 "应用"。这将需要几分钟的时间来更新环境。

修改你的应用程序设置

接下来,我们需要对我们的FastAPI应用程序做一些修改。

我们需要将所有流量从HTTP重定向到HTTPS。有多种方法可以做到这一点,但最简单的方法是将Apache设置为代理主机。我们可以通过在*.ebextensions/01_fastapi.config*中的option_settings 的末尾添加以下内容来实现这一程序。

# .ebextensions/01_fastapi.config

option_settings:
  # ...
  aws:elasticbeanstalk:environment:proxy:  # new
    ProxyServer: apache                    # new

你最终的01_fastapi.config文件现在应该是这样的。

# .ebextensions/01_fastapi.config

option_settings:
  aws:elasticbeanstalk:application:environment:
    PYTHONPATH: "/var/app/current:$PYTHONPATH"
  aws:elasticbeanstalk:container:python:
    WSGIPath: "main:app"
  aws:elasticbeanstalk:environment:proxy:
    ProxyServer: apache
container_commands:
  01_initdb:
    command: "source /var/app/venv/*/bin/activate && python3 init_db.py"
    leader_only: true

接下来,在项目根目录下创建一个".platform "文件夹,并添加以下文件和文件夹。

-- .platform-- httpd-- conf.d-- ssl_rewrite.conf

ssl_rewrite.conf

# .platform/httpd/conf.d/ssl_rewrite.conf

RewriteEngine On
<If "-n '%{HTTP:X-Forwarded-Proto}' && %{HTTP:X-Forwarded-Proto} != 'https'">
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R,L]
</If>

你的项目结构现在应该是这样的:

|-- .ebextensions
|   └-- 01_fastapi.config
|-- .elasticbeanstalk
|   └-- config.yml
|-- .gitignore
├── .platform
│   └── httpd
│       └── conf.d
│           └── ssl_rewrite.conf
|-- Procfile
|-- README.md
|-- database.py
|-- default.db
|-- init_db.py
|-- main.py
|-- models.py-- requirements.txt

将修改提交到git并部署:

$ git add .
$ git commit -m "updates for eb"

$ eb deploy

现在,在你的浏览器中,你的应用程序的https:// 味道应该工作。试着进入http:// 的版本。你应该被重定向到https:// 。确保证书也被正确加载。

secure app

环境变量

在生产中,最好将特定环境的配置存储在环境变量中。通过Elastic Beanstalk,你可以通过两种不同的方式设置自定义环境变量。

通过EB CLI设置环境变量

你可以像这样执行一个命令来设置它们。

$ eb setenv VARIABLE_NAME='variable value'

你可以用一条命令设置多个环境变量,用空格隔开它们。这是推荐的方法,因为它只导致对EB环境的一次更新。

然后你可以通过os.environ ,在你的Python环境中访问这些变量。

比如说:

VARIABLE_NAME = os.environ['VARIABLE_NAME']

通过EB控制台的环境变量

通过eb open ,进入Elastic Beanstalk控制台。导航到 "配置" > "软件" > "编辑"。然后,向下滚动到 "环境属性"。

AWS Elastic Beanstalk Environment Variables

完成后,点击 "应用",你的环境就会更新。

同样,你可以通过os.environ ,访问Python中的环境变量。

调试Elastic Beanstalk

在使用Elastic Beanstalk时,如果你不知道如何访问日志文件,要找出出错的地方是非常令人沮丧的。在本节中,我们将讨论这个问题。

有两种方法可以访问日志。

  1. Elastic Beanstalk CLI或控制台
  2. SSH进入EC2实例

根据个人经验,我已经能够用第一种方法解决所有问题。

Elastic Beanstalk CLI或控制台

CLI:

$ eb logs

这个命令将从以下文件中获取最后100行。

/var/log/web.stdout.log
/var/log/eb-hooks.log
/var/log/nginx/access.log
/var/log/nginx/error.log
/var/log/eb-engine.log

运行eb logs ,相当于登录到EB控制台,导航到 "日志"。

我建议用管道将日志送到CloudWatch。运行下面的命令来启用它。

$ eb logs --cloudwatch-logs enable

你通常会在*/var/log/web.stdout.log/var/log/eb-engine.log*中发现FastAPI错误。

要了解更多关于Elastic Beanstalk日志的信息,请查看查看亚马逊EC2实例的日志

SSH进入EC2实例

要连接到运行FastAPI应用程序的EC2实例,请运行:

$ eb ssh

你会被提示第一次将该主机添加到你的已知主机中。说 "是"。这样,你现在就可以完全访问你的EC2实例了。请随意检查上一节中提到的一些日志文件。

请记住,Elastic Beanstalk会自动扩展和部署新的EC2实例。你在这个特定的EC2实例上所做的改变不会反映在新启动的EC2实例上。一旦这个特定的EC2实例被替换,你的改动将被抹去。

总结

在本教程中,我们走过了将FastAPI应用程序部署到AWS Elastic Beanstalk的过程。现在你应该对Elastic Beanstalk的工作原理有了一定的了解。通过回顾本教程开头的目标,进行一次快速的自我检查。

接下来的步骤。

  1. 你应该考虑创建两个独立的EB环境(devproduction )。
  2. 回顾Elastic Beanstalk环境的自动扩展组,了解如何配置触发器以自动扩展你的应用程序。

要删除我们在整个教程中创建的所有AWS资源,首先终止Elastic Beanstalk环境:

$ eb terminate

你需要手动删除SSL证书。

最后,你可以在GitHub上的fastapi-elastic-beanstalkrepo中找到最终版本的代码。