1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

AWS CDK(Python)を利用してECS×Fargate×ServiceConnectの構成でLocust(Master/Slave)の環境を構築してみた。

Last updated at Posted at 2023-02-23

大変ご無沙汰しております。
最近Locustという負荷試験ツールを触る機会があり、せっかくならCDK(Python)を利用して構築してみたいと思い立ったので、ECS(Fargate)上で動く環境を構築していきたいと思います。
さらに今回は、『AWS re:Invent 2022』で発表されたECSの新機能Service Connectも取り入れていきます。

用語解説(粗)

  • CDK
    AWS Cloud Development Kitのこと。特定のプログラミング言語でAWSリソースをコード化し、そのコードを実行することでAWS環境ができあがる優れものです。
    CDKの導入編としてこちらの記事を書いておりますので、CDKをまだ試したことのない方はそちらを見ていただくことをお勧めいたします。
  • ECS
    DockerコンテナアプリケーションをAWS上で簡単に実行できるサービス。docker-composeで行うような管理(複数のコンテナ群をひとまとめにして管理するなど)をAWS上で利用できるイメージ。
  • Fargate
    ECSEKS(AWSのk8sサービス)で定義したコンテナをAWSが提供しているホスト環境で実行することができるサービス。いわゆるサーバーレス。
    なお、ECSEC2にも展開できますが、EC2の場合は自分で環境管理が必要になり運用の手間が発生するため、その手間を省きたい場合はFargateがおすすめです。
  • Service Connect
    ECS内のService間の通信を容易に設定できるようなサービス。
    今まではCloud MapApp Meshといったサービスを駆使する必要があったのですが、ECSのサービス内で完結して設定できるようになったようです。(裏ではCloud Mapが動いているようですが、それらをわざわざ自前で準備、または意識しなくても良くなった、という感じでしょうか。)
  • Locust
    OSSの負荷試験ツール。日本語に訳すと『イナゴ』。Pythonコードでテストシナリオを定義できるため、テストシナリオをPythonでごりごり書きたいという方にはおすすめのツールです。
    また、Master/Slave構成で起動することも可能で、Slaveを複数起動させて大規模な負荷をかけることも容易にできます。
    以降の記事では、Master=Master機、Slave=Worker機として解説します。

構成図

今回構築するAWS環境イメージは以下となります。(ECS周辺サービスのみ掲載)
メインはLocust側の環境構築となりますが、Locustの動作確認用におまけでNginx環境も添えています。
aws.drawio.png

構築手順

実施環境

本記事で構築を実施した環境は以下となります。特別な環境は用意していませんが、Service Connectを利用できるのはCDKのバージョンが2.52.0以上となっていますので、Service Connectを利用したい場合にはご自身のCDKのバージョンをお確かめください。

  • OS : Ubuntu 20.04 LTS (GNU/Linux 5.10.16.3-microsoft-standard-WSL2 x86_64)
  • Python : 3.8.10
  • AWS CDK : 2.65.0 (build 5862f7a)
  • Nodejs : v19.6.0
  • AWS CLI : 1.18.69 (Credentials Configured)

手順

1. CDKプロジェクト作成

まずはCDKプロジェクトを作成します。
cdk init app --language pythonのコマンドにて必要なファイルを生成し、その後各環境のスタック別のフォルダやLocustのコンテナイメージ作成用のフォルダを手動でカスタマイズしまして、以下のようなファイル群を作りました。
本記事では一部の主要なファイルのみ説明していきます。

tree
.
├── README.md
├── app.py # 4.にて説明
├── cdk.context.json
├── cdk.json
├── cdk.out
├── locust_docker # Locustのコンテナイメージ向けフォルダ
│   ├── Dockerfile # 5.にて説明
│   ├── docker-compose.yml # ローカルでのみ利用したため割愛
│   └── locustfile.py # 5.にて説明
├── locust_on_ecs # Locust環境向けスタック向けフォルダ
│   ├── __init__.py
│   └── locust_on_ecs_stack.py # 2.にて説明
├── nginx_on_ecs # Nginx環境向けスタック向けフォルダ
│   ├── __init__.py
│   └── nginx_on_ecs_stack.py # 3.にて説明
├── requirements-dev.txt
├── requirements.txt
├── source.bat
└── tests

