こんにちは、技術1課の加藤です。
現在、iOS アプリケーションの言語といえば Swift なわけですが、この Swift を Lambda で動かすことができるカスタムランタイムが出ました。
というわけで早速触ってみます。
著者のレベル
- Lambda は業務で触っています
- Lambda カスタムランタイムは遊んだことがあります
- Swift はちょっとチュートリアルを触って挫折しました
という感じです。
特に Swift に関してはほぼ素人ですので、処理自体はとても簡単なものを使っています。
ご了承ください。
環境
今回は Swift の開発を行うため、以下を用意します。
- OS: macOS
- 開発環境: Xcode
また本チュートリアルでは Docker を用いたビルドを行うため、Docker Desktop for mac についても導入しておいてください。
手順
以下手順で進めていきます。
- SwiftPM プロジェクトを作成
- プロジェクトに
Swift AWS Lambda Runtime
をインポート - main.swift に Lambda の実行スクリプトを実装
- Docker を用いてスクリプトをビルド
- デプロイ
- 動作確認
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 の学習コストやカスタムランタイムの運用コストもそれなりに高いので、
現時点では何か特別な理由がなければ標準でサポートされている言語を使った方がいいんじゃないかなというのが感想でした。
気になった方がいれば試してみてください。