Lambda + Goでネストされたアプリケーションを構築

f:id:moriwaki111:20210323212504p:plain

エキサイト株式会社で新規事業の開発を行っている森脇です。

新規事業ではawsを使いシステムの構築を行っています。 当初の計画ではAPIをLambdaで作成する予定になっており開発を進めておりましたが、 幾つか課題が出てきてしまい、その時の話を書こうと思います。

言語はGoを採用し、最初は小規模の認識で開発を行っていたが、仕様がどんどん膨らみ気づいた時にはエンドポイントが150を超え、そしてやってきたリソース制限

Template format error: Number of resources, 206, is greater than maximum allowed, 200

ネストさせることで回避できることを知り、ネストするもsam buildできず、結局ネストされた各アプリケションを個別にビルドし、マージするシェルを書いて運用していました。 苦労した話を書こうと思って調べていると、いつの間にかネストされたアプリケーションのビルドができるようになっていた!!!

f:id:moriwaki111:20210323210711p:plain

前段の話が長くなりましたが、Lambdaでネストされたアプリケーションを簡単に構築できる話です

構築

2つのアプリケーションにネストさせる例です

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  lambda-go
  Sample SAM Template for lambda-go

Parameters:
  Region:
    Type: String
    Default: ap-northeast-1
  Stage:
    Type: String
    Default: Dev
  ApiDomainName:
    Type: String
    Default: api.example.com

Resources:
  # Lambda Application
  App1Application:
    Type: AWS::Serverless::Application
    Properties:
      Location: app1.yaml
      Parameters:
        Region: !Ref Region
        Stage: !Ref Stage
        ApiDomainName: !Ref ApiDomainName
  App2Application:
    Type: AWS::Serverless::Application
    Properties:
      Location: app2.yaml
      Parameters:
        Region: !Ref Region
        Stage: !Ref Stage
        ApiDomainName: !Ref ApiDomainName

アプリケーション1

app1.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  lambda-go
  Sample SAM Template for lambda-go
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 10
    Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object
      Variables:
        REGION: !Ref Region

Parameters:
  Stage:
    Type: String
  Region:
    Type: String
  ApiDomainName:
    Type: String

Resources:
  # ロール
  App1Role:
    Type: AWS::IAM::Role
    Properties:
      RoleName: app1-role
      Policies:
        - PolicyName: app1-policy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - ec2:CreateNetworkInterface
                  - ec2:DescribeNetworkInterfaces
                  - ec2:DetachNetworkInterface
                  - ec2:DeleteNetworkInterface
                Resource: '*'
      AssumeRolePolicyDocument: {
        Version: "2012-10-17",
        Statement: [
          {
            Effect: "Allow",
            Principal: {
              Service: [
                  "lambda.amazonaws.com"
              ]
            },
            Action: [
                "sts:AssumeRole"
            ]
          }
        ]
      }
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Path: /

  # Api Gateway
  ApiGateway:
    Type: AWS::Serverless::Api
    Properties:
      StageName: !Ref Stage

  # Base path mapping
  ApiGatewayBasePathMapping:
    Type: AWS::ApiGateway::BasePathMapping
    Properties:
      DomainName: !Ref ApiDomainName
      RestApiId: !Ref ApiGateway
      BasePath: app1
      Stage: !Ref ApiGateway.Stage

  # Lambda Function
  Test1Function:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: ../../test1/
      Handler: test1
      Runtime: go1.x
      Tracing: Active # https://docs.aws.amazon.com/lambda/latest/dg/lambda-x-ray.html
      Role: !GetAtt App1Role.Arn
      Events:
        GET:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            RestApiId: !Ref ApiGateway
            Path: /test1
            Method: GET
  Test2Function:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: ../../test2/
      Handler: test2
      Runtime: go1.x
      Tracing: Active # https://docs.aws.amazon.com/lambda/latest/dg/lambda-x-ray.html
      Role: !GetAtt App1Role.Arn
      Events:
        GET:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            RestApiId: !Ref ApiGateway
            Path: /test2
            Method: GET
  Test3Function:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: ../../test3/
      Handler: test3
      Runtime: go1.x
      Tracing: Active # https://docs.aws.amazon.com/lambda/latest/dg/lambda-x-ray.html
      Role: !GetAtt App1Role.Arn
      Events:
        GET:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            RestApiId: !Ref ApiGateway
            Path: /test3
            Method: GET

