AWS SAM + CodeBuild + CodePipeline + CodeCommitの環境構築

# 0. はじめに

まとまった余暇ができたけど、技術的な世界からあまりにも離れすぎると知識や勘が衰えそうなので、簡単なWebアプリを構築してみることにした。

見てくれた人のTipsになったら嬉しいですが、執筆目的が自分の学習記録なので、手順の合間に試行錯誤の履歴が織り込まれています。
ご了承ください。
特別難しいこともやっていないので、何かしらの解決策を提示するというレベルの内容でもありませんので、あまり期待しないでください。

# 1. 開発/デプロイ方式
* git慣れしていないので、ソースコードはCodeCommitを使ってみる
* サーバレスかつIaCを活用したいので、AWS SAM or CloudFormationを使ってデプロイする
* 継続的なデプロイを簡易化するため、CodePipelineを使って自動化する

# 2. 開発予定のWebアプリのアウトライン
在宅期間中にビールを飲む量が増えたので、実際に飲んだアルコール量を簡単にレコーディングできるWebアプリを作成する。

### 2.1. 利用シナリオ
1. ビール（やアルコール類）の一覧が表示される
2. 「飲んだ」ボタンを押すと、それが記録される

そもそもログインやよく飲むビールのお気に入り登録、さらに自宅の在庫管理まで出来たら…と思うけど、まずはこのシンプルな機能で実装を目指す。

### 2.2. 利用するAWSサービス

今のところ、利用する予定のAWSサービスは以下の通り。

|サービス|用途|
|:--|:--|
|CloudFront|画像などの静的コンテンツを利用予定なので、CDNを挟む。|
|S3|ビールの画像やWebページなどの静的コンテンツを配置する。|
|API Gateway|こっちもCloudFrontのオリジン。LambdaをInvokeする。|
|Lambda|機能ごとにFunctionを追加していくイメージ。ちなみにPythonでやってみます。|
|Aurora Serverless|永続化ストアとして利用する。追々集計や検索も必要になるので、KVSではなくRDBMSでいく。|
|RDS Proxy|LambdaからRDSを利用する際のベストプラクティスの一つなので、使ってみる。（もしくは、DataAPI）|

### 2.3. アーキテクチャ図

以下のライブラリを使ってアーキ図をざっくり書いてみた。

Diagrams - Diagram as Code
https://diagrams.mingrammer.com/

![architecture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179633/cafb3645-23f5-23d1-d26c-4e96234c19f1.png)

# 3. CodeCommitの利用
Management Consoleと一般的なチュートリアルで事足りたので、詳細は割愛。

# 4. AWS SAMのセットアップ

## 4.1. ローカル環境で動作させてみる
AWSのドキュメントに従う。
https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-sam-cli-install-linux.html
が、WSL環境ではsystemctlコマンドが利用できないことが分かった…。

解決策を示すサイトは複数あったが、なかなかに厄介そう。

```
[root@DESKTOP-OVB73GR ~]# systemctl start docker.service
Failed to get D-Bus connection: Operation not permitted
```

本来はSAMで実行される内容を事前にローカル環境で試験したほうがいいと思うけど、無しにします。
ちなみにCloudShellにはデフォルトでSAMがインストールされていました。

## 4.2. CodePipelineでAWS SAMを実行する

### 4.2.1. 事前調査
着手前の想定では、おおまかに下図のようなフローで自動化できるはず。

![codepipeline.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179633/72348a25-abbf-2b65-3163-77836db08e11.png)

Codepipelineのドキュメントを見ると、この図が分かりやすかった。
https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/concepts-devops-example.html
stageの概念がまだよくわかってないけど…。

### 4.2.2. CodePipelineのプロジェクト作成
Management ConsoleでCodePipelineのプロジェクトを作成する。（そういえばCloudFormation使うって言っていたような…。）
作成ウィザードの中でCodeBuildのプロジェクトも作れた。
AWS SAMを入れたカスタムイメージを用意する必要があるだろう、と思っていたが、AWSが提供するイメージにSAMがインストール済みのものがあったので、それを指定。
https://github.com/aws/aws-codebuild-docker-images/tree/master/al2/x86_64/standard/3.0
プロジェクト作成が完了すると自動的にプロジェクトが実行されるが、エラー終了。
とはいえ、作成は完了。

