0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ECS間通信にService Connectを使わず、Service Discoveryを選んだ理由

Posted at

おそらく誰しも一度は、なんでこんな感じになってしまったんだろう?みたいなインフラ構成に出くわしたことがあるんじゃないでしょうか。

自分の場合は、2つのアプリケーションを別のVPC上のECSで動かしていて、ECS間の通信をインターネット経由で行っているという構成が放置されていました。VPCをまたいでいるため、ECSタスク同士の通信がNAT GatewayやALBを経由しており、パフォーマンス、セキュリティ、コスト、どれをとってもあまりよくない状態でした。

スクリーンショット 2025-05-06 9.01.57.png

結論:Service Connectを断念してService Discoveryで構成した

AWSのドキュメントやECSの設定画面では、"Service Connect推奨"と書かれていて、Cloud Mapを使ったService Discovery(サービス検出)構成はなんとなく古いやり方に思えたのですが、結果的にService Connectを使わず、Service Discoveryだけで構成しました。

スクリーンショット 2025-05-05 12.54.56.png

Blue/GreenデプロイとService Connectは共存できない

構成としては、接続元のECS(以降A)から補助的な機能をもつ接続先のECS(B)に通信する形です。AのタスクはALB経由で外部公開しており、デプロイにはCodeDeployによるBlue/Green方式を採用しています。
トラフィックの切り替え制御でもしものときのロールバックが簡単だったり、最小ダウンタイムでのリリースができるのでBlue/Greenデプロイを選択したのですが、ECSのサービス定義でService Connectを有効にしてデプロイを試みたところ、以下のようなエラーが返ってきてしまいました。

InvalidParameterException: DeploymentController#type CODE_DEPLOY is not supported by ECS Service Connect.

なんと、Service ConnectはCodeDeployベースのBlue/Greenデプロイと併用できませんでした。

この時点で、自分の構成ではService Connectを採用できないと判断しました。

そもそものService Connectのよさ

Service Connectを有効にすると、Envoyというサイドカーのプロキシを各タスクに自動で入れてくれます。

  • 内部DNSの抽象化(clientAliases)
  • トラフィックの可視化(Service Graph)
  • mTLSやセキュリティポリシー強化

など、サービス間通信をモダンにしてくれます。

でも、Envoyが裏で動いているので、その分のリソース(メモリ、CPU)が必要になるというデメリットもあります。例えば、FargateだとvCPUとメモリサイズで料金が変わってくるので、リソース増でコストも上がってしまいます。

今回は接続先(B)が補助的なマイクロサービスで
、Service Graphでの可視化までは必要なしと考え、Service Discoveryだけでシンプルにいこうと判断しました。

Service ConnectとService Discoveryの簡単な比較

項目 Service Connect Service Discovery
サイドカー 必要(Envoy) 不要
通信パス ECS A → Envoy A → Envoy B → ECS B ECS A → ECS B
CPU・メモリ負荷 増える(サイドカーあり) 軽い(直接通信)
Blue/Green対応 非対応 対応可能
DNS名の簡潔さ clientAliasesで短縮可能 service.service.localなど長め

より詳細に知りたい方は、以下の記事がとても参考になります。

Service Discovery(Cloud Map)の導入手順

今回採用したService Discovery構成を組む手順を紹介します。Cloud Mapを使って、接続先のECSサービス(B)を名前解決できるようにします。

1. 名前空間(Cloud Map)の作成

まずはCloud Mapの名前空間を作成します。名前空間はサービスのドメインのようなもので、たとえば service.local のように使います。

スクリーンショット 2025-05-05 14.51.33.png

Terraformで作成する場合は以下のようになります。

resource "aws_service_discovery_private_dns_namespace" "service_local" {
  name        = "service.local"
  description = "Private DNS namespace"
  vpc         = <vpc_id>
}

2. サービスの作成

この名前空間に対して、ECSサービスB用の「サービス」を作成します。

スクリーンショット 2025-05-05 14.59.50.png