アプリケーション2

app2.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  lambda-go
  Sample SAM Template for lambda-go
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 10
    Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object
      Variables:
        REGION: !Ref Region

Parameters:
  Stage:
    Type: String
  Region:
    Type: String
  ApiDomainName:
    Type: String

Resources:
  # ロール
  App2Role:
    Type: AWS::IAM::Role
    Properties:
      RoleName: app2-role
      Policies:
        - PolicyName: app2-policy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - ec2:CreateNetworkInterface
                  - ec2:DescribeNetworkInterfaces
                  - ec2:DetachNetworkInterface
                  - ec2:DeleteNetworkInterface
                Resource: '*'
      AssumeRolePolicyDocument: {
        Version: "2012-10-17",
        Statement: [
          {
            Effect: "Allow",
            Principal: {
              Service: [
                  "lambda.amazonaws.com"
              ]
            },
            Action: [
                "sts:AssumeRole"
            ]
          }
        ]
      }
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Path: /

  # Api Gateway
  ApiGateway:
    Type: AWS::Serverless::Api
    Properties:
      StageName: !Ref Stage

  # Base path mapping
  ApiGatewayBasePathMapping:
    Type: AWS::ApiGateway::BasePathMapping
    Properties:
      DomainName: !Ref ApiDomainName
      RestApiId: !Ref ApiGateway
      BasePath: app2
      Stage: !Ref ApiGateway.Stage

  # Lambda Function
  Test10Function:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: ../../test10/
      Handler: test10
      Runtime: go1.x
      Tracing: Active # https://docs.aws.amazon.com/lambda/latest/dg/lambda-x-ray.html
      Role: !GetAtt App2Role.Arn
      Events:
        GET:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            RestApiId: !Ref ApiGateway
            Path: /test10
            Method: GET
  Test11Function:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: ../../test11/
      Handler: test11
      Runtime: go1.x
      Tracing: Active # https://docs.aws.amazon.com/lambda/latest/dg/lambda-x-ray.html
      Role: !GetAtt App2Role.Arn
      Events:
        GET:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            RestApiId: !Ref ApiGateway
            Path: /test11
            Method: GET
  Test12Function:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: ../../test12/
      Handler: test12
      Runtime: go1.x
      Tracing: Active # https://docs.aws.amazon.com/lambda/latest/dg/lambda-x-ray.html
      Role: !GetAtt App2Role.Arn
      Events:
        GET:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            RestApiId: !Ref ApiGateway
            Path: /test12
            Method: GET

ネスト以外のポイントとして、API GatewayのAPI マッピングを使ってネストされたアプリケーションをマッピングしています。

  ApiGatewayBasePathMapping:
    Type: AWS::ApiGateway::BasePathMapping
    Properties:
      DomainName: !Ref ApiDomainName
      RestApiId: !Ref ApiGateway
      BasePath: app2
      Stage: !Ref ApiGateway.Stage

これをすると、template.yamlで指定している、ApiDomainName(api.example.com)で各ネストされたアプリケーションにアクセスできるようになる 例えば、https://api.example.com/app1/test1 とか https://api.example.com/app2/test10 など *実際に試して頂く場合はApiDomainNameを変更して頂く必要があります

デプロイ

デプロイ時にはcapabilitiesCAPABILITY_AUTO_EXPANDを指定しデプロイをおこないます、成功するとCloudFormationでネストされたアプリケーションが表示されます

f:id:moriwaki111:20210323210818j:plain

ソース

参考までにソースを公開します

https://github.com/akihiro-moriwaki/lambda-nest-go