Blog 開発ブログ

Blog

API Gateway+Lambda+Serverless+TravisでAPI開発のCI/CDパイプラインを構築する

概要

スクリーンショット 2017-02-21 10.15.16.png

API GatewayとLambdaを使ったAPI開発時のCI/CDについての記事です。
僕がnode.jsを普段から使用しているため、解説はnodeがベースになっています。

https://github.com/horike37/serverless-api-integration-test-sample

ソースはすべてGitHubに上がってますのでそちらもご確認ください。

CIで実施する内容

以下の内容をCIとして行うことを考えます。

  • ESLintによる構文チェック
  • Mocha, Chaiを使用したユニットテスト
  • APIをAWSへデプロイしてテストを行うインテグレーションテスト

CDで実施する内容

以下のようなルールでデプロイのサイクルを回します。

  • Gitのdevelopmentブランチへのpushをテスト環境へのデプロイと想定。
    • 構文チェックとユニットテストを実施。
    • ビルドが通れば、ServerlessのdevelopmentステージにAPIをデプロイ。
  • Gitのmasterを最新ソースの集約場所として想定して、pushを実施。
    • 構文チェックとユニットテストとインテグレーションテストを実施。
    • ビルドが通ってもデプロイは実施しない。
  • Gitのtagへのpushを本番環境へのデプロイと想定。
    • 構文チェックとユニットテストとインテグレーションテストを実施。
    • ビルドが通れば、ServerlessのproductionステージにAPIをデプロイ。

Continuous Integration

ESLintによる構文チェック

http://eslint.org/

JavaScript界隈ではもっともメジャーな構文チェックツールだと思います。構文ルールを定義して、構文エラーや記法を統一させることでソースの可読性や品質を向上させます。

https://github.com/horike37/serverless-api-integration-test-sample/blob/master/.eslintrc.js#L2

今回はルールとしてArbnbを採用しました。どの構文ルールを採用するかはチームの好みで決めれば良いと思います。

Mocha, Chaiを使用したユニットテスト

単体テストを実施します。メソッドや関数毎に入力値と出力値をチェックして関数単位での品質チェックを行います。

https://github.com/horike37/serverless-api-integration-test-sample/tree/master/lib

このようにLambdaがメインで実行する部分から、ビジネスロジックをclassに切り出してあげるとテストが書きやすくなるのでそうすることが多いです。

APIをAWSへデプロイしたテストを行うインテグレーションテスト

インテグレーションテストはサーバレスアーキテクチャの特徴とも言えるテストです。

サーバレスアーキテクチャは複数のサービスで構成されているケースが多く、ユニットテストだけではアーキテクチャ全体のテストを網羅できません。実際のクラウド環境へリソースをデプロイして結合テストを行い、すべて成功すれば、それを破棄します。

このデプロイ管理にはServerlessを使用しています。

https://github.com/horike37/serverless-api-integration-test-sample/blob/master/integration-test/test.js#L18
https://github.com/horike37/serverless-api-integration-test-sample/blob/master/integration-test/test.js#L41
テストの最初と最後にアーキテクチャのデプロイと削除を行っています。このメソッドの実体は、sls deploysls removeです。

it('should return correct values from all apis', () => {
     const testEndpoint = `${endpoint}/hello`;

     return fetch(testEndpoint, { method: 'GET' })
       .then(response => response.json())
       .then((json) => expect(json.message).to.equal('Go Serverless v1.0! Your function executed successfully!'));
  });

https://github.com/horike37/serverless-api-integration-test-sample/blob/master/integration-test/test.js#L35-L37

そしてここがテストのメインの部分です。デプロイされたAPIへリクエストを送り、その返り値をchaiでチェックを行います。こうすることで何本APIを作ったとしても自動テストが可能になるというメリットがあります。

Continuous Delivery

デプロイスクリプト

以下の様なスクリプトでデプロイを実施します。Gitのtagにpushされた際にproduction環境へ。Gitのdevelopmentブランチにpushされた際にdeployment環境へ。それぞれビルドが正しく通ればデプロイされるようになっています。bin/deploy.sh

#!/bin/bash
set -e
BRANCH=${TRAVIS_BRANCH:-$(git rev-parse --abbrev-ref HEAD)}
if [[ $TRAVIS_TAG ]]; then
  STAGE="production"
elif [[ $BRANCH == 'development' ]]; then
  STAGE="development"
fi

if [ -z ${STAGE+x} ]; then
  echo "Not deploying changes";
  exit 0;
fi

echo "Deploying from branch $BRANCH to stage $STAGE"
npm prune --production  #remove devDependencies
sls deploy --stage $STAGE --region $AWS_REGION

ServerlessのIAM権限

