使用python和docker部署API

There are a few skills in software development that act as floodgates holding back a vast ocean of opportunity. When learning one of these skills, it seems that a whole new world is suddenly illuminated.

Ť这里是在软件开发中充当闸门阻碍机会的汪洋大海几个技能。 当学习其中一种技能时,似乎整个世界突然被照亮了。

With this article, we will cover what I believe is one of these skills. The ability to write code that can be deployed to the internet and communicated with — an API.

在本文中,我们将介绍我认为是其中一项技能。 编写可以部署到Internet并与之通信的代码的功能-API。

Several sub-skills feed into building a deployable API, which we will cover as we put our API together. Nonetheless, Rome wasn’t built in a day, and neither is mastery (nor even competency) — so this is not a zero-to-hero in five minutes article.

在构建可部署的API时需要考虑几个技巧,在将我们的API放在一起时将涉及这些技能。 但是,罗马不是一天建成的,也不是精通(甚至也不是能力)的,因此这不是五分钟内的零到英雄。

This article is an introduction, we will cover the essentials — nothing more, nothing less. At the end of the article, we will have a fully functional API hosted on the cloud.

本文是引言,我们将介绍要点-仅此而已。 在本文的结尾,我们将在云上托管一个功能齐全的API。

In short, we will cover —

简而言之,我们将介绍-

> Build an API with Flask
> Package our app with Docker
> Deploy with Google Cloud Platform (GCP)
> Test with Postman

You can find the GitHub repo for this article here.

您可以在此处找到本文的GitHub存储库。

用Flask构建API (Building an API with Flask)

We create a new folder that will contain a single Python script named app.py. This script will contain our API code, and will be structured like so:

我们创建一个新文件夹,其中将包含一个名为app.py Python脚本。 该脚本将包含我们的API代码,并且结构如下:

importsapp initializationAPI class
GET method
POST method
DELETE methodHTTP mapping

If the order of things get confusing — the full app.py script is at the end of this section, and you can find the project on GitHub here.

如果事情变得混乱,完整的app.py脚本在本节的结尾,您可以在GitHub上找到该项目。

建立 (Setup)

Imports and App Initialization

导入和应用初始化

Here we set up our bare minimum in imports and initialize our API flask app like so:

在这里,我们在导入中设置了最低要求,并像这样初始化API flask应用程序:

from flask import Flask
from flask_restful import Resource, Api, reqparse
import pandas as pd
import osapp = Flask(__name__)
api = Api(app)

API类 (API Class)

Inside our API script, we need to define a class that will contain our HTTP methods, GET, POST, and DELETE.

在我们的API脚本中,我们需要定义一个包含HTTP方法GET,POST和DELETE的类。

Flask needs to know that this class is an entry point for our API, and so we pass Resource in with the class definition. We will create a class called Places:

Flask需要知道此类是我们API的入口点,因此我们将Resource与类定义一起传递。 我们将创建一个名为Places的类:

class Places(Resources):

We also need to tell Flask where the entry point to this API class is. For example, if our API was located at www.api.com we may want to specify that entry to the Places class is provided at www.api.com/places.

我们还需要告诉Flask该API类的入口在哪里。 例如,如果我们的API位于www.api.com我们可能希望指定在www.api.com /places提供Places类的条目。

We can do this using the add_resource method:

我们可以使用add_resource方法做到这一点:

api.add_resource(Places, '/places')

This method is placed outside and following the class definition.

该方法位于类定义的外部并遵循该类的定义。

Of course, this means we can add multiple classes and entry points to our API. This can be particularly useful for more complex services — although we will stick with just one in this article.

当然,这意味着我们可以向我们的API添加多个类和入口点。 对于更复杂的服务,这可能特别有用-尽管我们在本文中仅坚持其中的一项。

For the sake of simplicity, we will manipulate data embedded within the code as a Python dictionary. It looks like this:

为了简单起见,我们将把嵌入代码中的数据作为Python字典进行处理。 看起来像这样:

DATA = {
'places':
['rome',
'london',
'new york city',
'los angeles',
'brisbane',
'new delhi',
'beijing',
'paris',
'berlin',
'barcelona']
}

GET

得到

We use this method to GET data, which in our case is simply our DATA dictionary.

我们使用这种方法来获取数据,在我们的例子中,它只是我们的DATA字典。

def get(self):# return our data and 200 OK HTTP codereturn {'data': DATA}, 200

POST

开机自检

The POST method is used to add data to our DATA['places'] list.

