# SAM 專案多環境部署 [TOC] ###### tags: `chatgpt` --- ## 有個基於 aws-serverless-springboot2-archetype 的 SAM 專案,佈署到 AWS 時想要用 ImageUri,但在本地開發測試時想用 CodeUri,是否可共用一份 template.yml? 可以使用一份 `template.yml` 文件,並根據需要在不同的環境中設置不同的參數值。具體來說,您可以在 `template.yml` 中定義兩個不同的參數,例如 `CodeUri` 和 `ImageUri`,然後在您的 SAM 應用程序中使用這些參數。 以下是一個示例 `template.yml` 文件,其中包含 `CodeUri` 和 `ImageUri` 參數的定義: ```yaml AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Parameters: CodeUri: Type: String Default: ./target/my-app-1.0.jar ImageUri: Type: String Default: 123456789012.dkr.ecr.us-west-2.amazonaws.com/my-app:latest Resources: MyFunction: Type: AWS::Serverless::Function Properties: CodeUri: !Ref CodeUri ImageUri: !Ref ImageUri Handler: com.example.MyFunctionHandler Runtime: java8 ``` 在此範例中,`CodeUri` 和 `ImageUri` 參數都被定義為字串類型,並分別設置了默認值。在您的 SAM 應用程序中,您可以使用以下命令來佈署應用程序: ```bash # 執行本地部署,使用本地的 CodeUri sam local start-api --parameter-overrides CodeUri=./target/my-app-1.0.jar # 佈署到 AWS,使用 ImageUri sam deploy --stack-name mystack --parameter-overrides ImageUri=123456789012.dkr.ecr.us-west-2.amazonaws.com/my-app:1.0 ``` 在本地開發時,使用 `sam local` 命令行工具啟動本地 API 網關,並使用 `--parameter-overrides` 選項來覆蓋 `CodeUri` 參數的值。在佈署到 AWS 時,使用 `sam deploy` 命令行工具,並同樣使用 `--parameter-overrides` 選項來覆蓋 `ImageUri` 參數的值。 ## 能利用 `Condition` 來自動辨識嗎 是的,您可以使用 `Conditions` 在 `template.yml` 文件中自動辨識要使用的 URI,並相應地部署應用程序。具體來說,您可以在 `Conditions` 中定義一個條件,根據此條件來選擇要使用的 URI,然後在 `Resources` 中使用 `Fn::If` 函数來選擇要使用的 URI。 以下是一個示例 `template.yml` 文件,其中使用了 `Conditions` 來自動辨識要使用的 URI: ```yaml AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Conditions: UseImageUri: !Not [!Equals [!Ref ImageUri, ""]] Parameters: CodeUri: Type: String Default: ./target/my-app-1.0.jar ImageUri: Type: String Default: "" Resources: MyFunction: Type: AWS::Serverless::Function Properties: CodeUri: !If [UseImageUri, "", !Ref CodeUri] ImageUri: !If [UseImageUri, !Ref ImageUri, ""] Handler: com.example.MyFunctionHandler Runtime: java8 ``` 在此範例中,我們定義了一個名為 `UseImageUri` 的條件,該條件將判斷 `ImageUri` 參數是否被設置為空。如果 `ImageUri` 參數不為空,則 `UseImageUri` 條件將為 `false`,否則為 `true`。 在 `Resources` 中,我們使用了 `Fn::If` 函数來選擇要使用的 URI。如果 `UseImageUri` 條件為 `true`,則使用 `ImageUri` 參數作為 `ImageUri` 屬性的值,並將 `CodeUri` 屬性的值設置為空字符串。如果 `UseImageUri` 條件為 `false`,則使用 `CodeUri` 參數作為 `CodeUri` 屬性的值,並將 `ImageUri` 屬性的值設置為空字符串。 使用此方法,您可以在部署應用程序時自動辨識要使用的 URI,而不需要手動覆蓋參數。 ## 首先,你的範例並未配置 API Gateway;再者,在不修改 template.yml 內容的狀況下,該條件如何判斷 ImageUri 參數是否被設置為空? 非常抱歉我在先前的回答中有誤,您是對的,我的範例確實未配置 API Gateway。 此外,在不修改 `template.yml` 內容的情況下,條件本身並不能判斷 `ImageUri` 參數是否被設置為空。如果您想在運行時判斷 `ImageUri` 參數是否為空,您可以在 Lambda 函數代碼中使用環境變數來傳遞此信息。具體來說,您可以在 Lambda 函數代碼中讀取環境變數,然後根據變數的值來決定要使用的 URI。 以下是一個示例 Lambda 函數代碼,使用環境變數來判斷要使用的 URI: ```java package com.example; import java.io.InputStream; import java.io.OutputStream; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; public class MyFunctionHandler implements RequestStreamHandler { private static final String USE_IMAGE_URI_ENV_VAR = "USE_IMAGE_URI"; public void handleRequest(InputStream input, OutputStream output, Context context) { boolean useImageUri = Boolean.parseBoolean(System.getenv(USE_IMAGE_URI_ENV_VAR)); String uri = useImageUri ? System.getenv("IMAGE_URI") : System.getenv("CODE_URI"); // TODO: Use the URI to execute the desired logic } } ``` 在此範例中,我們使用 `System.getenv` 函數來讀取 `USE_IMAGE_URI` 環境變數,然後根據其值來決定要使用的 URI。如果 `USE_IMAGE_URI` 環境變數為 `true`,則使用 `IMAGE_URI` 環境變數作為 URI,否則使用 `CODE_URI` 環境變數作為 URI。 在部署 Lambda 函數時,您可以使用 SAM 模板文件的 `Environment` 屬性來定義這些環境變數,例如: ```yaml Resources: MyFunction: Type: AWS::Serverless::Function Properties: CodeUri: ./target/my-app-1.0.jar Handler: com.example.MyFunctionHandler Runtime: java8 Environment: Variables: USE_IMAGE_URI: "true" CODE_URI: "./target/my-app-1.0.jar" IMAGE_URI: "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:latest" ``` 在此範例中,我們定義了三個環境變數:`USE_IMAGE_URI`、`CODE_URI` 和 `IMAGE_URI`。您可以在部署 Lambda 函數時更改 `USE_IMAGE_URI` 環境變數的值,以控制要使用的 URI。 ## 重寫一份 `template.yml`,透過 `Environment` 屬性定義環境變數搭配 `Conditions` 以達到自動辨識、決定該以 `CodeUri` 進行本地開發測試或以 `ImageUri` 佈署到 AWS,還要加上 API Gateway 的配置 以下是一個重寫的 `template.yml`,其中使用 `Environment` 屬性定義了環境變數 `USE_IMAGE_URI`,並使用 `Conditions` 條件判斷是否要使用 `ImageUri`: ```yaml AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Parameters: ImageUri: Type: String Default: '' Globals: Function: Runtime: java11 MemorySize: 512 Timeout: 10 Resources: MyFunction: Type: AWS::Serverless::Function Properties: CodeUri: ./target/my-app-1.0.jar Handler: com.example.MyFunctionHandler Environment: Variables: USE_IMAGE_URI: !If [UseImageUri, 'true', 'false'] CODE_URI: ./target/my-app-1.0.jar IMAGE_URI: !If [UseImageUri, !Ref ImageUri, ''] ApiGateway: Type: AWS::Serverless::Api Properties: StageName: prod DefinitionBody: swagger: "2.0" info: title: "My API" paths: /: get: produces: - "application/json" responses: "200": description: "OK" schema: type: "object" properties: message: type: "string" x-amazon-apigateway-integration: httpMethod: POST uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" passthroughBehavior: "when_no_templates" type: "aws_proxy" responses: default: statusCode: "200" responseTemplates: application/json: '{ "message": "Hello, World!" }' Conditions: UseImageUri: !Not [!Equals [!Ref ImageUri, '']] ``` 在此範例中,我們定義了 `ImageUri` 參數作為 Lambda 函數的映像 URI,然後在 `MyFunction` 資源中定義了 `USE_IMAGE_URI`、`CODE_URI` 和 `IMAGE_URI` 三個環境變數。我們使用 `Conditions` 條件 `UseImageUri` 來判斷 `ImageUri` 參數是否被設置為空,如果不是空,則設置 `USE_IMAGE_URI` 環境變數為 `true`,並使用 `IMAGE_URI` 作為 Lambda 函數的代碼 URI;如果是空,則設置 `USE_IMAGE_URI` 環境變數為 `false`,並使用 `CODE_URI` 作為 Lambda 函數的代碼 URI。 ~~在 ApiGateway 資源中,我們使用 `MyFunction` 資源的 ARN 來定義 API Gateway 的整合資源,並將其定義為 `aws_proxy` 類型,以便在 API Gateway 和 Lambda 函數之間進行整合。注意,我~~ ### 2/2 以下是一份更新後的 `template.yml`,使用 `Environment` 屬性定義環境變數,搭配 `Conditions` 以達到自動辨識、決定該以 `CodeUri` 進行本地開發測試或以 `ImageUri` 佈署到 AWS,同時加上 API Gateway 的配置: ```yaml AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: My Serverless Application Globals: Function: Runtime: java11 Timeout: 30 Resources: MyFunction: Type: AWS::Serverless::Function Properties: CodeUri: ./target/my-app-1.0.jar Handler: com.example.MyFunctionHandler Environment: Variables: USE_IMAGE_URI: Fn::If: - UseImageUri - "true" - "false" CODE_URI: "./target/my-app-1.0.jar" IMAGE_URI: Fn::If: - UseImageUri - "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:latest" - "" Policies: - AmazonEC2ContainerRegistryReadOnly Metadata: DockerTag: Fn::If: - UseImageUri - latest - '' MyApi: Type: AWS::Serverless::Api Properties: StageName: prod MyApiDeployment: Type: AWS::Serverless::Deployment Properties: ApiId: !Ref MyApi MyApiResource: Type: AWS::ApiGateway::Resource Properties: ParentId: !GetAtt [MyApi, RootResourceId] PathPart: myresource RestApiId: !Ref MyApi MyApiMethod: Type: AWS::ApiGateway::Method Properties: RestApiId: !Ref MyApi ResourceId: !Ref MyApiResource HttpMethod: GET Integration: Type: AWS_PROXY Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" MethodResponses: - StatusCode: 200 AuthorizationType: NONE Conditions: UseImageUri: !Not [!Equals [!Ref ImageUri, '']] ``` 在此範例中,我們將 `USE_IMAGE_URI` 環境變數更改為一個條件,使用 `Fn::If` 函數根據 `UseImageUri` 條件的值來決定變數的值。同樣地,我們也使用 `Fn::If` 函數根據 `UseImageUri` 條件的值來決定 `IMAGE_URI` 環境變數的值,以及 `MyFunction` 函數的 `DockerTag` 屬性的值。 我們還添加了一些新的資源和屬性來配置 API Gateway。具體來說,我們添加了 `MyApi`、`MyApiDeployment`、`MyApiResource` 和 `MyApiMethod` 資源,並使用 `Integration` 屬性