AWS LambdaのCI環境について ~表参道.rb #24~

## 自己紹介
@ebihara99999 

- [LICTOOR株式会社](http://lictoor.com/)所属
- Rails中級者向け勉強会[Step-to-Rails-Expert.rb](https://step-to-rails-expert-rb.connpass.com/)主催
- 普段はインフラ（Iaas/Paas）からフロントエンドまで浅く広く触っています

---
## ところで

私事ですが明日結婚します。
めでたいLTです
<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">明後日結婚するので明日のLTは独身最後のLTになるのか。なんかめでたいLTだ笑</p>&mdash; ebi (@ebihara99999) <a href="https://twitter.com/ebihara99999/status/882584497094483968">July 5, 2017</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>


---
## 発表すること
- AWS Summit 2017でのt_wadaさんの講演がすごく参考になったので、AWS Lambdaで自動テストを行う仕組みを導入した
- その際の知見やハマりどころを発表する

---
## t_wadaさんの講演？

### Testable Lambda: Working Effectively with Legacy Lambda
公式Lambdaチュートリアルを例として扱い、Lambda開発で自動テストを導入していくお話

スライド: 
https://speakerdeck.com/twada/testable-lambda-working-effectively-with-legacy-lambda

動画: https://www.youtube.com/watch?v=C0zNc4bdWhY

---
## t_wadaさんの講演？

拙記事もご覧ください（ご本人にはてぶして頂いて嬉しい）。

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">&quot;t_wadaさんの講演を聞いたのは初めてでしたが、その分かりやすさにとても驚きました。概念の説明も実例の説明もすごく分かりやすく、一度聞いただけなのにすんなりと頭に入ってきました&quot; ありがとうございます！ / “AWS Sum…” <a href="https://t.co/mMLSWYoPwF">https://t.co/mMLSWYoPwF</a></p>&mdash; Takuto Wada (@t_wada) <a href="https://twitter.com/t_wada/status/877756625330683904">June 22, 2017</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">&quot;TLにオーラでテストを書かせてこそ本物と流れていましたが、たしかにt_wadaさん見てオーラを近くで感じるだけでテスト書けそうな気がしてきました&quot; オーラ…… <a href="https://t.co/mUmzeZUETN">https://t.co/mUmzeZUETN</a></p>&mdash; Takuto Wada (@t_wada) <a href="https://twitter.com/t_wada/status/877757146061877248">June 22, 2017</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

http://biibiebisuke.hatenablog.com/entry/2017/06/03/132538

---
## 講演内容

AWS公式のLambdaチュートリアルのコードをリファクタリングしながらテストを書いていく

- ローカル環境でのテスト  
localstack超便利
- 本番環境相当のテスト
AWS SAMを利用したCI環境の構築 ← 今日はこれについて
- production環境でのサービス間のテスト

---
## 題材
構成としては、本家のLambdaチュートリアルと同様。

srcBucketにファイルが格納されたら何らかの処理を行い、結果をdestBucketに吐き出す。

<img width="558" alt="lambda-flow.png" src="https://qiita-image-store.s3.amazonaws.com/0/95156/e1fd8328-6e69-0d94-fa9d-f7681ba08ce4.png">

引用: http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/with-s3-example.html

---
## SAM（Serverless Application Model）とは

- サーバレスアーキテクチャ構築フレームワーク
- CloudFormationの拡張
- サーバーレスアプリケーションのリソースに最適化された専用のリソースタイプを定義できる
- [本家にサンプル色々ある](https://github.com/awslabs/serverless-application-model/tree/master/examples/2016-10-31)

AWS Summit 2017での吉田さんの講演がとても参考になります
動画: https://www.youtube.com/watch?v=ZDvcCY8QRWA

参考: 
https://aws.amazon.com/jp/about-aws/whats-new/2016/11/introducing-the-aws-serverless-application-model/
http://dev.classmethod.jp/cloud/aws/aws-serverless-application-model/

---
## 実現したいCI環境
push後ブランチごとに実際のAWSサービスを利用した環境を立ち上げて、テストが終わったら使用した環境を削除したい（CircleCI使用）。

- スタック名をブランチを含む形にしたい
- テストで使用するリソース（今回はS3のバケット）も、ブランチ名を含む命名規則とし、想定可能な名称にしたい  

リソースの名称を指定しないと下記のようにユニークな値を振ってくれるが、それではテストで利用できない（「hogeバケットにファイルが存在すること」など）。

```
例
arn:aws:iam:*******:role/lambda-tutorial-ProcessorFunctionRole-1GFMCGG1ORHNM
```

---
## 実現したいCI環境

例として、

- CloudFormationスタック名   
``` 
${プロジェクト名}-${ブランチ}    
```

```
例: lambda-tutorial-develop
```
- Sourceバケット名    
``` 
${プロジェクト名}-\${ブランチ}-source   
```

```
例: lambda-tutorial-develop-source
```

などなど

---
## 実現したいCI環境
後掃除もしっかり

- S3のバケットはオブジェクト（ファイル）が存在している場合は消せないので、delete-stackを実行する前にs3バケット内のオブジェクトを削除しておく必要がある

---
## 具体的な設定（CircleCI編）
CircleCIの環境変数

- PROJECT_NAME: lambda-tutorial
- REGION
- S3_BUCKET: CloudFormationのソース置き場
- CIRCLE_BRANCH: CircleCIで保持している環境変数。ブランチ名を格納している


---
## 具体的な設定（CircleCI編）

circle.yml

```yaml
machine:
  timezone: Asia/Tokyo
  node:
    version: 6.1.0
dependencies:
  override:
    - sudo pip install awscli
    - npm install
  post:
    - aws configure set region $REGION
test:
  pre:
    - aws cloudformation package --template-file app-spec.yml --output-template-file app-spec.deploy --s3-bucket $S3_BUCKET_NAME
    - aws cloudformation deploy --template-file app-spec.deploy --stack-name "${PROJECT_NAME}-${CIRCLE_BRANCH}" --capabilities CAPABILITY_NAMED_IAM 
  override: 
    - mocha test/large/index-test.js --timeout 10000
  post:
    - aws s3 rm --recursive s3://"${PROJECT_NAME}-${CIRCLE_BRANCH}"-source
    - aws s3 rm --recursive s3://"${PROJECT_NAME}-${CIRCLE_BRANCH}"-dest
    - aws cloudformation delete-stack --stack-name "${PROJECT_NAME}-${CIRCLE_BRANCH}"
deployment:
  production:
    branch: master
    commands:
      - aws cloudformation package --template-file app-spec.yml --output-template-file app-spec.deploy --s3-bucket $S3_BUCKET_NAME
      - aws cloudformation deploy --template-file app-spec.deploy --stack-name "${PROJECT_NAME}" --capabilities CAPABILITY_NAMED_IAM
```

---
## ポイント（CircleCI編）
- `--capabilities CAPABILITY_NAMED_IAM`つけないとダメ
- CloudFormationへデプロイ時に`--stack-name "${PROJECT_NAME}-${CIRCLE_BRANCH}"`のように、stack名にブランチ名を含めてしまうこと

SAM（CloudFormation）のテンプレートでブランチ名を取得するよりCircleCIで取得する方がはるかに楽。

```yaml
test:
  pre:
    - aws cloudformation package --template-file app-spec.yml --output-template-file app-spec.deploy --s3-bucket $S3_BUCKET_NAME
    - aws cloudformation deploy --template-file app-spec.deploy --stack-name "${PROJECT_NAME}-${CIRCLE_BRANCH}" --capabilities CAPABILITY_NAMED_IAM
```

---
## ポイント（CircleCI編）

- S3バケット内にオブジェクトが残っているとdelete-stackできないので、先にオブジェクトを削除しておく

```yaml
post:
    - aws s3 rm --recursive s3://"${PROJECT_NAME}-${CIRCLE_BRANCH}"-source
    - aws s3 rm --recursive s3://"${PROJECT_NAME}-${CIRCLE_BRANCH}"-dest
    - aws cloudformation delete-stack --stack-name "${PROJECT_NAME}-${CIRCLE_BRANCH}"
```

---
## 設定（SAM編）
app-spec.yml

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: A function that is triggered off an upload to a bucket. It does something to the uploaded file and put the proccessed file to another bucket. 
Resources:
  ProcessorFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      Runtime: nodejs6.10
      CodeUri: ./
      Timeout: 40
      Policies: AmazonS3FullAccess
      Environment:
        Variables:
          STACK_NAME: !Sub "${AWS::StackName}"
      Events:
        ImgUpload:
          Type: S3
          Properties:
            Bucket: !Ref SourceBucket
            Events: s3:ObjectCreated:*
  SourceBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub "${AWS::StackName}-source"
  DestinationBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub "${AWS::StackName}-dest"
```

---
## ポイント（SAM編）
- スタック名の取得
CloudFormationの擬似パラメータで、`!Sub "${AWS::StackName}" `でスタック名を取得できるので、こちらを利用する。

デプロイ時にスタック名にブランチ名も含めて渡しているのでapp-spec.ymlではブランチ名について考慮する必要はない。

---
## その他はまりどころ

- ブランチ名はハイフンを使うこと    

CloudFormationのスタック名はアンダーバーを使えない。
スタック名: `lambda-tutorial-update_sam_configuration`はinvalidになってしまう

---
## 触れられなかったお役立ち資料

- [Developers.IO 2017セッション「AWS Serverless Application Modelのデプロイ戦略」で話しました](http://dev.classmethod.jp/event/devio2017-how-to-deploy-with-aws-serverless-application-model/
)

SAMの概要から好ましいディレクトリ構成、CIパイプラインの構築まで幅広く非常に詳しく説明されています。　

私の設定ではディレクトリ構成やLambdaパッケージに含むファイルの選別が雑なところがあるので、こちらを参考にして直していきたいと思います。

---
## 最後に
ご清聴ありがとうございました