スタイル・エッジ・グループ技術ブログ

士業集客支援/コンサルティングのスタイル・エッジ・グループのエンジニアによるブログです。

LocalStackを使ってLambda関数をローカルで検証してみよう


こんにちは。morizoです。
もともとwebアプリケーションエンジニアとして入社しましたが、ココ一年くらいはAWSを利用したサーバ運用やセキュリティ面の強化に取り組んでいます。

さて、チーム内で共有してる手順書をもとに日頃の運用業務を進めていると
作成日の古い手順書がUIの変更に追いついておらず、混乱することが多々あります。
近頃はメンバー内でも「AWS CLIつかっていこう」という機運が高まっておりますが、
あまり気楽に試せる環境がないことが悩みのタネになっています。
そこで、今回はLocalStackを利用してローカル環境でAWSサービスを動かしてみようと思います。
無料版だとCLIの操作のみに制限されているため、CLI操作の練習にもなりますね。
※執筆中にちょうど正式版がリリースされました

LocalStack導入とその動作検証

LocalStackのインストールと起動

githubで公開されていますのでこちらからcloneしてきます。
今回はお試しということもあり、Docker経由で起動します。
また、clone後にdocker-compose.ymlを一部編集しました。(編集箇所にコメントを追加しています)
環境変数についてはこちらにまとまっています。

version: "3.8"

services:
  localstack:
    container_name: "${LOCALSTACK_DOCKER_NAME-localstack_main}"
    image: localstack/localstack
    ports:
      - "127.0.0.1:4566:4566"            # LocalStack Gateway
      - "127.0.0.1:4510-4559:4510-4559"  # external services port range
      - "127.0.0.1:53:53"                # DNS config (only required for Pro)
      - "127.0.0.1:53:53/udp"            # DNS config (only required for Pro)
      - "127.0.0.1:443:443"              # LocalStack HTTPS Gateway (only required for Pro)
    environment:
      - DEBUG=${DEBUG-}
      - PERSISTENCE=${PERSISTENCE-}
     # Lambda関数実行のたびに同じコンテナを再利用する
      - LAMBDA_EXECUTOR=docker-reuse
      - LOCALSTACK_API_KEY=${LOCALSTACK_API_KEY-}  # only required for Pro
      - DOCKER_HOST=unix:///var/run/docker.sock
     # Lambda関数をコンテナにマウントすることで処理が早くなる場合がある(ローカル環境向け)
      - LAMBDA_REMOTE_DOCKER=0
    volumes:
      - "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"

どのようなことができる?

ここから実際にコンテナに入って作業をしてみます。
コンテナ内ではawslocalコマンドが使用できるので簡単のために使用しています。
まずはs3バケットを作成してみました。
※以後コマンドの行頭に実行環境を補足しています

(host) docker exec -it localstack_main bash

(container) awslocal s3api create-bucket --bucket morizo-s3
{
    "Location": "/morizo-s3"
}
(container) awslocal s3api list-buckets
{
    "Buckets": [
        {
            "Name": "morizo-s3",
            "CreationDate": "2022-07-ddThh:00:00.000Z"
        }
    ],
    "Owner": {
        "DisplayName": "webfile",
        "ID": "XXXXXXXXXXXXXXXXXXXXXXXXXXX"
    }
}

できていますね。
続いてLambdaを実行してみます。
以下ファイルをローカルで用意してコンテナ内にコピーしました。

def handler(event, context):
    print(event)
    return 'localstackはじめました'

作成したファイルをzip形式に圧縮して、Lambda関数を作成します。
※roleに指定する値は任意で問題ありません。

(container) zip localstack.zip localstack.py
(container) awslocal lambda create-function --function-name localstack --runtime python3.8 --role morizo --handler localstack.handler --zip-file fileb://localstack.zip
{
    "FunctionName": "localstack",
    "FunctionArn": "arn:aws:lambda:us-east-1:000000000000:localstack",
    "Runtime": "python3.8",
    "Role": "morizo",
    "Handler": "handler.handler",
    ~
    "LastUpdateStatus": "Successful",
    "PackageType": "Zip",
    "Architectures": [
        "x86_64"
    ]
}

Successfulとあるのでどうやらうまくいったようです。
それでは実行してみます。

(container) awslocal lambda invoke --function-name localstack output.log
{
    "StatusCode": 200,
    "LogResult": "",
    "ExecutedVersion": "$LATEST"
}

200で返ってきました。
中身をみてみましょう。

(container) cat output.log
"localstack\u306f\u3058\u3081\u307e\u3057\u305f"
(container) echo -e `cat output.log`
"localstackはじめました"

unicode表示になっていたのでcatだけではうまく表示できませんでしたが、
コード内でreturnした文字列がファイルに出力されていることを確認できました。
簡単な関数なら充分やっていけそうです。

