serverless framework + CodePipeLine + CodeBuildを使ってサーバーレスでCI/CDする

# 概要

以下のようなアーキテクチャを作っていきます。アプリケーション自体は、LambdaからSlackBotに通知させるだけの単純なものです。

GitHubのプッシュをトリガーに、CodePipeLine + CodeBuildでビルドとテストを行なって、OKだったら開発環境にデプロイします。手動承認後、本番環境にデプロイするような流れです。AWSの新アイコンを使ってみたかったので使っています。個人的には、新アイコンの方が、スタイリッシュでかっこいいけど、旧アイコンの方が見やすいかなと思います。
![スライド1.JPG](https://qiita-image-store.s3.amazonaws.com/0/53763/ce081011-f313-4b25-6a2a-76972b15800b.jpeg)

# serverless frameworkのプロジェクトを作成
## ①GitHubのリポジトリとserverless frameworkのプロジェクトの作成
まずは、適当なGitHubのリポジトリを作成します。その後、serverless frameworkを使って、プロジェクトを作成します。axiosを使ってPOSTリクエストを行うため、npmでインストールしておきます。

```
serverless create -t aws-nodejs -p sample-slack-app
npm init
npm install --save axios
```

## ②serverless.ymlを修正
以下のようにserverless.ymlに記述します。内容としては、API GatewayとLambda関数を作成しています。

```yaml:serverless.yml
service: serverless-qiita-advent-sample


custom:
  defaultStage: dev

provider:
  name: aws
  runtime: nodejs8.10
  stage: ${opt:stage, self:custom.defaultStage}
  region: ap-northeast-1
  environment: 
    SLACK_WEBHOOK_URL: https://hooks.slack.com/services/.............

package:
  exclude:
    - .git/**

functions:
  postSlack:
    handler: handler.postSlack
    events:
      - http:
          path: slack
          method: post
          cors: true

```

## ③handler.jsを修正
SlackBotに通知させるプログラムを書きます。node v8.10を使っています。

```javascript:handler.js
'use strict';

const axios = require('axios');

module.exports.postSlack = async (event, context) => {

  const slackWebhookUrl = process.env.SLACK_WEBHOOK_URL;
  const message = "テストだよ";

  try {
    //リクエスト送信
    await axios.post(slackWebhookUrl, { text: message });
    return {
      statusCode: 200,
      body: JSON.stringify({
        message: 'Slackの通知に成功しました',
        input: event,
      }),
    };
  } catch (err) {
    console.log(err);
    return {
      statusCode: 400,
      body: JSON.stringify({
        message: 'Slackの通知に失敗しました。',
        input: event,
      }),
    };
  }
};

```

動作確認のため、一度AWSへデプロイして、Lambdaを実行してみます。

```
$ sls deploy
Serverless: WARNING: Missing "tenant" and "app" properties in serverless.yml. Without these properties, you can not publish the service to the Serverless Platform.
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (1.99 KB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..............
Serverless: Stack update finished...
Service Information
service: serverless-qiita-advent-sample
stage: dev
region: ap-northeast-1
stack: serverless-qiita-advent-sample-dev
api keys:
  None
endpoints:
  POST - https://1xxxxxxxxxxxxxxxxxxxxxxx.ap-northeast-1.amazonaws.com/dev/slack
functions:
  postSlack: serverless-qiita-advent-sample-dev-postSlack

```

```
sls invoke -f postSlack
{
    "statusCode": 200,
    "body": "{\"message\":\"Slackの通知に成功しました\",\"input\":{}}"
}
```

SlackにPostしているのを確認できました。
![slack.png](https://qiita-image-store.s3.amazonaws.com/0/53763/e5674806-47ce-2f80-c779-2bb80d51a490.png)

# AWSでCI/CD環境を構築する
今回の主題である、CodePipeline + CodeBuildを用いて、AWS上にCI/CD環境を構築していきます。

## CodepPipeLineの作成

### パイプラインプロジェクトの作成
AWSコンソール上でCodePipeLineを選択します。パイプラインの作成ボタンがあるのでそれをクリックします。まずはパイプラインの設定をしていきます。以下のように設定します

パイプライン名: serverless-qiita-advent-sample-pipeline
サービスロール: 新しいロール
アーティファクトストア: デフォルトの場所

![codepipeline2.png](https://qiita-image-store.s3.amazonaws.com/0/53763/947ace75-1d66-d36e-90d2-166617367e70.png)

### ソースステージの設定
次に、ソースステージの設定をしていきます。ソースステージとはソースコードを取得するステージです。今回はGitHubのPushをトリガーにするので、GitHubを選択します。他にもAWSのCodeCommitやS3などを設定することもできます。GitHubに接続し対象となるリポジトリとブランチを選択します。

ソースプロバイダ: GitHub
リポジトリ: 自分のリポジトリ
ブランチ: master
変更検出オプション: GitHubウェブフック

![codepipeline3.png](https://qiita-image-store.s3.amazonaws.com/0/53763/41ad6889-7af1-15a1-0e22-9feab9ba5c11.png)

### ビルドステージの設定
ビルドステージの設定をしていきます。ビルドステージとは実際にビルドやテストが行われる場所で、今回はAWS CodeBuildを選択します。他にもJenkinsやSolano CIなども選択することができます。
CreateProjectをクリックするとCodeBuildの設定画面が小画面で表示されます。今回はDev環境とProd環境でビルドを分けるためBuildSpec名を変更しています。

![codepipeline4.png](https://qiita-image-store.s3.amazonaws.com/0/53763/a7bd2d84-1a4d-8543-1d22-213a98a44959.png)

プロジェクト名: serverless-qiita-advent-sample-dev-build
環境イメージ: マネージド型イメージ
オペレーティングシステム: Ubuntu
ランタイム: Node.js
ランタイムバージョン: aws/codebuild/nodejs:8.11.0
イメージのバージョン: このランタイムイメージには常に最新のイメージを使用してください
サービスロール: 新しいサービスロール
ロール名: codebuild-serverless-qiita-advent-sample-build-service-role
ビルド仕様: buildspecファイルを使用する
BuildSpec名: buildspec-dev.yml

![codepipeline5.png](https://qiita-image-store.s3.amazonaws.com/0/53763/f49a6a4f-8709-8e90-1aef-cf561d732b9b.png)
![codepipeline6.png](https://qiita-image-store.s3.amazonaws.com/0/53763/dc7106b8-d365-1a78-babe-5b8f9d6e48c1.png)

CodeBuildプロジェクトを作成するとCodePipelineプロジェクトに戻り、先程作成したCodeBuildプロジェクトが自動で選択されています。

![codepipeline7.png](https://qiita-image-store.s3.amazonaws.com/0/53763/d711a4e5-ad30-d565-1e8b-123883abc241.png)

### CodeBuildのIAMロールのポリシーの設定
今回はCodeBuildからserverless frameworkを用いてデプロイを行うため、先ほど作ったIAMロールにポリシーを追加しておかないと各種サービスへのデプロイ時にアクセス拒否されてしまいます。先程作った`codebuild-serverless-qiita-advent-sample-build-service-role`のIAMロールにポリシーをアタッチします。

AWSのコンソールからIAMを選択し、IAMロールタブに移動し、`codebuild-serverless-qiita-advent-sample-build-service-role`があると思うのでそれを選択します。

![qiita11.png](https://qiita-image-store.s3.amazonaws.com/0/53763/ec9c98d6-4492-0eda-7be7-63ba1aee3e5f.png)

serverless frameworkでデプロイをするのでかなりポリシーを許可しないといけません。あまりよろしくはないですが、`AdministratorAccess`のポリシーをアタッチします。本当ならserverless frameworkで使用するものだけを許可するのがいいです。

![qiita12.png](https://qiita-image-store.s3.amazonaws.com/0/53763/485afb82-9702-11a1-707b-9af99fa203f7.png)


### デプロイステージの設定
次にデプロイステージですが、CodeBuild上でserverless frameworkを使用してデプロイを行うため、今回は使用しません。EC2などにソースコードをデプロイするような場合はここで設定することができます。今回はスキップを選択します。

![codepipeline8.png](https://qiita-image-store.s3.amazonaws.com/0/53763/4caf639c-d2af-30f8-2081-ebb6ac71c6ed.png)

入力した内容を確認して、パイプラインの作成をします。

![codepipeline9.png](https://qiita-image-store.s3.amazonaws.com/0/53763/35af05f0-57e6-8ee2-af86-a9cbf5bbfb4c.png)


パイプラインの作成をすると実際にパイプラインが動き始めて、実際にGithubから最新のソースの取得とビルドが始まります。ソースの取得はSucceedになると思いますが、ビルドはFaildになると思います。これはまだGitHubのリポジトリにBuildSpecファイルを含めていないためです。

![codepipeline10.png](https://qiita-image-store.s3.amazonaws.com/0/53763/253721d0-3bd2-e5d2-dd7b-4f14998bbb40.png)

## buildspecファイルと単体テストの作成

### buildspecファイルの作成
先程作ったserverless frameworkのプロジェクトのルートにbuildspecファイルを作成します。今回はDev環境とProd環境用の2つを作ります。

```yaml:buildspec-dev.yml
version: 0.2

phases:
  install:
    commands:
       - npm install -g serverless
       - npm install     
  build:
    commands:
       - npm test
       - sls deploy
```

```yaml:buildspec-prod.yml
version: 0.2

phases:
  install:
    commands:
       - npm install -g serverless
       - npm install     
  build:
    commands:
       - npm test
       - sls deploy --stage prod
```

2つ作りますが、違いはsls deployの--stateオプションの違いぐらいです。

### 単体テストの作成
次にLambda関数の簡単な単体テストを書いていきます。この程度のアプリケーションだと単体テストはあまり意味がないですが、実際にテスト→ビルドを実行するためにサンプルとして書いていきます。Lambda関数のテストを書くためには、Eventオブジェクトのモックが必要になります。今回はAPI Gatewayを使用しているためAPIGateway用のEventオブジェクトのモックを作成します。また、axiosも使用しているためaxiosのモックも作成します。今回は`aws-event-mocks`と`axios-mock-adapter`というOSSのモックライブラリを使用します。
まずは、単体テストを行うために、必要なライブラリのインストールをします。

```
npm install axios-mock-adapter aws-event-mocks chai mocha --save-dev
```

次にtestフォルダを作成し、単体テストを書いていきます。

```javascript:test/test-lambda-function.js
"use strict";

const chai = require('chai');
const createEvent = require('aws-event-mocks');
const handler = require('../handler');
const should = chai.should();

// axios mock
const axios = require('axios');
const MockAdapter = require('axios-mock-adapter');
var mock = new MockAdapter(axios);

// event mock
const event = createEvent({
  template: 'aws:apiGateway',
  merge: {}
});

describe('Lambda function test', () => {

  beforeEach(function() {

    process.env.SLACK_WEBHOOK_URL = "test"

  });
  
  it('postリクエストに成功したら、ステータスコード200', async () => {
    mock.onPost().reply(200);
    const res = await handler.postSlack(event, null);
    res.statusCode.should.equal(200); 
  });
  
  it('postリクエストに失敗したら、ステータスコード400', async () => {
    mock.onPost().reply(500);
    const res = await handler.postSlack(event, null);
    res.statusCode.should.equal(400); 
  });
});
```

npmで単体テストを実行するため、package.jsonに追記します。

```json:package.json
"scripts": {
    "test": "mocha --timeout 10000 ./test/*"
  }, 
```

単体テストが通るかどうかテストを実行してみます。

```
$ npm test

> serverless-qiita-advent-sample@1.0.0 test C:\Users\sato14\github\serverless-qiita-advent-sample
> mocha --timeout 10000 ./test/*



  Lambda function test
    √ postリクエストに成功したら、ステータスコード200
    √ postリクエストに失敗したら、ステータスコード400


  2 passing (12ms)

```

通りました。

## GitHubにプッシュしてCodePipeLineを動作させる
実際にGitHubのmasterにプッシュしてCodePipeLineを動作させてみます。

```
git add -A
git commit -m "buildspecファイルとLambda関数の単体テストの作成"
git push origin master
```

動作してソースステージ、ビルドステージともにSucceedになりました。
![qiita13.png](https://qiita-image-store.s3.amazonaws.com/0/53763/893fda53-328e-38de-9129-2296c4bc82e9.png)

CodeBuildのログでも単体テスト実行後にserverless frameworkでのデプロイが確認できました。
![qiita14.png](https://qiita-image-store.s3.amazonaws.com/0/53763/38eb06d8-437a-ae69-49c6-1597ad1c2da4.png)

## 承認プロセスの作成とProd環境用ビルドの作成
ここまでで、Dev環境用のビルドとデプロイができましたので、今度は承認プロセスとProdのビルド環境を作成します。

### 承認プロセスの作成
先程作ったCodePipeLineのプロジェクトを開きます。開いたら編集ボタンがあると思いますので、クリックします。

編集モードになりますので、一番下のステージの追加でステージ名:Prodとして追加します。追加後、ステージの編集をクリックしステージの編集モードにしアクショングループの追加をクリックします。

以下の内容にし保存をクリックします。他にもSNSトピックで承認プロセスを通知したり、承認するためのレビュー用のURLなどを設定することもできます。今回は空白で設定します。

![qiita20.png](https://qiita-image-store.s3.amazonaws.com/0/53763/ce14e435-715c-36b4-cb77-f5faa0cce8f8.png)

次にもう一度アクショングループの追加をクリックし、CodeBuildの新プロジェクトを作成します。以下の画像の内容で作成します。Dev環境と違うのは一箇所だけでBuildSpec名が`buildspec-prod.yml`になっています。本当ならブランチも分けたいところですが、今回はmaster一本でやっています。

![qiita22.png](https://qiita-image-store.s3.amazonaws.com/0/53763/e4901a3c-02eb-b85d-00a9-46abb3c93d2c.png)
![qiita23.png](https://qiita-image-store.s3.amazonaws.com/0/53763/e5a81c62-79af-ff85-cbcd-751e08a985ac.png)


その後以下を設定してパイプラインを保存します。
入力アーティファクト: SourceArtifact
出力アーティファクト: ProdBuildArtifact

最終的には以下のようなパイプラインになっています。
![qiita28.png](https://qiita-image-store.s3.amazonaws.com/0/53763/378ca8e6-010a-383c-fd09-347bce004f32.png)

## 手動承認してみる
パイプラインを動かすために適当にソースコードを修正してGitHubにプッシュします。Dev環境のビルドとデプロイが終わったタイミングで承認プロセスのところで承認待機状態になります。
![qiita26.png](https://qiita-image-store.s3.amazonaws.com/0/53763/330d647e-a799-d7f0-1633-2416cccdf9fc.png)
確認というボタンがでてるはずなので、押すとコメントを入力する小画面がでます。そこで適当なコメントを書いて承認を完了します。完了し、Prod環境のビルドとデプロイが走ってSucceedになればOKです。

#まとめ
今回初めてアドベントカレンダーに参加しました。CodePipeLineとCodeBuildはAWSの中でもあまり触れていないサービスであったため、これを気に勉強をしようと思い、今回参加しました。触ってみた感想としてはあまりハマることもなくCI/CDを構築できたなーという印象です。出たばかりのときにちょっと触ったときはまだ環境が整っていなかったため、難しい印象があったんですが、UIが刷新され、Codeシリーズが一つの管理画面に収まったことでCodeシリーズの連携がスムーズにできるようになったと思います。

この記事のソースコードは以下のリポジトリに入っています。
https://github.com/briete/serverless-qiita-advent-sample