このブログは Vicenç García-Altés. [氏による寄稿記事です。許可を得て 個人ブログ, の内容を再掲しています。どうぞお楽しみください。質問については、 こちら から同氏にお問い合わせください。

はじめに

AWS Lambda はとても使い始めやすく、ブラウザーで関数を記述することもできます。しかし、日々の業務とするのであれば、CI/CD パイプラインのセットアップをお勧めします。また、2 つのアカウント (本番環境用と開発用) や、インフラストラクチャを繰り返し作成できる信頼性の高い方法も必要になるでしょう。この記事では、シンプルな継続的デリバリー パイプラインを作成する方法について紹介し、アプリケーション開発のプロに一歩近づくためのお手伝いをします。

免責事項

この記事の目的は、サーバーレス アプリケーションのビルドやテストの方法を説明することではありません。したがって、サンプル アプリケーションは非常にシンプルであり、テストは実用に耐えない可能性があります。本記事の目的は、サーバーレス アプリケーションをデプロイする CD パイプラインの作成方法であることをご了承ください。

前提条件

作業を行う前に、以下を準備しておく必要があります。

  • 2 つの AWS アカウント (開発のシミュレーションを行うものと、本番環境のシミュレーションを行うもの)
  • セットアップ済みの CircleCI アカウント
  • コンピューターにインストール済みの Node.js
  • コンピューターにインストール済みのサーバーレス フレームワーク
  • コンピューターで作成済みの AWS プロファイル (本記事では vgaltes-serverless)
  • コンピューターにインストール済みの jq

サンプル アプリケーション

まずは、非常にシンプルな NodeJS サーバーレス アプリケーションを作成します。ターミナルを起動し、任意の場所に新しいフォルダーを作成してそこに移動し、以下のように入力します。

serverless create --template aws-nodejs

これにより、hello を出力するだけの関数を持つ基本的なサーバーレス プロジェクトが作成されます。次は、その関数にテストを追加しましょう。handler.spec.js という名前のファイルを作成して、以下のコードをコピーします。

const mocha = require('mocha');
const chai = require('chai');
const should = chai.should();

const handler = require('./handler');

describe("The handler function", () => {
    it("returns a message", () => {
        handler.hello(undefined, undefined, function(error, response){
            let body = JSON.parse(response.body);
            body.message.should.be.equal('Go Serverless v1.0! Your function executed successfully!');
        });
    });
});

このテストを実行するには、以下のようにしてパッケージをインストールする必要があります。

npm install --save-dev mocha
npm install --save-dev chai

テストの実行まであと一歩。package.json ファイルの scripts セクション内に、以下の行を追加しましょう。

"test": "./node_modules/.bin/mocha **/*.spec.js"

npm test を実行すると、以下のようにテストに合格するはずです。

CDServerlessArticle1.png

これで完了です。前述のように、本記事で作成するアプリケーションは非常にシンプルです。

基本的な CI パイプライン

ここからは CI パイプラインを扱います。今回は CircleCI を選びました。その理由は CircleCI が PaaS サービスであり個人的に試してみたかったからですが、手順はほとんど同じなので CI システムはお好みのもので構いません。

ここでは CircleCI を使用するので、プロジェクトのルートに .circleci という名前のフォルダーを作成し、その中に config.yml という名前のファイルを作成します。その後、以下の yaml コードをファイルにコピーします。

version: 2
jobs:
  build:
    docker:
      - image: circleci/node:8.10

    working_directory: ~/repo

    steps:
      - checkout

      # 依存関係をダウンロードしてキャッシュ
      - restore_cache:
          keys:
          - v1-dependencies-
          # 完全一致が見つからない場合はフォールバックとして最新のキャッシュを使用
          - v1-dependencies-

      - run:
          name: Install Serverless CLI と依存関係のインストール
          command: |
            sudo npm i -g serverless
            npm install

      - save_cache:
          paths:
            - node_modules
          key: v1-dependencies-
        
      # テストの実行
      - run: 
          name: カバレッジを指定したテストの実行
          command: npm test --coverage

      - run:
          name: アプリケーションのデプロイ
          command: sls deploy --stage pre

コードは非常にシンプルです。Node 8.10 がプリインストールされた Docker イメージを使用し、コードをチェックアウトし、サーバーレス フレームワークおよびプロジェクトの依存関係をインストールして、いくつかの単体テストを実行してから、pre というステージへのデプロイを行っています。

CircleCI の構成

このファイルを GitHub にプッシュする前に、リポジトリをリッスンし config.yml で設定したアクションを実行するように CircleCI を構成しなければなりません。まず、CI の 管理者ユーザーを作成し (circleci という名前を付けました)、コード経由でのみアクセスできるようにコンソールのパスワードを無効にします。

最初に、[Add Projects (プロジェクトの追加)] に移動し、追加したいプロジェクトの [Set Up Project (プロジェクトのセットアップ)] ボタンをクリックします。オペレーティング システムが Linux であること、言語が Node であることを確認して、[Start Building (ビルドの開始)] をクリックします。これにより、初回のビルドが起動します。

次に、ビルド ページの右上隅にある [Project Settings (プロジェクト設定)] ボタンをクリックします。画面左側にある [PERMISSIONS (権限)] セクションで、[AWS Permissions (AWS 権限)] ボタンをクリックします。

ProjectPermissions.png

ここで、CI ユーザーのキーペアを設定します。

AWSPermissions.png

それでは、プッシュを行い CircleCI の状況を確認してみましょう。

CDServerlessArticle2.png

万事うまくいっているようです。AWS の開発アカウントも見てみましょう。

CDServerlessArticle3.png

上出来です!

AWS の Assume Role

無事、CircleCI を使用して開発アカウントに Lambda をデプロイすることができました。ですが、本番環境用のアカウントは別に用意している組織の方が多数派です。開発アカウントで構成したシステムから、まったく別のアカウントにデプロイするにはどうすればよいでしょうか。ここで便利なのが、AWS のAssume Role(権限の委譲)機能です。

初めに、開発アカウントのアカウント ID を取得する必要があります。[サポート]、[サポート センター] の順にクリックしてサポート センターに移動し、ID を取得します。

次に、本番環境アカウントでロールを作成する必要があるので、管理者アカウントでログインして、IAM サービスに移動し新しいロールを作成します。種類は [別の AWS アカウント] を選択して、開発アカウント ID を指定します。

CDServerlessArticle4.png

[次のステップ: アクセス権限] をクリックして、ロールの権限を付与します。今回は、すべてが問題なく機能することを確認できるように、管理者の権限を選択します。これは後ほど変更します。

CDServerlessArticle5.png

最後に、ロールに名前を付けます (ここでは circleci_role)。

CDServerlessArticle6.png

これで、ロールを作成できました。今度は、開発アカウントのユーザー アカウント (またはグループ) について、そのロールに切り替えて本番環境アカウントにアクセスできるように変更を行います。

ここでは 1 人のユーザーに対して変更を実行しますが、グループでも構いません。チームや組織の構成に応じて、全員が本番環境に直接デプロイできるようにするのか、CI ユーザーのみが本番環境にデプロイできるようにするのかを検討してください。合理的なのは後者でしょう。

開発アカウントに移動し、IAM サービスにアクセスして、権限を付与するユーザーを選択します。[アクセス権限]、[インライン ポリシーの追加] を順にクリックし、以下の JSON を使用します。

{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Allow",
    "Action": "sts:AssumeRole",
    "Resource": "arn:aws:iam::262170986110:role/circleci_role"
  }
}

