AWS SAMを使ってTypeScriptでImage形式のLambdaを起動する

## 概要

- AWS SAMの `sam init` 使ってLambdaの言語にTypeScript指定する場合にlambdaのZip形式にのみ Templdateファイルが対応しているようだったので、 Image形式に対応させた際の手順のメモ

- 公式ドキュメント
    - https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/what-is-sam.html


## 準備

- 参考資料： https://dev.classmethod.jp/articles/typescript-native-support-in-the-aws-sam-cli/

- `What package type would you like to use?` の聞かれた際の選択肢 `1 - Zip`を選択すると、 後続の`Select your starter template` と聞かれた際に　Typescript を指定できるが、 `2 - Image` を選択すると js でプロジェクトが作成されてしまう

- 今回はTypescriptかつZip形式のプロジェクトを作成した後に、Image形式への対応を行なっていく


### AWS SAM の `sam init` で Typescriptを利用するプロジェクトを作成する

```
% sam init

You can preselect a particular runtime or package type when using the `sam init` experience.
Call `sam init --help` to learn more.

Which template source would you like to use?
	1 - AWS Quick Start Templates
	2 - Custom Template Location
Choice: 1

Choose an AWS Quick Start application template
	1 - Hello World Example
	2 - Data processing
	3 - Hello World Example with Powertools for AWS Lambda
	4 - Multi-step workflow
	5 - Scheduled task
	6 - Standalone function
	7 - Serverless API
	8 - Infrastructure event management
	9 - Lambda Response Streaming
	10 - Serverless Connector Hello World Example
	11 - Multi-step workflow with Connectors
	12 - GraphQLApi Hello World Example
	13 - Full Stack
	14 - Lambda EFS example
	15 - Hello World Example With Powertools for AWS Lambda
	16 - DynamoDB Example
	17 - Machine Learning
Template: 1

Use the most popular runtime and package type? (Python and zip) [y/N]: N

Which runtime would you like to use?
	1 - aot.dotnet7 (provided.al2)
	2 - dotnet6
	3 - go1.x
	4 - go (provided.al2)
	5 - graalvm.java11 (provided.al2)
	6 - graalvm.java17 (provided.al2)
	7 - java17
	8 - java11
	9 - java8.al2
	10 - java8
	11 - nodejs18.x
	12 - nodejs16.x
	13 - nodejs14.x
	14 - python3.9
	15 - python3.8
	16 - python3.7
	17 - python3.11
	18 - python3.10
	19 - ruby3.2
	20 - ruby2.7
	21 - rust (provided.al2)
Runtime: 11

What package type would you like to use?
	1 - Zip
	2 - Image
Package type: 1

Based on your selections, the only dependency manager available is npm.
We will proceed copying the template using npm.

Select your starter template
	1 - Hello World Example
	2 - Hello World Example TypeScript
Template: 2

Would you like to enable X-Ray tracing on the function(s) in your application?  [y/N]: y
X-Ray will incur an additional cost. View https://aws.amazon.com/xray/pricing/ for more details

Would you like to enable monitoring using CloudWatch Application Insights?
For more info, please view https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-application-insights.html [y/N]: y
AppInsights monitoring may incur additional cost. View https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/appinsights-what-is.html#appinsights-pricing for more details

Project name [sam-app]: sam-app-sample
                                                                                                           
Cloning from https://github.com/aws/aws-sam-cli-app-templates (process may take a moment)                  

    -----------------------
    Generating application:
    -----------------------
    Name: sam-app-sample
    Runtime: nodejs18.x
    Architectures: x86_64
    Dependency Manager: npm
    Application Template: hello-world-typescript
    Output Directory: .
    Configuration file: sam-app-sample/samconfig.toml
    
    Next steps can be found in the README file at sam-app-sample/README.md
        

Commands you can use next
=========================
[*] Create pipeline: cd sam-app-sample && sam pipeline init --bootstrap
[*] Validate SAM template: cd sam-app-sample && sam validate
[*] Test Function in the Cloud: cd sam-app-sample && sam sync --stack-name {stack-name} --watch
```

### 初期化後のディレクトリ構成

- `sam init` が完了すると下記のディレクトリ構成で作成されるので、こちらを土台に、TypescriptのImageを作成していく
- ディレクトリ `hello-world` 存在しており関数ごとにpackage.json等の設定ファイル系を用意する構成になっている
- そのままだとLambdaの関数を増やす際は関数用のディレクトリ新規に作成し、package.json等の設定ファイル系もそれぞれ用意する必要がある構成

```
.
├── .gitignore
├── README.md
├── events
│   └── event.json
├── hello-world
│   ├── .eslintignore
│   ├── .eslintrc.js
│   ├── .npmignore
│   ├── .prettierrc.js
│   ├── app.ts
│   ├── jest.config.ts
│   ├── package.json
│   ├── tests
│   │   └── unit
│   │       └── test-handler.test.ts
│   └── tsconfig.json
├── samconfig.toml
└── template.yaml
```

## LambdaのImage形式への対応

### ディレクトリ構成の変更

- Lambda関数の設定ファイル系を `hello-world` ディレクトリ内から プロジェクト直下に移動し、関数が増えた場合にも共通の設定ファイルを利用できる構成に変更していく

```
.
├── .DS_Store
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── .prettierrc.js
├── Dockerfile
├── README.md
├── events
│   └── event.json
├── jest.config.ts
├── package-lock.json
├── package.json
├── samconfig.toml
├── src
│   └── hello-world
│       ├── app.ts
│       └── tests
│           └── unit
│               └── test-handler.test.ts
├── template.yaml
└── tsconfig.json
```

- srcの部分について、関数を増やす場合に以下のような構成で増やすことも可能

```
src
├── hello-world
│   └── app.ts
└── hello-world-2
    └── app.ts
```


### buildコマンドの追記

- tscを使ってbuisを行うためのスクリプトを追記

```
"scripts": {
    "build": "tsc"
}
```

### tsconfigファイルの修正

- `outDir`・`rootDir`・`baseUrl`・`paths`・`incluede`を追記
- moduleは`es2020`・targetは`es2015`が初期化時は指定されているが、関数実行時に　`export` が存在しないと怒られるので削除
- コンパイル時にjsファイルを生成するために`noEmit`をfalseに修正
- `include`にsrcを指定

```
{
  "compilerOptions": {
    "outDir": "dist",
    "rootDir": ".",
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    },
    "strict": true,
    "preserveConstEnums": true,
    "noEmit": false,
    "sourceMap": false,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "**/*.test.ts"]
}
```

### Dockerファイルの作成

- AWSが用意しているLamba用のImageを利用するDockerファイルを作成
  - 参考：https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/typescript-image.html

```
FROM public.ecr.aws/lambda/nodejs:18 as builder
WORKDIR /usr/app
COPY package.json ./
RUN npm install
COPY tsconfig.json ./
COPY src ./src
RUN npm run build

FROM public.ecr.aws/lambda/nodejs:18
WORKDIR ${LAMBDA_TASK_ROOT}
COPY --from=builder /usr/app/dist/* ./
COPY --from=builder /usr/app/node_modules ./node_modules
```

### templdate.yamlの変更

- Lambdaの関数をImageを使うように修正

```
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      PackageType: Image
      Architectures:
        - x86_64
      ImageConfig:
        Command:
          - /var/task/hello-world/app.lambdaHandler
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get
    Metadata:
      DockerTag: nodejs18.x-v1
      DockerContext: .
      Dockerfile: Dockerfile
```

### ビルド

```
$ sam build
```

### デプロイ

```
$ sam deploy --guided
```