kubernetes

Setting Up Multi-Architecture and Workload-Specific Node Pools with Karpenter

클라우드 환경에서 모든 워크로드를 하나의 노드풀에 몰아넣는 방식은 단순하지만, 효율적인 운영에는 한계가 있습니다. 워크로드의 특성이나 CPU 아키텍처에 따라 요구사항이 달라지기 때문에, 이를 고려한 세밀한 자원 관리가 필요합니다. 이번 글에서는 Karpenter의 requirements라벨/테인트 기능을 활용하여 멀티 아키텍처(amd64 + arm64)를 지원하고, 동시에 특정 워크로드 전용 풀을 구성하는 방법을 알아봅시다.


1. 멀티 아키텍처란?

멀티 아키텍처란 서로 다른 CPU 아키텍처를 가진 노드를 하나의 Kubernetes 클러스터 내에서 함께 운영하는 것을 의미합니다. 오늘날 클라우드 환경에서는 크게 두 가지 아키텍처가 자주 사용됩니다.

  • amd64(x86-64): 흔히 x86-64 또는 AMD64라고 불리는 아키텍처는 오늘날 가장 널리 사용되는 범용 서버 및 PC용 CPU 구조입니다. 원래 인텔의 x86 아키텍처를 기반으로, AMD가 이를 64비트로 확장하면서 AMD64라는 이름이 붙었고, 현재는 인텔과 AMD 모두 이 아키텍처를 지원합니다. 서버, 데스크톱, 노트북 등 대부분의 범용 컴퓨팅 환경에서 사용되고 있으며, 소프트웨어와 운영체제의 호환성이 가장 뛰어납니다. 따라서 대부분의 애플리케이션과 라이브러리가 기본적으로 amd64 환경을 지원합니다. 전통적인 선택지이자 안정성이 검증된 아키텍처라고 볼 수 있습니다.
  • arm64: ARM 아키텍처의 64비트 확장 버전으로, 모바일 기기에서 먼저 주목받다가 최근에는 서버와 클라우드 환경으로 빠르게 확산되고 있습니다. 가장 큰 특징은 높은 전력 효율성으로, 동일한 성능을 내면서도 전력 소비가 적기 때문에 대규모 데이터센터에서 비용 절감 효과가 큽니다. AWS의 Graviton 시리즈 인스턴스(예: Graviton2, Graviton3)처럼 클라우드 서비스 제공업체들이 자체 개발한 ARM 기반 CPU를 제공하면서, 점점 더 많은 워크로드가 arm64 아키텍처로 옮겨가고 있습니다. 최근에는 대규모 병렬 처리, 마이크로서비스 환경, 그리고 클라우드 네이티브 애플리케이션에서 arm64가 빠르게 자리 잡고 있습니다.

Kubernetes는 여러 CPU 아키텍처(예: amd64, arm64)를 혼합해 운영할 수 있습니다. Pod 스케줄러는 컨테이너 이미지가 해당 아키텍처에서 실행 가능한지 자동으로 확인하고, 조건에 맞는 노드에 배치합니다.

하지만 여기에는 중요한 전제가 하나 있습니다. 바로 컨테이너 이미지 자체도 멀티 아키텍처를 지원해야 한다는 점입니다. 예를 들어 nginx의 공식 이미지는 amd64와 arm64 모두 지원하므로 어느 노드에서든 실행할 수 있습니다. 반면 일부 오픈소스나 사내 빌드 이미지는 amd64 전용으로만 만들어져 있어 arm64 노드에서는 실행이 불가능합니다. 따라서 운영자가 멀티 아키텍처 환경을 도입하려면 이미지 단계에서부터 아키텍처 지원 여부를 반드시 확인해야 합니다.

멀티 아키텍처를 운영하는 이유는 단순히 “여러 CPU를 함께 쓰자”는 데 있지 않습니다. 아키텍처를 혼합하면 비용 최적화와 성능 최적화를 동시에 이룰 수 있습니다. 예를 들어 ARM 기반 노드는 동일 성능 대비 비용이 저렴하기 때문에 배치성 작업이나 비용에 민감한 워크로드에 적합합니다. 반면 기존 x86 환경에 최적화된 애플리케이션은 amd64 노드에서 안정적으로 운영할 수 있습니다. 또한 머신러닝이나 데이터 처리처럼 특정 연산에서 ARM이 더 효율적인 경우에는 ARM 노드를 전용으로 활용할 수도 있습니다.

