💭

VS Code で AWS Application Composer を使ってみる

2023/12/08に公開

この記事は mob Advent Calendar 8日目の記事です。

Visual Studio Codeで Application Composer が使えるようになったので実際に使ってみたいと思います。

https://aws.amazon.com/jp/blogs/aws/ide-extension-for-aws-application-composer-enhances-visual-modern-applications-development-with-ai-generated-iac/

ツール類のインストール

AWS SAM CLI のインストール

まずは AWS SAM CLI のインストールをします。
下記から いくつかの手段でインストールする方法が提示されているので好きな手法でインストールしてください。

https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/install-sam-cli.html

プラグインのダウンロード

VS Codeのプラグインページから AWS Toolkit を検索してインストールします。

使ってみる

Lambda から 別の Lambda を実行してその結果をクライアントに返却するようなものを作ってみます。

まずは プロジェクトを作ってみる

まずは sam init してプロジェクトを作ってみます。

$ 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
	...
Template: 1

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

Which runtime would you like to use?
	1 - aot.dotnet7 (provided.al2)
	2 - dotnet6
	...
	13 - nodejs20.x
	14 - nodejs18.x
	...
Runtime: 13

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]: 

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]: 

Would you like to set Structured Logging in JSON format on your Lambda functions?  [y/N]: 

Project name [sam-app]: 

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

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

Application Composer を使うと、意図せぬ変更があったりするのでこのタイミングで git init しておきます。

$ cd sam-app/
$ git init
$ git add . 
$ git commit -m "init

ここまでできたらひとまず sam build して sam deploy --guided で deploy しておきます。

Application Composer を使ってみる

template.yaml で右クリックして Open with Application Composer を選択すると

こんな感じで表示されます。

Lambda を追加して、

リソースプロパティはこのように入力します。

welcomeのディレクトリができて、 いくつかファイルができます。
ただ、 hello-world と中身が同じでいいので、生成されたファイルは全て削除して hello-world の中身をこのディレクトリにコピーして、 app.ts の一部だけ変更しておきます。

app.ts
export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
    try {
        return {
            statusCode: 200,
            body: JSON.stringify({
                message: 'welcome', // <= hello world を welcome に変更
            }),
        };
    } catch (err) {
        console.log(err);
        return {
            statusCode: 500,
            body: JSON.stringify({
                message: 'some error happened',
            }),
        };
    }
};

template.yaml に戻ります。 Application Composer ではなくてエディタで開きます。
ここが先ほど追加した WelcomeFunction はこのようになっています。

template.yaml
  WelcomeFunction:
    Type: AWS::Serverless::Function
    Properties:
      Description: !Sub
        - Stack ${AWS::StackName} Function ${ResourceName}
        - ResourceName: WelcomeFunction
      CodeUri: welcome
      Handler: app.lambdaHandler
      Runtime: nodejs20.x
      MemorySize: 3008
      Timeout: 30
      Tracing: Active
    Metadata:
      BuildMethod: esbuild
      BuildProperties:
        EntryPoints:
          - index.mts
        External:
          - '@aws-sdk/*'
          - aws-sdk
        Minify: false

Metadataの部分が index.mts になっているのですが、ここを app.ts に変更したいです。これに関しては Application Composer 側で変更できなそうなので、手で変更します。

  WelcomeFunction:
    Type: AWS::Serverless::Function
    Properties:
      Description: !Sub
        - Stack ${AWS::StackName} Function ${ResourceName}
        - ResourceName: WelcomeFunction
      CodeUri: welcome
      Handler: app.lambdaHandler
      Runtime: nodejs20.x
      MemorySize: 3008
      Timeout: 30
      Tracing: Active
    Metadata:
      BuildMethod: esbuild
      BuildProperties:
-       EntryPoints:
-         - index.mts
-       External:
-         - '@aws-sdk/*'
-         - aws-sdk
-       Minify: false
+       Minify: true
+       Target: es2020
+       Sourcemap: true
+       EntryPoints:
+         - app.ts

hello-world Function側のソースコードを編集していないですが、この段階で一度同期します。Application Composer の右上のボタンです。ここでの同期は AWS 側からローカルへの同期ではなくて、ローカルの状態を AWS 側に同期させるということをさします。ほぼほぼデプロイです。

AWS Console の方で確認すると、 WelcomeFunction ができていることが確認できます。

