2022.10.11

LocalStack でローカル環境に EC2 と RDS を用意する

D.M. です。

こんな悩みはありませんか。

・AWS を勉強するためガチャガチャやりたいが、いきなり課金されるのが怖い。(無料が賄っている範囲を超えてしまうことがある)

こうした開発者向けに登場したプロダクト LocalStack が2022年7月にバージョン 1.0 になりました。
Announcing LocalStack 1.0 General Availability!

LocalStack には無料版と有料版(LocalStack Pro)があり、今回は有料版のトライアルを使ってみたので、そこから得た技術的知見と感想を書いていきます。

TL;DR

・LocalStack 無料版は Lambda、S3がローカルで動かせる。Terraform の練習ができる。
・LocalStack Pro の EC2 は Docker コンテナである。ネットワーク構成のコマンドは通るが、制約効果はない。
・RDS は LocalStack コンテナ内のプロセスとして立ち上がる。

LocalStack とは

LocalStack は2016年に有志が GitHub で始めたプロジェクトのようで、2021年に法人化されています。
公式ページ 会社紹介
https://LocalStack.cloud/company/

オープンソースなので GitHub で中身をのぞくことも可能です。見た感じPythonで書かれている模様。
https://github.com/localstack/localstack

無料版 LocalStack の特徴とメリット・デメリット


LocalStack は AWS をエミュレートしてローカルにテスト用の環境を構築できます。

無料版と有料版の機能一覧はこちらにあります。
https://docs.LocalStack.cloud/aws/feature-coverage/

