AWS Step Functions 作為流程管理服務,簡化了無伺服器應用程式中複雜操作的協調與執行。它允許開發者以視覺化的方式定義多步驟工作流程,並自動執行,從而提升開發效率。本文將詳細介紹如何使用 AWS CDK 建構 Step Functions 狀態機,並將其與 Lambda 函式和 API Gateway 整合,實作完整的無伺服器應用程式開發流程。同時也探討無伺服器開發的常見問題,例如本地開發環境的建置與最佳化,並提供相應的解決方案,例如 LocalStack 的應用。
使用 AWS CDK 進行無伺服器應用程式開發
介紹 AWS Step Functions
AWS Step Functions 是一種由 AWS 提供的流程管理服務,讓您能夠建立、執行和視覺化多步驟操作或應用程式。可以把它想成是一種工具,用於簡化無伺服器環境中的複雜操作,方法是連線和協調不同的任務。這些任務可以從 AWS Lambda 函式到 AWS Batch 作業,甚至是 AWS Glue 作業。使用 Step Functions,您不再需要手動執行工作流程中的每個步驟;相反,您只需在狀態機中定義操作的流程,然後讓 Step Functions 來完成繁重的工作。
Step Functions 的優勢
使用 Step Functions 的一個關鍵優勢是能夠追蹤和提供工作流程的視覺化表示。這使得理解正在發生的事情變得更加容易,因為您可以準確地看到發生了什麼以及出了什麼問題,從而使除錯和故障排除變得更加容易。Step Functions 廣泛用於諸如資料管道、大資料處理、基礎設施管理和應用程式佈署等應用程式。它們是許多無伺服器架構中的重要組成部分,讓您能夠構建複雜的無伺服器應用程式,而無需管理伺服器或基礎設施。
將 Step Function 整合到應用程式中
讓我們將 Step Function 整合到我們的應用程式中。目標是每次點選特定路由時觸發狀態機的執行。流程完成後,我們將收到一封電子郵件,指示觸發源。
建立 Step Function 構件
- 前往
infrastructure/lib/constructs/Step-Function/index.ts;您將找到用於建立狀態機和組態必要的步驟任務以傳送電子郵件的程式碼。讓我們逐步深入程式碼。
設定 SES 電子郵件身份驗證
const emailAddress = process.env.EMAIL_ADDRESS;
const resourceArn = `arn:aws:ses:${Stack.of(this).region}:${Stack.of(this).account}:identity/${emailAddress}`;
const verifyEmailIdentityPolicy = AwsCustomResourcePolicy.fromStatements([
new PolicyStatement({
actions: ['ses:VerifyEmailIdentity', 'ses:DeleteIdentity'],
effect: Effect.ALLOW,
resources: ['*'],
}),
]);
new AwsCustomResource(this, `Verify-Email-Identity-${process.env.NODE_ENV || ''}`, {
onCreate: {
service: 'SES',
action: 'verifyEmailIdentity',
parameters: {
EmailAddress: emailAddress,
},
physicalResourceId: PhysicalResourceId.of(`verify-${emailAddress}`),
region: Stack.of(this).region,
},
policy: verifyEmailIdentityPolicy,
logRetention: 7,
});
內容解密:
此段程式碼設定了 SES 電子郵件身份驗證。首先,從環境變數中取得電子郵件地址,並建構出 SES 資源的 ARN。然後,定義了一個自定義資源策略,允許 SES 驗證和刪除身份。接著,建立了一個自定義資源,用於在堆積疊建立時驗證電子郵件地址。這是必要的,以便 SES 可以使用指定的電子郵件地址傳送電子郵件。佈署堆積疊後,您將收到一封來自 Amazon 的驗證電子郵件。
設定傳送電子郵件任務
const emailBody = '<h2>Chapter 7 Step Function.</h2><p>This step function was triggered by: <strong>{}</strong>.';
const sendEmail = new CallAwsService(this, `Send-Email-${process.env.NODE_ENV || ''}`, {
service: 'sesv2',
action: 'sendEmail',
parameters: {
Destination: {
ToAddresses: [emailAddress],
},
FromEmailAddress: emailAddress,
Content: {
Simple: {
Body: {
Html: {
Charset: 'UTF-8',
Data: JsonPath.format(emailBody, JsonPath.stringAt('$.message')),
},
},
Subject: {
Charset: 'UTF-8',
Data: 'Chapter 7 Step Function',
},
},
},
},
iamResources: [resourceArn],
});
內容解密:
此段程式碼定義了一個傳送電子郵件的任務。使用 CallAwsService 構件呼叫 SESv2 的 sendEmail 操作。電子郵件的內容,包括收件人、寄件人、主旨和正文,都被詳細指定。其中,電子郵件正文使用了 JsonPath.format 方法,將狀態機中的 $.message 值插入到預先定義的範本中。這使得電子郵件內容可以根據狀態機的執行結果動態生成。
介紹 Step Functions 與無伺服器應用的整合實作
在開發無伺服器應用程式時,AWS Step Functions 提供了一個強大的工具,用於協調多個 AWS 服務之間的互動。本章節將探討如何使用 AWS CDK 建立 Step Functions 狀態機,並將其與 Lambda 函式和 API Gateway 整合。
設定 Step Functions 狀態機
首先,我們需要定義狀態機的結構和行為。以下程式碼展示瞭如何使用 AWS CDK 建立一個簡單的 Step Functions 狀態機,該狀態機使用 SES(Simple Email Service)傳送電子郵件:
const sendEmail = new tasks.CallAwsService(this, 'Send Email', {
service: 'ses',
action: 'sendEmail',
iamResources: [resourceArn],
parameters: {
Source: '[email protected]',
Destination: {
ToAddresses: ['[email protected]'],
},
Message: {
Body: {
Html: {
Data: JsonPath.stringAt('$.message'),
},
},
Subject: {
Data: 'Notification from Step Functions',
},
},
},
});
const stateMachine = new StateMachine(this, 'State-Machine', {
definition: sendEmail,
timeout: Duration.minutes(5),
});
stateMachine.role.attachInlinePolicy(new Policy(this, `SESPermissions-${process.env.NODE_ENV || ''}`, {
statements: [
new PolicyStatement({
actions: ['ses:SendEmail'],
resources: [resourceArn],
}),
],
}));
this.stateMachine = stateMachine;
內容解密:
- 建立
CallAwsService任務:我們建立了一個CallAwsService任務,用於呼叫 SES 的sendEmail方法。 - 設定 SES 引數:在
parameters中,我們設定了發件人、收件人、郵件主題和內容。其中,郵件內容使用JsonPath.stringAt('$.message')從狀態機輸入中動態取得。 - 建立 StateMachine:使用
StateMachine建構式建立狀態機,並將sendEmail任務作為其定義。 - 設定超時和許可權:設定了狀態機的超時時間為 5 分鐘,並為其角色附加必要的 SES 許可權策略。
在 Lambda 函式中觸發 Step Functions
接下來,我們需要在 Lambda 函式中觸發 Step Functions 狀態機的執行。以下是在 GET 和 POST Lambda 函式中啟動 Step Functions 的程式碼範例:
const stepFunctions = new StepFunctions({
region: process.env.REGION as string,
});
await stepFunctions.startExecution({
stateMachineArn: process.env.STATE_MACHINE_ARN as string,
input: JSON.stringify({
message: 'GET / route',
}),
}).promise();
內容解密:
- 初始化 StepFunctions 使用者端:使用 AWS SDK 初始化 StepFunctions 使用者端,指定區域。
- 啟動狀態機執行:呼叫
startExecution方法,傳入狀態機的 ARN 和輸入資料(這裡是訊息內容)。
將 Step Functions 與 API Gateway 和 Lambda 整合
最後,我們需要將 Step Functions 狀態機與 API Gateway 和 Lambda 函式整合。以下是相關程式碼:
this.stepFunction = new StepFunction(this, `Step-Function-${process.env.NODE_ENV || ''}`, {});
new ApiGateway(this, `Api-Gateway-${process.env.NODE_ENV || ''}`, {
route53: this.route53,
acm: this.acm,
dynamoTable: this.dynamo.table,
stateMachine: this.stepFunction.stateMachine,
});
在 API Gateway 建構式中,我們將狀態機例項傳遞給 Lambda 函式,以便它們可以啟動狀態機的執行。
最佳化無伺服器開發流程
在前面的章節中,我們探討瞭如何透過使用 API Gateway、Lambda 函式和 DynamoDB 取代 ECS、RDS 和 VPC 服務,將程式碼轉換為無伺服器架構。然而,在開發流程中仍有改進的空間。
儘管使用無伺服器技術簡化了佈署後的維護工作,但我們仍然可以最佳化本地開發程式碼的方式。
在本章中,我們將涵蓋以下主題:
- 無伺服器開發中的常見問題
- 在本地執行 Lambda 應用邏輯並將其與本地 Express 伺服器整合
- 如何使用 LocalStack 工具集在本地執行 AWS 服務
在本章結束時,您將瞭解無伺服器開發中的問題,並且您的程式碼將被簡化到可以在佈署到 AWS 之前以基礎設施即程式碼(IaC)的方式進行本地測試。
技術需求
由於本章是上一章的延續,因此目錄結構保持不變,只對本章將要探討的服務進行了一些修改。
設定專案
在您選擇的編輯器中開啟本章的程式碼。就像前面的章節一樣,我們將程式碼分成基礎設施、伺服器和網頁目錄。
首先,讓我們安裝基礎設施和伺服器資料夾中的所有依賴項。 進入基礎設施目錄並在終端機中執行以下命令:
$ yarn
然後,回到伺服器目錄並執行以下命令:
$ yarn
這將安裝所有專案依賴項。
無伺服器開發中的常見問題
開發無伺服器應用程式已成為科技產業中流行且有用的工具,承諾簡化應用程式的佈署和建立。然而,與任何新技術一樣,總是存在一些問題。
最常見的問題之一是對底層基礎設施缺乏可見性。與傳統的根據伺服器的應用程式不同,伺服器及其組態是可見且可存取的,無伺服器應用程式將這些抽象化,使得在出現問題時更難以識別和解決問題。
另一個問題是對環境缺乏控制。由於無伺服器技術旨在抽象化底層基礎設施,開發人員通常對其應用程式執行的環境的控制權有限。這使得實作某些型別的功能(如精細的存取控制或資料儲存)變得困難,也使得與現有系統和工具整合變得困難。諸如 AWS 之類別的平台不斷為開發人員提供更多選項,以便在可自定義的環境中執行其程式碼。自定義 Lambda 容器映像(AWS 允許您在開源基礎映像上建立自己的 Lambda 執行環境,更多資訊請參閱 https://docs.aws.amazon.com/lambda/latest/dg/images-create.html)為我們提供了一個朝正確方向邁進的步驟。
程式碼範例:使用自定義 Lambda 容器映像
# 使用 AWS 提供的基礎映像
FROM public.ecr.aws/lambda/python:3.9
# 設定工作目錄
WORKDIR /app
# 複製 requirements.txt 檔案
COPY requirements.txt .
# 安裝依賴項
RUN pip install -r requirements.txt
# 複製應用程式碼
COPY . .
# 設定 Lambda 函式的處理程式
CMD ["lambda_function.lambda_handler"]
內容解密:
此 Dockerfile 使用 AWS 提供的 Python 3.9 基礎映像來建立自定義 Lambda 容器映像。它設定工作目錄,複製 requirements.txt 檔案並安裝依賴項。然後,它複製應用程式碼並設定 Lambda 函式的處理程式。
除錯也可能是無伺服器開發中的一個挑戰,特別是當您的程式碼執行在多個 AWS Lambda 函式上時。這使得追蹤問題和找到根本原因變得困難,因此正確設定日誌至關重要。
測試是另一個無伺服器開發可能很棘手的領域。在傳統的開發環境中,您可以在自己的機器上本地執行測試,但是在無伺服器開發中,您通常依賴雲端來執行測試,這可能導致測試執行緩慢和成本增加。
此外,在編寫無伺服器應用程式時,開發人員缺乏合適的本地開發環境。這可能是一個真正的痛點,因為這意味著您必須將程式碼佈署到實際環境中才能進行測試,這既耗時又昂貴。
儘管存在這些挑戰,開發人員仍繼續採用無伺服器開發,因為它提供了許多好處,例如可擴充套件性和成本文省,如前所述。
在第 10 章中,我們將探討 AWS 平台用於無伺服器開發的更多開源替代方案,這可能有助於克服其中一些限制。
在本地執行 Lambda 應用邏輯
您可能已經注意到,自從我們將基礎設施程式碼更改為無伺服器後,我們還沒有接觸過伺服器資料夾中的程式碼。您可能想知道我們是否仍將使用它。答案是肯定的:我們將把它用於本地開發,就像我們以前使用 RDS 時一樣,但我們需要進行一些更改才能使其與 DynamoDB 一起使用。此外,我們將把執行在已佈署的 Lambda 函式中的相同程式碼匯入本地伺服器,並在 POST 和 GET 路由中執行它。那麼,讓我們開始吧。
您可以在 server/src/index.ts 檔案中找到所有在此討論的程式碼。
首先,我們需要匯入用於 API Gateway 的 Lambda 整合中的 POST 和 GET 端點的程式碼:
import { handler as PostHandler } from '../../infrastructure/lib/constructs/Lambda/post/lambda';
import { handler as GetHandler } from '../../infrastructure/lib/constructs/Lambda/get/lambda';
由於兩個函式都匯出了 handler(),因此我們使用匯入別名來區分每個函式。完成後,讓我們繼續討論 Express 程式碼本身。在 POST 路由中,我們將刪除其中的所有內容,並用 PostHandler() 函式替換它:
app.post('/', async (req, res) => {
const event = {
body: JSON.stringify(req.body),
};
const { statusCode, body } = await PostHandler(event);
return res.status(statusCode).send(body);
});
內容解密:
此程式碼匯入用於 API Gateway 的 Lambda 整合中的 POST 和 GET 端點的處理程式。它使用匯入別名來區分每個函式。在 POST 路由中,它建立一個事件物件,將請求主體字串化,然後呼叫 PostHandler() 函式。最後,它傳回帶有狀態碼和主體的回應。