2026년 ECS vs EKS: 실제 운영 경험을 바탕한 솔직 비교

발행: (2026년 5월 23일 PM 11:19 GMT+9)
9 분 소요
원문: Dev.to

Source: Dev.to

마케팅 수식어는 없습니다. 제가 세 개의 다른 회사에서 5년 동안 운영하면서 배운 내용만을 전합니다.
이 대화를 몇 번이나 했는지 셀 수 없습니다.
새 팀에 합류한 사람이 인프라를 살펴보며 “왜 우리는 ECS를 쓰고 Kubernetes는 안 쓰나요?” 라고 묻거나, 반대로 EKS 클러스터를 보고 “왜 ECS를 안 쓰나요? 너무 복잡해 보이는데요.” 라고 묻는 경우가 있습니다.

두 질문 모두 타당합니다. 답도 존재합니다. 그리고 그 답은 블로그 포스트의 비교표가 포착할 수 없는 맥락에 전적으로 달려 있습니다. 그래서 대부분의 ECS vs EKS 비교는 쓸모가 없습니다. 기능을 나란히 나열하고 “사용 사례에 따라 다릅니다” 라는 결론을 내리는데, 이는 전혀 도움이 되지 않죠.

저는 다른 방식을 시도하고 싶습니다. 두 플랫폼을 실제로 운영하면서 어떤 느낌이었는지, 무엇이 잘못될 수 있는지, 비용은 실제로 어떻게 나오는지, 그리고 새로운 서비스를 시작할 때 어떤 신호를 보고 결정을 내리는지를 알려드리겠습니다.

제가 겪어온 현장

회사 1 – 중간 규모 SaaS 스타트업, 약 40명의 엔지니어. 2020년부터 ECS를 사용했으며 제가 떠날 때까지 그대로 유지했습니다. Fargate와 EC2 런치 타입을 혼합한 35개의 서비스가 있었고, CodePipeline을 통해 배포했습니다. ECS 때문에 직접적인 대규모 사고는 없었고, 온콜도 감당할 수 있었습니다.

회사 2 – 레거시 모놀리스를 마이크로서비스로 전환 중인 대기업. “요즘 다들 쓰니까” 라는 이유로 EKS를 선택했습니다. 제가 합류했을 때 클러스터는 이미 18개월째 운영 중이었고, 클러스터 설정을 수정할 수 있는 엔지니어는 12명, 실제로 이해하고 있는 사람은 2명에 불과했습니다. 온콜 시에는 새벽 3시 “왜 이 파드가 CrashLoopBackOff 상태인가?” 라는 문의가 끊이질 않았습니다.

회사 3 – 데이터 중심 플랫폼, 약 80명의 엔지니어. 처음에는 ML 워크로드를 위해 EKS를 도입했고, 이후 API 레이어를 위해 ECS를 추가했습니다. 두 서비스는 현재까지도 병행 운영 중이며, 그 이유는 뒤에서 자세히 설명하겠습니다.

제가 여기서 한쪽이 더 낫다고 말하려는 것이 아니라, 두 시스템의 현실을 솔직히 전하려는 것입니다.

근본적인 차이점

네트워킹, 오토스케일링, 가격 등 구체적인 주제로 들어가기 전에 반드시 이해해야 할 근본적인 차이가 있습니다.

ECS관리형 컨테이너 오케스트레이터이고, EKS관리형 Kubernetes 컨트롤 플레인입니다.

이 차이는 사소해 보이지만, 실제 의미는 엄청납니다.

  • ECS에서는 스케줄링 로직, 서비스 재조정 루프, 로드밸런서 연동, 시크릿 주입, 배포 메커니즘 등을 AWS가 직접 운영합니다. 사용자는 이들을 설정만 하면 되고, 문제가 발생하면 태스크 정의, 서비스 이벤트, CloudWatch 로그를 확인하면 됩니다. 잘못될 수 있는 경우의 수가 제한적입니다.

  • EKS에서는 AWS가 Kubernetes API 서버, etcd, 스케줄러, 컨트롤러 매니저 등 컨트롤 플레인만 관리합니다. 워커 노드, CNI 플러그인, 인그레스 컨트롤러, cert‑manager, 서비스 메시, 클러스터 오토스케일러, 파드 디스럽션 예산, RBAC 정책 등은 모두 사용자가 직접 설정·운영·디버깅해야 합니다. 잘못될 수 있는 경우의 수는 사실상 무한에 가깝습니다.

이는 EKS에 대한 비판이 아니라, Kubernetes가 확장성을 제공하기 때문에 발생하는 운영 비용이 존재한다는 점을 강조하는 것입니다.

ECS 클러스터를 처음 구축하는 데 걸리는 시간

