ぺい

渋谷系アドテクエンジニアの落書き

ServerlessでAWS Lambdaのデプロイをいい感じにする

Serverless Frameworkとは

f:id:tikasan0804:20180627074913p:plain

github.com

通常AWS Lambdaを使う上で面倒な手順としてある。

  • 関数を作成する
  • AWS マネジメントコンソールからLambdaの管理画面にアクセスする
  • 関数をアップロードする(圧縮するとかもある)
  • トリガーとなるイベントの設定など

上に書いたような手間を解消してくれるのが今回紹介する「Serverless Framework」(以下Serverless)です。

http://tikasan.hatenablog.com/entry/2018/06/27/083316

AWSしか使わない人へ

Serverless Frameworkは様々なクラウドへのサーバーレスをアプリケーションの展開を考えている人にはうってつけですが、AWSしか使わないなら、AWS SAMがおすすめでした。

tikasan.hatenablog.com

インストール

Serverlessのインストールにはnode(v4以上)の環境が必要です。

❯ node -v
v10.0.0

❯ npm install -g serverless
...

+ serverless@1.27.3
added 310 packages in 19.063s

❯ serverless -v
1.27.3

IAMユーザーの作成

ServerlessはAWS APIを使って操作をします。なので、Adminstrator Acessを付与したServerless用のユーザーの作成をして、そのユーザーを使って色々な操作をすることになります。(defaultの自分のアカウントでも良いですが)

f:id:tikasan0804:20180627072752p:plain

f:id:tikasan0804:20180627072756p:plain

f:id:tikasan0804:20180627072800p:plain

❯ vim ~/.aws/credentials

[default]
aws_access_key_id =  hoge
aws_secret_access_key = fuga

[serverless]
aws_access_key_id = hoge2
aws_secret_access_key = fuga2
❯ vim ~/.aws/config

[default]
output = json
region = ap-northeast-1

[serverless]
output = json
region = ap-northeast-1

credentialsどうする問題は、公式が結構頑張って書いてくれてるので、そこ見たら良さそう。
Serverless Framework - AWS Lambda Guide - Credentials
今回は、専用のユーザーを作成するパターンでやっていきます。

事始め

さっそくServerlessのプロジェクトを作成してみましょう。ここからは長ったらしい serverless ではなく、短縮形の sls を使っていきます。slsには、templeateを作成する機能があり、これを使うと良い感じのsampleを見ることが出来ます。

選べるテンプレートは以下の通り。(すごい)

 Template "true" is not supported. Supported templates are: "aws-nodejs", "aws-nodejs-typescript", "aws-nodejs-ecma-script", "aws-python", "aws-python3", "aws-groovy-gradle", "aws-java-maven", "aws-java-gradle", "aws-kotlin-jvm-maven", "aws-kotlin-jvm-gradle", "aws-kotlin-nodejs-gradle", "aws-scala-sbt", "aws-csharp", "aws-fsharp", "aws-go", "aws-go-dep", "azure-nodejs", "fn-nodejs", "fn-go", "google-nodejs", "kubeless-python", "kubeless-nodejs", "openwhisk-java-maven", "openwhisk-nodejs", "openwhisk-php", "openwhisk-python", "openwhisk-swift", "spotinst-nodejs", "spotinst-python", "spotinst-ruby", "spotinst-java8", "webtasks-nodejs", "plugin" and "hello-world".
~/go/src/github.com/pei0804/serverless-sample/serverless master*
❯ sls create --template aws-go
Serverless: Generating boilerplate...
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.27.3
 -------'

Serverless: Successfully generated boilerplate for template: "aws-go"
Serverless: NOTE: Please update the "service" property in serverless.yml with your service name

~/go/src/github.com/pei0804/serverless-sample/serverless master*
❯ ls
Makefile       hello          serverless.yml world

では、何が書かれているか見てみましょう。

hello/main.go

よくあるhello worldサンプル

package main

import (
    "github.com/aws/aws-lambda-go/lambda"
)

type Response struct {
    Message string `json:"message"`
}

func Handler() (Response, error) {
    return Response{
        Message: "Go Serverless v1.0! Your function executed successfully!",
    }, nil
}

func main() {
    lambda.Start(Handler)
}

world/main.go

package main

import (
    "github.com/aws/aws-lambda-go/lambda"
)

type Response struct {
    Message string `json:"message"`
}

func Handler() (Response, error) {
    return Response{
        Message: "Okay so your other function also executed successfully!",
    }, nil
}

func main() {
    lambda.Start(Handler)
}

Makefile

デプロイ先でも動くようにというお気持ちの入ったbuildコマンドが書かれていました。

build:
  go get github.com/aws/aws-lambda-go/lambda
  env GOOS=linux go build -ldflags="-s -w" -o bin/hello hello/main.go
  env GOOS=linux go build -ldflags="-s -w" -o bin/world world/main.go

serverless.yml

slsに関する設定が色々書いてある。ひとまず、serviceの名前と、regionだけ変えておく。

# Welcome to Serverless!
#
# This file is the main config file for your service.
# It's very minimal at this point and uses default values.
# You can always add more config options for more control.
# We've included some commented out config examples here.
# Just uncomment any of them to get that config option.
#
# For full config options, check the docs:
#    docs.serverless.com
#
# Happy Coding!