resource "aws_service_discovery_service" "service_b" {
  name         = "service-b"
  namespace_id = aws_service_discovery_private_dns_namespace.service_local.id

  dns_config {
    namespace_id = aws_service_discovery_private_dns_namespace.service_local.id
    dns_records {
      type = "A"
      ttl  = 30
    }
    routing_policy = "MULTIVALUE"
  }

  health_check_custom_config {
    failure_threshold = 3
  }
}

3. セキュリティグループの作成

接続元(ECS A)

通信元のECSタスクが、ECS Bにアクセスできるように双方向のルールを設定します。

resource "aws_security_group" "service_a_ecs_sg" {
  name        = "service-a-ecs-sg"
  description = "Security group for ECS A (caller)"
  vpc_id      = <vpc-id>
}

resource "aws_security_group_rule" "service_a_to_b_egress" {
  type                     = "egress"
  from_port                = 8080
  to_port                  = 8080
  protocol                 = "tcp"
  security_group_id        = aws_security_group.service_a_ecs_sg.id
  source_security_group_id = aws_security_group.service_b_ecs_sg.id
}

接続元(ECS B)

resource "aws_security_group" "service_b_ecs_sg" {
  name        = "service-b-ecs-sg"
  description = "Security group for ECS B (callee)"
  vpc_id      = <vpc-id>
}

resource "aws_security_group_rule" "service_b_from_a_ingress" {
  type                     = "ingress"
  from_port                = 8080
  to_port                  = 8080
  protocol                 = "tcp"
  security_group_id        = aws_security_group.service_b_ecs_sg.id
  source_security_group_id = aws_security_group.service_a_ecs_sg.id
}

# 必要であれば外部通信も許可
resource "aws_security_group_rule" "service_b_egress_all" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  security_group_id = aws_security_group.service_b_ecs_sg.id
  cidr_blocks       = ["0.0.0.0/0"]
}

4. アプリケーションの接続先を変更

ECS(A)のアプリケーションから、ECS(B)をhttp://service-b.service.local:8080のようにアクセスできるようになります。ここで:8080のようにポート番号を明示する必要がある点に注意が必要です。

5. 接続先(ECS B)のサービス/タスク定義を更新

サービス定義

接続先(ECS B)のサービス定義で、Service Discoveryに作成したCloud Mapの名前空間とサービスを登録します。

スクリーンショット 2025-05-05 17.17.01.png

ecspressoでサービス定義を管理している場合は以下を追加します。

"serviceRegistries": [
    {
        "registryArn": "arn:aws:servicediscovery:ap-northeast-1:*********:service/srv-*********"
    }
]

タスク定義

