Serverless Framework での AWS Lambda + Go ローカル開発事情

Serverless Framework での最近の AWS Lambda + Go ローカル開発事情について紹介します。

## はじめに

Lambda 開発という面では公式の [SAM CLI](https://github.com/awslabs/aws-sam-cli) の登場以降影が薄くなりつつある [Serverless Framework](https://serverless.com/) (以下 serverless)ですが、serverless 特有の便利機能や豊富なプラグインは魅力的ですし、過去に serverless でシステムを組んでしまったために今更 SAM に移行できない等の理由などからまだまだ使われるケースはあるのではないでしょうか。
とは言えやはり今から Lambda でサーバレスアプリケーションの新規開発をしようというケースでは中々 serverless は採用しにくいものがあります。
というのも Lambda 開発において最も重要視される要素の1つであるローカル実行機能という面において、serverless は SAM CLI に劣っているためです。
特に Go 言語で開発された Lambda 関数のローカル実行機能は長らく serverless ではサポートされておらず、Go で serverless 開発をしたい人たちにとっては大きな悩みのタネでした。

しかし、2020年3月現在その状況は変わりつつあるということをこの記事で紹介したいと思います。

## `sls invoke local --docker`

[`sls invoke local`](https://serverless.com/framework/docs/providers/aws/cli-reference/invoke-local/) というと SAM CLI で言うところの [`sam local invoke`](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-invoke.html) に相当する serverless のローカル実行コマンドですが、[v1.39.0](https://github.com/serverless/serverless/releases/tag/v1.39.0) からこのコマンドに `--docker` オプションが追加されました。
`--docker` オプションをつけて実行することにより、SAM CLI と同様に Docker の [lambci/lambda](https://github.com/lambci/docker-lambda) image から立ち上げたコンテナ内で Lambda 関数が実行されるようになります。これにより Lambda でサポートされている全ての runtime のローカル実行が可能となりました。

<details><summary>Lambda + Go の実行例</summary><div>

動作させるには以下を満たす環境が必要です。

- Node.js 10 以上
- Go 1.12 以上
- `docker` コマンドが `sudo` なしで使えること

node のプロジェクトを作って `serverless` をインストールします。

```console
$ npm init
$ npm install --save-dev serverless
```

Go のプロジェクトを作ります。serverless のテンプレート機能を使います。

```console
$ npx sls create --template aws-go-mod
$ ls
gomod.sh  hello  Makefile  node_modules  package.json  package-lock.json  serverless.yml  world
```

ローカル実行前にビルドしておきます。

```console
$ make
```

`invoke local` を実行します。
初回は `docker pull` などが走るので時間がかかります。

```console
$ npx sls invoke local --docker -f hello -d {}
```

実行に成功すると次のような出力が得られるはずです。

```
START RequestId: f74e55a3-cf4d-1306-4e4b-5d6fbf5b4f01 Version: $LATEST
END RequestId: f74e55a3-cf4d-1306-4e4b-5d6fbf5b4f01
REPORT RequestId: f74e55a3-cf4d-1306-4e4b-5d6fbf5b4f01  Init Duration: 294.93 ms        Duration: 2.32 ms       Billed Duration: 
100 ms  Memory Size: 1024 MB    Max Memory Used: 20 MB

{"statusCode":200,"headers":{"Content-Type":"application/json","X-MyCompany-Func-Reply":"hello-handler"},"multiValueHeaders":null,"body":"{\"message\":\"Go Serverless v1.0! Your function executed successfully!\"}"}
```

</div></details>

## `sls offline --useDocker`

[serverless-offline](https://github.com/dherault/serverless-offline) はローカル環境で API Gateway のエミュレートを行ってくれる serverless のプラグインです。`sls offline` コマンドでローカルサーバが立ち上がり、Lambda + API Gateway 構成の動作確認をローカルで行うことができます。SAM CLI で言うところの [`sam local start-api`](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-start-api.html) コマンドに相当する機能を提供してくれるプラグインですね。
この serverless-offline、[v5.x](https://github.com/dherault/serverless-offline/tree/v5.x-release)では Go のサポートを謳っているのですが実際にはバグで動作しません。そして現在開発が進められている v6-alpha では Go サポートの文言が削除されてしまっています。
しかし、[v6.0.0-alpha.54](https://github.com/dherault/serverless-offline/releases/tag/v6.0.0-alpha.54) から本家 serverless 同様 Docker のサポート(`--useDocker` オプション)が追加され、これによりひっそりと Go 製 Lambda を実行できるようになっています(こちらは .NET や Java はサポートされていないのですが、[近いうちにサポートされる見込み](https://github.com/dherault/serverless-offline/pull/906)です)。

<details><summary>Lambda + Go の実行例</summary><div>

動作させるには以下を満たす環境が必要です。

- Node.js 10 以上
- Go 1.12 以上
- `docker` コマンドが `sudo` なしで使えること

node のプロジェクトを作って `serverless` と `serverless-offline` をインストールします。
~~`serverless-offline` は alpha 版を入れる必要があるので `@next` が必要なことに注意してください。~~ 2020/03/20 v6.0.0 がリリースされました

```console
$ npm init
$ npm install --save-dev serverless serverless-offline
```

Go のプロジェクトを作ります。serverless のテンプレート機能を使います。

```console
$ npx sls create --template aws-go-mod
$ ls
gomod.sh  hello  Makefile  node_modules  package.json  package-lock.json  serverless.yml  world
```

`serverless.yml` に serverless-offline の設定を書きます。
次のように `plugins` と `custom` を書き加えてください。`custom` はなくても問題ありませんが書いておくことで実行時に `--useDocker` オプションが不要になります。

```yaml:serverless.yml
service: sls-offline-test
provider:
  name: aws
  runtime: go1.x

package:
  exclude:
    - ./**
  include:
    - ./bin/**

# 追加
plugins:
  - serverless-offline

# 追加
custom:
  serverless-offline:
    useDocker: true

functions:
  hello:
    handler: bin/hello
    events:
      - http:
          path: hello
          method: get
  world:
    handler: bin/world
    events:
      - http:
          path: world
          method: get
```


ローカル実行前にビルドしておきます。

```console
$ make
```

`sls offline` を実行します。

```console
$ npx sls offline
offline: Starting Offline: dev/us-east-1.
offline: Offline [http for lambda] listening on http://localhost:3002

   ┌───────────────────────────────────────────┐
   │                                           │
   │   GET | http://localhost:3000/dev/hello   │
   │   GET | http://localhost:3000/dev/world   │
   │                                           │
   └───────────────────────────────────────────┘

offline:
offline: [HTTP] server ready: http://localhost:3000 �🚀
offline:
offline: Enter "rp" to replay the last request
offline:
```

これでローカルサーバが起動しました。アクセス可能なパスの一覧が表示されるのでリクエストを送ってみましょう。
初回リクエスト時は `docker pull` などが走るので時間がかかりますが、正しく実行できていれば次のようにレスポンスが返ってくるはずです。

```console
$ curl localhost:3000/dev/hello
{"message":"Go Serverless v1.0! Your function executed successfully!"}
```
</div></details>

## まとめ

Serverless Framework とそのプラグインである serverless-offline に Docker サポートが追加され、Go 言語製 Lambda のローカル開発環境がかなり改善されたことを紹介しました。

Lambda 開発というとやはり公式の SAM CLI に乗っかるのが一番ではあるのですが、Serverless Framework もまだまだ捨てたものじゃないということがお分かりいただけたらと思います。