lambroll と bash layer で気軽に Lambda shell script を実行する

先日えいやと書いた AWS Lambda のデプロイツール lambroll ですが、これと公開済みの bash layer を使うとかなり気軽に(雑な) shell script を Lambda で実行できて体験がよかったので書いておきます。

AWS Lambda のミニマルなデプロイツール lambroll を書いた - 酒日記 はてな支店


今回はとある理由で ECS のサービス内のタスクを定期的に入れ換えたかったので、aws ecs update-service を一発実行する、という要件。やりたいことはただ aws-cli を一発叩くだけでできるのに、Lambda 化するには Node やら Go やらで SDK 使ってコードを書いて…というのがめんどくさいこと、よくあると思います。

まず適当にディレクトリを掘って、そこで lambroll init で初期化します。

$ mkdir ecs-task-replacer
$ cd ecs-task-replacer
$ lambroll init --function-name ecs-task-replacer
2019/11/13 22:16:21 [info] lambroll v0.2.1
2019/11/13 22:16:23 [info] function ecs-task-replacer is not found
2019/11/13 22:16:23 [info] creating .lambdaignore
2019/11/13 22:16:23 [info] creating function.json
2019/11/13 22:16:23 [info] completed

関数が存在しないので、次のようなスケルトンの function.json が生成されます。

{
  "FunctionName": "ecs-task-replacer",
  "Handler": "index.handler",
  "MemorySize": 128,
  "Role": "arn:aws:iam::123456789012:role/YOUR_LAMBDA_ROLE_NAME",
  "Runtime": "nodejs10.x",
  "Timeout": 3
}

これを bash layer GitHub - gkrizek/bash-lambda-layer: Run Bash scripts in AWS Lambda via Layers を使うように書き換えます。 Layers, MemorySize, Runtime, Role, Timeout を適当に指定。この例のように aws-cli を使う場合、Memory が少ない(CPUも比例して少ない) と aws コマンドを起動するのに大変時間がかかるので、1コアをフルに使える 1.5GB を指定するのがよいです。

{
  "FunctionName": "ecs-task-replacer",
  "Handler": "index.handler",
  "Layers": [
    "arn:aws:lambda:ap-northeast-1:744348701589:layer:bash:8"
  ],
  "MemorySize": 1536,
  "Role": "arn:aws:iam::123456789012:role/lambda-function",
  "Runtime": "provided",
  "Timeout": 30
}

関数本体は index.sh というファイルに、event を jq で処理して aws-cli を叩くだけのを書きます。今回は event で ECS cluster と service を指定できるように {"cluster": "CLUSTER_NAME", "service": "SERVICE_NAME"} という構造にしてみました。(決め打ちでいいならハードコードしちゃってもいいですね)

function handler {
    set -e

    EVENT=$1
    SERVICE=$(echo "$EVENT" | jq -r .service)
    CLUSTER=$(echo "$EVENT" | jq -r .cluster)

    aws ecs update-service \
        --cluster "$CLUSTER" \
        --service "$SERVICE" \
        --force-new-deployment

    echo "{\"success\": true}" >&2
}

lambroll deploy でデプロイします。

$ lambroll deploy
2019/11/14 08:40:52 [info] lambroll v0.2.1
2019/11/14 08:40:52 [info] starting deploy function ecs-task-replacer
2019/11/14 08:40:54 [info] creating zip archive from .
2019/11/14 08:40:54 [info] zip archive wrote 342 bytes
2019/11/14 08:40:54 [info] updating function configuration
2019/11/14 08:40:54 [info] updating function code
2019/11/14 08:41:00 [info] deployed version 2
2019/11/14 08:41:00 [info] updating alias set current to version 2
2019/11/14 08:41:01 [info] alias updated
2019/11/14 08:41:01 [info] completed

手元から実行するには lambroll invoke を使って、標準入力に event を与えます。--log-tail を付けると実行した Lambda のログ(の最後の4KB)が出力されます。

$ echo '{"cluster":"default", "service":"example"}' | lambroll invoke --log-tail
2019/11/14 08:46:35 [info] lambroll v0.2.1
{"success": true}
2019/11/14 08:46:57 [info] StatusCode:200 ExecutionVersion:$LATEST
s reached a steady state."
            },
            {
                "id": "8304a25b-8329-4099-903f-677013206fe8",
                "createdAt": 1572800779.798,
                "message": "(service example) has reached a steady state."
            },
...(略)
        "schedulingStrategy": "REPLICA",
        "enableECSManagedTags": false,
        "propagateTags": "NONE"
    }
}
END RequestId: c821f023-0af2-48e5-ae21-c6f381e9ec65
REPORT RequestId: c821f023-0af2-48e5-ae21-c6f381e9ec65  Duration: 20484.06 ms   Billed Duration: 20600 ms   Memory Size: 128 MB Max Memory Used: 103 MB Init Duration: 44.74 ms
2019/11/14 08:46:57 [info] completed

おしまい。

定期実行するには CloudWatch Events でポチポチとスケジュールを定義してやればよいですね。

最初に IAM role を用意するところと、init で生成された function.json を書き換えるところが少しだけ手間ですが、そのあとは index.sh を書き換えて deploy して invoke するのに1コマンドずつ叩くだけです。

個人的には雑に cron で shell script を定期実行したいだけなのに、コード書いて Lambda 用意してって億劫だなあ、みたいなストレスがだいぶ減る気がします。どうぞお試しください。