CLOVER🍀

That was when it all began.

LocalStack内のAWS Lambda関数から、LocalStackで動作している別のAWSリソースにアクセスするにはLOCALSTACK_HOSTNAME環境変数を使う

これは、なにをしたくて書いたもの?

AWS Lambda関数を試す時にLocalStackをよく使うのですが、LocalStack内で動作するAWS Lambda関数内から、同じLocalStack上の
リソースにアクセスする際の方法をよく忘れるのでメモしておくことにしました。

localhostでアクセスするわけじゃないんですよね。

結論を言うと、LOCALSTACK_HOSTNAMEという環境変数を使います。

LOCALSTACK_HOSTNAME環境変数

LOCALSTACK_HOSTNAME環境変数については以下に説明があり、AWS Lambda関数内から他のサービスにアクセスするための
エンドポイントとしてのホスト名が格納された環境変数とされています。

LOCALSTACK_HOSTNAME。

Deprecated. Name of the host where LocalStack services are available. Use this hostname as endpoint in order to access the services from within your Lambda functions (e.g., to store an item to DynamoDB or S3 from a Lambda). This option is read-only. Use LOCALSTACK_HOST instead.

Configuration / Deprecated

一方でこの環境変数は非推奨扱いになっていて、LOCALSTACK_HOST環境変数を代わりに使うこと、となっています。

LOCALSTACK_HOST。

This is interpolated into URLs and addresses that are returned by LocalStack. It has the form :.

Configuration / Core

なのですが、実際に使ってみたらLocalStack 2.1.0の時点ではLOCALSTACK_HOST環境変数には値が入っていませんでした…。
今回のエントリーではLOCALSTACK_HOSTNAME環境変数を使うことにします。

ちなみに、LOCALSTACK_HOSTNAME環境変数に入っているのはホスト名ですが、ポートについてはEDGE_PORT環境変数を
参照すればよさそうです。

EDGE_PORT。

Deprecated. Port number for the edge service, the main entry point for all API invocations.

Configuration / Deprecated

まあ、EDGE_PORT環境変数も非推奨なのですが…。

非推奨なのはさておき、現時点ではLOCALSTACK_HOSTNAME環境変数とEDGE_PORT環境変数を使っていってみましょう。

お題

今回のお題は、以下とします。

  • Amazon SQSキューをサブスクライブするAWS Lambda関数を作成し、LocalStack内で動作させる
  • 作成したAWS Lambda関数からは、Amazon S3バケットにデータをアップロードする
  • テストでの確認のために、LocalStackにアクセスしてテストするものと、AWS SDK v3 Client mockを使ってテストするものの2種類を用意する

環境

今回の環境は、こちら。

$ python3 -V
Python 3.10.6


$ localstack --version
2.1.0

LocalStackを起動。

$ LAMBDA_EXECUTOR=docker-reuse localstack start

AWS CLI。

$ awslocal --version
aws-cli/2.12.6 Python/3.11.4 Linux/5.15.0-76-generic exe/x86_64.ubuntu.22 prompt/off

LocalStack向けのものですが。

AWS Lambda関数を作成する

まずはAWS Lambda関数を作成します。AWS Lambda関数はTypeScript+Serverless Frameworkを使って作成することにします。

Node.jsプロジェクトの作成と、TypeScript、Serverless Frameworkのインストール。

$ npm init -y
$ npm i -D typescript
$ npm i -D prettier
$ npm i -D serverless

Serverless Frameworkでaws-nodejsをテンプレートにサービス作成。

$ npx serverless create --template aws-nodejs

Node.jsとAWS Lambda関数の型宣言のインストール。

$ npm i -D @types/node@v18
$ npm i -D @types/aws-lambda

Serverless Frameworkのプラグインをインストール。

$ npm i -D serverless-esbuild esbuild
$ npm i -D serverless-localstack

Amazon S3にアクセスするためのクライアントライブラリをインストール。