Terraform을 한 번만 실행하면, 로드밸런서 뒤에 서비스가 배포된 작동하는 ECS 클러스터를 몇 시간 안에 만들 수 있습니다. 전체 설정은 다음과 같습니다.

# ECS Cluster
resource "aws_ecs_cluster" "main" {
  name = "${var.project_name}-${var.environment}"

  setting {
    name  = "containerInsights"
    value = "enabled"
  }

  tags = {
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

resource "aws_ecs_cluster_capacity_providers" "main" {
  cluster_name       = aws_ecs_cluster.main.name
  capacity_providers = ["FARGATE", "FARGATE_SPOT"]

  default_capacity_provider_strategy {
    capacity_provider = "FARGATE"
    weight            = 100
    base              = 1
  }
}

# Task Definition
resource "aws_ecs_task_definition" "app" {
  family                   = "${var.project_name}-${var.environment}"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = 512
  memory                   = 1024
  execution_role_arn       = aws_iam_role.ecs_execution.arn
  task_role_arn            = aws_iam_role.ecs_task.arn

  container_definitions = jsonencode([{
    name      = "app"
    image     = "${var.ecr_repository_url}:${var.image_tag}"
    essential = true

    portMappings = [{
      containerPort = 8080
      protocol      = "tcp"
    }]

    environment = [
      { name = "APP_ENV", value = var.environment }
    ]

    secrets = [{
      name      = "DATABASE_URL"
      valueFrom = aws_secretsmanager_secret.db_url.arn
    }]

    logConfiguration = {
      logDriver = "awslogs"
      options = {
        "awslogs-group"         = aws_cloudwatch_log_group.app.name
        "awslogs-region"        = var.region
        "awslogs-stream-prefix" = "ecs"
      }
    }

    healthCheck = {
      command     = ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
      interval    = 30
      timeout     = 5
      retries     = 3
      startPeriod = 60
    }
  }])
}

# ECS Service
resource "aws_ecs_service" "app" {
  name            = "${var.project_name}-${var.environment}"
  cluster         = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.app.arn
  desired_count   = 2

  capacity_provider_strategy {
    capacity_provider = "FARGATE"
    weight            = 100
    base              = 1
  }

  network_configuration {
    subnets          = var.private_subnet_ids
    security_groups  = [aws_security_group.ecs_tasks.id]
    assign_public_ip = false
  }

  load_balancer {
    target_group_arn = aws_lb_target_group.app.arn
    container_name   = "app"
    container_port   = 8080
  }

  deployment_circuit_breaker {
    enable   = true
    rollback = true
  }

  deployment_maximum_percent         = 200
  deployment_minimum_healthy_percent = 100

  lifecycle {
    ignore_changes = [task_definition, desired_count]
  }
}

이게 전부입니다. 별도의 툴, CNI 설정, 인그레스 컨트롤러 설치가 필요하지 않으며, ALB 연동도 기본 제공됩니다. 시크릿은 IAM을 통해 Secrets Manager에서 직접 주입되고, 로그는 자동으로 CloudWatch에 전송됩니다.
경험이 어느 정도 있는 엔지니어라면 이 전체 흐름을 스스로 관리할 수 있습니다.

EKS 설정은 훨씬 더 복잡합니다

실제 운영 환경에서 EKS를 구축하려면 클러스터 자체 외에도 수많은 부가 요소를 직접 구성해야 합니다. 아래는 전형적인 프로덕션 EKS 설정 예시입니다.

# EKS Cluster
resource "aws_eks_cluster" "main" {
  name     = "${var.project_name}-${var.environment}"
  role_arn = aws_iam_role.eks_cluster.arn
  version  = "1.29"

  vpc_config {
    subnet_ids              = concat(var.private_subnet_ids, var.public_subnet_ids)
    endpoint_private_access = true
    endpoint_public_access  = true
    public_access_cidrs     = var.allowed_cidrs
  }

  enabled_cluster_log_types = [
    "api", "audit", "authenticator", "controllerManager", "scheduler"
  ]

  depends_on = [
    aws_iam_role_policy_attachment.eks_cluster_policy,
    aws_iam_role_policy_attachment.eks_vpc_resource_controller,
    aws_cloudwatch_log_group.eks,
  ]

  tags = {
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

# Node Group
resource "aws_eks_node_group" "main
0 조회
Back to Blog

관련 글

더 보기 »

내 스킬

프로젝트를 위한 AI 지시문을 만들고, 설치하고, 관리하세요 — 코딩이 필요 없습니다. CREATE 이름을 정하고, 카테고리를 선택하고, 원하는 것을 설명하세요 — 마법사가 자동으로 구성합니다.