サーバレスコンピューティングサービスであるAWS Lambdaを使って開発する際に、一つ一つの小さな関数を関連する機能でまとめて、1プロジェクトとして開発する際のであれば、CircleCI のダイナミックコンフィグを使い、追加・修正された関数だけを自動でビルド、テスト、デプロイすることができます。
SAMを使ったLambda関数の開発とは?
AWS Lambdaを使って開発する際に、AWS Serverless Application Model(AWS SAM) というフレームワークを使って、開発効率を高めることができます。AWS SAMを使うことで、Node.jsやPython, Ruby, Go, Java, .NETに対応したコンパイラ言語やスクリプト言語を使って、サーバレスの実装やビルド、デプロイを簡単に行うことができます。
例えば、Javaを使って、sam-java-app
というLambda関数を実装するのであれば、プロジェクトは次のようなディレクトリ構成を取ります。
同様に、Node.jsを使って、sam-nodejs-app
というLambda関数を実装するのであれば、プロジェクトは次のようなディレクトリ構成を取ります。
AWS SAMではコマンドラインから利用可能な、AWS SAM CLIコマンドを提供しており、どちらのプロジェクトであっても、アプリケーションのビルドは sam buildコマンドで、デプロイは sam deploy コマンドで行うことができます。
一方、テストに関しては、Javaの例であれば、HelloWorldFunctionディレクトリに移動し、mvn test
コマンドを実行することで、src/test/java/helloworld/AppTest.java
で定義されたテストを実行することができます。Node.jsの例であれば、hello-worldディレクトリに移動し、npm install
そして npm run test
の順に実行することで、tests/unit/test-handler.js
で定義されたテストを実行することができます。
CircleCIを使ったビルド、テスト、デプロイ
CircleCI が備える Orb (設定を再利用可能な部品として切り出したもの) を使うことで、ビルド、テスト、デプロイの自動化手順を簡潔に記述することができます。
これから、前述のJavaで書かれた sam-java-app
というlambda関数の自動化手順を例にとって、ジョブ build_test_deploy-sam-java-app
ジョブの各ステップを記述していきます。
なお、CircleCIでは設定ファイルをプロジェクトのルートディレクトリにある .circleci
ディレクトリ内の config.yml
ファイルの中に記述してきますが、以下の説明は、sam-java-app
ディレクトリにある .circleci
ディレクトリ内の config.yml
ファイルの内容を基に説明していきます。
初期設定
jobs:
build_test_deploy-sam-java-app:
machine:
image: ubuntu-2004:202010-01
docker_layer_caching: true
steps:
- checkout
- aws-cli/setup:
role-arn: arn:aws:iam::XXXXXXXXXXXX:role/mshk-sam-role
- run:
name: AWS SAM installation
command: |
curl -L https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip -o aws-sam-cli-linux-x86_64.zip
unzip aws-sam-cli-linux-x86_64.zip -d sam-installation
sudo ./sam-installation/install
まずは、sam-nodejs-app
関数のビルド、テスト、デプロイを実行する build_test_deploy-sam-java-app
ジョブを定義します。通常であれば、ジョブ名を build_test_deploy
といった感じにしてしまうのですが、他の関数(sam-nodejs-app)用に用意したジョブ名と同じになってしまうことがないよう、ジョブ名に関数名を付加することで、ユニークな名前になるようにしています。
初期設定では、リポジトリの内容をチェックアウト(checkout)した後、aws-cli Orb を使い、setupコマンド を使って初期設定をしています。この際、(AWSのシークレットを使ってではなく)OpenID Connectトークンを使ってAWSにアクセスするために、必要なIAMロールのARNを指定しています。
次に、AWS SAM CLIのインストールを行なっています。AWS SAM CLIが提供する機能に対応した aws-sam-serverless Orbが用意されており、後ほど使用するのですが、この Orb が提供するinstallコマンド がOpenID Connect に対応していない(つまり、AWS_ACCESS_KEY_ID
やAWS_SECRET_ACCESS_KEY
を必要とする)ため、ここでは手動でインストールを行なっています。
ビルド
- aws-sam/build:
template: sam-java-app/template.yaml
ここでは、AWS SAM CLIのsam build
コマンドに対応する aws-sam-serverless Orb
のbuildコマンドを実行します。
テスト
- run:
name: Test HelloWorldFunction
command: |
cd sam-java-app/HelloWorldFunction
mvn test
mvn test
コマンドを直接実行することで、テスト(具体的には AppTest.javaで定義されたテスト)を実行します。
デプロイ
- aws-sam/deploy:
stack-name: sam-java-app
image-repositories: $AWS_ECR_HOST/mshk-sam-java-app
最後にデプロイです。AWS SAM CLIのsam deploy
コマンドに対応するaws-sam-serverless Orb の deploy コマンドを実行し、CloudFormationのsam-java-app
スタックを作成、または更新します。
変更された関数だけをビルド〜デプロイする
さて、今回開発している sam-java-app関数とsam-nodejs-app関数ですが、常に両方をビルド、テスト、デプロイするのではなく、何らかの変更や更新のあった方だけビルド〜デプロイしたいというのは、当然の希望でしょう。
このような希望に応えるため、CircleCIではダイナミックコンフィグと呼ばれる機能を提供しています。ダイナミック(動的)という名前が示す通り、設定ファイル(コンフィグ)をあらかじめ定義しておいて(静的)実行するのではなく、実際にビルド〜デプロイするための手順をその場で(動的に)設定ファイルとして作成し、作成したファイルに処理を引き継ぐ、という機能です。
処理の引き継ぎ自体は、continuation Orbが提供する機能を使うことで、比較的簡単に行うことができますが、「動的に設定ファイル(コンフィグ)を生成」するところのハードルが高いものでした(必ずしも動的に生成しなくても、複数用意しておいた設定ファイルのうちの、いずれかを選んで処理を引き継ぐことも可能)。
この設定ファイルの生成を用意にするためのOrbとして、弊社のエンジニア @bufferings さんが split-config Orb を開発、公開しました。
設定ファイル(コンフィグ)の生成
- split-config/generate-config:
fixed-config-paths: |
./sam-java-app/.circleci/config.yml
./sam-nodejs-app/.circleci/config.yml
generated-config-path: /tmp/generated_config.yml
continuation: false
post-steps:
- persist_to_workspace:
root: /tmp
paths:
- generated_config.yml
ここでは、sam-java-app
のビルド〜デプロイ用に用意した設定ファイルとsam-nodejs-app
のビルド〜デプロイ用に用意した設定ファイルをマージして、1つのファイル /tmp/generated_config.yml
として出力しています。なお、マージする設定ファイルは、この例のように直接パス名を指定する(fixed-config-paths
)ことも、あるいは正規表現にマッチ(find-config-regex
)させたり、外部ファイルから読み出す(config-list-path
)ことも可能です。
出力した設定ファイル(/tmp/generated_config.yml
)は、別のジョブからも利用できるように、ワークスペースの中に保存しておきます。
追加・変更・修正箇所の検出(パスフィルタリング)
- path-filtering/filter:
workspace_path: /tmp
config-path: /tmp/generated_config.yml
mapping: |
sam-java-app/.* run-build_test_deploy-sam-java-app true
sam-nodejs-app/.* run-build_test_deploy-sam-nodejs-app true
requires:
- split-config/generate-config
ここでは、mapping
の中で、
- 指定された正規表現で示されるパス中のファイルが追加・変更・修正されていれば
- パラメータ(ここでは
run-build_test_deploy-sam-java-app
やrun-build_test_deploy-sam-nodejs-app
)に - 値(ここでは boolean値
true
)
を設定した上で、ワークスペースから読み出された設定ファイル(/tmp/generated_config.yml
)に処理が移ります。
生成された設定ファイルでの継続処理
parameters:
run-build_test_deploy-sam-java-app:
type: boolean
default: false
workflows:
sam-java-app-workflow:
when: << pipeline.parameters.run-build_test_deploy-sam-java-app >>
jobs:
- build_test_deploy-sam-java-app:
context: oidc-aws
設定ファイル(generated_config.yml
)では、パイプラインパラメータ(上の例では run-build_test_deploy-sam-java-app
)が定義されています。継続処理時にこのパラメータが引き渡されると(つまり true
だと)、ワークフロー sam-java-app-workflow
のwhen
節にあるpipeline.parameters.run-build_test_deploy-sam-java-app
がtrue
と評価される結果、build_test_deploy-sam-java-app
ジョブが実行されるわけです。
実行例
sam-java-app
関数とsam-nodejs-app
関数の双方が更新されていると、ワークフロー generate-config
が実行された後、ワークフロー sam-java-app-workflow
と ワークフロー sam-node-js-app-workflow
が並行して実行されます。
![sam-node-js-app-workflow実行]cci-nodejs-app-workflow.jpeg)
一方、sam-java-app
関数だけが更新された場合、ワークフロー generate-config
が実行された後、ワークフロー sam-java-app-workflow
だけが実行されます。
![sam-java-app-workflow]sam-java-app-workflow.jpeg)
おわりに
これまでにCircleCIを使用し、設定ファイル(コンフィグ)を記述したことがある人であれば、今回取り上げた AWS SAM(サーバーレス)の実装にとどまらず、1つのリポジトリの中で、関連する複数のアプリケーションやサービスを追加・変更・修正箇所に応じて、自動的にビルド〜デプロイするようなダイナミックコンフィグを容易に記述することが可能になります。
また、今回は個別の関数が固有の設定ファイルを持っていましたが、それ以外にも、共通して使われる設定ファイルと、アプリやサービス固有の設定ファイルを動的に組み合わせるような用途も考えられます。
長い設定ファイルの見通しを良くするには、Orbによる部品化/再利用も有効な手段ではありますが、ダイナミックコンフィグのように、サブプロジェクト単位で設定ファイルを用意して、必要に応じて組み合わせて利用するといったアプローチも併せてご検討いただければと思います。