POST方法用于将数据添加到我们的DATA['places']列表中。

def post(self):# parse request argumentsparser = reqparse.RequestParser()
parser.add_argument('location', required=True)
args = parser.parse_args()# check if we already have the location in places listif args['location'] in DATA['places']:# if we do, return 401 bad requestreturn {
'message': f"'{args['location']}' already exists."
}, 401
else:# otherwise, add the new location to placesDATA['places'].append(args['location'])
return {'data': DATA}, 200

What is happening here?

这是怎么回事

  • The reqparse library allows us to parse arguments passed to the POST request. For example, if our places API is at the web address api.com/places we can specify the location of our new place to add like so:

    reqparse库允许我们解析传递给POST请求的参数。 例如,如果places API位于网址api.com/places我们可以指定要添加的新位置的location ,如下所示:

api.com/places?location=sydney
  • After pulling out the location argument, we need to check if it already exists in DATA['places'] — which we do with our if statement. If the data already exists, we simply return 409 Conflict with a brief message explaining that the location provided already exists.

    取出location参数后,我们需要检查它是否已经存在于DATA['places'] -我们使用if语句进行此操作。 如果数据已经存在,我们仅返回409 Conflict并附带一条简短消息,说明提供的位置已存在。

  • Otherwise, we can add the new location to our DATA['places'] list and then return our new DATA alongside a 200 OK HTTP code.

    否则,我们可以将新位置添加到DATA['places']列表中,然后返回新的DATA200 OK HTTP代码。

DELETE

删除

We use DELETE as an equal and opposite action of POST. The user specifies a location to remove from the DATA['places'] list.

我们将DELETE用作POST的同等相反的动作。 用户指定要从DATA['places']列表中删除的位置。

def delete(self):# parse request argumentsparser = reqparse.RequestParser()
parser.add_argument('location', required=True)
args = parser.parse_args()# check if we have given location in places listif args['location'] in DATA['places']:# if we do, remove and return data with 200 OKDATA['places'].remove(args['location'])
return {'data': DATA}, 200
else:# if location does not exist in places list return 404return {
'message': f"'{args['location']}' does not exist."
}, 404

What is happening here?

这是怎么回事

As an equal and opposite operation, we slightly modify the POST logic:

作为相等且相反的操作,我们稍微修改POST逻辑:

  • We still use reqparse in the same way.

    我们仍然以相同的方式使用reqparse

  • With the location argument, we again use the same if statement to check if it already exists in DATA['places']. If it does, we remove it from DATA['places'] and return the new DATA alongside a 200 OK HTTP code.

    使用location参数,我们再次使用相同的if语句来检查它是否已经存在于DATA['places'] 。 如果是这样,我们remove其从DATA['places'] remove ,并返回新的DATA200 OK HTTP代码。

  • Otherwise, we return 404 Not Found with a message explaining that the location provided does not exist in DATA['places'].

    否则,我们返回404 Not Found并显示一条消息,说明提供的位置在DATA['places']中不存在。

(Altogether)

If we put all of these parts together, we will get the following script:

如果将所有这些部分放在一起,我们将获得以下脚本:

from flask import Flask
from flask_restful import Resource, Api, reqparse
import os


app = Flask(__name__)
api = Api(app)


DATA = {
    'places':
        ['rome',
         'london',
         'new york city',
         'los angeles',
         'brisbane',
         'new delhi',
         'beijing',
         'paris',
         'berlin',
         'barcelona']
}


class Places(Resource):
    def get(self):
        # return our data and 200 OK HTTP code
        return {'data': DATA}, 200


    def post(self):
        # parse request arguments
        parser = reqparse.RequestParser()
        parser.add_argument('location', required=True)
        args = parser.parse_args()


        # check if we already have the location in places list
        if args['location'] in DATA['places']:
            # if we do, return 401 bad request
            return {
                'message': f"'{args['location']}' already exists."
            }, 401
        else:
            # otherwise, add the new location to places
            DATA['places'].append(args['location'])
            return {'data': DATA}, 200


    def delete(self):
        # parse request arguments
        parser = reqparse.RequestParser()
        parser.add_argument('location', required=True)
        args = parser.parse_args()


        # check if we have given location in places list
        if args['location'] in DATA['places']:
            # if we do, remove and return data with 200 OK
            DATA['places'].remove(args['location'])
            return {'data': DATA}, 200
        else:
            # if location does not exist in places list return 404 not found
            return {
                'message': f"'{args['location']}' does not exist."
                }, 404




