Lambda LayersをGolangで実装してみる

# はじめに
Lambda の共通処理と言えば Lambda Layers である。

Golang には共通モジュールの扱い方が他にもいくつかある(Git Submodulesを使うとか、ローカルのGitリポジトリを使う([過去の記事参照](https://qiita.com/neruneruo/items/fdbb9bbe02861223c215))とか)が、今回は、Lambda Layers を使った共通化の使用感を確認して考察してみよう。

# Go Plugin パッケージを使う
Lambda Layers を Golang で使う場合、Plugin パッケージを使用する。
まずは、このモジュールについてちゃんと知っておこう。

パッケージの説明は[公式](https://golang.org/pkg/plugin/)を確認。

ログ出力をする mylogger なプラグインを作ってみることにしよう。

```golang:mylogger.go
package main

import (
	"log"
)

func OutputLog(str string) {
	log.Printf(str)
}
```

これを、.so 形式で出力するようにビルドする。

```shell
$ GOOS=linux go build -buildmode=plugin -o mylogger.so
```

モジュールの呼び出し元を実装してみる。

```golang:main.go
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 関数側で

```golang
	if pi, err = plugin.Open("/opt/mylogger.so"); err != nil {
```

と呼び出してあげよう。

また、Lambda Layers をLambda側に設定しなければいけない。
今回の例では Terraform を使い、

```HCL
################################################################################
# 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 の画面で、↓こんな感じで表示される。

![キャプチャ1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/ed351c10-8c20-f97a-3121-884bc12662d4.png)

マネージメントコンソールから設定する場合は、上記画面の「レイヤーの追加」で自分で設定しよう。

この関数を実行すると、

![キャプチャ2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/8b825231-1d64-634a-829a-b3cb166b08e0.png)

なログが出力される。
やったね！

# 実際のところの使い方の考察
さて、Lambda LayersをGolangで実装できることは分かったが、実際に使い物になるだろうか？
まずは、[ここ](https://stackoverflow.com/questions/53960985/is-lambda-layers-compatible-with-golang)で書かれているように、異常に制限事項が多い。

- Layer と Function で Golang のコンパイラバージョンが一致している必要がある
- Layer と Function が同じパッケージを使っている場合、そのバージョンが一致している必要がある

うーん、おいそれと Layer の関数をデプロイ後に適用したら動かなくなる可能性がありそうだ。

そういうのもあり、Lambda Layer は、デプロイをしても呼び出し元の Lambda関数側は自動では切り替わらないし、$LATEST とかエイリアスも存在しないように見える。
しかし、共通ライブラリってそういうものなのだろうか？C言語の共通ライブラリなんかは、シンボル定義さえ合っていれば、何かあった時にはライブラリだけ修正して吸収することが可能だったはずだ。

まあ、AWS的な考え方としても、Golangの思想としても、共通処理を直すんだったらしっかりと呼び出し元でCI/CDを回してデプロイしろよ、という話なのかもしれない。
となると、やっぱり最初に書いたように、Git Submodules や ローカルGitリポジトリを使うのが良い気がする。だが、そうしたら、Golang における Lambda Layers の役割っていうのは一体何なのだろうか？もしかして不要なのかな……。