# 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` 屬性