🏔️

LambdaでGoのWebアプリを動かす ridge - fujiwara-ware 2024 day 10

2024/12/10に公開2

この記事は fujiwara-ware advent calendar 2024 の10日目です。

ridge とは

https://github.com/fujiwara/ridge

ridge は、AWS Lambdaで、Go の net/http.Server のハンドラとして動くアプリケーションを動かすためのライブラリです。

Lambda では、Function URLs, API Gateway, ALB などで受信した HTTP リクエストから、Lambda Function を呼び出すことができます。その場合 Lambda に渡されるペイロードは HTTP のリクエスト内容を含んだ JSON に、Lambda が返却するレスポンスは HTTP のレスポンス内容を含んだ JSON になります。

ridge は、Lambda が受け取ったリクエストを Go の net/http.Request に、net/http.ResponseWriter に書き込まれた内容をレスポンス形式の JSON に変換します。そのため、Go の net/http.Server で動くアプリケーションをコード修正なしに、ビルドされたバイナリもそのまま Lambda で動かすことができます。

なぜ作ったのか

ridge を開発したのは 2017 年のことです。API Gateway から Lambda を呼び出すことで、HTTP を処理するアプリケーションが Lambda で動くようになりました。処理をしているときだけ課金される Lambda は、コストを抑えるためにも魅力的でした。

しかし Lambda が受け取るリクエストと返すレスポンスは独自形式の JSON です。この JSON に依存したアプリケーションを書いてしまうと、Lambda 以外で動かすことが難しくなります。Go の標準的な net/http で書くことができれば、Lambda 以外の環境でも動かすことができます。例えば、ローカルで動かしたり、性能要件によって ECS に移行したりすることができます。

そのため、Lambda のペイロードと Go の net/http を相互変換するライブラリを書きました。それが ridge です。

使い方

ridge の使い方は簡単です。net/http のハンドラを ridge.Run に渡すだけです。

package main

import (
	"fmt"
	"net/http"
	"github.com/fujiwara/ridge"
)

func main() {
	var mux = http.NewServeMux()
	mux.HandleFunc("/hello", handleHello)
	ridge.Run(":8080", "/", mux)
}

func handleHello(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/plain")
	fmt.Fprintf(w, "Hello %s\n", r.FormValue("name"))
}

ridge は、自分が動作している環境が Lambda かどうかを判定して、Lambda で動いている場合は Lambda のハンドラとして動作します。

Lambda のペイロードを自動的に変換し、アプリケーションのハンドラに渡します。アプリケーションが返す net/http.ResponseWriter の内容を Lambda が要求する JSON に変換して返します。

Lambda 以外の環境で実行した場合は、通常の net/http.Server を起動して単体の HTTP サーバーとして動作します。

ユーザーが実装する Web アプリケーションのハンドラは、動作環境を意識することなく書くことができます。多くの Go の Web アプリケーションフレームワークは net/http.Handler のインターフェースに則っているため、もちろん ridge でもそのまま動作します。

類似ライブラリとの違い

AWS が公式にリリースしている、ridge と同様の動作をする aws-lambda-go-api-proxy というライブラリがあります。

https://github.com/awslabs/aws-lambda-go-api-proxy

なぜこれを使わずに ridge をメンテナンスしているのかというと、実は単に ridge のほうが先発であるためです。2017 年に ridge を書いたときには aws-lambda-go-api-proxy は存在していませんでした。

また、aws-lambda-go-api-proxy は動作環境を自動判別する機能がありません。同一のコードで Lambda でも他の環境でも動かすためには、自分で環境を判別した上で、どちらで動かすかを切り替えるコードを書く必要があります。

次のコードは aws-lambda-go-api-proxy の README にある例です。lambda.Start で Lambda 用のハンドラを指定しているので、このコードは Lambda でしか動きません。

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		io.WriteString(w, "Hello")
	})
	lambda.Start(httpadapter.New(http.DefaultServeMux).ProxyWithContext)
}

ridge はそのあたりをユーザーが意識することなく、ビルドしたバイナリを Lambda でも他の環境でもそのまま動かすことができるように設計されています。

利用例

https://pkg.go.dev/github.com/fujiwara/ridge?tab=importedby

ridge は他の多くの fujiwara-ware のうち、HTTP サーバーを起動するアプリケーションに組み込まれて使われています。
このアドベントカレンダーで紹介予定の fujiwara-ware には以下のようなものがあります。

ridge さえ使っておけば Lambda で動かすことが非常に容易にできるため、常時起動するサーバーのコストを抑えることができます。

まとめ

ridge は、Lambda で Go の net/http.Server のハンドラとして動くアプリケーションを動かすためのライブラリです。

特にユーザーが実装するアプリケーションのコードを変更することなく、Lambda で動かすことができるため、コストを抑えながら柔軟にアプリケーションを動かすことができます。

大変便利なので、ぜひ使ってみてください。

それでは、明日もお楽しみに!

参考資料

https://speakerdeck.com/fujiwara3/yapc-hakodate2024

https://speakerdeck.com/fujiwara3/go-mitainafalse-on-aws

Discussion

tenkohtenkoh

個人のプロジェクト使用させて頂いております。大変便利で助かります…!!大感謝…!!