$ npm i @aws-sdk/client-s3

テストライブラリをインストール。

$ npm i -D jest @types/jest
$ npm i -D esbuild-jest
$ npm i -D aws-sdk-client-mock aws-sdk-client-mock-jest

依存関係は、このようになりました。

  "devDependencies": {
    "@types/aws-lambda": "^8.10.119",
    "@types/jest": "^29.5.2",
    "@types/node": "^18.16.19",
    "aws-sdk-client-mock": "^3.0.0",
    "aws-sdk-client-mock-jest": "^3.0.0",
    "esbuild": "^0.17.19",
    "esbuild-jest": "^0.5.0",
    "jest": "^29.5.0",
    "prettier": "^2.8.8",
    "serverless": "^3.33.0",
    "serverless-esbuild": "^1.45.1",
    "serverless-localstack": "^1.1.1",
    "typescript": "^5.1.6"
  },
  "dependencies": {
    "@aws-sdk/client-s3": "^3.363.0"
  }

scripts。

  "scripts": {
    "build:watch": "tsc --project . --watch",
    "format": "prettier --write **/*.ts",
    "test": "jest"
  },

設定ファイル。

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "commonjs",
    "moduleResolution": "node",
    "lib": ["esnext"],
    "baseUrl": "./",
    "noEmit": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitOverride": true,
    "noImplicitReturns": true,
    "noPropertyAccessFromIndexSignature": true,
    "esModuleInterop": true
  },
  "include": ["./**/*.ts"],
  "exclude": [
    "node_modules/**/*",
    ".serverless/**/*"
  ]
}

.prettierrc.json

{
  "singleQuote": true,
  "printWidth": 120
}

jest.config.js

module.exports = {
  testEnvironment: 'node',
  transform: {
    '^.+\\.tsx?$': 'esbuild-jest',
  },
};

生成されたソースコードは削除しておきます。

$ rm handler.js

作成したAWS Lambda関数は、こちら。Amazon SQSキューをサブスクライブするものとして作成しています。

subscribe-sqs/handler.ts

import { SQSEvent } from 'aws-lambda';
import { s3Client } from './clients';
import { CreateBucketCommand, HeadBucketCommand, NotFound, PutObjectCommand } from '@aws-sdk/client-s3';

export const handler = async (event: SQSEvent): Promise<void> => {
  console.log(`env[LOCALSTACK_HOST] => ${process.env['LOCALSTACK_HOST']}`);
  console.log(`env[LOCALSTACK_HOSTNAME] => ${process.env['LOCALSTACK_HOSTNAME']}`);
  console.log(`env[S3_ENDPOINT] => ${process.env['S3_ENDPOINT']}`);

  const bucketName = 'my-bucket';

  try {
    await s3Client.send(new HeadBucketCommand({ Bucket: bucketName }));
  } catch (e) {
    if (e instanceof NotFound) {
      await s3Client.send(new CreateBucketCommand({ Bucket: bucketName }));
    }
  }

  for (const record of event.Records) {
    const messageId = record.messageId;
    const message = record.body;

    await s3Client.send(new PutObjectCommand({ Bucket: bucketName, Key: messageId, Body: message }));

    console.log(`put object => ${JSON.stringify({ Bucket: bucketName, Key: messageId, Body: message })}`);
  }
};

Amazon S3バケットが存在していなかったら、まず作成。

  const bucketName = 'my-bucket';

  try {
    await s3Client.send(new HeadBucketCommand({ Bucket: bucketName }));
  } catch (e) {
    if (e instanceof NotFound) {
      await s3Client.send(new CreateBucketCommand({ Bucket: bucketName }));
    }
  }

その後、Amazon SQSから受け取ったメッセージの内容をAmazon S3バケットにオブジェクトとしてアップロードする流れにしています。

  for (const record of event.Records) {
    const messageId = record.messageId;
    const message = record.body;

    await s3Client.send(new PutObjectCommand({ Bucket: bucketName, Key: messageId, Body: message }));

    console.log(`put object => ${JSON.stringify({ Bucket: bucketName, Key: messageId, Body: message })}`);
  }

