はじめに
Lambda の共通処理と言えば Lambda Layers である。
Golang には共通モジュールの扱い方が他にもいくつかある(Git Submodulesを使うとか、ローカルのGitリポジトリを使う(過去の記事参照)とか)が、今回は、Lambda Layers を使った共通化の使用感を確認して考察してみよう。
Go Plugin パッケージを使う
Lambda Layers を Golang で使う場合、Plugin パッケージを使用する。
まずは、このモジュールについてちゃんと知っておこう。
パッケージの説明は公式を確認。
ログ出力をする mylogger なプラグインを作ってみることにしよう。
package main
import (
"log"
)
func OutputLog(str string) {
log.Printf(str)
}
これを、.so 形式で出力するようにビルドする。
$ GOOS=linux go build -buildmode=plugin -o mylogger.so
モジュールの呼び出し元を実装してみる。
package main
import (
"log"
"os"
"plugin"
)
var (
outputLog plugin.Symbol
)
func init() {
var (
pi *plugin.Plugin
err error
)
if pi, err = plugin.Open("mylogger.so"); err != nil {
log.Printf("plugin Open() error: %s", err.Error())
os.Exit(-1)
}
if outputLog, err = pi.Lookup("OutputLog"); err != nil {
log.Printf("plugin Lookup() error: %s", err.Error())
os.Exit(-1)
}
}
func main() {
outputLog.(func(string))("Hello Golang Plugin!!")
}
plugin パッケージの Open
で共通ライブラリを Open し、Lookup
で共通モジュールから関数をロードする(正確には、変数もロードできる。要は、変数の型(plugin.Symbol
)のとおり、シンボルをロードするということだ)。
あとは、この main をビルドして、mylogger.so のおいてあるディレクトリで実行すると、
2020/11/01 11:55:27 Hello Golang Plugin!!
と出力される。
Lambda Layers で使う場合
Lambda Layers で使う場合も、共通モジュール側は特に何かを変更する必要はない。
Lambda Layers は /opt に展開されるらしいので、Lambda 関数側で
if pi, err = plugin.Open("/opt/mylogger.so"); err != nil {
と呼び出してあげよう。
また、Lambda Layers をLambda側に設定しなければいけない。
今回の例では Terraform を使い、
################################################################################
# Lambda #
################################################################################
resource "aws_lambda_function" "test" {
depends_on = [
aws_cloudwatch_log_group.lambda,
]
function_name = local.lambda_function_name
filename = data.archive_file.main.output_path
role = aws_iam_role.lambda.arn
handler = "lambda_function"
source_code_hash = data.archive_file.main.output_base64sha256
runtime = "go1.x"
memory_size = 128
timeout = 30
layers = [
aws_lambda_layer_version.test.arn,
]
}
################################################################################
# Lambda Layer #
################################################################################
resource "aws_lambda_layer_version" "test" {
layer_name = local.lambda_layer_name
filename = data.archive_file.layer.output_path
source_code_hash = data.archive_file.layer.output_base64sha256
}
な感じで設定する。
aws_lambda_layer_version
で定義したリソースを aws_lambda_function
の layers
のブロックで設定しよう。
正しく設定されると、マネージメントコンソールの Lambda の画面で、↓こんな感じで表示される。
マネージメントコンソールから設定する場合は、上記画面の「レイヤーの追加」で自分で設定しよう。
この関数を実行すると、
なログが出力される。
やったね!
実際のところの使い方の考察
さて、Lambda LayersをGolangで実装できることは分かったが、実際に使い物になるだろうか?
まずは、ここで書かれているように、異常に制限事項が多い。
- Layer と Function で Golang のコンパイラバージョンが一致している必要がある
- Layer と Function が同じパッケージを使っている場合、そのバージョンが一致している必要がある
うーん、おいそれと Layer の関数をデプロイ後に適用したら動かなくなる可能性がありそうだ。
そういうのもあり、Lambda Layer は、デプロイをしても呼び出し元の Lambda関数側は自動では切り替わらないし、$LATEST とかエイリアスも存在しないように見える。
しかし、共通ライブラリってそういうものなのだろうか?C言語の共通ライブラリなんかは、シンボル定義さえ合っていれば、何かあった時にはライブラリだけ修正して吸収することが可能だったはずだ。
まあ、AWS的な考え方としても、Golangの思想としても、共通処理を直すんだったらしっかりと呼び出し元でCI/CDを回してデプロイしろよ、という話なのかもしれない。
となると、やっぱり最初に書いたように、Git Submodules や ローカルGitリポジトリを使うのが良い気がする。だが、そうしたら、Golang における Lambda Layers の役割っていうのは一体何なのだろうか?もしかして不要なのかな……。