Apex
docker
jq

apex で作成した Lambda Function を docker-lambda 上で動作させるシェルスクリプト

目的

apex で作成した Lambda function (python 2.7) をapex deploy
前に docker-lambda コンテナ上で動作確認するためのスクリプトを作る。

必要なもの(前提)

  • apex
    • AWS Lambda のコードを apex で管理しているものとする
  • docker
  • jq
  • AWS CLI の実行環境設定
    • AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
    • 環境変数にセット済み

動機

職場で運用中のAWSサービスの状態通知を行うため、AWS Lambda を活用しています。
Lambda function の作成/デプロイには apex を使用していますが、以下のように考えました。

  1. apex deploy 前に, apex で作成した Lambda function の 動作テストを行いたい
  2. AWS Lambda の動作環境を複製した docker-lambda イメージがある
  3. docker-lambda コンテナ上で動作させ、通知先サービス(Slack等) に通知テストができそう
  4. どの function 向けにも使えるよう, docker-lambda コンテナを実行するスクリプトを書けば良いのでは?

作成

docker-lambda の Project Page にある Example を参考に, まず以下のように作成。

docker-lambda.sh(修正前)
#!/bin/bash

SAMPLE_MESSAGE=`cat sample/test.json` # json形式のテストメッセージ

docker run --rm \
    -e AWS_ACCESS_KEY_ID \
    -e AWS_SECRET_ACCESS_KEY \
    -e SLACK_OPS_CHANNEL="#channel-name" \
    -e SLACK_HOOK_URL="https://hooks.slack.com/services/xxx/xxx/xxx" \
    -v $(pwd):/var/task lambci/lambda:python2.7 main.handle $SAMPLE_MESSAGE

-e オプションで環境変数を docker-lambda コンテナへ渡していますが、 apex では
AWS Lambda へ渡す環境変数を function.json で既に持っています。

function.json
{
  "description": "Notify the state of the instance to Slack",
  "timeout": 10,
  "memory": 128,
  "role": "arn:aws:iam::xxxxxxxxxxxx:role/LambdaEC2Readonly",
  "environment": {
      "SLACK_OPS_CHANNEL": "#channel-name",
      "SLACK_HOOK_URL": "https://hooks.slack.com/services/xxx/xxx/xxxxxxxx"
  }
}

これを読み込むため、今回は jq を使って シェルスクリプトでdocker に渡す ことにしました。

jq の処理

参考にしたのはこちらの記事です。

まずは実行結果から。

実行結果
% jq -r '.environment | to_entries | map("\(.key)=\(.value)") | join("\n")' function.json
SLACK_OPS_CHANNEL=#channel-name
SLACK_HOOK_URL=https://hooks.slack.com/services/xxx/xxx/xxxxxxxx

オプション解説

次に引数指定したオプションを、順を追って見ていきます。

  • .environment ... json の environment キーの値を読み込み
部分実行その1
% jq -r '.environment' function.json
{
  "SLACK_OPS_CHANNEL": "#channel-name",
  "SLACK_HOOK_URL": "https://hooks.slack.com/services/xxx/xxx/xxxxxxxx"
}
  • to_entries ... key, value の各値を持った ハッシュ配列に変換
部分実行その2
% jq -r '.environment | to_entries' function.json
[
  {
    "key": "SLACK_OPS_CHANNEL",
    "value": "#channel-name"
  },
  {
    "key": "SLACK_HOOK_URL",
    "value": "https://hooks.slack.com/services/xxx/xxx/xxxxxxxx"
  }
]
  • map("\(.key)=\(.value)") ... ハッシュ配列を .key=.value 形式の配列に変換
部分実行その3
% jq -r '.environment | to_entries | map("\(.key)=\(.value)")' function.json
[
  "SLACK_OPS_CHANNEL=#channel-name",
  "SLACK_HOOK_URL=https://hooks.slack.com/services/xxx/xxx/xxxxxxxx"
]
  • join("\n") ... 配列を改行コード区切りの文字列に変換
部分実行その4(実行結果に同じ)
% jq -r '.environment | to_entries | map("\(.key)=\(.value)") | join("\n")' function.json
SLACK_OPS_CHANNEL=#channel-name
SLACK_HOOK_URL=https://hooks.slack.com/services/xxx/xxx/xxxxxxxx

変換後の文字列は、スクリプトでの変数宣言と同じ形にしました。
これをファイルにリダイレクトすると、 後述の方法で docker から読み込むことができます。

docker --env-file オプション

先ほどの jq 実行結果をさらに、 .tmp.list にリダイレクトしておきます。

% jq -r '~(中略)~' function.json > .tmp.list
.tmp.list
SLACK_OPS_CHANNEL=#channel-name
SLACK_HOOK_URL=https://hooks.slack.com/services/xxx/xxx/xxxxxxxx

docker--env-file オプションを使うことで、 .tmp.list 内の文字列を、 dockerの環境変数として 読み込むことができます。

オプション指定例
% MESSAGE=`cat sample/test.json` && docker run --rm \
    -e AWS_ACCESS_KEY_ID \
    -e AWS_SECRET_ACCESS_KEY \
    --env-file .tmp.list \
    -v $(pwd):/var/task lambci/lambda:python2.7 main.handle $MESSAGE

※便宜上、メッセージデータとなるjson を環境変数に入れる処理を行っています

詳しくは以下のドキュメントをご覧ください。

シェルスクリプト修正後

これらを踏まえて、最終的に以下のような形になりました。

docker-lambda.sh(修正後)
#!/bin/bash

set -e

cd $(dirname "$0")

create_env_file() {
    local env_json="function.json"

    jq -r '.environment | to_entries | map("\(.key)=\(.value)") | join("\n")' $env_json > .tmp.list
}

remove_env_file() {
    rm .tmp.list
}

create_env_file

docker run --rm \
  -e AWS_ACCESS_KEY_ID \
  -e AWS_SECRET_ACCESS_KEY \
  --env-file .tmp.list \
  -v $(pwd):/var/task lambci/lambda:python2.7 main.handle "$@"

remove_env_file

実行例

目的の apex ソースディレクトリ内に docker-lambda.sh を置いて、以下のように実行してください。

% MESSAGE=`cat ./sample_messages/test.json` && ./docker-lambda.sh $MESSAGE