ほかのサービスとも組み合わせてみたい

固定の文字列を返すだけでは少し味気ないので、
ほかのサービスを組み合わせた処理を書いてみます。
「cloudwatch logsに保管したログをs3にエクスポート」してみましょう。

まずはlogsにlog groupとlog streamを追加します。

(container) awslocal logs create-log-group --log-group-name morizo-logs
(container) awslocal logs create-log-stream --log-group-name morizo-logs --log-stream-name morizo-logs-stream

logsに追加するログはこちらです。

{
  "logEvents":[
    {
      "timestamp": 1658746080711,
      "message": "Example Event 1"
    },
    {
      "timestamp": 1658746081711,
      "message": "Example Event 2"
    },
    {
      "timestamp": 1658746082711,
      "message": "Example Event 3"
    }
  ]
}

こちらのログをlogsに作成したlogstreamに追加します。

(container) awslocal logs put-log-events --log-group-name morizo-logs --log-stream-name morizo-logs-stream --cli-input-json file://logs.json
(container) awslocal logs get-log-events --log-group-name  morizo-logs --log-stream-name morizo-logs-stream
{
    "events": [
        {
            "timestamp": 1658746080711,
            "message": "Example Event 1",
            "ingestionTime": 1658758594869
        },
        {
            "timestamp": 1658746081711,
            "message": "Example Event 2",
            "ingestionTime": 1658758594869
        },
        {
            "timestamp": 1658746082711,
            "message": "Example Event 3",
            "ingestionTime": 1658758594869
        }
    ],
    "nextForwardToken": "f/00000000000000000000000000000000000000000000000000000001",
    "nextBackwardToken": "b/00000000000000000000000000000000000000000000000000000000"
}

こちらも無事に追加できました。
ではこのログをs3にエクスポートするLambda関数を実行してみましょう。
以下pythonコードを用意しました。

import boto3

def handler(event, context):
    client = boto3.client('logs', endpoint_url='http://host.docker.internal:4566', region_name = "us-east-1", aws_access_key_id='dummy', aws_secret_access_key='dummy')
    return client.create_export_task(
        logGroupName      = 'morizo-logs',
        fromTime          = 1658746080000,
        to                = 1658750400000,
        destination       = 'morizo-s3',
        destinationPrefix = 'localstack'
    )

挙動確認用に使用したLambda関数と同様にzipファイルを作成して登録・実行します

(container) awslocal lambda create-function --function-name create_export_task --runtime python3.8 --role morizo --handler create_export_task.handler --zip-file fileb://create_export_task.zip
(container) awslocal lambda invoke --function-name create_export_task output.log
(container) cat output.log | jq
{
  "ResponseMetadata": {
    "HTTPHeaders": {
      "access-control-allow-headers": "authorization,cache-control,content-length,content-md5,content-type,etag,location,x-amz-acl,x-amz-content-sha256,x-amz-date,x-amz-request-id,x-amz-security-token,x-amz-tagging,x-amz-target,x-amz-user-agent,x-amz-version-id,x-amzn-requestid,x-localstack-target,amz-sdk-invocation-id,amz-sdk-request",
      "access-control-allow-methods": "HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH",
      "access-control-allow-origin": "*",
      "access-control-expose-headers": "etag,x-amz-version-id",
      "content-length": "50",
      "content-type": "application/x-amz-json-1.1",
      "date": "aaa, dd Jul 2022 00:00:00 GMT",
      "server": "amazon.com, hypercorn-h11"
    },
    "HTTPStatusCode": 200,
    "RetryAttempts": 0
  },
  "taskId": "XXXXXXXXXXXXXXXXXXXXXXXXX"
}

ステータスコード200とtaskidが返ってきました!
確認のためにs3バケットをみてみます。

(container) awslocal s3 ls s3://morizo-s3 --recursive # 表示なし

おや、ステータスは200にはなっているけどオブジェクトがありません。
作成していないロググループやバケット名を指定するとエラーになるため、
コマンド自体は成功していますがログファイルがS3にエクスポートされていないようです。
こちらについては次回以降改めて挑戦してみます。

まとめ

さらなる調査は必要ですが、LocalStackを使ってDockerコンテナ上でLambda関数を実行するところまでできました。
無料版でも利用できるAWSサービスが数多くありますし、
業務で実際に活用できるLambda関数の動作確認をローカル環境でも一通りできるのではないでしょうか?
正式版リリースによって今後更に勢いづいてくるかと思いますので
更新情報を追っていきつつ、よりよいローカルでの開発・検証環境構築も進めていきたいです。

おわりに

☆スタイル・エッジ・グループでは、一緒に働く仲間を募集しています☆
もし興味を持っていただけましたら、採用サイト↓も覗いてみてください!
recruit.styleedge-labo.co.jp