Lambdaの作成を通じて学ぶGolang(Lambdaのデプロイ)
普段Lambdaの開発にはPythonを使用していますが、最近Golangを使い始めたのでLambdaの作成を通じてGolangの勉強を進めたいと考えています。
今後APIなどLamdbaを使ったいくつかのパターンをまとめて投稿予定です。
今回は初回ということでCDKを使ってGolangで作成したLambdaをデプロイするところまでをまとめます。
取り扱う内容としては以下になります。(CDKはTypescriptで定義しています)
- CDKを使ったGolangの定義
- CDKにおけるGolangのパッケージ化(ビルド)
- CDKとSAM CLIを組み合わせたローカル稼働
- Golangを使ったLambdaの基本的な構造
今回作成したソースコードは以下から確認できます。
前提
- aws-cdkはインストール済み
- sam-cliはインストール済み
- AWSのクレデンシャル情報は設定済み
インストール方法についてはAWSの公式ドキュメントやその他参考資料を適宜参照の上ご対応ください。
AWS SAM CLI のインストール
Getting started with the CDK
Setup
CDKのプロジェクト作成や必要なライブラリのインストールなど、開発を実施するための前準備を行います。
プロジェクト構造の作成
cdkのファイル格納用のディレクトリを作成し、cdk init
コマンドでCDKのプロジェクトを作成します。
mkdir cdk
cd cdk
cdk init --language typescript
cdkディレクトリ内の構造は以下の通りになります。
.
├── README.md
├── bin
├── cdk.json
├── jest.config.js
├── lib
├── node_modules
├── package-lock.json
├── package.json
├── test
└── tsconfig.json
CDKで必要なライブラリのインストール
CDKでLambda Functionの定義を行うにあたって必要なライブラリのインストールを行います。
APIを作ったりS3Eventをトリガーにしたりすればそれに応じて必要なライブラリをインストールする必要がありますが、今回はトリガーなしでLambdaを作成しますので必要なのは@aws-cdk/aws-lambda
のみです。
npm install @aws-cdk/aws-lambda
ライブラリの詳細については@aws-cdk/aws-lambda moduleを参照してください。
Go側で必要なライブラリのインストール
libディレクトリ配下にLambdaのソースコードを格納するためのディレクトリとファイルを作成します。
.
├── cdk-stack.ts
+ └── lambda
+ └── api
+ └── main.go
作成したLambdaをGo Modulesとして管理するためgo mod
コマンドを実行し、GolangのLambdaで必要となるライブラリであるaws-lambda-go/lambda
をインストールします(詳細はLambdaの作成で説明します。)
cd lib/lambda/api
go mod init api
# ライブラリのインストール
go get github.com/aws/aws-lambda-go/lambda
これで依存ライブリラのダウンロードやディレクトリ構造の作成などの準備は完了です。
CDKにおけるLambdaの定義
プロジェクト作成時に自動で作成されるcdk-stack.ts
ファイルにLambdaの定義を追加します。
GolangのLambda Functionにおける定義のポイントの一つとしてバンドル処理(デプロイ用パッケージの作成)があると思います。
- Golangにおけるデプロイ用パッケージにはGo executable(
go build
で生成される実行用バイナリファイル)を含める必要がある - Go executableはソースコードをコンパイルして作成されるものなので、リポジトリの管理対象とはせずにCI/CDパイプラインなどデプロイ等の処理の中で自動的に実行したい
自分でスクリプトを書いたり、ライブラリの機能を利用したり色々な方法があるとは思いますが、今回は先ほどインストールしたaws-lambda-go/lambda
ライブラリの機能を利用します。
aws-lambda-go/lambdaにおけるコードの指定方法
まず前提としてCDKにおけるLambdaのソースコードの指定方法を簡単にまとめます。
aws-lambda-go/lambda - Handler Codeに記載してあるとおり、Lambdaのソースコードとして以下4つの指定方法があります。
- fromBucket
- S3バケットのオブジェクトを指定
- fromInline
- インラインでコードを記載する
- fromAsset
- ローカルのディレクトリかZIPファイルを指定
- fromDockerBuild
- ビルドしたDocker Imageを指定
以下は公式ドキュメントから転載したものですが、fromAsset
を使用してPythonのLambdaを定義する例です。
new lambda.Function(this, 'MyLambda', {
code: lambda.Code.fromAsset(path.join(__dirname, 'my-lambda-handler')),
handler: 'index.main',
runtime: lambda.Runtime.PYTHON_3_6,
});
前述の通りGoではソースコード→Go Executableへのコンパイルが必要なので、その部分をどうにかして自動化したいです。
bundlingオプションの活用
fromAssetにはbundling
というオプションの指定が可能で、これを使うことで任意のバンドル処理を定義でき、今回はこの方法を利用します。
bundling
オプションを指定することでDockerを使ったビルドを実行することが可能で、設定内容としてビルドに使用するイメージや実施するコマンドなどの指定が可能です。
以下が最終的なコードです。
import * as cdk from '@aws-cdk/core';
import * as lambda from '@aws-cdk/aws-lambda'
import * as path from 'path'
export class CdkStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// The code that defines your stack goes here
new lambda.Function(this, 'MyGoFunction', {
runtime: lambda.Runtime.GO_1_X,
handler: 'main',
code: lambda.Code.fromAsset(path.join(__dirname, './lambda/api'), {
bundling: {
image: lambda.Runtime.GO_1_X.bundlingImage,
command: [
'bash',
'-c',
['go test -v', 'GOOS=linux go build -o /asset-output/main'].join(
' && '
),
],
user: 'root',
},
}),
})
}
}
ビルド結果の出力先がasset-ouput
ディレクトリになっているのはライブラリの仕様としてasset-ouput
ディレクトリ配下のファイルがZIPされLambdaのコードとして使用されるからです。
詳細についてはBuilding, bundling, and deploying applications with the AWS CDKに非常にわかりやすくまとまっています。
実行環境についてはDockerを使わずにローカルで行うことも可能みたいです。こちらについても上記の記事に載っていますので、よろしければご覧ください。
ビルドの実行
後述するローカル稼働でも説明しますが、cdk synth
コマンドを実行することでビルドが行われ、デプロイパッケージが作成されます(今回の例だとgo build
による依存ライブラリのインストールとGo Executableの作成)。
実際にcdk synth
コマンドを実行すると以下の通りcdk.out
ディレクトリが作成され、CloudFormationのテンプレートとデプロイパッケージが格納されていることが確認できます。
これでデプロイパッケージの作成についてはやり方が確認できましたので、次にLambdaのソースコードを作っていきます。
Lambdaの作成
今回作成するLambdaのコードはGo の AWS Lambda 関数ハンドラーに記載してある内容そのままです。
eventとして受け取った名前を付け加えた文字列を返すだけの簡単なプログラムですね。
package main
import (
"context"
"fmt"
"github.com/aws/aws-lambda-go/lambda"
)
type MyEvent struct {
Name string `json:"name"`
}
func HandleRequest(ctx context.Context, name MyEvent) (string, error) {
return fmt.Sprintf("Hello %s!", name.Name), nil
}
func main() {
lambda.Start(HandleRequest) // ハンドラー関数を呼び出す必要がある
}
ポイントとしては以下3点になります。
- github.com/aws/aws-lambda-go/lambda パッケージを含める必要がある
- main関数の実装が必要になる
- github.com/aws/aws-lambda-go/lambda パッケージを利用してmain関数からハンドラー関数を呼び出す必要がある
contextやeventの活用についてはAPIの作成等を題材にした記事を執筆予定ですので、その際にまとめようと思います。
ローカルでの稼働
ローカルで稼働させる方法はいくつかあると思いますが、今回はAWS SAMを利用する方法を使います。
手順としては以下の通りです。
- cdk synthによるtemplateファイルの作成
- Temlateファイルで稼働対象のLambdaの確認
- AWS SAMによるLambdaの稼働
ポイントとしてはAWS SAMでLambdaをローカル稼働させたいが、AWS SAMでローカル稼働するためにはCloudFormationのテンプレート(CDKではなく)が必要になるため、CDK→CloudFormationの変換とそれに付随してテンプレートの確認が必要になります。
cdk synthによるtemplateファイルの作成
前述の通りcdk synth
コマンドでビルドが行えます。この際、CDKのコードからCloudFormationのコードを出力されますが、出力方法を指定します。
cdk synth --no-staging > template.yaml
Temlateファイルで稼働対象のLambdaの確認
cdk synth
コマンドで生成したtemplate.yamlファイルから稼働させたいLambdaのIDを確認します。
template.yamlファイルには色々なリソースが定義されていますが、Lambdaの定義はType: AWS::Lambda::Function
でされていますので、これをヒントに探すとスムーズだと思います。
以下は今回生成されたテンプレートファイルの中からLambdaの箇所を抜き出したものです。
...
MyGoFunction0AB33E85:
Type: AWS::Lambda::Function
Properties:
Code:
S3Bucket:
Ref: AssetParam
AWS SAMによるLambdaの稼働
稼働対象のLambdaのIDも特定できたので、後はLambdaを稼働させるだけです。
% sam local invoke MyGoFunction0AB33E85 --no-event
START RequestId: e3159a84-ac33-4bf4-9ac6-dcd7575a3b22 Version: $LATEST
END RequestId: e3159a84-ac33-4bf4-9ac6-dcd7575a3b22
REPORT RequestId: e3159a84-ac33-4bf4-9ac6-dcd7575a3b22 Init Duration: 0.88 ms Duration: 292.87 ms Billed Duration: 300 ms Memory Size: 128 MB Max Memory Used: 128 MB
"Hello !"%
また、-e
オプションをつけることでインプットとなるEventの情報を渡すことが可能です。
% sam local invoke MyGoFunction0AB33E85 -e lib/events/test.json
...
"Hello Test User!"%
eventを渡すとそれが出力結果に反映されることも確認できました。
指定可能なオプション等の詳細はsam local invokeを参照ください。
Deployの実施
ローカル稼働で確認が取れましたので、デプロイを実施していきます。
cdkディレクトリからcdk deploy
コマンドを実行して、デプロイを実施します。
cdk deploy
コマンドの実行が完了したらマネジメントコンソールからLambdaが作成されていることを確認します。
テストコマンドを実行して、デプロイ後も想定通りに動作していることを確認します。
確認結果にも問題はありませんでしたので、これで今回の対応は完了です。
おまけ(名称等の変更)
デプロイ結果でも分かったと思いますが、Lambdaの名前などが適当なので、該当する部分の設定を変更してみます。
変更対象としては以下2点です。
- Lambda Functionの名前が適当
- CloudFormation Stackの名前がデフォルト値(CdkStackのまま)
Lambda Functionの名前が適当
functionNameで、Lambdaの名前を指定することができます
Type: string (optional, default: AWS CloudFormation generates a unique physical ID and uses that ID for the function's name. For more information, see Name Type.)
A name for the function.
@aws-cdk_aws-lambda.Function
以下の通りfunctionNameの指定を加えて再度デプロイを実施します。
new lambda.Function(this, 'MyGoFunction', {
runtime: lambda.Runtime.GO_1_X,
+ functionName: 'GoSampleLambdaFunction',
handler: 'main',
Lambdaが指定した名前に変更されていることを確認しました。
CDKstackの名前
ここまで一切いじってきませんでしたが、bin
ディレクトリ直下のcdk.ts
ファイルでスタック自体の定義が行われているため、この部分の設定を変更します。
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from '@aws-cdk/core';
import { CdkStack } from '../lib/cdk-stack';
const app = new cdk.App();
- new CdkStack(app, 'CdkStack', {
+ new CdkStack(app, 'GoLambdaStack', {
この状態でcdk deployを実施したら、以下のエラーが出てデプロイが落ちてしまいました。
12:23:32 | CREATE_FAILED | AWS::Lambda::Function | MyGoFunction0AB33E85
GoSampleLambdaFunction already exists in stack arn:aws:cloudformation:ap-northeast-1:111111111111:stac
k/CdkStack/75bc0d30-c5ab-11eb-8bd3-0ea6b7b9f1b7
一度Stackを削除してから再度デプロイを実施すれば問題なくデプロイが完了しました。
Discussion