Bitbucket, AWS CodeDeploy を連携させて Serverless Frameworkのデプロイを簡単にする

# はじめに

[Bitbucket](https://bitbucket.org/) と [AWS CodeDeploy](https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/welcome.html) を連携させて [Serverless Framework](https://serverless.com/) 製のアプリケーションデプロイを簡単にする為の手順を紹介させて頂きます。

# 対象読者と事前に必要な設定

この記事を読む為には以下の条件を満たしている必要があります。

- [Bitbucket](https://bitbucket.org/) に最低1つリポジトリを持っている事
- [AWS CodeDeploy](https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/welcome.html) を使ったデプロイフローを構築した事がある
- [Serverless Framework](https://serverless.com/) で開発した事がある

[Serverless Framework](https://serverless.com/) のバージョンは `1.26.0` （2018-02-15 時点での最新安定版） を利用しています。

# システム構成

最終的には以下のような構成を作成します。

![BitbucketToCodeDeploy.png](https://qiita-image-store.s3.amazonaws.com/0/71899/ce6aaf6f-27ea-9050-d696-c595b437d126.png)

`1. デプロイ要求を出す` でBitbucket上の任意のブランチを選択すると自動的に2〜5の手順が自動実行されます。

# 構築手順

ここからは具体的な構築手順を説明します。

## builderサーバ（EC2インスタンス）の用意

アプリケーションのbuildが出来る環境を作成します。

アプリケーションのbuild要件によって環境構築の方法は変わると思いますので詳細な手順は割愛させて頂きます。

しかし以下の2つの手順は必ず行っておく必要があります。（CodeDeployからインプレースデプロイが出来る設定）

- `AmazonS3ReadOnlyAccess` を持ったIAMをEC2インスタンスに指定しておく事
- AWS CodeDeploy エージェントがインストールされている事

以前 [AWS CodeDeploy でEC2のBlue/Greenデプロイを作成する](https://qiita.com/keita-nishimoto/items/6abe6c971e4dec3b69ef) という記事を書きました。

こちらも一部参考にして頂けるかと思います。

## CodeDeployアプリケーションの作成

CodeDeployアプリケーションを作成します。

アプリケーション名: 任意の名前
デプロイグループ名: 任意のグループ名
デプロイ対象のインスタンス: builderサーバを指定

## デプロイ用のS3バケットを作成

デプロイに使うS3バケットを作成します。

分かりやすい名前であれば何でも大丈夫です。

## Bitbucket、CodeDeploy間の連携を有効化する

自身のアカウントから 統合の検索 → AWS Code Deploy for Bitbucketを選択します。

![BitbucketCodeDeploy1.png](https://qiita-image-store.s3.amazonaws.com/0/71899/2f78fb06-6878-0a87-1485-69b663f28858.png)

下記のようにアクセスを求められるので `Grant access` を選択します。

![BitbucketCodeDeploy2.png](https://qiita-image-store.s3.amazonaws.com/0/71899/d3ceca8c-5a97-c024-e3a2-15298d595487.png)

## デプロイ対象のGitリポジトリ設定

デプロイ対象のGitリポジトリページに移動します。

設定 → CodeDeploy Settings を選択します。

赤で囲ってある部分がIAMロール、ポリシーの作成に必要になるのでメモしておきます。

![BitbucketCodeDeploy3.png](https://qiita-image-store.s3.amazonaws.com/0/71899/f5aeac39-a94c-12e0-7a8e-ee2497ca840b.png)

## IAMロールの作成を行う

AWSマネジメントコンソール → IAM → ロール からロールの作成を行います。

「信頼されたエンティティの種類を選択」で別のAWSアカウントを選択します。

![CreateRole1.png](https://qiita-image-store.s3.amazonaws.com/0/71899/0a0157ab-ce34-f4fe-2b22-a1b05a3354c6.png)

アカウントIDと外部IDに先程控えた `AWS Account ID` と `External ID` を入力します。

![CreateRole2.png](https://qiita-image-store.s3.amazonaws.com/0/71899/a3c7a8e0-9606-47d4-aeb4-252c0bd51f8f.png)

名前とロールの説明は分かりやすい内容を入れて下さい。

私の場合は名前を「BitbucketToCodeDeploy」としました。

![CreateRole3.png](https://qiita-image-store.s3.amazonaws.com/0/71899/c3ac2e8f-cf38-d817-91d4-a334103a8ce9.png)

## IAMロールのポリシーの設定を行う

以下のポリシーを設定して下さい。（この内容は先程「デプロイ対象のGitリポジトリ設定」でコピーした内容を整形した物です）

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListAllMyBuckets",
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "codedeploy:*"
            ],
            "Resource": "*"
        }
    ]
}
```

## デプロイ対象のGitリポジトリにIAMロールの設定を行う

BitbucketのGitリポジトリのページに戻り、作成したIAMロールのARNと、利用したいリージョンを選択します。

![BitbucketCodeDeploy4.png](https://qiita-image-store.s3.amazonaws.com/0/71899/185f2652-1f3f-995e-5407-937bb92cc50a.png)

問題なければアプリケーションとS3バケットが選択出来るようになっているハズです。

先程作成したCodeDeployアプリケーションとS3バケットを指定しておきましょう。

![BitbucketCodeDeploy5.png](https://qiita-image-store.s3.amazonaws.com/0/71899/8ca4484e-374a-ac27-5ff4-8911aa2e5de6.png)

## デプロイ対象のアプリケーションに `appspec.yml` 等を追加

参考までに私が行った設定を載せておきます。

```yml:appspec.yml
version: 0.0
os: linux
files:
  - source: /
    destination: /home/ec2-user/api-gateway
permissions:
  - object: /
    owner: ec2-user
    group: ec2-user
    mode: 777
    pattern: "**"
hooks:
  AfterInstall:
    - location: hooks/install.sh
      timeout: 300
      runas: ec2-user
    - location: hooks/deploy.sh
      timeout: 3300
      runas: ec2-user
```

```bash:hooks/install.sh
#!/usr/bin/env bash

source /home/ec2-user/.bash_profile

cd /home/ec2-user/api-gateway

yarn install
```

```bash:hooks/deploy.sh
#!/usr/bin/env bash

source /home/ec2-user/.bash_profile

if [ "$DEPLOY_STAGE" != 'dev' ] && [ "$DEPLOY_STAGE" != 'stg' ] && [ "$DEPLOY_STAGE" != 'prd' ];
then
  echo "Invalid DEPLOY_STAGE! Please set one of 'qa' or 'dev' or 'stg' or 'prd'."
  exit 1
fi

cd /home/ec2-user/api-gateway

yarn run deploy:${DEPLOY_STAGE}
```

詳しい説明は割愛させて頂きますが、`package.json` のscriptsにデプロイ用のコマンドを設定しておき、それがCodeDeployによってデプロイされた時に実行されます。

参考までに例を載せておきます。（関係なさそうな部分は削除してあります。）

```json:package.json
{
  "scripts": {
    "build": "tsc",
    "deploy:dev": "yarn run build && DEPLOY_STAGE=dev serverless deploy -v",
  },
  "devDependencies": {
    "@types/aws-lambda": "^0.0.31",
    "@types/aws-serverless-express": "^3.0.1",
    "@types/chai": "^4.1.2",
    "@types/compression": "^0.0.35",
    "@types/cookie-parser": "^1.4.1",
    "@types/cors": "^2.8.3",
    "@types/ejs": "^2.5.0",
    "@types/express": "^4.11.1",
    "@types/helmet": "^0.0.37",
    "@types/jsonwebtoken": "^7.2.3",
    "@types/mocha": "^2.2.48",
    "@types/node": "^9.4.5",
    "@types/qs": "^6.5.1",
    "@types/source-map-support": "^0.4.0",
    "@types/urlsafe-base64": "^1.0.28",
    "@types/uuid": "^3.4.3",
    "aws-sdk": "^2.193.0",
    "axios-mock-adapter": "^1.13.1",
    "chai": "^4.1.2",
    "cpx": "^1.5.0",
    "mocha": "^4.0.1",
    "nyc": "^11.3.0",
    "serverless": "^1.26.0",
    "serverless-dynamodb-local": "^0.2.27",
    "serverless-offline": "^3.16.0",
    "serverless-plugin-tracing": "^2.0.0",
    "serverless-plugin-warmup": "^3.3.0-rc.1",
    "serverless-prune-plugin": "^1.3.0",
    "ts-loader": "^3.5.0",
    "ts-node": "^4.1.0",
    "tslint": "^5.8.0",
    "tslint-config-airbnb": "^5.6.0",
    "typescript": "2.6.2"
  },
  "dependencies": {
    "aws-serverless-express": "^3.0.2",
    "aws-xray-sdk": "^1.1.7",
    "axios": "^0.17.1",
    "compression": "^1.7.1",
    "cookie-parser": "^1.4.3",
    "cors": "^2.8.4",
    "ejs": "^2.5.7",
    "express": "^4.16.2",
    "helmet": "^3.11.0",
    "jsonschema": "^1.2.0",
    "jsonwebtoken": "^8.1.1",
    "moment": "^2.20.1",
    "qs": "^6.5.1",
    "source-map-support": "^0.5.3",
    "urlsafe-base64": "^1.0.0",
    "uuid": "^3.2.1"
  }
}
```

# デプロイを実行する

デプロイを実行してみましょう。

Bitbucket → デプロイ対象のGitリポジトリ → デプロイ対象のブランチ から 「Deploy to AWS」 をクリックします。

![DoDeploy1.png](https://qiita-image-store.s3.amazonaws.com/0/71899/475957e3-b89f-caa1-f0fb-ebadb166c263.png)

デプロイグループを選択します。

私の場合はデプロイグループ名を環境変数名と同様にして環境毎のbuilderサーバを用意する事で `dev` `stg` `prd` の3つの環境にデプロイ出来るように設定しました。

こうしておくと開発環境のソースを間違って本番にデプロイしてしまうようなミスを防げます。

「Submit」をクリックするとデプロイが開始されます。

![DoDeploy2.png](https://qiita-image-store.s3.amazonaws.com/0/71899/5334cfe9-4b47-e9e6-ef95-bf4e1897344d.png)

しばらく時間が経つと以下のようにCodeDeploy上にデプロイが作成されている事が確認出来ます。

![DeploySuccess.png](https://qiita-image-store.s3.amazonaws.com/0/71899/a3592e84-db51-2243-5c32-988be8ef55d4.png)

# 感想

[Serverless Framework](https://serverless.com/) は単独でも十分便利なのですが、この設定を行う事で環境変数の設定ミスや間違ったデプロイを一定防ぐ事に成功しました。

導入もそれほど難しくないので、オススメです。

今回はBitbucketを利用しましたが、GitHubでも似たような仕組みを構築する事は可能です。（というかGitHubのほうがより簡単）

以上になります。最後まで読んで頂きありがとうございました。