🙌

AWS学びなおし(+TF)_ECS

2025/02/24に公開

AWS ECS (Elastic Container Service) は、AWS が提供するコンテナオーケストレーションサービスです。コンテナ化されたアプリケーションをAWS上で簡単にデプロイ、管理、スケールできるように設計されています。

ECSの主な特徴とメリット:

  • 高い可用性とスケーラビリティ: 複数のアベイラビリティーゾーンにまたがってコンテナを分散配置し、自動スケーリング機能により、アプリケーションの負荷変動に柔軟に対応できます。
  • シンプルな操作性: AWSマネジメントコンソール、CLI、API、SDK、Terraformなど、多様なインターフェースから操作でき、直感的な操作が可能です。
  • 幅広いコンピューティングオプション: EC2インスタンス (EC2起動タイプ) またはサーバーレスコンピューティング (Fargate起動タイプ) を選択でき、ワークロードの特性に合わせて最適な環境を選択できます。
  • AWSサービスとの統合: VPC、ELB (Elastic Load Balancing)、IAM (Identity and Access Management)、CloudWatch、CloudTrail、X-Rayなど、AWSの様々なサービスとシームレスに連携し、高度なシステム構築が可能です。
  • 高いセキュリティ: IAMロールによるアクセス制御、VPCによるネットワーク分離、コンテナイメージスキャンなど、多層的なセキュリティ対策を講じることが可能です。
  • コスト効率: EC2起動タイプでは、EC2インスタンスの柔軟な料金体系 (オンデマンド、リザーブド、スポット) を活用でき、Fargate起動タイプでは、実際に使用したコンピューティングリソースに対してのみ料金が発生するため、コスト効率に優れています。

ECSの主要コンポーネント:

  • クラスター (Cluster): ECSサービスを実行するための論理的なグループです。EC2インスタンスまたはFargateキャパシティプロバイダーの集合体として機能します。
  • タスク定義 (Task Definition): コンテナの実行に必要な設定 (コンテナイメージ、ポートマッピング、リソース要件、環境変数、ボリュームマウントなど) を定義するブループリントです。
  • タスク (Task): タスク定義に基づいて実際に起動されるコンテナのインスタンスです。クラスター内のコンテナインスタンスまたはFargate上で実行されます。
  • サービス (Service): 指定された数のタスク定義のインスタンスを常に実行し続けることを保証するコンポーネントです。ロードバランサーとの連携や自動スケーリング設定もサービスを通じて行います。
  • コンテナインスタンス (Container Instance): EC2起動タイプの場合、ECSエージェントがインストールされたEC2インスタンスを指します。タスクはこのコンテナインスタンス上で実行されます。
  • ECR (Elastic Container Registry): Dockerイメージをプライベートに保管、管理するためのフルマネージドなコンテナレジストリサービスです。ECSタスク定義でECRのイメージを指定することで、コンテナイメージをデプロイできます。
  • ロードバランサー (Load Balancer): 複数のタスクにトラフィックを分散するためのコンポーネントです。Application Load Balancer (ALB)、Network Load Balancer (NLB)、Classic Load Balancer (CLB) をECSサービスと連携できます。
  • Auto Scaling (Auto Scaling): CPU使用率、メモリ使用率、リクエスト数などのメトリクスに基づいて、タスク数やコンテナインスタンス数を自動的に増減させる機能です。

ECSの動作原理:

  1. ユーザーはタスク定義を作成し、コンテナイメージ、リソース要件、ネットワーク設定などを定義します。
  2. サービスを作成し、実行したいタスク定義、Desired tasks (希望するタスク数)、デプロイ設定などを指定します。
  3. ECSスケジューラーが、クラスター内の利用可能なリソース (EC2インスタンスまたはFargate) を確認し、タスクを配置します。
  4. ECSエージェントがコンテナインスタンス上でタスクを起動し、コンテナを実行します。
  5. サービスはDesired tasksを維持するようにタスクを監視し、異常終了したタスクがあれば自動的に再起動します。
  6. ロードバランサーと連携している場合、サービスはタスクをロードバランサーに登録し、外部からのトラフィックをタスクに分散します。
  7. Auto Scalingが設定されている場合、メトリクスに基づいてタスク数やコンテナインスタンス数が自動的に調整されます。

