AWS Amplify Gen2 の defineFunction が Node.js 以外もサポートしました

AWS Amplify Gen2 の defineFunction が Node.js 以外もサポートしました

Clock Icon2025.01.09

いわさです。

AWS Amplify Gen2 では defineFunction というコンポーネントを使って Lambda 関数をデプロイすることが出来ます。
しかし、これまでこの機能は Node.js ランタイムのみをサポートしており、Python など他の関数をデプロイしたい場合には CDK 側で頑張ってデプロイする必要がありました。

昨日、以下のプルリクエストが Amplify Backend の main ブランチにマージされ、@‌aws-ampliufy/backend@1.13.0 から Node.js 以外のランタイムも defineFunction でデプロイ出来るようになります。

https://github.com/aws-amplify/amplify-backend/pull/1602

なお、今回のアップデートにあわせて次のドキュメントも追加されておりました。
今回はまずこちらのドキュメントに従って Python とカスタムランタイム(ドキュメントでは Go を使用)の関数をデプロイし、どのような関数が作成されるのか実際に試してみます。

https://docs.amplify.aws/react/build-a-backend/functions/custom-functions/

なお、注意事項としてフルスタック Git ベースの環境では関数をバンドルするための Docker がサポートされていない旨が記載されています。
Amplify コンソール上での CI/CD 自動ビルドは使えずカスタムパイプライン(AWS CodePipeline、Amazon CodeCatalyst、GitHub Actions など)[1]を使ってデプロイする必要があるようです。

Python

まず、Python ですが、こちらはほぼドキュメントどおりで実装可能です。
こんな感じで実装しました。

/amplify/functions/hoge/resource.ts
import { execSync } from "node:child_process";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import { defineFunction } from "@aws-amplify/backend";
import { DockerImage, Duration } from "aws-cdk-lib";
import { Code, Function, Runtime } from "aws-cdk-lib/aws-lambda";

const functionDir = path.dirname(fileURLToPath(import.meta.url));

export const hogeFunctionHandler = defineFunction(
  (scope) =>
    new Function(scope, "say-hello", {
      handler: "index.handler",
      runtime: Runtime.PYTHON_3_9, // or any other python version
      timeout: Duration.seconds(20), //  default is 3 seconds
      code: Code.fromAsset(functionDir, {
        bundling: {
          image: DockerImage.fromRegistry("dummy"),
          local: {
            tryBundle(outputDir: string) {
              execSync(
                `python3 -m pip install -r ${path.join(functionDir, "requirements.txt")} -t ${path.join(outputDir)} --platform manylinux2014_x86_64 --only-binary=:all:`
              );
              execSync(`rsync -rLv ${functionDir}/* ${path.join(outputDir)}`);
              return true;
            },
          },
        },
      }),
    })
);
/amplify/functions/hoge/index.py
import json
        
def handler(event, context):
  return {
      "statusCode": 200,
      "body": json.dumps({
          "message": "Hello World",
      }),
  }

ただし、バンドルオプション内で requirements.txt を参照しており次のエラーが出る場合があるので、依存ライブラリがない場合でも空の requirements.txt は配置しておきましょう。

Failed to instantiate custom function provider
Caused By: Failed to bundle asset amplify-hoge0109app-iwasatakahito-sandbox-8636202ca4/function/say-hello/Code/Stage, bundle output is located at /Users/iwasa.takahito/work/hoge0109amplify/hoge0109app/.amplify/artifacts/cdk.out/asset.0190349d29e087b536bb3276a205a7a839b0f2c8da2e233859f544ee7e12a7dd-error: Error: Command failed: python3 -m pip install -r /Users/iwasa.takahito/work/hoge0109amplify/hoge0109app/amplify/functions/hoge/requirements.txt -t /Users/iwasa.takahito/work/hoge0109amplify/hoge0109app/.amplify/artifacts/cdk.out/asset.0190349d29e087b536bb3276a205a7a839b0f2c8da2e233859f544ee7e12a7dd --platform manylinux2014_x86_64 --only-binary=:all:
ERROR: Could not open requirements file: [Errno 2] No such file or directory: '/Users/iwasa.takahito/work/hoge0109amplify/hoge0109app/amplify/functions/hoge/requirements.txt'