2. Locust環境向けスタック作成

今回はLocust用とNginx用の2つスタックを作成します。まずはLocust用のスタックのソースコードを作っていきます。

locust_on_ecs/locust_on_ecs_stack.py
from aws_cdk import (
    RemovalPolicy,
    Stack,
    aws_ec2 as ec2,
    aws_ecs as ecs,
    aws_ecr as ecr,
    aws_logs as logs,
)
from constructs import Construct

vpc_cidr = "10.0.0.0/16"
repository_name = "locustio/locust/custom"

class LocustOnEcsStack(Stack):
    
    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # [1] ECR Repository
        repository = ecr.Repository(self, "Repository",
            repository_name=repository_name
        )
        repository.apply_removal_policy(RemovalPolicy.DESTROY)

        # [2] Create VPC (Public Only)
        vpc = ec2.Vpc(self, "VPC",
            cidr=vpc_cidr,
            max_azs=2,
            subnet_configuration=[
                ec2.SubnetConfiguration(
                    cidr_mask=24,
                    name='public',
                    subnet_type=ec2.SubnetType.PUBLIC,
                )
            ],
        )

        # [3] Create ECS Cluster
        cluster = ecs.Cluster(self, "Cluster",
            vpc=vpc
        )

        # [4] Create Cloud Map NameSpace and ECS Cluster Setting (for ECS Service Connect)
        namespace = cluster.add_default_cloud_map_namespace(
            name="local"
        )

        # [5] Create Log Group
        log_group = logs.LogGroup(self, "LogGroup",
            log_group_name="locust-logs"
        )
        log_group.apply_removal_policy(RemovalPolicy.DESTROY)

        # [6] Create ECS Task Definition for Locust Master
        task_definition_master = ecs.FargateTaskDefinition(self, "TaskDefinitionForLocustMaster",
            memory_limit_mib =512,
            cpu=256,
        )
        task_definition_master.add_container("locust-master",
            image=ecs.ContainerImage.from_ecr_repository(repository), # Custom image(from ECR)
            port_mappings=[
                ecs.PortMapping(
                    container_port = 8089,
                    # container_port = 80,
                ),
                ecs.PortMapping(
                    name="master-and-worker-connect-1",
                    container_port = 5557,
                ),
                ecs.PortMapping(
                    name="master-and-worker-connect-2",
                    container_port = 5558,
                )
            ],
            command=[
                "-f",
                "/mnt/locust/locustfile.py",
                "--master",
                # "-P",
                # "80",
            ],
            logging=ecs.LogDrivers.aws_logs(
                log_group=log_group,
                stream_prefix="locust-master"
            ),
        )

        # [7] Create ECS Task Definition for Locust Worker 
        task_definition_worker = ecs.FargateTaskDefinition(self, "TaskDefinitionForLocustWorker",
            memory_limit_mib=512,
            cpu=256,
        )
        task_definition_worker.add_container("web",
            image=ecs.ContainerImage.from_ecr_repository(repository), # Custom image(from ECR)
            command=[
                "-f",
                "/mnt/locust/locustfile.py",
                "--worker",
                "--master-host",
                "locust-master",
            ],
            logging=ecs.LogDrivers.aws_logs(
                log_group=log_group,
                stream_prefix="locust-worker"
            ),
        )

        # [8] Create ECS Service for Locust Master
        service_master = ecs.FargateService(self, "ServiceMaster",
            cluster=cluster,
            task_definition=task_definition_master,
            assign_public_ip=True,
            desired_count=0,
            service_connect_configuration=ecs.ServiceConnectProps(
                services=[
                    ecs.ServiceConnectService(
                        port_mapping_name="master-and-worker-connect-1",
                        dns_name="locust-master",
                        port=5557,
                    ),
                    ecs.ServiceConnectService(
                        port_mapping_name="master-and-worker-connect-2",
                        dns_name="locust-master",
                        port=5558,
                    ),
                ],
                log_driver=ecs.LogDrivers.aws_logs(
                    log_group=log_group,
                    stream_prefix="service-connect-traffic-master"
                ),
            )
        )
        service_master.node.add_dependency(namespace)

        # [9] Create ECS Service for Locust Worker
        service_worker = ecs.FargateService(self, "ServiceWorker",
            cluster=cluster,
            task_definition=task_definition_worker,
            assign_public_ip=True,
            desired_count=0,
        )
        service_worker.enable_service_connect()
        service_worker.node.add_dependency(namespace)

        # [10] Update ECS Service SecurityGroups
        service_master.connections.allow_from_any_ipv4(ec2.Port.tcp(8089))
        service_master.connections.allow_from(service_worker.connections, ec2.Port.tcp(5557))
        service_master.connections.allow_from(service_worker.connections, ec2.Port.tcp(5558))