Amazon S3へアクセスするためのクライアントですが、以下のようにしました。

subscribe-sqs/clients.ts

import { S3Client } from '@aws-sdk/client-s3';

const s3Endpoint = [
  process.env['LOCALSTACK_HOST'] ? `http://${process.env['LOCALSTACK_HOST']}:${process.env['EDGE_PORT']}` : undefined,
  process.env['LOCALSTACK_HOSTNAME']
    ? `http://${process.env['LOCALSTACK_HOSTNAME']}:${process.env['EDGE_PORT']}`
    : undefined,
  process.env['S3_ENDPOINT']!,
].find((v) => v !== undefined);

export const s3Client = new S3Client({
  endpoint: s3Endpoint,
});

環境変数LOCALSTACK_HOST、LOCALSTACK_HOSTNAMEのどちらかがあったら、EDGE_PORTと合わせてエンドポイント
とします。
S3_ENDPOINTというのは、自前で用意するJestでのテストで使う環境変数です。

実際の実行時に、これらがどのような値になるかはAWS Lambda関数内でログ出力するようにしています。

  console.log(`env[LOCALSTACK_HOST] => ${process.env['LOCALSTACK_HOST']}`);
  console.log(`env[LOCALSTACK_HOSTNAME] => ${process.env['LOCALSTACK_HOSTNAME']}`);
  console.log(`env[S3_ENDPOINT] => ${process.env['S3_ENDPOINT']}`);

Serverless Frameworkの設定ファイルは、こちら。

serverless.yml

service: access-aws-resource-in-localstack-lambda

frameworkVersion: "3"

provider:
  name: aws
  runtime: nodejs18.x
  stage: dev
  region: us-east-1

package:
  individually: true

functions:
  subscribeSqs:
    handler: subscribe-sqs/handler.handler
    events:
      - sqs:
          arn: !GetAtt MyQueue.Arn

resources:
  Resources:
    MyQueue:
      Type: AWS::SQS::Queue
      Properties:
        QueueName: my-queue

custom:
  esbuild:
    bundle: true
    target: node18
    platform: node

plugins:
  - serverless-esbuild
  - serverless-localstack

Amazon SQSキューは、この中で作成します。

デプロイ。

$ npx serverless deploy

作成されたAmazon SQSキューに対して、メッセージを送信。

$ QUEUE_URL=$(awslocal sqs list-queues --query 'QueueUrls[-1]' --output text)
$ awslocal sqs send-message --queue-url $QUEUE_URL --message-body 'Hello LocalStack!!'

Amazon CloudWatch Logsを確認してみます。