service: hello <-- ここ自分のつけたい名前を適当につける
# service: aws-go # NOTE: update this with your service name

# You can pin your service to only deploy with a specific Serverless version
# Check out our docs for more details
# frameworkVersion: "=X.X.X"

provider:
  name: aws
  runtime: go1.x

# you can overwrite defaults here
#  stage: dev
#  region: us-east-1
  region: ap-northeast-1 <-- 東京リージョンにしておく

# you can add statements to the Lambda function's IAM Role here
#  iamRoleStatements:
#    - Effect: "Allow"
#      Action:
#        - "s3:ListBucket"
#      Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ]  }
#    - Effect: "Allow"
#      Action:
#        - "s3:PutObject"
#      Resource:
#        Fn::Join:
#          - ""
#          - - "arn:aws:s3:::"
#            - "Ref" : "ServerlessDeploymentBucket"
#            - "/*"

# you can define service wide environment variables here
#  environment:
#    variable1: value1

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

functions:
  hello:
    handler: bin/hello
  world:
    handler: bin/world

#    The following are a few example events you can configure
#    NOTE: Please make sure to change your handler code to work with those events
#    Check the event documentation for details
# events:
#    events:
#      - http:
#          path: users/create
#          method: get
#      - s3: ${env:BUCKET}
#      - schedule: rate(10 minutes)
#      - sns: greeter-topic
#      - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
#      - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
#      - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
#      - iot:
#          sql: "SELECT * FROM 'some_topic'"
#      - cloudwatchEvent:
#          event:
#            source:
#              - "aws.ec2"
#            detail-type:
#              - "EC2 Instance State-change Notification"
#            detail:
#              state:
#                - pending
#      - cloudwatchLog: '/aws/lambda/hello'
#      - cognitoUserPool:
#          pool: MyUserPool
#          trigger: PreSignUp

#    Define function environment variables here
#    environment:
#      variable2: value2

# you can add CloudFormation resource templates here
#resources:
#  Resources:
#    NewResource:
#      Type: AWS::S3::Bucket
#      Properties:
#        BucketName: my-new-bucket
#  Outputs:
#     NewOutput:
#       Description: "Description for the output"
#       Value: "Some output value"

デプロイと実行

~/go/src/github.com/pei0804/serverless-sample/serverless master* 16s
❯ make build
go get github.com/aws/aws-lambda-go/lambda
env GOOS=linux go build -ldflags="-s -w" -o bin/hello hello/main.go
env GOOS=linux go build -ldflags="-s -w" -o bin/world world/main.go

~/go/src/github.com/pei0804/serverless-sample/serverless master*
❯ sls deploy
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (4.5 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
........................
Serverless: Stack update finished...
Service Information
service: hello
stage: dev
region: ap-northeast-1
stack: hello-dev
api keys:
  None
endpoints:
  None
functions:
  hello: hello-dev-hello
  world: hello-dev-world

~/go/src/github.com/pei0804/serverless-sample/serverless master*
❯ sls invoke -f hello
{
    "message": "Go Serverless v1.0! Your function executed successfully!"
}

~/go/src/github.com/pei0804/serverless-sample/serverless master*
❯ sls invoke -f world
{
    "message": "Okay so your other function also executed successfully!"
}

~/go/src/github.com/pei0804/serverless-sample/serverless master*
❯ sls logs -f hello
START RequestId: 7dbebc56-7995-11e8-be4f-4788beaf19d2 Version: $LATEST
END RequestId: 7dbebc56-7995-11e8-be4f-4788beaf19d2
REPORT RequestId: 7dbebc56-7995-11e8-be4f-4788beaf19d2  Duration: 0.75 ms   Billed Duration: 100 ms     Memory Size: 1024 MB    Max Memory Used: 27 MB

動いているっぽい。この時点で既に便利!!

f:id:tikasan0804:20180627081459p:plain マネジメントコンソールからも確認出来た。

stageとかprofileをcliからいじる

設定ファイルにハードコーディングせずに、stageとかprofile変えたいなーっていうアレ。やり方としては、sreverless.ymlのproviderのところに設定を加えれば良い感じになる。
内容としては、--profileに指定がなければdefaultを使う。--stageに指定がなければdevという感じ。

service: hello

provider:
  name: aws
  runtime: go1.x
  profile: ${opt:profile, self:custom.defaultProfile}
  stage: ${opt:stage, self:custom.defaultStage}
  region: ap-northeast-1
custom:
  defaultStage: dev
  defaultProfile: default

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

functions:
  hello:
    handler: bin/hello
  world:
    handler: bin/world

cliでオプションで渡す。

~/go/src/github.com/pei0804/serverless-sample/serverless master* 15s
❯ sls deploy --profile serverless --stage production
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (4.5 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
........................
Serverless: Stack update finished...
Service Information
service: hello
stage: production
region: ap-northeast-1
stack: hello-production
api keys:
  None
endpoints:
  None
functions:
  hello: hello-production-hello
  world: hello-production-world

f:id:tikasan0804:20180627082914p:plain いい感じ!!

とりあえず、今回はここまで、次回はロジックとハンドラ分けたり、もうちょっと踏み込んだ内容やってみる。
前はapexを使ったりしていたけど、serverlessもかなり良さそう。