ECS起動タイプ:

  • EC2起動タイプ:

    • ユーザーが管理するEC2インスタンス上でコンテナを実行します。
    • EC2インスタンスのOS、Dockerランタイム、ECSエージェントの管理が必要です。
    • インスタンスタイプ、Auto Scaling設定、ネットワーク設定などを柔軟にカスタマイズできます。
    • Fargateよりもコスト効率が高い場合があります (特に常時稼働のワークロード)。
    • コンテナインスタンスの管理、パッチ適用などの運用負荷が発生します。
  • Fargate起動タイプ:

    • サーバーレスコンピューティング環境でコンテナを実行します。
    • サーバー (EC2インスタンス) の管理が不要で、インフラストラクチャの運用負荷を大幅に削減できます。
    • タスクごとにCPU、メモリを細かく指定でき、リソース効率に優れています。
    • EC2起動タイプよりもコストが高くなる場合があります (特に常時稼働のワークロード)。
    • EC2起動タイプに比べてカスタマイズの自由度が低い場合があります。

ECS Anywhere:

  • オンプレミス環境や他のクラウド環境など、AWS外のインフラストラクチャでECSタスクを実行できる機能です。
  • ハイブリッドクラウド環境やエッジコンピューティング環境でのコンテナ実行を可能にします。
  • AWSのマネージドサービスとしてのECSの利点を、AWS外の環境でも享受できます。

ECS Exec:

  • ECSタスク内で実行中のコンテナにインタラクティブにアクセスできる機能です。
  • デバッグ、トラブルシューティング、コンテナ内でのコマンド実行などに役立ちます。
  • セキュリティが強化されており、IAMロールによるアクセス制御が可能です。

【実務レベルの内容と重要事項】

実務におけるECS設計・構築・運用で重要な事項:

  • ネットワーク設計:
    • VPC、サブネット、セキュリティグループ、NACL (Network ACL) を適切に設計し、コンテナ間の通信、外部からのアクセス制御、セキュリティを確保します。
    • パブリックサブネット/プライベートサブネットの使い分け、NAT Gateway/NAT Instanceの構成、VPCエンドポイントの利用などを検討します。
  • IAMロール設計:
    • ECSタスク、ECSエージェント、EC2インスタンスに適切なIAMロールを付与し、最小権限の原則に基づいてアクセス権限を制御します。
    • コンテナがAWSリソース (S3、DynamoDBなど) にアクセスする場合、IAMロールを通じて安全にアクセス権限を付与します。
  • セキュリティ対策:
    • コンテナイメージの脆弱性スキャン (Amazon Inspectorなど) を実施し、セキュリティリスクを低減します。
    • コンテナレジストリ (ECR) のアクセス制御 (IAMポリシー) を適切に設定し、不正アクセスを防止します。
    • シークレット管理 (AWS Secrets Manager、AWS Systems Manager Parameter Storeなど) を利用し、機密情報を安全に管理します。
    • セキュリティグループ、NACLで不要なポートを閉じ、ネットワークセキュリティを強化します。
  • モニタリングとロギング:
    • Amazon CloudWatch Logsでコンテナのログを収集・監視し、アプリケーションの異常やエラーを早期に検知します。
    • Amazon CloudWatch MetricsでECSクラスター、サービス、タスクのメトリクスを監視し、パフォーマンスボトルネックやリソース使用状況を把握します。
    • AWS X-Rayで分散トレーシングを有効化し、マイクロサービス間のリクエスト追跡、パフォーマンス分析を行います。
  • デプロイ戦略:
    • ローリングアップデート、ブルー/グリーンデプロイメント、Canaryデプロイメントなど、適切なデプロイ戦略を選択し、アプリケーションのダウンタイムを最小限に抑えます。
    • ECS Service Auto Scalingと連携し、デプロイ中の負荷変動に対応できるようにします。
  • Auto Scaling設計:
    • CPU使用率、メモリ使用率、リクエスト数、カスタムメトリクスなど、適切なAuto Scalingポリシーを設定し、負荷変動に追従して自動的にスケールイン/スケールアウトするようにします。
    • スケールイン/スケールアウトの閾値、クールダウン期間などを適切に設定し、過剰なスケーリングやリソース不足を防止します。
  • ステートフルアプリケーションの考慮:
    • ステートフルアプリケーション (データベースなど) をECSで実行する場合、データ永続化のために、EBSボリューム、EFS、外部データベースサービス (RDS、Auroraなど) を利用します。
    • コンテナの再起動やスケールアウト時のデータ整合性を考慮した設計が必要です。
  • コスト最適化:
    • ワークロードの特性に合わせてEC2起動タイプとFargate起動タイプを使い分けます。
    • EC2起動タイプの場合、リザーブドインスタンス、スポットインスタンスの利用を検討します。
    • Auto Scalingでリソース使用率を最適化し、不要なリソースを削減します。
    • コンテナイメージサイズを最適化し、ECRストレージコストを削減します。
  • トラブルシューティング:
    • ECSコンソール、CloudWatch Logs、CloudWatch Metrics、ECS Execなどを活用し、問題発生時の原因特定、切り分け、解決を行います。
    • ECSエージェントログ、Dockerログ、アプリケーションログなどを確認し、エラーメッセージやスタックトレースを分析します。
    • ECSイベントログを確認し、クラスター、サービス、タスクのイベント履歴を把握します。
  • ECS Execの活用:
    • 本番環境での緊急時のトラブルシューティング、デバッグ作業にECS Execを活用できます。
    • セキュリティに配慮し、ECS Execのアクセス権限を適切に管理します。