以下にソースコードの内容を説明します。細かく説明しておりますので少し長くなりますが、ご容赦ください。

  • [1] ECRにコンテナリポジトリ作成
    Locustのコンテナイメージを自前で利用するため、それを管理するコンテナリポジトリをECR上に作成します。後ほどこちらにイメージをPushします。
  • [2] VPC作成
    ECS Cluster内にコンテナを起動させるためには、まずネットワーク環境が必要になりますので、VPCを作成します。
    ECS Clusterのコンストラクタで自動生成もできますが、NAT Gatewayを利用したリッチな構成が爆誕してしまいますので、今回は節約構成(Public Subnetのみ)を自前で作成します。
  • [3] ECS Cluster作成
    [2]で構築したVPCを割り当て作成します。それだけです。
  • [4] Cloud MapName Space作成+ECS Clusterに紐づけ
    Service Connectを使用するために、Cloud MapName Spaceを作成します。
    そして作成したName Spaceは、[3]で作成したECS Clusterにて利用でできるように設定します。
  • [5] CloudWatch LogsLog Group作成
    ECS内で実行するコンテナ群から出力されるログの送信先となるCloud Watch LogsLog Groupを作成します。
    こちらは事前に作成せずともECSのタスク定義側で自動生成することもできるのですが、自動生成されたものはCDKで環境削除した際に残ってしまうため、ゴミが残らないように自前で作成しています。
  • [6] Fargate向けTaskDefinition作成(Locust Master用)
    LocustMaster機が稼働するタスク定義(TaskDefinition)を作成します。
    Master機はLocustのWebUIが稼働するポート(8089)とWorkerとのやり取りを行うポート5557 5558を利用しますので、それらをポートマッピングに定義します。なお、Service Connectで利用するECS内部のポート(今回は5557 5558)はECS Serviceでの設定でnameが必要になりますので、各ポートマッピングにnameをそれぞれ定義してください。(重複NG)
    imageには、[1]で作成したECRのコンテナリポジトリを利用するよう定義をしています。この定義をすると、タスク定義のタスク実行ロールが作成され、ECRからイメージをPullする権限が自動的に付与されます。
    commandには、このコンテナがMaster機として稼働するおまじないを実行しています。
    loggingには、[5]で作成したLog Groupのログを出力するような定義をしています。この定義をすると、タスク定義のタスク実行ロールが作成され、CloudWatch Logsへログを送信する権限が自動的に付与されます。
  • [7] Fargate向けTaskDefinition作成(Locust Worker用)
    LocustWorker機が稼働するタスク定義(TaskDefinition)を作成します。
    commandには、このコンテナがWorker機として稼働するおまじないを実行しています。
    ここで重要なのが、Master機に対する接続先の指定です。今回はlocust-masterというDNS Nameを利用しますので、そちらを指定しています。(後述で説明します。)
    その他は[6]と同じ設定ですが、Worker機はMaster機に対して接続しに行き、そのコネクション内で通信をする仕様のため、Master機が起点となる接続はありません(maybe...もし誤ってたらご指摘ください)。そのため、Worker機のポートマッピング設定は不要となります。
  • [8] Fargate向けECS Service作成(Locust Master用)
    LocustMaster機を動かすためのECS Serviceを作成します。
    外部からのアクセスしたいため、assign_public_ipを有効化しています。
    現段階では[1]に利用するコンテナイメージをPushしていないので、desired_countはいったん0にしています。(デフォルトは1で自動起動してしまいますのでご注意を。)
    service_connect_configurationではECS Cluster内でECS Service間でマッピングできるよう定義していきます。定義の仕方としては、[6]で定義したポートマッピングのnameに関連付けさせるよう、ポートやDiscovery NameDNS Nameを指定します。なお、Discovery Nameはポート単位で名前を分けなければならず、今回のように2つ以上のポートを同一名称で利用した場合にはDNS Nameを利用するのがよさそう(maybe...)なので、5557 5558どちらも同じDNS Namelocust-master)を指定しています。
    また、Service Connectのログを出力することができますので、今回は出力するように定義しています。
  • [9] Fargate向けECS Service作成(Locust Worker用)
    LocustWorker機を動かすためのECS Serviceを作成します。
    基本的には[8]と同じですが、[7]で説明した通り、Master機が起点となる接続はありませんので、[8]のようなマッピング定義は不要です。ただし、クライアントとして利用するだけでもService Connectの有効化を行わないとMaster機に接続できないので、必ずservice_worker.enable_service_connect()を定義してください。(ここが今回のドはまりポイントでした…)
  • [10]各ECS Serviceに適用されたSecurity Group修正
    最後に各ECS ServiceSecurity Groupを設定します。
    ECS Serviceでは自動的にSecurity Groupを構築してくれますが、デフォルトでは、アウトバウンドトラフィックがすべて許可されているものが作成されるようです。今回は、以下のアクセスが必要となるため、各Security Groupにルールを追加していきます。
    • Master機へブラウザアクセスできるよう8089ポートをフル開放
    • Worker機がMaster機へ5557 5558ポートで接続するためにMaster機側のインバウンドルール追加

