curl で DockerHub から公式イメージのタグ一覧を取得する方法

基本的には以下のやり方でOK。

qiita.com

curl https://hub.docker.com/v2/repositories/<repository name>/tags?page_size=100 | jq .results[].name

公式イメージを取得する場合は、リポジトリ名に library を指定する必要があるので注意。

以下では "magic string" と呼んでいる人がいる。公式ドキュメントに記載はない模様(見つけた人いたら教えて)。

stackoverflow.com

例えば python:3.6 の亜種を見つけたい場合は以下のようにする。

curl https://hub.docker.com/v2/repositories/library/python/tags?page_size=100 | jq .results[].name | grep 3.6

CKA, CKAD の試験では DockerHub を見ることができないので覚えておきたい。

が、、、そんな問題出るのか? Nginx Ingress だって試験中に公式ページ見れないけど全部覚える???

無理、そんなむずい問題でたら諦めます。

Serverless Framework で StepFunctions 作成とログ監視を設定する(1)

今回は Serverless Framework で StepFunctions を作成し、エラー時の Cloudwatch Alarm も併せて定義します。

構築する環境

今回構築する環境を超適当に書くとこんな感じです。

Step Functions の Map を使って後続の関数をスケールします。SQS 使わずに実装できてとても便利です。

また、Step Functions の実行がコケたときに通知するため、Alarm と SNS を実装しています。

プラグイン準備

今回は2つのプラグインを利用します。

$ npm install -g serverless-step-functions
$ npm install -g serverless-prune-plugin

www.serverless.com

www.serverless.com

Serverless プロジェクト作成

こんな感じでディレクトリを作成します。

$ tree stepfunctions-tutorial/
stepfunctions-tutorial/
├── email.yml
├── handler1.py
├── handler2.py
└── serverless.yml

0 directories, 4 files

email.yml は SNS トピックに設定するメールをブログでうっかり公開しないように serverless.yml から外出しするために作りました。

email.yml
address: hogehoge@fugafuga.com

Lambda 実装

適当に Lambda を実装します。

handler1.py
import json

def func1(event, context):
    body = {
        "message": "Go Serverless v3.0! Your function executed successfully!",
        "input": event,
        "targets": [0,1,2]
    }
    response = {"statusCode": 200, "body": body} # json.dumps しない。
    return response
handler2.py
import json

def func2(event, context):
    print(event)
    body = {
        "message": "Go Serverless v3.0! Your function executed successfully!",
        "input": event,
    }
    response = {"statusCode": 200, "body": json.dumps(body)}
    return response

handler1.py から body.targets 配列を返しています。Lambda の初期レスポンス値は json.dumps() されているのですが、dump するとエスケープされてしまうので json.dumps() しないように注意。

qiita.com

ちなみに、json.dumps() した値が後続処理に渡ると以下のようにマッピング失敗となります。

Serverless 設定

serverless.yml を書いていきます。

まずは全体像。

serverless.yml
org: talkeyboid
service: stepfunctions-tutorial
frameworkVersion: '3'

plugins:
  - serverless-step-functions
  - serverless-prune-plugin

provider:
  name: aws
  runtime: python3.9
  region: ap-northeast-1
  memorySize: 128
  timeout: 3
  logRetentionInDays: 14
  apiKeys:
    - ${self:service}-key
  usagePlan:
    quota:
      limit: 100
      offset: 0
      period: MONTH
    throttle:
      burstLimit: 3
      rateLimit: 1

custom:
  prune:
    automatic: true
    number: 3
  env:
    email: ${file(./email.yml)}

functions:
  func1:
    handler: handler1.func1
  func2:
    handler: handler2.func2

stepFunctions:
  stateMachines:
    myStateMachine1:
      name: myStateMachine1
      # role: "設定する場合はここにARN"
      loggingConfig:
        level: ALL
        includeExecutionData: true
        destinations:
        - Fn::GetAtt: [StepFunctionLogGroup, Arn]
      alarms:
        topics:
          alarm:
            Ref: EmailTopic
        metrics:
        - executionTimedOut
        - executionFailed
        - executionsAborted
        - executionThrottled
        treatMissingData: missing
      events:
      - http:
          path: hoge
          method: POST
          private: true
          # iamRole: "設定する場合はここにARN"
      definition:
        StartAt: Task1
        States:
          Task1:
            Type: Task
            Resource:
              Fn::GetAtt: [Func1LambdaFunction, Arn]
            Next: MapTask
          MapTask:
            Type: Map
            ItemsPath: "$.body.targets"
            Iterator:
              StartAt: Task2
              States:
                Task2:
                  Type: Task
                  Resource:
                    Fn::GetAtt: [Func2LambdaFunction, Arn]
                  End: true
            End: true

resources:
  Resources:
    EmailTopic:
      Type: AWS::SNS::Topic
      Properties:
        TopicName: EmailTopic
    EmailSubscription:
      Type: AWS::SNS::Subscription
      Properties:
        Protocol: email
        TopicArn: !Ref EmailTopic
        Endpoint: ${self:custom.env.email.address}
    StepFunctionLogGroup:
      Type: AWS::Logs::LogGroup
      DeletionPolicy: Delete
      Properties:
        LogGroupName: /aws/states/${self:service}-${self:stepFunctions.stateMachines.myStateMachine1.name}-Logs
        RetentionInDays: 14

ポイントをいくつか。

provider