重要事項まとめ:

  • セキュリティ: IAMロール、ネットワーク設計、コンテナイメージスキャン、シークレット管理
  • モニタリング: CloudWatch Logs, CloudWatch Metrics, X-Ray
  • 可用性: 複数AZ構成、Auto Scaling、デプロイ戦略
  • パフォーマンス: リソース最適化、Auto Scaling、ネットワーク設計
  • コスト: 起動タイプ選定、リザーブド/スポットインスタンス、Auto Scaling
  • 運用性: デプロイ自動化、モニタリング、トラブルシューティング

【実務でどの程度使用されるのか】

ECSは実務で非常に広く使用されています。コンテナ技術の普及とともに、ECSの利用も増加傾向にあります。

実務でのECS利用状況:

  • 業界: Webサービス、モバイルアプリ、APIサーバー、マイクロサービス、バッチ処理、機械学習、IoTなど、幅広い業界で利用されています。
  • 企業規模: スタートアップから大企業まで、様々な規模の企業で採用されています。
  • 利用シーン:
    • Webアプリケーション: Webサイト、Webアプリケーションのデプロイ、スケーリング、運用に利用されます。
    • APIサーバー: REST API、GraphQL APIなどのバックエンドAPIサーバーのデプロイ、スケーリング、運用に利用されます。
    • マイクロサービス: マイクロサービスアーキテクチャを採用したシステムのコンテナオーケストレーション基盤として利用されます。
    • バッチ処理: 定期的または非定期的に実行されるバッチ処理ジョブの実行基盤として利用されます。
    • 機械学習: 機械学習モデルのトレーニング、推論処理の実行基盤として利用されます。
    • CI/CD: 継続的インテグレーション/継続的デリバリー (CI/CD) パイプラインの一部として、コンテナイメージのビルド、テスト、デプロイを自動化するために利用されます。

ECSが選ばれる理由:

  • AWSとの統合: AWSの他のサービスとの連携が容易で、AWS環境全体でのシステム構築がスムーズに行えます。
  • Fargateの簡便さ: サーバーレスでコンテナを実行できるFargateは、インフラ運用負荷を大幅に削減したい場合に有効です。
  • 運用負荷の低さ: フルマネージドサービスであり、コントロールプレーンの運用はAWSに任せることができます。
  • 高いパフォーマンスとスケーラビリティ: 大規模なワークロードにも対応できる高いパフォーマンスとスケーラビリティを備えています。
  • コスト効率: ワークロードに合わせた起動タイプ選択、Auto Scaling、リザーブド/スポットインスタンスなどを活用することで、コストを最適化できます。

ECSの競合サービス:

  • Kubernetes (EKS, GKE, AKSなど): コンテナオーケストレーションのデファクトスタンダードであり、機能豊富で柔軟性が高いですが、ECSよりも運用負荷が高い場合があります。
  • AWS App Runner: よりシンプルなコンテナデプロイサービスで、ECSよりもさらに手軽にコンテナアプリケーションをデプロイできますが、カスタマイズ性はECSよりも低い場合があります。

実務でのECS利用頻度:

コンテナ技術を採用している企業では、ECSまたはKubernetes (EKS) のいずれかがコンテナオーケストレーション基盤として採用されているケースが多く、ECSはKubernetesと並んで、実務で頻繁に使用されるコンテナオーケストレーションサービスと言えます。特にAWS環境に特化してコンテナ基盤を構築したい場合や、Fargateを活用したい場合には、ECSが有力な選択肢となります。

