Swift の Lambda カスタムランタイムを使ってHello World してみる

記事タイトルとURLをコピーする

こんにちは、技術1課の加藤です。

現在、iOS アプリケーションの言語といえば Swift なわけですが、この Swift を Lambda で動かすことができるカスタムランタイムが出ました。
というわけで早速触ってみます。

著者のレベル

という感じです。

特に Swift に関してはほぼ素人ですので、処理自体はとても簡単なものを使っています。
ご了承ください。

環境

今回は Swift の開発を行うため、以下を用意します。 

  • OS: macOS
  • 開発環境: Xcode

また本チュートリアルでは Docker を用いたビルドを行うため、Docker Desktop for mac についても導入しておいてください。

手順

以下手順で進めていきます。

  1. SwiftPM プロジェクトを作成
  2. プロジェクトに `Swift AWS Lambda Runtime` をインポート
  3. main.swift に Lambda の実行スクリプトを実装
  4. Docker を用いてスクリプトをビルド
  5. デプロイ
  6. 動作確認

1. SwiftPM プロジェクトを 作成

ではまずプロジェクトを作成していきます。
Swift Package Manager = SwiftPM というパッケージマネージャを用いて、プロジェクトの作成を行なっていきます。

ターミナルを開き、任意の場所で以下を実行しましょう。

$ mkdir HelloLambdabySwift
$ swift package init --type=executable
Creating executable package: HelloLambdabySwift
Creating Package.swift
Creating README.md
Creating Sources/
Creating Sources/HelloLambdabySwift/main.swift
Creating Tests/
Creating Tests/LinuxMain.swift
Creating Tests/HelloLambdabySwiftTests/
Creating Tests/HelloLambdabySwiftTests/HelloLambdabySwiftTests.swift
Creating Tests/HelloLambdabySwiftTests/XCTestManifests.swift
$ open Package.swift

これで HelloLambdabySwift というプロジェクトの Package.swift ファイルが Xcode で開かれます。

2. プロジェクトに Swift AWS Lambda Runtime をインポート

必要なライブラリのインポート設定を書き込んでいきます。
Package.swfit を以下のように変更してください。

// swift-tools-version:5.2

import PackageDescription

let package = Package(
    name: "HelloLambdabySwift",
    platforms: [
        .macOS(.v10_13),
    ],
    products: [
        .executable(name: "HelloLambdabySwift", targets: ["HelloLambdabySwift"]),
    ],
    dependencies: [
        .package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", from: "0.1.0"),
    ],
    targets: [
        .target(
            name: "HelloLambdabySwift",
            dependencies: [
                .product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
        ]),
    ]
)

これでインポート設定は完了です。

3. main.swift に Lambda の実行スクリプトを実装

では実際に処理を書いていきます。
[Sources] > [HelloLambdabySwift] > [main.swift]
を選択し、中身を以下のように変更します。

import AWSLambdaRuntime

Lambda.run { (_, _, callback) in
    callback(.success("Hello, Lambda. by Swift"))
}

今回はイベントからもコンテキストからも何も受け取らず、ただ文字列を返すだけの関数を作成したため、第1, 2引数を _ にして捨ててしまっています。

本来、クロージャの第1引数が Context、第2引数が Event、第3引数が callback 関数を受け取る形になっている様子。今回はcallback以外捨てちゃいましたが、必要に応じて利用してあげてください。

さてさて、作成した関数をテスト実行してみたいのですが、これだとまだテスト実行ができません。
ちょこっと改修を加えます。

import AWSLambdaRuntime

try Lambda.withLocalServer {
    Lambda.run { (_, _, callback) in
        callback(.success("Hello, Lambda. by Swift"))
    }
}

こうすることで、テスト実行時には Lambda.withLocalServer が実行され、関数が動いてくれます。

Xcode でコードをテスト実行してみます。
実行対象を MyMac に変更し、Xcode の実行ボタン (三角形の再生マーク) をクリックしてテスト実行をしてみましょう。
Build Succceeded の表示とともにコンソールのログが出ればOKです

実行が成功すると以下の内容のログが出力され、ローカルサーバーが待機状態になります。

  • 127.0.0.1:7000 を localLambdaServer で開いていること
  • /invoke に対して payloads を投げ込めば受け取ること

試しに以下の curl コマンドをターミナルで実行してみましょう。

$ curl -X POST --data 'test' http://127.0.0.1:7000/invoke
Hello, Lambda. by Swift

何がしかのデータを payloads として放り込むことで、callback 関数に指定した文字列が帰ってくることが確認できます。

ちなみに /invoke にGETをすると 404(Not Found)
データを渡さずに /invoke に POST をすると 400 (Bad Request) が帰ってきました。この辺は LocalServer の実装 みたいです。
(※2020/06/10現在、 SwiftAWSLambdaRuntime ver0.1.0 環境での結果)

Docker を用いてスクリプトをビルド

作成したコードをビルドしていきます。
ただ本番デプロイするコードに LocalServer はいらないためテスト実行時のみ利用するようコードを変更します。

Swift にはデバッグ時に実行するコードを判別する #if DEBUG という書き方があるのでこれを利用します。

import AWSLambdaRuntime

#if DEBUG
try Lambda.withLocalServer {
    Lambda.run { (_, _, callback) in
        callback(.success("Hello, Lambda. by Swift"))
    }
}
#else
Lambda.run { (_, _, callback) in
    callback(.success("Hello, Lambda. by Swift"))
}
#endif

これでデバッグ時のみ LocalServer で動くように修正できました。
(DRY ではなくもっといい書き方があるんじゃないかとは思うのですが、Swift 力が低く何も思いつかなかったので、ひとまずこれで進めます。)

Lambda にデプロイするためには、Amazon Linux 用にコンパイルしたコードを Zip にパッケージし、AWS へアップロードをする必要があります。

まずは Amazon Linux 用にコンパイルを行なっていきます。
これは Docker を用い専用のコンテナを使って実施します。
(なおビルド時に使うスクリプト類は swift-was-lambda-runtime のリポジトリにある、サンプル用のスクリプトを転用させていただいています。)

プロジェクトルート (Package.swift がある階層) に Dockerfile を作り、以下を書き込んでください。

FROM swiftlang/swift:nightly-master-amazonlinux2

RUN yum -y install zip

そして同じくプロジェクトルートで以下のコマンドを実行していきます。

$ docker build -t builder .
$ docker run --rm -v "$(pwd)":/workspace -w /workspace builder bash -cl "swift build --product HelloLambdabySwift -c release -Xswiftc -g"

これでコンパイルができました。

次にコードの Zip パッケージ化を行います。
プロジェクトルートに package.sh ファイルを作り、以下の内容を書き込んでください。

#!/bin/bash

set -eu

executable="HelloLambdabySwift"
target=.build/lambda/$executable

rm -rf "$target"
mkdir -p "$target"

cp ".build/release/$executable" "$target/"
cp -Pv /usr/lib/swift/linux/lib*so* "$target"
cd "$target"
ln -s "$executable" "bootstrap"
zip --symlinks lambda.zip *

そして以下コマンドを実行し、package.sh をコンテナ内で動かすことで Zip にパッケージします。

$ chmod +x package.sh
$ docker run --rm -v "$(pwd)":/workspace -w /workspace builder bash -cl "./package.sh"

.build/lambda/HelloLambdabySwift/lambda.zip が作成されていれば完了です。

5. デプロイ

作成したスクリプトを AWS 環境にデプロイをしましょう。
AWS マネージメントコンソールで AWS Lambda を開きます。

画面右にある [関数の作成] を押して関数の設定画面を開きます。

以下の画像の通り設定を入力し、右下の [関数の作成] を押してください。カスタムランタイムを使用した Lambda が作成されます。

さて 作成した Lambda の画面が開きましたら、[関数コード][アクション] から [.zip ファイルをアップロード] を選択します。

[アップロード]ボタンを押し、.build/lambda/HelloLambdabySwift の lambda.zip を選択、 [保存] を押します。

「関数 HelloLambdabySwift が正常に更新されました。」と表示が出ればアップロード完了です。

6. 動作確認

最後に、正しく動作するか確認します。画面右上の [テスト] ボタンを押しテストイベントを作成しましょう。
今回特に event に渡す値は必要ないので、{} を設定しておきます。

[作成] ボタンを押しイベントが作られたら、再度 [テスト] ボタンを押しテストを実行しましょう。
実行結果が 成功 になり"Hello Lambda. by Swift" という文字列が返ってきていれば成功です。

お疲れ様でした。

所感

iOS アプリケーションをメインで作っていらっしゃる方がバックエンドに Lambda を使おうと思ったら、Swift on Lambda を利用する、ということもあるのかもしれませんね。

とはいえそれなりに手順はかかりますし、Swift の学習コストやカスタムランタイムの運用コストもそれなりに高いので、
現時点では何か特別な理由がなければ標準でサポートされている言語を使った方がいいんじゃないかなというのが感想でした。

気になった方がいれば試してみてください。

参考