LoginSignup
5
6

More than 5 years have passed since last update.

CircleCI と serverless を使いブランチごとに Lambda/API Gateway 環境を構築する

Last updated at Posted at 2017-12-21

ブランチごとに確認環境がほしかったので、作っていく。

前提として、

  • 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 と同様。
serverless.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 を作成し、以下のようなファイルを作る

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.ymlcustom.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にしておく。
scripts/deploy.sh
#!/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

上のスクリプトで、デプロイを行うと、どんどん関数が増えちゃうので、リモートブランチを消したら、次のデプロイのタイミングで環境も消すスクリプトを作っておく。

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

設定

.circleci/config.yml
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分ほどリクエストできるようになるまで時間がかかるとのこと。

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6