AWS ECS 服務發現與負載平衡

AWS ECS Service Discovery and Load Balancing

ECS 服務發現概述

在微服務架構中,服務發現是一個關鍵元件,它允許服務在不需要硬編碼 IP 位址或端點的情況下相互尋找和通訊。AWS ECS 提供了與 AWS Cloud Map 整合的原生服務發現功能,讓容器化應用程式能夠透過 DNS 或 API 呼叫自動發現其他服務。

服務發現的核心優勢

  • 動態服務註冊:當 ECS 任務啟動時,自動註冊到服務發現命名空間
  • 自動健康檢查:不健康的任務會自動從服務發現中移除
  • DNS 解析:透過 DNS 名稱存取服務,無需管理 IP 位址
  • 跨服務通訊:簡化微服務間的網路通訊配置
  • 與負載平衡器整合:可同時使用服務發現和負載平衡器

Cloud Map 整合

AWS Cloud Map 是一個雲端資源發現服務,與 ECS 緊密整合。它提供了一個統一的方式來命名和發現所有雲端資源。

Cloud Map 架構元件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
┌─────────────────────────────────────────────────────────┐
│                    AWS Cloud Map                        │
│  ┌─────────────────────────────────────────────────┐   │
│  │              命名空間 (Namespace)                │   │
│  │  ┌─────────────┐    ┌─────────────┐            │   │
│  │  │   服務 A    │    │   服務 B    │            │   │
│  │  │ ┌─────────┐ │    │ ┌─────────┐ │            │   │
│  │  │ │ 實例 1  │ │    │ │ 實例 1  │ │            │   │
│  │  │ │ 實例 2  │ │    │ │ 實例 2  │ │            │   │
│  │  │ └─────────┘ │    │ └─────────┘ │            │   │
│  │  └─────────────┘    └─────────────┘            │   │
│  └─────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

啟用 Cloud Map 服務

1
2
3
4
5
6
# 確認 Cloud Map 服務可用
aws servicediscovery list-namespaces

# 查看特定命名空間詳情
aws servicediscovery get-namespace \
  --id ns-xxxxxxxxxxxxxxxxx

建立命名空間

命名空間是服務發現的最上層容器,定義了服務的 DNS 域名。AWS Cloud Map 支援兩種類型的命名空間。

DNS 命名空間類型

  1. 私有 DNS 命名空間:僅在 VPC 內部可解析
  2. 公有 DNS 命名空間:可透過公網 DNS 解析

建立私有 DNS 命名空間

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 建立私有 DNS 命名空間
aws servicediscovery create-private-dns-namespace \
  --name internal.myapp.local \
  --vpc vpc-0123456789abcdef0 \
  --description "Internal service discovery namespace"

# 取得命名空間 ID
NAMESPACE_ID=$(aws servicediscovery list-namespaces \
  --query "Namespaces[?Name=='internal.myapp.local'].Id" \
  --output text)

echo "Namespace ID: $NAMESPACE_ID"

建立公有 DNS 命名空間

1
2
3
4
# 建立公有 DNS 命名空間(需要已存在的 Route 53 託管區域)
aws servicediscovery create-public-dns-namespace \
  --name services.example.com \
  --description "Public service discovery namespace"

查看命名空間狀態

1
2
3
4
5
6
7
8
# 列出所有命名空間
aws servicediscovery list-namespaces \
  --query "Namespaces[*].{Name:Name,Id:Id,Type:Type}" \
  --output table

# 取得命名空間詳細資訊
aws servicediscovery get-namespace \
  --id ns-xxxxxxxxxxxxxxxxx

服務註冊

在 Cloud Map 命名空間中建立服務定義,用於註冊 ECS 任務實例。

建立 Cloud Map 服務

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 建立支援 DNS 的服務
aws servicediscovery create-service \
  --name api-service \
  --namespace-id ns-xxxxxxxxxxxxxxxxx \
  --dns-config '{
    "NamespaceId": "ns-xxxxxxxxxxxxxxxxx",
    "DnsRecords": [
      {
        "Type": "A",
        "TTL": 10
      }
    ],
    "RoutingPolicy": "MULTIVALUE"
  }' \
  --health-check-custom-config '{
    "FailureThreshold": 1
  }'

# 取得服務 ID
SERVICE_ID=$(aws servicediscovery list-services \
  --query "Services[?Name=='api-service'].Id" \
  --output text)

建立 ECS Service 並啟用服務發現

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 建立 ECS Cluster
aws ecs create-cluster --cluster-name my-app-cluster

# 建立整合服務發現的 ECS Service
aws ecs create-service \
  --cluster my-app-cluster \
  --service-name api-service \
  --task-definition api-task:1 \
  --desired-count 3 \
  --launch-type FARGATE \
  --network-configuration '{
    "awsvpcConfiguration": {
      "subnets": ["subnet-private-1a", "subnet-private-1b"],
      "securityGroups": ["sg-ecs-tasks"],
      "assignPublicIp": "DISABLED"
    }
  }' \
  --service-registries '[
    {
      "registryArn": "arn:aws:servicediscovery:ap-northeast-1:123456789012:service/srv-xxxxxxxxx",
      "containerName": "api-container",
      "containerPort": 8080
    }
  ]'