【terraformのコードで記述する場合の基本的内容と実務レベルの内容】

terraformのコードで記述する場合の基本的内容:

ECSをTerraformで記述する場合、主に以下のリソースを定義します。

  • aws_ecs_cluster: ECSクラスターを作成します。
  • aws_ecs_task_definition: ECSタスク定義を作成します。
  • aws_ecs_service: ECSサービスを作成します。

基本的なTerraformコード例:

resource "aws_ecs_cluster" "example" {
  name = "example-cluster"
}

resource "aws_ecs_task_definition" "example" {
  family               = "example-task-definition"
  container_definitions = jsonencode([
    {
      name      = "example-container"
      image     = "nginx:latest"
      portMappings = [
        {
          containerPort = 80
          hostPort      = 80
        }
      ]
    }
  ])
  requires_compatibilities = ["EC2"] # または ["FARGATE"]
  network_mode             = "awsvpc"  # Fargateの場合は必須
  cpu                      = 256       # Fargateの場合に指定
  memory                   = 512       # Fargateの場合に指定
}

resource "aws_ecs_service" "example" {
  name            = "example-service"
  cluster         = aws_ecs_cluster.example.id
  task_definition = aws_ecs_task_definition.example.arn
  desired_count   = 1
  launch_type     = "EC2" # または "FARGATE"

  load_balancer { # ロードバランサー連携 (ALBの場合)
    target_group_arn = aws_lb_target_group.example.arn
    container_name   = "example-container"
    container_port   = 80
  }

  deployment_controller {
    type = "ECS"
  }
}

実務レベルのTerraformコード:

実務レベルでは、上記の基本的なリソース定義に加えて、以下の要素を考慮する必要があります。

  • モジュール化:
    • ECSクラスター、タスク定義、サービスなどをモジュール化し、再利用性と可読性を高めます。
    • 環境 (dev, stg, prod) やアプリケーションごとにモジュールを分割し、管理を容易にします。
  • 変数の活用:
    • 環境変数、イメージタグ、リソース数、AZ指定などを変数化し、設定の柔軟性と再利用性を高めます。
    • terraform.tfvars ファイルや環境変数で値を外部から注入できるようにします。
  • backend設定:
    • Terraform stateをS3 backendなどに保存し、チームでのstate共有、ロック、バージョン管理を可能にします。
    • DynamoDBでstateロックを設定し、stateの競合を防ぎます。
  • プロビジョニング:
    • null_resourcelocal-exec プロビジョナーを利用して、Dockerイメージのビルド、ECRへのプッシュ、タスク定義の更新などを自動化します。
    • CI/CDパイプラインとの連携を考慮した設計にします。
  • 依存関係の管理:
    • depends_on を利用して、VPC、サブネット、セキュリティグループ、ロードバランサーなどの依存リソースの作成順序を制御します。
    • terraform_remote_state を利用して、VPCなどの共通基盤リソースのstateを参照し、構成を連携させます。
  • セキュリティ:
    • IAMロール定義 (aws_iam_role, aws_iam_policy, aws_iam_policy_attachment) を含め、ECSタスク、ECSエージェントに必要な最小限の権限を付与します。
    • シークレット管理 (AWS Secrets Manager, Parameter Store) と連携し、機密情報をTerraformコードにハードコードしないようにします。
  • モニタリング:
    • CloudWatch Logs Group (aws_cloudwatch_log_group) を定義し、コンテナログの保存設定を行います。
    • CloudWatchメトリクスアラーム (aws_cloudwatch_metric_alarm) を定義し、異常検知、通知設定を行います。
  • Auto Scaling:
    • aws_appautoscaling_policyaws_appautoscaling_target を利用して、ECSサービスAuto Scaling設定を定義します。
    • CPU使用率、メモリ使用率、ALBリクエスト数などのメトリクスに基づいたスケーリングポリシーを設定します。

実務レベルのTerraformコード例 (モジュール化、変数、backend設定、IAMロール):

# backend.tf
terraform {
  backend "s3" {
    bucket         = "your-terraform-state-bucket"
    key            = "ecs/example/terraform.tfstate"
    region         = "ap-northeast-1"
    dynamodb_table = "terraform-state-lock"
  }
}

