ApexでAWS Lambdaファンクションを管理する
以前AWS LambdaファンクションをGulpでデプロイというブログを書きましたが、今回はAWS Lambdaファンクションの管理ツールApexをご紹介したいと思います。
Apexとは
@TJ Holowaychukさんが中心となって開発されている、AWS Lambdaファンクションをビルド、デプロイ、管理するためのツールです。Mediumでも語られていますが、TJ Holowaychukさんはサーバーレスなアーキテクチャが実現できるAWS Lambdaに魅力を感じつつも、AWS Lambdaのユーザビリティの低さに不満を持っており、その問題を解決するためにApexの開発に至ったようです。
Apexの特徴
- AWS Lambdaがネイティブにサポートしていない言語をサポート(本ブログ記事執筆時点ではGolangをサポート)
- バイナリから簡単にインストール可能(CI/CDへの組み込みが容易)
- apexコマンド実行時のフックをサポート(ビルド時にコードをトランスパイルする、デプロイ時にコードの構文チェックを実行する、など)
- "バッテリー同梱"(多くの機能が標準で組み込まれている)、それらの機能を選択して使うことができる
- プロジェクト単位でファンクションとリソースの管理が可能
- 設定の継承、オーバーライドが可能(プロジェクトレベルでの設定を、ファンクションレベルで継承/オーバーライドできる)
- apexコマンド実行時にJSONストリームを渡せる
- 透過的なデプロイパッケージ(zipファイル)の作成(デプロイ実行時に自動的にzipファイルが生成される)
- Terraformのインテグレーション(AWSリソースの作成にTerraformが利用出来る)
- 「.apexignore」で特定ファイルをデプロイパッケージからが除外できる
- ファンクションのロールバックに対応
- ファンクションのログをTailで参照可能
- ファンクションのデプロイの並列実行
- Dry-runでデプロイ前にファンクションの変更箇所を確認可能
- VPCサポート
個人的にはGolangのサポートが目を引きますが、その他にも便利機能が満載です。詳しくは後ほど触れたいと思います。
サポートされているランタイム
本ブログ記事執筆時点の最新版であるv0.7.2でサポートされているランタイムは以下の4つです。
- Node.js
- Golang
- Python
- Java
Apexを使ってみる
それでは実際にApexの使い方について見ていきたいと思います。今回は以下の環境でApexの動作を確認しました。
- OS : OS X Yosemite 10.11.4
- Apex : v0.7.2
インストール
以下を実行すると、/usr/local/bin
ディレクトリ下にapex
のバイナリファイルがインストールされます。
$ curl https://raw.githubusercontent.com/apex/apex/master/install.sh | sh
動作確認のため、apexのバージョンを確認してみましょう。
$ apex version Apex version 0.7.2
アップグレードは以下のコマンドで簡単に実行できます。
$ apex upgrade
OS Xの他、Linux、OpenBSD、Windows用のバイナリが用意されています。
AWSのクレデンシャル
Apexを使うには、ApexにAWSのクレデンシャル情報を渡す必要があります。これにはAWS CLIのそれと同じ方法が使えます。
(1) 環境変数にクレデンシャル情報を設定する
- AWS_ACCESS_KEY アクセスキー
- AWS_SECRET_KEY シークレットアクセスキー
- AWS_REGION リージョン
(2) ~/.aws/config
、~/.aws/credentials
にクレデンシャル情報を記載する
[default] output = json region = ap-northeast-1
[default] aws_access_key_id = XXXXXXXXXX aws_secret_access_key = XXXXXXXXXX
(3) コマンド実行時にプロファイルを指定する
~/.aws/credentials
に[default]以外のプロファイルを設定している場合は、apexコマンド実行時にプロファイルを指定することもできます。この場合リージョンの設定は~/.aws/config
からは読み込まれないようで、あらかじめ環境変数AWS_REGION
で設定しておくか、apexコマンド実行時に--region
フラグで指定する必要があります。
$ apex --profile <プロファイル名> --region <リージョン名> deploy
コマンド実行時に毎回プロファイル名とリージョン名を渡すのは若干面倒なので、複数のクレデンシャル情報を使い分ける場合は、direnvなどを使ってプロジェクトディレクトリ毎に(1)の環境変数を設定するのが良いかと思います。
Apexプロジェクトの作成
v0.7.0でプロジェクトジェネレーターの機能が追加されました。これを使ってApexプロジェクトの雛形を作成します。プロジェクト用のディレクトリを作成&移動し、apex init
コマンドを実行します。
$ mkdir apex-sample $ cd apex-sample $ apex init
始めにProject name
とProject description
を入力します。Project name
はAWS Lambdaファンクションのプレフィックスとして使用されるます(この設定は後で変更することが可能です)。
Enter the name of your project. It should be machine-friendly, as this is used to prefix your functions in Lambda. Project name: apex-sample Enter an optional description of your project. Project description: apex-sample
「Apexの特徴」でも触れましたが、v0.7.0からApexにTerraformがインテグレーションされており、Terraformを使ってAWSリソースを作成できるようになっています(apex infra
というコマンドが追加されており、これがTerraformコマンドのエイリアスのような形になっています)。
以降の入力項目は、Terraformを使う場合と使わない場合とで異なります。なお本ブログ記事ではTerraformについては解説しませんので、Terraformについて知りたい方は以下のブログエントリなどを参照ください。
Terraformを使わない場合
AWS Lambdaファンクションに割り当てるIAMロールの入力を求められるので、あらかじめ作成しておきます。
## no を選択 Would you like to manage infrastructure with Terraform? (yes/no) no ## AWS Lambdaファンクションに割り当てるIAMロールのarnを入力する Enter IAM role used by Lambda functions. IAM role: arn:aws:iam::5xxxxxxxxxxx:role/apex_lambda_execution [+] creating ./project.json [+] creating ./functions Setup complete! Next step: - apex deploy - deploy example function
Terraformを使う場合
前提条件として、事前にTerraformをインストールしておく必要があるのでご注意ください。またTerraformのstateファイルをS3で管理する場合は事前にS3のバケットを作成しておく必要があります。
## yes を選択 Would you like to manage infrastructure with Terraform? (yes/no) yes [+] creating ./project.json [+] creating ./functions [+] creating ./infrastructure [+] fetching modules ## TerraformのstateファイルをS3にストアする場合は yes を選択 Would you like to store Terraform state on S3? (yes/no) yes Enter the S3 bucket name for managing Terraform state (bucket needs to exist). ## stateファイルをS3にストアする場合は、ストア先にS3バケット名を入力(バケットはあらかじめ作成しておく) S3 bucket name: apex-terraform-state [+] setting up remote state in bucket "apex-terraform-state" Setup complete! Next steps: - apex infra plan - show an execution plan for Terraform configs - apex infra apply - apply Terraform configs - apex deploy - deploy example function
プロジェクトディレクトリ
apex init
で作成されたプロジェクトディレクトリを見てみます。
Terraformを使わない場合
. ├── functions │ └── hello │ └── index.js └── project.json
project.json
がプロジェクト全体の設定です。必須なのはname
(プロジェクト名)です。その他メモリやタイムアウト値などのファンクションのデフォルト値をここで定義します。設定可能な項目については公式ドキュメントを参照ください。
{ "name": "apex-sample", "description": "apex-sample", "memory": 128, "timeout": 5, "role": "arn:aws:iam::5xxxxxxxxxxx:role/apex_lambda_execution", "environment": {} }
functions
ディレクトリ以下にファンクション毎にディレクトリを作成し、その中にファンクション本体(index.jsなど)を作成していきます(デフォルトで、サンプルとしてNode.jsのhello
ファンクションが作成されます)。
ファンクション毎の設定はfunction.json
ファイルで定義します。このfunction.json
はファンクション毎のディレクトリ(helloなど)に配置します。必須項目はdescription
、runtime
、memory
、timeout
、role
の5つです。ただしdescription
以外の定義はproject.json
から継承できるので、project.json
で定義済みのデフォルト値を使う場合はfunction.json
での定義は不要です。
project.json
で未定義の項目がある場合、またはproject.json
の定義をオーバーライドした場合はfunction.json
に定義を追加します。
定義できる項目はほぼproject.json
と同じです。こちらも詳しくは公式ドキュメントを参照ください。
{ "description": "Node.js example function", "runtime": "nodejs", "memory": 256, "timeout": 10 }
Terraformを使う場合
. ├── functions │ └── hello │ └── index.js ├── infrastructure │ ├── main.tf │ └── modules │ └── iam │ ├── iam.tf │ └── outputs.tf └── project.json
Terraformを使うことを選択した場合はinfrastructure
ディレクトリが作成されます。中身はまんまTerraformです。デフォルトでCloudWatch Logsへのフルアクセス権限が付与されたIAMロールを作成するためのtfファイルが作成されるので、この状態でapex infra apply
を実行すると新規に「lambda_function」という名前のIAMロールが作成され、outputとしてIAMロールのarnが表示されます。
それ以外のファイルやディレクトリ構成は基本的にTerraformを使わない場合と同じですが、Terraformでロールを新規作成することを想定しているためproject.json
のrole
の定義は手動で追加する必要があるのでご注意ください。
{ "name": "apex-sample", "description": "apex-sample", "memory": 128, "timeout": 5, "environment": {} }
ファンクションのデプロイ
試しにデフォルトで作成されるhello
ファンクションをデプロイしてみます。index.js
を以下のように書き換えます(後ほどファンクションの動作確認をするため)。
console.log('starting function') exports.handle = function(e, ctx) { console.log('processing event: %j', e) ctx.succeed({ hello: e.hello }) }
hello
ディレクトリ下に以下のようなfunction.json
を作成します。
{ "description": "Node.js example function", "runtime": "nodejs" }
apex deploy <ファンクション名>
を実行すると指定したファンクションがデプロイされます。
$ apex deploy hello • creating function function=hello • created alias current function=hello version=1 • function created function=hello name=apex-sample_hello version=1
apex deploy
のようにファンクション名を指定しなかった場合はfunctions
ディレクトリ以下の全てのファンクションがデプロイされます。
ファンクションの実行
apex invoke
でファンクションを実行できます。プロジェクトディレクトリ直下に以下のようなevent.json
を作成して、これを渡してみます。
{ "hello": "world" }
$ apex invoke hello < event.json {"hello":"world"}
このほかにもイベントを渡す方法がいくつかありますので、詳しくは公式ドキュメントを参照ください。
ファンクション一覧の表示
apex list
で、プロジェクトに含まれるファンクションの一覧が表示されます(デプロイされていないものも含めて表示されます)。
$ apex list hello description: Node.js example function runtime: nodejs memory: 128mb timeout: 5s role: arn:aws:iam::551480077585:role/apex_lambda_execution handler: index.handle current version: 1
デプロイされていないファンクションについてはcurrent version
は表示されません。
ファンクションの削除
apex delete <ファンクション名>
でデプロイしたファンクションを削除することができます。ファンクション名を指定しなかった場合は全てのファンクションが削除対象となります。
$ apex delete hello Are you sure? (yes/no) yes • deleting function=hello • function deleted function=hello
ファンクションのビルド
apex deploy
を実行すると自動的にビルド処理(zipファイルの作成)が実行されるので通常はビルド処理を単体で実行する必要はありませんが、作成されたzipファイルの中身を確認したい場合にはapex build
でビルド処理のみを実行します。
$ apex build hello > out.zip
ファンクションのロールバック
ファンクションを以前のバージョンにロールバックしたい場合はapex rollback <ファンクション名> <バージョン番号>
を実行します。バージョン番号を指定しなかった場合は1つ前のバージョンにロールバックされます。
$ apex rollback hello • rolling back function=hello • rollback to version: 9 function=hello • function rolled back current version=9 function=hello
ログの表示
apex logs <ファンクション名>
でファンクションの実行ログを表示できます。ファンクション名を指定しなかった場合は全てのファンクションのログが表示されます。
$ apex logs hello /aws/lambda/apex-sample_hello 2016-03-22T17:51:05.173Z 770qu830atjmfhrq start simple /aws/lambda/apex-sample_hello START RequestId: a6184505-f056-11e5-8fe2-51cd1249c703 Version: 9 /aws/lambda/apex-sample_hello 2016-03-22T17:51:05.209Z a6184505-f056-11e5-8fe2-51cd1249c703 processing event: {"hello":"world"} /aws/lambda/apex-sample_hello END RequestId: a6184505-f056-11e5-8fe2-51cd1249c703 /aws/lambda/apex-sample_hello REPORT RequestId: a6184505-f056-11e5-8fe2-51cd1249c703 Duration: 35.33 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 8 MB
--filter
でログをフィルタリングしたり(例えば--filter error
でerrorのみ表示するなど)、--start
でログの表示期間を指定することも可能です(--start 1h
で直近1時間のログを表示など。デフォルトでは直近5分のログが表示されます)。
メトリクスの表示
apex metrics <ファンクション名>
でファンクションの実行回数、実行時間などのメトリクスを表示することができます。ファンクション名を指定しなかった場合は全てのファンクションのメトリクスが表示されます。
$ apex metrics hello hello invocations: 5 duration: 60ms throttles: 0 error: 0
Dry-run
apexコマンドに--dry-run
フラグをつけると、文字通りdry runが実行可能です。デプロイ前のhello
ファンクションがある状態でapex deploy --dry-run
を実行すると以下のように出力されます。
$ apex deploy hello --dry-run + function apex-sample_hello runtime: nodejs memory: 128 timeout: 5 handler: index.handle + alias apex-sample_hello alias: current version: 1
+
はリソースが作成されることを表します。
ファンクションのデプロイ後にfunction.json
のmemory
の設定を128から256に変えてapex deploy --dry-run
を実行してみます。
$ apex deploy hello --dry-run ~ config apex-sample_hello memory: 128 -> 256 + alias apex-sample_hello alias: current version: $LATEST
~
はリソースが更新されることを表します。
--dry-run
はapex delete
でも使えます。リソースの削除は-
で表されます。
$ apex delete hello --dry-run Are you sure? (yes/no) yes - function apex-sample_hello
フック
以下のフックがサポートされており、apexのライフサイクルの中で任意のコマンドを実行することが可能です。
build
ビルド実行中(使用例:コンパイルを実行する)deploy
デプロイ実行前(使用例:テスト、構文チェック)clean
デプロイ実行後(使用例:ビルドで生成されたファイルを削除する)
これらのフックはproject.json
またはfunction.json
で定義します。具体的な使用方法として、Apexのgithubレポジトリにdeployフックを使用してbrowserifyを実行する例があります。
この例ではproject.json
buildフックでbrowserifyでindex.jsをmain.jsにトランスパイルし、cleanフックでそのmain.jsを削除する、という処理が定義されています。
環境変数
Apexでは環境変数を使うことが可能です。環境変数はファンクションのデプロイ時(apex deploy
実行時)に--env
フラグで定義します。
以下は環境変数「LOGGLY_TOKEN」を定義して「using-env」ファンクションをデプロイする例です。
$ apex deploy --env LOGGLY_TOKEN=15xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx using-env
実態としては、ビルド時に.env.json
と_apex_index.js
が生成され、デプロイパッケージ(zipファイル)に同梱される形となっています。
{"LOGGLY_TOKEN":"15xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}
try { var config = require('./.env.json') for (var key in config) { process.env[key] = config[key] } } catch (err) { // ignore } exports.handle = require('./index').handle
ファンクション内では、Node.jsであればprocess.env.LOGGLY_TOKEN
で、Golangであればos.Getenv("LOGGLY_TOKEN")
で値を取得します。
console.log('start using-env LOGGLY_TOKEN=%s', process.env.LOGGLY_TOKEN) exports.handle = function(e, ctx) { console.log('processing event: %j', e) ctx.succeed({ user_name: process.env.LOGGLY_TOKEN }) }
環境変数は--env
フラグで定義する意外にproject.json
またはfunction.json
のenvironment
で定義することも可能です。
{ "description": "Node.js example function using environment variables", "runtime": "nodejs", "memory": 256, "timeout": 10, "environment": { "LOGGLY_TOKEN": "15xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } }
.apexignore
.apexignore
で、特定のファイルをデプロイパッケージ(zipファイル)から除外することができます。書式は.gitignoreと同じです。例えばGolangのソースファイルをデプロイパッケージから除外するには以下のような.apexignore
を作成して、プロジェクトディレクトリまたはファンクション毎のディレクトリに配置します。
*.go
なお、デフォルトで.apexignore
自身とfunction.json
はデプロイパッケージから除外される設定になっているので.apexignore
で定義する必要はありません。
まとめ
"Batteries included"の言葉通り豊富な機能を持ったApexですが、とてもシンプルで使いやすいと感じました。Terraformとのインテグレーションも今後の進化が楽しみです。
Apexのgithubレポジトリには各ランタイム毎のサンプルプロジェクトがあるので、GolangやTerraformでAPI Gatewayを作成する例も試してみたいと思います。