CDServerlessArticle7.png

[ポリシーの確認] をクリックしてポリシーに名前を付けます (allow-assume-circleci-role-production など)

それでは、正常に機能するかどうかテストしてみましょう。テストには以下の 4 種類があります。

テスト 1 - UI を使用する

Web サイトの UI を使用してロールを引き受けます。ユーザー名、[ロールの切り替え] の順にクリックします。必要なデータ (本番環境アカウント ID と、先ほど作成した本番環境ロール) を以下のフォームに入力します。

AssumeRoleUI1.png

入力した名前に変更されれば成功です。本番環境アカウントに切り替わったので、S3 バケットを表示することができるようになりました。

AssumeRoleUI2.png

テスト 2 - プロファイルを使用した CLI

AWS の名前付きプロファイルを適切に構成することで、権限の委譲を行うこともできます。ご存じの方もいると思いますが、プロファイルとは複数のアカウント認証情報を運用するための方法で、名前を付けて管理することができます。

~/.aws/credentials を編集して、以下の構成を追加します。

[vgaltes-prod] role_arn = arn:aws:iam::262170986110:role/circleci_role source_profile = vgaltes-serverless

source_profile は開発アカウントの名前付きプロファイルです。role_arn は本番環境アカウントの ARN (Amazon Resource Name)、つまり移譲を受ける権限です。