# variables.tf
variable "cluster_name" {
  type    = string
  default = "example-cluster"
}

variable "container_image" {
  type    = string
  default = "nginx:latest"
}

# modules/ecs_cluster/main.tf
resource "aws_ecs_cluster" "main" {
  name = var.cluster_name
}

# modules/ecs_task_definition/main.tf
resource "aws_ecs_task_definition" "main" {
  family               = "${var.cluster_name}-task-definition"
  container_definitions = jsonencode([
    {
      name      = "example-container"
      image     = var.container_image
      portMappings = [
        {
          containerPort = 80
          hostPort      = 80
        }
      ]
    }
  ])
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = 256
  memory                   = 512

  execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
  task_role_arn      = aws_iam_role.ecs_task_role.arn
}

# modules/ecs_service/main.tf
resource "aws_ecs_service" "main" {
  name            = "${var.cluster_name}-service"
  cluster         = var.ecs_cluster_id
  task_definition = var.ecs_task_definition_arn
  desired_count   = var.desired_count
  launch_type     = "FARGATE"
  network_configuration {
    subnets          = var.subnet_ids
    security_groups = var.security_group_ids
  }

  deployment_controller {
    type = "ECS"
  }
}

# modules/iam_roles/main.tf
resource "aws_iam_role" "ecs_task_execution_role" {
  name               = "${var.cluster_name}-ecs-task-execution-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Principal = {
          Service = "ecs-tasks.amazonaws.com"
        }
        Effect = "Allow"
      },
    ]
  })
}

resource "aws_iam_policy_attachment" "ecs_task_execution_role_policy_attachment" {
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
  roles      = [aws_iam_role.ecs_task_execution_role.name]
}

resource "aws_iam_role" "ecs_task_role" {
  name               = "${var.cluster_name}-ecs-task-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Principal = {
          Service = "ecs-tasks.amazonaws.com"
        }
        Effect = "Allow"
      },
    ]
  })
}

# main.tf (root module)
module "ecs_cluster" {
  source       = "./modules/ecs_cluster"
  cluster_name = var.cluster_name
}

module "ecs_task_definition" {
  source          = "./modules/ecs_task_definition"
  cluster_name    = var.cluster_name
  container_image = var.container_image

  depends_on = [module.iam_roles]
}

module "ecs_service" {
  source                = "./modules/ecs_service"
  ecs_cluster_id          = module.ecs_cluster.ecs_cluster_id
  ecs_task_definition_arn = module.ecs_task_definition.ecs_task_definition_arn
  desired_count         = 1
  subnet_ids            = ["subnet-xxxxxxxxxxxxxxxxx", "subnet-yyyyyyyyyyyyyyyyy"] # 例: VPCのサブネットID
  security_group_ids      = ["sg-zzzzzzzzzzzzzzzzzz"] # 例: セキュリティグループID

  depends_on = [module.ecs_task_definition]
}

module "iam_roles" {
  source       = "./modules/iam_roles"
  cluster_name = var.cluster_name
}

output "ecs_cluster_name" {
  value = module.ecs_cluster.ecs_cluster_name
}

output "ecs_service_name" {
  value = module.ecs_service.ecs_service_name
}

実務レベルのTerraformコードのポイント:

  • モジュール化: modules ディレクトリに各リソースタイプ (ecs_cluster, ecs_task_definition, ecs_service, iam_roles) のモジュールを作成し、main.tf (root module) から呼び出すことで、コードの構造化、再利用性、可読性を向上させています。
  • 変数: variables.tf で定義した変数 (cluster_name, container_image など) を利用し、設定値を外部から変更できるようにしています。
  • backend設定: backend.tf でS3 backendを設定し、Terraform stateをS3に保存するようにしています。
  • IAMロール: modules/iam_roles モジュールでECSタスク実行ロール (ecs_task_execution_role) と ECSタスクロール (ecs_task_role) を定義し、必要なIAMポリシーをアタッチしています。
  • 依存関係: depends_on を利用して、モジュール間の依存関係を明示的に記述し、リソース作成順序を制御しています。

上記はあくまで一例であり、実際の環境に合わせて、VPC、サブネット、セキュリティグループ、ロードバランサー、Auto Scalingなどのリソース定義や設定を追加する必要があります。また、CI/CDパイプラインとの連携、環境ごとの設定管理、テストコードの追加なども実務レベルでは重要な要素となります。

Discussion