TravisにてServerlessが動作するように専用のユーザを発行します。今回は最低限で以下の様なIAMポリシーを与えています。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt543534534",
            "Effect": "Allow",
            "Action": [
                "iam:GetRole",
                "iam:CreateRole",
                "iam:DeleteRole",
                "iam:PutRolePolicy",
                "iam:DeleteRolePolicy",
                "iam:PassRole",
                "logs:*",
                "s3:*",
                "lambda:*",
                "cloudformation:*",
                "apigateway:*"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

そして、TravisからAWSリソースへアクセスするためにcredentialをTravisへ設定します。
もちろん生のcredentialを.travis.ymlへ追記することはご法度です。

Travisのコマンドにより暗号化し、credentialがTravisの環境上でしか使えないようにします。

$ travis encrypt AWS_ACCESS_KEY_ID=xxxxxxxxxxx --add
$ travis encrypt AWS_SECRET_ACCESS_KEY=xxxxxxxxxxx --add

このコマンドにより。.travis.ymlに暗号化した状態でcredentialが設定されます。
これはTravis上でビルドを実行した際に環境変数として設定されます。

Travisの設定ファイル

最終的にはこんな感じに.travis.ymlが仕上がりました。

travis.yml

language: node_js
node_js:
  - '4.3'
env:
  global:
    - AWS_REGION=us-east-1
    - SLS_DEBUG=true
    - secure: lSxO7tZ0c/FA8VL72042dqQZ+tRjsS93iVYxMr1ghP/0tBxdmrhhYdAD9UrSv/Kk+Y1jRlkpQ2uaARHoy+6ZqmhchX32HpIYBQVJ/ntMSgv37gFbrNTOfSFoATMTRy6RT2UKaIWAa4xnmDxaQNFPx4X5l9Y25RdivoR+WXrEPd4eCCTVL/23bABSIySSTs+VGqQIppE4Jw5ibbcSoTLsuj00nK+VrmYHNlTSiEuKIxgFC1Ix0hqayJ/kely0DqYW/CY/vCCf0V4yazJo9fG1EFfrHsSIAKKeGRY7WMnLPJ7hJGwRiVV1/atMx/5kRPKOADcRTfoh3noXS3/sd1hbGjTwnJVRVrYiUocHwuNbo1TpW1On85jXEdvnKY9JYelFEnXnWn6A2bRMhgL/zul/WuSPCGq7HpsGMRhXrEiEYJ9YhnVNiUTaV2amoOClMOpHFnStMfTJVg7NJ8mBF4XOzODvhAzyPFDWdJ94Ejl1LAnGOAp/wBQVbFswKPdwdosFU6LyirQkA4k0q7C4zXYywyQtrY7H9w7FtKo+U4596GQAQtvzQz6GS42c1WBX0fIrMu1VXc+KmwCUBEVmvBxLS7c0DJUI61atDFGq7788K7IMWw83lIFjJULdwv1qU4uBi3MvPm2OHCdRAzBGEYIC87zfcYI/gi41rh3bj/C0wiI=
    - secure: LIh0lkl/t72EbMd47WgEXqnoG3REp+oPhIfDR5Cs8SMO4sacvo2j4pRkRKIwwpdKozxdgLEMl1rwDoHyYPH77FzvDnwiufpaYgQs278wmi+6ZvoC9nhgdn2sT7cFnYuYAO8dC7G/NHzXogAVmiObf3I+hzNLjDqWwWVjqPm41p4P4c2EJUVo0nVlcaUOf8elS1j6zp+ZL1EQo4Fm4IumDgNpZUP4bSq8CcVPvF0ynMlslI8XNMnBOiYmG+644qILScyPK1Q2SPdMLqL5YXHuYfE0aCpFcWOcNZIalBmaxPqFNW+QHQvaYiwoENx/i91KS3U2mqfcNYY4o9viih47PFsaddvtBeB83Wfls7GIZ/XmvBKREuS5Gwhz930DbAvUNQT1ylS9Y6TTIIWIbe3Qmv6ngd9TrDHlnbVhQYgar9ur+TgvLyhs5YLAeLn85c3Z3GYN5JUuCq5bclzh4I+myagaWJPoujS1nT+vKLUW8hu5MkxApn+uFUo/OIW1WG4qho/ddh7RoJbI9oTebWjpXhLwd1pSET1yBVSEetORluh0pW7r5A425Rm80B58Mg/x0NNmM1x6DBrCZ5R8d+Bam6C7P4WxxrRex7vFcqRWeKlJNINO+rtPW3Uuo/s9yC9980uQ00eY0kqhouR8ol6xs/4Xye1AHovPR2unzEe37fM=
before_install:
  - chmod +x ./bin/deploy.sh
  - npm i -g serverless@1.6.1
install:
  - travis_retry npm install
script:
  - npm run lint
  - npm run test
  - if [[ $TRAVIS_BRANCH == "master" ]] || [[ $TRAVIS_TAG ]]; then npm run integration-test; fi
after_success:
  - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage
  - ./bin/deploy.sh


実行結果

developmentブランチへのpush

https://travis-ci.org/horike37/serverless-api-integration-test-sample/builds/203491545

ESLintとユニットテストのビルドが成功し、developmentステージへデプロイされています。

masterブランチへのpush

https://travis-ci.org/horike37/serverless-api-integration-test-sample/builds/203489525

ESLintとユニットテストとインテグレーションテストが成功しましたが、デプロイはされていません。

tagへのpush

https://travis-ci.org/horike37/serverless-api-integration-test-sample/builds/203484838

0.1というタグにpushをしています。
ESLintとユニットテストとインテグレーションテストが成功し、puroductionステージにデプロイされています。

如何でしたでしょうか。こんな感じでCI/CDパイプラインを構築することですべてが自動化され品質も担保されるようになりました。よろしければ是非参考にしてみてください!

では、良いパイプラインライフを!

参考文献

CodePipelineでServerless Frameworkのデプロイを管理する

Our Partners

サーバーレスで
クラウドの価値を最大限にMaximize the cloud value with serverless

Serverless Operationsはこれまでグローバルの第一線で培ってきたクラウド技術(AWS − アマゾンウェブサービス)の豊富な実績と知見を活かし、お客さまのサーバーレスに関するさまざまな課題を解決します。