### 4.2.3. CodeBuildの作成
まずはエラーの原因を確認する。

```
CLIENT_ERROR: RequestError: send request failed caused by: Get "https://codepipeline-ap-northeast-1-000000000000.s3.ap-northeast-1.amazonaws.com/stillfirstcan/SourceArti/qPBiODO": dial tcp 52.219.1.131:443: i/o timeout for primary source and source version arn:aws:s3:::codepipeline-ap-northeast-1-000000000000/stillfirstcan/SourceArti/qPBiODO
```

VPC内で起動したCodeBuildからCodePipeline?S3?のアクセスでエラーになっていたのは、単にCodeBuildがS3にアクセスできるネットワーク環境じゃなかっただけだった。
その後も細かなエラーをつぶして、とりあえずBuildが正常終了するだけのbuildspec.ymlを用意した。動くだけでechoしかしない。

```YML:buildspec.yml
version: 0.2

env:
  variables:
    JAVA_HOME: "/usr/lib/jvm/java-8-openjdk-amd64"

phases:
  install:
    commands:
      - echo Entered the install phase...
    finally:
      - echo This always runs even if the update or install command fails
  pre_build:
    commands:
      - echo Entered the pre_build phase...
    finally:
      - echo This always runs even if the login command fails
  build:
    commands:
      - echo Entered the build phase...
      - echo Build started on `date`
    finally:
      - echo This always runs even if the install command fails
  post_build:
    commands:
      - echo Entered the post_build phase...
      - echo Build completed on `date`
artifacts:
  files:
    - '**/*'
  name: $AWS_REGION-$(date +%Y-%m-%d)
  discard-paths: yes
cache:
  paths:
    - '/root/.m2/**/*'
```

このあたりから頻繁にpushするようになったが、都度ユーザ・パスワードを入力するのが億劫になったので、以下のサイトを参考に認証情報をstoreにした。
https://qiita.com/Kamo123/items/c92b03278b6302c641e3

### 4.2.4. デプロイに必要なymlの作成
疎通の最後に、sam deployまで実行可能なbuildspec.ymlとtemplate.ymlを作成する。

#### template.yml
template.ymlはCloudFormationが動きさえすればいいので、以下のようなDynamoDBテーブルを作るだけにする。

```YML:template.yml
AWSTemplateFormatVersion : '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: A testing SAM template just create DynamoDB Table

Resources:
  DynamoDBTable:
    Type: AWS::DynamoDB::Table
    Properties:
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5
```

#### buildspec.yml

以下の点の設定変更をした。

* telemetryの無効化

初回実行時にtelemetryが云々というメッセージが出ていたので、無効化にする。
https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-sam-telemetry.html
CodeBuildで起動するコンテナは1build 1コンテナなので、pre_buildでexportしておくだけでOK。

* sam deployコマンドのオプション指定(1)

`sam deploy`だけだとdeployに必要なパラメータが足りないため、エラーになる。
`--guided`オプションを指定することで対話形式で必要なパラメータを定義することができるが、そういうわけにもいかないので、samconfig.tomlというファイルを用意する。
https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html

* sam deployコマンドのオプション指定(2)

changesetの適用要否のプロンプトをスキップするため、実行コマンドのオプションとして`--no-confirm-changeset`を有効化する。
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-deploy.html

コメントは残っているが、できたbuildspec.ymlは以下の通り。