$ awslocal logs tail /aws/lambda/access-aws-resource-in-localstack-lambda-dev-subscribeSqs
2023-07-02T13:05:38.177000+00:00 2023/07/02/[$LATEST]bca1008fdc670ed62f6482ac15e3eb88 START RequestId: d70805e7-e2b7-43fc-bf98-7c695e73d35c Version: $LATEST
2023-07-02T13:05:38.181000+00:00 2023/07/02/[$LATEST]bca1008fdc670ed62f6482ac15e3eb88 2023-07-02T13:05:37.991Z  d70805e7-e2b7-43fc-bf98-7c695e73d35c    INFO    env[LOCALSTACK_HOST] => undefined
2023-07-02T13:05:38.185000+00:00 2023/07/02/[$LATEST]bca1008fdc670ed62f6482ac15e3eb88 2023-07-02T13:05:37.992Z  d70805e7-e2b7-43fc-bf98-7c695e73d35c    INFO    env[LOCALSTACK_HOSTNAME] => 172.17.0.2
2023-07-02T13:05:38.189000+00:00 2023/07/02/[$LATEST]bca1008fdc670ed62f6482ac15e3eb88 2023-07-02T13:05:37.992Z  d70805e7-e2b7-43fc-bf98-7c695e73d35c    INFO    env[S3_ENDPOINT] => undefined
2023-07-02T13:05:38.193000+00:00 2023/07/02/[$LATEST]bca1008fdc670ed62f6482ac15e3eb88 2023-07-02T13:05:38.150Z  d70805e7-e2b7-43fc-bf98-7c695e73d35c    INFO    put object => {"Bucket":"my-bucket","Key":"56b7b34a-0b4a-4c2b-9e9c-2d93c158e89e","Body":"Hello LocalStack!!"}
2023-07-02T13:05:38.197000+00:00 2023/07/02/[$LATEST]bca1008fdc670ed62f6482ac15e3eb88 END RequestId: d70805e7-e2b7-43fc-bf98-7c695e73d35c
2023-07-02T13:05:38.201000+00:00 2023/07/02/[$LATEST]bca1008fdc670ed62f6482ac15e3eb88 REPORT RequestId: d70805e7-e2b7-43fc-bf98-7c695e73d35c    Duration: 163.37 ms     Billed Duration: 164 msMemory Size: 1024 MB    Max Memory Used: 1024 MB

よく見ると、LOCALSTACK_HOST環境変数には値が入っていません…。LOCALSTACK_HOSTNAME環境変数には値が入っています。
ドキュメント上はLOCALSTACK_HOSTNAME環境変数は非推奨なのですが…どうなっているのでしょう?

43fc-bf98-7c695e73d35c    INFO    env[LOCALSTACK_HOST] => undefined
2023-07-02T13:05:38.185000+00:00 2023/07/02/[$LATEST]bca1008fdc670ed62f6482ac15e3eb88 2023-07-02T13:05:37.992Z  d70805e7-e2b7-43fc-bf98-7c695e73d35c    INFO    env[LOCALSTACK_HOSTNAME] => 172.17.0.2

なお、取得できている値はコンテナのIPアドレスなのです。LocalStackだからlocalhost:4566でアクセスできるという想定で
値をハードコードしてしまうと、ここでハマることになります。

それはそうと、受信したメッセージの内容がAmazon S3にアップロードされたはずなので、そちらも確認します。

$ awslocal s3 ls my-bucket/
2023-07-02 22:05:38         18 56b7b34a-0b4a-4c2b-9e9c-2d93c158e89e


$ awslocal s3 cp s3://my-bucket/56b7b34a-0b4a-4c2b-9e9c-2d93c158e89e -
Hello LocalStack!!

OKですね。これで、LocalStack内で動作するAWS Lambda関数内から、別のリソースにアクセスする際には
LOCALSTACK_HOSTNAME環境変数を活用するとよさそうなことがわかりました。

テストを書く

続いては、テストを書いていきたいと思います。

まずは、LocalStackにアクセスする前提でテストを書いてみます。

subscribe-sqs/handler-with-localstack.test.ts

import { SQSEvent } from 'aws-lambda';
import { handler } from './handler';
import { s3Client } from './clients';
import { GetObjectCommand } from '@aws-sdk/client-s3';

test('lambda test with LocalStack', async () => {
  const payload: SQSEvent = JSON.parse(`
{
  "Records": [
    {
      "messageId": "059f36b4-87a3-44ab-83d2-661975830a7d",
      "receiptHandle": "AQEBwJnKyrHigUMZj6rYigCgxlaS3SLy0a...",
      "body": "{\\"message\\": \\"Hello\\"}",
      "attributes": {
        "ApproximateReceiveCount": "1",
        "SentTimestamp": "1545082649183",
        "SenderId": "AIDAIENQZJOLO23YVJ4VO",
        "ApproximateFirstReceiveTimestamp": "1545082649185"
      },
      "messageAttributes": {},
      "md5OfBody": "098f6bcd4621d373cade4e832627b4f6",
      "eventSource": "aws:sqs",
      "eventSourceARN": "arn:aws:sqs:us-east-2:123456789012:my-queue",
      "awsRegion": "us-east-2"
    }
  ]
}
`);

  await handler(payload);

  const s3Object = await s3Client.send(
    new GetObjectCommand({ Bucket: 'my-bucket', Key: '059f36b4-87a3-44ab-83d2-661975830a7d' })
  );

  expect(await s3Object.Body?.transformToString('utf-8')).toBe('{"message": "Hello"}');
});

