CLOVER🍀

That was when it all began.

LocalStackの初期化フックを使って、起動時に初期化処理を行う

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

LocalStackをよく使っているのですが、起動時にLocalStack上のリソースを作る方法がないのかなと思って調べてみました。

初期化フック(Initialization Hooks)というものを使うと、LocalStackのライフサイクルに合わせて処理を追加できるようなので
こちらを試してみることにしました。

LocalStackのInitialization Hooks

LocalStackの初期化フックに関するドキュメントは、こちら。

Initialization Hooks | Docs

LocalStackには4つのライフサイクルフェーズ(またはステージ)があるようです。

  • BOOT … コンテナは実行されているが、LocalStackランタイムは開始されていない
  • START … Pythonプロセスが実行中で、LocalStackランタイムが開始している
  • READY … LocalStackはリクエストを処理する準備ができた
  • SHUTDOWN … LocalStackがシャットダウンしている

このライフサイクルに合わせて、所定のディレクトリにシェルスクリプト(.sh)またはPythonスクリプト(.py)を配置しておくことで
各フェーズ(ステージ)でスクリプトを実行できるようです。
シェルスクリプトは実行権限が付与されている必要があります。

各フェーズ(ステージ)とディレクトリは、以下の対応になっています。

  • BOOT … /etc/localstack/init/boot.d
  • START … /etc/localstack/init/ready.d
  • READY … /etc/localstack/init/shutdown.d
  • SHUTDOWN … /etc/localstack/init/start.d

なお、LocalStackはDockerで動作するので、LocalStackが動作しているコンテナ内のディレクトリに配置する、ということになります。

ドキュメントにはDocker ComposeおよびLocalStack CLIでの設定方法が書かれています。

Initialization Hooks / Usage example

今回は起動時にLocalStack上にリソースを作成するということを試してみます。

環境

今回の環境は、こちら。

$ python3 -V
Python 3.10.6


$ pip3 -V
pip 22.0.2 from /usr/lib/python3/dist-packages/pip (python 3.10)


$ docker version
Client: Docker Engine - Community
 Version:           24.0.2
 API version:       1.43
 Go version:        go1.20.4
 Git commit:        cb74dfc
 Built:             Thu May 25 21:51:00 2023
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          24.0.2
  API version:      1.43 (minimum version 1.12)
  Go version:       go1.20.4
  Git commit:       659604f
  Built:            Thu May 25 21:51:00 2023
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.21
  GitCommit:        3dce8eb055cbb6872793272b4f20ed16117344f8
 runc:
  Version:          1.1.7
  GitCommit:        v1.1.7-0-g860f061
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0


$ docker compose version
Docker Compose version v2.18.1

LocalStack向けの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コンテナ内を確認する

まずはLocalStackをインストールします。

$ pip3 install --upgrade localstack

バージョン。

$ localstack --version
2.1.0

起動。

$ localstack start

localstack_mainというコンテナ内でLocalStackは実行されているので、この中を少し確認してみましょう。

awsコマンドは入っているようです。

$ docker container exec -it localstack_main which aws
/usr/local/bin/aws

/usr/local/binディレクトリの中を見てみましょう。

$ docker container exec -it localstack_main ls -1 /usr/local/bin
2to3
2to3-3.10
__pycache__
aws
aws.cmd
aws_bash_completer
aws_completer
aws_zsh_completer.sh
awslocal
awslocal.bat
docker-entrypoint.sh
docker-java-home
idle
idle3
idle3.10
jp.py
normalizer
pip
pip3
pip3.10
pydoc
pydoc3
pydoc3.10
pyrsa-decrypt
pyrsa-encrypt
pyrsa-keygen
pyrsa-priv2pub
pyrsa-sign
pyrsa-verify
python
python-config
python3
python3-config
python3.10
python3.10-config
rst2html.py
rst2html4.py
rst2html5.py
rst2latex.py
rst2man.py
rst2odt.py
rst2odt_prepstyles.py
rst2pseudoxml.py
rst2s5.py
rst2xetex.py
rst2xml.py
rstpep2html.py
virtualenv
wheel

awslocalも入っていますね。

バージョンを確認。

$ docker container exec -it localstack_main aws --version
aws-cli/1.27.164 Python/3.10.12 Linux/5.15.0-76-generic botocore/1.29.164


$ docker container exec -it localstack_main awslocal --version
aws-cli/1.27.164 Python/3.10.12 Linux/5.15.0-76-generic botocore/1.29.164

AWS CLIは、v1のようです。

Pythonは3.10が入っていました。

$ docker container exec -it localstack_main python3 -V
Python 3.10.12

初期状態では、/etc/localstack/initディレクトリにはなにも入っていないようです。

$ docker container exec -it localstack_main find /etc/localstack/init
/etc/localstack/init

OSはDebianですね。

$ docker container exec -it localstack_main cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

とりあえず、ざっくりと環境は確認できました。

LocalStack起動後にリソースを作成する

では、初期化フックを使ってみましょう。まずはLocalStackの起動後にリソースを作成してみたいと思います。

例に習って、init-aws.shというスクリプトを作成。

init-aws.sh

#!/bin/bash

# S3バケットの作成
awslocal s3 mb s3://my-bucket

# SQSキューの作成
awslocal sqs create-queue --queue-name my-queue

Initialization Hooks / Usage example