```YML:buildspec.yml
version: 0.2

env:
  variables:
    JAVA_HOME: "/usr/lib/jvm/java-8-openjdk-amd64"

phases:
  install:
    commands:
      - echo Entered the install phase...
    finally:
      - echo This always runs even if the update or install command fails
  pre_build:
    commands:
      - echo Entered the pre_build phase...
      - echo Disabling aws sam cli telemetry.
      - export SAM_CLI_TELEMETRY=0
    finally:
      - echo This always runs even if the login command fails
  build:
    commands:
      - echo Entered the build phase...
      - echo Build started on `date`
      - sam build
      - sam deploy --stack-name stillfirstcan --no-confirm-changeset
    finally:
      - echo This always runs even if the install command fails
  post_build:
    commands:
      - echo Entered the post_build phase...
      - echo Build completed on `date`
artifacts:
  files:
    - '**/*'
  name: $AWS_REGION-$(date +%Y-%m-%d)
  discard-paths: yes
cache:
  paths:
    - '/root/.m2/**/*'
```

これで、やっとCodePipelineを使ったデプロイの流れが準備できた。

# 4.3. AWS SAM非対応のリソースを作成する

Aurora ServerlessもSAMでいけるのかな、と思っていたけどDynamoDBしか対応していなかった。
他にも諸々対応していないので、CloudFormationも使う。

Data APIを実行する際に必要なsecret-arnはCloudFormationだと作れないようなので、Management Consoleで作成。
作成されたキーを見ると、ホスト名など必要な属性さえ指定すれば、自分でも作れるのかな…？

試しに、ビルド時のコンテナからData APIに疎通をしてみたところ、以下のエラーが発生。

```
[Container] 2021/01/22 07:09:33 Running command aws rds-data execute-statement --resource-arn "arn:aws:rds:ap-northeast-1:000000000000:cluster:my-serverless-cluster" --secret-arn "arn:aws:secretsmanager:ap-northeast-1:000000000000:secret:/prod/db_secret-tUMMJ5" --sql "show databases"

An error occurred (BadRequestException) when calling the ExecuteStatement operation: Failed to fetch secret arn:aws:secretsmanager:ap-northeast-1:000000000000:secret:/prod/db_secret-tUMMJ5
```

AWS Managed Policyである`arn:aws:iam::aws:policy/AmazonRDSDataFullAccess`には、対象リソースの制限が含まれていることが原因と推測。

- 該当箇所の抜粋：`"Resource": "arn:aws:secretsmanager:*:*:secret:rds-db-credentials/*"`

上記ポリシーのアタッチはAWSのドキュメント上も利用を推奨しているが、ちょっとした落とし穴じゃないかな？と思いつつ、SecretsのARNを直して再作成。
しかし、エラーは変わらず。
ただし、AdministratorAccessポリシーをアタッチすると接続に成功すること、またコンテナが外部接続できないと（NAT Gatewayをつけておかないとタイムアウト）エラーになるから、権限周りの設定が原因で、何かしらのAWSサービスアクセスでコケていることは間違いなさそう。
そこまでしか分からず、結局どの権限が引っかかっているのかを判別できなかったので、渋々AdministratorAccessをつけることで解決（したことにする）。

ちなみに、一度AdministratorAccessポリシーをアタッチして接続に成功した後、ポリシーをデタッチしても接続性が失われなかった。
権限情報がキャッシュされているような印象だが…そうなの？
それともそのコンテナあたりで一回だけでも実行されれば済むようなAPIを発行しているということだろうか。
うーん。ちゃんと調べなくては。

# 4.4. template.ymlを製造する

初期に実装する機能の分については作成済み。とはいえ、ビルドに成功したというだけでやりたいことができるようになっているかは不明。

```YML:template.yml
AWSTemplateFormatVersion : '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: StillFirstCan SAM template

Globals:
  Function:
    Runtime: python3.8
    Timeout: 180
    Handler: index.handler
    MemorySize: 128
    VpcConfig:
      SecurityGroupIds:
        - sg-e8bd8f8f
      SubnetIds:
        - subnet-01cad7946f16abaf7
    Environment:
      Variables:
        STAGE: production

Resources:
  getListFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: stillfirstcan/getList
      Policies:
        - AmazonRDSDataFullAccess
      Handler: app.lambda_handler
      Runtime: python3.8
      Events:
        getList:
          Type: Api
          Properties:
            Path: /list
            Method: get
  registRecordFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: stillfirstcan/registRecord
      Policies:
        - AmazonRDSDataFullAccess
      Handler: app.lambda_handler
      Runtime: python3.8
      Events:
        registRecord:
          Type: Api
          Properties:
            Path: /record
            Method: put
```