결국 멀티 아키텍처 환경을 지원한다는 것은 운영자가 비용 효율성과 운영 유연성을 동시에 확보할 수 있다는 의미입니다.


2. 구성도

멀티 아키텍처 클러스터를 운영한다고 해서 모든 노드를 똑같이 사용하는 것은 아닙니다. 실제로는 **일반 풀(General Pool)**과 **전용 풀(Dedicated Pool)**을 구분하여 병행 운영하는 것이 효율적입니다.

일반 풀은 클러스터의 기본 역할을 하는 노드 그룹으로, 웹 서비스나 API 서버처럼 항상 가용성이 필요한 워크로드를 배치하는 데 사용됩니다. 이 풀은 amd64와 arm64를 동시에 지원하도록 설정해 두면, 스케줄러가 상황에 따라 적절한 아키텍처를 선택해 Pod를 배치할 수 있습니다. 즉, 애플리케이션이 멀티 아키텍처 이미지를 지원한다면 운영자는 어떤 아키텍처에서 실행될지 신경 쓰지 않고도 안정적으로 서비스할 수 있습니다.

반면, 전용 풀은 특정 성격의 워크로드를 위해 따로 마련한 노드 그룹입니다. 예를 들어 **배치 작업(Job)**이나 백그라운드 데이터 처리 같은 경우, 고가용성보다는 비용 효율성이 더 중요한 경우가 많습니다. 이런 워크로드는 중간에 노드가 회수되더라도(Spot 인스턴스 특성) 다시 재실행하면 되므로, Spot 인스턴스 + Taint를 활용한 전용 풀을 구성하는 것이 효과적입니다. 이를 통해 중요한 서비스는 안정적인 일반 풀에서, 비용을 아끼고 싶은 작업성 워크로드는 전용 풀에서 각각 분리해 운영할 수 있습니다. 아래 그림은 이런 구조를 간단히 나타낸 것입니다.

       ┌──────────────────────────────┐
       │  공통 NodeClass (default)     │
       └──────────────┬───────────────┘
                      │
          ┌───────────┴─────────────┐
          │                         │
       멀티 아키텍처 일반 풀      Spot + Taint 배치 전용 풀

즉, 하나의 NodeClass를 기반으로 여러 개의 NodePool을 만들어, 운영 목적에 따라 다른 라벨과 테인트를 부여하는 구조입니다. 이를 통해 운영자는 단순히 노드를 늘리는 차원을 넘어, 아키텍처와 워크로드 특성에 맞게 클러스터를 세분화하여 최적화할 수 있습니다.


3. YAML 예시

멀티 아키텍처 + 워크로드 분리 구조를 실제로 적용하려면, Karpenter가 노드를 생성할 때 참고할 NodeClassNodePool을 정의해야 합니다. NodeClass는 공통 인프라 설정을 담는 역할을 하고, NodePool은 실제 어떤 아키텍처/워크로드 용도로 노드를 운영할지를 구체화합니다.

3.1 공통 NodeClass

NodeClass는 말 그대로 “공통 템플릿” 역할을 합니다. AMI, IAM Role, 서브넷, 보안 그룹 같은 인프라 리소스 설정을 여기에 정의해 두면, 여러 NodePool에서 이 NodeClass를 공유할 수 있습니다. 즉, 인프라 관련 공통 설정은 NodeClass에서 관리하고, 운영 목적에 따른 세부 정책은 NodePool에서 나눠 정의하는 구조입니다.

예시에서는 default라는 이름의 NodeClass를 만들고, Karpenter가 새로운 노드를 띄울 때 사용할 AMI 패밀리(AL2023), IAM Role, VPC 서브넷, 보안 그룹을 지정합니다.

apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
  name: default
spec:
  amiFamily: AL2023
  role: "KarpenterNodeRole-<YOUR-CLUSTER-NAME>" # 변경 필요
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: "<YOUR-CLUSTER-NAME>"
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: "<YOUR-CLUSTER-NAME>"

3.2 NodePool - 일반 멀티 아키텍처

다음으로, 실제 워크로드를 수용할 첫 번째 NodePool을 정의합니다. 이 풀은 general-multiarch라는 이름을 가지고 있으며, requirements를 통해 amd64와 arm64 아키텍처 모두 지원하도록 설정되어 있습니다.

