概要
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 deploy
やsls 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/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が仕上がりました。
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
0.1
というタグにpushをしています。
ESLintとユニットテストとインテグレーションテストが成功し、puroductionステージにデプロイされています。
如何でしたでしょうか。こんな感じでCI/CDパイプラインを構築することですべてが自動化され品質も担保されるようになりました。よろしければ是非参考にしてみてください!
では、良いパイプラインライフを!