以下を試してみましょう。プロジェクトのベース フォルダーに移動して次のように入力します。

aws s3 ls —-profile vgaltes-prod

本番環境アカウントの S3 バケットが表示されれば成功です。

テスト 3 - CLI を使用して認証情報を取得する

3 つ目のテスト方法 (上 2 つとは異なり、CI 環境に関係するテストです) は、AWS STS 経由で本番環境アカウント (一時的なアカウント) にログインするために必要な認証情報を取得することです。コンソールを起動して以下のように入力します。

aws sts assume-role --role-arn "arn:aws:iam::262170986110:role/circleci_role" --role-session-name "Vgaltes-Prod" --profile vgaltes-serverless

role-arn は移譲を受ける権限、profile は開発プロファイルです。

このコマンドの出力は以下のようになります。

{
    "AssumedRoleUser": {
        "AssumedRoleId": "AROAIHUHXX66KCXPBZHP4:Vgaltes-Prod", 
        "Arn": "arn:aws:sts::262170986110:assumed-role/circleci_role/Vgaltes-Prod"
    }, 
    "Credentials": {
        "SecretAccessKey": "Q833K5zxeaWiWI1hBbbmfTYF01Px72imuvcGB3TA", 
        "SessionToken": "FQoDYXdzENz//////////wEaDKbdCGIckdfH7OdGcyLwAY1V/m+S38Fu/Mh+KHWNbg58kfm7047AAMYZR1kVdFjuvr/idqEHLqkYq3aRWr2G+x1ZM2fQeB0RaHTlNpQ9gxrSFooFRSTSKK3rOpfIHyX9uQN5yPSUotA/At6LRYCqhDEIRsWVYNFFNjjjCgL4N8uyDsZfLcIsZ2zbTV/3oJSOQdm2i07pWwBI5w4J5l4oEgN7vZlq41/HK10jTHKtoYf+xoDUD6fa/R6gUvsphhXJ16Cs9OlhbljvcIweYKrnZCDR/u0GCyLs7PrIyAA1AgEFSqeb3dmI5ONwTpX8GSmLPuTOOfRaHTvbKeI3hyy0hCiJ0ePWBQ==“, 
        "Expiration": "2018-04-19T20:05:45Z", 
        "AccessKeyId": "ASIAJMXB2HHJRVWLFMXQ"
    }
}

ここで得られたデータを使用し、コンソールで以下のように入力します。

export AWS_ACCESS_KEY_ID=ASIAJMXB2HHJRVWLFMXQ
export AWS_SECRET_ACCESS_KEY=Q833K5zxeaWiWI1hBbbmfTYF01Px72imuvcGB3TA
export AWS_SESSION_TOKEN=FQoDYXdzENz//////////wEaDKbdCGIckdfH7OdGcyLwAY1V/m+S38Fu/Mh+KHWNbg58kfm7047AAMYZR1kVdFjuvr/idqEHLqkYq3aRWr2G+x1ZM2fQeB0RaHTlNpQ9gxrSFooFRSTSKK3rOpfIHyX9uQN5yPSUotA/At6LRYCqhDEIRsWVYNFFNjjjCgL4N8uyDsZfLcIsZ2zbTV/3oJSOQdm2i07pWwBI5w4J5l4oEgN7vZlq41/HK10jTHKtoYf+xoDUD6fa/R6gUvsphhXJ16Cs9OlhbljvcIweYKrnZCDR/u0GCyLs7PrIyAA1AgEFSqeb3dmI5ONwTpX8GSmLPuTOOfRaHTvbKeI3hyy0hCiJ0ePWBQ==

