ブランチごとに確認環境がほしかったので、作っていく。
前提として、
- serverless-express を使ったWebアプリ。
- ドメインについては Route53 により管理されている。
という状態で説明する。
プラグインの利用
プラグインを2つ使う
- serverless-domain-manager: 環境ごとに独自のドメインを割り当てることができる。
- serverless-kms-secrets: OAuth の secret など、機密情報を暗号化するためにKMSの利用をする。このプラグインはKMSの暗号化を便利に扱うことができる。機密情報がないのであれば、不要。
yarn add -D serverless-domain-manager serverless-kms-secrets
serverless.yml の調整
- ブランチごとに、S3のバケットがあると鬱陶しいので、
deploymentBucket
を指定する。 - 平文の利用が可能な環境変数については、
config/sls-config.yml
に設定を外出する。このファイルは環境ごとに、用意しておきS3にアップロードし、deploy時にダウンロードして使うようにする。(後述) - 暗号化済みの環境変数は
config/secure.yml
に格納する。扱いについては、config/sls-config.yml
と同様。
service:
name: ${self:custom.name}
provider:
name: aws
region: ap-northeast-1
runtime: nodejs6.10
stage: ${opt:stage, 'dev'}
deploymentBucket:
name: ${self:custom.name}.${self:provider.region}.deploy
environment:
XXX: ${self:custom.config.XXX, ''}
YYY: ${self:custom.config.YYY, ''}
SECURE_ZZZ: ${self:custom.kmsSecrets.secrets.ZZZ}
iamRoleStatements:
- Effect: 'Allow'
Action:
- "KMS:Decrypt"
Resource: ${self:custom.kmsSecrets.keyArn}
custom:
name: project-name
serverless-kms-secrets:
secretsFile: config/secure.yml
customDomain:
domainName: ${self:provider.stage}.sls.example.com
basePath: ''
stage: ${self:provider.stage}
certificateName: '*.sls.example.com'
createRoute53Record: true
config: ${file(./config/sls-config.yml)}
kmsSecrets: ${file(./config/secure.yml)}
plugins:
- serverless-domain-manager
- serverless-kms-secrets
functions:
render:
handler: server.render
events:
- http:
path: '/'
method: 'get'
private: false
- http:
path: '{proxy+}'
method: 'get'
private: false
一部環境変数の暗号化
Lambda の環境変数は、機密なものであればKMSを利用して暗号化することを推奨している。そこで、serverless-kms-secrets を利用して暗号化を行う。
予めKMSで暗号化キーを作成しておく。
config/secure.yml
を作成し、以下のようなファイルを作る
keyArn: 'arn:aws:kms:ap-northeast-1:xxxx:key/xxxxxxxxxxxx'
secrets: {}
ZZZ という環境変数を暗号化する場合は、
sls encrypt --name="ZZZ" --value="value"
のようにして暗号化を行うことが出来る。
暗号化された情報は、config/secure.yml
(serverless.yml
の custom.serverless-kms-secrets.secretsFile
で設定されたファイル) に格納される。serverless.yml
では、暗号化された config/secure.yml
内の情報を、環境変数として利用する。
利用時は、以下のように情報を復号化して使う。
import { KMS } from 'aws-sdk'
const kms = new KMS()
const encryptedBuf = Buffer.from(process.env['SECURE_ZZZ'], 'base64')
const cipherText = {CiphertextBlob: encryptedBuf}
kms.decrypt(cipherText).promise().then((data) => {
// 値の取り出し
const value = data.Plaintext.toString('ascii')
})
環境ごとに設定ファイルを準備する
デプロイの戦略を以下とする。
- master branch については、production 用の設定を取り出す
- develop branch については staging 用の設定を取り出す
- その他の branch については、 preview 用の設定を取り出す
ということで、上記3つの環境ごとに設定ファイル (sls-config.yml
, secure.yml
) を用意して、環境名ごとにprefixを分けてS3 Bucketに格納しておく。
デプロイ用スクリプトの作成
デプロイに使うスクリプトを用意する。
- ステージ名には
-/_
あたりの文字が入ると厄介なので (API Gateway, Lambda, CloudFormation あたりのどれかでいずれかの文字を受け付けない) 除去したものを利用する。123-test-branch
とかであれば、123testbranch
が stage 名となる。 - パーミッションは755にしておく。
#!/bin/bash
set -eu
export NODE_ENV="preview"
if [ "${CIRCLE_BRANCH}" == "master" ]; then
export NODE_ENV="production"
elif [ "${CIRCLE_BRANCH}" == "develop" ]; then
export NODE_ENV="staging"
fi
STAGE=${CIRCLE_BRANCH//[-\/]/}
echo stage=${STAGE}
# get config file from S3
aws s3 cp s3://xxx/${NODE_ENV}/sls-config.yml config/
aws s3 cp s3://xxx/${NODE_ENV}/secure.yml config/
# make Route53 configuration
./node_modules/.bin/sls create_domain --stage=$STAGE
# deploy
./node_modules/.bin/sls deploy --stage=$STAGE
# remove old lambda functions
./scripts/remove_old_stage.sh
上のスクリプトで、デプロイを行うと、どんどん関数が増えちゃうので、リモートブランチを消したら、次のデプロイのタイミングで環境も消すスクリプトを作っておく。
#!/bin/bash
set -eu
STACKS=`aws cloudformation list-stacks --stack-status-filter CREATE_COMPLETE CREATE_FAILED UPDATE_COMPLETE ROLLBACK_COMPLETE ROLLBACK_FAILED UPDATE_ROLLBACK_FAILED --max-items 1000 | jq '.StackSummaries[].StackName' -r | grep '^project-name-'`
BRANCHES=`git branch -r | sed -e "s/^[[:space:]]*origin\///" | grep -v "^HEAD" | uniq`
echo "stacks----"
echo "${STACKS}"
echo "branches----"
echo "${BRANCHES}"
while read line
do
found=0
lineStage=`echo $line | sed -e 's/^project-name-//'`
while read branch
do
stage=${branch//[-\/]/}
if [[ "$lineStage" = "$stage" ]]; then
found=1
break;
fi
done <<END
$BRANCHES
END
if [ $found -eq 0 ]; then
./node_modules/.bin/sls remove --stage=$lineStage || true
./node_modules/.bin/sls delete_domain --stage=$lineStage || true
fi
done <<END
$STACKS
END
.circleci/config.yml の調整
docker image を用意する
以下が動作する docker image を作っておく
- nodejs6
- python
- pip
- jq
- awscli
設定
version: 2
jobs:
build:
docker:
- image: my_docker_image
parallelism: 1
working_directory: ~/project-name
steps:
- checkout
- restore_cache:
key: project-name-yarn-{{ checksum "yarn.lock" }}
- run:
name: Install node modules
command: yarn install
- run:
name: lint
command: yarn run lint
- run:
name: unit
command: yarn run unit
- save_cache:
key: project-name-yarn-{{ checksum "yarn.lock" }}
paths:
- ~/.yarn-cache
- run:
name: deploy
command: scripts/deploy.sh
完了
これで、各ブランチごとに stage名.sls.example.com
が展開される。
serverless-domain-manager による環境の展開については、Route53、CloudFront の設定に時間がかかるため、初回展開時には40分ほどリクエストできるようになるまで時間がかかるとのこと。