驗證服務註冊

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 列出服務實例
aws servicediscovery list-instances \
  --service-id srv-xxxxxxxxx \
  --query "Instances[*].{Id:Id,Attributes:Attributes}" \
  --output table

# 使用 discover-instances API 發現服務
aws servicediscovery discover-instances \
  --namespace-name internal.myapp.local \
  --service-name api-service \
  --query-parameters HealthStatus=HEALTHY

DNS 服務發現

透過 DNS 查詢發現服務是最常見的服務發現方式,應用程式只需要知道服務的 DNS 名稱即可。

DNS 查詢範例

1
2
3
4
5
6
7
8
# 在 VPC 內的 EC2 實例或容器中執行 DNS 查詢
# 服務 DNS 格式:<service-name>.<namespace-name>

# 查詢 A 記錄
dig api-service.internal.myapp.local A +short

# 查詢 SRV 記錄(包含端口資訊)
dig api-service.internal.myapp.local SRV +short

應用程式中使用服務發現

1
2
3
4
5
# 範例:使用 curl 呼叫透過服務發現找到的服務
curl http://api-service.internal.myapp.local:8080/health

# 在容器內使用環境變數配置服務端點
export API_ENDPOINT="http://api-service.internal.myapp.local:8080"

DNS TTL 設定考量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 更新服務的 DNS TTL 設定
aws servicediscovery update-service \
  --id srv-xxxxxxxxx \
  --service '{
    "DnsConfig": {
      "DnsRecords": [
        {
          "Type": "A",
          "TTL": 5
        }
      ]
    }
  }'

ALB 整合

Application Load Balancer(ALB)提供進階的 Layer 7 負載平衡功能,適合 HTTP/HTTPS 應用程式。

建立 ALB 與目標群組

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 建立 Application Load Balancer
aws elbv2 create-load-balancer \
  --name my-app-alb \
  --type application \
  --scheme internet-facing \
  --subnets subnet-public-1a subnet-public-1b \
  --security-groups sg-alb

# 建立目標群組(使用 IP 目標類型供 Fargate 使用)
aws elbv2 create-target-group \
  --name my-app-tg \
  --protocol HTTP \
  --port 8080 \
  --vpc-id vpc-0123456789abcdef0 \
  --target-type ip \
  --health-check-protocol HTTP \
  --health-check-path /health \
  --health-check-interval-seconds 30 \
  --health-check-timeout-seconds 5 \
  --healthy-threshold-count 2 \
  --unhealthy-threshold-count 3

# 建立監聽器
aws elbv2 create-listener \
  --load-balancer-arn arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:loadbalancer/app/my-app-alb/xxx \
  --protocol HTTP \
  --port 80 \
  --default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:targetgroup/my-app-tg/xxx

建立整合 ALB 的 ECS Service

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 建立使用 ALB 的 ECS Service
aws ecs create-service \
  --cluster my-app-cluster \
  --service-name web-frontend \
  --task-definition web-task:1 \
  --desired-count 3 \
  --launch-type FARGATE \
  --load-balancers '[
    {
      "targetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:targetgroup/my-app-tg/xxx",
      "containerName": "web-container",
      "containerPort": 8080
    }
  ]' \
  --health-check-grace-period-seconds 60 \
  --network-configuration '{
    "awsvpcConfiguration": {
      "subnets": ["subnet-private-1a", "subnet-private-1b"],
      "securityGroups": ["sg-ecs-tasks"],
      "assignPublicIp": "DISABLED"
    }
  }'

設定路徑型路由

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 建立額外的目標群組
aws elbv2 create-target-group \
  --name api-tg \
  --protocol HTTP \
  --port 8080 \
  --vpc-id vpc-0123456789abcdef0 \
  --target-type ip

# 建立路徑型路由規則
aws elbv2 create-rule \
  --listener-arn arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:listener/app/my-app-alb/xxx/xxx \
  --priority 10 \
  --conditions '[{"Field":"path-pattern","Values":["/api/*"]}]' \
  --actions '[{"Type":"forward","TargetGroupArn":"arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:targetgroup/api-tg/xxx"}]'

NLB 整合

Network Load Balancer(NLB)提供 Layer 4 負載平衡,適合需要極低延遲或 TCP/UDP 協定的應用程式。

建立 NLB

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 建立 Network Load Balancer
aws elbv2 create-load-balancer \
  --name my-app-nlb \
  --type network \
  --scheme internet-facing \
  --subnets subnet-public-1a subnet-public-1b