3. Nginx環境向けスタック作成

次にNginx用のスタックのソースコードを作っていきます。
こちらは特に説明するところはないため、詳細は割愛しますが、NginxのコンテナイメージはAWSが提供しているDockerリポジトリAmazon ECR Public GalleryにあるNginxの公式イメージpublic.ecr.aws/nginx/nginx:1-alpine-perlを利用しています。Docker Hubのイメージを利用するのもありですが、Docker HubはPull制限がありますので、制限が気になる方はAmazon ECR Public Galleryを利用することをお勧めします。

nginx_on_ecs/nginx_on_ecs_stack.py
from aws_cdk import (
    RemovalPolicy,
    Stack,
    aws_ec2 as ec2,
    aws_ecs as ecs,
    aws_logs as logs,
)
from constructs import Construct

vpc_cidr = "10.0.0.0/16"

class NginxOnEcsStack(Stack):
    
    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # [1] Create VPC
        vpc = ec2.Vpc(self, "VPC",
            cidr=vpc_cidr,
            max_azs=2,
            subnet_configuration=[
                ec2.SubnetConfiguration(
                    cidr_mask=24,
                    name='public',
                    subnet_type=ec2.SubnetType.PUBLIC,
                )
            ],
        )

        # [2] Create ECS Cluster
        cluster = ecs.Cluster(self, "Cluster",
            vpc=vpc
        )

        # [3] Create Log Group
        log_group = logs.LogGroup(self, "LogGroup",
            log_group_name="nginx-logs"
        )
        log_group.apply_removal_policy(RemovalPolicy.DESTROY)

        # [4] Create ECS Task Definition for Nginx
        task_definition = ecs.FargateTaskDefinition(self, "TaskDefinition",
            memory_limit_mib =512,
            cpu=256,
        )
        task_definition.add_container("nginx",
            image=ecs.ContainerImage.from_registry('public.ecr.aws/nginx/nginx:1-alpine-perl'), # from Amazon ECR Public Gallery
            logging=ecs.LogDrivers.aws_logs(
                log_group=log_group,
                stream_prefix="nginx"
            ),
        )

        # [5] Create ECS Service for Nginx
        service = ecs.FargateService(self, "Service",
            cluster=cluster,
            task_definition=task_definition,
            assign_public_ip=True,
            desired_count=1,
        )

        # [6] Update ECS Service SecurityGroups
        service.connections.allow_from_any_ipv4(ec2.Port.tcp(80))

