使用 AWS Lambda 的面向服務的架構:分步教程

已發表: 2022-03-11

在構建 Web 應用程序時,有很多選擇可以幫助或阻礙您的應用程序,一旦您承諾它們。 語言、框架、託管和數據庫等選擇至關重要。

一個這樣的選擇是是使用面向服務的體系結構 (SOA) 創建基於服務的應用程序,還是使用傳統的單體應用程序。 這是影響初創公司、擴大規模和企業公司等的常見架構決策。

面向服務的架構被谷歌、Facebook、Twitter、Instagram、優步等一大批知名獨角獸和頂尖科技公司採用。 看起來,這種架構模式適用於大公司,但它對你有用嗎?

使用 AWS Lambda 的面向服務的架構:分步教程

使用 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 Gateway 的服務結合使用,以將來自 AWS Lambda 的 HTTP 響應返回給最終用戶,但也可以通過直接調用 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 存儲桶上觸發的文件上傳事件,該存儲桶將容納上傳的圖像。 它將獲取提供的圖像並調整其大小以適應 400x400 圖像。

另一個 lambda 函數將為 HTML 頁面提供服務,為用戶提供查看由我們的其他 Lambda 函數調整大小的圖像的功能以及用於上傳圖像的界面。

初始 AWS 配置

在開始之前,我們需要配置一些必要的 AWS 服務,例如 IAM 和 S3。 這些將使用基於 Web 的 AWS 控制台進行配置。 但是,大部分配置也可以通過使用 AWS 命令​​行實用程序來實現,我們稍後將使用該實用程序。

創建 S3 存儲桶

S3(或簡單存儲服務)是一種 Amazon 對象存儲服務,可為任何數據提供可靠且經濟高效的存儲。 我們將使用 S3 存儲將要上傳的圖像,以及我們處理的圖像的調整大小版本。

S3 服務可在 AWS 控制台的“存儲和內容交付”子部分下的“服務”下拉菜單中找到。 創建存儲桶時,系統會提示您輸入存儲桶名稱以及選擇區域。 選擇靠近用戶的區域將使 S3 能夠針對延遲和成本以及一些監管因素進行優化。 對於此示例,我們將選擇“美國標準”區域。 該區域稍後將用於託管 AWS Lambda 函數。

值得注意的是,S3 存儲桶名稱必須是唯一的,因此如果選擇的名稱被採用,您將需要選擇一個新的、唯一的名稱。

對於這個示例項目,我們將創建兩個名為“test-upload”和“test-resized”的存儲桶。 “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 存儲桶現在應該(大部分)配置好。 我們稍後將在控制台中返回此服務,以便將我們的圖像上傳事件連接到我們的調整大小函數的調用。

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 依賴項,即我們的圖像處理庫,將Wand==0.4.2保存到 requirements.txt。 這將是我們的 image_resize lambda 函數的唯一依賴項。 resize.py 中的 resize_image 函數將需要處理來自 Wand 庫的圖像資源,並根據指定的寬度和高度參數調整其大小。 為了保留正在調整大小的圖像,我們將使用“最佳匹配”調整大小算法,該算法將保持原始圖像的圖像比例,同時減小圖像大小以適應指定的寬度和高度。

 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 :這是標識我們在其下運行 Lambda 函數的角色的 Amazon 資源名稱。
  • FUNCTION_NAME :我們正在創建/更新的 Lambda 函數的名稱。
  • REGION :將在其下創建/更新 Lambda 函數的區域。
  • TIMEOUT :在 Lambda 調用被終止之前的超時時間(以秒為單位)。
  • MEMORY_SIZE :Lambda 函數在調用時將有權訪問的內存大小(以 MB 為單位)。
  • ZIPFILE_NAME :包含 Lambda 函數代碼和依賴項的壓縮包的名稱。
  • HANDLER :處理函數的絕對導入路徑,以點表示法。

配置完成後,運行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 儀表板,展開位於“屬性”菜單中的折疊“事件”部分以顯示“事件通知”表單。 我們必須填寫的字段是“事件”和“發送至”輸入。 從“事件”中,選擇“對像已創建(全部)”事件。 這將允許攔截在上傳存儲桶上創建對象的所有事件。 對於“Sent To”輸入,選擇“Lambda function”單選按鈕。 將出現一個新部分,其中包含一個下拉菜單,其中包含我們已配置為選項的“ResizeImage”lambda 函數。 單擊“保存”後,任何“已創建對象”事件現在都將作為輸入路由到“ResizeImage”Lambda 函數的調用。

我們現在擁有應用程序的核心功能。 讓我們運行另一個 cURL 測試以確保一切都按預期工作。 使用 cURL 將圖片上傳到 S3 存儲桶中,並手動檢查該圖片是否已上傳到 resize 存儲桶中。

 curl https://test-upload.s3.amazonaws.com -F 'key=test.jpeg' -F '[email protected]'

執行此命令後,應在 50-1000 毫秒後在“test-resized”存儲桶中創建調整大小的圖像,具體取決於 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 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 Gateway 是一種將端點建模為一組資源和方法的方法,本質上是一個 REST 接口。 除了提供驗證和轉換請求的工具外,API Gateway 還執行其他功能,例如提供限制/速率限制請求。

在 API Gateway 儀表板中,創建一個新的 API 來為 ListImage 函數提供服務。 名稱和描述可以根據您的喜好設置。 創建後,單擊新 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”。 同時刪除舊的“內容類型”。 在右側,將下拉菜單從“Output Passthrough”更改為“Mapping template”。 這將允許我們更改 API Gateway 接受的原始 JSON,並使用我們返回數據的“htmlContent”屬性中的 HTML 內容。 對於映射模板,指定“$input.htmlContent”作為模板。 最後,通過從“200 的響應模型”中刪除“application/json”並添加“text/html”來更改“方法響應”部分。

返回到 API 的儀表板,頁面左上角有一個標記為“部署 API”的按鈕。 單擊此按鈕可更新或創建具有指定資源、方法、模型和映射的 API。 完成此操作後,將顯示所選部署階段的 URL(默認為階段)。 最後,示例完成! 您可以上傳一些文件來測試和查看調整大小的圖像。

包起來

AWS 是一項大型服務,不會很快消失。 儘管供應商鎖定始終是需要小心的事情,但 AWS Lambda 提供了一項相對精簡的服務,具有一組豐富的輔助配置選項。 利用 AWS 提供的服務來實施易於擴展和維護的應用程序將提供使用 AWS 平台的最大好處。 AWS Lambda 提供優雅、可擴展且經濟高效的解決方案,由大量消費者使用的企業級平台提供支持。 我相信“無服務器”應用程序是未來的方式。 在下面的評論中讓我們知道您的想法。