無料版で特筆すべき点は Lambda と S3 が使えるというところです。
プログラミングは1から開発するときはどうしてもとりあえず書いてみて動かすといったことを繰りかえす必要がありますが、有料のAWS環境で雑に繰り返し実行するにはコスト面で不安があります。 LocalStack はサーバレス環境のローカル開発の手法として一石投じていると思います。
S3 と Lambda を取り扱ったブログ記事はいろいろあり、参考にさせていただきました。(たとえばこちら

無料版でイタいのは、 EC2 と RDS は使えないところです。ただここは自前の Docker コンテナで EC2 と RDS の機能を代替させ、無理やり LocalStack Lambda と接続させてしまえばなんとかなったりするという意見も Web 上には散見されるので、無料版の利用用途は工夫の余地があると思っています。

LocalStack の内部構造


LocalStack は Docker コンテナとして動きます。

コンテナ内の構成要素
・LocalStack メインプロセス:AWS CLIを受け付けるAPIサーバみたいなもの
・他のプロセス:S3、Dynamo、Kinesis など

公式の図がわかりやすいのでそのまま引用。
https://docs.LocalStack.cloud/developer-guide/basics/

Lambda や EC2 は別の Docker コンテナとして立ち上がります。

https://docs.LocalStack.cloud/LocalStack/

まとめると。。
LocalStack 本体 = 独立した Docker コンテナ
RDS, S3, DynamoDB, Kinesis … = LocalStack コンテナ内の独立したプロセス
EC2, lambda = 独立した Docker コンテナ(本体とは別コンテナで動く)

なのでうまく動かないなとなった場合、最悪 Docker のコンテナを直接調査して操作すればなんとなく解決できるような構造になっています。

LocalStack のイケてないところ

AWS マネジメントコンソールの画面をブラウザでカチカチいじって構築ができません。
全部 aws cli でやる必要があります。逆に言うと API は全部通るので Terraform は実行できますし、その辺の開発環境としては充分に役に立ちます。

AWS マネジメントコンソールがないかわりに、独自の Cockpit という UI があります。
https://docs.LocalStack.cloud/get-started/cockpit/
メリット
・LocalStack main の起動など管理ができる。
・LocalStack main のログが読める(aws cli の実行結果ログ)
ただ Windows と Mac がまだベータ版なのと、私は今回 Windows WSL 上で動いている LocalStack を使っておりそこにつなぐことができなかったので使えませんでした。

awslocal


LocalStack に向けて aws cli を実行するうえで、ENDPOINT URL は LocalStack のサーバのIPを指定する必要があります。
そのときコマンドで –endpoint-url を毎回指定するのが面倒なので、以下のツールで簡易的に実行できます。
https://github.com/localstack/awscli-local
例えばこんな感じ。

aws –endpoint-url=http://localhost:4566 kinesis list-streams

awslocal kinesis list-streams

本番との差別化で事故も減らせるので、是非こいつを使っていきましょう。

LocalStack Pro (有料版)

本題です。

やりたいことは以下の検証です。


目次
  1. 1 EC2
  2.  1-1 立てられるか?
  3.  1-2 ネットワーク設定できるか?
  4. 2 RDS
  5.  2-1 立てられるか?
  6.  2-2 EC2 と通信できるか?

で、さっそく結論です。

結論
  1. 1 EC2
  2.  1-1 〇  立てられた。
  3.  1-2 △✕ 設定値は登録されるが、期待したような通信制約はなさげ。
  4. 2 RDS
  5.  2-1 〇 立てられた。 Aurora Postgres, MySQL
  6.  2-2 〇 EC2 と通信できた。


私が実行した環境は以下の通りです。

環境
・Windows 10
・WSL 2
・Ubuntu 20.04.4 LTS
・docker 20.10
・docker-compose 2.11
・LocalStack 1.1


準備 LocakStacl を Pro 版で立ち上げる


前提としてProの申し込みが必要です。
Proを使うためにはアカウントを作ってログインし、 Account → Subscriptions でProのトライアルを選んで進むとAPIキーが発行されます。特にクレジットカード等の入力は不要でした。

次に LocalStack を Pro で立ち上げる必要があります。
これは公式に解説があります。
https://docs.LocalStack.cloud/get-started/pro/
これは動作時にキーを環境変数で渡してあればなんでもよく、

OS の環境変数
export LocalStack_API_KEY=your-api-key

docker run の引数
-e LocalStack_API_KEY=${LocalStack_API_KEY:- }

docker-compose ファイルのパラメータ
environment:
– LocalStack_API_KEY=${LocalStack_API_KEY- }

どのパターンでも大丈夫です。
今回は公式GitHub の docker-compose をベースに構築しました。
https://github.com/LocalStack/LocalStack


Pro 初回実行時の確認方法

LocalStack の health API で使える機能がわかります。主に以下が有料版で使える機能なので、ここが available やら running となっていたらOKです。
persistence
ec2
rds

私の実行結果です。

$ curl -s "$ENDPOINT_URL/health" | jq .
{
  "features": {
    "persistence": "initialized",
    "initScripts": "initialized"
  },
  "services": {
    "acm": "available",
    "apigateway": "available",
    "cloudformation": "available",
    "cloudwatch": "available",
    "config": "available",
    "dynamodb": "available",
    "dynamodbstreams": "available",
    "ec2": "running",
...

1 EC2


1-1 EC2 インスタンス(Pro版)を立ち上げる


結論としてはすぐにうまくいきました。
ミニマムでとりあえず立ち上げるには以下を行います。
1 image の取得とタグ付け
2 aws cli 実行

$ docker pull public.ecr.aws/amazonlinux/amazonlinux:2.0.20220912.1

docker tag public.ecr.aws/amazonlinux/amazonlinux:2.0.20220912.1 LocalStack-ec2/amazonlinux2:ami-amazonlinux2

awslocal ec2 run-instances --image-id  ami-amazonlinux2 

これで立ち上がります。

1点注意が必要なのが image_id です。
これはホスト OS の方で LocalStack-ec2 の接頭辞をつけた Docker Image のバージョン番号に相当します。
今回の場合は ami-amazonlinux2 を自分でつけています。
Image は ubuntu focal と amazonlinux2 でやったところ両方とも普通に動作しました。
( amazonlinux2022 でやったところ、立ち上がりましたが ssh キー登録に失敗しました)

1-2 ネットワークをしっかり設定してみる


以下公式のガイドに従って基本的なネットワーク設定コマンドを実行してEC2を構築します。
これも結論としてはコマンド自体は正常に動きました。

AWS CLI を使用して IPv4 対応 VPC とサブネットを作成する
https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/vpc-subnets-commands-example.html

サマリーとしては以下の項目を設定します。
・VPC
・Subnet
・Internet Gateway
・Route Table
・Security Group
・KeyPair


(コマンドは長いので折り畳んであります)

# VPC 作成
# CIDR BLOCKを作る
awslocal ec2 create-vpc –cidr-block 10.0.0.0/16 –query Vpc.VpcId –output text

# VpcId
export VPC_ID=vpc-5b98d9fd

# サブネット1を作る
awslocal ec2 create-subnet –vpc-id $VPC_ID –cidr-block 10.0.1.0/24

export SUBNET_ID_1=subnet-c92f5e90

# サブネット2をつくる
awslocal ec2 create-subnet –vpc-id $VPC_ID –cidr-block 10.0.0.0/24 –endpoint-url=$ENDPOINT_ URL

# インターネットゲートウェイを作る(パブリックサブネットになる)
awslocal ec2 create-internet-gateway –query InternetGateway.InternetGatewayId –output text

export IGW_ID=igw-790b3244

# VPC と IG をくっつける
awslocal ec2 attach-internet-gateway –vpc-id $VPC_ID –internet-gateway-id $IGW_ID

# ルートテーブル作成
awslocal ec2 create-route-table –vpc-id $VPC_ID –query RouteTable.RouteTableId –output text

export RTB_ID=rtb-411d208f

# 全トラフィック 0.0.0.0 がIGを指すようにルート作成する。
awslocal ec2 create-route –route-table-id $RTB_ID –destination-cidr-block 0.0.0.0/0 –gateway-id $IGW_ID


# ルートテーブル確認
awslocal ec2 describe-route-tables –route-table-id $RTB_ID

# サブネット確認
awslocal ec2 describe-subnets –filters “Name=vpc-id,Values=$VPC_ID” –query “Subnets[*].{ID:SubnetId,CIDR:CidrBlock}”

# パブリックサブネットをルートテーブルに紐づける
awslocal ec2 associate-route-table –subnet-id $SUBNET_ID_1 –route-table-id $RTB_ID

# サブネットに起動されたEC2が自動的にIPを紐づけられる設定
awslocal ec2 modify-subnet-attribute –subnet-id $SUBNET_ID_1 –map-public-ip-on-launch

# キーペア作成
awslocal ec2 create-key-pair –key-name MyKeyPair –query “KeyMaterial” –output text > MyKeyPair.pem

chmod 400 MyKeyPair.pem

# ssh アクセス用セキュリティグループ
awslocal ec2 create-security-group –group-name SSHAccess –description “Security group for SSH access” –vpc-id $VPC_ID

export SG_ID=sg-356e095957483464b

awslocal ec2 authorize-security-group-ingress –group-id $SG_ID –protocol tcp –port 22 –cidr 172.16.0.0/12

export AMI_ID=ami-a4827dc9

# ec2 作成 10.0.1.0 のほうのサブネットに入れる
awslocal ec2 run-instances –image-id $AMI_ID –count 1 –instance-type t2.micro –key-name MyKeyPair –security-group-ids $SG_ID –subnet-id $SUBNET_ID_1


結論としては以下のように設定が完了しました。

$ awslocal ec2 describe-instances --instance-id  i-4d9cc4bda8a9ef129

"Instances": [
                {
                    "AmiLaunchIndex": 0,
                    "ImageId": "ami-amazonlinux2",
                    "InstanceId": "i-4d9cc4bda8a9ef129",
                    "InstanceType": "t2.micro",
                    "KernelId": "None",
                    "KeyName": "MyKeyPair",
                    "LaunchTime": "2022-10-09T09:57:17+00:00",
                    "Monitoring": {
                        "State": "disabled"
                    },
                    "Placement": {
                        "AvailabilityZone": "ap-northeast-1a",
                        "GroupName": "",
                        "Tenancy": "default"
                    },
                    "PrivateDnsName": "ip-10-0-1-9.ap-northeast-1.compute.internal",
                    "PrivateIpAddress": "10.0.1.9",
                    "ProductCodes": [],
                    "PublicDnsName": "ec2-54-214-116-86.ap-northeast-1.compute.amazonaws.com",
                    "PublicIpAddress": "54.214.116.86",
"State": {
                        "Code": 16,
                        "Name": "running"
                    },
                    "StateTransitionReason": "",
                    "SubnetId": "subnet-c92f5e90",
                    "VpcId": "vpc-5b98d9fd",
                    "Architecture": "x86_64",
                    "BlockDeviceMappings": [
                        {
                            "DeviceName": "/dev/sda1",
                            "Ebs": {
                                "AttachTime": "2022-10-09T09:57:17+00:00",
                                "DeleteOnTermination": true,
                                "Status": "in-use",
                                "VolumeId": "vol-2e059d9d"
                            }
                        }
                    ],
                    "ClientToken": "ABCDE0000000000003",
                    "EbsOptimized": false,
                    "Hypervisor": "xen",
"NetworkInterfaces": [
                        {
                            "Association": {
                                "IpOwnerId": "000000000000",
                                "PublicIp": "54.214.116.86"
                            },
                            "Attachment": {
                                "AttachTime": "2015-01-01T00:00:00+00:00",
                                "AttachmentId": "eni-attach-2a097216",
                                "DeleteOnTermination": true,
                                "DeviceIndex": 0,
                                "Status": "attached"
                            },
                            "Description": "Primary network interface",
                            "Groups": [
                                {
                                    "GroupName": "SSHAccess",
                                    "GroupId": "sg-356e095957483464b"
                                }
                            ],
                            "MacAddress": "1b:2b:3c:4d:5e:6f",
                            "NetworkInterfaceId": "eni-5d7635ca",
                            "OwnerId": "000000000000",
                            "PrivateIpAddress": "10.0.1.9",
                            "PrivateIpAddresses": [
...

EC2を立ち上げた直後にホスト OS の docker ps を見ると、以下のように EC2 が別のコンテナとして立ち上がっていることがわかります。

$ docker ps
CONTAINER ID   IMAGE                                            COMMAND                  CREATED          STATUS                  PORTS                                                                                                                                                                                                                                                                   NAMES
cc7ddaf79d14   LocalStack-ec2/amazonlinux2:ami-amazonlinux2   "sleep 43200"            20 minutes ago   Up 20 minutes           0.0.0.0:41011->22/tcp, :::41011->22/tcp                                                                                                                                                                                                                                 LocalStack-ec2.i-4d9cc4bda8a9ef129
946a193a8361   LocalStack-ec2/ubuntu-focal:ami-ubuntu       "sleep 43200"            6 hours ago      Up 6 hours              0.0.0.0:47355->22/tcp, :::47355->22/tcp                                                                                                                                                                                                                                 LocalStack-ec2.i-156483f5cc20a3683
649129d9f464   LocalStack/LocalStack:latest                     "docker-entrypoint.sh"   4 weeks ago      Up 22 hours (healthy)   0.0.0.0:53->53/tcp, :::53->53/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp, 0.0.0.0:4566->4566/tcp, :::4566->4566/tcp, 4510-4559/tcp, 0.0.0.0:4571->4571/tcp, :::4571->4571/tcp, 0.0.0.0:8080->8080/tcp, 0.0.0.0:53->53/udp, :::8080->8080/tcp, :::53->53/udp, 5678/tcp   LocalStack_main


当然ですが docker exec でコンテナ内にログインできます。
$ docker exec -it LocalStack-ec2.i-4d9cc4bda8a9ef129 /bin/bash


また SSH も可能です。 aws cli で設定したキーがしっかり使えました。
$ ssh [email protected]  -i MyKeyPair1.pem  -v

EC2 立ち上げ時の LocalStack の Docker log を読むと以下がわかります。
・IP が割り振られている。(172.17.0.2)
・ssh が初期設定されている。(dropbearで入っているっぽい)

$ docker logs LocalStack_main | less
2022-10-10T04:27:50.590 DEBUG --- [   asgi_gw_1] l.s.ec2.vmmanager.docker   : Launching instance i-4d9cc4bda8a9ef129
2022-10-10T04:27:50.591 DEBUG --- [   asgi_gw_1] l.u.c.docker_sdk_client    : Running container with image: LocalStack-ec2/amazonlinux2:ami-amazonlinux2
2022-10-10T04:27:50.591 DEBUG --- [   asgi_gw_1] l.u.c.docker_sdk_client    : Creating container with attributes: {'mount_volumes': [('/var/run/docker.sock', '/var/run/docker.sock')], 'ports': <PortMappings: {'22/tcp': 54063}>, 'cap_add': None, 'cap_drop': None, 'security_opt': None, 'dns': None, 'additional_flags': None, 'workdir': None, 'command': ['sleep', '43200'], 'detach': True, 'entrypoint': None, 'env_vars': {}, 'image_name': 'LocalStack-ec2/amazonlinux2:ami-amazonlinux2', 'interactive': False, 'name': 'LocalStack-ec2.i-4d9cc4bda8a9ef129', 'network': None, 'remove': False, 'self': <LocalStack.utils.container_utils.docker_sdk_client.SdkDockerClient object at 0x7ff9429d9c90>, 'tty': False, 'user': None}
2022-10-10T04:27:50.615 DEBUG --- [   asgi_gw_1] l.u.c.docker_sdk_client    : Starting container cc7ddaf79d14ab8088061ff44befbb54ed1f0cc00f17b0ae03c39c4ea4e7fae0
2022-10-10T04:27:51.110  INFO --- [   asgi_gw_1] l.s.ec2.vmmanager.docker   : Instance i-f112790c673b3f1e2 will be accessible via SSH at: 127.0.0.1:54063, 172.17.0.2:22
2022-10-10T04:27:51.110 DEBUG --- [   asgi_gw_1] l.u.c.docker_sdk_client    : Listing containers with filters: None
2022-10-10T04:27:51.130 DEBUG --- [Thread-41233] l.u.c.docker_sdk_client    : Executing command in container LocalStack-ec2.i-4d9cc4bda8a9ef129: mkdir -p /var/log
2022-10-10T04:27:51.189  INFO --- [   asgi_gw_1] LocalStack.request.aws     : AWS ec2.RunInstances => 200
2022-10-10T04:27:51.315 DEBUG --- [Thread-41233] l.s.ec2.vmmanager.docker   : Starting ssh setup in container: LocalStack-ec2.i-4d9cc4bda8a9ef129
2022-10-10T04:27:51.315 DEBUG --- [Thread-41233] l.u.c.docker_sdk_client    : Copying file /var/lib/LocalStack/tmp/scp into LocalStack-ec2.i-4d9cc4bda8a9ef129:/usr/bin/scp
2022-10-10T04:27:51.353 DEBUG --- [Thread-41233] l.u.c.docker_sdk_client    : Copying file /var/lib/LocalStack/tmp/dropbear-sshd into LocalStack-ec2.i-4d9cc4bda8a9ef129:/usr/bin/dropbear
2022-10-10T04:27:51.395 DEBUG --- [Thread-41233] l.u.c.docker_sdk_client    : Executing command in container LocalStack-ec2.i-4d9cc4bda8a9ef129: ['mkdir', '-p', '/etc/dropbear']
2022-10-10T04:27:51.430 DEBUG --- [Thread-41233] l.u.c.docker_sdk_client    : Executing command in container LocalStack-ec2.i-4d9cc4bda8a9ef129: ['sh', '-c', 'mkdir -p $HOME/.ssh; echo -n "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCxfo+NcFxLKqyy1W+Q5BPtge0Gee4mpz3sjOhuhn7QzzH77AkDJm8oh8ZaK3Rnkrc/LLlGH7pcq+kipO7nPZ9g/N/t+yg1uo6i+CC293coR/42dnjOA+TgdMSTXd9hmdRoZsxU5jTbFoJ6mRj26Sp4aHlYFh+gL8b5SuLKgs7piy4yfJtdVqEHEKaD+USEzNhf/MYD6661lTPnPYWdL/GQbIxXc4VouJD/tU0h1vFY5Kjnba4nYxJFd1o+QZ2LxsIc9FlmnCYmPX1/HV1OBt4BL67FKYxjVaylo2CeE3ACc+IiNvGA93A3uKAIZ6u381MFnPMqOrFeDCph/wD50M9F" >> $HOME/.ssh/authorized_keys']
2022-10-10T04:27:51.464 DEBUG --- [Thread-41233] l.u.c.docker_sdk_client    : Executing command in container LocalStack-ec2.i-4d9cc4bda8a9ef129: ['/usr/bin/dropbear', '-R', '-p', '22']
2022-10-10T04:27:51.498 DEBUG --- [Thread-41233] l.s.ec2.vmmanager.docker   : Finished ssh setup in container: LocalStack-ec2.i-4d9cc4bda8a9ef129

EC2 の VPC を分けると通信はどうなるのか


EC2コンテナをポコポコ複数個量産し、その際に以下の変化をつけてみます。
・VPCを分ける
・Subnetを分ける
・セキュリティグループで22以外のポートを開けてみる

さてどうなったでしょうか。
期待値としては通信ができないような設定が追加されて欲しいところです。
ですが結論は、設定値としては作られますが、裏側の Docker コンテナの通信制約は特に生成されませんでした。

以下のように docker network の bridge に全 EC2 インスタンスがぶっこまれています。
$ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "32025d62064d219c823300358ce313fb6edfed894a945aee2946f323ef023272",
        "Created": "2022-10-03T19:11:48.803259032+09:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "3091fa994cfa33df4056157da275115341997d7a7585557ed058493f14fca7ac": {
                "Name": "LocalStack-ec2.i-4d9cc4bda8a9ef129",
                "EndpointID": "3c95cc592322b4d5cbc8f6cda33e65160b132040582108a0f76e87a0a86d6e51",
                "MacAddress": "02:42:ac:11:00:04",
                "IPv4Address": "172.17.0.4/16",
                "IPv6Address": ""
            },
            "7778e2f6a670b05978089e54944243350d6cc4941a5973af43461673f98635fe": {
                "Name": "LocalStack-ec2.i-f82277fe83ab34d65",
                "EndpointID": "1a908fa777e4b332d620f3b76e5eb944e7a830307e6646f8cc48154680dc0c6a",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            },
            "9c6217e10c6fdd05030eea0332b70ee9fe408ef00eeed9eea64e4173dc5da571": {
                "Name": "LocalStack-ec2.i-dd5a8dd8635f14f6e",
                "EndpointID": "c61e73036fba5e5297bbba33d9637e5eaf76461fc538edaaba3207664bbef954",
                "MacAddress": "02:42:ac:11:00:05",
                "IPv4Address": "172.17.0.5/16",
                "IPv6Address": ""
            },
            "d7cd4280426f0ac2f54b8b5bcb8c58971f055bb235c8d4683e41a5a5297fce6c": {
                "Name": "LocalStack-ec2.i-cb182d093c26d4440",
                "EndpointID": "bcc2c81f66d83280ad1ea0ba415d3a05a088e7bb50af07a69c5269947f08130b",
                "MacAddress": "02:42:ac:11:00:03",
                "IPv4Address": "172.17.0.3/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

何種類かやりましたが iptables 的には以下のようにコンテナIPである 172.17.0.X のsshポート 22 が空いてるのみとなっています。
ACCEPT     tcp  --  anywhere             172.17.0.2           tcp dpt:ssh
ACCEPT     tcp  --  anywhere             172.17.0.3           tcp dpt:ssh
ACCEPT     tcp  --  anywhere             172.17.0.4           tcp dpt:ssh
ACCEPT     tcp  --  anywhere             172.17.0.5           tcp dpt:ssh
ACCEPT     tcp  --  anywhere             172.17.0.6           tcp dpt:ssh

期待値としては、aws cli でネットワークと ACL を作ってから通信ができませんねをテストができるとありがたかったのですが、そこまでは現状はできないようです。
VPC や Subnet を分けた場合であっても、デフォルトと同じ Docker ネットワークに含まれる状態になります。(デフォルトの bridge ネットワーク)
Docker コンテナのネットワークは最終的には iptables で確認できますが、VPC を分けても特別なルールは生成されず、ssh のポートが空いただけでした。
もしかしたら別のところで制約があるのかもですが、今回はそこが見つけられませんでした。
この点は新しい情報が入ればアップデートしたいと思います。

Python を入れてHTTP SERVERを動かしてみる。


EC2なのでWebアプリケーションも動かしてみます。

雑に手動でPythonを入れて、簡単な HTTP SERVER を実装します。

$ amazon-linux-extras install python3.9
$ vi http_server.py
$ cat http_server.py
from http.server import SimpleHTTPRequestHandler, HTTPServer
server = HTTPServer(('', 8000), SimpleHTTPRequestHandler)
server.serve_forever()
$ python3.9 http_server.py 

ホストOS側から curl でアクセス。
するとしっかり動作しています。

$ curl 172.17.0.2:8000
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
<li><a href=".dockerenv">.dockerenv</a></li>
<li><a href="bin/">bin@</a></li>
<li><a href="boot/">boot/</a></li>
<li><a href="dev/">dev/</a></li>
<li><a href="etc/">etc/</a></li>
<li><a href="home/">home/</a></li>
<li><a href="http_server.py">http_server.py</a></li>
<li><a href="lib/">lib@</a></li>
<li><a href="lib64/">lib64@</a></li>
<li><a href="local/">local/</a></li>
<li><a href="media/">media/</a></li>
<li><a href="mnt/">mnt/</a></li>
<li><a href="nohup.out">nohup.out</a></li>
<li><a href="opt/">opt/</a></li>
<li><a href="proc/">proc/</a></li>
<li><a href="root/">root/</a></li>
<li><a href="run/">run/</a></li>
<li><a href="sbin/">sbin@</a></li>
<li><a href="srv/">srv/</a></li>
<li><a href="sys/">sys/</a></li>
<li><a href="tmp/">tmp/</a></li>
<li><a href="usr/">usr/</a></li>
<li><a href="var/">var/</a></li>
</ul>
<hr>
</body>
</html>

python側のログもしっかり出ています。
172.17.0.1 - - [09/Oct/2022 05:22:38] "GET / HTTP/1.1" 200 -
172.17.0.1 - - [09/Oct/2022 05:41:41] "GET / HTTP/1.1" 200 -

まあこれは言ってしまえばただのDockerコンテナなので動くのは当然でした。
WSL 2 内の docker コンテナのポートをWindowのブラウザから参照するにはもうひとつ技が必要だと思いますがまあなんとかなるでしょう。

2 RDS


公式の情報によると RDS は MySQL. Postgres, SQL Server が使えます。(2022年10月現在)
https://docs.LocalStack.cloud/aws/rds/

まずは定石通り公式に出ているコマンドをそのまま実行してみます。

公式ではいきなり強気にローカルに aurora-postgresql を作ります。

$ awslocal rds create-db-cluster --db-cluster-identifier db1 --engine aurora-postgresql --database-name test 

{
    "DBCluster": {
        "AllocatedStorage": 1,
        "DatabaseName": "test",
        "DBClusterIdentifier": "db1",
        "Status": "available",
        "Endpoint": "localhost:4510",
        "MultiAZ": false,
        "Engine": "aurora-postgresql",
        "Port": 4510,
        "MasterUsername": "test",
        "StorageEncrypted": false,
        "DBClusterArn": "arn:aws:rds:ap-northeast-1:000000000000:cluster:db1",
        "IAMDatabaseAuthenticationEnabled": false,
        "TagList": []
    }
}

aurora-postgresql が難なく生成できました。

ホストOSから接続してみます。

$ psql -d test -U test -p 4510 -h  172.18.0.2  -W
Password:
psql (11.17 (Ubuntu 11.17-1.pgdg20.04+1))
Type "help" for help.
test=# create table test1 (id int );
CREATE TABLE
test=# insert into test values (123) ;
INSERT 0 1
test=# select * from test;
 id
----
  123
(1 row)

問題なく動作しました。
デフォルトで作られるのは postgres 11 でした。(11,12,13が選べる模様)

mysql の方も構築してコマンド実行してみます。

$ awslocal rds create-db-instance --db-instance-identifier db-mysql --engine mysql --db-instance-class db.t3.large    --master-username test    --master-user-password  test  
{
    "DBInstance": {
        "DBInstanceIdentifier": "db-mysql",
        "DBInstanceClass": "db.t3.large",
        "Engine": "mysql",
        "DBInstanceStatus": "creating",
        "MasterUsername": "test",
        "DBName": "test",
        "Endpoint": {
            "Address": "localhost",
            "Port": 4511
        },

ほとんど同じなので割愛しますが、デフォルトで入るのは MariaDB 5.5 のようでした。

RDS は LocalStack が動いてるDocker コンテナ内のプロセスとして動作します。
ps で見てみるとこんな感じ

$ ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   5484  3248 ?        Ss   Oct06   0:00 /bin/bash /usr/local/bin/docker-entrypoi
root        14  0.0  0.2  31672 27052 ?        S    Oct06   0:29 /usr/local/bin/python /usr/local/bin/sup
root        17  0.0  0.0   4084   680 ?        S    Oct06   0:00 tail -qF /var/lib/LocalStack/logs/locals
root        19  0.3  2.3 4535152 296552 ?      Sl   Oct06  13:30 .venv/bin/python -m LocalStack.cli.main
root        22  0.0  0.0      0     0 ?        Z    Oct06   0:00 [python] <defunct>
root      8581  0.0  0.0   7892  2908 ?        S    Oct08   0:00 /bin/su LocalStack -c /usr/lib/postgresq
localst+  8582  0.0  0.2 210164 26116 ?        Ss   Oct08   0:04 /usr/lib/postgresql/11/bin/postgres -p 3
localst+  8584  0.0  0.0 210308  8600 ?        Ss   Oct08   0:00 postgres: checkpointer
localst+  8585  0.0  0.0 210164  5748 ?        Ss   Oct08   0:01 postgres: background writer
localst+  8586  0.0  0.0 210164  9668 ?        Ss   Oct08   0:01 postgres: walwriter
localst+  8587  0.0  0.0 210704  6612 ?        Ss   Oct08   0:03 postgres: autovacuum launcher
localst+  8588  0.0  0.0  65216  5088 ?        Ss   Oct08   0:07 postgres: stats collector
localst+  8589  0.0  0.0 210564  7996 ?        Ss   Oct08   0:00 postgres: TimescaleDB Background Worker
localst+  8590  0.0  0.0 210560  6724 ?        Ss   Oct08   0:00 postgres: logical replication launcher
root     13680  0.0  0.0   2392   760 ?        Ss   13:18   0:00 /bin/sh -c mysqld --no-defaults --user=m
mysql    13681  0.0  0.6 1761980 84392 ?       Sl   13:18   0:00 mysqld --no-defaults --user=mysql --data
root     14066  0.0  0.0   5748  3536 pts/0    Ss   13:24   0:00 /bin/bash
root     14444  0.0  0.0   9388  3052 pts/0    R+   13:29   0:00 ps aux

EC2 から RDS へ接続してみる


再び EC2 側に入り Python で DB 接続するだけのコードを書いてみます。

$ python3.8
Python 3.8.5 (default, Aug 16 2022, 20:22:19)
[GCC 7.3.1 20180712 (Red Hat 7.3.1-13)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import psycopg2
>>> connection = psycopg2.connect("host=172.18.0.2 port=4510 dbname=test user=test password=test")
>>> connection.get_backend_pid()
10754
>>> cur = connection.cursor()
>>> cur.execute("select * from test")
>>> for row in cur:
...   print(row)
...
(123,)

問題なく動作しています。

実はここで1回ハマりました。
EC2 と RDS が通信できませんでした(苦笑)
デフォルトではつながるのですが、私のケースでは少し特殊なことをしておりつながりませんでしたのでTipsとしてメモを残しておきます。
RDS は LocalStack のいるコンテナの中に立ち上がるプロセスなので、EC2内でそのLocalStack の IP を指してやればつながるはずです。
私は LocalStack を docker-compose で独自の Docker ネットワークを構築して立ち上げてしまっていました。
こうすると EC2 のデフォルトのネットワークである bridge 内からアクセスできなくなります。
こうしたケースでは個別にEC2のネットワークを LocalStack と同じものに着け直す必要があります。
docker network connect LocalStack-tutorial LocalStack-ec2.i-4258e122f244813bb

同じ問題が lambda にも起こりえますが設定で解決できるようになっています。 lambda はコンテナとして立ち上がるので、どのDockerネットワークに属するかを指定しないとRDSやS3に接続不能になります。 デフォルトの bridge 以外の場合は LAMBDA_DOCKER_NETWORK で指定することができます。(なんでこれがEC2側にないのか。。)

今回検証できなかったこと


・EC2のIAM連携
・RDSのメモリなどパラメータを変更
・RDSバックアップ

また LocalStack は 1.0 に際し面白い機能をいくつか出しています。これらも継続して検証していこうと思います。
・チーム内でのローカル環境の共有
・CI 環境でのテスト

2022年9月の段階で Roadmap ページがあり GCP 、 Azure のサポートなどの要望が上がっていましたが、今見たところページがなくなっていました。
Azure 対応については GitHub がありますがしばらく更新されていないように見えるので当面は動きがないかもしれません。
https://github.com/LocalStack/azure-cli-local
Roadmap は現状のディスカッションページに吸収されているようなので、今後はここでの盛り上がりに期待していきます。
https://discuss.LocalStack.cloud/

宣伝


次世代システム研究室では、最新のテクノロジーを調査・検証しながらインターネットのアプリケーション開発を行うアーキテクトを募集しています。募集職種一覧 からご応募をお待ちしています。

  • Twitter
  • Facebook
  • はてなブックマークに追加

グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。

 
  • AI研究開発室
  • 大阪研究開発グループ

関連記事