4. 環境デプロイ

AWSに環境をデプロイしていきます。デプロイする前に、今回利用するapp.pyの内容を説明します。

app.py
#!/usr/bin/env python3
import os
import aws_cdk as cdk

from locust_on_ecs.locust_on_ecs_stack import LocustOnEcsStack
from nginx_on_ecs.nginx_on_ecs_stack import NginxOnEcsStack

app = cdk.App()
LocustOnEcsStack(app, "LocustOnEcsStack",
    env=cdk.Environment(account=os.getenv('CDK_DEFAULT_ACCOUNT'),
        region=os.getenv('CDK_DEFAULT_REGION')),
)
NginxOnEcsStack(app, "NginxOnEcsStack",
    env=cdk.Environment(account=os.getenv('CDK_DEFAULT_ACCOUNT'),
        region=os.getenv('CDK_DEFAULT_REGION')),
)
cdk.Tags.of(app).add("User", "MatsuMikan")
app.synth()

今回はLocust用とNginx用でスタックを分けています。そのため、それぞれ二つのスタックを定義し、デプロイ先のAWSアカウントIDやリージョンを指定するようにしています。また、おまけとしてすべてのリソースに自分の名前をタグ付けするようにもしています。
では、デプロイしていきます。一度もCDKを実行したことがない場合はcdk bootstrapのコマンドを行う必要がありますが、私はすでに実施済みなので省略し、今回利用するAWSアカウントIDとリージョンを環境変数に設定したうえで、すべてのスタックをデプロイするコマンドを実行します。

$ export CDK_DEFAULT_ACCOUNT=0123456789AB # Deploy to AWS Account ID
$ export CDK_DEFAULT_REGION=ap-northeast-1 # Deploy to Region
$ cdk deploy --all # --all オプション付与でapp.pyに定義したスタックを一括デプロイ

問題なくデプロイしたことは、cdkコマンドの結果もしくはコンソール上で確認できます。
image.png

5. LocustのDockerイメージ作成+ECRにPush

ここからはLocustのコンテナイメージ作成していきます。
今回はLocust公式が提供しているイメージlocustio/locustを利用し、自前で作成したテストコードlocustfile.pyをイメージの中に内包します。

locust_docker/Dockerfile
FROM locustio/locust
WORKDIR /mnt/locust
COPY *.py /mnt/locust/

以下はLocust上で実行するテストコードです。Locustの中身についてはあまり深くは触れませんが、以下の内容が含まれています。

  • WebUIで指定したHostGetメソッドでアクセスする
  • 2~5秒の間でランダム秒数でタスク(Hostへのアクセス)を繰り返し実行する
  • Locust側の標準ログに各ユーザが何回Hostへのアクセスしたかをわかるように出力する(ユーザはWebUIのユーザ数により生成)
locust_docker/locustfile.py
from locust import HttpUser, task, events, between
from locust.runners import MasterRunner, WorkerRunner

usernames = []

def setup_test_users(environment, msg, **kwargs):
    usernames.extend(map(lambda u: u["name"], msg.data))


