serverless deployしたLambdaとDynamoDBの接続で詰まったお話

# はじめに
serverless deploy後にLambdaとDynamoDBの接続で色々詰まった点をまとめてみます。ファイルに関しては数が多いため記載は一部にしています

# 前提
- Docker in Dcokerでserverless deploy
開発環境のコンテナの中でデプロイコマンド(sls deploy)を実行しています

- React+Rails+DynamoDB+APIGateway+Lambda
- フロント(React)からバックエンド(Rails)にパラメータを渡せている

# ディレクトリ構成(一部)
この記事の環境に関しては前回の続きになります

https://qiita.com/does_not_exist/items/a68570cd062f5ff991e5

```
sample-project
┣ backend
┃  ┗ config
┃      ┗ aws.yml
┣ Dockerfile
┣ serverless.yml
┣ docker-compose.yml
```

# 詰まった点
## フロントからパラメータを送ると、DynamoDB側で500エラーが発生
CloudWatchでログを確認すると以下のエラーが出ました

```
task time out after 25seconds...
```

このときバックエンド側でパラメータは受け取れていたので、
LambdaとDynamoDBが接続できているか、以下の点をserverless.ymlで確認しましたが問題は見つかりませんでした

- DynamoDBのアクセス権限をroleに設定できているか
- DynamoDBのテーブルに対して参照権限を設定できているか

エラー文で検索をかけてみると以下の記事を見つけました

https://qiita.com/yukitaka13-1110/items/9add7abe8b5e4e093016

こちらの記事を参考にし、Lambdaの実行制限時間を25秒から適当に180秒まで増やして再度動作を確認したところ、エラー文が以下に変わりました

```sample-project/serverless.yml
service: sample-project-api

provider:
  name: aws
  runtime: ruby2.7
  timeout: 180
略
```

```
Failed to open TCP connection to dynamodb-hoge-local:8000 (getaddrinfo: Name or service not known)
```

接続先が本番環境のDynamoDBではなく、開発環境のDynamoDBになっていました
開発環境でフロントからパラメータを送った際はDynamoDBにレコードが作成されているため、本番環境では認証もできていなさそうでした

そこでDynamoDBとの接続や認証まわりについて確認することにしました

## 本番環境のDynamoDBで認証ができていない
まず開発環境のコンテナ内で.envから認証情報を取得できているか確認するとnilでした
```
# 開発環境用のコンテナに入ってrails c
> Rails.env
```

これを解決するためにはAWS Parameter StoreやSecrets Managerを利用するようです
今回はAWS Parameter Storeを使いました

serverless.ymlに以下のような書き方をすると、
パラメータストアに入れた情報を取得できるようです

```sample-project/serverless.yml

  environment:
    QIITA_DYNAMODB_ACCESS_KEY_ID: ${ssm:/sample-project/AWS_ACCESS_KEY_ID}
    QIITA_DYNAMODB_SECRET_ACCESS_KEY: ${ssm:/sample-project/AWS_SECRET_ACCESS_KEY}
    RAILS_ENV: production
略
```

この時、Lambdaで予約されている環境変数名と被らないようにする必要があったので「QIITA_DYNAMODB_」を頭につけています

パラメータストアに情報を入れる際は以下のようなコマンドを実行します

https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ssm/put-parameter.html

```
# 開発環境のコンテナに入る
aws ssm put-parameter --name "/sample-project/AWS_ACCESS_KEY_ID" \
--type "Secure String" --value "hogehogehogehoge"

aws ssm put-parameter --name "/sample-project/AWS_SECRET_ACCESS_KEY" \
--type "Secure String" --value "fugafugafugafuga"
```
## 接続先が本番環境のDynamoDBではなく、開発環境のDynamoDBになっている
次に開発環境のDynamoDBに接続しようとしていた点ですが、
aws.ymlの設定に不足がありました
以下の記事を参考に、本番環境用の設定を追加しました

https://qiita.com/takuya0301/items/9a5d7a4539ad56cc5d56