4CFC75A3-A0EC-42D7-8013-98A97031BD48_4_5005_c.jpeg

カスタムランタイム(Go)

Go は resource.ts の実装がドキュメントどおりだとエラーになります。何箇所かおかしいところがありました。
なので、次のように直しています。

/amplify/functions/fuga/resource.ts
import { execSync } from "node:child_process";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import { defineFunction } from "@aws-amplify/backend";
import { DockerImage, Duration } from "aws-cdk-lib";
import { Code, Function, Runtime } from "aws-cdk-lib/aws-lambda";

const functionDir = path.dirname(fileURLToPath(import.meta.url));

export const fugaFunctionHandler = defineFunction(
  (scope) =>
    new Function(scope, "fuga", {
      handler: "bootstrap",
      runtime: Runtime.PROVIDED_AL2023,
      timeout: Duration.seconds(3), //  default is 3 seconds
      code: Code.fromAsset(functionDir, {
        bundling: {
          image: DockerImage.fromRegistry("dummy"),
          local: {
            tryBundle(outputDir: string) {
              execSync(`rsync -rLv ${functionDir}/* ${path.join(outputDir)}`);
              execSync(
                `cd ${path.join(outputDir)} && GOARCH=amd64 GOOS=linux go build -tags lambda.norpc -o ${path.join(outputDir)}/bootstrap ${functionDir}/main.go`
              );
              return true;
            },
          },
        },
      }),
    }),
);

main.goはそのままでいきました。

/amplify/functions/fuga/main.go
package main

import (
	"context"
	"fmt"

	"github.com/aws/aws-lambda-go/lambda"
)

type Event struct {
	Arguments Arguments `json:"arguments"`
}

type Arguments struct {
	Title string `json:"phone"`
	Msg   string `json:"msg"`
}

func HandleRequest(ctx context.Context, event Event) (string, error) {
	fmt.Println("Received event: ", event)

	// fmt.Println("Message sent to: ", event.Arguments.Msg)
	// You can use lambda arguments in your code

	return "Hello World!", nil
}

func main() {
	lambda.Start(HandleRequest)
}

なお Go のビルドが必要なので、ドキュメント記載のとおりローカルへのインストールと事前にビルドコマンドの実行が必要です。

go mod init lambda
go mod tidy

関数のデプロイと Lambda コンソール上での確認

上記プロセスを経て Functions モジュールが準備出来たら、あとは Node.js と同じです。
defineBackend に取り込んでやれば関数スタックに含まれてデプロイされます。

/amplify/backend.ts
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { data } from './data/resource';
import { hogeFunctionHandler } from './functions/hoge/resource'
import { fugaFunctionHandler } from './functions/fuga/resource'

/**
 * @see https://docs.amplify.aws/react/build-a-backend/ to add storage, functions, and more
 */
defineBackend({
  auth,
  data,
  hogeFunctionHandler,
  fugaFunctionHandler,
});

今回はサンドボックスへデプロイし、マネジメントコンソールから関数が作成されたのか確認しました。
まずは Python です。

1E7EE55B-90AD-4F3C-8023-D76279CD7963.png

良いですね。作成されています。
続いてこちらはカスタムランタイム(Go)です。

21983A90-78CF-4D59-9942-7381B49415B5.png

こちらも良さそうです。

さいごに

本日は AWS Amplify Gen2 の defineFunction が Node.js 以外もサポートしたので試してみました。

これはなかなかうれしい方が多いのでは。
マネージドランタイムであれば .NET とか Java、カスタムランタイムであれば Rust とか PHP もいけるってことですよね。

欲をいうと、Node.js よりもちょっと面倒な実装が多いので SAM みたいにランタイムごとにうまくビルドして欲しいのと、カスタムパイプラインに実装が必要になってしまうので Docker をサポートしてもらえると、すごく嬉しいです。
このあたりは今後のアップデートに期待したいです。

脚注
  1. Custom pipelines - AWS Amplify Gen 2 Documentation ↩︎

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.