サーバー系は苦手なのに、必要に迫られて AWS で APIを実装する必要性に迫られた。別に、Node.jsやJavaを使えば、もっと簡単に実装できるかもしれないけれど、やはり Swift での実装にこだわってみた。
情報収集
まずは「aws、lambda、swift」くらいで検索してみる。このあたりがトップででてくる。
このあたりも気になる。
Projectを作成
mkdir mylambda
cd mylambda
次の構造のようにファイルを構成します。
$ tree .
.
├── Package.swift
└── Sources
└── mylambda
└── main.swift
// swift-tools-version:5.2
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "mylambda",
products: [
.executable(name: "mylambda", targets: ["mylambda"]),
],
dependencies: [
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", .upToNextMajor(from:"0.3.0")),
],
targets: [
.target(
name: "mylambda",
dependencies: [
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
]
),
]
)
今回の lambda は JSON の name
を取得して、"Hello" に続けて名前をつけて返すだけのシンプルな仕様とします。
import AWSLambdaRuntime
struct Input: Codable {
let name: String
}
struct Output: Codable {
let message: String
}
Lambda.run { (context, input: Input, callback: @escaping (Result<Output, Error>) -> Void) in
callback(.success(Output(message: "Hello \(input.name)!")))
}
Xcode で Package.swift
を開きます。そして、Edit Scheme..
からEnvironment Variables
で
LOCAL_LAMBDA_SERVER_ENABLED
にtrue
を設定します。
これで、実行すると、コンソールに以下のような表示がされ、呼び出しを待っている状態になります。AWS上ではありませんが、まずはローカルで簡単な動作の確認を行います。
2024-06-08T01:21:49-0400 info LocalLambdaServer : [AWSLambdaRuntimeCore] LocalLambdaServer started and listening on 127.0.0.1:7000, receiving events on /invoke
2024-06-08T01:21:49-0400 info Lambda : [AWSLambdaRuntimeCore] lambda lifecycle starting with Configuration
General(logLevel: info))
Lifecycle(id: 41133856746458, maxTimes: 0, stopSignal: TERM)
RuntimeEngine(ip: 127.0.0.1, port: 7000, requestTimeout: nil
Postman などで、JSON を POST してみます。URLはログから拾い出したアドレスに、ログに記載のあるinvoke
をつけます。
127.0.0.1:7000/invoke
こんなかんじになります。
出力した結果は以下の通りです。ローカルでの動作が確認できました。
{"message":"Hello John Doe"}
パッケージング
SwiftはAWSのコンソールで編集して動作させる事ができないので、アップロードできるようにパッケージングする必要があります。
Xcode から Archive します。すると Organizer ウィンドウの Archives に mylambda が現れます。
Dockerを使いイメージを作成します。私は普段Dockerなど使わないので、Dockerの知識はあまりないのですが、この場合は Dockerfile を用意する必要はないようです。
実はここに辿り着くまで、結構いろいろな古い情報に踊らされて、結構しんどかったのですが、この方法でイメージを作れるのは良かったです。ちなみに、元ネタは「swift:5.3.1」だったのですが、これもうまくいかず、「5.9.2」でうまくいきましたが、将来はわかりませんね。
$ docker run \
--rm \
--volume "$(pwd)/:/src" \
--workdir "/src/" \
swift:5.9.2-amazonlinux2 \
swift build --product mylambda -c release -Xswiftc -static-stdlib
$ docker run \
--rm \
--volume "$(pwd)/:/src" \
--workdir "/src/" \
swift:5.9.2-amazonlinux2 \
swift build --product mylambda -c release -Xswiftc -static-stdlib
Fetching https://github.com/apple/swift-nio.git
Fetching https://github.com/apple/swift-system.git
Fetching https://github.com/apple/swift-atomics.git
...snip...
[58/59] Compiling mylambda main.swift
[59/60] Linking mylambda
Build complete! (49.28s)
そして、以下のように package.sh
を用意します。
$ tree ../mylambda\ copy
.
...snip...
├── Package.swift
├── Sources
│ └── mylambda
│ └── main.swift
└── scripts
└── package.sh
#!/bin/bash
set -eu
executable=$1
target=.build/lambda/$executable
rm -rf "$target"
mkdir -p "$target"
cp ".build/release/$executable" "$target/"
cd "$target"
ln -s "$executable" "bootstrap"
zip --symlinks lambda.zip *
実行できるようにします。
$ chmod +x scripts/package.sh
そして、package.sh
を実行します。mylambda
を自分の利用する名前に変える事を忘れないでください。
$ scripts/package.sh mylambda
adding: bootstrap (stored 0%)
adding: mylambda (deflated 66%)
上手くできたら、以下のファイルが生成されているはずです。この「lambda.zip」をAWSのポータルでアップロードします。他にも方法があるようですが、ここではこの方法で進めます。
$ ls .build/lambda/mylambda
bootstrap lambda.zip mylambda
AWS上で動かしてみる
AWSのポータルにログインして、ダッシュボードにアクセスします。
Functiion を作ります。AWSのポータルから Lambda に入ります。Scratchから作成して。「Amazon Linux 2023」を選びます。「Architecture」は「Arm64」を選びます。
こんなかんじになります、で、ここから「Upload from」から「Zip File」を選びます。
ここで、先ほどの「Lambda.zip」を指定してアップロードします。
API Gateway から「Create API」を選びます。
「New API」で API の名前の指定します。分かりやすい名前をつけましょう。今回は「mylambda_api」としてみました。
パス「/」を選んで、「Create Method」を選びます。
Method type で「POST」を選び「Lambda function」を選択します。そして、先ほど作った Lambda を選びます。
次にステージを選びます。実はいろいろ試行錯誤していてこれが必要かどうかは確信がないのですが、今回は作成します。「Create Stage」を選びます。
Stage の名前に今回は「develop」とします。「Create Stage」を選びます。
Depoloy します。
「develop」が作られたかんじは、こんなふうです。「invoke URL」をコピペして、POSTMANなどから、APIを叩いてあげます。
JSON を編集して、送信すると、AWS上の Lambda が動作し、レスポンスが帰ってくる事が確認できました。
悪戦苦闘
実は、ここに来るまでにスッタモンダしました。将来の自分がこのページを見て同じ問題に遭遇しないように、上手くいかなかった場合も記録に残しておく事にします。
さて、Lambdaの画面からトリガーを作り、API Gatewayを設定しても、「Missing Authentication Token」に遭遇して、解決する方法がわかりませんでした。よって、API Getway側から Lambda を指定するようにしました。
{
"message": "Missing Authentication Token"
}
次の課題
次の課題は、Lambda から S3 のストーレージにアクセスしたり。より高度な機能を提供する事です。