# 建立 TCP 目標群組
aws elbv2 create-target-group \
  --name my-app-nlb-tg \
  --protocol TCP \
  --port 8080 \
  --vpc-id vpc-0123456789abcdef0 \
  --target-type ip \
  --health-check-protocol TCP \
  --health-check-interval-seconds 10 \
  --healthy-threshold-count 2 \
  --unhealthy-threshold-count 2

# 建立 TCP 監聽器
aws elbv2 create-listener \
  --load-balancer-arn arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:loadbalancer/net/my-app-nlb/xxx \
  --protocol TCP \
  --port 80 \
  --default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:targetgroup/my-app-nlb-tg/xxx

建立整合 NLB 的 ECS Service

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 建立使用 NLB 的 ECS Service
aws ecs create-service \
  --cluster my-app-cluster \
  --service-name grpc-service \
  --task-definition grpc-task:1 \
  --desired-count 2 \
  --launch-type FARGATE \
  --load-balancers '[
    {
      "targetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:targetgroup/my-app-nlb-tg/xxx",
      "containerName": "grpc-container",
      "containerPort": 50051
    }
  ]' \
  --network-configuration '{
    "awsvpcConfiguration": {
      "subnets": ["subnet-private-1a", "subnet-private-1b"],
      "securityGroups": ["sg-ecs-tasks"],
      "assignPublicIp": "DISABLED"
    }
  }'

健康檢查設定

健康檢查確保只有健康的任務接收流量,對於服務發現和負載平衡都至關重要。

容器層級健康檢查

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "containerDefinitions": [
    {
      "name": "app",
      "image": "my-app:latest",
      "healthCheck": {
        "command": ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"],
        "interval": 30,
        "timeout": 5,
        "retries": 3,
        "startPeriod": 60
      }
    }
  ]
}

ALB 健康檢查設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 更新目標群組健康檢查設定
aws elbv2 modify-target-group \
  --target-group-arn arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:targetgroup/my-app-tg/xxx \
  --health-check-protocol HTTP \
  --health-check-path /health \
  --health-check-interval-seconds 15 \
  --health-check-timeout-seconds 5 \
  --healthy-threshold-count 2 \
  --unhealthy-threshold-count 3 \
  --matcher '{"HttpCode":"200-299"}'

Cloud Map 健康檢查設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 建立帶有自訂健康檢查的 Cloud Map 服務
aws servicediscovery create-service \
  --name monitored-service \
  --namespace-id ns-xxxxxxxxx \
  --dns-config '{
    "DnsRecords": [{"Type": "A", "TTL": 10}],
    "RoutingPolicy": "WEIGHTED"
  }' \
  --health-check-config '{
    "Type": "HTTP",
    "ResourcePath": "/health",
    "FailureThreshold": 3
  }'

健康檢查寬限期

1
2
3
4
5
# 設定健康檢查寬限期,避免容器啟動期間被標記為不健康
aws ecs update-service \
  --cluster my-app-cluster \
  --service web-frontend \
  --health-check-grace-period-seconds 120

最佳實踐

服務發現最佳實踐

  • 使用私有命名空間:內部服務間通訊應使用私有 DNS 命名空間
  • 設定適當的 DNS TTL:較低的 TTL 可更快反映服務變化,但會增加 DNS 查詢
  • 啟用健康檢查:確保不健康的實例自動從服務發現中移除

負載平衡最佳實踐

  • 選擇正確的負載平衡器類型:HTTP/HTTPS 用 ALB,TCP/UDP 或需要低延遲用 NLB
  • 使用多可用區域部署:確保高可用性
  • 配置連線排空:優雅處理任務終止
1
2
3
4
# 設定目標群組連線排空時間
aws elbv2 modify-target-group-attributes \
  --target-group-arn arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:targetgroup/my-app-tg/xxx \
  --attributes Key=deregistration_delay.timeout_seconds,Value=30

網路安全最佳實踐

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 設定安全群組只允許來自負載平衡器的流量
aws ec2 authorize-security-group-ingress \
  --group-id sg-ecs-tasks \
  --protocol tcp \
  --port 8080 \
  --source-group sg-alb

# 為內部服務發現設定安全群組規則
aws ec2 authorize-security-group-ingress \
  --group-id sg-ecs-tasks \
  --protocol tcp \
  --port 8080 \
  --source-group sg-ecs-tasks

監控與告警

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 建立 ALB 目標健康告警
aws cloudwatch put-metric-alarm \
  --alarm-name "ALB-UnHealthyHosts" \
  --metric-name "UnHealthyHostCount" \
  --namespace "AWS/ApplicationELB" \
  --statistic Average \
  --period 60 \
  --threshold 1 \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --dimensions Name=TargetGroup,Value=targetgroup/my-app-tg/xxx Name=LoadBalancer,Value=app/my-app-alb/xxx \
  --evaluation-periods 2 \
  --alarm-actions arn:aws:sns:ap-northeast-1:123456789012:my-alerts

參考資料

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy