Goで書いた関数をLambdaとAPI GatewayでサーバーレスAPIしてみる
こんにちは、株式会社SHIFT、自動化エンジニアの水谷です。
少し前の記事では、Goで書いたマンデルブロー集合を計算するアプリをEchoを使ってAPI化する話を書いたのですが、今回はこれをAWSのLambdaとAPI GatewayでサーバーレスAPIにしてみたいと思います。
と、今回のネタを決めたはいいものの、これまでLambdaがサーバーレスで自分で作ったコードを実行させてくれるAWSのサービス、ということだけは知っているけど、実は何気に一度も使ったことがないので、手探りで試行錯誤しながら進めることになります。はたして無事動くでしょうか……?
Goコードの作成
まずは、AWSの公式ドキュメントなどを参考にGoのコードを作ります。作ると言っても、前回のコードをほぼ流用して、パラメーターの受け取りや、画像データを返すところをLambdaの仕様に合わせる感じになります。
まずは、適当なディレクトリを作成して、"go mod init"を実行します。
>go mod init mandel-lambda
go: creating new go.mod: module mandel-lambda
そして、github.com/aws/aws-lambda-go/lambdaをパッケージを"go get"でインストールします。
>go get github.com/aws/aws-lambda-go/lambda
go: downloading github.com/aws/aws-lambda-go v1.26.0
go get: added github.com/aws/aws-lambda-go v1.26.0
さて、コードの方ですが、まず下のようにLambda関連パッケージをimportしておきます。
import (
...
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
main()関数については、下のようにシンプルにハンドラー関数を登録するだけで良いようです。
func main() {
lambda.Start(getMapHandler)
}
そして、このハンドラー関数ですが、入力(APIに与えるパラメーター)がある場合とない場合、出力(APIが返す値)がある場合とない場合、それからコンテキスト情報にアクセスするかどうかによって、10種類程度の定義方法があります。今回は、入力(マンデルブロー集合の計算範囲)と出力(画像データ)が共にあり、コンテキスト情報にはアクセスしないため、"func (TIn) (TOut, error)"の形の関数にします。
ここで、TInとTOutは共にencoding/json 標準ライブラリと互換性のあるタイプである必要があるとのこと。要するに、JSON形式の値でなければなんでもよいのですが、TInは"events.APIGatewayProxyRequest"型にしておくことで、API Gateway Proxy経由のパラメーターを受け取れるようになる、とのことなので、これを使うことにします。また、TOutについては、ステータスコード、レスポンスボディ、ヘッダー、それからBASE64でエンコードされているかどうかの4項目のJSON形式となっているようですが、今回はその中のステータスコードとレスポンスボディのみを返すこととし、下のように定義しておきます。
type Response struct {
StatusCode int `json:"statusCode"`
Body string `json:"body"`
}
つまり、正しいパラメーターが与えられた場合は、マンデルブロー集合の計算を行って、StatusCodeには200を入れ、さらにBodyにはBASE64エンコードした画像データを入れてリターンするようにします。
ということで、コードは以下のようになりました(ファイル名はmain.go)。
package main
import (
"bytes"
"encoding/base64"
"fmt"
"image"
"image/color"
"image/png"
"math/cmplx"
"strconv"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
type Response struct {
StatusCode int `json:"statusCode"`
Body string `json:"body"`
}
func CalcGR(img *image.RGBA, rmin float64, rmax float64, imin float64, imax float64, offset int, nGR int, fchannel chan int) {
for y := 0; y < 800; y++ {
for x := offset; x < 800; x += nGR {
var C = complex(rmin+(rmax-rmin)*float64(x)/800, imin+(imax-imin)*float64(y)/800)
var V = C
var it uint8 = 0
for ; it <= 100; it++ {
V = V*V + C
if cmplx.Abs(V) > 2 {
break
}
}
img.Set(x, y, color.RGBA{uint8(cmplx.Abs(V) * 40), uint8(cmplx.Abs(V) * 120), it * 2, 255})
}
}
fchannel <- 1
}
func getMapHandler(request events.APIGatewayProxyRequest) (Response, error) {
realMin, err1 := strconv.ParseFloat(request.QueryStringParameters["realmin"], 64)
realMax, err2 := strconv.ParseFloat(request.QueryStringParameters["realmax"], 64)
imaginaryMin, err3 := strconv.ParseFloat(request.QueryStringParameters["imaginarymin"], 64)
imaginaryMax, err4 := strconv.ParseFloat(request.QueryStringParameters["imaginarymax"], 64)
if err1 != nil || err2 != nil || err3 != nil || err4 != nil ||
realMin >= realMax || imaginaryMin >= imaginaryMax {
return Response{
StatusCode: 400,
Body: "incorrect parameter",
}, nil
}
img := image.NewRGBA(image.Rect(0, 0, 800, 800))
nGR := 4
fchannel := make(chan int)
defer close(fchannel)
r := make([]int, nGR)
for i := range r {
go CalcGR(img, realMin, realMax, imaginaryMin, imaginaryMax, i, nGR, fchannel)
}
for range r {
<-fchannel
}
var buff bytes.Buffer
png.Encode(&buff, img)
encodedString := base64.StdEncoding.EncodeToString(buff.Bytes())
return Response{
StatusCode: 200,
Body: encodedString,
}, nil
}
func main() {
lambda.Start(getMapHandler)
}
ビルドとZip処理
Lambdaでは、ビルドしたコードがLinux環境で実行されますので、このコードもLinux向けにビルドする必要があります。もちろん、Linuxマシン上でビルドするなら、デフォルト設定でビルドしてしまえばOKなのですが、Windows環境でビルドするのであれば、以下のコマンドで環境変数"GOOS"を"linux"にしておきます。
> set GOOS=linux
あとは、いつものようにビルドすれば、Linux向けバイナリが生成されます。
> go build -o main
これで、拡張子がない"main"というファイルが出来ます。
Lambdaで実行するには、バイナリーをZipしたものをアップロードする必要があるので、このファイル("main")をエクスプローラーのコンテキストメニュー「送る」→「圧縮(zip形式)フォルダー」などの方法でZipしておきます。
Lambda関数の作成とアップロード
では、Lambda関数を作成しましょう。AWSのコンソールにログインして、サービス一覧からLambdaを選ぶ、あるいはサービス検索ボックスに"Lambda"と入力して、"AWS Lambda"のページに移動します。右上に「関数の作成」ボタンがありますので、これを押して新しいLambda関数を作成しましょう。
デフォルトで「一から作成」選択されているので、そのまま下の欄の「関数名」に作成する関数名を入力しますが、今回は"lambdaMandel"としました。また、「ランタイム」のドロップダウンボックスで"Go 1.x"を選択します。残りの部分は今回はデフォルトのままで「作成」ボタンを押します。
続いてZipファイルのアップロードです。「コード」タブが選択されている状態で、「アップロード元」のドロップダウンボックスで「.zipファイル」を選び、先ほど作成したmain.zipをアップロードします。
そして、ここでもう一つ大事な作業として、このLambda関数のエントリーポイントとなるGoコード内の関数名を指定する必要があります。これは、「ランタイム設定」という項目にある「編集」ボタンを押して、関数名を"main"に変更します。
これで、Lambda関数の実行準備が整いました。
さっそくテスト実行して見ましょう。「テスト」タブに移動して、ページの右の方にある「テスト」ボタンを押すと、下のようにAPIが実行され、「実行結果:成功」と表示されれば、APIが実行されたと判断できます。
なお、ここではまだAPIにパラメーターを渡していないので、"incorrect parameter"と返してきていますが、それでも、Goで作ったコードが正しくアップロードされたこと、それからそのAPIがLambda実行環境で実行できたことは確認できたことになります。
API GatewayでAPIを公開する
作成したLambda関数は、このままでは外部から呼び出して使用することができません。そこで、同じくAWSのサービスであるAPI Gatewayを使って公開してみましょう。
まずは、AWSのコンソールでサービス一覧から(あるいはサービスを検索して)API Gatewayのページに移動します。そして、ページの右上にある「API作成」のボタンを押して、APIの作成を行います。
APIの種類はREST APIにしますので、「REST API」(プライベートではないほう)と書かれたボックス内の「構築」ボタンを押します。
続くページではAPI名を指定し、それ以外の項目はデフォルトのまま「APIの作成」ボタンを押します。
そして、「アクション」から「リソースの追加」を選択し、"Mandel"というリソースを作り、さらにその下に"getmap"というリソースを追加して、そこに「メソッドの追加」で”GET”メソッドを作りました。
ここで、先ほど作成したLambda関数を選択し、また「Lambdaプロキシ統合の使用」にチェックを入れて、「保存」ボタンを押します。
APIの構成が表示され、その左に「テスト」のリンクがありますので、これを押してAPIのテスト実行をしてみます。
下のように、APIのパラメーターを入力して「テスト」ボタンを押すと、下のようにBASE64でエンコードされた文字列が表示されましたので、どうやら上手く動いているようです。
それでは、APIの公開作業を行います。アクションから「APIのデプロイ」を選択し、「デプロイされるステージ」で「新しいステージ」を選んで、ステージ名を「beta」としました。「デプロイ」ボタンを押すと、下のようにAPIのURLが表示されます(青色バックの「URLの呼び出し」のところ)。
これが外部からAPIを呼び出すためのURLとなりますので、curlコマンドで実行してみましょう。
>curl -x GET "https://b9rpdojdpe.execute-api.ap-northeast-1.amazonaws.com/beta/mandel/getmap?realmin=-1.5&realmax=0.5&imaginarymin=-1.0&imaginarymax=1.0"
iVBORw0KGgoAAAANSUhEUgAAAyAAAAMgCAIAAABUEpE/AACAAElEQVR4nOy9B5gb1dn+feuRVtJqq9e763UvuHfTMRhMN5hiTInpBAyEXgOhpb6pQBIIJbSQhBB6b6FjwAYb99772rvrrdpVH535rqmarpFWa/L+32+vuXSdOTozGo2kmd/ez32exzerA8IfLzw88hmBgReX3Bq80wDpWWM/73qVt9kVb+rkXT+lH2AcxttvUsBH82vZdNptaDvMPMDdU3l2avdpGGO/Sc6r5j0bXtduq/z67fZv2TAdWLca7vYm/Grtn73vLuI58GnwHJjSsFzl02KPYTVtNUbbqWk4PKV71vSUbY/dtnktwvl0GGD5LHPcg2b19osoHQUXRVpchEZMbMdMS1x4ZHGlHRfbaiOhPIqLrpEU20llSYCllLbY4FNC45RNlGxFsg2pNqQ6xCUsLFwnuC5xiSAdUY4wphxMHEe9Q9FdiNUjvhfxRiT2IdkCaVdHvExtS9CxGl2bENmGM9uo4T9o+w7h9YjuQLxBGDn1dWr8BC3fon0ZTl5LWx7H3ndx4KO09DoUVdBZnb7nGujgp8cvumR1OsYd+RbW/IIFeqPlW6o6jGv6hKs8EL2PwPZnmb8XiipRVAFfGbzFID88JJzhdBxcGIkW4eXmjqdl12HxVZjwa9/S66pvmME9Pr/17Bhe9TMwzI7RvONx6UB6vo3VHCOcmZZvsPd91E73B/uhbbFvzD3BhRexQedHS0dwe95iQ68QPspgHwpUsxcT/xlw9nHTv8Dq+9i+eay4H4r7IzQIocEoGYbS4SgbhfIxqBiPJ75j3hL4SuArVR6lpUxYisqEN+jxwryQT9/jMzbkAfr+TKe6qvRYtO0bVKSsFimdRfrHIvlZtSF1ahvCh2Jo++W2/Kgsv3uCwSN8gtpHwyoIHo/4aB5g6NSMtF41DO5+w2vq9ObQXnQJEy7QHvi0dCVfSgrEVXYDDLzltJoVs5w7rVgqCz9ZMpbdhvaDXWGTJTxZsVdWkMoPpyypyH2nW8YqLFRZwocVJNmxkRMzOcKWqwEF2aR7jZ/eTdbMZKYlNz36TqE/5Rqn3LFUQSgqT8YykxPLdILJi8WzYiNDVxpkUbFJhqeEZkkqwJTMEJKAR5zyyCkEzNlzp
...
BASE64のデータが送られてきたことが確認できます。これで、無事マンデルブロー集合の計算を行うAPIがLambdaとAPI Gatewayで公開できたことになります。
APIキーを使ってアクセスを制限することを始め、API Gatewayの設定はとても多くあるので、まだまだ勉強しながら設定していく必要があるのですが、今回の記事はここまでとさせていただきます。
――――――――――――――――――――――――――――――――――
★ 過去記事をまとめたマガジン「SHIFTエンジニアが学びながら解説するGo言語」もご参照ください。
お問合せはお気軽に
https://service.shiftinc.jp/contact/
SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/
SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/
SHIFTの導入事例
https://service.shiftinc.jp/case/
お役立ち資料はこちら
https://service.shiftinc.jp/resources/
SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/