```backend/config/aws.yml
default: &default
  access_key_id: <%= ENV['QIITA_DYNAMODB_ACCESS_KEY_ID'] %>
  secret_access_key: <%= ENV['QIITA_DYNAMODB_SECRET_ACCESS_KEY'] %>
  region: ap-northeast-1

development:
  <<: *default
  endpoint: http://dynamodb-hoge-local:8000

production:
  <<: *default
```

- これで開発環境のコンテナからserverless deployした際にACCESS_KEY_ID/KEYを環境変数としてrailsに渡すことができます
- developmentにのみendpointを指定しているのは、GUIとして使用しているdynamodb-adminの参照先にdynamodb-localを指定する必要があるためです

次に本番用のDockefileでenvの切り替えができるようにコマンドを追加しました

```:backend/Dockerfile
# 本番用
ARG RUBY_TAG

FROM public.ecr.aws/lambda/ruby:${RUBY_TAG} as builder

ENV LANG C.UTF-8

RUN yum update -y && yum groupinstall -y "Development Tools"

WORKDIR /var/task

# 追加分
ENV RAILS_ENV=production
ENV RACK_ENV=production

ARG BUNDLE_VER

RUN gem install bundler -v ${BUNDLE_VER}

COPY lambda.rb /var/task
COPY ./Gemfile /var/task
COPY ./Gemfile.lock /var/task
ENV GEM_HOME=/var/task
RUN bundle install

ADD . /var/task/

CMD ["lambda.App::Handler.process"]
```


これでsls removeした後に改めてsls deployを行い、フロントからパラメータを送るとDynamoDBにレコードが作成されることを確認できました！！

# おまけ
## 最終的なserverless.yml

```sample-project/serverless.yml
service: sample-project-api

provider:
  name: aws
  runtime: ruby2.7
  timeout: 180
  stage: ${opt:stage, 'dev'}
  region: ap-northeast-1
  deploymentBucket:
    name: sample-project-api-dev-slsdeploymentbucket
  ecr:
    scanOnPush: true
    images:
      lambda_docker:
        path: ./backend
        buildArgs:
          BUNDLE_VER: ${env:BUNDLE_VER, '2.3.5'}
          RUBY_TAG: ${env:RUBY_TAG, '2.7'}
  environment:
    RAILS_ENV: production
    SECRET_KEY_BASE: hogehogehogehoge
    QIITA_DYNAMODB_ACCESS_KEY_ID: ${ssm:/sample-project/AWS_ACCESS_KEY_ID}
    QIITA_DYNAMODB_SECRET_ACCESS_KEY: ${ssm:/sample-project/AWS_SECRET_ACCESS_KEY}

  iamRoleStatements:
    - Effect: 'Allow'
      Action:
        - dynamodb:*
      Resource:
        - arn:aws:dynamodb:ap-northeast-1:○○:table/*

custom:
  defaultStage: dev
  deploymentBucket:
    blockPublicAccess: true

functions:
  api:
    timeout: 25
    image:
      name: lambda_docker
    events:
      - http:
          path: /
          method: ANY
          cors: &cors
            origin: http://localhost:43000
            headers:
              - Content-Type
              - X-Amz-Date
              - Authorization
              - X-Api-Key
              - X-Amz-Security-Token
              - X-Amz-User-Agent
              - Access-Control-Allow-Headers
              - Access-Control-Allow-Origin
              - Time-Zone
              - Cache-Control
            allowCredentials: false

      - http:
          path: /{proxy+}
          method: ANY
          cors: *cors

resources:
  Resources:
    HogeTable:
      Type: AWS::DynamoDB::Table
      Properties: 
        TableName: dynamoid__${self:provider.environment.RAILS_ENV}_hoge
        BillingMode: PAY_PER_REQUEST
        AttributeDefinitions:
          - AttributeName: "id"
            AttributeType: "S"
        KeySchema:
          - AttributeName: "id"
            KeyType: "HASH"

plugins:
  - serverless-deployment-bucket
```

# おわりに
LambdaとDynamoDB間の繋ぎこみや細かい箇所でたくさん詰まりましたがなんとかできました。引き続いて理解を深めていきます。