api.add_resource(Places, '/places')


if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 8080)))

与Docker打包 (Package with Docker)

The next stage is where we will create a Docker image. There are two similar terms here — image and container.

下一阶段是创建Docker映像的地方。 这里有两个相似的术语-图像和容器。

An image is essentially a blueprint — whereas a container is a structure/deployment that is built from the blueprint.

图像本质上是一个蓝图,而容器是根据该蓝图构建的结构/部署。

First, we will create our image, before deploying it to Google cloud (where it will become a container).

首先,我们将创建映像 ,然后将其部署到Google云(它将成为容器)。

There are three key steps in creating our Docker image:

创建Docker映像的三个关键步骤:

  1. Get Docker

    获取Docker
  2. Setup our Dockerfile

    设置我们的Dockerfile
  3. Build our image

    建立我们的形象

1.The Docker docs will be able to guide you through the installation process much better than I can! You can find them here.

1. Docker文档将比我能更好地指导您完成安装过程! 您可以在这里找到它们。

For an overview of Docker on Windows or Mac, check out this video:

有关Windows或Mac上Docker的概述,请观看以下视频:

演示地址

1:46:21 for Docker on Windows, 1:53:22 for Docker on Mac.

1:46:21适用于Windows上的Docker,1:53:22适用于Mac上的Docker。

2.Because we are deploying our API as a docker container, we need to include a container setup file called a Dockerfile inside our directory.

2.因为我们将API部署为Docker容器,所以我们需要在目录中包含一个名为Dockerfile的容器设置文件。

Inside our Dockerfile, we will RUN the pip install command for three Python libraries — all of which will specify using a requirements.txt file.

在Dockerfile内,我们将为三个Python库RUN pip install命令-所有这些库都将使用requirements.txt文件指定。

For our simple API, it looks like this:

对于我们的简单API,它看起来像这样:

flask
flask-restful
gunicorn

In our Dockerfile, we outline our image (the container blueprint). Docker will read the Dockerfile which provides ‘build’ instructions.

Dockerfile中,我们概述了映像(容器蓝图)。 Docker将阅读提供“构建”说明的Dockerfile

FROM python:3.6-slim-busterWORKDIR /app
COPY . .RUN pip install -r requirements.txtCMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 app:app

What is happening here?

这是怎么回事

  • We initialize our image using another official image withFROM python:3.6-slim-buster. This script initializes the official Docker Python 3.6 ‘slim-buster’ image — a full list of official Python images is available here.

    我们使用FROM python:3.6-slim-buster使用另一个官方映像初始化映像。 该脚本会初始化Docker Python 3.6官方“App.svelte”映像- 此处提供了完整的Python官方映像列表。

  • Next, we set our active directory inside the image to /app with WORKDIR /app - this is the structure that our Google Cloud instance will expect.

    接下来,我们使用WORKDIR /app将映像内的活动目录设置为/app WORKDIR /app这是Google Cloud实例所期望的结构。

  • Now we COPY everything from our source directory to our image filesystem. In this case, from the directory Dockerfile is located — to the active image directory /app.

    现在,我们COPY从源目录到映像文件系统的所有内容。 在这种情况下,从目录Dockerfile定位到活动映像目录/app

  • With our list of dependencies requirements.txt now inside the image filesystem, we RUN pip install recursively -r through each line of requirements.txt. Essentially translating into:

    现在,在映像文件系统内部有了依赖项requirements.txt的依赖关系列表之后,我们便通过遍历了requirements.txt每一行,以递归方式RUN pip install -r 。 本质上翻译为:

pip install flask
pip install flask-restful
pip install gunicorn
  • Finally, we initialize our HTTP server using the gunicorn package. We use the CMD instruction to execute gunicorn and pass several server setup parameters.

    最后,我们使用gunicorn软件包初始化HTTP服务器。 我们使用CMD指令执行gunicorn并传递几个服务器设置参数。

3.Our final step is to build the Docker image, which we will later upload to Google Container Registry.

3.我们的最后一步是构建Docker映像,稍后将其上传到Google Container Registry。

To build the image, open a CLI, navigate to the directory containing our files and type:

要构建映像,请打开CLI,导航至包含我们文件的目录,然后键入:

docker build -t tut-api .
  • Here, docker build is the Docker image build command.

    在这里, docker build是Docker映像构建命令。

  • We use the -t flag to specify our image name.

    我们使用-t标志来指定图像名称。

  • tut-api is simply the name of our image, call it anything you want.

    tut-api只是我们图像的名称,可以tut-api命名。

  • . tells docker to include everything from the current directory in the image (don’t forget this, I do almost every time).

    . 告诉docker在映像中包括当前目录中的所有内容(别忘了,我几乎每次都这样做)

Finally, we can move onto deploying our API to the Cloud.

最后,我们可以继续将API部署到云中。

部署到云端 (Deploy to the Cloud)

Here, we will use Google Cloud Platform’s (GCP) Container Registry to store our Docker image, and Cloud Run to deploy it.

在这里,我们将使用Google Cloud Platform(GCP)的容器注册表来存储Docker映像,并使用Cloud Run进行部署。

项目和容器注册 (Project and Container Registry)

First, we need to set up a project. We start by navigating to the project selector page in the Cloud Console and clicking Create Project.

首先,我们需要建立一个项目。 首先,导航到Cloud Console中的项目选择器页面,然后单击Create Project

Project ID — we will need this later. 项目ID-我们稍后将需要它。

Here we simply give our project a name — mine is medium.

在这里,我们只是给我们的项目起一个名字-我的是中号

Next, we need to navigate to Container Registry (GCR) in Google Console. If we have set up our project correctly, we will see a screen that looks like this:

接下来,我们需要在Google控制台中导航到Container Registry (GCR)。 如果我们正确设置了项目,我们将看到一个类似以下的屏幕:

Our empty Google Container Registry (GCR) screen. 空的Google Container Registry(GCR)屏幕。

云构建 (Cloud Build)

We will be using Cloud Build to build our image into a container. Before we can do this, we need to enable the Cloud Build API for our project. Simply click here and select the project you’re working on:

我们将使用Cloud Build将我们的映像构建到容器中。 在此之前,我们需要为我们的项目启用Cloud Build API。 只需单击此处,然后选择您正在从事的项目:

Registration of a project for the Cloud Build API. 针对Cloud Build API的项目注册。

Now we have our project setup, we need to use Google Cloud SDK to push our API Docker image to GCR — which we download from here.

现在,我们已经完成了项目设置,我们需要使用Google Cloud SDK将API Docker映像推送到GCR(可以从此处下载)。

认证方式 (Authentication)

After installation, we need to log in to gcloud. To do so, in CMD prompt (or your equivalent CLI) simply type:

安装后,我们需要登录到gcloud。 为此,在CMD提示符(或等效的CLI)中,只需键入:

gcloud auth login

This command will open our web browser, allowing us to login as usual. We then configure Docker to use our credentials with:

此命令将打开我们的Web浏览器,使我们能够照常登录。 然后,我们将Docker配置为通过以下方式使用我们的凭据:

gcloud auth configure-docker

上载到容器注册表 (Uploading to Container Registry)

We will use GCR to store our image. To build our container and save it to the registry — we navigate to our API directory and submit it:

我们将使用GCR存储我们的图像。 要构建容器并将其保存到注册表中,请导航至我们的API目录并submit

gcloud builds submit --tag gcr.io/[PROJECT-ID]/tut-api
  • gcloud builds submit is the command we use to submit our image to Cloud Build.

    gcloud builds submit是用于将映像提交到Cloud Build的命令。

Our registry location is provided as the target image, where:

我们的注册表位置作为目标映像提供 ,其中:

  • gcr.io is the GCR hostname.

    gcr.io GCR主机名

  • [PROJECT-ID] is our project ID, we saw this when creating our project — for me it is medium-286319.

    [PROJECT-ID] 是我们的项目ID,我们在创建项目时就看到了这一点-对我来说是 medium-286319

  • tut-api is our image name.

    tut-api 是我们的图片名称。

Now, if we navigate back to our registry, we should see our newly uploaded Docker image (it can take some time to build — check your Cloud Build dashboard):

现在,如果我们导航回到注册表,我们应该看到我们新上传的Docker映像(可能需要一些时间来构建-检查您的Cloud Build仪表板):

使用Cloud Run进行部署 (Deploying with Cloud Run)

With our Docker container in place, we can deploy it to the web with Cloud Run.

有了我们的Docker容器,我们可以使用Cloud Run将其部署到Web上。

We open the Cloud Run console by clicking on the top-left navigation button (in the GCP console) and selecting Cloud Run.

通过单击左上角的导航按钮(在GCP控制台中 )并选择Cloud Run来打开Cloud Run控制台。