当然のことながら、1 つ前のコマンドの出力を使用する必要があります。

これで、AWS CLI を使用して何らかのコマンドを入力すると、このユーザーが使用されるようになりました。試しに、コンソールを起動して以下のように入力してみましょう。

aws s3 ls

前の例と同じバケットが表示されれば成功です。

テスト 4 - STS アクセスを自動化する

テスト 3 で実行した内容を自動化する Bash スクリプトを作成します。任意のエディターを開いて、以下のように入力します。

unset  AWS_SESSION_TOKEN

temp_role=$(aws sts assume-role \
                    --role-arn "arn:aws:iam::262170986110:role/circleci_role" \
                    --role-session-name "vgaltes-prod" \
                    --profile vgaltes-serverless)

export AWS_ACCESS_KEY_ID=$(echo $temp_role | jq .Credentials.AccessKeyId | xargs)
export AWS_SECRET_ACCESS_KEY=$(echo $temp_role | jq .Credentials.SecretAccessKey | xargs)
export AWS_SESSION_TOKEN=$(echo $temp_role | jq .Credentials.SessionToken | xargs)

role-arn は委譲を受ける権限、profile は開発プロファイルです。jq をインストールする必要があることに注意してください。

ファイルに名前を付けて (aws-cli-assumerole.sh など)、必要な実行権限を付与し (chmod +x aws-cli-assumerole.sh)、ファイルを実行します (source aws-cli-assumerole.sh)。これで、本番環境の認証情報が設定されたので、以下のように再び S3 バケットを表示してみましょう。

aws s3 ls

本番環境のバケットが出力として返されれば成功です。

CircleCI を使用した本番環境へのデプロイ

それでは、作成したスクリプトを使用して、ソリューションを本番環境にデプロイしましょう。まず、スクリプトをプロジェクトにコピーします。scripts という名前のフォルダー内 (scripts/aws-cli-assumerole.sh) がいいでしょう。そして、本番環境へのデプロイ前にこのスクリプトを呼び出すように、config.yml を更新します。AWS CLI をインストールするために、依存関係セクションの変更が必要になる場合があります。

version: 2
jobs:
  build:
    docker:
      # specify the version you desire here
      - image: circleci/node:8.10

    working_directory: ~/repo

    steps:
      - checkout

      # Download and cache dependencies
      - restore_cache:
          keys:
          - v1-dependencies-
          # fallback to using the latest cache if no exact match is found
          - v1-dependencies-

      - run:
          name: Install Serverless CLI and dependencies
          command: |
            sudo npm i -g serverless
            npm install
            sudo apt-get install awscli

      - save_cache:
          paths:
            - node_modules
          key: v1-dependencies-
        
      # run tests!
      - run: 
          name: Run tests with coverage
          command: npm test --coverage

      - run:
          name: Deploy application
          command: sls deploy --stage pre

      - run:
          name: Deploy application
          command: |
            chmod +x scripts/aws-cli-assumerole.sh
            source scripts/aws-cli-assumerole.sh
            sls deploy --stage prod

この変更をプッシュして、正常に機能するかどうか確認しましょう (注: AWS CLI をインストールする必要があるため、ビルドに少し時間がかかる場合があります。回避策としては、CLI がインストール済みの Docker イメージを使用します。この方法の詳細は CircleCI のドキュメントを参照してください)。

BuildProd1.png

ビルドは問題なさそうです。本番環境アカウントも見てみましょう。

BuildProd2.png

見事に成功しました。

最後の仕上げ

ここまで、本番環境へのデプロイに管理者アカウントを使用してきましたが、あまり良いアイデアとは言えません。カスタム ポリシーを作成して、適切な権限を設定することで、このアカウントの権限を制限する必要があります。設定すべき権限については、Serverless Policy Generator を使用して調べることができます。または、開発アカウントで AWS CloudTrail にアクセスし、CI ユーザー (ここでは circleci) のイベント履歴を確認してください。

CloudTrail.png