AWS上で Swift の Lambda を動かしてみる

サーバー系は苦手なのに、必要に迫られて AWS で APIを実装する必要性に迫られた。別に、Node.jsやJavaを使えば、もっと簡単に実装できるかもしれないけれど、やはり Swift での実装にこだわってみた。

## 情報収集

まずは「aws、lambda、swift」くらいで検索してみる。このあたりがトップででてくる。

https://github.com/swift-server/swift-aws-lambda-runtime

https://www.swift.org/blog/AWS-lambda-runtime/

このあたりも気になる。

https://developer.apple.com/videos/play/wwdc2020/10644/

## Projectを作成

```sh
mkdir mylambda
cd mylambda
```

次の構造のようにファイルを構成します。

```
$ tree .
.
├── Package.swift
└── Sources
    └── mylambda
        └── main.swift
```

```Package.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" に続けて名前をつけて返すだけのシンプルな仕様とします。

```main.swift
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`を設定します。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65634/bacb0403-2209-8c73-cd15-1c1484725803.png"/>

これで、実行すると、コンソールに以下のような表示がされ、呼び出しを待っている状態になります。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
```
こんなかんじになります。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65634/e11901fe-bc4a-b35a-ce02-ada678faffe2.png"/>

出力した結果は以下の通りです。ローカルでの動作が確認できました。

```
{"message":"Hello John Doe"}
```

## パッケージング

SwiftはAWSのコンソールで編集して動作させる事ができないので、アップロードできるようにパッケージングする必要があります。

Xcode から Archive します。すると Organizer ウィンドウの Archives に mylambda が現れます。

Dockerを使いイメージを作成します。私は普段Dockerなど使わないので、Dockerの知識はあまりないのですが、この場合は Dockerfile を用意する必要はないようです。

実はここに辿り着くまで、結構いろいろな古い情報に踊らされて、結構しんどかったのですが、この方法でイメージを作れるのは良かったです。ちなみに、元ネタは「swift:5.3.1」だったのですが、これもうまくいかず、「5.9.2」でうまくいきましたが、将来はわかりませんね。

```sh
$ docker run \
    --rm \
    --volume "$(pwd)/:/src" \
    --workdir "/src/" \
    swift:5.9.2-amazonlinux2 \
    swift build --product mylambda -c release -Xswiftc -static-stdlib 
```

```sh
$ 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
```

```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 *
```

実行できるようにします。

```sh
$ chmod +x scripts/package.sh
```

そして、`package.sh` を実行します。`mylambda`を自分の利用する名前に変える事を忘れないでください。

```sh
$ scripts/package.sh mylambda     
  adding: bootstrap (stored 0%)
  adding: mylambda (deflated 66%)
```

上手くできたら、以下のファイルが生成されているはずです。この「lambda.zip」をAWSのポータルでアップロードします。他にも方法があるようですが、ここではこの方法で進めます。

```sh
$ ls .build/lambda/mylambda 
bootstrap       lambda.zip      mylambda
```

## AWS上で動かしてみる

AWSのポータルにログインして、ダッシュボードにアクセスします。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65634/37d9d24b-1a3c-5416-2fc5-9b13c80df145.png" />

Functiion を作ります。AWSのポータルから Lambda に入ります。Scratchから作成して。「Amazon Linux 2023」を選びます。「Architecture」は「Arm64」を選びます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65634/29807a2d-e79b-958d-ef3d-be9b5d6ba79c.png" />

こんなかんじになります、で、ここから「Upload from」から「Zip File」を選びます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65634/86f30619-d1ea-4bd8-0e3e-93aa8dc8aada.png" />

ここで、先ほどの「Lambda.zip」を指定してアップロードします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65634/ab81c302-9882-c902-ce15-16826f4bb3ff.png" />

API Gateway から「Create API」を選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65634/afdcdf0c-9248-7699-8479-3f498ebaf12d.png)

「New API」で API の名前の指定します。分かりやすい名前をつけましょう。今回は「mylambda_api」としてみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65634/18beaf37-9a3a-d8e3-b11d-88ea3176fd5c.png)

パス「/」を選んで、「Create Method」を選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65634/6ee93ece-034b-a8a8-fd87-fe7cc174ca54.png)

Method type で「POST」を選び「Lambda function」を選択します。そして、先ほど作った Lambda を選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65634/b87eeb6f-ea53-32f3-79bb-577e3dc10457.png)

次にステージを選びます。実はいろいろ試行錯誤していてこれが必要かどうかは確信がないのですが、今回は作成します。「Create Stage」を選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65634/b6ee112e-70d9-329f-7dec-e7c482a00cbd.png)

Stage の名前に今回は「develop」とします。「Create Stage」を選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65634/bf14299c-306f-5242-82d3-99543761b5b3.png)

Depoloy します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65634/7a224ea0-625a-4863-3126-a942cd0356fd.png)

「develop」が作られたかんじは、こんなふうです。「invoke URL」をコピペして、POSTMANなどから、APIを叩いてあげます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65634/e13790b3-4f26-9542-231b-51a44272d122.png)

JSON を編集して、送信すると、AWS上の Lambda が動作し、レスポンスが帰ってくる事が確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65634/d1a2fb30-84a8-f225-e519-2962bc8f456e.png)

### 悪戦苦闘

実は、ここに来るまでにスッタモンダしました。将来の自分がこのページを見て同じ問題に遭遇しないように、上手くいかなかった場合も記録に残しておく事にします。

さて、Lambdaの画面からトリガーを作り、API Gatewayを設定しても、「Missing Authentication Token」に遭遇して、解決する方法がわかりませんでした。よって、API Getway側から Lambda を指定するようにしました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65634/56f37fd8-8b20-8c4e-f5dc-6887a330f493.png" />
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65634/efff92d2-8268-07b3-2dfd-340e6ca8c07c.png" />

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65634/709ed38e-97b1-a62e-2bb5-0eaec463ad7e.png)

![Screenshot 2024-06-08 at 4.05.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65634/1677dc99-8d8c-84f1-c5a2-0eefcbaeea2a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65634/69252f7e-e484-b2a9-c86f-5ee5b3ce9e4c.png)

```
{
    "message": "Missing Authentication Token"
}
```

### 次の課題

次の課題は、Lambda から S3 のストーレージにアクセスしたり。より高度な機能を提供する事です。