Next, we will see the Cloud Run interface, we deploy our container by clicking Create Service:

接下来,我们将看到Cloud Run界面,我们通过单击Create Service来部署容器:

Next, we (2) enter our preferred deployment settings, (3–4) choose our container image, and (5) create our deployment!

接下来,我们(2)输入首选的部署设置,(3–4)选择我们的容器映像,(5)创建我们的部署!

After our API has been deployed, we can find the URL for the API at the top of the Cloud Run console.

部署我们的API之后,我们可以在Cloud Run控制台顶部找到该API的URL。

Cloud Run. Cloud Run中 API的URL。

测试我们的部署 (Testing Our Deployment)

For testing APIs, Postman is your friend — find the download link here.

要测试API, Postman是您的朋友-在此处找到下载链接 。

Once installed, open Postman and enter the web address of your API — for example:

安装后,打开Postman并输入API的网址-例如:

https://tut-api-2czbipghsq-ew.a.run.app/

https://tut-api-2czbipghsq-ew.a.run.app/

In our code, we specified the location of our Places class to be /places:

在我们的代码中,我们将Places类的位置指定为/places

api.add_resource(Places, '/places')

Because of this, we know to access the methods contained within Places, we must add /places to our API address:

因此,我们知道要访问Places包含的方法,我们必须在API地址中添加/places

https://tut-api-2czbipghsq-ew.a.run.app/places

https://tut-api-2czbipghsq-ew.a.run.app /places

得到 (GET)

For the GET request, this is all we need — enter it into the address bar, click GET in the dropdown, and click Send.

对于GET请求,这就是我们所需要的—将其输入到地址栏中,在下拉列表中单击GET ,然后单击发送

GET request to GET请求发送到 https://tut-api-2czbipghsq-ew.a.run.app/places.https://tut-api-2czbipghsq-ew.a.run.app/placesDATA response, with a DATA响应,右上角的 Status: 200 OK in the top-right. 状态为:200 OK

Let’s test the other methods too — remember to add our location argument for both the POST and DELETE requests, and to update the request type (in the dropdown).

让我们也测试其他方法-记住为POST和DELETE请求添加location参数,并更新请求类型(在下拉列表中)。

开机自检 (POST)

POST in the dropdown, add POST ,将 ?location=sydney to the address bar, and click ?location = sydney添加到地址栏中,然后点击 Send. We will return our Send 。 我们将返回悉尼附加的 DATA appended with sydney. 数据
POST, we change sydney to POST ,我们在地址栏中将悉尼更改为 rome in the address bar and click 罗马 ,然后单击 Send. We will return a 发送 。 我们将返回 401 Unauthorized HTTP code with a simple message telling us 401未经授权的 HTTP代码,并带有一条简单消息告诉我们 rome already exists. rome已经存在。

删除 (DELETE)

DELETE in the dropdown, we keep DELETE ,我们将 location as 位置保留为 rome, and click rome ,然后点击 Send. We will return our Send 。 我们将退回 DATA with 罗马rome removed. 数据返回。
DELETE tokyo (which doesn’t exist) — we will return a 删除东京 (不存在)-我们将返回 404 Not Found HTTP code with a message telling us 404 Not Found HTTP代码,并显示一条消息,告诉我们 tokyo doesn’t exist. 东京不存在。

打开闸门 (Open the Floodgates)

That’s all for this simple toy example of an API built in Flask and deployed using Docker on the GCP.

这就是Flask内置的API的简单玩具示例的全部内容,并使用Docker在GCP上进行了部署。

Simply put, building an API and deploying it to the web is an incredibly easy and efficient process. It’s not hard and unlocks a vast expanse of opportunity to us.

简而言之,构建一个API并将其部署到Web上是一个非常简单有效的过程。 这并不难,并且为我们带来了广阔的机会。

I hope you enjoyed the article — if you have any questions or suggestions, feel free to reach out via Twitter or in the comments below.

我希望您喜欢这篇文章-如果您有任何疑问或建议,请随时通过Twitter或以下评论与我们联系。

Thanks for reading!

谢谢阅读!

If you enjoyed this article and are interested in widening your skill sets further in web-enabled tech — try out my introduction to Angular for data science here:

如果您喜欢本文,并且有兴趣进一步扩展基于Web的技术,请在这里尝试我对Angular的数据科学介绍:

翻译自: https://towardsdatascience.com/deploy-apis-with-python-and-docker-4ec5e7986224

你可能感兴趣的:(docker,python)