# 5. Webアプリを開発する

## 5.1. テーブル設計

テーブル設計とSQL（DDL）が必要だが、それを手作業で用意するのは手間。というかSQLに明るくない…。
こういう時に使うツールとして「A5:SQL Mk-2」は知っているけど、最近のトレンドはどうなのかな、と思って調べてみた。（そうしてまた横道にそれるのであった。）
今回は良さそうなツールを2つ見てみた。

#### dbdiagram.io
https://dbdiagram.io/home
テーブル定義をコード管理できることが特徴。
ただ、文法覚えないといけないし、そもそもDB素人が書くには難しかった…。
でもリポジトリと連携してSQL出力までパイプラインに組み込めたら美しいだろうなぁ。

#### SqlDBM
https://app.sqldbm.com
こっちを選びました。Web GUIで直感的にテーブル定義を作成できる。
プルダウンやチェックボックスでオプション設定も便利！素人向け。
無料版だとSQL生成が一度につき1テーブルのみという制約があったけど、テーブル数は少ないので気にしない。

## 5.2. DBリリース方式
フルサーバレスで設計しているため、ちょっとSQL流そうと思っても流せないことが発覚。（当然だった。）
早くCloudShellにVPC対応してほしい…。
と思ったら、Aurora Serverlessについてはクエリエディタ機能がすでに存在していた、さすが。
https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/query-editor.html

ただし、S3やCodeCommitからSQLをロードするとかまでできるわけではないので、構成管理的にはあまり積極的に使えない。
ので、とりあえず新たにbastionを構築して、事前に用意したDDLファイルを管理しながらSQLを実行する。
flywayを使いたかったが、そっちのビルド環境を作るとさらに時間がかかるので、いったん直接流す方法で妥協。

## 5.3. Pythonのコーディング
APIと言っても、SQL投げて返すだけなので、超簡単だった。（変数ベタ書きとかはあるけど。）

## 5.4. 画面
ここで詰んだ…画面作ったことなかった…。
ので、とりあえずURLを叩くだけのゴミみたいなindex.htmlを作って満足することにした。
もしかしてこういう時にAWS Amplifyを使うのか？と思って調べてみようと思う、追々…。

## 4.4.4. おまけ：API Gatewayの設定

マネコンでAPI Gatewayの設定を見ていたら、いくつか気になるところがあったので、チェックボックスを有効化。

- CloudWatch ログを有効化（ログレベル：INFO）
- リクエスト/レスポンスをすべてログ
- X-Ray トレースの有効化

# 6. 動かす

ついに全ての努力が結実し、APIが動いた！！

![スクリーンショット 2021-02-06 11.05.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179633/3a6fa682-1488-155a-e39d-bf7be024e6d3.png)

これはマスタテーブルにある登録可能なアルコール一覧を取得した結果。
レコーディングボタンは、「ボタン…？」となったけど、まぁSQL投げられさえすればいいから、どうにかなりそうだ。

ちなみにCloudFrontも通していなければhtmlをS3に配置したわけでもないです。
せめてS3まではパイプライン化しておきたい…。

# 7. 終わりに

どうにか動くようにはなったけど、まだまだやりたいことがいっぱい。

- 最低限追加したい
    - ちゃんとしたWeb UIの作成
    - 関数の追加（レコード登録がまだ…。）
    - 画面資材のS3への自動デプロイ（web hostingにしちゃおう）
- 追加機能として実装したい
    - ログイン
    - 記録したレコードの可視化

余暇も終わりそうだけど、今後もコツコツ作りたいと思います。

最後まで見ていただいてありがとうございました。