本文概述
在构建Web应用程序时, 一旦做出承诺, 将会做出很多选择, 它们可能会在将来帮助或阻碍你的应用程序。语言, 框架, 托管和数据库等选择至关重要。
这样的选择之一是使用面向服务的体系结构(SOA)还是传统的整体应用程序创建基于服务的应用程序。这是一个常见的体系结构决策, 会影响到初创企业, 大型企业和企业公司。
面向服务的体系结构被许多著名的独角兽和高科技公司(例如Google, Facebook, Twitter, Instagram和Uber)使用。看来, 这种架构模式适用于大型公司, 但是它对你有用吗?
使用AWS Lambda的面向服务的体系结构:分步教程
鸣叫
在本文中, 我们将介绍面向服务的体系结构的主题, 以及如何利用AWS Lambda与Python相结合来轻松构建可扩展的, 具有成本效益的服务。为了演示这些想法, 我们将使用Python, AWS Lambda, Amazon S3和其他一些相关工具和服务构建一个简单的图像上传和调整大小服务。
什么是面向服务的体系结构?
面向服务的体系结构(SOA)并不是什么新鲜事物, 其起源可追溯到几十年前。近年来, 由于为面向Web的应用程序提供了许多好处, 因此它作为一种模式的受欢迎程度正在增长。
本质上, SOA是将一个大型应用程序抽象为许多可通信的较小应用程序。这遵循了软件工程的几种最佳实践, 例如去耦, 关注点分离和单一职责体系结构。
SOA的实现在粒度方面有所不同:从涉及大范围功能的极少数服务到所谓的”微服务”体系结构中的数十个或数百个小型应用程序。不管粒度级别如何, SOA的从业人员普遍同意, 它绝不是免费的午餐。像软件工程中的许多良好实践一样, 这是一项投资, 需要额外的计划, 开发和测试。
什么是AWS Lambda?
AWS Lambda是Amazon Web Services平台提供的服务。 AWS Lambda允许你上载将在由Amazon管理的按需容器上运行的代码。 AWS Lambda将管理用于运行代码的服务器的供应和管理, 因此用户所需的全部内容是一整套要运行的代码, 以及一些用于定义服务器运行环境的配置选项。这些托管应用程序称为Lambda函数。
AWS Lambda有两种主要的操作模式:
异步/事件驱动:
Lambda函数可以响应异步模式下的事件而运行。任何事件源(例如S3, SNS等)都不会阻塞, Lambda函数可以通过多种方式利用此功能, 例如为某些事件链建立处理管道。信息源很多, 根据事件源的不同, 事件将从事件源推送到Lambda函数, 或由AWS Lambda轮询事件。
同步/请求->响应:
对于需要同步返回响应的应用程序, Lambda可以在同步模式下运行。通常, 它与称为API网关的服务结合使用, 以将HTTP响应从AWS Lambda返回到最终用户, 但是, 也可以通过直接调用AWS Lambda来同步调用Lambda函数。
除了处理程序操作所需的任何依赖关系之外, AWS Lambda函数还以包含处理程序代码的zip文件形式上载。一旦上载, AWS Lambda将在需要时执行此代码, 并在需要时将服务器数量从零扩展到数千, 而无需用户额外干预。
Lambda功能是SOA的演进
基本SOA是一种将代码库结构化为小型应用程序的方法, 以便以本文前面介绍的方式使应用程序受益。因此, 这些应用程序之间的通信方法成为人们关注的焦点。事件驱动的SOA(又名SOA 2.0)不仅允许SOA 1.0进行传统的直接服务到服务的通信, 而且还允许事件在整个体系结构中传播以传达变更。
事件驱动的体系结构是自然促进松散耦合和可组合性的一种模式。通过创建事件并对事件做出反应, 可以临时添加服务以向现有事件添加新功能, 并且可以组合多个事件以提供更丰富的功能。
AWS Lambda可用作轻松构建SOA 2.0应用程序的平台。有很多方法可以触发Lambda函数。从使用Amazon SNS的传统消息队列方法, 到将文件上传到Amazon S3或通过Amazon SES发送电子邮件所创建的事件。
实施简单的图像上传服务
我们将构建一个简单的应用程序, 以使用AWS堆栈上载和检索图像。这个示例项目将包含两个lambda函数:一个以request-> response模式运行, 将用于服务我们的简单Web前端, 另一个将检测上载的图像并调整其大小。
第一个lambda函数将异步运行, 以响应在S3存储桶中触发的文件上传事件, 该事件将存储上传的图像。它将采用提供的图像并调整其大小以适合400×400图像。
另一个lambda函数将为HTML页面提供服务, 既为用户提供了查看由我们的其他Lambda函数调整大小的图像的功能, 又提供了用于上传图像的界面。
初始AWS配置
在开始之前, 我们需要配置一些必要的AWS服务, 例如IAM和S3。这些将使用基于Web的AWS控制台进行配置。但是, 大多数配置也可以通过使用AWS命令行实用程序来实现, 稍后我们将使用它。
创建S3存储桶
S3(或简单存储服务)是一项Amazon对象存储服务, 可提供可靠且经济高效的任何数据存储。我们将使用S3存储将要上传的图像以及已处理图像的调整大小版本。
可以在AWS控制台的”存储和内容交付”子部分下的”服务”下拉菜单下找到S3服务。创建存储区时, 系统会提示你输入存储区名称以及选择区域。选择一个靠近用户的区域将使S3可以优化延迟和成本以及一些监管因素。在此示例中, 我们将选择”美国标准”区域。稍后将使用同一区域来托管AWS Lambda函数。
值得注意的是, S3存储桶名称必须是唯一的, 因此, 如果采用所选名称, 则将需要选择一个新的唯一名称。
对于此示例项目, 我们将创建两个名为” test-upload”和” test-resized”的存储桶。 “测试上传”存储桶将用于上传图像并在处理和调整大小之前存储上传的图像。调整大小后, 图像将被保存到”测试调整大小”存储桶中, 原始的上传图像也将被删除。
S3上传权限
默认情况下, S3权限是限制性的, 不允许外部用户甚至非管理用户读取, 写入, 更新或删除存储桶上的任何权限或对象。为了更改此设置, 我们需要以具有管理AWS桶权限的权限的用户身份登录。
假设我们在AWS控制台上, 我们可以通过按名称选择存储桶, 单击屏幕右上方的”属性”按钮, 然后打开折叠的”权限”部分来查看上传存储桶的权限。
为了允许匿名用户上传到此存储桶, 我们将需要编辑存储桶策略以允许允许上传的特定权限。这是通过基于JSON的配置策略完成的。此类JSON策略与IAM服务一起在整个AWS中广泛使用。点击”编辑存储区策略”按钮后, 只需粘贴以下文本, 然后单击”保存”即可上传公共图片:
{
"Version": "2008-10-17", "Id": "Policy1346097257207", "Statement": [
{
"Sid": "Allow anonymous upload to /", "Effect": "Allow", "Principal": {
"AWS": "*"
}, "Action": "s3:PutObject", "Resource": "arn:aws:s3:::test-upload/*"
}
]
}
完成此操作后, 我们可以通过尝试将图像上传到存储桶来验证存储桶策略是否正确。以下cURL命令可以解决问题:
curl https://test-upload.s3.amazonaws.com -F 'key=test.jpeg' -F '[email protected]'
如果返回200范围的响应, 我们将知道上传存储桶的配置已成功应用。现在, (大部分)我们的S3存储桶应已配置。稍后, 我们将在控制台中返回此服务, 以将我们的图像上传事件连接到我们的resize函数的调用。
Lambda的IAM权限
Lambda角色都在权限上下文中运行, 在这种情况下, 这是IAM服务定义的”角色”。该角色定义Lambda函数在其调用期间拥有的所有权限。就本示例项目而言, 我们将创建一个通用角色, 该角色将在两个Lambda函数之间使用。但是, 在生产方案中, 建议在权限定义中使用更精细的粒度, 以确保将任何安全利用都仅隔离于已定义的权限上下文。
你可以在”服务”下拉菜单的”安全性和身份”子部分中找到IAM服务。 IAM服务是用于管理跨AWS服务的访问的功能非常强大的工具, 如果你不熟悉类似的工具, 那么提供的界面一开始可能会有些麻烦。
进入IAM仪表板页面后, 可以在页面左侧找到”角色”子部分。在这里, 我们可以使用”创建新角色”按钮来创建一个多步骤向导, 以定义角色的权限。让我们使用” lambda_role”作为通用权限的名称。从名称定义页面继续后, 将为你提供选择角色类型的选项。由于我们仅需要S3访问权限, 因此单击” AWS服务角色”, 然后在选择框中选择” AWS Lambda”。你将看到可以附加到该角色的策略页面。选择” AmazonS3FullAccess”策略, 然后继续下一步以确认要创建的角色。
请务必注意所创建角色的名称和ARN(Amazon资源名称)。在创建新的Lambda函数以标识将用于函数调用的角色时, 将使用此方法。
注意:AWS Lambda将自动将来自函数调用的所有输出记录在日志服务AWS Cloudwatch中。如果需要此功能(建议在生产环境中使用), 则必须将写入Cloudwatch日志流的权限添加到该角色的策略中。
代码!
总览
现在我们准备开始编码。我们将假设此时你已经设置了” awscli”命令。如果尚未安装, 则可以按照https://aws.amazon.com/cli/上的说明在计算机上设置awscli。
注意:这些示例中使用的代码经过简化, 以方便查看屏幕。要获取更完整的版本, 请访问位于https://github.com/gxx/aws-lambda-python/的存储库。
首先, 让我们为项目设置一个骨架结构。
aws-lambda-python/
- image_list/
- handler.py
- list.html
- Makefile
- requirements.txt
- image_resize/
- handler.py
- resize.py
- Makefile
- requirements.txt
- .pydistutils.cfg
我们的结构中有两个子目录, 每个lambda函数一个。在每种情况下, 我们都有通用的handler.py, Makefile和requirements.txt文件。 handler.py文件将包含用于调用每个lambda函数的方法, 并且可以将其视为函数的入口点。 Requirements.txt文件将包含我们的依赖关系列表, 因此我们可以轻松地指定和更新需求。最后, 我们将使用Makefile命令提供一种与awscli进行交互的简单机制。这将使创建和更新lambda函数的过程变得更加容易。
你会在项目目录的根目录中注意到.pydistutils.cfg文件。如果你在Homebrew下使用Python, 则需要此文件。由于部署Lambda函数的方法(在下一节中介绍), 因此需要此文件。有关更多详细信息, 请参见存储库。
调整图像Lambda函数的大小
代码
从resize_image函数开始, 通过将Wand == 0.4.2保存到requirements.txt, 冻结Wand依赖关系, 即图像处理库。这将是我们image_resize lambda函数的唯一依赖项。 resize.py中的resize_image函数将需要处理Wand库中的图像资源, 并根据指定的width和height参数调整其大小。为了保留正在调整大小的图像, 我们将使用”最佳匹配”调整大小算法, 该算法将维持原始图像的图像比例, 同时减小图像尺寸以适合指定的宽度和高度。
def resize_image(image, resize_width, resize_height):
...
original_ratio = image.width / float(image.height)
resize_ratio = resize_width / float(resize_height)
# We stick to the original ratio here, regardless of what the resize ratio is
if original_ratio > resize_ratio:
# If width is larger, we base the resize height as a function of the ratio of the width
resize_height = int(round(resize_width / original_ratio))
else:
# Otherwise, we base the width as a function of the ratio of the height
resize_width = int(round(resize_height * original_ratio))
if ((image.width - resize_width) + (image.height - resize_height)) < 0:
filter_name = 'mitchell'
else:
filter_name = 'lanczos2'
image.resize(width=resize_width, height=resize_height, filter=filter_name, blur=1)
return image
这样一来, 就需要处理函数来接受由S3上传的图像生成的事件, 将其交给resize_image函数, 然后保存生成的调整大小的图像。
from __future__ import print_function
import boto3
from wand.image import Image
from resize import resize_image
def handle_resize(event, context):
# Obtain the bucket name and key for the event
bucket_name = event['Records'][0]['s3']['bucket']['name']
key_path = event['Records'][0]['s3']['object']['key']
response = boto3.resource('s3').Object(bucket_name, key_path).get() # Retrieve the S3 Object
# Perform the resize operation
with Image(blob=response['Body'].read()) as image:
resized_data = resize_image(image, 400, 400).make_blob()
# And finally, upload to the resize bucket the new image
s3_connection.Object('test-resized', key_path).put(ACL='public-read', Body=resized_data)
# Finally remove, as the bucket is public and we don't want just anyone dumping the list of our files!
s3_object.delete()
部署中
代码完成后, 需要将其作为新的Lambda函数上传到Amazon Lambda。这是已添加到目录结构中的Makefile起作用的地方。此Makefile将用于部署我们正在创建的Lambda函数定义。
ROLE_ARN = arn:aws:iam::601885388019:role/lambda_role
FUNCTION_NAME = ResizeImage
REGION = us-west-1
TIMEOUT = 15
MEMORY_SIZE = 512
ZIPFILE_NAME = image_resize.zip
HANDLER = handler.handle_resize
clean_pyc :
find . | grep .pyc$ | xargs rm
install_deps :
pip install -r requirements.txt -t .
build : install_deps clean_pyc
zip $(ZIPFILE_NAME) -r *
create : build
aws lambda create-function --region $(REGION) \
--function-name $(FUNCTION_NAME) \
--zip-file fileb://$(ZIPFILE_NAME) \
--role $(ROLE_ARN) \
--handler $(HANDLER) \
--runtime python2.7 \
--timeout $(TIMEOUT) \
--memory-size $(MEMORY_SIZE)
update : build
aws lambda update-function-code --region $(REGION) \
--function-name $(FUNCTION_NAME) \
--zip-file fileb://$(ZIPFILE_NAME) \
--publish
此Makefile的主要功能是”创建”和”更新”。这些函数首先打包当前目录, 该目录表示运行Lambda函数所需的所有代码。接下来, 在requirements.txt文件中指定的所有依赖项都将安装到要打包的当前子目录中。打包步骤压缩目录的内容, 以供以后使用” awscli”命令上载。我们的Makefile可以修改为在其他Lambda函数定义中使用。
在实用程序Makefile中, 我们定义了一些创建/更新映像所需的变量。根据需要配置这些命令, 以使Makefile命令对你正常工作。
- ROLE_ARN:这是Amazon Resource Name, 用于标识我们在其下运行Lambda函数的角色。
- FUNCTION_NAME:我们正在创建/更新的Lambda函数的名称。
- 地区:将在其下创建/更新Lambda函数的地区。
- 超时:Lambda调用将被杀死之前的超时(以秒为单位)。
- MEMORY_SIZE:Lambda函数在调用时可以访问的内存大小(以兆字节为单位)。
- ZIPFILE_NAME:包含Lambda函数代码和依赖项的压缩包的名称。
- 处理程序:处理程序函数的绝对导入路径, 以点符号表示。
配置完成后, 运行make create命令将生成类似于以下输出的内容:
$ make create
pip install -r requirements.txt -t .
...
find . | grep .pyc| xargs rm
zip image_resize.zip -r *
...
aws lambda create-function --region ap-northeast-1 \
--function-name ResizeImage2 \
--zip-file fileb://image_resize.zip \
--role arn:aws:iam::11111111111:role/lambda_role \
--handler handler.handle_resize \
--runtime python2.7 \
--timeout 15 \
--memory-size 512
{
"CodeSha256": "doB1hsujmZnxZHidnLKP3XG2ifHM3jteLEBvsK1G2nasKSo=", "FunctionName": "ResizeImage", "CodeSize": 155578, "MemorySize": 512, "FunctionArn": "arn:aws:lambda:us-west-1:11111111111:function:ResizeImage", "Version": "$LATEST", "Role": "arn:aws:iam::11111111111:role/lambda_role", "Timeout": 15, "LastModified": "2016-01-10T11:11:11.000+0000", "Handler": "handler.handle_resize", "Runtime": "python2.7", "Description": ""
}
测试中
执行完创建命令后, 可以使用图像大小调整功能, 但是尚未将其调整到S3存储桶以接收事件。我们仍然可以通过AWS控制台测试该功能, 以断言该功能正确处理了S3文件上传事件。在”服务”下拉菜单的”计算”子部分下的AWS Lambda仪表板上, 选择我们创建的函数的名称。该Lambda函数详细信息页面提供了一个标有”操作”的下拉菜单, 其中包含一个标有”配置测试事件”的选项。
单击此按钮将打开一个模式, 允许你指定测试事件和一些示例模板。选择” S3 Put”示例, 然后用已设置的存储桶名称替换存储桶名称的所有提及。配置完毕后, 随后使用Lambda函数页面上的”测试”按钮将调用Lambda函数, 就好像之前配置的事件确实发生过一样。
为了监视任何错误堆栈跟踪或日志消息, 你可以在Cloudwatch中查看日志流。在创建Lambda函数的同时, 将创建一个新的日志组。这些日志流非常有用, 可以通过管道传输到其他服务中。
连接到S3存储桶事件
返回S3仪表板, 展开位于”属性”菜单中的折叠的”事件”部分, 以显示”事件通知”表单。我们必须填写的必填字段是”事件”和”发送至”输入。从”事件”中, 选择”对象已创建(全部)”事件。这将允许拦截在上传存储桶上创建对象的所有事件。对于”发送至”输入, 选择” Lambda功能”单选按钮。将出现一个新部分, 并带有一个下拉菜单, 其中包含我们已配置为选项的” ResizeImage” lambda函数。单击”保存”后, 任何”对象创建”事件现在都将作为输入传递给调用” ResizeImage” Lambda函数的输入。
现在, 我们有了应用程序的核心功能。让我们运行另一个cURL测试, 以确保一切正常。使用cURL将图像上传到S3存储桶中, 然后手动检查图像是否已上传到调整大小存储桶中。
curl https://test-upload.s3.amazonaws.com -F 'key=test.jpeg' -F '[email protected]'
执行此命令后, 应在50-1000ms后在”测试调整大小”存储桶中创建调整大小的图像, 具体取决于Lambda函数是否已”预热”。
列表图像Lambda函数
代码
ListImage Lambda函数将检索调整大小后的图像列表, 并将其显示在HTML页面上供用户使用。该HTML页面还为用户提供了上传自己的图像的功能。函数中使用Jinja2从模板定义中呈现HTML。像以前一样, 这些要求是在requirements.txt文件中指定的。
from __future__ import print_function
import os
import boto3
from jinja2 import Environment
from jinja2 import FileSystemLoader
def _render_template(image_urls):
env = Environment(loader=FileSystemLoader(os.path.abspath(os.path.dirname(__file__))))
template = env.get_template('list.html')
rendered_template = template.render(image_urls=image_urls)
return rendered_template
def handle_list_image(event, context):
bucket = boto3.resource('s3').Bucket('test-resized')
image_summaries = sorted((image_summary for image_summary in bucket.objects.all()), key=lambda o: o.last_modified)
image_urls = []
for summary in image_summaries:
image_urls.append(
boto3.client('s3').generate_presigned_url(
'get_object', Params={
'Bucket': summary.bucket_name, 'Key': summary.key
}
)
)
return {'htmlContent': _render_template(image_urls)}
<html>
<head>
<title>List Images</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script>
var uploadImage = (function () {
var inProgress = false;
return function () {
if (inProgress) { return; }
inProgress = true;
var formData = new FormData();
var fileData = $('#image-file').prop('files')[0];
formData.append('key', parseInt(Math.random() * 1000000));
formData.append('acl', 'public-read');
formData.append('file', fileData);
$.ajax({
url: 'https://test-upload.s3.amazonaws.com/', type: 'POST', data: formData, processData: false, contentType: false, success: function (data) { window.location.reload(); }
});
}
})();
</script>
<style type="text/css">
.image__container {
float: left;
width: 30%;
margin-left: 2.5%;
margin-right: 2.5%;
max-width: 400px;
}
</style>
</head>
<body>
<nav>
<input id="image-file" type="file" onchange="uploadImage()" value="Upload Image" />
</nav>
<section>
{% for image_url in image_urls %}
<div class="image__container">
<img src="{{ image_url }}" />
</div>
{% endfor %}
</section>
</body>
</html>
再一次, 我们可以更改先前的Makefile并使用create命令来部署我们的lambda函数。
网关API
ImageList Lambda函数已完成, 但是无法提供给任何用户。这是因为只能响应于来自另一个服务的事件或以编程方式来调用Lambda函数。这是Amazon AWS API Gateway服务所在的位置。可以在”应用程序服务”小节下找到API网关。
API网关是一种将端点建模为一组资源和方法的方法, 本质上是REST接口。除了提供用于验证和转换请求的功能外, API Gateway还执行其他功能, 例如提供限制/速率限制请求。
在API Gateway仪表板中, 创建用于服务ListImage功能的新API。名称和描述可以根据你的喜好进行设置。创建完成后, 单击新API的名称以访问API详细信息。为根URL” /”创建一个新资源。该URL将用于提供HTML页面。
在查看根资源页面的详细信息时, 添加GET方法。将”集成类型”设置为” Lambda函数”, 将” Lambda区域”设置为” us-west-1″或你选择的区域, 然后键入ListImage Lambda函数的名称。
在开始将响应映射到HTML输出之前, 我们需要定义一个”模型”, 除了将响应映射到内容类型之外, 该模型还将为服务器的响应定义模式。选择API的”模型”部分, 然后单击”创建”以创建新模型。为模型指定名称” HTML”, 内容类型为” text / html”, 然后按以下方式定义架构:
{
"$schema": "http://json-schema.org/draft-04/schema#", "title" : "HTML", "type" : "object"
}
返回API仪表板, 选择我们创建的资源, 然后导航到”集成响应”部分。本节定义了从Lambda函数接收到响应之后, 将响应传递到最终步骤之前要处理的任何转换。
打开”映射模板”部分, 并添加” text / html”的新”内容类型”。同时删除旧的”内容类型”。在右侧, 将下拉列表从”输出传递”更改为”映射模板”。这将使我们能够更改API网关接受的原始JSON, 并使用返回的数据的” htmlContent”属性内的HTML内容。对于映射模板, 指定” $ input.htmlContent”作为模板。最后, 通过从” Response Models for 200″中删除” application / json”并添加” text / html”来更改” Method Response”部分。
返回API的信息中心, 页面左上角有一个标记为” Deploy API”的按钮。单击此按钮可以使用指定的资源, 方法, 模型和映射来更新或创建API。完成此操作后, 将显示所选部署阶段的URL(默认为过渡)。最后, 示例已完成!你可以上传一些文件来测试和查看调整大小的图像。
本文总结
AWS是一项大型服务, 并且不会很快消失。尽管始终需要注意供应商锁定, 但是AWS Lambda提供了相对较薄的服务, 其中包含一组丰富的辅助配置选项。利用AWS提供的服务来实施易于扩展和可维护的应用程序, 将提供使用AWS平台的最大好处。 AWS Lambda提供了一个优雅, 可扩展且具有成本效益的解决方案, 并由大量消费者使用的企业级平台提供支持。我相信”无服务器”应用程序是未来之路。在下面的评论中让我们知道你的想法。
评论前必须登录!
注册