@events.init.add_listener
def on_locust_init(environment, **_kwargs):
    if not isinstance(environment.runner, MasterRunner):
        environment.runner.register_message("test_users", setup_test_users)


@events.test_start.add_listener
def on_test_start(environment, **_kwargs):
    if not isinstance(environment.runner, WorkerRunner):
        users = []
        for i in range(environment.runner.target_user_count):
            users.append({"name": f"User{i+1}"})

        worker_count = environment.runner.worker_count
        chunk_size = int(len(users) / worker_count)

        for i, worker in enumerate(environment.runner.clients):
            start_index = i * chunk_size

            if i + 1 < worker_count:
                end_index = start_index + chunk_size
            else:
                end_index = len(users)

            data = users[start_index:end_index]
            environment.runner.send_message("test_users", data, worker)

class WebAccessUser(HttpUser):
    seq = 0
    wait_time = between(2, 5)

    def __init__(self, parent):
        self.username = usernames.pop()
        super().__init__(parent)

    @task
    def web_access(self):
        self.seq += 1
        self.client.get("/")
        print({
            "Username": self.username,
            "Sequence": str(self.seq)
        })

上記ファイル群をビルドしてイメージを作成し、Locust環境向けスタック内で準備したECRのリポジトリにPushします。

$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 0123456789AB.dkr.ecr.ap-northeast-1.amazonaws.com
$ docker build -t locustio/locust/custom .
$ docker tag locustio/locust/custom:latest 0123456789AB.dkr.ecr.ap-northeast-1.amazonaws.com/locustio/locust/custom:latest
$ docker push 0123456789AB.dkr.ecr.ap-northeast-1.amazonaws.com/locustio/locust/custom:latest

6. ECS Serviceにてタスク起動

いよいよ準備が整ったので、LocustECS Serviceにてタスクを起動していきます。
今回はCDKでタスク数を変更し、再度deployしていきます。

locust_on_ecs/locust_on_ecs_stack.py(修正箇所のみ抜粋)
# {前略}