この際に、 WelcomeFunctionの 物理ID( 上の場合は sam-app-WelcomeFunction-xhLevfvgW7ib) をコピーしておきます。

HelloWorldFunction から WelcomeFunction を invoke する

HelloWorldFunction 側で aws-sdk を追加します。

npm install aws-sdk

コードを次のように編集します。

app.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { Lambda } from 'aws-sdk';

const lambda = new Lambda();

export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
    try {
+       const response = await lambda
+           .invoke({
+               FunctionName: 'sam-app-WelcomeFunction-xhLevfvgW7ib', // <- 先ほどコピーした WelcomeFunction の物理ID
+           })
+           .promise();

        return {
            statusCode: 200,
            body: JSON.stringify({
+               message: 'hello world, ' + JSON.stringify(JSON.parse(response.Payload as string)),
            }),
        };
    } catch (err) {
        ...
    }
};

これで完了です。もう一度、 Application Composer の同期をしてみましょう。
同期が完了したら、エンドポイントを呼び出してみます。すると、 このような結果が返ってきます。

{"message": "Internal server error"}

原因を調べるために、AWS Console の HelloWorldFunction の方でテストをしてみると、このような結果になりました。どうやらタイムアウトしているようです。

Application Composer で HelloWorldFunction のタイムアウトを眺めると 30(s) になってるので、3秒でタイムアウトになることはないはずですが、、、

yamlの方を眺めると、タイムアウトの設定がされていません...

  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-world/
      Handler: app.lambdaHandler
      Runtime: nodejs20.x
      Architectures:
        - x86_64
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get
      Environment:
        Variables:
          WELCOMEFUNCTION_FUNCTION_NAME: !Ref WelcomeFunction
          WELCOMEFUNCTION_FUNCTION_ARN: !GetAtt WelcomeFunction.Arn
      Policies:
        - LambdaInvokePolicy:
            FunctionName: !Ref WelcomeFunction
    Metadata:
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Target: es2020
        Sourcemap: true
        EntryPoints:
          - app.ts

Application Composerの方で タイムアウトを 30から31にして、また30にして保存すると次のようにタイムアウトが設定されました。

  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-world/
      Handler: app.lambdaHandler
      Runtime: nodejs20.x
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get
      Environment:
        Variables:
          WELCOMEFUNCTION_FUNCTION_NAME: !Ref WelcomeFunction
          WELCOMEFUNCTION_FUNCTION_ARN: !GetAtt WelcomeFunction.Arn
      Policies:
        - LambdaInvokePolicy:
            FunctionName: !Ref WelcomeFunction
+     MemorySize: 3008
+     Timeout: 30
    Metadata:
      BuildMethod: esbuild
      BuildProperties:
        EntryPoints:
+         - index.mts
+       External:
+         - '@aws-sdk/*'
+         - aws-sdk
+       Minify: false

ただ、変えてほしくない EntryPoints 周りも変わってしまいます。
さらに言うと の  HelloWorldFunction のpackage .json が下記の内容で上書きされてしまっています。

{
  "name": "function",
  "version": "1.0.0",
  "type": "module",
  "devDependencies": {
    "@types/aws-lambda": "~8"
  }
}

これらを元に戻して、再度「同期」します。すると、結果がこのようになり無事動いていることがわかりました。

{"message":"hello world, {\"statusCode\":200,\"body\":\"{\\\"message\\\":\\\"welcome\\\"}\"}"}

感想まとめ

良いところ

  • 視覚的でわかりやすい
  • 権限周りは勝手にやってくれるので助かる
    • 今回で言うと HelloWorldFunction に WelcomeFunction の InvokeAction を設定してくれるところ

微妙なところ

  • デフォルト値(設定していない値)がAWS側とズレてるものがある
    • 今回で言うと Lambdaのタイムアウトが Application Composer では 30s だが、AWS側では 3s
  • 意図しないタイミングでソースコードの上書きがある
    • 勝手に EntryPoint の設定がずれたり、 package.json の中身が空になったり
  • Lambdaの EntryPoint などは Application Composer 側で設定できない

自分としての結論は、初期でまだソースコードがない時に AWS 側の構築をするのにはかなり役立つんだろうと思いました。一方で既にデプロイしており、そこに編集かけていく場合は意図しない変更が生まれやすいツールになってるので、 差分を注視する必要がありそうです。

Discussion