여기에 추가로 on-demand와 spot 두 가지 용량 타입을 모두 허용하고, EC2 인스턴스 타입은 범용(t), 메모리 최적화(m), 컴퓨팅 최적화(c) 계열을 사용하도록 제한합니다. 이렇게 하면 스케줄러는 상황에 맞게 적절한 아키텍처/용량 타입을 선택해 노드를 생성하고, 워크로드를 유연하게 배치할 수 있습니다.

즉, 이 일반 풀은 서비스 운영의 기본 뼈대 역할을 하며, 다양한 아키텍처에서 동작할 수 있는 안정적인 풀이라고 볼 수 있습니다.

apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: general-multiarch
spec:
  template:
    metadata:
      labels:
        nodeclass: general
        arch: multi
    spec:
      nodeClassRef:
        name: default
      requirements:
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64", "arm64"]
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["on-demand", "spot"]
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ["t", "m", "c"]
  disruption:
    consolidationPolicy: WhenUnderutilized
    consolidateAfter: 30s

3.3 NodePool - Spot 전용 + Taint 배치 전용

두 번째 NodePool은 batch-spot이라는 이름을 가진 전용 풀입니다. 이 풀의 특징은 두 가지입니다.

  • Spot 전용: requirements에서 capacity-type을 spot으로만 지정했기 때문에, Karpenter는 오직 Spot 인스턴스만 사용할 수 있습니다. 이는 곧 비용 절감에 초점이 맞춰진 풀임을 의미합니다.
  • Taint 적용: workload=batch라는 테인트를 걸어 두었기 때문에, 기본적으로는 어떤 Pod도 이 노드에 스케줄링될 수 없습니다. 대신, 해당 테인트를 toleration으로 허용한 Pod만 이 노드에 올라갑니다. 예를 들어, ARM 기반 노드에 배치성 작업(batch jobs)만 올리고 싶다면, ARM 노드에 workload=batch 테인트를 걸어두고 batch 전용 Pod에만 toleration을 설정합니다. 이렇게 하면 다른 서비스용 Pod가 실수로 ARM 노드에 배치되지 않아서 안정적인 운영이 가능합니다.

이 풀은 “일반적인 서비스 워크로드는 절대 들어올 수 없고, 오직 배치성 워크로드만 들어올 수 있는 Spot 전용 풀”로 기능합니다. 운영자는 이를 통해 중요한 서비스와 실험적/비용 절감형 워크로드를 철저하게 분리할 수 있습니다.

apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: batch-spot
spec:
  template:
    metadata:
      labels:
        nodeclass: batch
        capacity: spot
        workload: batch
    spec:
      nodeClassRef:
        name: default
      taints:
        - key: workload
          value: batch
          effect: NoSchedule
      requirements:
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64", "arm64"]
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot"]
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ["t", "m", "c"]
  disruption:
    consolidationPolicy: WhenUnderutilized
    consolidateAfter: 30s

3.4 배치 워크로드 Pod 예시

마지막으로, 실제 배치 워크로드(Job) 예시입니다. 여기서는 tolerations를 설정하여 workload=batch 테인트가 걸린 노드에도 스케줄링될 수 있도록 허용했습니다.

즉, 일반 풀에는 절대 배치되지 않고, 반드시 Spot 전용 풀에서만 실행됩니다. 예시에서는 단순히 busybox 이미지를 사용해 메시지를 출력하고 60초간 대기하는 Job을 정의했지만, 실제 운영 환경에서는 데이터 ETL, 로그 처리, 배치 리포트 생성 같은 작업을 여기에 배치할 수 있습니다.

apiVersion: batch/v1
kind: Job
metadata:
  name: batch-job
spec:
  template:
    spec:
      tolerations:
        - key: "workload"
          operator: "Equal"
          value: "batch"
          effect: "NoSchedule"
      containers:
        - name: busybox
          image: busybox
          command: ["sh", "-c", "echo Processing... && sleep 60"]
      restartPolicy: Never

4. 적용 방법

4-1. NodeClass 생성

kubectl apply -f ec2nodeclass.yaml

가장 먼저 공통 EC2NodeClass를 생성합니다. NodeClass에는 AMI, IAM Role, 네트워크 설정 등 클러스터 공통 인프라 정보가 들어가기 때문에, 이후에 생성할 NodePool들이 이를 참조하게 됩니다. IAM Role(KarpenterNodeRole-<YOUR-CLUSTER-NAME>)과 VPC 서브넷/보안 그룹 태그는 반드시 실제 클러스터 환경에 맞게 수정해야 합니다. 그렇지 않으면 Karpenter가 노드를 생성하지 못합니다.

