CodeBuild+CodeDeployでNodeアプリをデプロイする

AWSのCodeBuild+CodeDeployでビルド＋デプロイ環境を作る機会があったので、自分用のメモをかねて書きます。

# やること
よくある
1.Githubでソースを管理
2.CodeBuildでビルド
3.CodeDeployでデプロイ
といった構成のリリース環境を用意します。

上に書いたようにAWSのCodeBuild、CodeDeploy、CodePipelineを使います。これらを軽く説明します。

### CodeBuild
ビルドやテストを実行するビルドサービスです。s3（Github）からソースを受け取り、ビルドをし、s3にアップロードします。

### CodeDeploy
デプロイを自動で実行するデプロイサービスです。s3からソースやビルド済みのアーティファクトを受け取り、サーバにデプロイします。

### CodePipeline
Github、CodeBuild、CodeDeployの連携をするリリース自動化サービスです。実は自動リリースでなければ、CodePipelineを使わなくても連携は可能のようです。ただ、当たり前ですが使った方が楽です。

# 1.アプリケーションの準備
今回はビルド＋デプロイ環境を作るだけなので、Node.jsの[超簡単なアプリ](https://github.com/KouheiHara/NodeApp)を用意しました。

アプリは下記で起動できます。

```shell
# npmパッケージインストール
$ npm install
# ビルド
$ npm run build
# 起動
$ npm start
```

# 2.CodeBuildの設定
今回、CodeBuildの設定で書くことは下記です。
・ロールの設定
・ビルド環境の用意
・ビルドの設定ファイル用意

## ロールの設定
CodeBuildを実行するために、IAMロールと呼ばれる権限の設定が必要です。
結論から言いますと、必要なロールは下記です。

* CodeBuildの実行権限（AWSCodeBuildDeveloperAccessなど）
* s3にパッケージをアップロードする権限（AmazonS3FullAccessなど）
* ECRからImageをpullする権限（AmazonEC2ContainerRegistryReadOnlyなど、Dockerイメージを使う場合）
* CloudWatchへのアクセス(CloudWatchLogsFullAccessなど、ログを残す場合)

上で挙げたポリシーは不要な権限も持っているため、自分用にポリシーをカスタムすることをおすすめします。

また、上で挙げた設定をしなくても、CodeBuildの設定時に
「AWS CodeBuild にこのサービスロールの編集を許可し、このビルドプロジェクトでの使用を可能にする」
にチェックを入れて設定することで、CodeBuildに必要最低限のロールを設定してくれます。

### ※はまったこと１
CodeBuild側で設定してくれたロールを変えた際に、s3へのファイルアップロード部分やECRからDockerイメージをpullする部分で失敗しました。ロールの設定を変えてエラーが出た場合はロールの設定を見直してみてください。

## ビルド環境の用意
ビルド環境は、AWS側で用意されている環境か、自前で用意した環境を使うことができます。
AWS側で用意されている環境は、言語のバージョンの指定が厳密にはできなかったり、必要なパッケージが入っていなかったりするので、基本自前で用意した環境を使う方がいいです。

参考：[AWS側で用意されている環境](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-env-ref-available.html)

↑の参考のページで、2021/3/13現在、例えばnodejsの14系のバージョンをAWSの環境で使う場合は、「Ubuntu standard:5.0」を使う必要があります。

自前の環境は、ECRにDockerイメージをpushすることで用意できます。

### ECRにDockerイメージをpushする
軽く、ECR側の準備にも触れます。
やることとしては、

1.IAMユーザを作り、アクセスキーとシークレットキーを取得する
2.AWS CLIをインストールし、credentialファイルの設定をする
3.Dockerイメージにタグをつける
4.Dockerイメージをpushする


です。詳細は[他者様の記事](https://qiita.com/3utama/items/b19e2239edb6996a735f)を参照ください。

ECRにDockerイメージをpushできましたら、CodeBuildで使う設定にします。

### ※はまったこと2
CodeBuildで、VPC内のリソースと連携させたい場合、VPCを指定することで連携することができます。
VPC内のリソースとの連携が不要の場合はVPCの入力欄は空で問題ありません。

VPC内のリソースへのアクセスが必要な方はこちらの[他者様の記事](https://qiita.com/sasasin/items/6e5f7001cba350851d49#vpc%E3%82%92%E6%BA%96%E5%82%99%E3%81%99%E3%82%8B)をご参照ください。

## ビルドの設定ファイル用意
buildspec.ymlというファイルを用意し、そこに実行する処理を書きます。

```buildspec.yml
version: 0.2

phases:
  install:
    commands:
      - npm install
  build:
    commands:
      - npm run build
artifacts:
  files:
    - '**/*'
```

上のような感じで書きます。Dockerイメージ自体をビルドする場合は、

1.pre_buildでECRから対象のイメージを取ってきて
2.buildをして
3.post_buildでECRにpushする

ような感じの実行になります。

ちなみに、CodePipelineを使わず、CodeBuildとCodeDeployを自動で連携したい場合は、phases内のpost_buildで、①s3にビルドしたパッケージをアップロードし、②CodeDeployでデプロイを実行すれば、CodeBuildを実行するだけでCodeDeployも実行してくれるようにできるようです。

# 3.CodeDeployの設定
今回、CodeDeployの設定で書くことは下記です。
・ロールの設定
・code-deployエージェントのインストール
・デプロイ先で実行するファイルの用意

## ロールの設定
ロールは、CodeDeploy側とデプロイ先のEC2側で設定が必要です。

* CodeDeploy側  
・CodeDeployの実行権限（AWSCodeDeployRoleなど）
* EC2側  
・CodeDeployの実行権限（AWSCodeDeployDeployerAccessなど）  
・s3の読み取り権限（AmazonS3ReadOnlyAccessなど）  
・CloudWatchのアクセス権限（CloudWatchLogsFullAccessなど、ログを残す場合）

といった感じになります。こちらも自分用に必要な権限だけ設定するのがおすすめです。

## code-deployエージェントのインストール
code-deployエージェントが入っていないサーバにデプロイする場合、インストールが必要になります。
CodeDeployの設定で、code-deployエージェントのインストールを指定することができます。

## デプロイ先で実行するファイルの用意
appspec.ymlというファイルでデプロイの設定ができます。

```appspec.yml

version: 0.0
os: linux
files:
  - source: /
    destination: /var/www/app
    overwrite: true
permissions:
  - object: /
    owner: user1
    mode: 744
hooks:
  AfterInstall:
    - location: AfterInstall.sh
      timeout: 60
      runas: user1
  ApplicationStart:
    - location: ApplicationStart.sh
      timeout: 30
      runas: user1
```

上の様な感じで書きます。上の例では、デプロイ時にAfterInstall.sh、ApplicationStart.shという実行ファイルでインストールやアプリケーションの起動などを行っています。

以下、デプロイ時にはまったことです。

### ※はまったこと3
デプロイ時に下記の3つでエラーがおきました。
A.リビジョンが残っている影響でディスク容量が足りない＆リビジョン削除でデプロイ履歴がない
B,デプロイしようとしたら、ファイルがすでにあった

#### A.リビジョンが残っている影響でディスク容量が足りない＆リビジョン削除でデプロイ履歴がない
デプロイを何回かしていたところ、
「No space left on device - /opt/codedeploy-agent/deployment-root/***/***/deployment-archive/***」
こんな感じのエラーが出ていました。原因は、
`/opt/codedeploy-agent/deployment-root/[デプロイグループID]/`
に過去のデプロイのリビジョンファイルがあり、ディスクを食っていたというものでした。

ちなみにcode-deployでは、リビジョン数の設定ができ、
`/etc/codedeploy-agent/conf/codedeployagent.yml`
の
`max_revisions`という変数
で設定できます。
参考：https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/reference-agent-configuration.html

過去のリビジョンを削除して再デプロイを実施したところ、次のように
「The CodeDeploy agent did not find an AppSpec file within the unpacked revision directory at revision-relative path "appspec.yml"...」
appspec.ymlファイルが見つからないというエラーが出ました。
これは過去のリビジョンを参照しようとして、出ているエラーなので、
`/opt/codedeploy-agent/deployment-root/deployment-instructions/`
こちらを削除すると過去のリビジョン情報を使わないでデプロイができます。
参考：[他者様の記事](https://qiita.com/nysalor/items/bfdc764ab65361f7bdc5)

#### B.デプロイしようとしたら、ファイルがすでにあった
リビジョン情報リセットして、デプロイしようとしたところ、
「The deployment failed because a specified file already exists at this location: ...」
というような、デプロイ先にファイルがすでにあるよ、というエラーが出ました。

これはCodeDeployの仕様で、過去のリビジョンを元に既存のファイルを削除し、新しいリビジョンを用意するというものがあるようなのですが、
リビジョンを削除していたりすると、code-depolyエージェントから見て、デプロイしていないファイルが存在することになり、エラーが起きるというものになります。（※appspec.ymlのoverrideがtrueでもおこります。）
CodeDeployデプロイメントグループが再作成されたり、デプロイメントグループIDが一致しない場合でも同様の事象が起こります。

対応方法としては、

* 既存のデプロイ先のファイルを削除する
* BeforeInstall（新しいリビジョンの準備はInstallで行われるので）内でファイルを削除する

のどちらかで行えます。

# 4.CodePipelineの設定
Github＋CodeBuild+CodeDeployを連携させます。
やることとしては

* CodeBuildはGithubのソースを、CodeDeployはCodeBuildのビルド済みのアーティファクトを受け取るようにする
* リリースのトリガーを設定する

という感じです。
CodePipelineの設定が終わりましたら、pushしてリリース完了です。

# 参考
[【AWS】git pushでEC2にも自動デプロイ(CodeDeploy / CodePipeline)](https://qiita.com/nasuB7373/items/081f5974e31419a1a844)