AWS Lambda関数として受け取るイベントデータを渡して、関数を呼び出します。最後にAmazon S3バケットにアップロードされた
オブジェクトを取得しています。

イベントのデータは、こちらで作成して少し加工しました。

$ npx serverless generate-event -t aws:sqs -b '{"message": "Hello"}' | jq
{
  "Records": [
    {
      "messageId": "059f36b4-87a3-44ab-83d2-661975830a7d",
      "receiptHandle": "AQEBwJnKyrHigUMZj6rYigCgxlaS3SLy0a...",
      "body": "{\"message\": \"Hello\"}",
      "attributes": {
        "ApproximateReceiveCount": "1",
        "SentTimestamp": "1545082649183",
        "SenderId": "AIDAIENQZJOLO23YVJ4VO",
        "ApproximateFirstReceiveTimestamp": "1545082649185"
      },
      "messageAttributes": {},
      "md5OfBody": "098f6bcd4621d373cade4e832627b4f6",
      "eventSource": "aws:sqs",
      "eventSourceARN": "arn:aws:sqs:us-east-2:123456789012:my-queue",
      "awsRegion": "us-east-2"
    }
  ]
}

ところで、ソースコードとしてこう書いただけだとクレデンシャルがないので動作しません。

今回はscripts内に環境変数を定義することにしました。

  "scripts": {
    "build:watch": "tsc --project . --watch",
    "format": "prettier --write **/*.ts",
    "test": "AWS_ACCESS_KEY_ID=mock_access_key_id AWS_SECRET_ACCESS_KEY=mock_secret_access_key AWS_REGION=us-east-1 S3_ENDPOINT=http://127.0.0.1:4566 jest"
  },

LocalStackのエンドポイントは、S3_ENDPOINT=http://127.0.0.1:4566として指定してあります。ちなみに、http://localhost:4566だと
以下のようなエラーになってうまく動きませんでした…。

    AWS SDK error wrapper for Error: connect ECONNREFUSED ::1:4566

今回は直接記述しましたが、場合によってはdotenvを使ったりするのでしょう。

もうひとつは、AWS SDK v3 Client mockを使った場合。

subscribe-sqs/handler-with-mock.test.ts