接続先ECS Bの前段にALBがなくなるので、ヘルスチェックをECS自身でできるようにタスク定義へhealthCheckを追加します。以下はコンテナポート8080を使用している例です。

  "containerDefinitions": [
    {
      (略)
      "portMappings": [
        {
          "containerPort": 8080,
          "protocol": "tcp",
        }
      ],
      "healthCheck": {
        "command": [
          "CMD-SHELL",
          "curl -f http://localhost:8080/health || exit 1"
        ],
        "interval": 30,
        "timeout": 5,
        "retries": 3,
        "startPeriod": 10
      }
    }

ヘルスチェックでcurlを叩くので、コンテナイメージにcurlが含まれていない場合は、Dockerfileにcurlをインストールする記述(ex. RUN apk add --no-cache curl)を追加します。

Service Connectの導入手順

今回はBlue/Greenデプロイとの併用ができないため採用しませんでしたが、参考までにService Connectの導入手順も紹介します。

1. 名前空間(Cloud Map)の作成

Service Discoveryと同様、service.localなどのプライベートDNS名前空間をCloud Map上に作成します。Terraformも同様です。

補足:Service Connectでは「サービス登録」を手動で行う必要はありません。ECSのデプロイ時に自動でCloud Mapに登録されます。

2. セキュリティグループの作成

Service Connectも内部ではEnvoyを経由したTCP通信のため、基本的には通常のECS間通信と同様にポート指定のセキュリティルールが必要です。

接続元(ECS A)

通信元のECSタスクが、ECS Bにアクセスできるように双方向のルールを設定します。

resource "aws_security_group" "service_a_ecs_sg" {
  name        = "service-a-ecs-sg"
  description = "Security group for ECS A (Service Connect caller)"
  vpc_id      = <vpc-id>
}

resource "aws_security_group_rule" "service_a_to_b_egress" {
  type                     = "egress"
  from_port                = 8080
  to_port                  = 8080
  protocol                 = "tcp"
  security_group_id        = aws_security_group.service_a_ecs_sg.id
  source_security_group_id = aws_security_group.service_b_ecs_sg.id
}

接続元(ECS B)

resource "aws_security_group" "service_b_ecs_sg" {
  name        = "service-b-ecs-sg"
  description = "Security group for ECS B (Service Connect callee)"
  vpc_id      = <vpc-id>
}

resource "aws_security_group_rule" "service_b_from_a_ingress" {
  type                     = "ingress"
  from_port                = 8080
  to_port                  = 8080
  protocol                 = "tcp"
  security_group_id        = aws_security_group.service_b_ecs_sg.id
  source_security_group_id = aws_security_group.service_a_ecs_sg.id
}

# 必要であれば外部通信も許可
resource "aws_security_group_rule" "service_b_egress_all" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  security_group_id = aws_security_group.service_b_ecs_sg.id
  cidr_blocks       = ["0.0.0.0/0"]
}

3. サービス定義の更新(ECS A)

コンソール画面からService Connectの設定を行います。

スクリーンショット 2025-05-05 17.56.49.png

ecspressoでサービス定義を管理している場合は、serviceConnectConfigurationを追加します。

  "serviceConnectConfiguration": {
    "enabled": true,
    "namespace": "service.local",
    "services": [
      {
        "portName": "http",
        "discoveryName": "service",
        "clientAliases": [
          {
            "port": 80,
            "dnsName": "service"
          }
        ]
      }
    ]
  },

注意:CodeDeploy(Blue/Greenデプロイ)とService Connectは併用できません。
DeploymentController#type CODE_DEPLOY is not supportedというエラーになります。

アプリケーション内の向き先変更

アプリケーション側では、以下のように clientAliases.dnsNameにあわせて通信先を指定します。

http://service

ポート番号の指定は不要です。

4. サービス定義の更新(ECS B)

ECS B側も同様にService Connect設定を行います。

スクリーンショット 2025-05-05 17.17.01.png

ecspressoの記述も同様です。

  "serviceConnectConfiguration": {
    "enabled": true,
    "namespace": "service.local",
    "services": [
      {
        "portName": "http",
        "discoveryName": "service",
        "clientAliases": [
          {
            "port": 80,
            "dnsName": "service"
          }
        ]
      }
    ]
  },

5. タスク定義の更新(ECS B)

ALB を使わない構成になるため、タスク自身がヘルスチェックを返すように構成します。

  "containerDefinitions": [
    {
      (略)
      "portMappings": [
        {
          "containerPort": 8080,
          "protocol": "tcp",
        }
      ],
      "healthCheck": {
        "command": [
          "CMD-SHELL",
          "curl -f http://localhost:8080/health || exit 1"
        ],
        "interval": 30,
        "timeout": 5,
        "retries": 3,
        "startPeriod": 10
      }
    }

ヘルスチェックでcurlを叩くので、コンテナイメージにcurlが含まれていない場合は、Dockerfileにcurlをインストールする記述(ex. RUN apk add --no-cache curl)を追加します。

まとめ

Service ConnectはBlue/Greenデプロイとの相性に制限があるものの、DNS名のシンプルさや将来的な拡張性、トラフィック可視化など多くの利点があります。

今回のように小規模でBlue/Greenが必要なケースではService Discoveryが適していますが、中〜大規模なマイクロサービス環境やセキュリティ要件が高い環境では、Service Connectを選ぶ価値があると思います。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?