シェルスクリプトの場合、Shebangの記述(今回は#!/bin/bash)が必須のようです。

実行権限を付与。

$ chmod a+x init-aws.sh

LocalStack CLIで指定する場合は、DOCKER_FLAGSという環境変数を使うようです。

DOCKER_FLAGS環境変数を使うとLocalStackをDocker内で動作させる時に、docker container runにカスタムフラグを渡せます。

Docker

この時に、-vオプションを使ってスクリプトをコンテナにマウントします。

起動。

$ DOCKER_FLAGS="-v $(pwd)/init-aws.sh:/etc/localstack/init/ready.d/init-aws.sh" localstack start

起動時のログを見ていると、リソースが作成されていることがわかります。

2023-07-01T13:07:59.822  INFO --- [   asgi_gw_0] localstack.request.aws     : AWS s3.CreateBucket => 200
make_bucket: my-bucket
2023-07-01T13:08:00.558  INFO --- [   asgi_gw_0] localstack.request.aws     : AWS sqs.CreateQueue => 200
{
"QueueUrl": "http://localhost:4566/000000000000/my-queue"
}

確認してみましょう。

$ awslocal s3 ls
2023-07-01 22:07:59 my-bucket


$ awslocal sqs list-queues
{
    "QueueUrls": [
        "http://localhost:4566/000000000000/my-queue"
    ]
}

確かに作成されているようです。

Docker Composeで行う場合は、こちら。

compose.yaml

services:
  localstack:
    image: localstack/localstack
    ports:
      - 4566:4566
    volumes:
      - "./init-aws.sh:/etc/localstack/init/ready.d/init-aws.sh"

AWS CLI v2をインストールする

別のサンプルとして、LocalStackに含まれているAWS CLIをv2にするようにしてみましょう。

軽い気持ちでやったら、だいぶ苦労しましたが…。

スクリプトを作成して

install-aws-cli-v2.sh

#!/bin/bash

# AWS CLI v1をアンインストール
# 相対パスのpip3だと、LocalStackのvenvのpip3を向いているため
/usr/local/bin/pip3 uninstall -y awscli

cd /tmp

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
./aws/install

rm -rf aws awscliv2.zip

実行権限を付与。

$ chmod a+x install-aws-cli-v2.sh

今回はBOOTフェーズ(ステージ)で実行するようにしました。

$ DOCKER_FLAGS="-v $(pwd)/install-aws-cli-v2.sh:/etc/localstack/init/boot.d/install-aws-cli-v2.sh" localstack start

起動後は、AWS CLIがv2になりましたね。

$ docker container exec -it localstack_main aws --version
aws-cli/2.12.6 Python/3.11.4 Linux/5.15.0-76-generic exe/x86_64.debian.11 prompt/off


$ docker container exec -it localstack_main awslocal --version
aws-cli/2.12.6 Python/3.11.4 Linux/5.15.0-76-generic exe/x86_64.debian.11 prompt/off

ちょっとこの部分を説明しておきます。

# AWS CLI v1をアンインストール
# 相対パスのpip3だと、LocalStackのvenvのpip3を向いているため
/usr/local/bin/pip3 uninstall -y awscli

LocalStackのAWS CLIは、pipでインストールされているAWS CLIを見てバージョン判定を行います。このため、AWS CLI v2を
インストールしてもAWS CLI v1をpip内で解決可能だとそのバージョンを見てしまいます。

このためAWS CLI v1をアンインストールする必要があるのですが、相対パスのpip3を指定するとLocalStack管理のvenvのpip3を
見ているようなのでこのような形にしました。

まあ、ムリにv2に入れ替えようと思わない方がいい気がしてきましたね…。

起動時にリソースも一緒に作成する場合。

$ DOCKER_FLAGS="-v $(pwd)/install-aws-cli-v2.sh:/etc/localstack/init/boot.d/install-aws-cli-v2.sh -v $(pwd)/init-aws.sh:/etc/localstack/init/ready.d/init-aws.sh" localstack start

Docker Compose。

compose.yaml

services:
  localstack:
    image: localstack/localstack
    ports:
      - 4566:4566
    volumes:
      - "./install-aws-cli-v2.sh:/etc/localstack/init/boot.d/install-aws-cli-v2.sh"
      - "./init-aws.sh:/etc/localstack/init/ready.d/init-aws.sh"

ファイルの実行順を確認する

ディレクトリ内に複数のスクリプトを配置した際は、ソートされて実行されるようです。

https://github.com/localstack/localstack/blob/v2.1.0/localstack/runtime/init.py#L169

確認してみましょう。

ディレクトリを作成して

$ mkdir -p scripts/ready.d

こんなスクリプトを用意。

scripts/ready.d/01_echo.sh

#!/bin/bash

echo 'first script'

scripts/ready.d/02_print_pip3_version.sh

#!/bin/bash

pip3 -V

scripts/ready.d/03_hello.py

import boto3

print('print boto3 version:' + boto3.__version__)

数字の順で実行されるかどうかの確認ですね。

シェルスクリプトには、実行権限を付与。

$ chmod a+x scripts/ready.d/*.sh

確認。

$ DOCKER_FLAGS="-v $(pwd)/scripts/ready.d:/etc/localstack/init/ready.d" localstack start

結果。

first script
pip 23.1.2 from /opt/code/localstack/.venv/lib/python3.10/site-packages/pip (python 3.10)
print boto3 version:1.26.164

01、02、03の順で実行されたようです。

Docker Composeの設定も載せておきます。

compose.yaml

services:
  localstack:
    image: localstack/localstack
    ports:
      - 4566:4566
    volumes:
     - "./scripts/ready.d:/etc/localstack/init/ready.d"

まとめ

LocalStackの初期化フックを使って、起動時に初期化処理を行えることを確認してみました。

この機能、今まで知らなかったので起動と同時にリソースを作成したい場合などに使ってみましょう。
Terraformで作ることもありますが、まあ使い分けですね。