# [8] Create ECS Service for Locust Master
service_master = ecs.FargateService(self, "ServiceMaster",
    cluster=cluster,
    task_definition=task_definition_master,
    assign_public_ip=True,
-    desired_count=0,
+    desired_count=1,

# {中略}

# [9] Create ECS Service for Locust Worker
service_worker = ecs.FargateService(self, "ServiceWorker",
    cluster=cluster,
    task_definition=task_definition_worker,
    assign_public_ip=True,
-    desired_count=0,
+    desired_count=2,

# {後略}
$ cdk deploy LocustOnEcsStack

デプロイ結果はこちら。
Locust側は合計3つのタスク(Master:1,Worker:2)が無事に起動していることが確認できます。
image.png

7. ブラウザ経由でLocustへアクセス+Nginxへのアクセス実施

まずはNginxにブラウザ経由でアクセスしてみます。

  • http://{Nginxのタスクのパブリック IP}/
    image.png
    無事アクセスできました。このURLはメモしておきます。
    続きましてLocustへアクセスします。
  • http://{LocustのMaster機のタスクのパブリック IP}:8089/
    image.png
    こちらもアクセスできました。(初回アクセス時のスクショを取り忘れたので若干残像が残っていますが)
    右上にひょうじされているWORKERSの数字が2と表示されていますが、これはECS Serviceでデプロイしたタスク数と一致しており、Master機とWorker機が無事に接続できていることが確認できます。
    それでは実際に動かしてみましょう。
    LocustのWebUIでは、アクセスするユーザ数やホスト、実行時間などを指定することができますので、今回は上記画面の通りに設定して動かしてみます。
    そして、動かしてみた結果(チャート)がこちら。
    image.png
    Spawn rateで指定した時間に合わせてじわじわアクセスユーザを増やし、指定したユーザ数10のアクセスが維持された状態で定期的にリクエストが飛んでいたようです。
    次はLocustWorker機のログを見てみましょう。
    image.png
    ログ出力の仕方が雑だったのでわかりづらいですが、User1~10が、Nginxに1分間にアクセスしていたであろう形跡が確認できました。
    Worker機が2台いたのですが、2台それぞれにUserが分散されて、各Worker機からアクセスしていたようです。(Logstreamがタスク単位で払い出されるため、そちらとUserの番号を合わせて見ていくとそんな動きをしているようでした。)
    Nginx側も同時刻にアクセスログが動いており、Locustからのアクセスを受信できていることも確認できました。
    image.png
    という感じで、すべての環境をCDKを利用して構築(デプロイ)し、想定通りに動くところまで確認することができました。

お片付け

やりたいことは終わりましたので、環境を削除します。
早速cdk destroy --allで全スタック一括デストロイしたい気持ちはやまやまですが、今回ECRにイメージをPushしているため、まずはこちらを削除するところから始まります。
ECRS3と一緒で、データを空にしないとリポジトリを削除できない制限がかかっておりますので、まずはイメージを削除します。

$ aws ecr batch-delete-image --repository-name locustio/locust/custom --image-ids imageTag=latest

そして環境をすべてデストロイします。

$ cdk destroy --all
Are you sure you want to delete: NginxOnEcsStack, LocustOnEcsStack (y/n)? y
NginxOnEcsStack: destroying... [1/2]

 ✅  NginxOnEcsStack: destroyed
LocustOnEcsStack: destroying... [2/2]

 ✅  LocustOnEcsStack: destroyed

きれいになりました。

【補足】Service Connectについて

ECSのタスクを確認したところ、タスク定義で指定したLocustのコンテナ(以下の画像ではwebと表記)以外に、ecs-service-connect-xxxなるコンテナが動いていました。
image.png
おそらくこちらがService Connectとしての役目を果たす者(?)です。
Service Connectログを見てみると…なにやらEnvoyが動いている模様でした。
image.png
また、Master機のService Connectをコンソール上で確認したところ、以下のようになっていました。
image.png
Service ConnectでECS内の名前解決する方法は二つあり、検出(Discovery Name)とDNS Nameのいずれかを利用することができそうです。
Discovery Nameはデフォルトではポートマッピング名を使用していますが、変更できます。
ただし、既に説明した通り、Discovery Nameポート単位で一意になるため、今回のような複数のポートで同一の名称を利用したいケースの場合はDNS Nameを利用するのがよさそうです。
ちなみに、Discovery Nameを利用する場合は、Discovery Name.Name Spaceという形式で利用することになるのでご注意ください。

【余談】その他にはまったこと

実は今回LocostMaster機のWebUIのポートをデフォルトの8089ではなく80に変更したかったのですが、うまくいきませんでした。ローカル上ではうまくいったのですが、Fargateでは権限的な問題で指定できないようです。
そのため、80ポートなど他のポートでWebUIにアクセスしたい場合は、Master機の前にALBを利用してフォワーディングさせるしかなさそうです。(もし別の手段がありましたらご教示くださいませ)
ちなみに80ポートを利用しようとしてダメだったログはこちら。

[2023-02-19 11:35:08,755] ip-10-0-1-58.ap-northeast-1.compute.internal/INFO/locust.main: Starting web interface at http://0.0.0.0:80 (accepting connections from all network interfaces)
[2023-02-19 11:35:08,840] ip-10-0-1-58.ap-northeast-1.compute.internal/INFO/locust.main: Starting Locust 2.14.2
Traceback (most recent call last):
  File "src/gevent/greenlet.py", line 908, in gevent._gevent_cgreenlet.Greenlet.run
  File "/opt/venv/lib/python3.10/site-packages/locust/web.py", line 473, in start_server
    self.server.serve_forever()
  File "/opt/venv/lib/python3.10/site-packages/gevent/baseserver.py", line 398, in serve_forever
    self.start()
  File "/opt/venv/lib/python3.10/site-packages/gevent/baseserver.py", line 336, in start
    self.init_socket()
  File "/opt/venv/lib/python3.10/site-packages/gevent/pywsgi.py", line 1545, in init_socket
    StreamServer.init_socket(self)
  File "/opt/venv/lib/python3.10/site-packages/gevent/server.py", line 180, in init_socket
    self.socket = self.get_listener(self.address, self.backlog, self.family)
  File "/opt/venv/lib/python3.10/site-packages/gevent/server.py", line 192, in get_listener
    return _tcp_listener(address, backlog=backlog, reuse_addr=cls.reuse_addr, family=family)
  File "/opt/venv/lib/python3.10/site-packages/gevent/server.py", line 288, in _tcp_listener
    sock.bind(address)
  File "/opt/venv/lib/python3.10/site-packages/gevent/_socketcommon.py", line 563, in bind
    return self._sock.bind(address)
PermissionError: [Errno 13] Permission denied: ('', 80)
2023-02-19T11:35:08Z <Greenlet at 0x7ff58e92bf60: <bound method WebUI.start_server of <locust.web.WebUI object at 0x7ff58e8b7130>>> failed with PermissionError
[2023-02-19 11:35:08,843] ip-10-0-1-58.ap-northeast-1.compute.internal/CRITICAL/locust.web: Unhandled exception in greenlet: <Greenlet at 0x7ff58e92bf60: <bound method WebUI.start_server of <locust.web.WebUI object at 0x7ff58e8b7130>>>
Traceback (most recent call last):
  File "src/gevent/greenlet.py", line 908, in gevent._gevent_cgreenlet.Greenlet.run
  File "/opt/venv/lib/python3.10/site-packages/locust/web.py", line 473, in start_server
    self.server.serve_forever()
  File "/opt/venv/lib/python3.10/site-packages/gevent/baseserver.py", line 398, in serve_forever
    self.start()
  File "/opt/venv/lib/python3.10/site-packages/gevent/baseserver.py", line 336, in start
    self.init_socket()
  File "/opt/venv/lib/python3.10/site-packages/gevent/pywsgi.py", line 1545, in init_socket
    StreamServer.init_socket(self)
  File "/opt/venv/lib/python3.10/site-packages/gevent/server.py", line 180, in init_socket
    self.socket = self.get_listener(self.address, self.backlog, self.family)
  File "/opt/venv/lib/python3.10/site-packages/gevent/server.py", line 192, in get_listener
    return _tcp_listener(address, backlog=backlog, reuse_addr=cls.reuse_addr, family=family)
  File "/opt/venv/lib/python3.10/site-packages/gevent/server.py", line 288, in _tcp_listener
    sock.bind(address)
  File "/opt/venv/lib/python3.10/site-packages/gevent/_socketcommon.py", line 563, in bind
    return self._sock.bind(address)
PermissionError: [Errno 13] Permission denied: ('', 80)
[2023-02-19 11:35:09,345] ip-10-0-1-58.ap-northeast-1.compute.internal/INFO/locust.main: Shutting down (exit code 2)

さいごに/感想

久しぶりにCDKを触ったのですが、やはり最低限のコード記述だけでいい感じに環境構築できるのは大変素晴らしい!と改めて実感しました。(CloudFormationは細かく定義しなければならないので、それに比べると本当に楽ちんでした…)
そしてSevice Connectの登場で、ECS Service間の通信設定がかなり簡単にできることもわかり、ECS ユーザにはかなり良いアップデートだったのではないでしょうか。
ちなみにService Connectはリリースされてから3か月程度しかたっていないと思うのですが、早い段階でCDK経由で構築できるようになっていたので、AWSの中の皆様のお仕事の速さにも驚きです。

こんなに便利なツールCDKをもっと積極的に勉強して使っていかねばと思いつつ、その前に自身のPython言語の理解の浅さを痛感した(ドキュメントの解読に苦労していた)ため、Python言語力(それともコーディング力?)をまずは養う必要があるのでは…と感じた次第でした。

こちらからは、以上です。

参考URL

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?