4-2. 멀티 아키텍처 일반 풀 생성

kubectl apply -f general-multiarch.yaml

멀티 아키텍처(amd64/arm64)를 동시에 지원하는 NodePool을 생성합니다. 이 시점에는 노드가 자동으로 뜨지 않고, Pending Pod가 발생해야 노드가 프로비저닝됩니다. 멀티 아키텍처 환경에서는 Pod 이미지가 반드시 multi-arch 지원을 해야 합니다. 예를 들어 nginx 같은 공식 이미지는 문제 없지만, 자체 빌드 이미지가 x86 전용이라면 arm64 노드에서 실행되지 않습니다. 이런 경우 스케줄링 오류가 발생하므로 배포 전에 이미지 아키텍처를 확인해야 합니다.

4-3. Spot 전용 배치 풀 생성

kubectl apply -f batch-spot.yaml

이 풀은 Spot 인스턴스 기반이며, workload=batch 테인트가 걸려 있습니다. Spot 인스턴스는 언제든 회수될 수 있으므로, 반드시 중간 종료에 대비할 수 있는 워크로드만 배치해야 합니다. 예를 들어 데이터 처리 Job이나 캐시성 작업은 적합하지만, 실시간 트랜잭션 서비스에는 배치하면 안 됩니다.


5. 동작 확인

리소스를 모두 적용한 후에는 실제로 노드와 워크로드가 의도한 대로 배치되는지 확인하는 과정이 필요합니다.

5-1. 노드 생성 확인

  • NodeClass/NodePool만 만들면 노드는 생기지 않습니다.
  • 새 노드가 필요한 상황(예: Pod가 Pending 상태로 스케줄 불가)이 발생해야 Karpenter가 노드를 띄웁니다.
  • 일반 Pod를 생성하면 general-multiarch NodePool에서 노드가 올라옵니다.
  • 배치 Job을 생성하면 batch-spot NodePool에서 Spot 노드가 새로 생성됩니다.
kubectl get nodes -L karpenter.sh/nodepool

위 명령으로 확인하면 karpenter.sh/nodepool=general-multiarch 혹은 karpenter.sh/nodepool=batch-spot 라벨이 붙어 있어 어떤 풀에서 생성된 노드인지 쉽게 구분할 수 있습니다. 추가로 NodePool에서 spec.template.metadata.labels를 정의했다면 그 라벨도 함께 표시됩니다.

5-2. Taint/Toleration 확인

kubectl describe node <NODE_NAME>

배치 전용 풀의 노드를 확인하면 workload=batch:NoSchedule 테인트가 걸려 있는 것을 볼 수 있습니다. 만약 toleration을 갖지 않은 Pod가 이 노드에 스케줄링된다면 실패하므로, 워크로드 정의 시 toleration 설정이 제대로 들어갔는지 꼭 확인해야 합니다.

5-3. 멀티 아키텍처 이미지 실행 여부 확인

Pod가 정상적으로 배치되더라도 컨테이너가 CrashLoopBackOff 상태에 빠지는 경우가 있습니다. 이는 이미지가 해당 아키텍처(amd64/arm64)를 지원하지 않는 경우 발생합니다. 따라서 kubectl describe pod로 이벤트 메시지를 확인하고, 필요 시 multi-arch 이미지를 다시 빌드하거나 nodeSelector: kubernetes.io/arch=amd64 같은 제약 조건을 Pod에 추가해야 합니다.

정리

Karpenter 기반 멀티 아키텍처 및 워크로드 전용 노드풀 구성은 단순한 노드 확장이 아니라 운영 효율성과 비용 최적화를 동시에 달성하는 전략입니다. 공통 NodeClass로 인프라를 표준화하고, NodePool을 목적별로 구분해 라벨과 테인트를 적용하면 안정적인 서비스 워크로드와 비용 절감형 배치 워크로드를 명확히 분리해 운영할 수 있습니다.

이로써 운영자는 amd64/arm64 아키텍처를 유연하게 혼합 활용하면서도, 서비스에는 안정성을, 배치 작업에는 비용 효율성을 보장할 수 있습니다. 결국 이는 클라우드 네이티브 환경에서 리소스를 정교하게 관리하고 클러스터 운영 전략을 한 단계 고도화하는 핵심 방법론이라 할 수 있습니다.

참고자료