serverless.yml
provider:
  name: aws
  runtime: python3.9
  region: ap-northeast-1
  memorySize: 128
  timeout: 3
  logRetentionInDays: 14
  apiKeys:
    - ${self:service}-key
  usagePlan:
    quota:
      limit: 100
      offset: 0
      period: MONTH
    throttle:
      burstLimit: 3
      rateLimit: 1

provider で メモリサイズとタイムアウト等を明示的に指定しています。serverless のデフォルト値だとオーバースペックすぎるためです。

apiKeys, usagePlan で API Gateway のキーと使用量プランを設定しています。これは明示的に設定しない限り作成されないので自分で書く必要があります。API キーが不要な場合は記載しなくていいです。

custom

serverless.yml
custom:
  prune:
    automatic: true
    number: 3
  env:
    email: ${file(./email.yml)}

custom.prune で Lambda のバージョン世代保持数を指定しています。デフォルトでは無限に増殖するため、抑えないといけません。今回は検証なので特に指定する必要はなかったですが、後学のために設定しました。

dev.classmethod.jp

また、env.emailemail.yml ファイルを設定しています。そのため、メールアドレスを取得したければ ファイル内のプロパティと結合し、${self:custom.env.email.address} で取得できます。

stepFunctions

今回の本題、StepFunctions の部分です。

ロググループについて

serverless.yml
stepFunctions:
  stateMachines:
    myStateMachine1:
      name: myStateMachine1
      # role: "設定する場合はここにARN"
      loggingConfig:
        level: ALL
        includeExecutionData: true
        destinations:
        - Fn::GetAtt: [StepFunctionLogGroup, Arn]

StepFunctions のロググループは明示的に設定しないと作成されないようなので、後述する resources でロググループを作成し、それを loggingConfig.destinationsFn::GetAtt で Arn を取得し設定しています。

Alarm について

serverless.yml
      alarms:
        topics:
          alarm:
            Ref: EmailTopic
        metrics:
        - executionTimedOut
        - executionFailed
        - executionsAborted
        - executionThrottled
        treatMissingData: missing

Cloudwatch Alarm の設定もここでできます。alarms プロパティの中で、後述する resources で定義した SNS トピックを設定しています。メトリクスは以下を参照。

docs.aws.amazon.com

欠損データの取り扱いについては以下を参照。

docs.aws.amazon.com

ここでは書いていませんが、スレッショルドも設定できると思うので次やるときには設定したいと思います。

API Gateway について

API Gateway の設定は Lambda の定義と同じく、events に設定します。API キーを利用する場合、private=true に設定します。

serverless.yml
      events:
      - http:
          path: hoge
          method: POST
          private: true
          # iamRole: "設定する場合はここにARN"

データフローについて

serverless.yml
      definition:
        StartAt: Task1
        States:
          Task1:
            Type: Task
            Resource:
              Fn::GetAtt: [Func1LambdaFunction, Arn]
            Next: MapTask
          MapTask:
            Type: Map
            ItemsPath: "$.body.targets"
            Iterator:
              StartAt: Task2
              States:
                Task2:
                  Type: Task
                  Resource:
                    Fn::GetAtt: [Func2LambdaFunction, Arn]
                  End: true
            End: true

StepFunctions の中身を definition に設定します。ここはマネジメントコンソールのビジュアルエディタで作成した json の内容をほぼそのまま転記する形で記載できそうです。

ただし、どの Lambda 関数を実行するかは若干のハックが必要です。Arn を指定する必要があるため、Fn::GetAtt をしていますが、ここへは functions へ定義した関数名をパスカルケースにし、後ろに LambdaFunction を付けます。

functions:
  func1: # これをパスカルケースにする -> Func1、+ LambdaFunction = Func1LambdaFunction
    handler: handler1.func1
  func2: # これをパスカルケースにする -> Func2、+ LambdaFunction = Func2LambdaFunction
    handler: handler2.func2

これは serverless の仕様で、serverless deploy(package) するときに作成される CloudFormation テンプレートにそのように定義されるためです。

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "The AWS CloudFormation template for this Serverless application",
  "Resources": {
  ...
    "Func1LambdaFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        ...
        "Handler": "handler1.func1",
        "Runtime": "python3.9",
        "FunctionName": "stepfunctions-tutorial-dev-func1",
        ...
  }
}

resources

serverless.yml
resources:
  Resources:
    EmailTopic:
      Type: AWS::SNS::Topic
      Properties:
        TopicName: EmailTopic
    EmailSubscription:
      Type: AWS::SNS::Subscription
      Properties:
        Protocol: email
        TopicArn: !Ref EmailTopic
        Endpoint: ${self:custom.env.email.address}
    StepFunctionLogGroup:
      Type: AWS::Logs::LogGroup
      DeletionPolicy: Delete
      Properties:
        LogGroupName: /aws/states/${self:service}-${self:stepFunctions.stateMachines.myStateMachine1.name}-Logs
        RetentionInDays: 14

Email 通知用の SNS トピック・サブスクリプションと、StepFunctions 用のロググループを作成しています。StepFunctions のデフォルトのロググループ名は /aws/vendedlogs/states/<statemachine name>-Logs のようになっているため、それに合わせています。※AWSで作成しているわけではないので、vendedlogs は抜いています。

RetentionInDays は完全に適当です。実務で使うときには考えましょう。

実行確認

マネジメントコンソールからエンドポイントとAPI キーを取得し、Postman に設定し、実行します。