import { mockClient } from 'aws-sdk-client-mock';
import 'aws-sdk-client-mock-jest';
import { HeadBucketCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { SQSEvent } from 'aws-lambda';
import { handler } from './handler';

const s3Mock = mockClient(S3Client);

beforeEach(() => {
  s3Mock.reset();
});

test('lambda test with AWS SDK v3 Client mock', async () => {
  s3Mock.on(HeadBucketCommand).resolves({});
  s3Mock.on(PutObjectCommand).resolves({});

  const payload: SQSEvent = JSON.parse(`
{
  "Records": [
    {
      "messageId": "059f36b4-87a3-44ab-83d2-661975830a7d",
      "receiptHandle": "AQEBwJnKynrHigUMZj6rYigCgxlaS3SLy0a...",
      "body": "{\\"message\\": \\"Hello\\"}",
      "attributes": {
        "ApproximateReceiveCount": "1",
        "SentTimestamp": "1545082649183",
        "SenderId": "AIDAIENQZJOLO23YVJ4VO",
        "ApproximateFirstReceiveTimestamp": "1545082649185"
      },
      "messageAttributes": {},
      "md5OfBody": "098f6bcd4621d373cade4e832627b4f6",
      "eventSource": "aws:sqs",
      "eventSourceARN": "arn:aws:sqs:us-east-2:123456789012:my-queue",
      "awsRegion": "us-east-2"
    }
  ]
}
`);

  await handler(payload);

  expect(s3Mock).toReceiveNthCommandWith(1, HeadBucketCommand, { Bucket: 'my-bucket' });
  expect(s3Mock).toReceiveNthCommandWith(2, PutObjectCommand, {
    Bucket: 'my-bucket',
    Key: '059f36b4-87a3-44ab-83d2-661975830a7d',
    Body: '{"message": "Hello"}',
  });
});

こちらは、AWS SDK for JavaScript v3のクライアントをモックにするので、LocalStackがなくても動作させられます。

確認。

$ npm run test

> access-aws-resource-in-localstack-lambda@1.0.0 test
> AWS_ACCESS_KEY_ID=mock_access_key_id AWS_SECRET_ACCESS_KEY=mock_secret_access_key AWS_REGION=us-east-1 S3_ENDPOINT=http://127.0.0.1:4566 jest

 PASS  subscribe-sqs/handler-with-localstack.test.ts
  ● Console

    console.log
      env[LOCALSTACK_HOST] => undefined

      at handler (subscribe-sqs/handler.ts:26:11)

    console.log
      env[LOCALSTACK_HOSTNAME] => undefined

      at handler (subscribe-sqs/handler.ts:27:11)

    console.log
      env[S3_ENDPOINT] => http://127.0.0.1:4566

      at handler (subscribe-sqs/handler.ts:28:11)

    console.log
      put object => {"Bucket":"my-bucket","Key":"059f36b4-87a3-44ab-83d2-661975830a7d","Body":"{\"message\": \"Hello\"}"}

      at handler (subscribe-sqs/handler.ts:41:13)

 PASS  subscribe-sqs/handler-with-mock.test.ts
  ● Console

    console.log
      env[LOCALSTACK_HOST] => undefined

      at handler (subscribe-sqs/handler.ts:26:11)

    console.log
      env[LOCALSTACK_HOSTNAME] => undefined

      at handler (subscribe-sqs/handler.ts:27:11)

    console.log
      env[S3_ENDPOINT] => http://127.0.0.1:4566

      at handler (subscribe-sqs/handler.ts:28:11)

    console.log
      put object => {"Bucket":"my-bucket","Key":"059f36b4-87a3-44ab-83d2-661975830a7d","Body":"{\"message\": \"Hello\"}"}

      at handler (subscribe-sqs/handler.ts:41:13)


Test Suites: 2 passed, 2 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        1.275 s, estimated 2 s
Ran all test suites.

OKですね。

AWS SDK v3 Client mockを使っている方は、LocalStackに依存していないことを確認するために単独で実行しておきましょう。

$ npx jest subscribe-sqs/handler-with-mock.test.ts
  console.log
    env[LOCALSTACK_HOST] => undefined

      at handler (subscribe-sqs/handler.ts:26:11)

  console.log
    env[LOCALSTACK_HOSTNAME] => undefined

      at handler (subscribe-sqs/handler.ts:27:11)

  console.log
    env[S3_ENDPOINT] => undefined

      at handler (subscribe-sqs/handler.ts:28:11)

  console.log
    put object => {"Bucket":"my-bucket","Key":"059f36b4-87a3-44ab-83d2-661975830a7d","Body":"{\"message\": \"Hello\"}"}

      at handler (subscribe-sqs/handler.ts:41:13)

 PASS  subscribe-sqs/handler-with-mock.test.ts
  ✓ lambda test with AWS SDK v3 Client mock (27 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.769 s, estimated 2 s
Ran all test suites matching /subscribe-sqs\/handler-with-mock.test.ts/i.

大丈夫そうですね。

オマケ

AWS Lambda関数が動作しているコンテナ内で参照できる環境変数は、どこで定義されているのかな?と思って見てみたのですが、
以下のようですね。

        env_vars = {
            # 1) Public AWS defined runtime environment variables (in same order):
            # https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html
            # a) Reserved environment variables
            # _HANDLER conditionally added below
            # TODO: _X_AMZN_TRACE_ID
            "AWS_DEFAULT_REGION": self.function_version.id.region,
            "AWS_REGION": self.function_version.id.region,
            # AWS_EXECUTION_ENV conditionally added below
            "AWS_LAMBDA_FUNCTION_NAME": self.function_version.id.function_name,
            "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": self.function_version.config.memory_size,
            "AWS_LAMBDA_FUNCTION_VERSION": self.function_version.id.qualifier,
            "AWS_LAMBDA_INITIALIZATION_TYPE": self.initialization_type,
            "AWS_LAMBDA_LOG_GROUP_NAME": self.get_log_group_name(),
            "AWS_LAMBDA_LOG_STREAM_NAME": self.get_log_stream_name(),
            # Access IDs for role
            "AWS_ACCESS_KEY_ID": credentials["AccessKeyId"],
            "AWS_SECRET_ACCESS_KEY": credentials["SecretAccessKey"],
            "AWS_SESSION_TOKEN": credentials["SessionToken"],
            # AWS_LAMBDA_RUNTIME_API is set in the runtime interface emulator (RIE)
            "LAMBDA_TASK_ROOT": "/var/task",
            "LAMBDA_RUNTIME_DIR": "/var/runtime",
            # b) Unreserved environment variables
            # LANG
            # LD_LIBRARY_PATH
            # NODE_PATH
            # PYTHONPATH
            # GEM_PATH
            "AWS_XRAY_CONTEXT_MISSING": "LOG_ERROR",
            # TODO: allow configuration of xray address
            "AWS_XRAY_DAEMON_ADDRESS": "127.0.0.1:2000",
            # not 100% sure who sets these two
            # extensions are not supposed to have them in their envs => TODO: test if init removes them
            "_AWS_XRAY_DAEMON_PORT": "2000",
            "_AWS_XRAY_DAEMON_ADDRESS": "127.0.0.1",
            # AWS_LAMBDA_DOTNET_PREJIT
            "TZ": ":UTC",
            # 2) Public AWS RIE interface: https://github.com/aws/aws-lambda-runtime-interface-emulator
            "AWS_LAMBDA_FUNCTION_TIMEOUT": self.function_version.config.timeout,
            # 3) Public LocalStack endpoint
            "LOCALSTACK_HOSTNAME": self.runtime_executor.get_endpoint_from_executor(),
            "EDGE_PORT": str(config.EDGE_PORT),
            "AWS_ENDPOINT_URL": f"http://{self.runtime_executor.get_endpoint_from_executor()}:{config.EDGE_PORT}",
            # 4) Internal LocalStack runtime API
            "LOCALSTACK_RUNTIME_ID": self.id,
            "LOCALSTACK_RUNTIME_ENDPOINT": self.runtime_executor.get_runtime_endpoint(),
            # used by the init to spawn the x-ray daemon
            # LOCALSTACK_USER conditionally added below
        }

https://github.com/localstack/localstack/blob/v2.1.0/localstack/services/awslambda/invocation/runtime_environment.py#L93-L141

LOCALSTACK_HOSTは入っていないのですが…。

まとめ

LocalStack内で動作しているAWS Lambda関数から、LocalStackの別のリソースにアクセスするための情報をLOCALSTACK_HOSTNAME
環境変数を使うことで取得できることを確認しました。

最初はLOCALSTACK_HOST環境変数を見るべきだとドキュメントを読んでハマったり、テストのことも考えた構成にしたら
いろいろハマりましたが、最終的にはやりたかったことは全部確認できたので良かったかなと。

今後、環境を使い分けていく時のやり方のメモとして。