項目
メソッド POST
URL ステージのエンドポイント + パス(https://~/dev/hoge
HTTPヘッダ x-api-key=APIキー

私はよくルートURLの後にパスをつけ忘れて 403 になってしまうことがあります。404 なら気づきようがあるのですが、403 だと「キー設定してるのにおかしいな...」となるので備忘として残しておきます。

実行結果は以下のように返ってきます。executionArn は ステートマシン名 + 実行ID(ランダム値)です。

{
    "executionArn": "arn:aws:states:ap-northeast-1:XXXXXXXXXXXX:execution:myStateMachine1:0fbeb2e9-581c-49ed-88b8-1c70e200a609",
    "startDate": 1.676463213988E9
}

感想

少々ハックはありますが、簡単に実装することができました。

Alarm の確認もしようと思ったのですが、結構時間がかかったのでまた次回。

Serverless Framework Functions プロパティ(2)

前回の記事

前回に引き続き serverless.yml の Functions プロパティについてまとめていきたいと思います。

基本的には以下のドキュメントを読んでまとめ、思うところを徒然に書いていきます。内容の保証はしません、というか間違ってる可能性も多いにあるので鵜呑みにせず一次情報に当たってください。

www.serverless.com

アーキテクチャ

Lambda の CPU アーキテクチャを指定します。

provider.architecture, functions.<function name>.architecture に記載します。

arm64 の場合

serverless.yml
functions:
  hello:
    architecture: arm64

インテルの場合はドキュメントに記載がないですが、x86_64 を書けばよいと思います。

ランタイム管理

セキュリティパッチ等のランタイム更新タイミングをモードとして指定します。

provider.runtimeManagement, functions.<function name>.runtimeManagement に記載します。

モードは auto, onFunctionUpdate, manual(デフォルト値) が指定可能です。

各モードの詳細はこちら。

docs.aws.amazon.com

provider に設定する場合、auto, onFunctionUpdate が利用可能です。

serverless.yml
provider:
  runtimeManagement: onFunctionUpdate

functions に設定する場合は3つのモードすべて記述することができます。この場合、mode の他に arn を指定する必要があります。

serverless.yml
functions:
  hello:
    runtimeManagement:
      mode: manual
      arn: <aws runtime arn>

"aws runtime arn" って何でしょうか? CloudFormation での記載方法と同じように ランタイム識別子である python3.9 とか nodejs16.x とかを記載すればよいのでしょうか。

識別子の一覧は以下参照。

docs.aws.amazon.com

SnapStart

コールドスタート問題を解消するためにキャッシュされた関数のスナップショットから起動する機能です。2023/2 現在では Java11 ランタイムのみでサポートされます。他にもいろいろと制限があるのでご注意。

docs.aws.amazon.com

functions.<function name>.snapStart に記載します。

serverless.yml
functions:
  hello:
    runtime: java11
    snapStart: true

VPC 設定

VPC Lambda 用設定です。

provider.vpc, functions.<function name>.vpc に記載します。

serverless.yml
functions:
  hello:
    vpc:
      securityGroupIds:
        - securityGroupId1
        - securityGroupId2
      subnetIds:
        - subnetId1
        - subnetId2

provider に設定しているが、一部の関数には適用したくない(パブリック Lambdaとしたい)場合には ~ で継承しないように設定できます。

serverless.yml
provider:
  name: aws
  vpc:
    securityGroupIds:
      - securityGroupId1
      - securityGroupId2
    subnetIds:
      - subnetId1
      - subnetId2

functions:
  hello: # パブリックな Lambda となる
    handler: handler.hello
    vpc: ~
  users: # provider 継承により VPC Lambda となる
    handler: handler.users

実行ロールについて 上記の設定を行った場合、デフォルトでは "AWSLambdaVPCAccessExecutionRole" がアタッチされます。

カスタムロールを利用する場合は、ENI の create, describe, delete 権限が必要なことに注意してください。

docs.aws.amazon.com

インターネットアクセスについて

デフォルトでは VPC Lambda はインターネットアクセスができないため、VPC エンドポイントや NAT GW の設定が必要なことに注意してください。

環境変数

provider.environment, function.<function name>.environment に記載します。両方に同じ環境変数が存在する場合は functions の値で上書きされます。

serverless.yml
provider:
  name: aws
  environment:
    SYSTEM_NAME: mySystem
    TABLE_NAME: tableName1
 
functions:
  hello: # 2つの環境変数を継承する
    handler: handler.hello
  users:
    handler: handler.users
    environment: # SYSTEM_NAME を継承し、TABLE_NAME は上書きする
      TABLE_NAME: tableName2

外の値を参照したい場合の書き方については以下参照。

www.serverless.com

タグ

provider.tags, functions.<function name>.tags に記載します。両方に同じタグが記載されている場合は functions の値で上書きされます。

serverless.yml
provider:
  tags:
    foo: bar
    baz: qux

functions:
  hello: # 2つのタグを継承する
    handler: handler.hello
  users:
    handler: handler.users
    tags: # baz を継承し、foo は上書きする
      foo: quux

Lambda レイヤー

functions.<function name>.layers に記載します。

serverless.yml
functions:
  hello:
    handler: handler.hello
    layers:
      - arn:aws:lambda:region:XXXXXX:layer:LayerName:Y

ロググループ

functions.<function name> 直下に disableLogs, logRetentionInDays, logDataProtectionPolicy を指定します。

デフォルトでは Serverless Framework により Lambda のロググループを新規作成されますが、これを無効化するために disableLogs が利用できます。

logRetentionInDays は文字通りログ保管期間(日数)の設定です。

logDataProtectionPolicy はログのマスク処理の設定をします。Serverless 側のドキュメントには詳細な記載方法がなかったのですが、AWS 公式ドキュメント記載のポリシー構文に従って書けばよさそうです。

docs.aws.amazon.com

serverless.yml
functions:
  hello:
    handler: handler.hello
    disableLogs: true # ロググループを新規作成しない
  goodBye:
    handler: handler.goodBye
    logRetentionInDays: 14 # 14日間保持
    logDataProtectionPolicy: # ポリシー構文に従い記述
      Name: data-protection-policy

既存のロググループを指定するときはどうするんだろう...(ベストプラクティスからはかけ離れていることは承知の上で)。

バージョニング

Serverless Framework では serverless deploy の度にバージョンを上げていきます。古いバージョンはフレームワークの管理対象ではないため自分で削除していく必要があります。

serverless deploy を2回やったときの図

関数バージョンをオフにするには provider.versionFunctions = false を設定します。

serverless.yml
provider:
  versionFunctions: false

DLQ

リトライが失敗した際の DLQ 送信先を設定します。

functions.<function name>.onError に SNS トピック ARN を設定します。resources で定義した SNS を組込関数(Ref, Fn::GetAtt など)で指定することもできそうです。

serverless.yml
functions:
  hello:
    handler: handler.hello
    onError: arn:aws:sns:us-east-1:XXXXXX:test

SQS のサポートは現状はないようなので、今後に期待。

KMS キー

環境変数を暗号化するための KMS キーを設定します。

service.awsKmsKeyArn, functions.<function name>.awsKmsKeyArn で指定することができ、functionsservice の値を継承または上書きします。

serverless.yml
service:
  awsKmsKeyArn: arn:aws:kms:us-east-1:XXXXXX:key/some-hash

provider:
  name: aws
  environment:
    TABLE_NAME: tableName1

functions:
  hello:
    handler: handler.hello
    awsKmsKeyArn: arn:aws:kms:us-east-1:XXXXXX:key/some-hash # キー上書き
    environment:
      TABLE_NAME: tableName2
  goodbye: # キー継承
    handler: handler.goodbye

X-Ray

X-Ray トレースを有効化します。

provider.tracing, functions.<function name>.tracing で設定します。functionsprovider の設定を継承または上書きします。

serverless.yml
provider:
  name: aws
  runtime: nodejs14.x
  tracing:
    lambda: true

functions:
  hello:
    handler: handler.hello
    tracing: Active
  goodbye:
    handler: handler.goodbye
    tracing: PassThrough

定義可能な値は Active, PassThrough です。

docs.aws.amazon.com

非同期呼出

送信先

非同期に実行された Lambda 関数の処理結果を他の Lambda や AWS サービスに送ることができます。ターゲットとして同じサービス内の Lambda 関数、外部の Lambda 関数、EventBridge イベントバス、SQS キュー、SNS トピックを設定できます。

functions.<function name>.destinationsonSuccess, onFailure に識別子※を設定します。※同じ serverless.yml 内で解決可能な関数名もしくは ARN。

ARN に CloudFormation 組込関数を利用する場合は明示的に type を指定する必要があります。

typesns, sqs, eventBus, function から選択します。

serverless.yml
functions:
  asyncHello:
    handler: handler.asyncHello
    destinations:
      onSuccess: otherFunctionInService # 同じサービス内の他 Lambda 関数(serverless 内で解決可能)
      onFailure: arn:aws:sns:us-east-1:xxxx:some-topic-name # SNS トピック ARN
  asyncGoodBye:
    handler: handler.asyncGoodBye
    destinations:
      onFailure:
        type: sns # ARN に CloudFormation 組込関数を利用する場合
        arn:
          Ref: SomeTopicName

ほぼ SAM と同じ書きぶりですね。

docs.aws.amazon.com

リトライ試行

リトライ期間とリトライ回数を設定します。

リトライ期間は functions.<function name>.maximumEventAge, リトライ回数は functions.<function name>.maximumRetryAttempts に設定します。

それぞれ、60 ~ 21600 秒(6時間)、0 ~ 2 (回)で設定できます。

serverless.yml
functions:
  asyncHello:
    handler: handler.asyncHello
    maximumEventAge: 7200
    maximumRetryAttempts: 1

maximumEventAgemaximumRetryAttempts は片方しか必要なくても両方定義する必要があるようです。

blog.serverworks.co.jp

エラーハンドリングについては以下の記事がとても参考になります。

dev.classmethod.jp

EFS

これは個人的にほとんど使わなそうなので軽く。。。というか EFS マウントできるんですね。恥ずかしながら初めて知りました(VPC Lambda やったことないからという言い訳をしておきます)。

functions.<function name>.fileSystemConfig に マウント先の localMountPath と EFS の ARN arn を指定します。

serverless.yml
functions:
  hello:
    handler: handler.hello
    fileSystemConfig:
      localMountPath: /mnt/example
      arn: arn:aws:elasticfilesystem:us-east-1:111111111111:access-point/fsap-0d0d0d0d0d0d0d0d0
    vpc:
      securityGroupIds:
        - securityGroupId1
      subnetIds:
        - subnetId1

エフェメラルストレージ

Lambda の /tmp サイズです。

functions.<function name>.ephemeralStorageSize に設定します。512 ~ 10240 MB で指定可能です。

functions:
  helloEphemeral:
    handler: handler.handler
    ephemeralStorageSize: 1024

Lambda Hashing Algorithm 移行

この章は現在 v3 を利用していて provider.lambdaHashingVersion = 20200924 を設定している人のためのマイグレーションガイドのようです。

provider.lambdaHashingVersion は Serverless Framework が関数のパッケージングに利用するハッシュアルゴリズムのバージョンですが、v3 では諸々の問題が修正され、初めから v3 を利用する人にとってはあまり関係がなさそうなので割愛します。

現在は非推奨ですね。

www.serverless.com

感想

2回にわたって Functions プロパティを見てきました。大体把握しましたが実際に書いてみないとなんともという感じですね。

そういえば、Lambda の基本的な設定であるメモリやタイムアウト設定はどこにあるんでしょうか。

ドキュメント一番上の functions 記載例の中にしれっと memorySize, timeout, provisionedConcurrency などの設定が入っていますね。全部が全部親切にドキュメンテーションされているわけではないので詰まったら都度ググるが正解ですかね。

ちなみに今回はドキュメントを読んでいきましたが、最新版をいち早く見るにはやはりリポジトリ直で見るのがよさそうです。ついこの間GAされた機能もサポートされていてスピード感がすごい。examples もとても充実していますね。

github.com

以上で Function の回を終わります。次回は API Gateway 設定か StepFunctions 周りを見ていきたいと思います。

WSL2 で時刻がめちゃめちゃになって AWS認証情報が Signature expired になる

WSL2 Ubuntu 20.04 で serverless deploy をしようとしたところ以下のエラーに遭遇しました。

$ serverless deploy

Deploying aws-python-http-api-project to stage dev (ap-northeast-1)

✖ Stack aws-python-http-api-project-dev failed to deploy (5s)
Environment: linux, node 18.12.1, framework 3.27.0, plugin 6.2.3, SDK 4.3.2
Credentials: Local, "talkeyboid-serverless-user" profile
Docs:        docs.serverless.com
Support:     forum.serverless.com
Bugs:        github.com/serverless/serverless/issues

Error:
Signature expired: 20230205T195512Z is now earlier than 20230207T123422Z (20230207T124922Z - 15 min.)

署名の時刻がかなり怪しい。

Ubuntu 時刻と Windows 時刻を確認。

$ date
Mon Feb  6 04:56:54 JST 2023
>date
現在の日付: 2023/02/07

>time
現在の時刻: 21:51:12.03

めちゃくちゃずれてる???

ということで、NTP サーバ同期します。こんなにずれていてはしょうがないので強制一発同期(-b)です。

$ sudo apt install ntpdate
$ sudo ntpdate -b ntp.nict.jp
$ date
Tue Feb  7 21:54:01 JST 2023

うまく同期できたので再度 serverless deploy

$ serverless deploy

Deploying aws-python-http-api-project to stage dev (ap-northeast-1)

✔ Service deployed to stack aws-python-http-api-project-dev (129s)

functions:
  hello: aws-python-http-api-project-dev-hello (85 kB)

無事にデプロイすることができました。

なんでこんなにもずれるのか、WSL2 のカーネル Hyper-V 周りだそうです(あまり詳しくないので首を突っ込まない)。

qiita.com

Serverless Framework Functions プロパティ(1)

前回の記事

今回は serverless.yml の Functions プロパティについてまとめてみたいと思います。ここが Serverless Framework で一番大事なところですね(たぶん)。

基本的には以下のドキュメントを読んでまとめ、思うところを徒然に書いていきます。内容の保証はしません、というか間違ってる可能性も多いにあるので鵜呑みにせず一次情報に当たってください。

www.serverless.com

設定方法(全般)

Provider 継承

  1. provider プロパティで設定した値は全ての関数に継承される。
  2. functions プロパティで設定した値が provider と異なる場合、functions の値で上書きされる。

1の例

provider.runtime = nodejs14.x ですが、functions.hello.runtime = python2.7 となっています。

serverless.yml
provider:
  name: aws
  runtime: nodejs14.x

functions:
  hello:
    handler: handler.hello
    name: ${sls:stage}-lambdaName
    runtime: python2.7

2の例

runtime, memorySize がすべての関数に適用されます。

serverless.yml
provider:
  name: aws
  runtime: nodejs14.x
  memorySize: 512

functions:
  functionOne:
    handler: handler.functionOne

関数定義

関数定義と実コードの関係は functions.<function name>.handler = <file name>.<function(module) name> です。

関数定義

serverless.yml
functions:
  hoge:
    handler: handler.fuga

実コード:Python の場合

handler.py
def fuga(event, context):
  return ""

実コード:JavaScript の場合

handler.js
module.exports.fuga = function (event, context, callback) {};

以下の定義方法が可能です。

  1. functions プロパティには複数の関数を定義することができる。
  2. 関数は別ファイルに外出しすることができる。

1の例

serverless.yml
functions:
  functionOne:
    handler: handler.functionOne
    description: optional description for your Lambda
  functionTwo:
    handler: handler.functionTwo
  functionThree:
    handler: handler.functionThree

2の例

serverless.yml
# serverless.yml
functions:
  - ${file(./hoge-functions.yml)}
  - ${file(./fuga-functions.yml)}
hoge-functions.yml
getHoge:
  handler: handler.getHoge
deleteHoge:
  handler: handler.deleteHoge

権限設定

Lambda にアタッチする IAM ロールの設定は provider.iam.role.statements で設定します。

書き方は IAM ポリシーの記載と同じ書き方なのでなじみやすいですね。また、Resources は CloudFormation の組込関数が利用可能です。ただし、serverless deploy 実行時に Serverless Framework として検証されることはなく、CloudFormation 側で解釈されるので適切に設定する必要があります(実行した後にCloudFormation スタックがこけて間違いに気づくということ)。

serverless.yml
provider:
  iam:
    role:
      statements: # このプロパティに権限を設定
        - Effect: Allow
          Action:
            - dynamodb:DescribeTable
            - dynamodb:Query
            - dynamodb:Scan
            - dynamodb:GetItem
            - dynamodb:PutItem
            - dynamodb:UpdateItem
            - dynamodb:DeleteItem
          Resource: 'arn:aws:dynamodb:us-east-1:*:*'

        - Effect: 'Allow'
          Action:
            - 's3:ListBucket'
          # CloudFormation の組込関数が利用可能。ただしバリデーションされることはないため注意。
          Resource: { 'Fn::Join': ['', ['arn:aws:s3:::', { 'Ref': 'ServerlessDeploymentBucket' }]] }

        - Effect: 'Allow'
          Action:
            - 's3:PutObject'
          Resource:
            Fn::Join:
              - ''
              - - 'arn:aws:s3:::'
                - 'Ref': 'ServerlessDeploymentBucket'
                - '/*'

既存の IAM ロールがある場合には、provider.iam.role にARN 指定ができます。

serverless.yml
provider:
  iam:
    role: arn:aws:iam::YourAccountNumber:role/YourIamRole

IAM 権限の詳細については別ページに記載されています。絶対大事ですが、今回はスルーします(余裕があれば別記事で書きます)。

www.serverless.com

関数 URL

これは、AWS Lambda Function URLs を利用するためのプロパティです。Lambda 単体でも HTTP エンドポイントを作成できる機能ですね。

dev.classmethod.jp

dev.classmethod.jp

利用ケースによっていくつかの定義パターンがあります。

serverless.yml
functions:
  func:
    handler: index.handler
    url: true # HTTP エンドポイントが作成される(CORS なし)
serverless.yml
functions:
  func:
    handler: index.handler
    url:
      authorizer: aws_iam # IAM 認証必須(lambda:InvokeFunctionUrl が Allow であるか)
serverless.yml
functions:
  func:
    handler: index.handler
    url:
      cors: true # CORS設定有で HTTP エンドポイントを作成
serverless.yml
functions:
  func:
    handler: index.handler
    url:
      cors: # CORS設定をカスタムしたい場合
        allowedOrigins:
          - https://url1.com
          - https://url2.com
        allowedHeaders:
          - Content-Type
          - Authorization
        allowedMethods:
          - GET
        allowCredentials: true
        exposedResponseHeaders:
          - Special-Response-Header
        maxAge: 6000 # In seconds

プロパティと CORS ヘッダの対応はドキュメント参照

Docker コンテナイメージを利用した関数

直近で利用しなさそうなので軽く目を通す程度に。。。

  • Serverless は ECR リポジトリを作成するが更新管理は行わない。リポジトリは新しいサービスや関数を初めてデプロイするときにのみ作成される。
  • scanOnPush を指定することで CVE スキャンの設定ができる。
  • イメージ URI を直で指定することが可能。
serverless.yml
provider:
  ecr:
    scanOnPush: true
    images:
      baseimage:
        path: ./path/to/context # docker build が走るディレクトリ?
        file: Dockerfile.dev # Dockerfile を明示的に指定する場合
        buildArgs: # --build-arg
          STAGE: ${opt:stage}
        cacheFrom: # --cache-from
          - my-image:latest
        platform: linux/amd64
      anotherimage:
        # イメージを直で指定
        uri: 000000000000.dkr.ecr.sa-east-1.amazonaws.com/test-lambda-docker@sha256:6bb600b4d6e1d7cf521097177dd0c4e9ea373edb91984a505333be8ac9455d38
  • function 側でイメージを直で指定できるが、handler, runtime プロパティは指定できない。

イメージ内にランタイム設定・ソースコードがあるのでそこはいじれないよということでしょう。

serverless.yml
provider:
  name: aws
  ecr:
    images:
      baseimage:
        path: ./path/to/context
        # 他のプロパティの指定がないので、単に docker build が走って Dockerfile を読みに行く?

functions:
  hello:
    image: 000000000000.dkr.ecr.sa-east-1.amazonaws.com/test-lambda-docker@sha256:6bb600b4d6e1d7cf521097177dd0c4e9ea373edb91984a505333be8ac9455d38
  world:
    image: baseimage

serverless.yaml 側で workingDirectory, entryPoint, command は指定可能なようです。

serverless.yml
service: service-name
provider:
  name: aws
  ecr:
    images:
      baseimage:
        path: ./path/to/context

functions:
  hello:
    image:
      uri: 000000000000.dkr.ecr.sa-east-1.amazonaws.com/test-lambda-docker@sha256:6bb600b4d6e1d7cf521097177dd0c4e9ea373edb91984a505333be8ac9455d38
      workingDirectory: /workdir
      command:
        - executable
        - flag
      entryPoint:
        - executable
        - flag
  world:
    image:
      name: baseimage
      command:
        - command
      entryPoint:
        - executable
        - flag

ちょっと長くなってしまったので続きはまた今度。

Serverless Framework トップレベルプロパティ

前回の記事

今回は serverless.yml のトップレベルのプロパティについてまとめてみたいと思います。

基本的には以下のドキュメントを読んで要約し、思うところを徒然に書いていきます。

www.serverless.com

トップレベルプロパティ一覧

トップレベルのプロパティの分類は以下の構成になっています。

分類 設定内容(例)
Root Properties サービス名
Serverless Framework API バージョン
バリデーション設定
.env 利用有無
Deprication通知設定
Parameters 環境変数
Provider クラウドプロバイダ設定(以下 AWS 前提で記載)
CloudFormation設定(デプロイメソッド・ロールバック)
Lambda設定(ランタイム・メモリサイズ)
デプロイ先 S3 バケット設定(パブリックアクセス・バージョニング・暗号化)
API Gateway v2 設定(CORS・認証)
API Gateway v1 設定(APIキー・使用量プラン)
ALB 設定(認証)
ECR 設定(Lambdaを Docker イメージで動かす場合)
CloudFront 設定(キャッシュポリシー)
IAM 設定(Lambda ロール)
VPC 設定 (VPC Lambda 利用の場合)
Logs 設定(フォーマット・ログレベル)
Lambda Events としての S3 設定(バケット名・バージョニング)
Package デプロイする関数のディレクトリ・アーティファクトzip
Functions Lambda 関数。コンセプトで触れた "Event" もこの中で設定。
Function layers Lambda レイヤーの設定
AWS Resources その他 AWS リソース(CloudFormation テンプレート)

Provider 設定では全体的な共通設定が行えるようです。例えば Lambda のメモリサイズについては Provider でも Functions でも設定可能なので、"Functions に定義がなければ Provider 設定を反映" のような動きをするのだと思います。

API Gateway v1 (REST API)と API Gateway v2 (HTTP API)の違いは以下参照。

docs.aws.amazon.com

dev.classmethod.jp

Serverless Framework では、v1, v2 と呼び分けているので、あたかも v2 のほうが良い様に見えますが、特殊な要件がないならば v1(REST API) でしょう。

実際の構成は以下のようになります。上のテーブルの「分類」列をコメントとして記載しています。

serverless.yml
# Root Properties
service: myservice
frameworkVersion: '3'
configValidationMode: error
useDotenv: true
approached deprecation
deprecationNotificationMode: warn:summary
disabledDeprecations:
  - DEP_CODE_1 # Deprecation code to disable
  - '*' # Disable all deprecation messages

# Parameters
params:
  prod:
    my-parameter: foo
  dev:
    my-parameter: bar

# Provider
provider:
  name: aws
  stage: dev
  region: us-east-1
  ...(略)

# Package
package:
  patterns:
    - src/**
    - handler.js
    - '!.git/**'
    - '!.travis.yml'
  individually: true
  artifact: path/to/my-artifact.zip
  excludeDevDependencies: false

# Functions
functions:
  hello:
    handler: users.create
    image: baseimage
    runtime: nodejs14.x
    ...(略)


# Function Layers
layers:
  hello:
    path: layer-dir
    name: ${sls:stage}-layerName
    description: Description of what the lambda layer does
    compatibleRuntimes:
      - python3.8
    ...(略)


# AWS Resources
resources:
  Resources:
    usersTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: usersTable
        AttributeDefinitions:
          - AttributeName: email
            AttributeType: S
        ...(略)

  extensions:
    UsersCreateLogGroup:
      Properties:
        RetentionInDays: '30'

  Outputs:
    UsersTableArn:
      Description: The ARN for the User's Table
      Value: !GetAtt usersTable.Arn
      Export:
        Name: ${self:service}:${sls:stage}:UsersTableArn

まとめ

Serverless Framework のコンセプトとマッピングすると以下のようになるでしょうか。

  • Service:serverless.yml ファイル単位。Root Properties にサービス名を service として指定。
    • Function:functions として指定。
      • Event:functions.<function name>.events に指定。
    • Resource:resources に指定。

その他にも、params, package, layers がトップレベルで指定可能なことがわかりました。

Serverless Framework のコンセプトについて

前回記事

Serverless Framework を利用するにあたり、コンセプトの理解が不可欠であると感じたのでまとめます。

基本的には以下のドキュメントを読んで要約し、思うところを徒然に書いていきます。

www.serverless.com

コンセプト

以下、公式ドキュメントでは "プロパティ" と書かれている部分を yaml ファイルの "フィールド" という意味で言い換えている場合があります。特に意図があるわけではないので適宜読み替えてください。

考え方

  • Serverless Framework は AWS Lambda 関数と Lambda 周りのインフラリソース開発のために利用できる
  • 構造化、自動化やベストプラクティスを提供するCLI(serverless のこと)を利用し、Functions と Events で構成される洗練されたサーバレスアーキテクチャの構築に集中できる

ここでは Lambda と書かれていますが、他のクラウドでも利用可能です。

www.serverless.com

特徴

  • コードとインフラの両方を管理する
  • 複数の言語をサポートする(Node.js, Python, Javaなど)

つまり、「Lambda を中心としたサーバレスアーキテクチャ開発のために利用でき、インフラとコードをまとめて管理するためのフレームワークである」ということです。

逆に言うと、Lambda(や他クラウドの FaaS) を伴わない開発の場合、特に Serverless Framework を採用する動機はないということでしょうかね。functions フィールドを指定しないとエラーになるとかそんなコントロールもあるのかしら。

Functions

  • コードは Lambda で実行される
  • 各関数はマイクロサービスのように独立した単位で実行・デプロイされる
  • 以下のような単一の処理を実行するためのコードをデプロイするために利用することが多い
    • ユーザ情報の保存
    • データベースのファイルを処理
    • スケジュールタスクの実行

「マイクロサービスのような」と書いてありますが、これは単に 「Lambda 関数は独立した単位でデプロイされる」という以上の意味はないですかね?もしくは、Serverless Framework の "サービス" は文字通りサービス毎に構築するのがベストプラクティスということでしょうか。そうした場合、現実問題としてサービスをまたぐDBも普通にあると思うのでどのサービスが主となり管理するかなど設計上の問題も出てきそうですね。

Events

  • 関数は以下のイベントによってトリガーされる
    • API Gateway URL へのリクエスト
    • S3 バケットへのアップロード
    • CloudWatch スケジュール
    • SNS トピックのメッセージ
    • CloudWatch アラート
    • etc...
  • Lambda にイベントを設定すると Serverless Framework は自動的にそのイベントに必要なリソースを作成し、それをリッスンするように設定する

例えば、Lambda 関数のみを serverless.yml に定義していたとしても、functions.<function name>.events フィールドに http アクセスの記載があった場合は API Gateway も作成されるということです。

Resources

  • Resource は以下のような AWS コンポーネントである
    • DynamoDB テーブル
    • S3 バケット
    • SNS トピック
    • CloudFormation で定義できる全てのリソースは Serverless Framework でサポートされる
  • Serverless Framework は Lambda 関数とそのイベントだけでなく、AWS リソースもデプロイできる

おっと、「CloudFormation で定義できるすべてのリソースが管理可能である」との文言が。また、 Lambda 関数とそのイベントだけでなく、AWS リソースもデプロイできる」ともありますね。

基本的には FaaS 利用に特化したほうがいい気がしますが、その他の用途でも Serverless Framework が利用できるのですね。どうなんでしょう、売り文句程度に捉えていいんですかね?他フレームワークとの使い分けや共存はここら辺が参考になりそうです(※まだ読んでないですが)。

dev.classmethod.jp

qiita.com

Services

  • Service とは Framework の管理単位
  • 一つのアプリケーションに対して複数のサービスを持つことができるが、Service は一つのプロジェクトファイルとして考えよ
  • Service は serverless.yml ファイルで構成され、関数・イベント・AWS リソースを以下のように定義する
  • 設定ファイル内のすべてが一度にデプロイされる
service: users

functions: # Your "Functions"
  usersCreate:
    events: # The "Events" that trigger this function
      - httpApi: 'POST /users/create'
  usersDelete:
    events:
      - httpApi: 'DELETE /users/delete'

resources: # The "Resources" your "Functions" use. Raw AWS CloudFormation goes in here.

serverless.yml と管理単位である Service は1対1(というよりファイルがサービスそのもの)であり、関連する関数やリソースは全て1ファイル内に書く。ファイル内に複数の関数を定義することができる。」ということですかね。

また、resources フィールドは素の CloudFormation を記述するようです。

設定形式

  • serverless.yaml 形式ではなく、serverless.json, serverless.js, serverless.ts で記載することもできる
  • JavaScript, TypeScript を使用する場合、以下のように設定をオブジェクトとしてエクスポートする必要がある
serverless.js
'use strict';

module.exports = {
  service: 'users',
  functions: {
    usersCreate: {
      events: [
        {
          httpApi: 'POST /users/create',
        },
      ],
    },
    // ...
  },
  resources: {},
};
serverless.ts
// Requiring @types/serverless in your project package.json
import type { Serverless } from 'serverless/aws';

const serverlessConfiguration: Serverless = {
  service: 'users',
  functions: {
    usersCreate: {
      events: [
        {
          httpApi: 'POST /users/create',
        },
      ],
    },
    // ...
  },
  resources: {},
};

module.exports = serverlessConfiguration;

動的に serverless.yml を作成したい場合に便利そうですね。一方でコードの管理(というか保守)が大変そうなので個人的には静的ファイルで管理したいです。コードを Git 管理するときも最終的な差分が目に見えてわかりやすいので、動的に吐くにしても静的ファイルに一旦落としてから serverless deploy したい。ここら辺は好みですかね?

プラグイン

  • プラグインを利用し機能の上書き・拡張が可能
  • plugins プロパティがあり、複数のプラグインを適用できる
serverless.yml
plugins:
  - serverless-offline
  - serverless-secrets

直近で StepFunction を作りたいので、プラグインにはお世話になりそうです。

まとめ

今回見てきた概念の関係をまとめるとこんな感じですかね?

  • Service:サービス単位、アプリケーションに関連する関数・イベント・リソースをこの中に入れる

    • Function:Lambda 関数
    • Event:Lambda トリガーのためのイベント(API Gateway や Cloudwatch、SNS など)
    • Resource:DynamoDB, S3 などの Lambda で処理するデータリソース。CloudFormation の記法で記述する。
  • yaml 形式だけでなく json, JavaScript, TypeScript でも記述可能

  • 拡張機能利用のためのプラグインが利用可能

コンセプトがあらかたわかったので次回は serverless.yml のトップレベルプロパティについて見ていこうと思います。